Skip to content
Permalink
main
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// Nullability flow analysis.
/// </summary>
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
internal sealed partial class NullableWalker
: LocalDataFlowPass<NullableWalker.LocalState, NullableWalker.LocalFunctionState>
{
/// <summary>
/// Used to copy variable slots and types from the NullableWalker for the containing method
/// or lambda to the NullableWalker created for a nested lambda or local function.
/// </summary>
internal sealed class VariableState
{
// Consider referencing the Variables instance directly from the original NullableWalker
// rather than cloning. (Items are added to the collections but never replaced so the
// collections are lazily populated but otherwise immutable. We'd probably want a
// clone when analyzing from speculative semantic model though.)
internal readonly VariablesSnapshot Variables;
// The nullable state of all variables captured at the point where the function or lambda appeared.
internal readonly LocalStateSnapshot VariableNullableStates;
internal VariableState(VariablesSnapshot variables, LocalStateSnapshot variableNullableStates)
{
Variables = variables;
VariableNullableStates = variableNullableStates;
}
}
/// <summary>
/// Data recorded for a particular analysis run.
/// </summary>
internal readonly struct Data
{
/// <summary>
/// Number of entries tracked during analysis.
/// </summary>
internal readonly int TrackedEntries;
/// <summary>
/// True if analysis was required; false if analysis was optional and results dropped.
/// </summary>
internal readonly bool RequiredAnalysis;
internal Data(int trackedEntries, bool requiredAnalysis)
{
TrackedEntries = trackedEntries;
RequiredAnalysis = requiredAnalysis;
}
}
/// <summary>
/// Represents the result of visiting an expression.
/// Contains a result type which tells us whether the expression may be null,
/// and an l-value type which tells us whether we can assign null to the expression.
/// </summary>
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
private readonly struct VisitResult
{
public readonly TypeWithState RValueType;
public readonly TypeWithAnnotations LValueType;
public VisitResult(TypeWithState rValueType, TypeWithAnnotations lValueType)
{
RValueType = rValueType;
LValueType = lValueType;
// https://github.com/dotnet/roslyn/issues/34993: Doesn't hold true for Tuple_Assignment_10. See if we can make it hold true
//Debug.Assert((RValueType.Type is null && LValueType.TypeSymbol is null) ||
// RValueType.Type.Equals(LValueType.TypeSymbol, TypeCompareKind.ConsiderEverything | TypeCompareKind.AllIgnoreOptions));
}
public VisitResult(TypeSymbol? type, NullableAnnotation annotation, NullableFlowState state)
{
RValueType = TypeWithState.Create(type, state);
LValueType = TypeWithAnnotations.Create(type, annotation);
Debug.Assert(TypeSymbol.Equals(RValueType.Type, LValueType.Type, TypeCompareKind.ConsiderEverything));
}
internal string GetDebuggerDisplay() => $"{{LValue: {LValueType.GetDebuggerDisplay()}, RValue: {RValueType.GetDebuggerDisplay()}}}";
}
/// <summary>
/// Represents the result of visiting an argument expression.
/// In addition to storing the <see cref="VisitResult"/>, also stores the <see cref="LocalState"/>
/// for reanalyzing a lambda.
/// </summary>
[DebuggerDisplay("{VisitResult.GetDebuggerDisplay(), nq}")]
private readonly struct VisitArgumentResult
{
public readonly VisitResult VisitResult;
public readonly Optional<LocalState> StateForLambda;
public TypeWithState RValueType => VisitResult.RValueType;
public TypeWithAnnotations LValueType => VisitResult.LValueType;
public VisitArgumentResult(VisitResult visitResult, Optional<LocalState> stateForLambda)
{
VisitResult = visitResult;
StateForLambda = stateForLambda;
}
}
private Variables _variables;
/// <summary>
/// Binder for symbol being analyzed.
/// </summary>
private readonly Binder _binder;
/// <summary>
/// Conversions with nullability and unknown matching any.
/// </summary>
private readonly Conversions _conversions;
/// <summary>
/// 'true' if non-nullable member warnings should be issued at return points.
/// One situation where this is 'false' is when we are analyzing field initializers and there is a constructor symbol in the type.
/// </summary>
private readonly bool _useConstructorExitWarnings;
/// <summary>
/// If true, the parameter types and nullability from _delegateInvokeMethod is used for
/// initial parameter state. If false, the signature of CurrentSymbol is used instead.
/// </summary>
private bool _useDelegateInvokeParameterTypes;
/// <summary>
/// If true, the return type and nullability from _delegateInvokeMethod is used.
/// If false, the signature of CurrentSymbol is used instead.
/// </summary>
private bool _useDelegateInvokeReturnType;
/// <summary>
/// Method signature used for return or parameter types. Distinct from CurrentSymbol signature
/// when CurrentSymbol is a lambda and type is inferred from MethodTypeInferrer.
/// </summary>
private MethodSymbol? _delegateInvokeMethod;
/// <summary>
/// Return statements and the result types from analyzing the returned expressions. Used when inferring lambda return type in MethodTypeInferrer.
/// </summary>
private ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>? _returnTypesOpt;
/// <summary>
/// Invalid type, used only to catch Visit methods that do not set
/// _result.Type. See VisitExpressionWithoutStackGuard.
/// </summary>
private static readonly TypeWithState _invalidType = TypeWithState.Create(ErrorTypeSymbol.UnknownResultType, NullableFlowState.NotNull);
/// <summary>
/// Contains the map of expressions to inferred nullabilities and types used by the optional rewriter phase of the
/// compiler.
/// </summary>
private readonly ImmutableDictionary<BoundExpression, (NullabilityInfo Info, TypeSymbol? Type)>.Builder? _analyzedNullabilityMapOpt;
/// <summary>
/// Manages creating snapshots of the walker as appropriate. Null if we're not taking snapshots of
/// this walker.
/// </summary>
private readonly SnapshotManager.Builder? _snapshotBuilderOpt;
// https://github.com/dotnet/roslyn/issues/35043: remove this when all expression are supported
private bool _disableNullabilityAnalysis;
/// <summary>
/// State of method group receivers, used later when analyzing the conversion to a delegate.
/// (Could be replaced by _analyzedNullabilityMapOpt if that map is always available.)
/// </summary>
private PooledDictionary<BoundExpression, TypeWithState>? _methodGroupReceiverMapOpt;
/// <summary>
/// State of awaitable expressions, for substitution in placeholders within GetAwaiter calls.
/// </summary>
private PooledDictionary<BoundAwaitableValuePlaceholder, (BoundExpression AwaitableExpression, VisitResult Result)>? _awaitablePlaceholdersOpt;
/// <summary>
/// Variables instances for each lambda or local function defined within the analyzed region.
/// </summary>
private PooledDictionary<MethodSymbol, Variables>? _nestedFunctionVariables;
private PooledDictionary<BoundExpression, ImmutableArray<(LocalState State, TypeWithState ResultType, bool EndReachable)>>? _conditionalInfoForConversionOpt;
/// <summary>
/// Map from a target-typed conditional expression (such as a target-typed conditional or switch) to the nullable state on each branch. This
/// is then used by VisitConversion to properly set the state before each branch when visiting a conversion applied to such a construct. These
/// states will be the state after visiting the underlying arm value, but before visiting the conversion on top of the arm value.
/// </summary>
private PooledDictionary<BoundExpression, ImmutableArray<(LocalState State, TypeWithState ResultType, bool EndReachable)>> ConditionalInfoForConversion
=> _conditionalInfoForConversionOpt ??= PooledDictionary<BoundExpression, ImmutableArray<(LocalState, TypeWithState, bool)>>.GetInstance();
/// <summary>
/// True if we're analyzing speculative code. This turns off some initialization steps
/// that would otherwise be taken.
/// </summary>
private readonly bool _isSpeculative;
/// <summary>
/// True if this walker was created using an initial state.
/// </summary>
private readonly bool _hasInitialState;
#if DEBUG
/// <summary>
/// Contains the expressions that should not be inserted into <see cref="_analyzedNullabilityMapOpt"/>.
/// </summary>
private static readonly ImmutableArray<BoundKind> s_skippedExpressions = ImmutableArray.Create(BoundKind.ArrayInitialization,
BoundKind.ObjectInitializerExpression,
BoundKind.CollectionInitializerExpression,
BoundKind.DynamicCollectionElementInitializer);
#endif
/// <summary>
/// The result and l-value type of the last visited expression.
/// </summary>
private VisitResult _visitResult;
/// <summary>
/// The visit result of the receiver for the current conditional access.
///
/// For example: A conditional invocation uses a placeholder as a receiver. By storing the
/// visit result from the actual receiver ahead of time, we can give this placeholder a correct result.
/// </summary>
private VisitResult _currentConditionalReceiverVisitResult;
/// <summary>
/// The result type represents the state of the last visited expression.
/// </summary>
private TypeWithState ResultType
{
get => _visitResult.RValueType;
}
private void SetResultType(BoundExpression? expression, TypeWithState type, bool updateAnalyzedNullability = true)
{
SetResult(expression, resultType: type, lvalueType: type.ToTypeWithAnnotations(compilation), updateAnalyzedNullability: updateAnalyzedNullability);
}
/// <summary>
/// Force the inference of the LValueResultType from ResultType.
/// </summary>
private void UseRvalueOnly(BoundExpression? expression)
{
SetResult(expression, ResultType, ResultType.ToTypeWithAnnotations(compilation), isLvalue: false);
}
private TypeWithAnnotations LvalueResultType
{
get => _visitResult.LValueType;
}
private void SetLvalueResultType(BoundExpression? expression, TypeWithAnnotations type)
{
SetResult(expression, resultType: type.ToTypeWithState(), lvalueType: type);
}
/// <summary>
/// Force the inference of the ResultType from LValueResultType.
/// </summary>
private void UseLvalueOnly(BoundExpression? expression)
{
SetResult(expression, LvalueResultType.ToTypeWithState(), LvalueResultType, isLvalue: true);
}
private void SetInvalidResult() => SetResult(expression: null, _invalidType, _invalidType.ToTypeWithAnnotations(compilation), updateAnalyzedNullability: false);
private void SetResult(BoundExpression? expression, TypeWithState resultType, TypeWithAnnotations lvalueType, bool updateAnalyzedNullability = true, bool? isLvalue = null)
{
_visitResult = new VisitResult(resultType, lvalueType);
if (updateAnalyzedNullability)
{
SetAnalyzedNullability(expression, _visitResult, isLvalue);
}
}
private bool ShouldMakeNotNullRvalue(BoundExpression node) => node.IsSuppressed || node.HasAnyErrors || !IsReachable();
/// <summary>
/// Sets the analyzed nullability of the expression to be the given result.
/// </summary>
private void SetAnalyzedNullability(BoundExpression? expr, VisitResult result, bool? isLvalue = null)
{
if (expr == null || _disableNullabilityAnalysis) return;
#if DEBUG
// https://github.com/dotnet/roslyn/issues/34993: This assert is essential for ensuring that we aren't
// changing the observable results of GetTypeInfo beyond nullability information.
//Debug.Assert(AreCloseEnough(expr.Type, result.RValueType.Type),
// $"Cannot change the type of {expr} from {expr.Type} to {result.RValueType.Type}");
#endif
if (_analyzedNullabilityMapOpt != null)
{
// https://github.com/dotnet/roslyn/issues/34993: enable and verify these assertions
#if false
if (_analyzedNullabilityMapOpt.TryGetValue(expr, out var existing))
{
if (!(result.RValueType.State == NullableFlowState.NotNull && ShouldMakeNotNullRvalue(expr, State.Reachable)))
{
switch (isLvalue)
{
case true:
Debug.Assert(existing.Info.Annotation == result.LValueType.NullableAnnotation.ToPublicAnnotation(),
$"Tried to update the nullability of {expr} from {existing.Info.Annotation} to {result.LValueType.NullableAnnotation}");
break;
case false:
Debug.Assert(existing.Info.FlowState == result.RValueType.State,
$"Tried to update the nullability of {expr} from {existing.Info.FlowState} to {result.RValueType.State}");
break;
case null:
Debug.Assert(existing.Info.Equals((NullabilityInfo)result),
$"Tried to update the nullability of {expr} from ({existing.Info.Annotation}, {existing.Info.FlowState}) to ({result.LValueType.NullableAnnotation}, {result.RValueType.State})");
break;
}
}
}
#endif
_analyzedNullabilityMapOpt[expr] = (new NullabilityInfo(result.LValueType.ToPublicAnnotation(), result.RValueType.State.ToPublicFlowState()),
// https://github.com/dotnet/roslyn/issues/35046 We're dropping the result if the type doesn't match up completely
// with the existing type
expr.Type?.Equals(result.RValueType.Type, TypeCompareKind.AllIgnoreOptions) == true ? result.RValueType.Type : expr.Type);
}
}
/// <summary>
/// Placeholder locals, e.g. for objects being constructed.
/// </summary>
private PooledDictionary<object, PlaceholderLocal>? _placeholderLocalsOpt;
/// <summary>
/// For methods with annotations, we'll need to visit the arguments twice.
/// Once for diagnostics and once for result state (but disabling diagnostics).
/// </summary>
private bool _disableDiagnostics = false;
/// <summary>
/// Whether we are going to read the currently visited expression.
/// </summary>
private bool _expressionIsRead = true;
/// <summary>
/// Used to allow <see cref="MakeSlot(BoundExpression)"/> to substitute the correct slot for a <see cref="BoundConditionalReceiver"/> when
/// it's encountered.
/// </summary>
private int _lastConditionalAccessSlot = -1;
private bool IsAnalyzingAttribute => methodMainNode.Kind == BoundKind.Attribute;
protected override void Free()
{
_nestedFunctionVariables?.Free();
_awaitablePlaceholdersOpt?.Free();
_methodGroupReceiverMapOpt?.Free();
_placeholderLocalsOpt?.Free();
_variables.Free();
Debug.Assert(_conditionalInfoForConversionOpt is null or { Count: 0 });
_conditionalInfoForConversionOpt?.Free();
base.Free();
}
private NullableWalker(
CSharpCompilation compilation,
Symbol? symbol,
bool useConstructorExitWarnings,
bool useDelegateInvokeParameterTypes,
bool useDelegateInvokeReturnType,
MethodSymbol? delegateInvokeMethodOpt,
BoundNode node,
Binder binder,
Conversions conversions,
Variables? variables,
ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>? returnTypesOpt,
ImmutableDictionary<BoundExpression, (NullabilityInfo, TypeSymbol?)>.Builder? analyzedNullabilityMapOpt,
SnapshotManager.Builder? snapshotBuilderOpt,
bool isSpeculative = false)
: base(compilation, symbol, node, EmptyStructTypeCache.CreatePrecise(), trackUnassignments: true)
{
Debug.Assert(!useDelegateInvokeParameterTypes || delegateInvokeMethodOpt is object);
_variables = variables ?? Variables.Create(symbol);
_binder = binder;
_conversions = (Conversions)conversions.WithNullability(true);
_useConstructorExitWarnings = useConstructorExitWarnings;
_useDelegateInvokeParameterTypes = useDelegateInvokeParameterTypes;
_useDelegateInvokeReturnType = useDelegateInvokeReturnType;
_delegateInvokeMethod = delegateInvokeMethodOpt;
_analyzedNullabilityMapOpt = analyzedNullabilityMapOpt;
_returnTypesOpt = returnTypesOpt;
_snapshotBuilderOpt = snapshotBuilderOpt;
_isSpeculative = isSpeculative;
_hasInitialState = variables is { };
}
public string GetDebuggerDisplay()
{
if (this.IsConditionalState)
{
return $"{{{GetType().Name} WhenTrue:{Dump(StateWhenTrue)} WhenFalse:{Dump(StateWhenFalse)}{"}"}";
}
else
{
return $"{{{GetType().Name} {Dump(State)}{"}"}";
}
}
// For purpose of nullability analysis, awaits create pending branches, so async usings and foreachs do too
public sealed override bool AwaitUsingAndForeachAddsPendingBranch => true;
protected override void EnsureSufficientExecutionStack(int recursionDepth)
{
if (recursionDepth > StackGuard.MaxUncheckedRecursionDepth &&
compilation.NullableAnalysisData is { MaxRecursionDepth: var depth } &&
depth > 0 &&
recursionDepth > depth)
{
throw new InsufficientExecutionStackException();
}
base.EnsureSufficientExecutionStack(recursionDepth);
}
protected override bool ConvertInsufficientExecutionStackExceptionToCancelledByStackGuardException()
{
return true;
}
protected override bool TryGetVariable(VariableIdentifier identifier, out int slot)
{
return _variables.TryGetValue(identifier, out slot);
}
protected override int AddVariable(VariableIdentifier identifier)
{
return _variables.Add(identifier);
}
protected override ImmutableArray<PendingBranch> Scan(ref bool badRegion)
{
if (_returnTypesOpt != null)
{
_returnTypesOpt.Clear();
}
this.Diagnostics.Clear();
this.regionPlace = RegionPlace.Before;
if (!_isSpeculative)
{
ParameterSymbol methodThisParameter = MethodThisParameter;
EnterParameters(); // assign parameters
if (methodThisParameter is object)
{
EnterParameter(methodThisParameter, methodThisParameter.TypeWithAnnotations);
}
makeNotNullMembersMaybeNull();
// We need to create a snapshot even of the first node, because we want to have the state of the initial parameters.
_snapshotBuilderOpt?.TakeIncrementalSnapshot(methodMainNode, State);
}
ImmutableArray<PendingBranch> pendingReturns = base.Scan(ref badRegion);
if ((_symbol as MethodSymbol)?.IsConstructor() != true || _useConstructorExitWarnings)
{
EnforceDoesNotReturn(syntaxOpt: null);
enforceMemberNotNull(syntaxOpt: null, this.State);
enforceNotNull(null, this.State);
foreach (var pendingReturn in pendingReturns)
{
enforceMemberNotNull(syntaxOpt: pendingReturn.Branch.Syntax, pendingReturn.State);
if (pendingReturn.Branch is BoundReturnStatement returnStatement)
{
enforceNotNull(returnStatement.Syntax, pendingReturn.State);
enforceNotNullWhenForPendingReturn(pendingReturn, returnStatement);
enforceMemberNotNullWhenForPendingReturn(pendingReturn, returnStatement);
}
}
}
return pendingReturns;
void enforceMemberNotNull(SyntaxNode? syntaxOpt, LocalState state)
{
if (!state.Reachable)
{
return;
}
var method = _symbol as MethodSymbol;
if (method is object)
{
if (method.IsConstructor())
{
Debug.Assert(_useConstructorExitWarnings);
var thisSlot = 0;
if (method.RequiresInstanceReceiver)
{
method.TryGetThisParameter(out var thisParameter);
Debug.Assert(thisParameter is object);
thisSlot = GetOrCreateSlot(thisParameter);
}
// https://github.com/dotnet/roslyn/issues/46718: give diagnostics on return points, not constructor signature
var exitLocation = method.DeclaringSyntaxReferences.IsEmpty ? null : method.Locations.FirstOrDefault();
foreach (var member in method.ContainingType.GetMembersUnordered())
{
checkMemberStateOnConstructorExit(method, member, state, thisSlot, exitLocation);
}
}
else
{
do
{
foreach (var memberName in method.NotNullMembers)
{
enforceMemberNotNullOnMember(syntaxOpt, state, method, memberName);
}
method = method.OverriddenMethod;
}
while (method != null);
}
}
}
void checkMemberStateOnConstructorExit(MethodSymbol constructor, Symbol member, LocalState state, int thisSlot, Location? exitLocation)
{
var isStatic = !constructor.RequiresInstanceReceiver();
if (member.IsStatic != isStatic)
{
return;
}
// This is not required for correctness, but in the case where the member has
// an initializer, we know we've assigned to the member and
// have given any applicable warnings about a bad value going in.
// Therefore we skip this check when the member has an initializer to reduce noise.
if (HasInitializer(member))
{
return;
}
TypeWithAnnotations fieldType;
FieldSymbol? field;
Symbol symbol;
switch (member)
{
case FieldSymbol f:
fieldType = f.TypeWithAnnotations;
field = f;
symbol = (Symbol?)(f.AssociatedSymbol as PropertySymbol) ?? f;
break;
case EventSymbol e:
fieldType = e.TypeWithAnnotations;
field = e.AssociatedField;
symbol = e;
if (field is null)
{
return;
}
break;
default:
return;
}
if (field.IsConst)
{
return;
}
if (fieldType.Type.IsValueType || fieldType.Type.IsErrorType())
{
return;
}
var annotations = symbol.GetFlowAnalysisAnnotations();
if ((annotations & FlowAnalysisAnnotations.AllowNull) != 0)
{
// We assume that if a member has AllowNull then the user
// does not care that we exit at a point where the member might be null.
return;
}
fieldType = ApplyUnconditionalAnnotations(fieldType, annotations);
if (!fieldType.NullableAnnotation.IsNotAnnotated())
{
return;
}
var slot = GetOrCreateSlot(symbol, thisSlot);
if (slot < 0)
{
return;
}
var memberState = state[slot];
var badState = fieldType.Type.IsPossiblyNullableReferenceTypeTypeParameter() && (annotations & FlowAnalysisAnnotations.NotNull) == 0
? NullableFlowState.MaybeDefault
: NullableFlowState.MaybeNull;
if (memberState >= badState) // is 'memberState' as bad as or worse than 'badState'?
{
Diagnostics.Add(ErrorCode.WRN_UninitializedNonNullableField, exitLocation ?? symbol.Locations.FirstOrNone(), symbol.Kind.Localize(), symbol.Name);
}
}
void enforceMemberNotNullOnMember(SyntaxNode? syntaxOpt, LocalState state, MethodSymbol method, string memberName)
{
foreach (var member in method.ContainingType.GetMembers(memberName))
{
if (memberHasBadState(member, state))
{
// Member '{name}' must have a non-null value when exiting.
Diagnostics.Add(ErrorCode.WRN_MemberNotNull, syntaxOpt?.GetLocation() ?? methodMainNode.Syntax.GetLastToken().GetLocation(), member.Name);
}
}
}
void enforceMemberNotNullWhenForPendingReturn(PendingBranch pendingReturn, BoundReturnStatement returnStatement)
{
if (pendingReturn.IsConditionalState)
{
if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } })
{
enforceMemberNotNullWhen(returnStatement.Syntax, sense: value, pendingReturn.State);
return;
}
if (!pendingReturn.StateWhenTrue.Reachable || !pendingReturn.StateWhenFalse.Reachable)
{
return;
}
if (_symbol is MethodSymbol method)
{
foreach (var memberName in method.NotNullWhenTrueMembers)
{
enforceMemberNotNullWhenIfAffected(returnStatement.Syntax, sense: true, method.ContainingType.GetMembers(memberName), pendingReturn.StateWhenTrue, pendingReturn.StateWhenFalse);
}
foreach (var memberName in method.NotNullWhenFalseMembers)
{
enforceMemberNotNullWhenIfAffected(returnStatement.Syntax, sense: false, method.ContainingType.GetMembers(memberName), pendingReturn.StateWhenFalse, pendingReturn.StateWhenTrue);
}
}
}
else if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } })
{
enforceMemberNotNullWhen(returnStatement.Syntax, sense: value, pendingReturn.State);
}
}
void enforceMemberNotNullWhenIfAffected(SyntaxNode? syntaxOpt, bool sense, ImmutableArray<Symbol> members, LocalState state, LocalState otherState)
{
foreach (var member in members)
{
// For non-constant values, only complain if we were able to analyze a difference for this member between two branches
if (memberHasBadState(member, state) != memberHasBadState(member, otherState))
{
reportMemberIfBadConditionalState(syntaxOpt, sense, member, state);
}
}
}
void enforceMemberNotNullWhen(SyntaxNode? syntaxOpt, bool sense, LocalState state)
{
if (_symbol is MethodSymbol method)
{
var notNullMembers = sense ? method.NotNullWhenTrueMembers : method.NotNullWhenFalseMembers;
foreach (var memberName in notNullMembers)
{
foreach (var member in method.ContainingType.GetMembers(memberName))
{
reportMemberIfBadConditionalState(syntaxOpt, sense, member, state);
}
}
}
}
void reportMemberIfBadConditionalState(SyntaxNode? syntaxOpt, bool sense, Symbol member, LocalState state)
{
if (memberHasBadState(member, state))
{
// Member '{name}' must have a non-null value when exiting with '{sense}'.
Diagnostics.Add(ErrorCode.WRN_MemberNotNullWhen, syntaxOpt?.GetLocation() ?? methodMainNode.Syntax.GetLastToken().GetLocation(), member.Name, sense ? "true" : "false");
}
}
bool memberHasBadState(Symbol member, LocalState state)
{
switch (member.Kind)
{
case SymbolKind.Field:
case SymbolKind.Property:
if (getSlotForFieldOrPropertyOrEvent(member) is int memberSlot &&
memberSlot > 0)
{
var parameterState = state[memberSlot];
return !parameterState.IsNotNull();
}
else
{
return false;
}
case SymbolKind.Event:
case SymbolKind.Method:
break;
}
return false;
}
void makeNotNullMembersMaybeNull()
{
if (_symbol is MethodSymbol method)
{
if (method.IsConstructor())
{
if (needsDefaultInitialStateForMembers())
{
foreach (var member in method.ContainingType.GetMembersUnordered())
{
if (member.IsStatic != method.IsStatic)
{
continue;
}
var memberToInitialize = member;
switch (member)
{
case PropertySymbol:
// skip any manually implemented properties.
continue;
case FieldSymbol { IsConst: true }:
continue;
case FieldSymbol { AssociatedSymbol: PropertySymbol prop }:
// this is a property where assigning 'default' causes us to simply update
// the state to the output state of the property
// thus we skip setting an initial state for it here
if (IsPropertyOutputMoreStrictThanInput(prop))
{
continue;
}
// We want to initialize auto-property state to the default state, but not computed properties.
memberToInitialize = prop;
break;
default:
break;
}
var memberSlot = getSlotForFieldOrPropertyOrEvent(memberToInitialize);
if (memberSlot > 0)
{
var type = memberToInitialize.GetTypeOrReturnType();
if (!type.NullableAnnotation.IsOblivious())
{
this.State[memberSlot] = type.Type.IsPossiblyNullableReferenceTypeTypeParameter() ? NullableFlowState.MaybeDefault : NullableFlowState.MaybeNull;
}
}
}
}
}
else
{
do
{
makeMembersMaybeNull(method, method.NotNullMembers);
makeMembersMaybeNull(method, method.NotNullWhenTrueMembers);
makeMembersMaybeNull(method, method.NotNullWhenFalseMembers);
method = method.OverriddenMethod;
}
while (method != null);
}
}
bool needsDefaultInitialStateForMembers()
{
if (_hasInitialState)
{
return false;
}
// We don't use a default initial state for value type instance constructors without `: this()` because
// any usages of uninitialized fields will get definite assignment errors anyway.
if (!method.HasThisConstructorInitializer(out _) && (!method.ContainingType.IsValueType || method.IsStatic))
{
return true;
}
return method.IncludeFieldInitializersInBody();
}
}
void makeMembersMaybeNull(MethodSymbol method, ImmutableArray<string> members)
{
foreach (var memberName in members)
{
makeMemberMaybeNull(method, memberName);
}
}
void makeMemberMaybeNull(MethodSymbol method, string memberName)
{
var type = method.ContainingType;
foreach (var member in type.GetMembers(memberName))
{
if (getSlotForFieldOrPropertyOrEvent(member) is int memberSlot &&
memberSlot > 0)
{
this.State[memberSlot] = NullableFlowState.MaybeNull;
}
}
}
void enforceNotNullWhenForPendingReturn(PendingBranch pendingReturn, BoundReturnStatement returnStatement)
{
var parameters = this.MethodParameters;
if (!parameters.IsEmpty)
{
if (pendingReturn.IsConditionalState)
{
if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } })
{
enforceParameterNotNullWhen(returnStatement.Syntax, parameters, sense: value, stateWhen: pendingReturn.State);
return;
}
if (!pendingReturn.StateWhenTrue.Reachable || !pendingReturn.StateWhenFalse.Reachable)
{
return;
}
foreach (var parameter in parameters)
{
// For non-constant values, only complain if we were able to analyze a difference for this parameter between two branches
if (GetOrCreateSlot(parameter) is > 0 and var slot && pendingReturn.StateWhenTrue[slot] != pendingReturn.StateWhenFalse[slot])
{
reportParameterIfBadConditionalState(returnStatement.Syntax, parameter, sense: true, stateWhen: pendingReturn.StateWhenTrue);
reportParameterIfBadConditionalState(returnStatement.Syntax, parameter, sense: false, stateWhen: pendingReturn.StateWhenFalse);
}
}
}
else if (returnStatement.ExpressionOpt is { ConstantValue: { IsBoolean: true, BooleanValue: bool value } })
{
// example: return (bool)true;
enforceParameterNotNullWhen(returnStatement.Syntax, parameters, sense: value, stateWhen: pendingReturn.State);
return;
}
}
}
void reportParameterIfBadConditionalState(SyntaxNode syntax, ParameterSymbol parameter, bool sense, LocalState stateWhen)
{
if (parameterHasBadConditionalState(parameter, sense, stateWhen))
{
// Parameter '{name}' must have a non-null value when exiting with '{sense}'.
Diagnostics.Add(ErrorCode.WRN_ParameterConditionallyDisallowsNull, syntax.Location, parameter.Name, sense ? "true" : "false");
}
}
void enforceNotNull(SyntaxNode? syntaxOpt, LocalState state)
{
if (!state.Reachable)
{
return;
}
foreach (var parameter in this.MethodParameters)
{
var slot = GetOrCreateSlot(parameter);
if (slot <= 0)
{
continue;
}
var annotations = parameter.FlowAnalysisAnnotations;
var hasNotNull = (annotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNull;
var parameterState = state[slot];
if (hasNotNull && parameterState.MayBeNull())
{
// Parameter '{name}' must have a non-null value when exiting.
Diagnostics.Add(ErrorCode.WRN_ParameterDisallowsNull, syntaxOpt?.GetLocation() ?? methodMainNode.Syntax.GetLastToken().GetLocation(), parameter.Name);
}
else
{
EnforceNotNullIfNotNull(syntaxOpt, state, this.MethodParameters, parameter.NotNullIfParameterNotNull, parameterState, parameter);
}
}
}
void enforceParameterNotNullWhen(SyntaxNode syntax, ImmutableArray<ParameterSymbol> parameters, bool sense, LocalState stateWhen)
{
if (!stateWhen.Reachable)
{
return;
}
foreach (var parameter in parameters)
{
reportParameterIfBadConditionalState(syntax, parameter, sense, stateWhen);
}
}
bool parameterHasBadConditionalState(ParameterSymbol parameter, bool sense, LocalState stateWhen)
{
var refKind = parameter.RefKind;
if (refKind != RefKind.Out && refKind != RefKind.Ref)
{
return false;
}
var slot = GetOrCreateSlot(parameter);
if (slot > 0)
{
var parameterState = stateWhen[slot];
// On a parameter marked with MaybeNullWhen, we would have not reported an assignment warning.
// We should only check if an assignment warning would have been warranted ignoring the MaybeNullWhen.
FlowAnalysisAnnotations annotations = parameter.FlowAnalysisAnnotations;
if (sense)
{
bool hasNotNullWhenTrue = (annotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNullWhenTrue;
bool hasMaybeNullWhenFalse = (annotations & FlowAnalysisAnnotations.MaybeNull) == FlowAnalysisAnnotations.MaybeNullWhenFalse;
return (hasNotNullWhenTrue && parameterState.MayBeNull()) ||
(hasMaybeNullWhenFalse && ShouldReportNullableAssignment(parameter.TypeWithAnnotations, parameterState));
}
else
{
bool hasNotNullWhenFalse = (annotations & FlowAnalysisAnnotations.NotNull) == FlowAnalysisAnnotations.NotNullWhenFalse;
bool hasMaybeNullWhenTrue = (annotations & FlowAnalysisAnnotations.MaybeNull) == FlowAnalysisAnnotations.MaybeNullWhenTrue;
return (hasNotNullWhenFalse && parameterState.MayBeNull()) ||
(hasMaybeNullWhenTrue && ShouldReportNullableAssignment(parameter.TypeWithAnnotations, parameterState));
}
}
return false;
}
int getSlotForFieldOrPropertyOrEvent(Symbol member)
{
if (member.Kind != SymbolKind.Field &&
member.Kind != SymbolKind.Property &&
member.Kind != SymbolKind.Event)
{
return -1;
}
int containingSlot = 0;
if (!member.IsStatic)
{
if (MethodThisParameter is null)
{
return -1;
}
containingSlot = GetOrCreateSlot(MethodThisParameter);
if (containingSlot < 0)
{
return -1;
}
Debug.Assert(containingSlot > 0);
}
return GetOrCreateSlot(member, containingSlot);
}
}
private void EnforceNotNullIfNotNull(SyntaxNode? syntaxOpt, LocalState state, ImmutableArray<ParameterSymbol> parameters, ImmutableHashSet<string> inputParamNames, NullableFlowState outputState, ParameterSymbol? outputParam)
{
if (inputParamNames.IsEmpty || outputState.IsNotNull())
{
return;
}
foreach (var inputParam in parameters)
{
if (inputParamNames.Contains(inputParam.Name)
&& GetOrCreateSlot(inputParam) is > 0 and int inputSlot
&& state[inputSlot].IsNotNull())
{
var location = syntaxOpt?.GetLocation() ?? methodMainNode.Syntax.GetLastToken().GetLocation();
if (outputParam is object)
{
// Parameter '{0}' must have a non-null value when exiting because parameter '{1}' is non-null.
Diagnostics.Add(ErrorCode.WRN_ParameterNotNullIfNotNull, location, outputParam.Name, inputParam.Name);
}
else if (CurrentSymbol is MethodSymbol { IsAsync: false })
{
// Return value must be non-null because parameter '{0}' is non-null.
Diagnostics.Add(ErrorCode.WRN_ReturnNotNullIfNotNull, location, inputParam.Name);
}
break;
}
}
}
private void EnforceDoesNotReturn(SyntaxNode? syntaxOpt)
{
// DoesNotReturn is only supported in member methods
if (CurrentSymbol is MethodSymbol { ContainingSymbol: TypeSymbol _ } method &&
((method.FlowAnalysisAnnotations & FlowAnalysisAnnotations.DoesNotReturn) == FlowAnalysisAnnotations.DoesNotReturn) &&
this.IsReachable())
{
// A method marked [DoesNotReturn] should not return.
ReportDiagnostic(ErrorCode.WRN_ShouldNotReturn, syntaxOpt?.GetLocation() ?? methodMainNode.Syntax.GetLastToken().GetLocation());
}
}
/// <summary>
/// Analyzes a method body if settings indicate we should.
/// </summary>
internal static void AnalyzeIfNeeded(
CSharpCompilation compilation,
MethodSymbol method,
BoundNode node,
DiagnosticBag diagnostics,
bool useConstructorExitWarnings,
VariableState? initialNullableState,
bool getFinalNullableState,
out VariableState? finalNullableState)
{
if (!HasRequiredLanguageVersion(compilation) || !compilation.IsNullableAnalysisEnabledIn(method))
{
if (compilation.IsNullableAnalysisEnabledAlways)
{
// Once we address https://github.com/dotnet/roslyn/issues/46579 we should also always pass `getFinalNullableState: true` in debug mode.
// We will likely always need to write a 'null' out for the out parameter in this code path, though, because
// we don't want to introduce behavior differences between debug and release builds
Analyze(compilation, method, node, new DiagnosticBag(), useConstructorExitWarnings: false, initialNullableState: null, getFinalNullableState: false, out _, requiresAnalysis: false);
}
finalNullableState = null;
return;
}
Analyze(compilation, method, node, diagnostics, useConstructorExitWarnings, initialNullableState, getFinalNullableState, out finalNullableState);
}
private static void Analyze(
CSharpCompilation compilation,
MethodSymbol method,
BoundNode node,
DiagnosticBag diagnostics,
bool useConstructorExitWarnings,
VariableState? initialNullableState,
bool getFinalNullableState,
out VariableState? finalNullableState,
bool requiresAnalysis = true)
{
if (method.IsImplicitlyDeclared && !method.IsImplicitConstructor && !method.IsScriptInitializer)
{
finalNullableState = null;
return;
}
Debug.Assert(node.SyntaxTree is object);
var binder = method is SynthesizedSimpleProgramEntryPointSymbol entryPoint ?
entryPoint.GetBodyBinder(ignoreAccessibility: false) :
compilation.GetBinderFactory(node.SyntaxTree).GetBinder(node.Syntax);
var conversions = binder.Conversions;
Analyze(compilation,
method,
node,
binder,
conversions,
diagnostics,
useConstructorExitWarnings,
useDelegateInvokeParameterTypes: false,
useDelegateInvokeReturnType: false,
delegateInvokeMethodOpt: null,
initialState: initialNullableState,
analyzedNullabilityMapOpt: null,
snapshotBuilderOpt: null,
returnTypesOpt: null,
getFinalNullableState,
finalNullableState: out finalNullableState,
requiresAnalysis);
}
/// <summary>
/// Gets the "after initializers state" which should be used at the beginning of nullable analysis
/// of certain constructors. Only used for semantic model and debug verification.
/// </summary>
internal static VariableState? GetAfterInitializersState(CSharpCompilation compilation, Symbol? symbol)
{
if (symbol is MethodSymbol method
&& method.IncludeFieldInitializersInBody()
&& method.ContainingType is SourceMemberContainerTypeSymbol containingType)
{
var unusedDiagnostics = DiagnosticBag.GetInstance();
Binder.ProcessedFieldInitializers initializers = default;
Binder.BindFieldInitializers(compilation, null, method.IsStatic ? containingType.StaticInitializers : containingType.InstanceInitializers, BindingDiagnosticBag.Discarded, ref initializers);
NullableWalker.AnalyzeIfNeeded(
compilation,
method,
InitializerRewriter.RewriteConstructor(initializers.BoundInitializers, method),
unusedDiagnostics,
useConstructorExitWarnings: false,
initialNullableState: null,
getFinalNullableState: true,
out var afterInitializersState);
unusedDiagnostics.Free();
return afterInitializersState;
}
return null;
}
/// <summary>
/// Analyzes a set of bound nodes, recording updated nullability information. This method is only
/// used when nullable is explicitly enabled for all methods but disabled otherwise to verify that
/// correct semantic information is being recorded for all bound nodes. The results are thrown away.
/// </summary>
internal static void AnalyzeWithoutRewrite(
CSharpCompilation compilation,
Symbol? symbol,
BoundNode node,
Binder binder,
DiagnosticBag diagnostics,
bool createSnapshots)
{
_ = AnalyzeWithSemanticInfo(compilation, symbol, node, binder, initialState: GetAfterInitializersState(compilation, symbol), diagnostics, createSnapshots, requiresAnalysis: false);
}
/// <summary>
/// Analyzes a set of bound nodes, recording updated nullability information, and returns an
/// updated BoundNode with the information populated.
/// </summary>
internal static BoundNode AnalyzeAndRewrite(
CSharpCompilation compilation,
Symbol? symbol,
BoundNode node,
Binder binder,
VariableState? initialState,
DiagnosticBag diagnostics,
bool createSnapshots,
out SnapshotManager? snapshotManager,
ref ImmutableDictionary<Symbol, Symbol>? remappedSymbols)
{
ImmutableDictionary<BoundExpression, (NullabilityInfo, TypeSymbol?)> analyzedNullabilitiesMap;
(snapshotManager, analyzedNullabilitiesMap) = AnalyzeWithSemanticInfo(compilation, symbol, node, binder, initialState, diagnostics, createSnapshots, requiresAnalysis: true);
return Rewrite(analyzedNullabilitiesMap, snapshotManager, node, ref remappedSymbols);
}
private static (SnapshotManager?, ImmutableDictionary<BoundExpression, (NullabilityInfo, TypeSymbol?)>) AnalyzeWithSemanticInfo(
CSharpCompilation compilation,
Symbol? symbol,
BoundNode node,
Binder binder,
VariableState? initialState,
DiagnosticBag diagnostics,
bool createSnapshots,
bool requiresAnalysis)
{
var analyzedNullabilities = ImmutableDictionary.CreateBuilder<BoundExpression, (NullabilityInfo, TypeSymbol?)>(EqualityComparer<BoundExpression>.Default, NullabilityInfoTypeComparer.Instance);
// Attributes don't have a symbol, which is what SnapshotBuilder uses as an index for maintaining global state.
// Until we have a workaround for this, disable snapshots for null symbols.
// https://github.com/dotnet/roslyn/issues/36066
var snapshotBuilder = createSnapshots && symbol != null ? new SnapshotManager.Builder() : null;
Analyze(
compilation,
symbol,
node,
binder,
binder.Conversions,
diagnostics,
useConstructorExitWarnings: true,
useDelegateInvokeParameterTypes: false,
useDelegateInvokeReturnType: false,
delegateInvokeMethodOpt: null,
initialState,
analyzedNullabilities,
snapshotBuilder,
returnTypesOpt: null,
getFinalNullableState: false,
out _,
requiresAnalysis);
var analyzedNullabilitiesMap = analyzedNullabilities.ToImmutable();
var snapshotManager = snapshotBuilder?.ToManagerAndFree();
#if DEBUG
// https://github.com/dotnet/roslyn/issues/34993 Enable for all calls
if (isNullableAnalysisEnabledAnywhere(compilation))
{
DebugVerifier.Verify(analyzedNullabilitiesMap, snapshotManager, node);
}
static bool isNullableAnalysisEnabledAnywhere(CSharpCompilation compilation)
{
if (compilation.Options.NullableContextOptions != NullableContextOptions.Disable)
{
return true;
}
return compilation.SyntaxTrees.Any(tree => ((CSharpSyntaxTree)tree).IsNullableAnalysisEnabled(new Text.TextSpan(0, tree.Length)) == true);
}
#endif
return (snapshotManager, analyzedNullabilitiesMap);
}
internal static BoundNode AnalyzeAndRewriteSpeculation(
int position,
BoundNode node,
Binder binder,
SnapshotManager originalSnapshots,
out SnapshotManager newSnapshots,
ref ImmutableDictionary<Symbol, Symbol>? remappedSymbols)
{
var analyzedNullabilities = ImmutableDictionary.CreateBuilder<BoundExpression, (NullabilityInfo, TypeSymbol?)>(EqualityComparer<BoundExpression>.Default, NullabilityInfoTypeComparer.Instance);
var newSnapshotBuilder = new SnapshotManager.Builder();
var (variables, localState) = originalSnapshots.GetSnapshot(position);
var symbol = variables.Symbol;
var walker = new NullableWalker(
binder.Compilation,
symbol,
useConstructorExitWarnings: false,
useDelegateInvokeParameterTypes: false,
useDelegateInvokeReturnType: false,
delegateInvokeMethodOpt: null,
node,
binder,
binder.Conversions,
Variables.Create(variables),
returnTypesOpt: null,
analyzedNullabilities,
newSnapshotBuilder,
isSpeculative: true);
try
{
Analyze(walker, symbol, diagnostics: null, LocalState.Create(localState), snapshotBuilderOpt: newSnapshotBuilder);
}
finally
{
walker.Free();
}
var analyzedNullabilitiesMap = analyzedNullabilities.ToImmutable();
newSnapshots = newSnapshotBuilder.ToManagerAndFree();
#if DEBUG
DebugVerifier.Verify(analyzedNullabilitiesMap, newSnapshots, node);
#endif
return Rewrite(analyzedNullabilitiesMap, newSnapshots, node, ref remappedSymbols);
}
private static BoundNode Rewrite(ImmutableDictionary<BoundExpression, (NullabilityInfo, TypeSymbol?)> updatedNullabilities, SnapshotManager? snapshotManager, BoundNode node, ref ImmutableDictionary<Symbol, Symbol>? remappedSymbols)
{
var remappedSymbolsBuilder = ImmutableDictionary.CreateBuilder<Symbol, Symbol>(Symbols.SymbolEqualityComparer.ConsiderEverything, Symbols.SymbolEqualityComparer.ConsiderEverything);
if (remappedSymbols is object)
{
// When we're rewriting for the speculative model, there will be a set of originally-mapped symbols, and we need to
// use them in addition to any symbols found during this pass of the walker.
remappedSymbolsBuilder.AddRange(remappedSymbols);
}
var rewriter = new NullabilityRewriter(updatedNullabilities, snapshotManager, remappedSymbolsBuilder);
var rewrittenNode = rewriter.Visit(node);
remappedSymbols = remappedSymbolsBuilder.ToImmutable();
return rewrittenNode;
}
private static bool HasRequiredLanguageVersion(CSharpCompilation compilation)
{
return compilation.LanguageVersion >= MessageID.IDS_FeatureNullableReferenceTypes.RequiredVersion();
}
/// <summary>
/// Returns true if the nullable analysis is needed for the region represented by <paramref name="syntaxNode"/>.
/// The syntax node is used to determine the overall nullable context for the region.
/// </summary>
internal static bool NeedsAnalysis(CSharpCompilation compilation, SyntaxNode syntaxNode)
{
return HasRequiredLanguageVersion(compilation) &&
(compilation.IsNullableAnalysisEnabledIn(syntaxNode) || compilation.IsNullableAnalysisEnabledAlways);
}
/// <summary>Analyzes a node in a "one-off" context, such as for attributes or parameter default values.</summary>
/// <remarks><paramref name="syntax"/> is the syntax span used to determine the overall nullable context.</remarks>
internal static void AnalyzeIfNeeded(
Binder binder,
BoundNode node,
SyntaxNode syntax,
DiagnosticBag diagnostics)
{
bool requiresAnalysis = true;
var compilation = binder.Compilation;
if (!HasRequiredLanguageVersion(compilation) || !compilation.IsNullableAnalysisEnabledIn(syntax))
{
if (!compilation.IsNullableAnalysisEnabledAlways)
{
return;
}
diagnostics = new DiagnosticBag();
requiresAnalysis = false;
}
Analyze(
compilation,
symbol: null,
node,
binder,
binder.Conversions,
diagnostics,
useConstructorExitWarnings: false,
useDelegateInvokeParameterTypes: false,
useDelegateInvokeReturnType: false,
delegateInvokeMethodOpt: null,
initialState: null,
analyzedNullabilityMapOpt: null,
snapshotBuilderOpt: null,
returnTypesOpt: null,
getFinalNullableState: false,
out _,
requiresAnalysis);
}
internal static void Analyze(
CSharpCompilation compilation,
BoundLambda lambda,
Conversions conversions,
DiagnosticBag diagnostics,
MethodSymbol? delegateInvokeMethodOpt,
VariableState initialState,
ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>? returnTypesOpt)
{
var symbol = lambda.Symbol;
var variables = Variables.Create(initialState.Variables).CreateNestedMethodScope(symbol);
UseDelegateInvokeParameterAndReturnTypes(lambda, delegateInvokeMethodOpt, out bool useDelegateInvokeParameterTypes, out bool useDelegateInvokeReturnType);
var walker = new NullableWalker(
compilation,
symbol,
useConstructorExitWarnings: false,
useDelegateInvokeParameterTypes: useDelegateInvokeParameterTypes,
useDelegateInvokeReturnType: useDelegateInvokeReturnType,
delegateInvokeMethodOpt: delegateInvokeMethodOpt,
lambda.Body,
lambda.Binder,
conversions,
variables,
returnTypesOpt,
analyzedNullabilityMapOpt: null,
snapshotBuilderOpt: null);
try
{
var localState = LocalState.Create(initialState.VariableNullableStates).CreateNestedMethodState(variables);
Analyze(walker, symbol, diagnostics, localState, snapshotBuilderOpt: null);
}
finally
{
walker.Free();
}
}
private static void Analyze(
CSharpCompilation compilation,
Symbol? symbol,
BoundNode node,
Binder binder,
Conversions conversions,
DiagnosticBag diagnostics,
bool useConstructorExitWarnings,
bool useDelegateInvokeParameterTypes,
bool useDelegateInvokeReturnType,
MethodSymbol? delegateInvokeMethodOpt,
VariableState? initialState,
ImmutableDictionary<BoundExpression, (NullabilityInfo, TypeSymbol?)>.Builder? analyzedNullabilityMapOpt,
SnapshotManager.Builder? snapshotBuilderOpt,
ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>? returnTypesOpt,
bool getFinalNullableState,
out VariableState? finalNullableState,
bool requiresAnalysis = true)
{
Debug.Assert(diagnostics != null);
var walker = new NullableWalker(compilation,
symbol,
useConstructorExitWarnings,
useDelegateInvokeParameterTypes,
useDelegateInvokeReturnType,
delegateInvokeMethodOpt,
node,
binder,
conversions,
initialState is null ? null : Variables.Create(initialState.Variables),
returnTypesOpt,
analyzedNullabilityMapOpt,
snapshotBuilderOpt);
finalNullableState = null;
try
{
Analyze(walker, symbol, diagnostics, initialState is null ? (Optional<LocalState>)default : LocalState.Create(initialState.VariableNullableStates), snapshotBuilderOpt, requiresAnalysis);
if (getFinalNullableState)
{
Debug.Assert(!walker.IsConditionalState);
finalNullableState = GetVariableState(walker._variables, walker.State);
}
}
finally
{
walker.Free();
}
}
private static void Analyze(
NullableWalker walker,
Symbol?