Skip to content

Commit 39e3df4

Browse files
Merge remote-tracking branch 'upstream/main' into extractMethodShareCode
2 parents d75054e + 56a3e41 commit 39e3df4

17 files changed

+201
-265
lines changed

.github/workflows/backport.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Backport PR to branch
2+
on:
3+
issue_comment:
4+
types: [created]
5+
schedule:
6+
# once a day at 13:00 UTC to cleanup old runs
7+
- cron: '0 13 * * *'
8+
9+
permissions:
10+
contents: write
11+
issues: write
12+
pull-requests: write
13+
actions: write
14+
15+
jobs:
16+
backport:
17+
if: ${{ contains(github.event.comment.body, '/backport to') || github.event_name == 'schedule' }}
18+
uses: dotnet/arcade/.github/workflows/backport-base.yml@main
19+
with:
20+
pr_description_template: |
21+
Backport of #%source_pr_number% to %target_branch%
22+
23+
/cc %cc_users%
24+
25+
## Customer Impact
26+
27+
## Regression
28+
29+
- [ ] Yes
30+
- [ ] No
31+
32+
[If yes, specify when the regression was introduced. Provide the PR or commit if known.]
33+
34+
## Testing
35+
36+
[How was the fix verified? How was the issue missed previously? What tests were added?]
37+
38+
## Risk
39+
40+
[High/Medium/Low. Justify the indication by mentioning how risks were measured and addressed.]

eng/Version.Details.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
<SourceBuild RepoName="source-build-externals" ManagedOnly="true" />
99
</Dependency>
1010
<!-- Intermediate is necessary for source build. -->
11-
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="10.0.561001">
11+
<Dependency Name="Microsoft.SourceBuild.Intermediate.source-build-reference-packages" Version="10.0.605602">
1212
<Uri>https://github.com/dotnet/source-build-reference-packages</Uri>
13-
<Sha>94798e07efab2663f2d1a71862780bc365d2e3ab</Sha>
13+
<Sha>72009fb6ce7327430539004be1dcbfb6fb88adab</Sha>
1414
<SourceBuild RepoName="source-build-reference-packages" ManagedOnly="true" />
1515
</Dependency>
1616
<Dependency Name="System.CommandLine" Version="2.0.0-beta4.24528.1">

src/Compilers/CSharp/Portable/Parser/LanguageParser.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10123,11 +10123,12 @@ private StatementSyntax ParseLocalDeclarationStatement(SyntaxList<AttributeListS
1012310123
awaitKeyword,
1012410124
usingKeyword,
1012510125
mods.ToList(),
10126-
_syntaxFactory.VariableDeclaration(type, _pool.ToListAndFree(variables)),
10126+
_syntaxFactory.VariableDeclaration(type, variables.ToList()),
1012710127
this.EatToken(SyntaxKind.SemicolonToken));
1012810128
}
1012910129
finally
1013010130
{
10131+
_pool.Free(variables);
1013110132
_pool.Free(mods);
1013210133
}
1013310134
}

src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.ExpressionCodeGenerator.cs

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -91,31 +91,16 @@ protected override ImmutableArray<StatementSyntax> GetInitialStatementsForMethod
9191
Contract.ThrowIfFalse(this.SelectionResult.IsExtractMethodOnExpression);
9292

9393
// special case for array initializer
94-
var returnType = AnalyzerResult.ReturnType;
95-
var containingScope = this.SelectionResult.GetContainingScope();
96-
97-
ExpressionSyntax expression;
98-
if (returnType.TypeKind == TypeKind.Array && containingScope is InitializerExpressionSyntax)
99-
{
100-
var typeSyntax = returnType.GenerateTypeSyntax();
94+
var returnType = AnalyzerResult.CoreReturnType;
95+
var containingScope = (ExpressionSyntax)this.SelectionResult.GetContainingScope();
10196

102-
expression = SyntaxFactory.ArrayCreationExpression(typeSyntax as ArrayTypeSyntax, containingScope as InitializerExpressionSyntax);
103-
}
104-
else
105-
{
106-
expression = containingScope as ExpressionSyntax;
107-
}
97+
var expression = returnType.TypeKind == TypeKind.Array && containingScope is InitializerExpressionSyntax initializerExpression
98+
? SyntaxFactory.ArrayCreationExpression((ArrayTypeSyntax)returnType.GenerateTypeSyntax(), initializerExpression)
99+
: containingScope;
108100

