Relax the shape of lambdas in our syntax model to allow for a list of modifiers.#39118
Conversation
src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs
Outdated
Show resolved
Hide resolved
|
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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 Branch |
|
added auto-merge config in dotnet/roslyn-tools@d1b2c49 😄 |
|
@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); |
There was a problem hiding this comment.
this is just a move.
| public new LambdaExpressionSyntax WithBody(CSharpSyntaxNode body) | ||
| => body is BlockSyntax block | ||
| ? WithBlock(block).WithExpressionBody(null) | ||
| : WithExpressionBody((ExpressionSyntax)body).WithBlock(null); |
There was a problem hiding this comment.
this moved to its own file.
|
|
||
| anonymous_method_expression | ||
| : 'async'? 'delegate' parameter_list? block expression? | ||
| : modifier* 'delegate' parameter_list? block expression? |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
a lot of these methods are to preserve the previous public API shape.
|
Thanks @RikkiGibson |
|
@jcouv can you ptal for this feature-branch pr? Thanks! :) |
| } | ||
| } | ||
|
|
||
| private static bool IsAttributeOrModifiersList(Field f) |
There was a problem hiding this comment.
IsAttributeOrModifiersList [](start = 28, length = 26)
could be local function?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
=> [](start = 107, length = 2)
nit: line break before => and also before next member?
There was a problem hiding this comment.
These were actually copied out of the generated code. But sure, i don't mind tweaking this :)
|
Would you be willing to merge in? the overhead on waiting for CI is so high :) |
|
Thanks @CyrusNajmabadi |
|
Thank you! |
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 => ...orasync 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.