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

Implement pattern-matching via ITuple #28859

Merged
merged 7 commits into from
Aug 14, 2018

Conversation

gafter
Copy link
Member

@gafter gafter commented Jul 26, 2018

Fixes #27746

This implements positional pattern-matching using the interface ITuple.

@gafter gafter added this to the 16.0 milestone Jul 26, 2018
@gafter gafter self-assigned this Jul 26, 2018
@gafter gafter requested review from agocke and cston July 26, 2018 18:38
@gafter gafter requested a review from a team as a code owner July 26, 2018 18:38
@jcouv
Copy link
Member

jcouv commented Jul 26, 2018

Consider updating the feature document (or start one there isn't already) or feature spec to explain expected behavior. Thanks #Resolved

@@ -1778,6 +1782,12 @@
<Field Name="VariableAccess" Type="BoundExpression" Null="allow"/>
</Node>

<Node Name="BoundITuplePattern" Base="BoundPattern">
<Field Name="getLengthMethod" Type="MethodSymbol" Null="disallow"/>
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getLengthMethod [](start = 17, length = 15)

nit: Not sure about capitalization #Closed

<Node Name="BoundITuplePattern" Base="BoundPattern">
<Field Name="getLengthMethod" Type="MethodSymbol" Null="disallow"/>
<Field Name="getItemMethod" Type="MethodSymbol" Null="disallow"/>
<Field Name="Deconstruction" Type="ImmutableArray&lt;BoundSubpattern&gt;" Null="disallow"/>
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deconstruction [](start = 17, length = 14)

nit: name probably can be tweaked, since no deconstruction involved. #Closed

@@ -1177,6 +1177,10 @@
<Node Name="BoundDagPropertyEvaluation" Base="BoundDagEvaluation">
<Field Name="Property" Type="PropertySymbol" Null="disallow"/>
</Node>
<Node Name="BoundDagIndexEvaluation" Base="BoundDagEvaluation">
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BoundDagIndexEvaluation [](start = 14, length = 23)

Do we need to customize the Display property for the new bound nodes (in Formatting.cs)? #Closed

Copy link
Member Author

@gafter gafter Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that is for nodes that extend BoundExpression but have no type. #Closed

#endif

[Fact]
public void XYZZY()
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XYZZY [](start = 20, length = 5)

Probably not intentional #Closed

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 file should be merged with PatternMatchingTests4.cs


In reply to: 205619266 [](ancestors = 205619266)

// case (1, 2):
Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(1, 2)").WithArguments("object", "2").WithLocation(10, 18)
Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(1, 2)").WithArguments("object", "2").WithLocation(8, 18)
);
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

); [](start = 16, length = 2)

Not related to this PR, but this reminded me:
I'm working on an IDE PR to generate a Deconstruct method when it's missing.
But that fixer is negatively affected by the fact that we have two diagnostics (as visible in this test). The IDE doesn't like when there are two diagnostics on the same span...
I'd like to brainstorm how to reduce this to a single diagnostic, while still providing good information to users. I'll stop by tomorrow or next week to discuss. #Closed

@@ -1093,15 +1091,15 @@ public static void M(object o)
}
}
";
var compilation = CreatePatternCompilation(source);
var compilation = CreateCompilationWithMscorlib45(source); // doesn't have ITuple
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't have ITuple [](start = 74, length = 19)

Would this test behave differently if the ITuple type was found? #Resolved

Copy link
Member Author

@gafter gafter Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes; compilation would pass and it would use ITuple for the matching. #Closed

Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't follow. The parameter object o doesn't implement ITuple.
Oh, I see the description in the issue covers this.
Some documentation would be good


In reply to: 205628798 [](ancestors = 205628798)

