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

Additional extract method cleanup. #76611

Merged
merged 32 commits into from
Jan 4, 2025

Conversation

CyrusNajmabadi
Copy link
Member

No description provided.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Jan 3, 2025
public override bool ContainsNonReturnExitPointsStatements(ImmutableArray<SyntaxNode> jumpsOutOfRegion)
=> jumpsOutOfRegion.Any(n => n is not ReturnStatementSyntax);
public override bool ContainsNonReturnExitPointsStatements(ImmutableArray<SyntaxNode> exitPoints)
=> exitPoints.Any(n => n is not ReturnStatementSyntax);
Copy link
Member Author

Choose a reason for hiding this comment

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

renaming to 'exitPoints' for consistency.

.CastArray<StatementSyntax>();
}
public override ImmutableArray<StatementSyntax> GetOuterReturnStatements(SyntaxNode commonRoot, ImmutableArray<SyntaxNode> exitPoints)
=> exitPoints.OfType<ReturnStatementSyntax>().ToImmutableArray().CastArray<StatementSyntax>();
Copy link
Member Author

Choose a reason for hiding this comment

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

so the prior logic was just completely unneccessary. it seems like it was written thinking that it would be passed all return-like statements in a span (for example, a return-statement inside an inner lambda), versus only the return statements out of that span. As such, it did all this complex work to filter down to only the actual return-statements returning from the span. Except that's what 'exit points' already gives you (plus things like goto/break/continue). So this can just be written to filter down to ReturnStatements and be done.

// make sure this method doesn't have return type.
return method.ReturnType is PredefinedTypeSyntax p &&
p.Keyword.Kind() == SyntaxKind.VoidKeyword;
return true;
Copy link
Member Author

Choose a reason for hiding this comment

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

