-
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
Refactoring on inferred tuple names #18726
Refactoring on inferred tuple names #18726
Conversation
022fa8a
to
43d94a4
Compare
574ca5f
to
5f49619
Compare
28857bf
to
2d2e4f1
Compare
2d2e4f1
to
7cb9a9d
Compare
@@ -46,6 +46,16 @@ private static SyntaxNode GetParentNode(SyntaxNode node) | |||
return GetParentNode(cref); | |||
} | |||
|
|||
if (node.IsKind(SyntaxKind.Argument) && node.Parent.IsKind(SyntaxKind.TupleExpression)) |
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.
arguments can be parented by tuple expressions? #Resolved
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.
got it. i didn't realize you reused ArgumentSyntax here. nifty :)
return SimplifyExpression( | ||
node, | ||
newNode: base.VisitArgument(node), | ||
simplifier: SimplifyTupleName); |
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 waht we do elsewhere, but these delegate allocations make me super sad :) #Resolved
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 don't see the allocation. SimplifyTupleName
is static.
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.
Unless something changed and we are now caching static methods, then this will be an allocation. I was under the impression that we had not done the optimization to cache these yet.
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 waht we do elsewhere, but these delegate allocations make me super sad :)
Says the guy who lives for IEnumerable<T>.Do(...)
extension methods. 😉
{ | ||
string inferredName = node.IsParentKind(SyntaxKind.TupleExpression) ? | ||
CSharpSimplificationService.ExtractAnonymousTypeMemberName(node.Expression).ValueText : | ||
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.
ide style is:
var inferredName = node.IsParentKind(SyntaxKind.TupleExpression)
? CSharpSimplificationService.ExtractAnonymousTypeMemberName(node.Expression).ValueText
: null;
``` #Resolved
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.
ExtractAnonymousTypeMemberName maybe "GetInferredTupleOrAnonymousTypeMemerName? #Resolved
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.
The name of the method matches the method I copied from compiler. I'd rather keep the name (or avoid copying the code entirely).
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.
Or just rename hte compiler method name :) But it's no biggie.
return node; | ||
} | ||
|
||
return node.WithNameColon(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.
probably needs to be node.WithNameColon(null).WithTriviaFrom(node). #Resolved
} | ||
|
||
private static ArgumentSyntax SimplifyTupleName(ArgumentSyntax node, SemanticModel semanticModel, OptionSet optionSet, CancellationToken cancellationToken) | ||
{ |
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.
can we bail out immediately if the node doesn't even have an name-colon? #Resolved
|
||
private static SyntaxNode SimplifyAnonymousTypeMemberName(AnonymousObjectMemberDeclaratorSyntax node, SemanticModel semanticModel, OptionSet optionSet, CancellationToken canellationToken) | ||
{ | ||
string inferredName = CSharpSimplificationService.ExtractAnonymousTypeMemberName(node.Expression).ValueText; |
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 #Resolved
{ | ||
string inferredName = CSharpSimplificationService.ExtractAnonymousTypeMemberName(node.Expression).ValueText; | ||
|
||
if (inferredName == null || inferredName != node?.NameEquals?.Name.Identifier.ValueText) |
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.
if it can return null, it should be TryGetXXX #Resolved
return node; | ||
} | ||
|
||
return node.WithNameEquals(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.
same comment about trivia. #Resolved
if (node.Parent.IsKind(SyntaxKind.TupleExpression)) | ||
{ | ||
var tuple = (TupleExpressionSyntax)node.Parent; | ||
var nonDuplicateTupleNames = ExtractTupleNames(tuple); |
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 allocating a list per argument, then checking each argument against that list? #Resolved
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.
If so, i'd prefer a non-allocating way of doing all of this. #Resolved
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'm thinking of having the Expander cache those. I'd create a map from tuple to hashset of names. It will be lazily populated, and entries can be removed once the visitor is done with a tuple.
Does that sound ok?
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.
so, i've been needing to go through and basically address the fact that the simplifier is a memory hog. So i'd prefer we remove unnecessary allocations. Having a set created for every tuple, which will likely never contain more than 4 items in it just seems super wasteful.
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.
(and i actually believe just linearly scanning the tuple will be faster in practice). This ia case where big-O may say something, but actual practical constants impact you.
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.
Based on your comment below, I'll change to no allocation.
{ | ||
var identifier = SyntaxFactory.Identifier(inferredName); | ||
identifier = TryEscapeIdentifierToken(identifier, node, _semanticModel); | ||
newArgument = newArgument.WithNameColon(SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(identifier))) |
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 probably want to move the leading trivia of the argument to the name-colon. #Resolved
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 added a test with trivia, but it seems to be attached to the open paren. I'm not sure how to get trivia attached to the first tuple element.
@@ -271,6 +290,46 @@ public override SyntaxNode VisitArgument(ArgumentSyntax node) | |||
return newArgument; | |||
} | |||
|
|||
// PROTOTYPE(tuple-names) this can be memoized |
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 wouldn't even allocate/memoize here. tuples are going to be small enough that you can check if the name you care about shows up in the tuple exactly once quite cheaply. #Resolved
var identifier = SyntaxFactory.Identifier(inferredName); | ||
identifier = TryEscapeIdentifierToken(identifier, node, _semanticModel); | ||
newDeclarator = newDeclarator.WithNameEquals(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(identifier))) | ||
.WithAdditionalAnnotations(Simplifier.Annotation); |
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.
same comment about triivia. #Resolved
newDeclarator = newDeclarator.WithNameEquals(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(identifier))) | ||
.WithAdditionalAnnotations(Simplifier.Annotation); | ||
} | ||
return newDeclarator; |
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.
blank line after }
|
||
var newArgument = (ArgumentSyntax)base.VisitArgument(node); | ||
|
||
if (inferredName != null && node.NameColon == 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.
You only needed the inferred name if nameColon was not null. So you should move that outer code in here. (though likely a helper method would be good. but htat helper method should be called inside this if block.
{ | ||
var identifier = SyntaxFactory.Identifier(inferredName); | ||
identifier = TryEscapeIdentifierToken(identifier, node, _semanticModel); | ||
newArgument = newArgument.WithLeadingTrivia(SyntaxTriviaList.Empty) |
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 think there's a WithoutLeadingTrivia.
Also, consider just saying newArgument....WithTriviaFrom(newArgument).
bool found = false; | ||
foreach (var argument in tuple.Arguments) | ||
{ | ||
string elementName = 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.
consider ?: expression here.
} | ||
else | ||
{ | ||
elementName = argument.Expression.TryGetInferredMemberName().ValueText; |
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.
ValueText is safe on a default tuple?
} | ||
found = true; | ||
} | ||
|
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.
extra newline.
|
||
public override SyntaxNode VisitAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax node) | ||
{ | ||
var inferredName = node.Expression.TryGetInferredMemberName().ValueText; |
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.
not needed until you verify that .NameEquals is null. move inside lower if block.
.WithLeadingTrivia(SyntaxTriviaList.Empty) | ||
.WithNameEquals(SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName(identifier)) | ||
.WithLeadingTrivia(node.GetLeadingTrivia())) | ||
.WithAdditionalAnnotations(Simplifier.Annotation); |
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.
same comment about trivia.
LGTM. However, i had some tiny questions i'd like some clarifation on and some nits. #Resolved |
Also, will need @jaredpar signoff on the public API change. That said, i now like the new API and think it's valuable to have |
static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.TryParse(this string version, out Microsoft.CodeAnalysis.CSharp.LanguageVersion result) -> bool | ||
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsReservedTupleElementName(string elementName, out int index) -> bool |
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 just exposing the form that doesn't return the index. THe index seems to be just internally by the compiler right now right? You only discard it on the IDE side. So really, all we're exposign is the generally useful concept of "is this name reserved inside a tuple".
static Microsoft.CodeAnalysis.CSharp.LanguageVersionFacts.TryParse(this string version, out Microsoft.CodeAnalysis.CSharp.LanguageVersion result) -> bool | ||
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsReservedTupleElementName(string elementName, out int index) -> bool | ||
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.TryGetInferredMemberName(this Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax input) -> Microsoft.CodeAnalysis.SyntaxToken |
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 think you only ever use .ValueText on hte IDE side. As such, consider just exposing a method that returns the string value (which also fits the name a lot better).
fb0202f
to
28e957d
Compare
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
28e957d
to
cf698b2
Compare
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsReservedTupleElementName(string elementName) -> bool | ||
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.TryGetInferredMemberName(this Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax input) -> string |
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.
nice. love the new APIs.
@@ -311,7 +306,7 @@ private static bool CanMakeNameExplicitInTuple(TupleExpressionSyntax tuple, stri | |||
} | |||
else | |||
{ | |||
elementName = argument.Expression.TryGetInferredMemberName().ValueText; | |||
elementName = argument.Expression?.TryGetInferredMemberName(); |
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.
dont' think expression can be null. but i don't mind being careful.
return false; | ||
} | ||
} | ||
while (true); |
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.
consdier making this "while (parent != null)". Consider moving the return false
out of hte do-while.
@DustinCampbell I feel that your concerns have been well addressed by julien. Are you ok with him checking in at this point? Or do you want to rereview? 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.
Looks much better. Thanks for taking the extra time to get things right.
{ | ||
return false; | ||
} | ||
parent = tuple.IsKind(SyntaxKind.TupleExpression) ? parent.Parent.Parent : 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.
i just realized this seems wrong. ```a = (b, c).d" would say that it's a tuple deconstruction withthis logic right? i think you want to verify that parent.Parent is an ArgumentSyntax.
Or, at least explain what you're doing parent.parent.parent in terms of what yuo're trying to skip over.
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.
Good catch. Thanks!
@CyrusNajmabadi
I took a rough stab at this.
I'm not really sure about handling trivia and null parents.
This is not handling deconstructions yet (need some LDM input), or VB.
Relates to #18480