@@ -570,6 +570,9 @@ public void AllWellKnownTypes()
case WellKnownType.ExtSentinel:
// Not a real type
continue;
case WellKnownType.System_Runtime_CompilerServices_ITuple:
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, ITuple has shipped (more details on where). And it may be available in ValueTupleRef which is referenced above (I don't remember exactly what that ref was based off of). #Closed

// Use a version of the platform APIs that lack ITuple
var compilation = CreateCompilationWithMscorlib40AndSystemCore(source, options: TestOptions.ReleaseExe);
compilation.VerifyDiagnostics(
// (7,32): error CS1061: 'object' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CS1061 [](start = 33, length = 6)

Should the error message mention ITuple? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like "Pattern matching in this case would work if we could find the type 'System.Runtime.CompilerServices.ITuple'"? If ITuple were available then we wouldn't be looking for an extension method.


In reply to: 205631111 [](ancestors = 205631111)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, I don't really know how to incorporate that into the message, and adding a third diagnostic would be pretty bad :-(
I don't have a good idea for this, but it feels like we're not guiding users to one of the possible solutions... :-S


In reply to: 206681158 [](ancestors = 206681158,205631111)

{
[CompilerTrait(CompilerFeature.Patterns)]
public class ITuplePatternTests : PatternMatchingTestBase
{
Copy link
Member

@jcouv jcouv Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ [](start = 4, length = 1)

I'd like to see the IL produced for at least one case. Thanks #WontFix

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blocking this PR: are we going to expose the ITuple-related information through public API? (ie. what semantic model scenario should be tested?)


In reply to: 205633476 [](ancestors = 205633476)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no design for semantic model support for pattern-matching for ITuple. It is hard to imagine an IDE scenario in which that would be helpful.


In reply to: 205633786 [](ancestors = 205633786,205633476)

Console.WriteLine(t is (3, 0, 5)); // false
Console.WriteLine(t is (3, 4, 5, 6)); // false
}
public void Deconstruct() {}
Copy link
Member

@jcouv jcouv Jul 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a variant of such scenario (where the existence of Deconstruct interferes with binding to ITuple) but the Deconstruct method has a non-void return type? #Resolved

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll finish reviewing tomorrow.

@jcouv jcouv self-assigned this Jul 29, 2018
return false;
}

if (((CSharpParseOptions)node.SyntaxTree.Options).LanguageVersion < MessageID.IDS_FeatureRecursivePatterns.RequiredVersion())
Copy link
Member

@jcouv jcouv Jul 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LanguageVersion [](start = 62, length = 15)

Feels like we should use the LanguageVersion from the compilation (ie. max of all trees) rather than from a single tree. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compilation's language version isn't the max. It requires that the trees have the same language version (else it throws an exception) and uses that. However, I agree that Compilation.LanguageVersion should be used here.


In reply to: 206289340 [](ancestors = 206289340)

);
}
}
}
Copy link
Member

@jcouv jcouv Jul 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test with obsolete ITuple interface? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but I wouldn't expect an error. The error would appear on the API that declares that it implements the interface.


In reply to: 206290578 [](ancestors = 206290578)

// declared to implement `ITuple` but contain no `Deconstruct` methods.
if (declType != (object)Compilation.GetSpecialType(SpecialType.System_Object) &&
declType != (object)Compilation.DynamicType &&
declType != (object)iTupleType &&
Copy link
Member

@jcouv jcouv Jul 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!= [](start = 25, length = 2)

I'm surprised we're using reference equality. Shouldn't we be using symbol equality (.Equals)? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are the same for "original" type symbols, which all of these are.


In reply to: 206291161 [](ancestors = 206291161)

ArrayBuilder<BoundPatternBinding> bindings)
{
var syntax = pattern.Syntax;
var patternLength = pattern.Deconstruction.Length;
Copy link
Member

@jcouv jcouv Jul 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var [](start = 12, length = 3)

nit: int #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it obvious that a length is an int? Why is this line different than all the others?


In reply to: 206292578 [](ancestors = 206292578)

Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a nit, but I don't see a good reason to use var for type names that are so short (like int or bool). For long type names I can see some benefits ;-) #Closed

Copy link
Member Author

@gafter gafter Jul 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see them as distracting unless they convey something relevant to the reader. #Closed

Copy link
Member

@jcouv jcouv left a 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 (iteration 1).

@gafter
Copy link
Member Author

gafter commented Jul 31, 2018

That is tracked by #28975 (there really isn't yet a separate doc for C# 8 pattern-matching changes).


In reply to: 408209102 [](ancestors = 408209102)

@gafter
Copy link
Member Author

gafter commented Jul 31, 2018

@jcouv I believe I've responded to all of your comments. #Resolved

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 2)

