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 ;
56using System . Collections . Generic ;
67using System . Collections . Immutable ;
78using System . Linq ;
1112using Microsoft . CodeAnalysis . PooledObjects ;
1213using Microsoft . CodeAnalysis . Shared . Collections ;
1314using Microsoft . CodeAnalysis . Shared . Extensions ;
15+ using Microsoft . CodeAnalysis . Text ;
1416using Roslyn . Utilities ;
1517
1618namespace 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 ;
0 commit comments