-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Relax conversion requirements for pattern-matching involving type parameters. #18784
Conversation
…ameters. Fixes dotnet#16195 This is a language change for 7.1. See dotnet/csharplang#154. Some tests may fail until dotnet#18756 is integrated.
@jaredpar Please look at this and determine if it is suitable for 15.3. A number of customers have complained, and the fix is small. |
This PR is pending a compiler team feature review. |
// It is possible that the input value is already of the correct type, in which case the pattern | ||
// is irrefutable, and we can just do the assignment and return true. | ||
// is irrefutable (there is no way for a non-nullable value type to be null), and we can just do | ||
// the assignment and return true. | ||
if (loweredInput.Type == type) |
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.
This type comparison should ignore dynamic/tuple/modopt differences
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.
Essentially if there is an identity conversion, then match is irrefutable.
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.
That may be true (the suggested change would be a code improvement), but it is not relevant to the change in the PR.
ImmutableArray.Create(s, i), | ||
ImmutableArray.Create<BoundExpression>( | ||
_factory.AssignmentExpression(_factory.Local(i), _factory.Convert(tmpType, loweredInput)), | ||
_factory.AssignmentExpression(_factory.Local(s), _factory.Is(_factory.Local(i), type)), |
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 should have some tests that validate IL, just to be sure how this is emitted.
compilation = CreateCompilationWithMscorlib45(source, references: new MetadataReference[] { CSharpRef, SystemCoreRef }, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular7_1); | ||
compilation.VerifyDiagnostics(); | ||
CompileAndVerify(compilation, expectedOutput: "True1False0"); | ||
} |
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.
Consider testing types that are not type parameters but where .ContainsTypeParameter()
is true
: T[]
, A<T>.B
, etc.
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.
Consider testing types that are not type parameters but where .ContainsTypeParameter() is true: T[], A.B, etc.
Yes, please add several tests for scenarios like that.
ImmutableArray.Create<BoundExpression>( | ||
_factory.AssignmentExpression(_factory.Local(i), _factory.Convert(tmpType, loweredInput)), | ||
_factory.AssignmentExpression(_factory.Local(s), _factory.Is(_factory.Local(i), type)), | ||
_factory.AssignmentExpression(loweredTarget, _factory.Conditional(_factory.Local(s), _factory.Convert(type, _factory.Local(i)), _factory.Default(type), type)) |
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.
_factory.Local(s) [](start = 90, length = 17)
Can we inline the assignment to s
here?
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. But it made no difference in any generated code.
} | ||
class X : B { } | ||
"; | ||
CreateStandardCompilation(source).VerifyDiagnostics( |
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.
CreateStandardCompilation(source).VerifyDiagnostics( [](start = 12, length = 52)
Consider explicitly using C# 7 version, otherwise this test will break once we bump the default version.
} | ||
} | ||
"; | ||
CreateStandardCompilation(source).VerifyDiagnostics( |
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.
CreateStandardCompilation(source).VerifyDiagnostics( [](start = 12, length = 52)
Consider explicitly using C# 7 version, otherwise this test will break once we bump the default version.
} | ||
} | ||
}"; | ||
CreateStandardCompilation(source).VerifyDiagnostics( |
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.
CreateStandardCompilation(source).VerifyDiagnostics( [](start = 12, length = 52)
Consider explicitly using C# 7 version, otherwise this test will break once we bump the default version.
} | ||
} | ||
"; | ||
var compilation = CreateCompilationWithMscorlib45(source, references: new MetadataReference[] { CSharpRef, SystemCoreRef }, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular); |
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.
var compilation = CreateCompilationWithMscorlib45(source, references: new MetadataReference[] { CSharpRef, SystemCoreRef }, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular); [](start = 12, length = 192)
Consider explicitly using C# 7 version, otherwise this test will break once we bump the default version.
} | ||
} | ||
"; | ||
var compilation = CreateCompilationWithMscorlib45(source, references: new MetadataReference[] { CSharpRef, SystemCoreRef }, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular); |
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.
var compilation = CreateCompilationWithMscorlib45(source, references: new MetadataReference[] { CSharpRef, SystemCoreRef }, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular); [](start = 12, length = 192)
Consider explicitly using C# 7 version, otherwise this test will break once we bump the default version.
Done with review pass. |
Previously the code assumed the type would be the same. Also use CanContainNull() rather than !IsNonNullableValueType() to improve code for type parameters
} | ||
else | ||
{ | ||
byValue.Default = new DecisionTree.ByType(byValue.Expression, byValue.Type, null); |
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.
This is a bug fix to insert a needed conversion.
} | ||
else | ||
{ | ||
Debug.Assert(byValue.Default.Type == type); | ||
return Add(byValue.Default, makeDecision); | ||
result = AddByType(byValue.Default, type, makeDecision); |
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.
This is a bug fix to insert a needed conversion.
@@ -93,10 +94,10 @@ private BoundExpression MakeIsDeclarationPattern(BoundDeclarationPattern lowered | |||
|
|||
return _factory.Sequence(ImmutableArray.Create(temp), | |||
sideEffects: ImmutableArray<BoundExpression>.Empty, | |||
result: MakeIsDeclarationPattern(loweredPattern.Syntax, loweredInput, discard, requiresNullTest: true)); | |||
result: MakeIsDeclarationPattern(loweredPattern.Syntax, loweredInput, discard, requiresNullTest: loweredInput.Type.CanContainNull())); |
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.
This is a bug fix to remove an unneeded null check.
} | ||
|
||
return MakeIsDeclarationPattern(loweredPattern.Syntax, loweredInput, loweredPattern.VariableAccess, requiresNullTest: true); | ||
return MakeIsDeclarationPattern(loweredPattern.Syntax, loweredInput, loweredPattern.VariableAccess, requiresNullTest: loweredInput.Type.CanContainNull()); |
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.
This is a bug fix to remove an unneeded null check.
Debug.Assert(!type.IsNullableType()); | ||
// It is possible that the input value is already of the correct type, in which case the pattern | ||
// is irrefutable, and we can just do the assignment and return true (or perform the null test). | ||
if (MatchIsIrrefutable(loweredInput.Type, loweredTarget.Type, requiresNullTest)) |
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.
This is a bug fix to remove an unneeded type test.
@@ -221,7 +221,7 @@ private DecisionTree AddByValue(DecisionTree.ByType byType, BoundConstantPattern | |||
var kvp = byType.TypeAndDecision[i]; | |||
var matchedType = kvp.Key; | |||
var decision = kvp.Value; | |||
if (matchedType.TupleUnderlyingTypeOrSelf() == value.Value.Type.TupleUnderlyingTypeOrSelf()) | |||
if (matchedType.Equals(value.Value.Type, TypeCompareKind.IgnoreDynamicAndTupleNames)) |
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.
Please add tests that ignore dynamic
.
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 can't think of a way to make that happen. The is no constant pattern whose type is generic. This could probably be simplified to (matchedType == value.Value.Type)
.
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.
Oh, I know a way to make it happen. An enum type nested in a generic class.
…are ignored in patterns.
LGTM |
@agocke or @AlekseyTs Please re-review for my second review. There have been changes since @agocke looked at this. |
@MeiChin-Tsai @jaredpar for ask-mode approval (once reviews are complete). |
Approved once you get code review and tests done. Thanks. |
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, except for test case (which may already be there and I missed it)
Console.Write(x is Container<T>.Derived[] b0); | ||
switch (x) | ||
{ | ||
case Container<T>.Derived[] b1: |
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 it legal to use an unbound type here? i.e., case Container<>.Derived d:
?
Even if not, consider adding a test case for it.
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.
Unbound types are an error in type binding everywhere except in a typeof()
. I don't think we have tests for each place a type can occur.
Customer scenario
This is a tiny language change for 7.1. See dotnet/csharplang#154.
See #16195 for customer scenario. In short, pattern-matching can give a compile-time error unexpectedly when type parameters are involved. The compiler is correct to give these errors according to the spec, but we wish to relax the language to make it legal.
Bugs this fixes:
Fixes #16195
Workarounds, if any
Cast the expression being matched to
object
.Risk
Low. This has little impact on existing code.
Performance impact
None expected.
Is this a regression from a previous update?
No.