@@ -3409,6 +3423,8 @@ static WellKnownMembers()
"Ceiling", // System_Math__CeilingDouble
"Floor", // System_Math__FloorDouble
"Truncate", // System_Math__TruncateDouble
"get_Item", // System_Runtime_CompilerServices_ITuple__Index
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Index [](start = 103, length = 5)

Item or get_Item. (If get_Item, then Length should be get_Length.) #Resolved

@@ -5420,4 +5420,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_PointerTypeInPatternMatching" xml:space="preserve">
<value>Pattern-matching is not permitted for pointer types.</value>
</data>
<data name="ERR_ArgumentNameInITuplePattern" xml:space="preserve">
<value>Element names are not permitted when patttern-matching via 'System.Runtime.CompilerServices.ITuple'.</value>
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patttern [](start = 48, length = 8)

Typo #Resolved

var objectType = Compilation.GetSpecialType(SpecialType.System_Object);
var deconstruction = node.DeconstructionPatternClause;
var patterns = ArrayBuilder<BoundSubpattern>.GetInstance(deconstruction.Subpatterns.Count);
for (int i = 0; i < deconstruction.Subpatterns.Count; i++)
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for (int i = 0; i < deconstruction.Subpatterns.Count; i++) [](start = 12, length = 58)

Consider using foreach. #Resolved

BoundExpression deconstruct = MakeDeconstructInvocationExpression(
node.Subpatterns.Count, inputPlaceholder, node, diagnostics, outPlaceholders: out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders);
deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol;
if (deconstructMethod is null)
{
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider keeping else with a single return patterns.ToImmutableAndFree(); outside of the if / else. #Resolved

public override bool Equals(object obj) => obj is BoundDagIndexEvaluation other && this.Equals(other);
public override bool Equals(BoundDagEvaluation obj) => obj is BoundDagIndexEvaluation other && this.Equals(other);
public override int GetHashCode() => base.GetHashCode() ^ this.Index;
public bool Equals(BoundDagIndexEvaluation other)
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public bool Equals(BoundDagIndexEvaluation other) [](start = 8, length = 49)

Are we calling this overload directly, other than in overloads above? If not, then it seems like we can get away with one overload in this class.

public override bool Equals(BoundDagEvaluation obj)
{
    return base.Equals(other) &&
        this.Index == ((BoundDagIndexEvaluation).Index;
}
``` #Resolved

Console.WriteLine(t is (3, 4, 5)); // TRUE
Console.WriteLine(t is (3, 0, 5)); // false
Console.WriteLine(t is (3, 4, 5, 6)); // false
}
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} [](start = 4, length = 1)

Consider adding a case that is not ITuple:

Console.WriteLine(new object() is (3, 4, 5)); // false
``` #Resolved

{
public class ITuple
{
int Length => 3;
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int [](start = 8, length = 3)

Consider making these properties public so it's more obvious the failure is due to the class rather than inaccessible properties. #Resolved

{
M(new C());
}
public static void M<T>(T t) where T: C
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where T: C [](start = 33, length = 10)

Perhaps also test where T : ITuple and when T is unconstrained. #Resolved

@gafter
Copy link
Member Author

gafter commented Aug 1, 2018

@cston I have made changes to address all of your review comments. Do you have any other comments? #Resolved

if (subpatternSyntax.NameColon != null)
{
// error: name not permitted in ITuple deconstruction
diagnostics.Add(ErrorCode.ERR_ArgumentNameInITuplePattern, subpatternSyntax.NameColon.Location);
Copy link
Member

@cston cston Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diagnostics.Add(ErrorCode.ERR_ArgumentNameInITuplePattern, subpatternSyntax.NameColon.Location); [](start = 20, length = 96)

Please add a test for this case. #Resolved

@gafter
Copy link
Member Author

gafter commented Aug 7, 2018

@cston I have added the test you mentioned. Do you have any other comments? #Resolved

}
}
";
// Use a version of the platform APIs that lack ITuple
Copy link
Member

@cston cston Aug 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment correct? #Resolved

@gafter gafter closed this Aug 13, 2018
@gafter gafter reopened this Aug 13, 2018
@gafter gafter merged commit 10eaa84 into dotnet:features/recursive-patterns Aug 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants