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

InitializeParameter refactorings support for local & anonymous functions #24624

Merged
merged 21 commits into from
Jun 4, 2018

Conversation

Neme12
Copy link
Contributor

@Neme12 Neme12 commented Feb 4, 2018

fixes #20983

@Neme12 Neme12 requested a review from a team as a code owner February 4, 2018 01:54
@@ -48,7 +48,7 @@ internal static class ArrowExpressionClauseSyntaxExtensions
return true;
}

private static StatementSyntax ConvertToStatement(
public static StatementSyntax ConvertToStatement(
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 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.

Copy link
Member

Choose a reason for hiding this comment

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

i would leave it here for now.

@Neme12 Neme12 changed the title Support for local & anonymous functions in InitializeParameter refactorings InitializeParameter refactorings support for local & anonymous functions Feb 8, 2018
@Neme12
Copy link
Contributor Author

Neme12 commented Feb 8, 2018

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

Copy link
Contributor Author

@Neme12 Neme12 Feb 8, 2018

Choose a reason for hiding this comment

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

All these blank lines in VB... the VB syntax generator does that for some reason, I have no idea what causes it.

Copy link
Member

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

@Neme12 Neme12 Feb 8, 2018

Choose a reason for hiding this comment

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

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.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

Copy link
Member

Choose a reason for hiding this comment

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

I'd have no problem with that.

case AnonymousFunctionExpressionSyntax anonymousFunction:
return (SyntaxNode)anonymousFunction.Body;
default:
Debug.Fail($"Unexpected {nameof(functionDeclaration)} type");
Copy link
Member

Choose a reason for hiding this comment

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

Throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

case AnonymousFunctionExpressionSyntax _:
return null;
default:
Debug.Fail($"Unexpected {nameof(functionDeclaration)} type");
Copy link
Member

Choose a reason for hiding this comment

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

throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

editor.SetStatements(functionDeclaration, statementToAddAfterOpt == null
? ImmutableArray.Create(statement, convertedStatement)
: ImmutableArray.Create(convertedStatement, statement)
);
Copy link
Member

Choose a reason for hiding this comment

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

put on previous line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just the );?

Copy link
Member

Choose a reason for hiding this comment

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

yup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

{
switch(declarationSyntax)
// either expression lambda or expression bodied member
return body is ExpressionSyntax || body is ArrowExpressionClauseSyntax;
Copy link
Member

Choose a reason for hiding this comment

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

consider using => here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

{
return;
}

if (parameter.Name == "")
var method = (IMethodSymbol)parameter.ContainingSymbol;
if (method == null ||
Copy link
Member

Choose a reason for hiding this comment

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

null check not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

method.IsAbstract ||
method.IsExtern ||
method.PartialImplementationPart != null ||
method.ContainingType?.TypeKind == TypeKind.Interface)
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -107,16 +104,12 @@ internal abstract partial class AbstractInitializeParameterCodeRefactoringProvid
if (bodyOpt != null)
{
blockStatementOpt = semanticModel.GetOperation(bodyOpt, cancellationToken) as IBlockOperation;
if (blockStatementOpt == null)
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 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")
Copy link
Member

Choose a reason for hiding this comment

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

throw.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@CyrusNajmabadi
Copy link
Member

Looking awesome!

@jinujoseph jinujoseph added this to the 15.8 milestone Feb 9, 2018
@Neme12
Copy link
Contributor Author

Neme12 commented Feb 9, 2018

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.

@Neme12
Copy link
Contributor Author

Neme12 commented Feb 9, 2018

All fixed... I think this is ready

@@ -1282,7 +1282,6 @@ End Module",
Module Program
Sub Main
Dim a = Sub(x As Integer)

If True Then
Copy link
Member

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

do we have a test that exercises this codepath when TryConvertExpressionBodyToStatement returns false?

Copy link
Contributor Author

@Neme12 Neme12 Feb 9, 2018

Choose a reason for hiding this comment

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

yes:

public async Task TestMissingInArrowExpression1()

Copy link
Member

Choose a reason for hiding this comment

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

great. thanks!

{
var bodyOpt = GetBody(functionDeclaration);
if (bodyOpt == null)
return null;
Copy link
Member

Choose a reason for hiding this comment

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

curlies always required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

:( ok

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

case LocalFunctionStatementSyntax localFunction:
return (SyntaxNode)localFunction.Body ?? localFunction.ExpressionBody;
case AnonymousFunctionExpressionSyntax anonymousFunction:
return (SyntaxNode)anonymousFunction.Body;
Copy link
Member

Choose a reason for hiding this comment

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

this cast should not be necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's not, i wanted it to look the same as on previous lines... i'll remove it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

@Neme12
Copy link
Contributor Author

Neme12 commented Mar 29, 2018

retest windows_coreclr_debug_prtest please

@jcouv jcouv added the Area-IDE label Mar 30, 2018
@Neme12
Copy link
Contributor Author

Neme12 commented Apr 11, 2018

retest windows_debug_spanish_unit32_prtest please

@@ -462,6 +462,150 @@ public C(string s)
Option(CSharpCodeStyleOptions.PreferExpressionBodiedConstructors, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement)));
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
Copy link
Member

Choose a reason for hiding this comment

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

💡 Would be good to see the new tests in this PR have a WorkItem attribute pointing to #20983.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added in d5dbed4

else if (condition is IIsPatternOperation isPatternOperation &&
isPatternOperation.Pattern is IConstantPatternOperation constantPattern)
{
if (IsNullCheck(constantPattern.Value, isPatternOperation.Value, parameter))
Copy link
Member

Choose a reason for hiding this comment

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

💡

// Look for code of the form "if (p is 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.

added in 9876619

@@ -370,6 +370,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting
Private Function TopLevelStatement(statement As StatementSyntax) As Boolean
Return TypeOf statement Is MethodStatementSyntax OrElse
TypeOf statement Is SubNewStatementSyntax OrElse
TypeOf statement Is LambdaHeaderSyntax OrElse
Copy link
Member

Choose a reason for hiding this comment

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

🎉

Return containingMember
Public Shared Function IsFunctionDeclaration(node As SyntaxNode) As Boolean
Return TypeOf node Is MethodBlockBaseSyntax OrElse
TypeOf node Is LambdaExpressionSyntax
Copy link
Member

Choose a reason for hiding this comment

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

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

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

Choose a reason for hiding this comment

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

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

Copy link
Contributor Author

@Neme12 Neme12 May 28, 2018

Choose a reason for hiding this comment

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

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.

@jinujoseph
Copy link
Contributor

@sharwell any thing pending here ? LGTM approved to merge for 15.8.preview3

@sharwell sharwell merged commit 419ba0a into dotnet:master Jun 4, 2018
@Neme12 Neme12 deleted the initialize-lambdas branch June 4, 2018 13:17
@Neme12
Copy link
Contributor Author

Neme12 commented Jun 4, 2018

I think this is my first PR 🎉 Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Approved to merge Area-IDE 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.

No suggestion to add null check for inner function
5 participants