Skip to content

Commit bd782d4

Browse files
Move code in extract method to shared location (#76421)
2 parents e30d638 + 1b16a1c commit bd782d4

File tree

12 files changed

+232
-225
lines changed

12 files changed

+232
-225
lines changed

src/Analyzers/CSharp/CodeFixes/UseImplicitOrExplicitType/UseExplicitTypeCodeFixProvider.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.CodeAnalysis.CSharp.Syntax;
1515
using Microsoft.CodeAnalysis.Diagnostics;
1616
using Microsoft.CodeAnalysis.Editing;
17+
using Microsoft.CodeAnalysis.LanguageService;
1718
using Microsoft.CodeAnalysis.PooledObjects;
1819
using Microsoft.CodeAnalysis.Shared.Extensions;
1920
using Roslyn.Utilities;
@@ -125,33 +126,41 @@ private static Task HandleVariableDeclarationAsync(Document document, SyntaxEdit
125126
varDecl.Variables.Single().Identifier.Parent!,
126127
cancellationToken);
127128

128-
private static async Task UpdateTypeSyntaxAsync(Document document, SyntaxEditor editor, TypeSyntax typeSyntax, SyntaxNode declarationSyntax, CancellationToken cancellationToken)
129+
private static async Task UpdateTypeSyntaxAsync(
130+
Document document,
131+
SyntaxEditor editor,
132+
TypeSyntax typeSyntax,
133+
SyntaxNode declarationSyntax,
134+
CancellationToken cancellationToken)
129135
{
130136
typeSyntax = typeSyntax.StripRefIfNeeded();
131137

138+
var semanticFacts = document.GetRequiredLanguageService<ISemanticFactsService>();
132139
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
133140
var typeSymbol = GetConvertedType(semanticModel, typeSyntax, cancellationToken);
134141

135142
typeSymbol = AdjustNullabilityOfTypeSymbol(
136-
typeSymbol,
143+
semanticFacts,
137144
semanticModel,
145+
typeSymbol,
138146
declarationSyntax,
139147
cancellationToken);
140148

141149
editor.ReplaceNode(typeSyntax, GenerateTypeDeclaration(typeSyntax, typeSymbol));
142150
}
143151

144152
private static ITypeSymbol AdjustNullabilityOfTypeSymbol(
145-
ITypeSymbol typeSymbol,
153+
ISemanticFacts semanticFacts,
146154
SemanticModel semanticModel,
155+
ITypeSymbol typeSymbol,
147156
SyntaxNode declarationSyntax,
148157
CancellationToken cancellationToken)
149158
{
150159
if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated)
151160
{
152161
// It's possible that the var shouldn't be annotated nullable, check assignments to the variable and
153162
// determine if it needs to be null
154-
var isPossiblyAssignedNull = NullableHelpers.IsDeclaredSymbolAssignedPossiblyNullValue(semanticModel, declarationSyntax, cancellationToken);
163+
var isPossiblyAssignedNull = NullableHelpers.IsDeclaredSymbolAssignedPossiblyNullValue(semanticFacts, semanticModel, declarationSyntax, cancellationToken);
155164
if (!isPossiblyAssignedNull)
156165
{
157166
// If the symbol is never assigned null we can update the type symbol to also be non-null

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,6 @@ protected override bool ReadOnlyFieldAllowed()
5454
return scope == null;
5555
}
5656

57-
protected override ITypeSymbol? GetSymbolType(SemanticModel semanticModel, ISymbol symbol)
58-
{
59-
var selectionOperation = semanticModel.GetOperation(SelectionResult.GetContainingScope());
60-
61-
// Check if null is possibly assigned to the symbol. If it is, leave nullable annotation as is, otherwise
62-
// we can modify the annotation to be NotAnnotated to code that more likely matches the user's intent.
63-
if (selectionOperation is not null &&
64-
NullableHelpers.IsSymbolAssignedPossiblyNullValue(semanticModel, selectionOperation, symbol) == false)
65-
{
66-
return base.GetSymbolType(semanticModel, symbol)?.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
67-
}
68-
69-
return base.GetSymbolType(semanticModel, symbol);
70-
}
71-
7257
protected override bool IsReadOutside(ISymbol symbol, HashSet<ISymbol> readOutsideMap)
7358
{
7459
if (!base.IsReadOutside(symbol, readOutsideMap))

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

Lines changed: 42 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Linq;
@@ -11,6 +12,7 @@
1112
using Microsoft.CodeAnalysis.PooledObjects;
1213
using Microsoft.CodeAnalysis.Shared.Collections;
1314
using Microsoft.CodeAnalysis.Shared.Extensions;
15+
using Microsoft.CodeAnalysis.Text;
1416
using Roslyn.Utilities;
1517

1618
namespace Microsoft.CodeAnalysis.ExtractMethod;
@@ -25,6 +27,7 @@ protected abstract partial class Analyzer
2527
protected readonly TSelectionResult SelectionResult;
2628
protected readonly bool LocalFunction;
2729

30+
protected ISemanticFactsService SemanticFacts => _semanticDocument.Document.GetRequiredLanguageService<ISemanticFactsService>();
2831
protected ISyntaxFactsService SyntaxFacts => _semanticDocument.Document.GetRequiredLanguageService<ISyntaxFactsService>();
2932

3033
protected Analyzer(TSelectionResult selectionResult, bool localFunction, CancellationToken cancellationToken)
@@ -532,6 +535,10 @@ private void GenerateVariableInfoMap(
532535
candidates.UnionWith(writtenInsideMap);
533536
candidates.UnionWith(variableDeclaredMap);
534537

538+
// Need to analyze from the start of what we're extracting to the end of the scope that this variable could
539+
// have been referenced in.
540+
var analysisRange = TextSpan.FromBounds(SelectionResult.FinalSpan.Start, SelectionResult.GetContainingScope().Span.End);
541+
535542
foreach (var symbol in candidates)
536543
{
537544
// We don't care about the 'this' parameter. It will be available to an extracted method already.
@@ -585,7 +592,7 @@ private void GenerateVariableInfoMap(
585592
continue;
586593
}
587594

588-
var type = GetSymbolType(model, symbol);
595+
var type = GetSymbolType(model, symbol, analysisRange);
589596
if (type == null)
590597
{
591598
continue;
@@ -608,60 +615,11 @@ private void GenerateVariableInfoMap(
608615
continue;
609616
}
610617

611-
type = FixNullability(symbol, type, variableStyle);
612-
613618
AddVariableToMap(
614619
variableInfoMap,
615620
symbol,
616621
CreateFromSymbol(symbol, type, variableStyle, variableDeclared));
617622
}
618-
619-
ITypeSymbol FixNullability(ISymbol symbol, ITypeSymbol type, VariableStyle style)
620-
{
621-
if (style.ParameterStyle.ParameterBehavior != ParameterBehavior.None &&
622-
type.NullableAnnotation is NullableAnnotation.Annotated &&
623-
!IsMaybeNullWithinSelection(symbol))
624-
{
625-
// We're going to pass this nullable variable in as a parameter to the new method we're creating. If the
626-
// variable is actually never null within the section we're extracting, then change its return type to
627-
// be non-nullable so that any usage of it within the new method will not warn.
628-
return type.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
629-
}
630-
631-
return type;
632-
}
633-
634-
bool IsMaybeNullWithinSelection(ISymbol symbol)
635-
{
636-
if (!symbolMap.TryGetValue(symbol, out var tokens))
637-
return true;
638-
639-
var syntaxFacts = this.SyntaxFacts;
640-
foreach (var token in tokens)
641-
{
642-
if (token.Parent is not TExpressionSyntax expression)
643-
continue;
644-
645-
// a = b;
646-
//
647-
// Need to ask what the nullability of 'b' is since 'a' may be currently non-null but may be
648-
// assigned a null value.
649-
if (syntaxFacts.IsLeftSideOfAssignment(expression))
650-
{
651-
syntaxFacts.GetPartsOfAssignmentExpressionOrStatement(expression.GetRequiredParent(), out _, out _, out var right);
652-
expression = (TExpressionSyntax)right;
653-
}
654-
655-
var typeInfo = model.GetTypeInfo(expression, this.CancellationToken);
656-
if (typeInfo.Nullability.FlowState == NullableFlowState.MaybeNull ||
657-
typeInfo.ConvertedNullability.FlowState == NullableFlowState.MaybeNull)
658-
{
659-
return true;
660-
}
661-
}
662-
663-
return false;
664-
}
665623
}
666624

667625
private static void AddVariableToMap(IDictionary<ISymbol, VariableInfo> variableInfoMap, ISymbol localOrParameter, VariableInfo variableInfo)
@@ -731,7 +689,7 @@ private bool IsWrittenInsideForFrameworkValueType(
731689
if (!symbolMap.TryGetValue(symbol, out var tokens))
732690
return writtenInside;
733691

734-
var semanticFacts = _semanticDocument.Document.Project.Services.GetRequiredService<ISemanticFactsService>();
692+
var semanticFacts = this.SemanticFacts;
735693
return tokens.Any(t => semanticFacts.IsWrittenTo(model, t.Parent, this.CancellationToken));
736694
}
737695

@@ -753,15 +711,44 @@ private bool SelectionContainsOnlyIdentifierWithSameType(ITypeSymbol type)
753711
return type.Equals(SelectionResult.GetContainingScopeType());
754712
}
755713

756-
protected virtual ITypeSymbol? GetSymbolType(SemanticModel model, ISymbol symbol)
757-
=> symbol switch
714+
private ITypeSymbol? GetSymbolType(
715+
SemanticModel semanticModel,
716+
ISymbol symbol,
717+
TextSpan analysisRange)
718+
{
719+
var type = symbol switch
758720
{
759721
ILocalSymbol local => local.Type,
760722
IParameterSymbol parameter => parameter.Type,
761-
IRangeVariableSymbol rangeVariable => GetRangeVariableType(model, rangeVariable),
723+
IRangeVariableSymbol rangeVariable => GetRangeVariableType(semanticModel, rangeVariable),
762724
_ => throw ExceptionUtilities.UnexpectedValue(symbol)
763725
};
764726

727+
if (type is null)
728+
return type;
729+
730+
// Check if null is possibly assigned to the symbol. If it is, leave nullable annotation as is, otherwise we
731+
// can modify the annotation to be NotAnnotated to code that more likely matches the user's intent.
732+
733+
if (type.NullableAnnotation is not NullableAnnotation.Annotated)
734+
return type;
735+
736+
var selectionOperation = semanticModel.GetOperation(SelectionResult.GetContainingScope());
737+
738+
// For Extract-Method we don't care about analyzing the declaration of this variable. For example, even if
739+
// it was initially assigned 'null' for the purposes of determining the type of it for a return value, all
740+
// we care is if it is null at the end of the selection. If it is only assigned non-null values, for
741+
// example, we want to treat it as non-null.
742+
if (selectionOperation is not null &&
743+
NullableHelpers.IsSymbolAssignedPossiblyNullValue(
744+
this.SemanticFacts, semanticModel, selectionOperation, symbol, analysisRange, includeDeclaration: false, this.CancellationToken) == false)
745+
{
746+
return type.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
747+
}
748+
749+
return type;
750+
}
751+
765752
protected static VariableStyle AlwaysReturn(VariableStyle style)
766753
{
767754
if (style == VariableStyle.InputOnly)
@@ -1021,7 +1008,7 @@ private OperationStatus CheckReadOnlyFields(SemanticModel semanticModel, Diction
10211008
return OperationStatus.SucceededStatus;
10221009

10231010
using var _ = ArrayBuilder<string>.GetInstance(out var names);
1024-
var semanticFacts = _semanticDocument.Document.Project.Services.GetRequiredService<ISemanticFactsService>();
1011+
var semanticFacts = this.SemanticFacts;
10251012
foreach (var pair in symbolMap.Where(p => p.Key.Kind == SymbolKind.Field))
10261013
{
10271014
var field = (IFieldSymbol)pair.Key;

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ public static bool IsWrittenTo(
364364
return true;
365365

366366
// An extension method invocation with a ref-this parameter can write to an expression.
367-
if (expression.Parent is MemberAccessExpressionSyntax memberAccess &&
367+
if (expression.Parent is MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax } memberAccess &&
368368
expression == memberAccess.Expression)
369369
{
370370
var symbol = semanticModel.GetSymbolInfo(memberAccess, cancellationToken).Symbol;

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public int Convert<TSyntaxKind>(TSyntaxKind kind) where TSyntaxKind : struct
114114
public int? RefExpression => (int)SyntaxKind.RefExpression;
115115
public int ReferenceEqualsExpression => (int)SyntaxKind.EqualsExpression;
116116
public int ReferenceNotEqualsExpression => (int)SyntaxKind.NotEqualsExpression;
117+
public int SimpleAssignmentExpression => (int)SyntaxKind.SimpleAssignmentExpression;
117118
public int SimpleMemberAccessExpression => (int)SyntaxKind.SimpleMemberAccessExpression;
118119
public int? SuppressNullableWarningExpression => (int)SyntaxKind.SuppressNullableWarningExpression;
119120
public int TernaryConditionalExpression => (int)SyntaxKind.ConditionalExpression;

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ internal interface ISyntaxKinds
157157
int? RefExpression { get; }
158158
int ReferenceEqualsExpression { get; }
159159
int ReferenceNotEqualsExpression { get; }
160+
int SimpleAssignmentExpression { get; }
160161
int SimpleMemberAccessExpression { get; }
161162
int? SuppressNullableWarningExpression { get; }
162163
int TernaryConditionalExpression { get; }

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService
116116
Public ReadOnly Property RefExpression As Integer? Implements ISyntaxKinds.RefExpression
117117
Public ReadOnly Property ReferenceEqualsExpression As Integer = SyntaxKind.IsExpression Implements ISyntaxKinds.ReferenceEqualsExpression
118118
Public ReadOnly Property ReferenceNotEqualsExpression As Integer = SyntaxKind.IsNotExpression Implements ISyntaxKinds.ReferenceNotEqualsExpression
119+
Public ReadOnly Property SimpleAssignmentExpression As Integer = SyntaxKind.SimpleAssignmentStatement Implements ISyntaxKinds.SimpleAssignmentExpression
119120
Public ReadOnly Property SimpleMemberAccessExpression As Integer = SyntaxKind.SimpleMemberAccessExpression Implements ISyntaxKinds.SimpleMemberAccessExpression
120121
Public ReadOnly Property SuppressNullableWarningExpression As Integer? Implements ISyntaxKinds.SuppressNullableWarningExpression
121122
Public ReadOnly Property TernaryConditionalExpression As Integer = SyntaxKind.TernaryConditionalExpression Implements ISyntaxKinds.TernaryConditionalExpression

src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/CSharpWorkspaceExtensions.projitems

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@
9090
<Compile Include="$(MSBuildThisFileDirectory)Utilities\CSharpSimplificationHelpers.cs" />
9191
<Compile Include="$(MSBuildThisFileDirectory)Utilities\NullableHelpers\NullableExtensions.cs" />
9292
<Compile Include="$(MSBuildThisFileDirectory)Utilities\SyntaxKindSet.cs" />
93-
<Compile Include="$(MSBuildThisFileDirectory)Utilities\NullableHelpers\NullableHelpers.cs" />
9493
<Compile Include="$(MSBuildThisFileDirectory)Formatting\CSharpSyntaxFormattingOptionsProviders.cs" />
9594
<Compile Include="$(MSBuildThisFileDirectory)Simplification\CSharpSimplifierOptionsProviders.cs" />
9695
</ItemGroup>

0 commit comments

Comments
 (0)