109-
if (AnalyzerResult.HasReturnType)
110-
{
111-
return [SyntaxFactory.ReturnStatement(
112-
WrapInCheckedExpressionIfNeeded(expression))];
113-
}
114-
else
115-
{
116-
return [SyntaxFactory.ExpressionStatement(
117-
WrapInCheckedExpressionIfNeeded(expression))];
118-
}
101+
return AnalyzerResult.CoreReturnType.SpecialType != SpecialType.System_Void
102+
? [SyntaxFactory.ReturnStatement(WrapInCheckedExpressionIfNeeded(expression))]
103+
: [SyntaxFactory.ExpressionStatement(WrapInCheckedExpressionIfNeeded(expression))];
119104
}
120105

121106
private ExpressionSyntax WrapInCheckedExpressionIfNeeded(ExpressionSyntax expression)

src/Features/CSharp/Portable/ExtractMethod/CSharpMethodExtractor.CSharpCodeGenerator.cs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ protected override IMethodSymbol GenerateMethodDefinition(
8989
attributes: [],
9090
accessibility: Accessibility.Private,
9191
modifiers: CreateMethodModifiers(),
92-
returnType: AnalyzerResult.ReturnType,
92+
returnType: this.GetFinalReturnType(),
9393
refKind: AnalyzerResult.ReturnsByRef ? RefKind.Ref : RefKind.None,
9494
explicitInterfaceImplementations: default,
9595
name: _methodName.ToString(),
@@ -209,7 +209,7 @@ private bool ShouldPutUnsafeModifier()
209209
private DeclarationModifiers CreateMethodModifiers()
210210
{
211211
var isUnsafe = ShouldPutUnsafeModifier();
212-
var isAsync = this.SelectionResult.CreateAsyncMethod();
212+
var isAsync = this.SelectionResult.ContainsAwaitExpression();
213213
var isStatic = !AnalyzerResult.UseInstanceMember;
214214
var isReadOnly = AnalyzerResult.ShouldBeReadOnly;
215215

@@ -596,23 +596,20 @@ protected override ExpressionSyntax CreateCallSignature()
596596
}
597597

598598
var invocation = (ExpressionSyntax)InvocationExpression(methodExpression, ArgumentList([.. arguments]));
599-
if (this.SelectionResult.CreateAsyncMethod())
599+
600+
// If we're extracting any code that contained an 'await' then we'll have to await the new method we're
601+
// calling as well. If we also see any use of .ConfigureAwait(false) in the extracted code, keep that
602+
// pattern on the await expression we produce.
603+
if (this.SelectionResult.ContainsAwaitExpression())
600604
{
601-
if (this.SelectionResult.ShouldCallConfigureAwaitFalse())
605+
if (this.SelectionResult.ContainsConfigureAwaitFalse())
602606
{
603-
if (AnalyzerResult.ReturnType.GetMembers().Any(static x => x is IMethodSymbol
604-
{
605-
Name: nameof(Task.ConfigureAwait),
606-
Parameters: [{ Type.SpecialType: SpecialType.System_Boolean }],
607-
}))
608-
{
609-
invocation = InvocationExpression(
610-
MemberAccessExpression(
611-
SyntaxKind.SimpleMemberAccessExpression,
612-
invocation,
613-
IdentifierName(nameof(Task.ConfigureAwait))),
614-
ArgumentList([Argument(LiteralExpression(SyntaxKind.FalseLiteralExpression))]));
615-
}
607+
invocation = InvocationExpression(
608+
MemberAccessExpression(
609+
SyntaxKind.SimpleMemberAccessExpression,
610+
invocation,
611+
IdentifierName(nameof(Task.ConfigureAwait))),
612+
ArgumentList([Argument(LiteralExpression(SyntaxKind.FalseLiteralExpression))]));
616613
}
617614

618615
invocation = AwaitExpression(invocation);

src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.StatementResult.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,13 @@ private sealed class StatementResult(
2727
: CSharpSelectionResult(document, selectionType, finalSpan)
2828
{
2929
public override bool ContainingScopeHasAsyncKeyword()
30-
{
31-
var node = GetContainingScope();
32-
33-
return node switch
30+
=> GetContainingScope() switch
3431
{
3532
MethodDeclarationSyntax method => method.Modifiers.Any(SyntaxKind.AsyncKeyword),
3633
LocalFunctionStatementSyntax localFunction => localFunction.Modifiers.Any(SyntaxKind.AsyncKeyword),
3734
AnonymousFunctionExpressionSyntax anonymousFunction => anonymousFunction.AsyncKeyword != default,
3835
_ => false,
3936
};
40-
}
4137

4238
public override SyntaxNode GetContainingScope()
4339
{

src/Features/CSharp/Portable/ExtractMethod/CSharpSelectionResult.cs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
using System.Threading.Tasks;
1111
using Microsoft.CodeAnalysis;
1212
using Microsoft.CodeAnalysis.CSharp.Extensions;
13-
using Microsoft.CodeAnalysis.CSharp.LanguageService;
1413
using Microsoft.CodeAnalysis.CSharp.Syntax;
1514
using Microsoft.CodeAnalysis.ExtractMethod;
16-
using Microsoft.CodeAnalysis.LanguageService;
1715
using Microsoft.CodeAnalysis.Shared.Extensions;
1816
using Microsoft.CodeAnalysis.Text;
1917
using Roslyn.Utilities;
@@ -68,27 +66,6 @@ protected override SyntaxNode GetNodeForDataFlowAnalysis()
6866
: node;
6967
}
7068

71-
protected override bool UnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken)
72-
=> IsUnderAnonymousOrLocalMethod(token, firstToken, lastToken);
73-
74-
public static bool IsUnderAnonymousOrLocalMethod(SyntaxToken token, SyntaxToken firstToken, SyntaxToken lastToken)
75-
{
76-
for (var current = token.Parent; current != null; current = current.Parent)
77-
{
78-
if (current is MemberDeclarationSyntax)
79-
return false;
80-
81-
if (current is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax)
82-
{
83-
// make sure the selection contains the lambda
84-
return firstToken.SpanStart <= current.GetFirstToken().SpanStart &&
85-
current.GetLastToken().Span.End <= lastToken.Span.End;
86-
}
87-
}
88-
89-
return false;
90-
}
91-
9269
public override StatementSyntax GetFirstStatementUnderContainer()
9370
{
9471
Contract.ThrowIfTrue(IsExtractMethodOnExpression);

src/Features/Core/Portable/ExtractMethod/MethodExtractor.Analyzer.cs

Lines changed: 39 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protected virtual bool IsReadOutside(ISymbol symbol, HashSet<ISymbol> readOutsid
7171
public AnalyzerResult Analyze()
7272
{
7373
// do data flow analysis
74-
var model = this.SemanticDocument.SemanticModel;
74+
var model = this.SemanticModel;
7575
var dataFlowAnalysisData = this.SelectionResult.GetDataFlowAnalysis();
7676

7777
// build symbol map for the identifiers used inside of the selection
@@ -127,8 +127,6 @@ public AnalyzerResult Analyze()
127127

128128
var (variables, returnType, returnsByRef) = GetSignatureInformation(variableInfoMap);
129129

130-
(returnType, var awaitTaskReturn) = AdjustReturnType(returnType);
131-
132130
// collect method type variable used in selected code
133131
var sortedMap = new SortedDictionary<int, ITypeParameterSymbol>();
134132
var typeParametersInConstraintList = GetMethodTypeParametersInConstraintList(variableInfoMap, symbolMap, sortedMap);
@@ -144,69 +142,12 @@ public AnalyzerResult Analyze()
144142
variables,
145143
returnType,
146144
returnsByRef,
147-
awaitTaskReturn,
148145
instanceMemberIsUsed,
149146
shouldBeReadOnly,
150147
endOfSelectionReachable,
151148
operationStatus);
152149
}
153150

154-
private (ITypeSymbol typeSymbol, bool awaitTaskReturn) AdjustReturnType(ITypeSymbol returnType)
155-
{
156-
// if selection contains await which is not under async lambda or anonymous delegate,
157-
// change return type to be wrapped in Task
158-
var shouldPutAsyncModifier = SelectionResult.CreateAsyncMethod();
159-
if (shouldPutAsyncModifier)
160-
return WrapReturnTypeInTask(returnType);
161-
162-
// unwrap task if needed
163-
return (UnwrapTaskIfNeeded(returnType), awaitTaskReturn: false);
164-
}
165-
166-
private ITypeSymbol UnwrapTaskIfNeeded(ITypeSymbol returnType)
167-
{
168-
// nothing to unwrap
169-
if (SelectionResult.ContainingScopeHasAsyncKeyword() &&
170-
ContainsReturnStatementInSelectedCode())
171-
{
172-
var originalDefinition = returnType.OriginalDefinition;
173-
174-
// see whether it needs to be unwrapped
175-
var model = this.SemanticDocument.SemanticModel;
176-
var taskType = model.Compilation.TaskType();
177-
if (originalDefinition.Equals(taskType))
178-
return model.Compilation.GetSpecialType(SpecialType.System_Void);
179-
180-
var genericTaskType = model.Compilation.TaskOfTType();
181-
if (originalDefinition.Equals(genericTaskType))
182-
return ((INamedTypeSymbol)returnType).TypeArguments[0];
183-
}
184-
185-
// nothing to unwrap
186-
return returnType;
187-
}
188-
189-
private (ITypeSymbol returnType, bool awaitTaskReturn) WrapReturnTypeInTask(ITypeSymbol returnType)
190-
{
191-
var compilation = this.SemanticModel.Compilation;
192-
var taskType = compilation.TaskType();
193-
194-
// convert void to Task type
195-
if (taskType is object && returnType.Equals(compilation.GetSpecialType(SpecialType.System_Void)))
196-
return (taskType, awaitTaskReturn: true);
197-
198-
if (!SelectionResult.IsExtractMethodOnExpression && ContainsReturnStatementInSelectedCode())
199-
return (returnType, awaitTaskReturn: false);
200-
201-
var genericTaskType = compilation.TaskOfTType();
202-
203-
// okay, wrap the return type in Task<T>
204-
if (genericTaskType is object)
205-
returnType = genericTaskType.Construct(returnType);
206-
207-
return (returnType, awaitTaskReturn: false);
208-
}
209-
210151
private (ImmutableArray<VariableInfo> finalOrderedVariableInfos, ITypeSymbol returnType, bool returnsByRef)
211152
GetSignatureInformation(Dictionary<ISymbol, VariableInfo> symbolMap)
212153
{
@@ -217,7 +158,7 @@ private ITypeSymbol UnwrapTaskIfNeeded(ITypeSymbol returnType)
217158
// check whether current selection contains return statement
218159
var (returnType, returnsByRef) = SelectionResult.GetReturnTypeInfo(this.CancellationToken);
219160

220-
return (allVariableInfos, returnType, returnsByRef);
161+
return (allVariableInfos, UnwrapTaskIfNeeded(returnType), returnsByRef);
221162
}
222163
else
223164
{
@@ -230,9 +171,34 @@ private ITypeSymbol UnwrapTaskIfNeeded(ITypeSymbol returnType)
230171
return (finalOrderedVariableInfos, returnType, returnsByRef: false);
231172
}
232173

174+
ITypeSymbol UnwrapTaskIfNeeded(ITypeSymbol returnType)
175+
{
176+
if (this.SelectionResult.ContainingScopeHasAsyncKeyword())
177+
{
178+
// We compute the desired return type for the extract method from our own return type. But for
179+
// the purposes of manipulating the return type, we need to get to the underlying type if this
180+
// was wrapped in a Task in an explicitly 'async' method. In other words, if we're in an `async
181+
// Task<int>` method, then we want the extract method to return `int`. Note: we will possibly
182+
// then wrap that as `Task<int>` again if we see that we extracted out any await-expressions.
183+
184+
var compilation = this.SemanticModel.Compilation;
185+
var knownTaskTypes = new KnownTaskTypes(compilation);
186+
187+
// Map from `Task/ValueTask` to `void`
188+
if (returnType.Equals(knownTaskTypes.TaskType) || returnType.Equals(knownTaskTypes.ValueTaskType))
189+
return compilation.GetSpecialType(SpecialType.System_Void);
190+
191+
// Map from `Task<T>/ValueTask<T>` to `T`
192+
if (returnType.OriginalDefinition.Equals(knownTaskTypes.TaskOfTType) || returnType.OriginalDefinition.Equals(knownTaskTypes.ValueTaskOfTType))
193+
return returnType.GetTypeArguments().Single();
194+
}
195+
196+
return returnType;
197+
}
198+
233199
ITypeSymbol GetReturnType(ImmutableArray<VariableInfo> variablesToUseAsReturnValue)
234200
{
235-
var compilation = this.SemanticDocument.SemanticModel.Compilation;
201+
var compilation = this.SemanticModel.Compilation;
236202

237203
if (variablesToUseAsReturnValue.IsEmpty)
238204
return compilation.GetSpecialType(SpecialType.System_Void);
@@ -289,19 +255,21 @@ private OperationStatus GetOperationStatus(
289255
? OperationStatus.LocalFunctionCallWithoutDeclaration
290256
: OperationStatus.SucceededStatus;
291257

292-
return readonlyFieldStatus.With(anonymousTypeStatus)
293-
.With(unsafeAddressStatus)
294-
.With(asyncRefOutParameterStatus)
295-
.With(variableMapStatus)
296-
.With(localFunctionStatus);
258+
return readonlyFieldStatus
259+
.With(anonymousTypeStatus)
260+
.With(unsafeAddressStatus)
261+
.With(asyncRefOutParameterStatus)
262+
.With(variableMapStatus)
263+
.With(localFunctionStatus);
297264
}
298265

299266
private OperationStatus CheckAsyncMethodRefOutParameters(IList<VariableInfo> parameters)
300267
{
301-
if (SelectionResult.CreateAsyncMethod())
268+
if (SelectionResult.ContainsAwaitExpression())
302269
{
303-
var names = parameters.Where(v => v is { UseAsReturnValue: false, ParameterModifier: ParameterBehavior.Out or ParameterBehavior.Ref })
304-
.Select(p => p.Name ?? string.Empty);
270+
var names = parameters
271+
.Where(v => v is { UseAsReturnValue: false, ParameterModifier: ParameterBehavior.Out or ParameterBehavior.Ref })
272+
.Select(p => p.Name ?? string.Empty);
305273

306274
if (names.Any())
307275
return new OperationStatus(succeeded: true, string.Format(FeaturesResources.Asynchronous_method_cannot_have_ref_out_parameters_colon_bracket_0_bracket, string.Join(", ", names)));
@@ -345,7 +313,7 @@ private ImmutableArray<VariableInfo> MarkVariableInfosToUseAsReturnValueIfPossib
345313
// return values of the method since we can't actually have out/ref with an async method.
346314
var outRefCount = numberOfOutParameters + numberOfRefParameters;
347315
if (outRefCount > 0 &&
348-
this.SelectionResult.CreateAsyncMethod() &&
316+
this.SelectionResult.ContainsAwaitExpression() &&
349317
this.SyntaxFacts.SupportsTupleDeconstruction(this.SemanticDocument.Document.Project.ParseOptions!))
350318
{
351319
var result = new FixedSizeArrayBuilder<VariableInfo>(variableInfo.Length);

0 commit comments

Comments
 (0)