-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Patterns: Allow non-negative and full integer sets to merge (intersect or union) #71968
Conversation
Would it make sense to not relate the two props in the first place? that is, including |
@alrz Thanks much for the suggestion. I'd not considered that. I'll give it a try (seems simpler). |
@alrz I took a stab at it, but I don't think tweaking the comparison is a good approach after all. It's better for all branches of the DAG that test the |
Curious why we don't think of that as special. I assumed we were using |
|
Clarified offline with @CyrusNajmabadi. Independently of the change in this PR, he's surprised that we don't make the non-negative assumption of well-behavedness for some other well-known BCL |
It could with enumerable list-patterns. Checking |
@dotnet/roslyn-compiler for review. Thanks |
// But we need to upgrade them to regular integers to perform operations against full integer sets. | ||
if (this is NumericValueSet<int, NonNegativeIntTC> nonNegativeThis && o is NumericValueSet<int, IntTC>) | ||
{ | ||
return ((IValueSet<T>)ExpandToIntegerRange(nonNegativeThis)).Intersect(o); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested this suggestion but it has no visible effect (what matters is the set of remaining values being tracked, which are the same in both designs). I think I prefer only using one adjustment (expansion) for both union and intersection, as it's a bit simpler.
// But we need to upgrade them to regular integers to perform operations against full integer sets. | ||
if (this is NumericValueSet<int, NonNegativeIntTC> nonNegativeThis && o is NumericValueSet<int, IntTC>) | ||
{ | ||
return ((IValueSet<T>)ExpandToIntegerRange(nonNegativeThis)).Union(o); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what is the scenario for hitting this code path (what new test needs this change?), but it is quite possible that we don't want to expand the set in this case either. We determined that the property doesn't have negative values, therefore, it doesn't seem useful to include negative values into the set of possible values, even if the other check didn't eliminate them on its own. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to modify the Union logic for the following tests: MixedCountPatterns_ReverseOrder
, MixedCountPatterns_NegativeTestAfterRegularPropertyPattern
and MixedCountPatterns_NegativeTest
(which otherwise fail with a cast exception as we try to union sets of different kinds).
That said, it looks like I only managed to hit the second branch (below). I'll see if I can manage to hit this first branch too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed the handling of two mirror cases that I don't think we need after all. Thanks
Done with review pass (commit 3) |
} | ||
"""; | ||
CompileAndVerify(source, expectedOutput: "012").VerifyDiagnostics( | ||
// (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// (13,18): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Count: 1 }' is not covered.
This warning looks unexpected to me. Both Count
references refer to the same ICollection.Count
property on the same instance. We checked it for 0 and for values >0, therefore '{ Count: 1 }' is also covered. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only checked for values >0 when the type is IList, so not for the general case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only checked for values >0 when the type is IList, so not for the general case.
Yes, I missed the fact. Thanks.
@@ -164,6 +164,17 @@ public IValueSet<T> Complement() | |||
|
|||
public IValueSet<T> Intersect(IValueSet<T> o) | |||
{ | |||
// We use non-negative integers for Count/Length on types that list-patterns can be used on (ie.countable and indexable ones). | |||
// But we need to upgrade them to regular integers to perform operations against full integer sets. | |||
if (this is NumericValueSet<int, NonNegativeIntTC> nonNegativeThis && o is NumericValueSet<int, IntTC>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's one place that a similar situation is handled (though this is a test while we want to track an evaluation):
roslyn/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs
Lines 929 to 933 in b0ebfe7
if (foundExplicitNullTest && d is BoundDagNonNullTest { IsExplicitTest: false } t) | |
{ | |
// Turn an "implicit" non-null test into an explicit one | |
state.SelectedTest = new BoundDagNonNullTest(t.Syntax, isExplicitTest: true, t.Input, t.HasErrors); | |
} |
I think a flag could find IsLengthOrCount=true and tweak the selected node with a matching IsLengthOrCount.
(edit: I missed the fact that value set is not affected there unless Test.Input is changed)
@@ -48,6 +49,8 @@ internal NumericValueSet(ImmutableArray<(T first, T last)> intervals) | |||
} | |||
#endif | |||
_intervals = intervals; | |||
_tc = tc; | |||
_numericValueSetFactory = new NumericValueSetFactory<T, TTC>(tc); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -18,32 +18,33 @@ internal static partial class ValueSetFactory | |||
/// </summary> | |||
/// <typeparam name="TFloating">A floating-point type.</typeparam> | |||
/// <typeparam name="TFloatingTC">A typeclass supporting that floating-point type.</typeparam> | |||
private sealed class FloatingValueSet<TFloating, TFloatingTC> : IValueSet<TFloating> where TFloatingTC : struct, FloatingTC<TFloating> | |||
private sealed class FloatingValueSet<TFloating, TFloatingTC> : IValueSet<TFloating> where TFloatingTC : class, FloatingTC<TFloating> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -8,35 +8,38 @@ namespace Microsoft.CodeAnalysis.CSharp | |||
{ | |||
internal static partial class ValueSetFactory | |||
{ | |||
private sealed class FloatingValueSetFactory<TFloating, TFloatingTC> : IValueSetFactory<TFloating> where TFloatingTC : struct, FloatingTC<TFloating> | |||
private sealed class FloatingValueSetFactory<TFloating, TFloatingTC> : IValueSetFactory<TFloating> where TFloatingTC : class, FloatingTC<TFloating> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -46,7 +58,7 @@ int INumericTC<int>.Next(int value) | |||
|
|||
int INumericTC<int>.Prev(int value) | |||
{ | |||
Debug.Assert(value != int.MinValue); | |||
Debug.Assert(value != (nonNegative ? 0 : int.MinValue)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MinValue is a private interface implementation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MinValue is a private interface implementation
It doesn't have to be private. And we can avoid code duplication.
@@ -13,6 +13,7 @@ internal static partial class ValueSetFactory | |||
private sealed class NintValueSetFactory : IValueSetFactory<int>, IValueSetFactory | |||
{ | |||
public static readonly NintValueSetFactory Instance = new NintValueSetFactory(); | |||
private static readonly NumericValueSetFactory<int, IntTC> s_numericValueSetFactory = new NumericValueSetFactory<int, IntTC>(IntTC.DefaultInstance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -15,49 +15,50 @@ private sealed class NonNegativeIntValueSetFactory : IValueSetFactory<int> | |||
{ | |||
public static readonly NonNegativeIntValueSetFactory Instance = new NonNegativeIntValueSetFactory(); | |||
|
|||
private NonNegativeIntValueSetFactory() { } | |||
private static readonly IValueSetFactory<int> s_underlying = new NumericValueSetFactory<int, IntTC>(IntTC.NonNegativeInstance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're caching the boxed struct
@@ -13,6 +13,7 @@ internal static partial class ValueSetFactory | |||
private sealed class NuintValueSetFactory : IValueSetFactory<uint>, IValueSetFactory | |||
{ | |||
public static readonly NuintValueSetFactory Instance = new NuintValueSetFactory(); | |||
private static readonly NumericValueSetFactory<uint, UIntTC> s_numericValueSetFactory = new NumericValueSetFactory<uint, UIntTC>(UIntTC.Instance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (commit 12). It looks like the title and the description are obsolete now. Make sure to adjust them during merging. |
{ | ||
int INumericTC<int>.MinValue => int.MinValue; | ||
// Note: whenever we intersect or union two sets of IntTCs, | ||
// we just keep the nonNegative flag of the set we're merging into. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// we just keep the nonNegative flag of the set we're merging into.
If we are uncomfortable relying on this behavior, we could add Intersect/Union APIs to INumericTC
and implement them for this type by preferring non-negative flavor among intersected, and preferring full-set among union-ed. NumericValueSet.Intersect/Union
will call the same named APIs to get the resulting INumericTC to use. #WontFix
int INumericTC<int>.MinValue => int.MinValue; | ||
// Note: whenever we intersect or union two sets of IntTCs, | ||
// we just keep the nonNegative flag of the set we're merging into. | ||
public bool nonNegative; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jcouv It looks like the build is broken |
@@ -12,20 +12,20 @@ private sealed class DecimalValueSetFactory : IValueSetFactory<decimal>, IValueS | |||
{ | |||
public static readonly DecimalValueSetFactory Instance = new DecimalValueSetFactory(); | |||
|
|||
private readonly IValueSetFactory<decimal> _underlying = NumericValueSetFactory<decimal, DecimalTC>.Instance; | |||
private readonly IValueSetFactory<decimal> _underlying = new NumericValueSetFactory<decimal>(DecimalTC.Instance); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a struct? Is it worth caching its value?
I see, we are caching an interface instance not a struct value directly
Done with review pass (commit 17) |
@dotnet/roslyn-compiler for another review. I updated OP to reflect the new design. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 19)
@dotnet/roslyn-compiler for second review. Thanks |
1 similar comment
@dotnet/roslyn-compiler for second review. Thanks |
src/Compilers/CSharp/Portable/Utilities/ValueSetFactory.NumericValueSet.cs
Outdated
Show resolved
Hide resolved
…cValueSet.cs Co-authored-by: Rikki Gibson <rikkigibson@gmail.com>
Fixes #71660
For background, the issue is that we treat certain
Count
property patterns as only resulting in a non-negative integer set, but in the problematic scenario we need to perform operations (intersection/union) between a non-negative integer set resulting from aCount
property with the special treatment (on anIList
) which is countable and indexable) and an integer set resulting from aCount
property in a context where it's not treated specially (on anICollection
).I considered a few possible avenues for fixing this:
NonNegativeIntValueSetFactory
on the basis ofIntTC
(ie. removeNonNegativeIntTC
)BoundPropertySubpattern.IsLengthOrCount
orBoundDagPropertyEvaluation.IsLengthOrCount
) when we detect such mix usage. This can be done by not setting the flag in the first place or by erasing it after all the tests have been prepared.BoundDabTemp.Equals
) to account for differences inIsLengthOrCount
(thx @alrz for suggestion)This PR initially implemented option 1, but we ended up shifting towards a new implementation of option 2. The type parameters for type classes were removed in favor of arguments holding instances of specific algorithms. This allows having two instances of
IntTC
, one for full integer set and one for non-negative sets. That flag doesn't need to be merged carefully when two sets get merged (intersection/union) at the moment, although that's something we could refine if it turns out necessary.Tagging @alrz in case you have some other thought.