none of hte above logic was relevant. we already ensured that all return-statements were not returning an expression (meaning they're all of the form return;). In which case one of two things are true:

  1. we're in a non-expr-returning function-like construct, which is fine.
  2. we're in a expr-returning function, and hte code is in error, which is also fine, we can proceed just fine, since we go into 'best effort mode' anyways and have already warned in that case.

In other words, we want to warn when the user's code was correct, but we don't know how to extract properly.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review January 3, 2025 18:12
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner January 3, 2025 18:12
@CyrusNajmabadi
Copy link
Member Author

@ToddGrun @JoeRobich ptal.

ISymbol symbol, ITypeSymbol type, VariableStyle style, bool variableDeclared)
{
return CreateFromSymbolCommon(symbol, type, style);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

this was a virtual to allow VB to specialize behavior. but that specialization could be pulled up to the caller.

@@ -712,7 +712,7 @@ [.. variableInfos.Select(v => Argument(v.ReturnBehavior == ReturnBehavior.Initia
}
}

protected override async Task<GeneratedCode> CreateGeneratedCodeAsync(
Copy link
Member Author

Choose a reason for hiding this comment

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

this type existed to hold onto a document and SyntaxAnnotations. but the syntax annotations could be static-readonlys. so no need for this wrapper.

}

// let's see whether this interface has coclass attribute
return info.ConvertedType.HasAttribute(coclassSymbol);
public override SyntaxNode GetOutermostCallSiteContainerToProcess(CancellationToken cancellationToken)
Copy link
Member Author

Choose a reason for hiding this comment

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

this wasa method in the base class that specialized itself for the expr vs statement case. but we have thse subclasses for that purpose. so i broke the method up and moved each part to the right subclass.

ParenthesizedLambdaExpressionSyntax lambda => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword,
SimpleLambdaExpressionSyntax lambda => lambda.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword,
AnonymousMethodExpressionSyntax anonymous => anonymous.AsyncKeyword.Kind() == SyntaxKind.AsyncKeyword,
LocalFunctionStatementSyntax localFunction => localFunction.Modifiers.Any(SyntaxKind.AsyncKeyword),
Copy link
Member Author

Choose a reason for hiding this comment

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

added this for completeness. figuring out how to add a test for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Just making sure that the lambda syntaxes were intentionally changed to always return false.

Copy link
Member Author

Choose a reason for hiding this comment

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

lambdas are subclasses of AnonymousFunctionExpressionSyntax .

@@ -52,9 +52,6 @@ public static async Task<CSharpSelectionResult> CreateAsync(
: new StatementResult(newDocument, selectionType, finalSpan);
}

protected override ISyntaxFacts SyntaxFacts
=> CSharpSyntaxFacts.Instance;
Copy link
Member Author

Choose a reason for hiding this comment

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

never used. removed.

ISymbol symbol,
ITypeSymbol type,
VariableStyle style)
{
Copy link
Member Author

Choose a reason for hiding this comment

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

moved from protected helper below to private local funciton here.

var visited = new HashSet<ITypeSymbol>();
var candidates = SpecializedCollections.EmptyEnumerable<ITypeParameterSymbol>();
using var _1 = PooledHashSet<ITypeSymbol>.GetInstance(out var visited);
using var _2 = PooledHashSet<ITypeParameterSymbol>.GetInstance(out var candidates);
Copy link
Member Author

Choose a reason for hiding this comment

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

switched to pooling, sets, and no longer concatting ienumerables.

Contract.ThrowIfTrue(IsExtractMethodOnExpression);

return GetFirstTokenInSelection().GetRequiredParent().AncestorsAndSelf().First(n =>
n is AccessorDeclarationSyntax or
LocalFunctionStatementSyntax or
BaseMethodDeclarationSyntax or
AccessorDeclarationSyntax or
Copy link
Member Author

Choose a reason for hiding this comment

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

duplicate in list.

internal sealed class InsertionPoint
{
private readonly SyntaxAnnotation _annotation;
private readonly Lazy<SyntaxNode?> _context;
Copy link
Member Author

Choose a reason for hiding this comment

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

unneeded type that existed to just hang onto an object and an annotation. like other places here and in previous prs, the annotation becomes static as there is only one of them ever.

@@ -36,7 +35,7 @@ protected sealed class AnalyzerResult(
{
public ImmutableArray<ITypeParameterSymbol> MethodTypeParametersInDeclaration { get; } = typeParametersInDeclaration;
public ImmutableArray<ITypeParameterSymbol> MethodTypeParametersInConstraintList { get; } = typeParametersInConstraintList;
public ImmutableArray<VariableInfo> VariablesToUseAsReturnValue { get; } = variablesToUseAsReturnValue;
public ImmutableArray<VariableInfo> VariablesToUseAsReturnValue { get; } = variables.WhereAsArray(v => v.UseAsReturnValue);
Copy link
Member Author

Choose a reason for hiding this comment

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

instead of making the callers do this, it is done once at this final position.

{
if (!symbolMap.TryGetValue(symbol, out var tokens))
var tokens = symbolMap[symbol];
Copy link
Contributor

Choose a reason for hiding this comment

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

var tokens = symbolMap[symbol];

Guess I haven't used MultiDictionary enough, threw me for a quick loop that the indexer doesn't throw if the key isn't present.

Copy link
Member Author

Choose a reason for hiding this comment

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

fair. but yeah, that is the semantics. it maps 'not there' to 'empty'

@@ -165,10 +166,10 @@ SyntaxNode InsertLocalFunction()

SyntaxNode InsertNormalMethod()
{
var syntaxKinds = document.GetLanguageService<ISyntaxKindsService>();
var syntaxKinds = document.GetRequiredLanguageService<ISyntaxKindsService>();
Copy link
Contributor

Choose a reason for hiding this comment

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

GetRequiredLanguageService

I would have expected this to have been done in response to removing the nullability pragma at the beginning of the file.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, i think i started removing nullability, then added back as it was too large.

@@ -29,6 +29,8 @@ internal abstract partial class MethodExtractor(
ExtractMethodGenerationOptions options,
bool localFunction)
{
public static readonly SyntaxAnnotation InsertionPointAnnotation = new();
Copy link
Contributor

Choose a reason for hiding this comment

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

InsertionPointAnnotation

Might be worth making a PR to remove the nullable pragmas if this code is all getting cleaned up anyways.

Copy link
Member Author

Choose a reason for hiding this comment

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

sure. can do in followup.

@@ -167,7 +165,7 @@ bool CanAddTo(Document document, SyntaxNode insertionPointNode, out OperationSta
return (formattedDocument, finalInvocationNameToken == default ? null : finalInvocationNameToken);
}

private static async Task<(SemanticDocument analyzedDocument, InsertionPoint insertionPoint)> GetAnnotatedDocumentAndInsertionPointAsync(
private static async Task<SemanticDocument> GetAnnotatedDocumentAndInsertionPointAsync(
Copy link
Contributor

Choose a reason for hiding this comment

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

GetAnnotatedDocumentAndInsertionPointAsync

Maybe rename the method?

Copy link
Member Author

Choose a reason for hiding this comment

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

sure. can do in followup.


public int CompareTo(ParameterVariableSymbol other)
protected static int DefaultCompareTo(ISymbol left, ISymbol right)
Copy link
Contributor

Choose a reason for hiding this comment

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

DefaultCompareTo

Why not have CompareTo be virtual and have this implementation (with ParameterVariableSymbol.CompareTo calling into the base instead of DefaultCompareTo)?

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 prefered forcing the subclasses to have to override, and decide what behavior they want :)

{
return 0;
}
var locationLeft = left.Locations.First();
Copy link
Contributor

Choose a reason for hiding this comment

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

.First()

nit: I like [0] better, makes it obvious that it's not needing to start an enumeration as the type of Locations isn't visible.

Copy link
Member Author

Choose a reason for hiding this comment

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

fair.


for (var i = 0; i < typeArguments.Count; i++)
for (var i = 0; i < typeArguments.Length; i++)
Copy link
Contributor

Choose a reason for hiding this comment

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

for (var i = 0; i < typeArguments.Length; i++)

nit: preference for me is to have the return false inside the condition. Always looks odd to me when a loop has the initial appearance of not looping more than once.

return firstStatement.GetRequiredParent();
}

if (this.IsExtractMethodOnMultipleStatements)
Copy link
Contributor

Choose a reason for hiding this comment

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

if (this.IsExtractMethodOnMultipleStatements)

personal preference: Contract.ThrowIfFalse and remove the if and Unreachable exception

Copy link
Member Author

Choose a reason for hiding this comment

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

can do in followup. tnx!

Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

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

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead VSCode
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants