|
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class RefSafetyAnalysis : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
{
internal static void Analyze(CSharpCompilation compilation, Symbol symbol, BoundNode node, BindingDiagnosticBag diagnostics)
{
var visitor = new RefSafetyAnalysis(
compilation,
symbol,
inUnsafeRegion: InUnsafeMethod(symbol),
useUpdatedEscapeRules: symbol.ContainingModule.UseUpdatedEscapeRules,
diagnostics);
try
{
visitor.Visit(node);
}
catch (CancelledByStackGuardException e)
{
e.AddAnError(diagnostics);
}
}
internal static void Analyze(CSharpCompilation compilation, Symbol symbol, ImmutableArray<BoundInitializer> fieldAndPropertyInitializers, BindingDiagnosticBag diagnostics)
{
var visitor = new RefSafetyAnalysis(
compilation,
symbol,
inUnsafeRegion: InUnsafeMethod(symbol),
useUpdatedEscapeRules: symbol.ContainingModule.UseUpdatedEscapeRules,
diagnostics);
foreach (var initializer in fieldAndPropertyInitializers)
{
try
{
visitor.VisitFieldOrPropertyInitializer(initializer);
}
catch (CancelledByStackGuardException e)
{
e.AddAnError(diagnostics);
}
}
}
private static bool InUnsafeMethod(Symbol symbol)
{
if (symbol is SourceMemberMethodSymbol { IsUnsafe: true })
{
return true;
}
var type = symbol.ContainingType;
while (type is { })
{
var def = type.OriginalDefinition;
if (def is SourceMemberContainerTypeSymbol { IsUnsafe: true })
{
return true;
}
type = def.ContainingType;
}
return false;
}
private readonly CSharpCompilation _compilation;
private readonly Symbol _symbol;
private readonly bool _useUpdatedEscapeRules;
private readonly BindingDiagnosticBag _diagnostics;
private bool _inUnsafeRegion;
private uint _localScopeDepth;
private Dictionary<LocalSymbol, (uint RefEscapeScope, uint ValEscapeScope)>? _localEscapeScopes;
private Dictionary<BoundValuePlaceholderBase, uint>? _placeholderScopes;
private uint _patternInputValEscape;
private RefSafetyAnalysis(
CSharpCompilation compilation,
Symbol symbol,
bool inUnsafeRegion,
bool useUpdatedEscapeRules,
BindingDiagnosticBag diagnostics,
Dictionary<LocalSymbol, (uint RefEscapeScope, uint ValEscapeScope)>? localEscapeScopes = null)
{
_compilation = compilation;
_symbol = symbol;
_useUpdatedEscapeRules = useUpdatedEscapeRules;
_diagnostics = diagnostics;
_inUnsafeRegion = inUnsafeRegion;
// _localScopeDepth is incremented at each block in the method, including the
// outermost. To ensure that locals in the outermost block are considered at
// the same depth as parameters, _localScopeDepth is initialized to one less.
_localScopeDepth = CurrentMethodScope - 1;
_localEscapeScopes = localEscapeScopes;
}
private ref struct LocalScope
{
private readonly RefSafetyAnalysis _analysis;
private readonly ImmutableArray<LocalSymbol> _locals;
public LocalScope(RefSafetyAnalysis analysis, ImmutableArray<LocalSymbol> locals)
{
_analysis = analysis;
_locals = locals;
_analysis._localScopeDepth++;
foreach (var local in locals)
{
_analysis.AddLocalScopes(local, refEscapeScope: _analysis._localScopeDepth, valEscapeScope: CallingMethodScope);
}
}
public void Dispose()
{
foreach (var local in _locals)
{
_analysis.RemoveLocalScopes(local);
}
_analysis._localScopeDepth--;
}
}
private ref struct UnsafeRegion
{
private readonly RefSafetyAnalysis _analysis;
private readonly bool _previousRegion;
public UnsafeRegion(RefSafetyAnalysis analysis, bool inUnsafeRegion)
{
_analysis = analysis;
_previousRegion = analysis._inUnsafeRegion;
_analysis._inUnsafeRegion = inUnsafeRegion;
}
public void Dispose()
{
_analysis._inUnsafeRegion = _previousRegion;
}
}
private ref struct PatternInput
{
private readonly RefSafetyAnalysis _analysis;
private readonly uint _previousInputValEscape;
public PatternInput(RefSafetyAnalysis analysis, uint patternInputValEscape)
{
_analysis = analysis;
_previousInputValEscape = analysis._patternInputValEscape;
_analysis._patternInputValEscape = patternInputValEscape;
}
public void Dispose()
{
_analysis._patternInputValEscape = _previousInputValEscape;
}
}
private ref struct PlaceholderRegion
{
private readonly RefSafetyAnalysis _analysis;
private readonly ArrayBuilder<(BoundValuePlaceholderBase, uint)> _placeholders;
public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders)
{
_analysis = analysis;
_placeholders = placeholders;
foreach (var (placeholder, valEscapeScope) in placeholders)
{
_analysis.AddPlaceholderScope(placeholder, valEscapeScope);
}
}
public void Dispose()
{
foreach (var (placeholder, _) in _placeholders)
{
_analysis.RemovePlaceholderScope(placeholder);
}
_placeholders.Free();
}
}
private (uint RefEscapeScope, uint ValEscapeScope) GetLocalScopes(LocalSymbol local)
{
Debug.Assert(_localEscapeScopes is { });
return _localEscapeScopes[local];
}
private void SetLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope)
{
Debug.Assert(_localEscapeScopes is { });
_localEscapeScopes[local] = (refEscapeScope, valEscapeScope);
}
private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, uint valEscapeScope)
{
_placeholderScopes ??= new Dictionary<BoundValuePlaceholderBase, uint>();
_placeholderScopes.Add(placeholder, valEscapeScope);
}
#pragma warning disable IDE0060
private void RemovePlaceholderScope(BoundValuePlaceholderBase placeholder)
{
Debug.Assert(_placeholderScopes is { });
// https://github.com/dotnet/roslyn/issues/65961: Currently, analysis may require subsequent calls
// to GetRefEscape(), etc. for the same expression so we cannot remove placeholders eagerly.
//_placeholderScopes.Remove(placeholder);
}
#pragma warning restore IDE0060
private uint GetPlaceholderScope(BoundValuePlaceholderBase placeholder)
{
Debug.Assert(_placeholderScopes is { });
return _placeholderScopes[placeholder];
}
public override BoundNode? VisitBlock(BoundBlock node)
{
using var _1 = new UnsafeRegion(this, _inUnsafeRegion || node.HasUnsafeModifier);
using var _2 = new LocalScope(this, node.Locals);
return base.VisitBlock(node);
}
public override BoundNode? Visit(BoundNode? node)
{
#if DEBUG
if (node is BoundValuePlaceholderBase placeholder
// CheckValEscapeOfObjectInitializer() does not use BoundObjectOrCollectionValuePlaceholder.
// CheckInterpolatedStringHandlerConversionEscape() does not use BoundInterpolatedStringHandlerPlaceholder.
&& node is not (BoundObjectOrCollectionValuePlaceholder or BoundInterpolatedStringHandlerPlaceholder))
{
Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) == true);
}
#endif
return base.Visit(node);
}
private void VisitFieldOrPropertyInitializer(BoundInitializer initializer)
{
var fieldEqualsValue = (BoundFieldEqualsValue)initializer;
var field = fieldEqualsValue.Field;
var value = fieldEqualsValue.Value;
using var _ = new LocalScope(this, fieldEqualsValue.Locals);
base.Visit(value);
ValidateEscape(value, CallingMethodScope, isByRef: field.RefKind != RefKind.None, _diagnostics);
}
public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
{
var localFunction = node.Symbol;
// https://github.com/dotnet/roslyn/issues/65353: We should not reuse _localEscapeScopes
// across nested local functions or lambdas because _localScopeDepth is reset when entering
// the function or lambda so the scopes across the methods are unrelated.
var analysis = new RefSafetyAnalysis(_compilation, localFunction, _inUnsafeRegion || localFunction.IsUnsafe, _useUpdatedEscapeRules, _diagnostics, _localEscapeScopes);
analysis.Visit(node.BlockBody);
analysis.Visit(node.ExpressionBody);
return null;
}
public override BoundNode? VisitLambda(BoundLambda node)
{
var lambda = node.Symbol;
// https://github.com/dotnet/roslyn/issues/65353: We should not reuse _localEscapeScopes
// across nested local functions or lambdas because _localScopeDepth is reset when entering
// the function or lambda so the scopes across the methods are unrelated.
var analysis = new RefSafetyAnalysis(_compilation, lambda, _inUnsafeRegion, _useUpdatedEscapeRules, _diagnostics, _localEscapeScopes);
analysis.Visit(node.Body);
return null;
}
public override BoundNode? VisitConstructorMethodBody(BoundConstructorMethodBody node)
{
using var _ = new LocalScope(this, node.Locals);
return base.VisitConstructorMethodBody(node);
}
public override BoundNode? VisitForStatement(BoundForStatement node)
{
using var outerLocals = new LocalScope(this, node.OuterLocals);
using var innerLocals = new LocalScope(this, node.InnerLocals);
return base.VisitForStatement(node);
}
public override BoundNode? VisitUsingStatement(BoundUsingStatement node)
{
using var _ = new LocalScope(this, node.Locals);
var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
if (node.AwaitOpt is { } awaitableInfo)
{
uint valEscapeScope = node.ExpressionOpt is { } expr
? GetValEscape(expr, _localScopeDepth)
: _localScopeDepth;
GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, valEscapeScope);
}
using var region = new PlaceholderRegion(this, placeholders);
return base.VisitUsingStatement(node);
}
public override BoundNode? VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node)
{
var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
if (node.AwaitOpt is { } awaitableInfo)
{
GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, _localScopeDepth);
}
using var _ = new PlaceholderRegion(this, placeholders);
return base.VisitUsingLocalDeclarations(node);
}
public override BoundNode? VisitFixedStatement(BoundFixedStatement node)
{
using var _ = new LocalScope(this, node.Locals);
return base.VisitFixedStatement(node);
}
public override BoundNode? VisitDoStatement(BoundDoStatement node)
{
using var _ = new LocalScope(this, node.Locals);
return base.VisitDoStatement(node);
}
public override BoundNode? VisitWhileStatement(BoundWhileStatement node)
{
using var _ = new LocalScope(this, node.Locals);
return base.VisitWhileStatement(node);
}
public override BoundNode? VisitSwitchStatement(BoundSwitchStatement node)
{
using var _1 = new LocalScope(this, node.InnerLocals);
using var _2 = new PatternInput(this, GetValEscape(node.Expression, _localScopeDepth));
base.VisitSwitchStatement(node);
return null;
}
public override BoundNode? VisitConvertedSwitchExpression(BoundConvertedSwitchExpression node)
{
using var _ = new PatternInput(this, GetValEscape(node.Expression, _localScopeDepth));
base.VisitConvertedSwitchExpression(node);
return null;
}
public override BoundNode? VisitSwitchSection(BoundSwitchSection node)
{
using var _ = new LocalScope(this, node.Locals);
return base.VisitSwitchSection(node);
}
public override BoundNode? VisitSwitchExpressionArm(BoundSwitchExpressionArm node)
{
using var _ = new LocalScope(this, node.Locals);
return base.VisitSwitchExpressionArm(node);
}
public override BoundNode? VisitCatchBlock(BoundCatchBlock node)
{
using var _ = new LocalScope(this, node.Locals);
return base.VisitCatchBlock(node);
}
public override BoundNode? VisitLocal(BoundLocal node)
{
// _localEscapeScopes may be null for locals in top-level statements.
Debug.Assert(_localEscapeScopes?.ContainsKey(node.LocalSymbol) == true ||
(node.LocalSymbol.ContainingSymbol is SynthesizedSimpleProgramEntryPointSymbol entryPoint && _symbol != entryPoint));
return base.VisitLocal(node);
}
private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope)
{
// From https://github.com/dotnet/csharplang/blob/main/csharp-11.0/proposals/low-level-struct-improvements.md:
//
// | Parameter or Local | ref-safe-to-escape | safe-to-escape |
// |------------------------|--------------------|----------------|
// | Span<int> s | current method | calling method |
// | scoped Span<int> s | current method | current method |
// | ref Span<int> s | calling method | calling method |
// | scoped ref Span<int> s | current method | calling method |
var scopedModifier = _useUpdatedEscapeRules ? local.Scope : ScopedKind.None;
if (scopedModifier != ScopedKind.None)
{
refEscapeScope = scopedModifier == ScopedKind.ScopedRef ?
_localScopeDepth :
CurrentMethodScope;
valEscapeScope = scopedModifier == ScopedKind.ScopedValue ?
_localScopeDepth :
CallingMethodScope;
}
_localEscapeScopes ??= new Dictionary<LocalSymbol, (uint RefEscapeScope, uint ValEscapeScope)>();
_localEscapeScopes.Add(local, (refEscapeScope, valEscapeScope));
}
#pragma warning disable IDE0060
private void RemoveLocalScopes(LocalSymbol local)
{
Debug.Assert(_localEscapeScopes is { });
// https://github.com/dotnet/roslyn/issues/65961: Currently, analysis may require subsequent calls
// to GetRefEscape(), etc. for the same expression so we cannot remove locals eagerly.
//_localEscapeScopes.Remove(local);
}
#pragma warning restore IDE0060
public override BoundNode? VisitLocalDeclaration(BoundLocalDeclaration node)
{
base.VisitLocalDeclaration(node);
if (node.InitializerOpt is { } initializer)
{
var localSymbol = (SourceLocalSymbol)node.LocalSymbol;
(uint refEscapeScope, uint valEscapeScope) = GetLocalScopes(localSymbol);
if (_useUpdatedEscapeRules && localSymbol.Scope != ScopedKind.None)
{
// If the local has a scoped modifier, then the lifetime is not inferred from
// the initializer. Validate the escape values for the initializer instead.
Debug.Assert(localSymbol.RefKind == RefKind.None ||
refEscapeScope >= GetRefEscape(initializer, _localScopeDepth));
if (node.DeclaredTypeOpt?.Type.IsRefLikeType == true)
{
ValidateEscape(initializer, valEscapeScope, isByRef: false, _diagnostics);
}
}
else
{
// default to the current scope in case we need to handle self-referential error cases.
SetLocalScopes(localSymbol, _localScopeDepth, _localScopeDepth);
valEscapeScope = GetValEscape(initializer, _localScopeDepth);
if (localSymbol.RefKind != RefKind.None)
{
refEscapeScope = GetRefEscape(initializer, _localScopeDepth);
}
SetLocalScopes(localSymbol, refEscapeScope, valEscapeScope);
}
}
return null;
}
public override BoundNode? VisitReturnStatement(BoundReturnStatement node)
{
base.VisitReturnStatement(node);
if (node.ExpressionOpt is { Type: { } } expr)
{
ValidateEscape(expr, ReturnOnlyScope, node.RefKind != RefKind.None, _diagnostics);
}
return null;
}
public override BoundNode? VisitYieldReturnStatement(BoundYieldReturnStatement node)
{
base.VisitYieldReturnStatement(node);
if (node.Expression is { Type: { } } expr)
{
ValidateEscape(expr, ReturnOnlyScope, isByRef: false, _diagnostics);
}
return null;
}
public override BoundNode? VisitAssignmentOperator(BoundAssignmentOperator node)
{
base.VisitAssignmentOperator(node);
if (node.Left.Kind != BoundKind.DiscardExpression)
{
ValidateAssignment(node.Syntax, node.Left, node.Right, node.IsRef, _diagnostics);
}
return null;
}
public override BoundNode? VisitIsPatternExpression(BoundIsPatternExpression node)
{
using var _ = new PatternInput(this, GetValEscape(node.Expression, _localScopeDepth));
return base.VisitIsPatternExpression(node);
}
public override BoundNode? VisitDeclarationPattern(BoundDeclarationPattern node)
{
SetPatternLocalScopes(node);
using var _ = new PatternInput(this, getDeclarationValEscape(node.DeclaredType, _patternInputValEscape));
return base.VisitDeclarationPattern(node);
static uint getDeclarationValEscape(BoundTypeExpression typeExpression, uint valEscape)
{
return typeExpression.Type.IsRefLikeType ? valEscape : CallingMethodScope;
}
}
public override BoundNode? VisitListPattern(BoundListPattern node)
{
SetPatternLocalScopes(node);
return base.VisitListPattern(node);
}
public override BoundNode? VisitRecursivePattern(BoundRecursivePattern node)
{
SetPatternLocalScopes(node);
return base.VisitRecursivePattern(node);
}
public override BoundNode? VisitPositionalSubpattern(BoundPositionalSubpattern node)
{
using var _ = new PatternInput(this, getPositionalValEscape(node.Symbol, _patternInputValEscape));
return base.VisitPositionalSubpattern(node);
static uint getPositionalValEscape(Symbol? symbol, uint valEscape)
{
return symbol is null
? valEscape
: symbol.GetTypeOrReturnType().IsRefLikeType() ? valEscape : CallingMethodScope;
}
}
public override BoundNode? VisitPropertySubpattern(BoundPropertySubpattern node)
{
using var _ = new PatternInput(this, getMemberValEscape(node.Member, _patternInputValEscape));
return base.VisitPropertySubpattern(node);
static uint getMemberValEscape(BoundPropertySubpatternMember? member, uint valEscape)
{
if (member is null) return valEscape;
valEscape = getMemberValEscape(member.Receiver, valEscape);
return member.Type.IsRefLikeType ? valEscape : CallingMethodScope;
}
}
private void SetPatternLocalScopes(BoundObjectPattern pattern)
{
if (pattern.Variable is LocalSymbol local)
{
SetLocalScopes(local, _localScopeDepth, _patternInputValEscape);
}
}
public override BoundNode? VisitConditionalOperator(BoundConditionalOperator node)
{
base.VisitConditionalOperator(node);
if (node.IsRef)
{
ValidateRefConditionalOperator(node.Syntax, node.Consequence, node.Alternative, _diagnostics);
}
return null;
}
private PlaceholderRegion GetArgumentPlaceholders(BoundExpression? receiverOpt, ImmutableArray<BoundExpression> arguments)
{
var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
foreach (var arg in arguments)
{
if (arg is BoundConversion { ConversionKind: ConversionKind.InterpolatedStringHandler, Operand: BoundInterpolatedString or BoundBinaryOperator } conversion)
{
var interpolationData = conversion.Operand.GetInterpolatedStringHandlerData();
GetInterpolatedStringPlaceholders(placeholders, interpolationData, receiverOpt, arguments);
}
}
return new PlaceholderRegion(this, placeholders);
}
public override BoundNode? VisitCall(BoundCall node)
{
using var _ = GetArgumentPlaceholders(node.ReceiverOpt, node.Arguments);
base.VisitCall(node);
if (!node.HasErrors)
{
var method = node.Method;
CheckInvocationArgMixing(
node.Syntax,
method,
node.ReceiverOpt,
method.Parameters,
node.Arguments,
node.ArgumentRefKindsOpt,
node.ArgsToParamsOpt,
_localScopeDepth,
_diagnostics);
}
return null;
}
private void GetInterpolatedStringPlaceholders(
ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders,
in InterpolatedStringHandlerData interpolationData,
BoundExpression? receiver,
ImmutableArray<BoundExpression> arguments)
{
placeholders.Add((interpolationData.ReceiverPlaceholder, _localScopeDepth));
foreach (var placeholder in interpolationData.ArgumentPlaceholders)
{
uint valEscapeScope;
int argIndex = placeholder.ArgumentIndex;
switch (argIndex)
{
case BoundInterpolatedStringArgumentPlaceholder.InstanceParameter:
Debug.Assert(receiver != null);
valEscapeScope = receiver.GetRefKind().IsWritableReference() ? GetRefEscape(receiver, _localScopeDepth) : GetValEscape(receiver, _localScopeDepth);
break;
case BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter:
case BoundInterpolatedStringArgumentPlaceholder.UnspecifiedParameter:
continue;
case >= 0:
valEscapeScope = GetValEscape(arguments[argIndex], _localScopeDepth);
break;
default:
throw ExceptionUtilities.UnexpectedValue(placeholder.ArgumentIndex);
}
placeholders.Add((placeholder, valEscapeScope));
}
}
public override BoundNode? VisitObjectCreationExpression(BoundObjectCreationExpression node)
{
using var _ = GetArgumentPlaceholders(receiverOpt: null, node.Arguments);
base.VisitObjectCreationExpression(node);
if (!node.HasErrors)
{
var constructor = node.Constructor;
CheckInvocationArgMixing(
node.Syntax,
constructor,
receiverOpt: null,
constructor.Parameters,
node.Arguments,
node.ArgumentRefKindsOpt,
node.ArgsToParamsOpt,
_localScopeDepth,
_diagnostics);
}
return null;
}
public override BoundNode? VisitIndexerAccess(BoundIndexerAccess node)
{
using var _ = GetArgumentPlaceholders(node.ReceiverOpt, node.Arguments);
base.VisitIndexerAccess(node);
if (!node.HasErrors)
{
var indexer = node.Indexer;
CheckInvocationArgMixing(
node.Syntax,
indexer,
node.ReceiverOpt,
indexer.Parameters,
node.Arguments,
node.ArgumentRefKindsOpt,
node.ArgsToParamsOpt,
_localScopeDepth,
_diagnostics);
}
return null;
}
public override BoundNode? VisitFunctionPointerInvocation(BoundFunctionPointerInvocation node)
{
using var _ = GetArgumentPlaceholders(receiverOpt: null, node.Arguments);
base.VisitFunctionPointerInvocation(node);
if (!node.HasErrors)
{
var method = node.FunctionPointer.Signature;
CheckInvocationArgMixing(
node.Syntax,
method,
receiverOpt: null,
method.Parameters,
node.Arguments,
node.ArgumentRefKindsOpt,
argsToParamsOpt: default,
_localScopeDepth,
_diagnostics);
}
return null;
}
public override BoundNode? VisitAwaitExpression(BoundAwaitExpression node)
{
var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
GetAwaitableInstancePlaceholders(placeholders, node.AwaitableInfo, GetValEscape(node.Expression, _localScopeDepth));
using var _ = new PlaceholderRegion(this, placeholders);
base.VisitAwaitExpression(node);
return null;
}
private void GetAwaitableInstancePlaceholders(ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders, BoundAwaitableInfo awaitableInfo, uint valEscapeScope)
{
if (awaitableInfo.AwaitableInstancePlaceholder is { } placeholder)
{
placeholders.Add((placeholder, valEscapeScope));
}
}
// Based on NullableWalker.VisitDeconstructionAssignmentOperator().
public override BoundNode? VisitDeconstructionAssignmentOperator(BoundDeconstructionAssignmentOperator node)
{
base.VisitDeconstructionAssignmentOperator(node);
var left = node.Left;
var right = node.Right;
var variables = GetDeconstructionAssignmentVariables(left);
VisitDeconstructionArguments(variables, right.Syntax, right.Conversion, right.Operand);
variables.FreeAll(v => v.NestedVariables);
return null;
}
private void VisitDeconstructionArguments(ArrayBuilder<DeconstructionVariable> variables, SyntaxNode syntax, Conversion conversion, BoundExpression right)
{
Debug.Assert(conversion.Kind == ConversionKind.Deconstruction);
// We only need to visit the right side when deconstruction uses a Deconstruct() method call
// (when !DeconstructionInfo.IsDefault), not when the right side is a tuple, because ref structs
// cannot be used as tuple type arguments.
if (conversion.DeconstructionInfo.IsDefault)
{
return;
}
var invocation = conversion.DeconstructionInfo.Invocation as BoundCall;
if (invocation is null)
{
return;
}
var deconstructMethod = invocation.Method;
if (deconstructMethod is null)
{
return;
}
var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
placeholders.Add((conversion.DeconstructionInfo.InputPlaceholder, GetValEscape(right, _localScopeDepth)));
var parameters = deconstructMethod.Parameters;
int n = variables.Count;
int offset = invocation.InvokedAsExtensionMethod ? 1 : 0;
Debug.Assert(parameters.Length - offset == n);
for (int i = 0; i < n; i++)
{
var variable = variables[i];
var nestedVariables = variable.NestedVariables;
var arg = (BoundDeconstructValuePlaceholder)invocation.Arguments[i + offset];
uint valEscape = nestedVariables is null
? GetValEscape(variable.Expression, _localScopeDepth)
: _localScopeDepth;
placeholders.Add((arg, valEscape));
}
using var _ = new PlaceholderRegion(this, placeholders);
CheckInvocationArgMixing(
syntax,
deconstructMethod,
invocation.ReceiverOpt,
parameters,
invocation.Arguments,
invocation.ArgumentRefKindsOpt,
invocation.ArgsToParamsOpt,
_localScopeDepth,
_diagnostics);
for (int i = 0; i < n; i++)
{
var variable = variables[i];
var nestedVariables = variable.NestedVariables;
if (nestedVariables != null)
{
var (placeholder, placeholderConversion) = conversion.DeconstructConversionInfo[i];
var underlyingConversion = BoundNode.GetConversion(placeholderConversion, placeholder);
VisitDeconstructionArguments(nestedVariables, syntax, underlyingConversion, right: invocation.Arguments[i + offset]);
}
}
}
private readonly struct DeconstructionVariable
{
internal readonly BoundExpression Expression;
internal readonly uint ValEscape;
internal readonly ArrayBuilder<DeconstructionVariable>? NestedVariables;
internal DeconstructionVariable(BoundExpression expression, uint valEscape, ArrayBuilder<DeconstructionVariable>? nestedVariables)
{
Expression = expression;
ValEscape = valEscape;
NestedVariables = nestedVariables;
}
}
private ArrayBuilder<DeconstructionVariable> GetDeconstructionAssignmentVariables(BoundTupleExpression tuple)
{
var arguments = tuple.Arguments;
var builder = ArrayBuilder<DeconstructionVariable>.GetInstance(arguments.Length);
foreach (var arg in arguments)
{
builder.Add(getDeconstructionAssignmentVariable(arg));
}
return builder;
DeconstructionVariable getDeconstructionAssignmentVariable(BoundExpression expr)
{
return expr is BoundTupleExpression tuple
? new DeconstructionVariable(expr, valEscape: uint.MaxValue, GetDeconstructionAssignmentVariables(tuple))
: new DeconstructionVariable(expr, GetValEscape(expr, _localScopeDepth), null);
}
}
private static ImmutableArray<BoundExpression> GetDeconstructionRightParts(BoundExpression expr)
{
switch (expr)
{
case BoundTupleExpression tuple:
return tuple.Arguments;
case BoundConversion conv:
switch (conv.ConversionKind)
{
case ConversionKind.Identity:
case ConversionKind.ImplicitTupleLiteral:
return GetDeconstructionRightParts(conv.Operand);
}
break;
}
throw ExceptionUtilities.Unreachable();
}
public override BoundNode? VisitForEachStatement(BoundForEachStatement node)
{
uint collectionEscape = GetValEscape(node.Expression, _localScopeDepth);
using var _ = new LocalScope(this, ImmutableArray<LocalSymbol>.Empty);
foreach (var local in node.IterationVariables)
{
AddLocalScopes(local, refEscapeScope: local.RefKind == RefKind.None ? _localScopeDepth : collectionEscape, valEscapeScope: collectionEscape);
}
var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
if (node.DeconstructionOpt?.TargetPlaceholder is { } targetPlaceholder)
{
placeholders.Add((targetPlaceholder, collectionEscape));
}
if (node.AwaitOpt is { } awaitableInfo)
{
GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, collectionEscape);
}
using var region = new PlaceholderRegion(this, placeholders);
base.VisitForEachStatement(node);
foreach (var local in node.IterationVariables)
{
RemoveLocalScopes(local);
}
return null;
}
private static void Error(BindingDiagnosticBag diagnostics, ErrorCode code, SyntaxNodeOrToken syntax, params object[] args)
{
var location = syntax.GetLocation();
RoslynDebug.Assert(location is object);
Error(diagnostics, code, location, args);
}
private static void Error(BindingDiagnosticBag diagnostics, ErrorCode code, Location location, params object[] args)
{
diagnostics.Add(new CSDiagnostic(new CSDiagnosticInfo(code, args), location));
}
}
}
|