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

Refactoring on inferred tuple names #18726

Merged
merged 21 commits into from
Apr 21, 2017

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Apr 15, 2017

@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

@jcouv jcouv self-assigned this Apr 15, 2017
@jcouv jcouv added this to the 15.3 milestone Apr 15, 2017
@jcouv jcouv force-pushed the tuple-names-refactoring branch from 022fa8a to 43d94a4 Compare April 15, 2017 07:10
@jcouv jcouv force-pushed the tuple-names-refactoring branch 2 times, most recently from 574ca5f to 5f49619 Compare April 15, 2017 18:43
@jcouv jcouv force-pushed the tuple-names-refactoring branch 2 times, most recently from 28857bf to 2d2e4f1 Compare April 16, 2017 17:02
@jcouv jcouv force-pushed the tuple-names-refactoring branch from 2d2e4f1 to 7cb9a9d Compare April 16, 2017 17:07
@@ -46,6 +46,16 @@ private static SyntaxNode GetParentNode(SyntaxNode node)
return GetParentNode(cref);
}

if (node.IsKind(SyntaxKind.Argument) && node.Parent.IsKind(SyntaxKind.TupleExpression))
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

Copy link
Member

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

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 don't see the allocation. SimplifyTupleName is static.

Copy link
Member

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.

Copy link
Member

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

Choose a reason for hiding this comment

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

ExtractAnonymousTypeMemberName maybe "GetInferredTupleOrAnonymousTypeMemerName? #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.

The name of the method matches the method I copied from compiler. I'd rather keep the name (or avoid copying the code entirely).

Copy link
Member

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

Copy link
Member

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

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'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?

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member Author

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

@CyrusNajmabadi CyrusNajmabadi Apr 16, 2017

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

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

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

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

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

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;
}

Copy link
Member

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

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

Choose a reason for hiding this comment

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

same comment about trivia.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Apr 21, 2017

LGTM. However, i had some tiny questions i'd like some clarifation on and some nits. #Resolved

@CyrusNajmabadi
Copy link
Member

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

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

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

@jcouv jcouv force-pushed the tuple-names-refactoring branch 2 times, most recently from fb0202f to 28e957d Compare April 21, 2017 21:43
Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

LGTM

@jcouv jcouv force-pushed the tuple-names-refactoring branch from 28e957d to cf698b2 Compare April 21, 2017 22:04
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsReservedTupleElementName(string elementName) -> bool
static Microsoft.CodeAnalysis.CSharp.SyntaxFacts.TryGetInferredMemberName(this Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax input) -> string
Copy link
Member

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

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

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.

@CyrusNajmabadi
Copy link
Member

:shipit:

@CyrusNajmabadi
Copy link
Member

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

Copy link
Member

@DustinCampbell DustinCampbell left a 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;
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. Thanks!

@jcouv jcouv merged this pull request into dotnet:features/tuple-names Apr 21, 2017
@jcouv jcouv deleted the tuple-names-refactoring branch April 21, 2017 23:58
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.

6 participants