-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Consider DBNull.Value and NullString.Value the same as $null #9794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
54ff36c
6eca8a4
a6d2c04
5db70bc
475529f
2561a4a
cd2d8d0
455cec0
ecccb08
805585d
c305f67
96d906d
242c371
aa09a21
99b50f5
fe6e110
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -591,9 +591,7 @@ public static PSDataCollection<PSObject> GetPSDataCollection(object inputValue) | |
| /// <param name="second">Object to compare first to.</param> | ||
vexx32 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// <returns>True if first is equal to the second.</returns> | ||
| public static new bool Equals(object first, object second) | ||
| { | ||
| return Equals(first, second, false, CultureInfo.InvariantCulture); | ||
| } | ||
| => Equals(first, second, false, CultureInfo.InvariantCulture); | ||
|
|
||
| /// <summary> | ||
| /// Used to compare two objects for equality converting the second to the type of the first, if required. | ||
|
|
@@ -604,9 +602,7 @@ public static PSDataCollection<PSObject> GetPSDataCollection(object inputValue) | |
| /// to specify the type of string comparison </param> | ||
| /// <returns>True if first is equal to the second.</returns> | ||
| public static bool Equals(object first, object second, bool ignoreCase) | ||
| { | ||
| return Equals(first, second, ignoreCase, CultureInfo.InvariantCulture); | ||
| } | ||
| => Equals(first, second, ignoreCase, CultureInfo.InvariantCulture); | ||
|
|
||
| /// <summary> | ||
| /// Used to compare two objects for equality converting the second to the type of the first, if required. | ||
|
|
@@ -644,25 +640,28 @@ public static bool Equals(object first, object second, bool ignoreCase, IFormatP | |
|
|
||
| if (first == null) | ||
| { | ||
| if (second == null) return true; | ||
| return false; | ||
| return IsNullLike(second); | ||
| } | ||
|
|
||
| if (second == null) | ||
| { | ||
| return false; // first is not null | ||
| return IsNullLike(first); | ||
| } | ||
|
|
||
| string firstString = first as string; | ||
| string secondString; | ||
| if (firstString != null) | ||
| if (first is string firstString) | ||
| { | ||
| secondString = second as string ?? (string)LanguagePrimitives.ConvertTo(second, typeof(string), culture); | ||
| return (culture.CompareInfo.Compare(firstString, secondString, | ||
| ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None) == 0); | ||
| return culture.CompareInfo.Compare( | ||
| firstString, | ||
| secondString, | ||
| ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None) == 0; | ||
| } | ||
|
|
||
| if (first.Equals(second)) return true; | ||
| if (first.Equals(second)) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| Type firstType = first.GetType(); | ||
| Type secondType = second.GetType(); | ||
|
|
@@ -706,24 +705,24 @@ public static bool Equals(object first, object second, bool ignoreCase, IFormatP | |
| /// Helper method for [Try]Compare to determine object ordering with null. | ||
| /// </summary> | ||
| /// <param name="value">The numeric value to compare to null.</param> | ||
| /// <param name="numberIsRightHandSide">True if the number to compare is on the right hand side if the comparison.</param> | ||
| /// <param name="numberIsRightHandSide">True if the number to compare is on the right hand side in the comparison.</param> | ||
| private static int CompareObjectToNull(object value, bool numberIsRightHandSide) | ||
| { | ||
| var i = numberIsRightHandSide ? -1 : 1; | ||
|
|
||
vexx32 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // If it's a positive number, including 0, it's greater than null | ||
| // for everything else it's less than zero... | ||
| switch (value) | ||
| { | ||
| case Int16 i16: return Math.Sign(i16) < 0 ? -i : i; | ||
| case Int32 i32: return Math.Sign(i32) < 0 ? -i : i; | ||
| case Int64 i64: return Math.Sign(i64) < 0 ? -i : i; | ||
| case sbyte sby: return Math.Sign(sby) < 0 ? -i : i; | ||
| case float f: return Math.Sign(f) < 0 ? -i : i; | ||
| case double d: return Math.Sign(d) < 0 ? -i : i; | ||
| case decimal de: return Math.Sign(de) < 0 ? -i : i; | ||
| default: return i; | ||
| } | ||
| return value switch | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. switch expression!!! Yay :) |
||
| { | ||
| Int16 i16 => Math.Sign(i16) < 0 ? -i : i, | ||
| Int32 i32 => Math.Sign(i32) < 0 ? -i : i, | ||
| Int64 i64 => Math.Sign(i64) < 0 ? -i : i, | ||
| sbyte s => Math.Sign(s) < 0 ? -i : i, | ||
| float f => Math.Sign(f) < 0 ? -i : i, | ||
| double d => Math.Sign(d) < 0 ? -i : i, | ||
| decimal m => Math.Sign(m) < 0 ? -i : i, | ||
| _ => IsNullLike(value) ? 0 : i | ||
| }; | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -739,9 +738,7 @@ private static int CompareObjectToNull(object value, bool numberIsRightHandSide) | |
| /// to the type of <paramref name="first"/>. | ||
| /// </exception> | ||
| public static int Compare(object first, object second) | ||
| { | ||
| return LanguagePrimitives.Compare(first, second, false, CultureInfo.InvariantCulture); | ||
| } | ||
| => LanguagePrimitives.Compare(first, second, false, CultureInfo.InvariantCulture); | ||
|
|
||
| /// <summary> | ||
| /// Compare first and second, converting second to the | ||
|
|
@@ -757,9 +754,7 @@ public static int Compare(object first, object second) | |
| /// to the type of <paramref name="first"/>. | ||
| /// </exception> | ||
| public static int Compare(object first, object second, bool ignoreCase) | ||
| { | ||
| return LanguagePrimitives.Compare(first, second, ignoreCase, CultureInfo.InvariantCulture); | ||
| } | ||
| => LanguagePrimitives.Compare(first, second, ignoreCase, CultureInfo.InvariantCulture); | ||
|
|
||
| /// <summary> | ||
| /// Compare first and second, converting second to the | ||
|
|
@@ -777,23 +772,20 @@ public static int Compare(object first, object second, bool ignoreCase) | |
| /// </exception> | ||
| public static int Compare(object first, object second, bool ignoreCase, IFormatProvider formatProvider) | ||
| { | ||
| if (formatProvider == null) | ||
| { | ||
| formatProvider = CultureInfo.InvariantCulture; | ||
| } | ||
| formatProvider ??= CultureInfo.InvariantCulture; | ||
|
|
||
| var culture = formatProvider as CultureInfo; | ||
| if (culture == null) | ||
| { | ||
| throw PSTraceSource.NewArgumentException("formatProvider"); | ||
| throw PSTraceSource.NewArgumentException(nameof(formatProvider)); | ||
| } | ||
|
|
||
| first = PSObject.Base(first); | ||
| second = PSObject.Base(second); | ||
|
|
||
| if (first == null) | ||
| { | ||
| return second == null ? 0 : CompareObjectToNull(second, true); | ||
| return CompareObjectToNull(second, true); | ||
| } | ||
|
|
||
| if (second == null) | ||
|
|
@@ -803,7 +795,7 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP | |
|
|
||
| if (first is string firstString) | ||
| { | ||
| string secondString = second as string; | ||
| var secondString = second as string; | ||
| if (secondString == null) | ||
| { | ||
| try | ||
|
|
@@ -812,19 +804,26 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP | |
| } | ||
| catch (PSInvalidCastException e) | ||
| { | ||
| throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure, | ||
| first.ToString(), second.ToString(), e.Message); | ||
| throw PSTraceSource.NewArgumentException( | ||
| nameof(second), | ||
| ExtendedTypeSystem.ComparisonFailure, | ||
| first.ToString(), | ||
| second.ToString(), | ||
| e.Message); | ||
| } | ||
| } | ||
|
|
||
| return culture.CompareInfo.Compare(firstString, secondString, | ||
| ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); | ||
| return culture.CompareInfo.Compare( | ||
| firstString, | ||
| secondString, | ||
| ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); | ||
| } | ||
|
|
||
| Type firstType = first.GetType(); | ||
| Type secondType = second.GetType(); | ||
| int firstIndex = LanguagePrimitives.TypeTableIndex(firstType); | ||
| int secondIndex = LanguagePrimitives.TypeTableIndex(secondType); | ||
|
|
||
| if ((firstIndex != -1) && (secondIndex != -1)) | ||
| { | ||
| return LanguagePrimitives.NumericCompare(first, second, firstIndex, secondIndex); | ||
|
|
@@ -837,8 +836,12 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP | |
| } | ||
| catch (PSInvalidCastException e) | ||
| { | ||
| throw PSTraceSource.NewArgumentException("second", ExtendedTypeSystem.ComparisonFailure, | ||
| first.ToString(), second.ToString(), e.Message); | ||
| throw PSTraceSource.NewArgumentException( | ||
| nameof(second), | ||
| ExtendedTypeSystem.ComparisonFailure, | ||
| first.ToString(), | ||
| second.ToString(), | ||
| e.Message); | ||
| } | ||
|
|
||
| if (first is IComparable firstComparable) | ||
|
|
@@ -853,7 +856,7 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP | |
|
|
||
| // At this point, we know that they aren't equal but we have no way of | ||
| // knowing which should compare greater than the other so we throw an exception. | ||
| throw PSTraceSource.NewArgumentException("first", ExtendedTypeSystem.NotIcomparable, first.ToString()); | ||
| throw PSTraceSource.NewArgumentException(nameof(first), ExtendedTypeSystem.NotIcomparable, first.ToString()); | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
@@ -866,9 +869,7 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP | |
| /// zero if it is greater or zero if they are the same.</param> | ||
| /// <returns>True if the comparison was successful, false otherwise.</returns> | ||
| public static bool TryCompare(object first, object second, out int result) | ||
| { | ||
| return TryCompare(first, second, ignoreCase: false, CultureInfo.InvariantCulture, out result); | ||
| } | ||
| => TryCompare(first, second, ignoreCase: false, CultureInfo.InvariantCulture, out result); | ||
|
|
||
| /// <summary> | ||
| /// Tries to compare first and second, converting second to the type of the first, if necessary. | ||
|
|
@@ -880,9 +881,7 @@ public static bool TryCompare(object first, object second, out int result) | |
| /// <param name="result">Less than zero if first is smaller than second, more than zero if it is greater or zero if they are the same.</param> | ||
| /// <returns>True if the comparison was successful, false otherwise.</returns> | ||
| public static bool TryCompare(object first, object second, bool ignoreCase, out int result) | ||
| { | ||
| return TryCompare(first, second, ignoreCase, CultureInfo.InvariantCulture, out result); | ||
| } | ||
| => TryCompare(first, second, ignoreCase, CultureInfo.InvariantCulture, out result); | ||
|
|
||
| /// <summary> | ||
| /// Tries to compare first and second, converting second to the type of the first, if necessary. | ||
|
|
@@ -898,10 +897,7 @@ public static bool TryCompare(object first, object second, bool ignoreCase, out | |
| public static bool TryCompare(object first, object second, bool ignoreCase, IFormatProvider formatProvider, out int result) | ||
| { | ||
| result = 0; | ||
| if (formatProvider == null) | ||
| { | ||
| formatProvider = CultureInfo.InvariantCulture; | ||
| } | ||
| formatProvider ??= CultureInfo.InvariantCulture; | ||
|
|
||
| if (!(formatProvider is CultureInfo culture)) | ||
| { | ||
|
|
@@ -986,8 +982,10 @@ public static bool TryCompare(object first, object second, bool ignoreCase, IFor | |
| public static bool IsTrue(object obj) | ||
| { | ||
| // null is a valid argument - it converts to false... | ||
| if (obj == null || obj == AutomationNull.Value) | ||
| if (IsNullLike(obj)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| obj = PSObject.Base(obj); | ||
|
|
||
|
|
@@ -1013,8 +1011,7 @@ public static bool IsTrue(object obj) | |
| if (objType == typeof(SwitchParameter)) | ||
| return ((SwitchParameter)obj).ToBool(); | ||
|
|
||
| IList objectArray = obj as IList; | ||
| if (objectArray != null) | ||
| if (obj is IList objectArray) | ||
| { | ||
| return IsTrue(objectArray); | ||
| } | ||
|
|
@@ -1059,15 +1056,20 @@ internal static bool IsTrue(IList objectArray) | |
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Internal routine that determines if an object meets any of our criteria for true null. | ||
| /// </summary> | ||
| /// <param name="obj">The object to test.</param> | ||
| /// <returns>True if the object is null.</returns> | ||
| public static bool IsNull(object obj) => obj == null || obj == AutomationNull.Value; | ||
|
|
||
| /// <summary> | ||
| /// Internal routine that determines if an object meets any of our criteria for null. | ||
| /// This method additionally checks for <see cref="NullString.Value"/> and <see cref="DBNull.Value"/> | ||
| /// </summary> | ||
| /// <param name="obj">The object to test.</param> | ||
| /// <returns>True if the object is null.</returns> | ||
| internal static bool IsNull(object obj) | ||
| { | ||
| return (obj == null || obj == AutomationNull.Value); | ||
| } | ||
| public static bool IsNullLike(object obj) => obj == DBNull.Value || obj == NullString.Value || IsNull(obj); | ||
|
|
||
| /// <summary> | ||
| /// Auxiliary for the cases where we want a new PSObject or null. | ||
|
|
@@ -3098,15 +3100,17 @@ private static object ConvertToVoid(object valueToConvert, | |
| return AutomationNull.Value; | ||
| } | ||
|
|
||
| private static bool ConvertClassToBool(object valueToConvert, | ||
| Type resultType, | ||
| bool recursion, | ||
| PSObject originalValueToConvert, | ||
| IFormatProvider formatProvider, | ||
| TypeTable backupTable) | ||
| private static bool ConvertClassToBool( | ||
| object valueToConvert, | ||
| Type resultType, | ||
| bool recursion, | ||
| PSObject originalValueToConvert, | ||
| IFormatProvider formatProvider, | ||
| TypeTable backupTable) | ||
| { | ||
| typeConversion.WriteLine("Converting ref to boolean."); | ||
| return valueToConvert != null; | ||
| // Both NullString and DBNull should be treated the same as true nulls for the purposes of this conversion. | ||
| return !IsNullLike(valueToConvert); | ||
| } | ||
|
|
||
| private static bool ConvertValueToBool(object valueToConvert, | ||
|
|
@@ -4711,10 +4715,11 @@ internal static IConversionData FigureConversion(object valueToConvert, Type res | |
| { | ||
| PSObject valueAsPsObj; | ||
| Type originalType; | ||
| if (valueToConvert == null || valueToConvert == AutomationNull.Value) | ||
|
|
||
| if (IsNull(valueToConvert)) | ||
| { | ||
| valueAsPsObj = null; | ||
| originalType = typeof(Null); | ||
| valueAsPsObj = null; | ||
| } | ||
| else | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -3028,15 +3028,19 @@ private DynamicMetaObject CompareEQ(DynamicMetaObject target, | |||||||
| if (target.Value == null) | ||||||||
| { | ||||||||
| return new DynamicMetaObject( | ||||||||
| arg.Value == null ? ExpressionCache.BoxedTrue : ExpressionCache.BoxedFalse, | ||||||||
| LanguagePrimitives.IsNullLike(arg.Value) | ||||||||
| ? ExpressionCache.BoxedTrue | ||||||||
| : ExpressionCache.BoxedFalse, | ||||||||
| target.CombineRestrictions(arg)); | ||||||||
| } | ||||||||
|
|
||||||||
| var enumerable = PSEnumerableBinder.IsEnumerable(target); | ||||||||
| if (enumerable == null && arg.Value == null) | ||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't handle the collection comparison case:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK... it looks like it should be done here somewhere? I... don't know where to start there. 😅
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @daxian-dbw looks like we both spoke too soon. I have a lot of difficulty understanding all those code paths, but even without any editing yet, this works: So... I would guess that somewhere in those paths we hook back into either LanguagePrimitives methods (which I've already handled) or back into the binder methods themselves as scalar comparisons. I'll add some tests to make sure we don't regress. 😄
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right :) I should have compile your branch and tried it myself. PowerShell/src/System.Management.Automation/engine/runtime/Binding/Binders.cs Lines 3360 to 3362 in 4967416
Thanks for adding the collection comparison test! |
||||||||
| { | ||||||||
| return new DynamicMetaObject( | ||||||||
| ExpressionCache.BoxedFalse, | ||||||||
| LanguagePrimitives.IsNullLike(target.Value) | ||||||||
| ? ExpressionCache.BoxedTrue | ||||||||
| : ExpressionCache.BoxedFalse, | ||||||||
| target.CombineRestrictions(arg)); | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -3051,14 +3055,19 @@ private DynamicMetaObject CompareNE(DynamicMetaObject target, | |||||||
| if (target.Value == null) | ||||||||
| { | ||||||||
| return new DynamicMetaObject( | ||||||||
| arg.Value == null ? ExpressionCache.BoxedFalse : ExpressionCache.BoxedTrue, | ||||||||
| LanguagePrimitives.IsNullLike(arg.Value) | ||||||||
| ? ExpressionCache.BoxedFalse | ||||||||
| : ExpressionCache.BoxedTrue, | ||||||||
| target.CombineRestrictions(arg)); | ||||||||
| } | ||||||||
|
|
||||||||
| var enumerable = PSEnumerableBinder.IsEnumerable(target); | ||||||||
| if (enumerable == null && arg.Value == null) | ||||||||
| { | ||||||||
| return new DynamicMetaObject(ExpressionCache.BoxedTrue, | ||||||||
| return new DynamicMetaObject( | ||||||||
| LanguagePrimitives.IsNullLike(target.Value) | ||||||||
| ? ExpressionCache.BoxedFalse | ||||||||
| : ExpressionCache.BoxedTrue, | ||||||||
| target.CombineRestrictions(arg)); | ||||||||
| } | ||||||||
|
|
||||||||
|
|
||||||||

Uh oh!
There was an error while loading. Please reload this page.