Skip to content
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

Merged
merged 13 commits into from
May 8, 2017

Conversation

gafter
Copy link
Member

@gafter gafter commented Apr 18, 2017

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.

…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.
@gafter gafter added Area-Compilers Feature Request PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. labels Apr 18, 2017
@gafter gafter added this to the 15.3 milestone Apr 18, 2017
@gafter gafter self-assigned this Apr 18, 2017
@gafter gafter added the 4 - In Review A fix for the issue is submitted for review. label Apr 18, 2017
@gafter gafter removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Apr 21, 2017
@gafter
Copy link
Member Author

gafter commented Apr 21, 2017

@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.

@CyrusNajmabadi
Copy link
Member

@gafter Is #16993 another manifestation of this issue?

@gafter
Copy link
Member Author

gafter commented Apr 25, 2017

This PR is pending a compiler team feature review.

@gafter gafter added the Blocked label Apr 25, 2017
// 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)
Copy link
Member

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

Copy link
Member

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.

Copy link
Member Author

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)),
Copy link
Member

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.

@gafter gafter removed the Blocked label May 2, 2017
@gafter
Copy link
Member Author

gafter commented May 2, 2017

@dotnet/roslyn-compiler May I please have a second review? @VSadov I believe the tests you suggested have been added.

@jaredpar May I have permission to merge when reviews are complete?

compilation = CreateCompilationWithMscorlib45(source, references: new MetadataReference[] { CSharpRef, SystemCoreRef }, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular7_1);
compilation.VerifyDiagnostics();
CompileAndVerify(compilation, expectedOutput: "True1False0");
}
Copy link
Member

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.

Copy link
Contributor

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))
Copy link
Contributor

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?

Copy link
Member Author

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(
Copy link
Contributor

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(
Copy link
Contributor

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(
Copy link
Contributor

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);
Copy link
Contributor

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);
Copy link
Contributor

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.

@AlekseyTs
Copy link
Contributor

Done with review pass.

gafter added 2 commits May 3, 2017 15:59
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);
Copy link
Member Author

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);
Copy link
Member Author

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()));
Copy link
Member Author

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());
Copy link
Member Author

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))
Copy link
Member Author

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))
Copy link
Member

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.

Copy link
Member Author

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).

Copy link
Member Author

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.

@cston
Copy link
Member

cston commented May 4, 2017

LGTM

@gafter
Copy link
Member Author

gafter commented May 5, 2017

@agocke or @AlekseyTs Please re-review for my second review. There have been changes since @agocke looked at this.

@gafter
Copy link
Member Author

gafter commented May 5, 2017

@MeiChin-Tsai @jaredpar for ask-mode approval (once reviews are complete).

@MeiChin-Tsai
Copy link

Approved once you get code review and tests done. Thanks.

Copy link
Member

@agocke agocke left a 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:
Copy link
Member

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.

Copy link
Member Author

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.

@gafter gafter merged commit 02a0942 into dotnet:master May 8, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Generic expression of a derived type cannot be handled by a pattern
8 participants