Skip to content

Relax the shape of lambdas in our syntax model to allow for a list of modifiers.#39118

Merged
RikkiGibson merged 9 commits intodotnet:features/static-lambdasfrom
CyrusNajmabadi:staticLambdas
Oct 14, 2019
Merged

Relax the shape of lambdas in our syntax model to allow for a list of modifiers.#39118
RikkiGibson merged 9 commits intodotnet:features/static-lambdasfrom
CyrusNajmabadi:staticLambdas

Conversation

@CyrusNajmabadi
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Oct 7, 2019

WIP: personal review. Now ready for review.

This is desirable if we add static lambdas (dotnet/csharplang#275) as it means we don't put unnecessary syntax model restriction on people writing static async a => ... or async static a => .... Note: the language may choose that only one of these is legal. But we won't restrict it in the model to make sure that parser resilience and modeling is high quality.

@CyrusNajmabadi
Copy link
Contributor Author

Tagging @jcouv . A precursor PR to adding support for static lambdas.

{
// first if there is a single list, then choose the list because it would not have been optional
int listCount = nd.Fields.Count(f => IsAnyNodeList(f.Type));
int listCount = nd.Fields.Count(f => IsAnyNodeList(f.Type) && !IsAttributeOrModifiersList(f));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the changes in SourceWriter are so that we don't generate this syntax factory overload:

public ParenthesizedLambdaExpression ParethesizedLambda(SyntaxTokenList modifiers = default) { ... }

We already have

public ParenthesizedLambdaExpression ParethesizedLambda() { ... }

Which this would conflict with. Note: this change was already made in the local-function-attributes branch as well. this just cherry-picks that bac to here.

Copy link
Member

Choose a reason for hiding this comment

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

I'm confused as to why there would be a = default optional value for the SyntaxTokenList when it is never optional. Is that just a special way that SyntaxTokenList gets handled in the generator? Sorry if this was already addressed when we pulled this into the other feature branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm confused as to why there would be a = default optional value for the SyntaxTokenList when it is never optional

So, first, Lists are always optional. That's because they can be 'empty' and thus can be elided.

We then have extra logic in our source generator to say: let me generate a bunch of helper factory methods that should try to balance determining which nodes/tokens we will generate on their behalf, and which nodes they need to provide.

Let's look at an example of that: BlockSyntax.

So a Block (prior to my attributes-on-statement work) was always: { statement* }.

So, the generator saw that and said: ah, you have a single list, and some required tokens. I will generate:

public BlockSyntax Block(SyntaxList<StatementSyntax> statements = default)

That's a really useful factory method. You can trivially make an empty block with Block() and you can pass some amount of statements into that to generate a block out of those statements.

The logic for this code however, was that we would do this for the first list we found (as long as there was only one), otherwise we would use the first optional node.

When I added attributes to statements (in the other PR), and now that i've added 'modifiers' to lambdas (in this PR), the source generator says: ah, i've found a list that i think should be the one to focus these helper factory functions around. For something like Block that would mean providing an entrypoint like:

public BlockSyntax Block(SyntaxList<AttributeListSyntax> attributeLists = default)

That's just literally not helpful. No one is going to say "i want an easy way to make a block, optionally deciding on some attributes for it.

In a very real sense, attributes and modifiers are low relevance. They can be on a lot of nodes, but they're never the primary concern people have when constructing them simply. So having the helper methods be targetted around them is not something we want.

Copy link
Member

Choose a reason for hiding this comment

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

Makes total sense, thanks, and this seems like a reasonable enough one-off way of handling it instead of adding an extra attribute to the AttributeLists and Modifiers elements to express this case.

@CyrusNajmabadi
Copy link
Contributor Author

@jcouv This should likely go in a feature branch. I don't have permission to do that. Could you maybe make one off of master called features/static-lambdas. I can then rebase this PR (and #39121) to that branch. Thanks! :)

@jcouv
Copy link
Member

jcouv commented Oct 8, 2019

@CyrusNajmabadi Branch features/static-lambdas created.

@CyrusNajmabadi CyrusNajmabadi changed the base branch from master to features/static-lambdas October 8, 2019 17:53
@RikkiGibson
Copy link
Member

added auto-merge config in dotnet/roslyn-tools@d1b2c49 😄

@CyrusNajmabadi
Copy link
Contributor Author

@jcouv This is ready for review. Thanks!

public new LambdaExpressionSyntax WithBody(CSharpSyntaxNode body)
=> body is BlockSyntax block
? WithBlock(block).WithExpressionBody(null)
: WithExpressionBody((ExpressionSyntax)body).WithBlock(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is just a move.

public new LambdaExpressionSyntax WithBody(CSharpSyntaxNode body)
=> body is BlockSyntax block
? WithBlock(block).WithExpressionBody(null)
: WithExpressionBody((ExpressionSyntax)body).WithBlock(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this moved to its own file.


anonymous_method_expression
: 'async'? 'delegate' parameter_list? block expression?
: modifier* 'delegate' parameter_list? block expression?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

in terms of grammar generator, we could consider adding information to Syntax.xml stating what subset of modifiers are legal. but no need to do that any time soon.

public AnonymousFunctionExpressionSyntax WithAsyncKeyword(SyntaxToken asyncKeyword)
=> WithAsyncKeywordCore(asyncKeyword);

internal abstract AnonymousFunctionExpressionSyntax WithAsyncKeywordCore(SyntaxToken asyncKeyword);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

a lot of these methods are to preserve the previous public API shape.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review October 8, 2019 18:51
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner October 8, 2019 18:51
@CyrusNajmabadi
Copy link
Contributor Author

Thanks @RikkiGibson

@jinujoseph jinujoseph added Area-Compilers Community The pull request was submitted by a contributor who is not a Microsoft employee. labels Oct 8, 2019
@RikkiGibson RikkiGibson added this to the Compiler.Next milestone Oct 8, 2019
@RikkiGibson RikkiGibson requested a review from a team October 8, 2019 21:22
@CyrusNajmabadi
Copy link
Contributor Author

@jcouv can you ptal for this feature-branch pr? Thanks! :)

}
}

private static bool IsAttributeOrModifiersList(Field f)
Copy link
Member

Choose a reason for hiding this comment

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

IsAttributeOrModifiersList [](start = 28, length = 26)

could be local function?

Copy link
Member

Choose a reason for hiding this comment

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

It will prevent conflicts with features/local-function-attributes to leave it as is. Maybe we can take care of it once one or the other feature branch is merged to master?

public override SyntaxToken AsyncKeyword
=> this.Modifiers.FirstOrDefault(SyntaxKind.AsyncKeyword);

internal override AnonymousFunctionExpressionSyntax WithAsyncKeywordCore(SyntaxToken asyncKeyword) => WithAsyncKeyword(asyncKeyword);
Copy link
Member

Choose a reason for hiding this comment

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

=> [](start = 107, length = 2)

nit: line break before => and also before next member?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These were actually copied out of the generated code. But sure, i don't mind tweaking this :)

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

@jcouv jcouv self-assigned this Oct 14, 2019
@CyrusNajmabadi
Copy link
Contributor Author

Would you be willing to merge in? the overhead on waiting for CI is so high :)

@RikkiGibson RikkiGibson merged commit 4dfb5a5 into dotnet:features/static-lambdas Oct 14, 2019
@RikkiGibson
Copy link
Member

Thanks @CyrusNajmabadi

@CyrusNajmabadi
Copy link
Contributor Author

Thank you!

@CyrusNajmabadi CyrusNajmabadi deleted the staticLambdas branch October 15, 2019 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Community The pull request was submitted by a contributor who is not a Microsoft employee.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants