InitializeParameter refactorings support for local & anonymous functions#24624
InitializeParameter refactorings support for local & anonymous functions#24624sharwell merged 21 commits intodotnet:masterfrom
Conversation
…ndling of return statement insertion
| } | ||
|
|
||
| private static StatementSyntax ConvertToStatement( | ||
| public static StatementSyntax ConvertToStatement( |
There was a problem hiding this comment.
This is obviously not ideal... The thing is that this method is also useful for converting expression lambdas to statements, not just expression-bodied members. Maybe it could be moved to ExpressionSyntaxExtensions and then there would be a separate class for lambda extensions? I didn't want to restructure the code significantly in my first contribution, especially in layers below that I wasn't supposed to touch, just to make a simple change in a refactoring.
There was a problem hiding this comment.
i would leave it here for now.
|
@CyrusNajmabadi could you please give me some feedback? there's a few rough spots I'm not sure what to do about |
| class C | ||
| public sub new() | ||
| dim f = sub (s as string) | ||
|
|
There was a problem hiding this comment.
All these blank lines in VB... the VB syntax generator does that for some reason, I have no idea what causes it.
There was a problem hiding this comment.
yeah... that's not good. we'd def want to fix this. or at least open a bug here. we don't do this for normal sub/function methods right?
There was a problem hiding this comment.
i checked again and it does create blank lines between the statements in methods, but not before the first one... in lambdas it also creates a blank line before the first statement.
In both cases (methods & lambdas) it does not create a blank line after the last statement - so if the check inserted is the last statement, there's no blank line after it, but if there's a return statement at the end, it creates a blank line before the return statement.
There was a problem hiding this comment.
I was suspicious this might have been caused by the call to Isolate (which calls PreserveTrivia) in VisualBasicSyntaxGenerator.WithStatements - there's no such thing in CSharpSyntaxGenerator, but that's not the case, I get same behavior if I directly call WithStatementsInternal. It must be some sort of formatting issue.
There was a problem hiding this comment.
@CyrusNajmabadi I identified the issue. It's in ElasticTriviaFormattingRule.TopLevelStatement. It's a one-line fix to add TypeOf statement Is LambdaHeaderSyntax. Should I include it in this PR?
There was a problem hiding this comment.
I'd have no problem with that.
| case AnonymousFunctionExpressionSyntax anonymousFunction: | ||
| return (SyntaxNode)anonymousFunction.Body; | ||
| default: | ||
| Debug.Fail($"Unexpected {nameof(functionDeclaration)} type"); |
| case AnonymousFunctionExpressionSyntax _: | ||
| return null; | ||
| default: | ||
| Debug.Fail($"Unexpected {nameof(functionDeclaration)} type"); |
| editor.SetStatements(functionDeclaration, statementToAddAfterOpt == null | ||
| ? ImmutableArray.Create(statement, convertedStatement) | ||
| : ImmutableArray.Create(convertedStatement, statement) | ||
| ); |
There was a problem hiding this comment.
put on previous line.
| { | ||
| switch(declarationSyntax) | ||
| // either expression lambda or expression bodied member | ||
| return body is ExpressionSyntax || body is ArrowExpressionClauseSyntax; |
There was a problem hiding this comment.
consider using => here.
|
|
||
| if (parameter.Name == "") | ||
| var method = (IMethodSymbol)parameter.ContainingSymbol; | ||
| if (method == null || |
There was a problem hiding this comment.
null check not needed.
| method.IsAbstract || | ||
| method.IsExtern || | ||
| method.PartialImplementationPart != null || | ||
| method.ContainingType?.TypeKind == TypeKind.Interface) |
| if (bodyOpt != null) | ||
| { | ||
| blockStatementOpt = semanticModel.GetOperation(bodyOpt, cancellationToken) as IBlockOperation; | ||
| if (blockStatementOpt == null) |
There was a problem hiding this comment.
this is just paranoia until IOperation is complete.
| SyntaxFactory.ReturnStatement(DirectCast(singleLineLambda.Body, ExpressionSyntax))) | ||
| return SyntaxFactory.List(ImmutableArray.Create(convertedStatement)) | ||
| Else | ||
| Debug.Fail($"Unexpected {NameOf(functionDeclaration)} type") |
|
Looking awesome! |
|
I found a bug: in VB lambdas the null checks don't get ordered correctly and are offered even if there is an existing one. The IOperation wireup must be wrong there. |
|
All fixed... I think this is ready |
| Sub Main | ||
| Dim a = Sub(x As Integer) | ||
|
|
||
| If True Then |
There was a problem hiding this comment.
these deletions are good. this PR also fixes a VB formatting bug around elastic trivia and lambdas.
| return InitializeParameterHelpers.TryConvertExpressionBodyToStatement(body, | ||
| semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken), | ||
| createReturnStatementForExpression: false, | ||
| statement: out var _); |
There was a problem hiding this comment.
do we have a test that exercises this codepath when TryConvertExpressionBodyToStatement returns false?
There was a problem hiding this comment.
| { | ||
| var bodyOpt = GetBody(functionDeclaration); | ||
| if (bodyOpt == null) | ||
| return null; |
There was a problem hiding this comment.
curlies always required.
| case LocalFunctionStatementSyntax localFunction: | ||
| return (SyntaxNode)localFunction.Body ?? localFunction.ExpressionBody; | ||
| case AnonymousFunctionExpressionSyntax anonymousFunction: | ||
| return (SyntaxNode)anonymousFunction.Body; |
There was a problem hiding this comment.
this cast should not be necessary.
There was a problem hiding this comment.
it's not, i wanted it to look the same as on previous lines... i'll remove it
|
@dotnet/roslyn-ide for another review. |
|
@sharwell too for community PR. |
|
@jasonmalinowski @dpoeschl This test is failing intermittently: CSharpCompletionCommandHandlerTests_Projections.TestInObjectCreationExpression. Could this be due to the recent changes going on around completion command handling? (Note: this is blocking a community PR). |
|
retest windows_coreclr_debug_prtest please |
|
retest windows_debug_spanish_unit32_prtest please |
| Option(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement))); | ||
| } | ||
|
|
||
| [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)] |
There was a problem hiding this comment.
💡 Would be good to see the new tests in this PR have a WorkItem attribute pointing to #20983.
| else if (condition is IIsPatternOperation isPatternOperation && | ||
| isPatternOperation.Pattern is IConstantPatternOperation constantPattern) | ||
| { | ||
| if (IsNullCheck(constantPattern.Value, isPatternOperation.Value, parameter)) |
There was a problem hiding this comment.
💡
// Look for code of the form "if (p is null)"| Private Function TopLevelStatement(statement As StatementSyntax) As Boolean | ||
| Return TypeOf statement Is MethodStatementSyntax OrElse | ||
| TypeOf statement Is SubNewStatementSyntax OrElse | ||
| TypeOf statement Is LambdaHeaderSyntax OrElse |
| Return containingMember | ||
| Public Shared Function IsFunctionDeclaration(node As SyntaxNode) As Boolean | ||
| Return TypeOf node Is MethodBlockBaseSyntax OrElse | ||
| TypeOf node Is LambdaExpressionSyntax |
There was a problem hiding this comment.
💭 The code could be simplified to ISyntaxFactsService if we could resolve the difference between TypeOf node Is MethodBlockBaseSyntax (which is VisualBasicSyntaxFactsService.IsBlockBody) and node is BaseMethodDeclarationSyntax (which is a bit different). The rest is just IsAnonymousFunction||IsLocalFunction (where the latter always returns false for VB).
There was a problem hiding this comment.
I would love to simplify this more but there's nothing I could come up with that would make this simpler (unless I created a new API, which of course would result in more code overall, not less)
| || node is LocalFunctionStatementSyntax | ||
| || node is AnonymousFunctionExpressionSyntax; | ||
|
|
||
| public static IBlockOperation GetBlockOperation(SyntaxNode functionDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken) |
There was a problem hiding this comment.
💭 It feels like some of the helpers could be unified, but it wasn't obvious how it would work so this seems fine for now.
There was a problem hiding this comment.
I tried a few approaches but nothing actually made things simpler. I don't think there's a unified way to get a block operation from a method-level declaration, local function or anonymous function, is there? Unfortunately I think this has to implemented separately.
|
@sharwell any thing pending here ? LGTM approved to merge for 15.8.preview3 |
|
I think this is my first PR 🎉 Thanks! |
fixes #20983