diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
index aea6580634b3a..5472b1d5161df 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
@@ -16,10 +16,10 @@ namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class RefSafetyAnalysis
{
- private enum EscapeLevel : uint
+ private enum EscapeLevel
{
- CallingMethod = CallingMethodScope,
- ReturnOnly = ReturnOnlyScope,
+ CallingMethod,
+ ReturnOnly
}
///
@@ -216,29 +216,6 @@ public void Deconstruct(out ParameterSymbol? parameter, out BoundExpression argu
? p.ToString()
: Argument.ToString();
}
-
- ///
- /// For the purpose of escape verification we operate with the depth of local scopes.
- /// The depth is a uint, with smaller number representing shallower/wider scopes.
- /// 0, 1 and 2 are special scopes -
- /// 0 is the "calling method" scope that is outside of the containing method/lambda.
- /// If something can escape to scope 0, it can escape to any scope in a given method through a ref parameter or return.
- /// 1 is the "return-only" scope that is outside of the containing method/lambda.
- /// If something can escape to scope 1, it can escape to any scope in a given method or can be returned, but it can't escape through a ref parameter.
- /// 2 is the "current method" scope that is just inside the containing method/lambda.
- /// If something can escape to scope 1, it can escape to any scope in a given method, but cannot be returned.
- /// n + 1 corresponds to scopes immediately inside a scope of depth n.
- /// Since sibling scopes do not intersect and a value cannot escape from one to another without
- /// escaping to a wider scope, we can use simple depth numbering without ambiguity.
- ///
- /// Generally these values are expressed via the following parameters:
- /// - escapeFrom: the scope in which an expression is being evaluated. Usually the current local
- /// scope
- /// - escapeTo: the scope to which the values are being escaped to.
- ///
- private const uint CallingMethodScope = 0;
- private const uint ReturnOnlyScope = 1;
- private const uint CurrentMethodScope = 2;
}
#nullable disable
@@ -1084,19 +1061,19 @@ private bool CheckLocalValueKind(SyntaxNode node, BoundLocal local, BindValueKin
internal partial class RefSafetyAnalysis
{
- private bool CheckLocalRefEscape(SyntaxNode node, BoundLocal local, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
+ private bool CheckLocalRefEscape(SyntaxNode node, BoundLocal local, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
{
LocalSymbol localSymbol = local.LocalSymbol;
// if local symbol can escape to the same or wider/shallower scope then escapeTo
// then it is all ok, otherwise it is an error.
- if (GetLocalScopes(localSymbol).RefEscapeScope <= escapeTo)
+ if (GetLocalScopes(localSymbol).RefEscapeScope.IsConvertibleTo(escapeTo))
{
return true;
}
var inUnsafeRegion = _inUnsafeRegion;
- if (escapeTo is CallingMethodScope or ReturnOnlyScope)
+ if (escapeTo.IsReturnable)
{
if (localSymbol.RefKind == RefKind.None)
{
@@ -1241,46 +1218,46 @@ static void reportReadOnlyParameterError(ParameterSymbol parameterSymbol, Syntax
internal partial class RefSafetyAnalysis
{
- private static EscapeLevel? EscapeLevelFromScope(uint scope) => scope switch
+ private static EscapeLevel? EscapeLevelFromScope(SafeContext lifetime) => lifetime switch
{
- ReturnOnlyScope => EscapeLevel.ReturnOnly,
- CallingMethodScope => EscapeLevel.CallingMethod,
+ { IsReturnOnly: true } => EscapeLevel.ReturnOnly,
+ { IsCallingMethod: true } => EscapeLevel.CallingMethod,
_ => null,
};
- private static uint GetParameterValEscape(ParameterSymbol parameter)
+ private static SafeContext GetParameterValEscape(ParameterSymbol parameter)
{
return parameter switch
{
- { EffectiveScope: ScopedKind.ScopedValue } => CurrentMethodScope,
- { RefKind: RefKind.Out, UseUpdatedEscapeRules: true } => ReturnOnlyScope,
- _ => CallingMethodScope
+ { EffectiveScope: ScopedKind.ScopedValue } => SafeContext.CurrentMethod,
+ { RefKind: RefKind.Out, UseUpdatedEscapeRules: true } => SafeContext.ReturnOnly,
+ _ => SafeContext.CallingMethod
};
}
private static EscapeLevel? GetParameterValEscapeLevel(ParameterSymbol parameter) =>
EscapeLevelFromScope(GetParameterValEscape(parameter));
- private static uint GetParameterRefEscape(ParameterSymbol parameter)
+ private static SafeContext GetParameterRefEscape(ParameterSymbol parameter)
{
return parameter switch
{
- { RefKind: RefKind.None } => CurrentMethodScope,
- { EffectiveScope: ScopedKind.ScopedRef } => CurrentMethodScope,
- { HasUnscopedRefAttribute: true, RefKind: RefKind.Out } => ReturnOnlyScope,
- { HasUnscopedRefAttribute: true, IsThis: false } => CallingMethodScope,
- _ => ReturnOnlyScope
+ { RefKind: RefKind.None } => SafeContext.CurrentMethod,
+ { EffectiveScope: ScopedKind.ScopedRef } => SafeContext.CurrentMethod,
+ { HasUnscopedRefAttribute: true, RefKind: RefKind.Out } => SafeContext.ReturnOnly,
+ { HasUnscopedRefAttribute: true, IsThis: false } => SafeContext.CallingMethod,
+ _ => SafeContext.ReturnOnly
};
}
private static EscapeLevel? GetParameterRefEscapeLevel(ParameterSymbol parameter) =>
EscapeLevelFromScope(GetParameterRefEscape(parameter));
- private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
{
if (_useUpdatedEscapeRules)
{
- if (GetParameterValEscape(parameter) > escapeTo)
+ if (!GetParameterValEscape(parameter).IsConvertibleTo(escapeTo))
{
Error(diagnostics, _inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, parameter);
return _inUnsafeRegion;
@@ -1294,13 +1271,13 @@ private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter,
}
}
- private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter, ParameterSymbol parameterSymbol, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
+ private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter, ParameterSymbol parameterSymbol, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
{
var refSafeToEscape = GetParameterRefEscape(parameterSymbol);
- if (refSafeToEscape > escapeTo)
+ if (!refSafeToEscape.IsConvertibleTo(escapeTo))
{
var isRefScoped = parameterSymbol.EffectiveScope == ScopedKind.ScopedRef;
- Debug.Assert(parameterSymbol.RefKind == RefKind.None || isRefScoped || refSafeToEscape == ReturnOnlyScope);
+ Debug.Assert(parameterSymbol.RefKind == RefKind.None || isRefScoped || refSafeToEscape.IsReturnOnly);
var inUnsafeRegion = _inUnsafeRegion;
if (parameter is BoundThisReference)
@@ -1314,14 +1291,14 @@ private bool CheckParameterRefEscape(SyntaxNode node, BoundExpression parameter,
{
(checkingReceiver: true, isRefScoped: true, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnScopedParameter2, parameter.Syntax),
(checkingReceiver: true, isRefScoped: true, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnScopedParameter2, parameter.Syntax),
- (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: false, ReturnOnlyScope) => (ErrorCode.ERR_RefReturnOnlyParameter2, parameter.Syntax),
- (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: true, ReturnOnlyScope) => (ErrorCode.WRN_RefReturnOnlyParameter2, parameter.Syntax),
+ (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: false, { IsReturnOnly: true }) => (ErrorCode.ERR_RefReturnOnlyParameter2, parameter.Syntax),
+ (checkingReceiver: true, isRefScoped: false, inUnsafeRegion: true, { IsReturnOnly: true }) => (ErrorCode.WRN_RefReturnOnlyParameter2, parameter.Syntax),
(checkingReceiver: true, isRefScoped: false, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnParameter2, parameter.Syntax),
(checkingReceiver: true, isRefScoped: false, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnParameter2, parameter.Syntax),
(checkingReceiver: false, isRefScoped: true, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnScopedParameter, node),
(checkingReceiver: false, isRefScoped: true, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnScopedParameter, node),
- (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: false, ReturnOnlyScope) => (ErrorCode.ERR_RefReturnOnlyParameter, node),
- (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: true, ReturnOnlyScope) => (ErrorCode.WRN_RefReturnOnlyParameter, node),
+ (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: false, { IsReturnOnly: true }) => (ErrorCode.ERR_RefReturnOnlyParameter, node),
+ (checkingReceiver: false, isRefScoped: false, inUnsafeRegion: true, { IsReturnOnly: true }) => (ErrorCode.WRN_RefReturnOnlyParameter, node),
(checkingReceiver: false, isRefScoped: false, inUnsafeRegion: false, _) => (ErrorCode.ERR_RefReturnParameter, node),
(checkingReceiver: false, isRefScoped: false, inUnsafeRegion: true, _) => (ErrorCode.WRN_RefReturnParameter, node)
};
@@ -1492,14 +1469,14 @@ private bool CheckSimpleAssignmentValueKind(SyntaxNode node, BoundAssignmentOper
internal partial class RefSafetyAnalysis
{
- private uint GetFieldRefEscape(BoundFieldAccess fieldAccess, uint scopeOfTheContainingExpression)
+ private SafeContext GetFieldRefEscape(BoundFieldAccess fieldAccess, SafeContext scopeOfTheContainingExpression)
{
var fieldSymbol = fieldAccess.FieldSymbol;
// fields that are static or belong to reference types can ref escape anywhere
if (fieldSymbol.IsStatic || fieldSymbol.ContainingType.IsReferenceType)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
if (_useUpdatedEscapeRules)
@@ -1515,7 +1492,7 @@ private uint GetFieldRefEscape(BoundFieldAccess fieldAccess, uint scopeOfTheCont
return GetRefEscape(fieldAccess.ReceiverOpt, scopeOfTheContainingExpression);
}
- private bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
{
var fieldSymbol = fieldAccess.FieldSymbol;
// fields that are static or belong to reference types can ref escape anywhere
@@ -1539,7 +1516,7 @@ private bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess,
return CheckRefEscape(node, fieldAccess.ReceiverOpt, escapeFrom, escapeTo, checkingReceiver: true, diagnostics: diagnostics);
}
- private bool CheckFieldLikeEventRefEscape(SyntaxNode node, BoundEventAccess eventAccess, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private bool CheckFieldLikeEventRefEscape(SyntaxNode node, BoundEventAccess eventAccess, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
{
var eventSymbol = eventAccess.EventSymbol;
@@ -1937,9 +1914,9 @@ private bool IsBadBaseAccess(SyntaxNode node, BoundExpression receiverOpt, Symbo
internal partial class RefSafetyAnalysis
{
- internal uint GetInterpolatedStringHandlerConversionEscapeScope(
+ internal SafeContext GetInterpolatedStringHandlerConversionEscapeScope(
BoundExpression expression,
- uint scopeOfTheContainingExpression)
+ SafeContext scopeOfTheContainingExpression)
{
var data = expression.GetInterpolatedStringHandlerData();
#if DEBUG
@@ -1949,7 +1926,7 @@ internal uint GetInterpolatedStringHandlerConversionEscapeScope(
var previousVisited = _visited;
_visited = null;
#endif
- uint escapeScope = GetValEscape(data.Construction, scopeOfTheContainingExpression);
+ SafeContext escapeScope = GetValEscape(data.Construction, scopeOfTheContainingExpression);
#if DEBUG
_visited = previousVisited;
#endif
@@ -1959,8 +1936,8 @@ internal uint GetInterpolatedStringHandlerConversionEscapeScope(
foreach (var argument in arguments)
{
- uint argEscape = GetValEscape(argument, scopeOfTheContainingExpression);
- escapeScope = Math.Max(escapeScope, argEscape);
+ SafeContext argEscape = GetValEscape(argument, scopeOfTheContainingExpression);
+ escapeScope = escapeScope.Intersect(argEscape);
}
arguments.Free();
@@ -1977,7 +1954,7 @@ internal uint GetInterpolatedStringHandlerConversionEscapeScope(
/// NOTE: we need scopeOfTheContainingExpression as some expressions such as optional in parameters or ref dynamic behave as
/// local variables declared at the scope of the invocation.
///
- private uint GetInvocationEscapeScope(
+ private SafeContext GetInvocationEscapeScope(
in MethodInfo methodInfo,
BoundExpression? receiver,
ThreeState receiverIsSubjectToCloning,
@@ -1985,7 +1962,7 @@ private uint GetInvocationEscapeScope(
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
- uint scopeOfTheContainingExpression,
+ SafeContext scopeOfTheContainingExpression,
bool isRefEscape
)
{
@@ -2010,7 +1987,7 @@ bool isRefEscape
//• the safe-to-escape of all argument expressions(including the receiver)
//
- uint escapeScope = CallingMethodScope;
+ SafeContext escapeScope = SafeContext.CallingMethod;
var escapeValues = ArrayBuilder.GetInstance();
GetEscapeValuesForOldRules(
in methodInfo,
@@ -2037,15 +2014,15 @@ bool isRefEscape
//
// val escape scope is the narrowest of
// - val escape of all byval arguments (refs cannot be wrapped into values, so their ref escape is irrelevant, only use val escapes)
- uint argumentEscape = (isRefEscape, argumentIsRefEscape) switch
+ SafeContext argumentEscape = (isRefEscape, argumentIsRefEscape) switch
{
(true, true) => GetRefEscape(argument, scopeOfTheContainingExpression),
(false, false) => GetValEscape(argument, scopeOfTheContainingExpression),
_ => escapeScope
};
- escapeScope = Math.Max(escapeScope, argumentEscape);
- if (escapeScope >= scopeOfTheContainingExpression)
+ escapeScope = escapeScope.Intersect(argumentEscape);
+ if (scopeOfTheContainingExpression.IsConvertibleTo(escapeScope))
{
// can't get any worse
return escapeScope;
@@ -2060,13 +2037,13 @@ bool isRefEscape
// check receiver if ref-like
if (methodInfo.Method?.RequiresInstanceReceiver == true && receiver?.Type?.IsRefLikeOrAllowsRefLikeType() == true)
{
- escapeScope = Math.Max(escapeScope, GetValEscape(receiver, scopeOfTheContainingExpression));
+ escapeScope = escapeScope.Intersect(GetValEscape(receiver, scopeOfTheContainingExpression));
}
return escapeScope;
}
- private uint GetInvocationEscapeWithUpdatedRules(
+ private SafeContext GetInvocationEscapeWithUpdatedRules(
in MethodInfo methodInfo,
BoundExpression? receiver,
ThreeState receiverIsSubjectToCloning,
@@ -2074,11 +2051,11 @@ private uint GetInvocationEscapeWithUpdatedRules(
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
- uint scopeOfTheContainingExpression,
+ SafeContext scopeOfTheContainingExpression,
bool isRefEscape)
{
//by default it is safe to escape
- uint escapeScope = CallingMethodScope;
+ SafeContext escapeScope = SafeContext.CallingMethod;
var argsAndParamsAll = ArrayBuilder.GetInstance();
GetFilteredInvocationArgumentsForEscapeWithUpdatedRules(
@@ -2105,12 +2082,12 @@ private uint GetInvocationEscapeWithUpdatedRules(
(param is { RefKind: not RefKind.None, Type: { } type } && type.IsRefLikeOrAllowsRefLikeType())) &&
isArgumentRefEscape == isRefEscape))
{
- uint argEscape = isArgumentRefEscape ?
+ SafeContext argEscape = isArgumentRefEscape ?
GetRefEscape(argument, scopeOfTheContainingExpression) :
GetValEscape(argument, scopeOfTheContainingExpression);
- escapeScope = Math.Max(escapeScope, argEscape);
- if (escapeScope >= scopeOfTheContainingExpression)
+ escapeScope = escapeScope.Intersect(argEscape);
+ if (scopeOfTheContainingExpression.IsConvertibleTo(escapeScope))
{
// can't get any worse
break;
@@ -2140,8 +2117,8 @@ private bool CheckInvocationEscape(
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
bool checkingReceiver,
- uint escapeFrom,
- uint escapeTo,
+ SafeContext escapeFrom,
+ SafeContext escapeTo,
BindingDiagnosticBag diagnostics,
bool isRefEscape
)
@@ -2233,8 +2210,8 @@ private bool CheckInvocationEscapeWithUpdatedRules(
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
bool checkingReceiver,
- uint escapeFrom,
- uint escapeTo,
+ SafeContext escapeFrom,
+ SafeContext escapeTo,
BindingDiagnosticBag diagnostics,
bool isRefEscape)
{
@@ -2768,7 +2745,7 @@ private bool ShouldInferDeclarationExpressionValEscape(BoundExpression argument,
_ => null
};
if (symbol is SourceLocalSymbol local &&
- GetLocalScopes(local).ValEscapeScope == CallingMethodScope)
+ GetLocalScopes(local).ValEscapeScope.IsCallingMethod)
{
localSymbol = local;
return true;
@@ -2797,7 +2774,7 @@ private bool CheckInvocationArgMixing(
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
- uint scopeOfTheContainingExpression,
+ SafeContext scopeOfTheContainingExpression,
BindingDiagnosticBag diagnostics)
{
if (methodInfo.UseUpdatedEscapeRules)
@@ -2818,7 +2795,7 @@ private bool CheckInvocationArgMixing(
}
// widest possible escape via writeable ref-like receiver or ref/out argument.
- uint escapeTo = scopeOfTheContainingExpression;
+ SafeContext escapeTo = scopeOfTheContainingExpression;
// collect all writeable ref-like arguments, including receiver
var escapeArguments = ArrayBuilder.GetInstance();
@@ -2850,7 +2827,7 @@ private bool CheckInvocationArgMixing(
&& !argument.IsDiscardExpression()
&& argument.Type?.IsRefLikeOrAllowsRefLikeType() == true)
{
- escapeTo = Math.Min(escapeTo, GetValEscape(argument, scopeOfTheContainingExpression));
+ escapeTo = escapeTo.Union(GetValEscape(argument, scopeOfTheContainingExpression));
}
}
@@ -2858,12 +2835,12 @@ private bool CheckInvocationArgMixing(
// track the widest scope that arguments could safely escape to.
// use this scope as the inferred STE of declaration expressions.
- var inferredDestinationValEscape = CallingMethodScope;
+ var inferredDestinationValEscape = SafeContext.CallingMethod;
foreach (var (parameter, argument, _) in escapeArguments)
{
// in the old rules, we assume that refs cannot escape into ref struct variables.
// e.g. in `dest = M(ref arg)`, we assume `ref arg` will not escape into `dest`, but `arg` might.
- inferredDestinationValEscape = Math.Max(inferredDestinationValEscape, GetValEscape(argument, scopeOfTheContainingExpression));
+ inferredDestinationValEscape = inferredDestinationValEscape.Intersect(GetValEscape(argument, scopeOfTheContainingExpression));
if (!hasMixingError && !CheckValEscape(argument.Syntax, argument, scopeOfTheContainingExpression, escapeTo, false, diagnostics))
{
string parameterName = GetInvocationParameterName(parameter);
@@ -2897,7 +2874,7 @@ private bool CheckInvocationArgMixingWithUpdatedRules(
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
- uint scopeOfTheContainingExpression,
+ SafeContext scopeOfTheContainingExpression,
BindingDiagnosticBag diagnostics)
{
var mixableArguments = ArrayBuilder.GetInstance();
@@ -2961,10 +2938,10 @@ void inferDeclarationExpressionValEscape()
{
// find the widest scope that arguments could safely escape to.
// use this scope as the inferred STE of declaration expressions.
- var inferredDestinationValEscape = CallingMethodScope;
+ var inferredDestinationValEscape = SafeContext.CallingMethod;
foreach (var (_, fromArg, _, isRefEscape) in escapeValues)
{
- inferredDestinationValEscape = Math.Max(inferredDestinationValEscape, isRefEscape
+ inferredDestinationValEscape = inferredDestinationValEscape.Intersect(isRefEscape
? GetRefEscape(fromArg, scopeOfTheContainingExpression)
: GetValEscape(fromArg, scopeOfTheContainingExpression));
}
@@ -3235,9 +3212,9 @@ private static ErrorCode GetStandardLvalueError(BindValueKind kind)
internal partial class RefSafetyAnalysis
{
- private static ErrorCode GetStandardRValueRefEscapeError(uint escapeTo)
+ private static ErrorCode GetStandardRValueRefEscapeError(SafeContext escapeTo)
{
- if (escapeTo is CallingMethodScope or ReturnOnlyScope)
+ if (escapeTo.IsReturnable)
{
return ErrorCode.ERR_RefReturnLvalueExpected;
}
@@ -3319,7 +3296,7 @@ internal partial class RefSafetyAnalysis
///
/// Checks whether given expression can escape from the current scope to the .
///
- internal void ValidateEscape(BoundExpression expr, uint escapeTo, bool isByRef, BindingDiagnosticBag diagnostics)
+ internal void ValidateEscape(BoundExpression expr, SafeContext escapeTo, bool isByRef, BindingDiagnosticBag diagnostics)
{
// The result of escape analysis is affected by the expression's type.
// We can't do escape analysis on expressions which lack a type, such as 'target typed new()', until they are converted.
@@ -3342,7 +3319,7 @@ internal void ValidateEscape(BoundExpression expr, uint escapeTo, bool isByRef,
/// There are few cases where RValues are permitted to be passed by reference which implies that a temporary local proxy is passed instead.
/// We reflect such behavior by constraining the escape value to the narrowest scope possible.
///
- internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpression)
+ internal SafeContext GetRefEscape(BoundExpression expr, SafeContext scopeOfTheContainingExpression)
{
#if DEBUG
AssertVisited(expr);
@@ -3351,13 +3328,13 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres
// cannot infer anything from errors
if (expr.HasAnyErrors)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// cannot infer anything from Void (broken code)
if (expr.Type?.GetSpecialTypeSafe() == SpecialType.System_Void)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// constants/literals cannot ref-escape current scope
@@ -3374,13 +3351,13 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres
case BoundKind.PointerIndirectionOperator:
case BoundKind.PointerElementAccess:
// array elements and pointer dereferencing are readwrite variables
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
case BoundKind.RefValueOperator:
// The undocumented __refvalue(tr, T) expression results in an lvalue of type T.
// for compat reasons it is not ref-returnable (since TypedReference is not val-returnable)
// it can, however, ref-escape to any other level (since TypedReference can val-escape to any other level)
- return CurrentMethodScope;
+ return SafeContext.CurrentMethod;
case BoundKind.DiscardExpression:
// same as write-only byval local
@@ -3414,8 +3391,8 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres
if (conditional.IsRef)
{
// ref conditional defers to its operands
- return Math.Max(GetRefEscape(conditional.Consequence, scopeOfTheContainingExpression),
- GetRefEscape(conditional.Alternative, scopeOfTheContainingExpression));
+ return GetRefEscape(conditional.Consequence, scopeOfTheContainingExpression)
+ .Intersect(GetRefEscape(conditional.Alternative, scopeOfTheContainingExpression));
}
// otherwise it is an RValue
@@ -3437,7 +3414,7 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres
// field-like events that are static or belong to reference types can ref escape anywhere
if (eventSymbol.IsStatic || eventSymbol.ContainingType.IsReferenceType)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// for other events defer to the receiver.
@@ -3527,7 +3504,7 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres
case BoundArrayAccess:
// array elements are readwrite variables
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
case BoundCall call:
var methodSymbol = call.Method;
@@ -3650,7 +3627,7 @@ internal uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpres
/// The result indicates whether the escape is possible.
/// Additionally, the method emits diagnostics (possibly more than one, recursively) that would help identify the cause for the failure.
///
- internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeFrom, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
+ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, SafeContext escapeFrom, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
{
#if DEBUG
AssertVisited(expr);
@@ -3658,7 +3635,7 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF
Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter());
- if (escapeTo >= escapeFrom)
+ if (escapeFrom.IsConvertibleTo(escapeTo))
{
// escaping to same or narrower scope is ok.
return true;
@@ -3694,7 +3671,7 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF
case BoundKind.RefValueOperator:
// The undocumented __refvalue(tr, T) expression results in an lvalue of type T.
// for compat reasons it is not ref-returnable (since TypedReference is not val-returnable)
- if (escapeTo is CallingMethodScope or ReturnOnlyScope)
+ if (escapeTo.IsReturnable)
{
break;
}
@@ -3723,7 +3700,7 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF
case BoundKind.CapturedReceiverPlaceholder:
// Equivalent to a non-ref local with the underlying receiver as an initializer provided at declaration
- if (((BoundCapturedReceiverPlaceholder)expr).LocalScopeDepth <= escapeTo)
+ if (((BoundCapturedReceiverPlaceholder)expr).LocalScopeDepth.IsConvertibleTo(escapeTo))
{
return true;
}
@@ -4019,12 +3996,12 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF
return false;
}
- internal uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheContainingExpression)
+ internal SafeContext GetBroadestValEscape(BoundTupleExpression expr, SafeContext scopeOfTheContainingExpression)
{
- uint broadest = scopeOfTheContainingExpression;
+ SafeContext broadest = scopeOfTheContainingExpression;
foreach (var element in expr.Arguments)
{
- uint valEscape;
+ SafeContext valEscape;
if (element is BoundTupleExpression te)
{
valEscape = GetBroadestValEscape(te, scopeOfTheContainingExpression);
@@ -4034,7 +4011,7 @@ internal uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheCon
valEscape = GetValEscape(element, scopeOfTheContainingExpression);
}
- broadest = Math.Min(broadest, valEscape);
+ broadest = broadest.Union(valEscape);
}
return broadest;
@@ -4045,7 +4022,7 @@ internal uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheCon
///
/// NOTE: unless the type of expression is ref-like, the result is Binder.ExternalScope since ordinary values can always be returned from methods.
///
- internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpression)
+ internal SafeContext GetValEscape(BoundExpression expr, SafeContext scopeOfTheContainingExpression)
{
#if DEBUG
AssertVisited(expr);
@@ -4054,19 +4031,19 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
// cannot infer anything from errors
if (expr.HasAnyErrors)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// constants/literals cannot refer to local state
if (expr.ConstantValueOpt != null)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// to have local-referring values an expression must have a ref-like type
if (expr.Type?.IsRefLikeOrAllowsRefLikeType() != true)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// cover case that can refer to local state
@@ -4081,7 +4058,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
case BoundKind.DefaultExpression:
case BoundKind.Utf8String:
// always returnable
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
case BoundKind.Parameter:
return GetParameterValEscape(((BoundParameter)expr).ParameterSymbol);
@@ -4089,7 +4066,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
case BoundKind.FromEndIndexExpression:
// We are going to call a constructor that takes an integer and a bool. Cannot leak any references through them.
// always returnable
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
case BoundKind.TupleLiteral:
case BoundKind.ConvertedTupleLiteral:
@@ -4101,10 +4078,10 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
// for compat reasons
// NB: it also means can`t assign stackalloc spans to a __refvalue
// we are ok with that.
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
case BoundKind.DiscardExpression:
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
case BoundKind.DeconstructValuePlaceholder:
case BoundKind.InterpolatedStringArgumentPlaceholder:
@@ -4121,7 +4098,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
case BoundKind.StackAllocArrayCreation:
case BoundKind.ConvertedStackAllocExpression:
- return CurrentMethodScope;
+ return SafeContext.CurrentMethod;
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
@@ -4136,14 +4113,13 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
}
// val conditional gets narrowest of its operands
- return Math.Max(consEscape,
- GetValEscape(conditional.Alternative, scopeOfTheContainingExpression));
+ return consEscape.Intersect(GetValEscape(conditional.Alternative, scopeOfTheContainingExpression));
case BoundKind.NullCoalescingOperator:
var coalescingOp = (BoundNullCoalescingOperator)expr;
- return Math.Max(GetValEscape(coalescingOp.LeftOperand, scopeOfTheContainingExpression),
- GetValEscape(coalescingOp.RightOperand, scopeOfTheContainingExpression));
+ return GetValEscape(coalescingOp.LeftOperand, scopeOfTheContainingExpression)
+ .Intersect(GetValEscape(coalescingOp.RightOperand, scopeOfTheContainingExpression));
case BoundKind.FieldAccess:
var fieldAccess = (BoundFieldAccess)expr;
@@ -4152,7 +4128,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
if (fieldSymbol.IsStatic || !fieldSymbol.ContainingType.IsRefLikeType)
{
// Already an error state.
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// for ref-like fields defer to the receiver.
@@ -4301,7 +4277,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
var initializerOpt = objectCreation.InitializerExpressionOpt;
if (initializerOpt != null)
{
- escape = Math.Max(escape, GetValEscape(initializerOpt, scopeOfTheContainingExpression));
+ escape = escape.Intersect(GetValEscape(initializerOpt, scopeOfTheContainingExpression));
}
return escape;
@@ -4311,12 +4287,12 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
{
var newT = (BoundNewT)expr;
// By default it is safe to escape
- var escape = CallingMethodScope;
+ var escape = SafeContext.CallingMethod;
var initializerOpt = newT.InitializerExpressionOpt;
if (initializerOpt != null)
{
- escape = Math.Max(escape, GetValEscape(initializerOpt, scopeOfTheContainingExpression));
+ escape = escape.Intersect(GetValEscape(initializerOpt, scopeOfTheContainingExpression));
}
return escape;
@@ -4325,8 +4301,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
case BoundKind.WithExpression:
var withExpression = (BoundWithExpression)expr;
- return Math.Max(GetValEscape(withExpression.Receiver, scopeOfTheContainingExpression),
- GetValEscape(withExpression.InitializerExpression, scopeOfTheContainingExpression));
+ return GetValEscape(withExpression.Receiver, scopeOfTheContainingExpression)
+ .Intersect(GetValEscape(withExpression.InitializerExpression, scopeOfTheContainingExpression));
case BoundKind.UnaryOperator:
var unaryOperator = (BoundUnaryOperator)expr;
@@ -4358,8 +4334,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
if (conversion.ConversionKind == ConversionKind.CollectionExpression)
{
return HasLocalScope((BoundCollectionExpression)conversion.Operand) ?
- CurrentMethodScope :
- CallingMethodScope;
+ SafeContext.CurrentMethod :
+ SafeContext.CallingMethod;
}
if (conversion.Conversion.IsInlineArray)
@@ -4429,8 +4405,8 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
isRefEscape: false);
}
- return Math.Max(GetValEscape(compound.Left, scopeOfTheContainingExpression),
- GetValEscape(compound.Right, scopeOfTheContainingExpression));
+ return GetValEscape(compound.Left, scopeOfTheContainingExpression)
+ .Intersect(GetValEscape(compound.Right, scopeOfTheContainingExpression));
case BoundKind.BinaryOperator:
var binary = (BoundBinaryOperator)expr;
@@ -4449,14 +4425,14 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
isRefEscape: false);
}
- return Math.Max(GetValEscape(binary.Left, scopeOfTheContainingExpression),
- GetValEscape(binary.Right, scopeOfTheContainingExpression));
+ return GetValEscape(binary.Left, scopeOfTheContainingExpression)
+ .Intersect(GetValEscape(binary.Right, scopeOfTheContainingExpression));
case BoundKind.RangeExpression:
var range = (BoundRangeExpression)expr;
- return Math.Max((range.LeftOperandOpt is { } left ? GetValEscape(left, scopeOfTheContainingExpression) : CallingMethodScope),
- (range.RightOperandOpt is { } right ? GetValEscape(right, scopeOfTheContainingExpression) : CallingMethodScope));
+ return (range.LeftOperandOpt is { } left ? GetValEscape(left, scopeOfTheContainingExpression) : SafeContext.CallingMethod)
+ .Intersect(range.RightOperandOpt is { } right ? GetValEscape(right, scopeOfTheContainingExpression) : SafeContext.CallingMethod);
case BoundKind.UserDefinedConditionalLogicalOperator:
var uo = (BoundUserDefinedConditionalLogicalOperator)expr;
@@ -4515,7 +4491,7 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
case BoundKind.PointerElementAccess:
case BoundKind.PointerIndirectionOperator:
// Unsafe code will always be allowed to escape.
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
case BoundKind.AsOperator:
case BoundKind.AwaitExpression:
@@ -4589,12 +4565,12 @@ private bool HasLocalScope(BoundCollectionExpression expr)
}
}
- private uint GetTupleValEscape(ImmutableArray elements, uint scopeOfTheContainingExpression)
+ private SafeContext GetTupleValEscape(ImmutableArray elements, SafeContext scopeOfTheContainingExpression)
{
- uint narrowestScope = scopeOfTheContainingExpression;
+ SafeContext narrowestScope = scopeOfTheContainingExpression;
foreach (var element in elements)
{
- narrowestScope = Math.Max(narrowestScope, GetValEscape(element, scopeOfTheContainingExpression));
+ narrowestScope = narrowestScope.Intersect(GetValEscape(element, scopeOfTheContainingExpression));
}
return narrowestScope;
@@ -4606,21 +4582,21 @@ private uint GetTupleValEscape(ImmutableArray elements, uint sc
/// passed to an indexer for example only matter if they can escape into the receiver
/// as a stored field.
///
- private uint GetValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint scopeOfTheContainingExpression)
+ private SafeContext GetValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, SafeContext scopeOfTheContainingExpression)
{
- var result = CallingMethodScope;
+ var result = SafeContext.CallingMethod;
foreach (var expr in initExpr.Initializers)
{
var exprResult = GetValEscapeOfObjectMemberInitializer(expr, scopeOfTheContainingExpression);
- result = Math.Max(result, exprResult);
+ result = result.Intersect(exprResult);
}
return result;
}
- private uint GetValEscapeOfObjectMemberInitializer(BoundExpression expr, uint scopeOfTheContainingExpression)
+ private SafeContext GetValEscapeOfObjectMemberInitializer(BoundExpression expr, SafeContext scopeOfTheContainingExpression)
{
- uint result;
+ SafeContext result;
if (expr.Kind == BoundKind.AssignmentOperator)
{
var assignment = (BoundAssignmentOperator)expr;
@@ -4643,23 +4619,23 @@ private uint GetValEscapeOfObjectMemberInitializer(BoundExpression expr, uint sc
return result;
- uint getIndexerEscape(
+ SafeContext getIndexerEscape(
PropertySymbol indexer,
BoundObjectInitializerMember expr,
- uint rightEscapeScope)
+ SafeContext rightEscapeScope)
{
Debug.Assert(expr.AccessorKind != AccessorKind.Unknown);
var methodInfo = MethodInfo.Create(indexer, expr.AccessorKind);
if (methodInfo.Method is null)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
// If the indexer is readonly then none of the arguments can contribute to
// the receiver escape
if (methodInfo.Method.IsEffectivelyReadOnly)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
var escapeValues = ArrayBuilder.GetInstance();
@@ -4676,7 +4652,7 @@ uint getIndexerEscape(
mixableArguments: null,
escapeValues);
- uint receiverEscapeScope = CallingMethodScope;
+ SafeContext receiverEscapeScope = SafeContext.CallingMethod;
foreach (var escapeValue in escapeValues)
{
// This is a call to an indexer so the ref escape scope can only impact the escape value if it
@@ -4686,25 +4662,25 @@ uint getIndexerEscape(
continue;
}
- uint escapeScope = escapeValue.IsRefEscape
+ SafeContext escapeScope = escapeValue.IsRefEscape
? GetRefEscape(escapeValue.Argument, scopeOfTheContainingExpression)
: GetValEscape(escapeValue.Argument, scopeOfTheContainingExpression);
- receiverEscapeScope = Math.Max(escapeScope, receiverEscapeScope);
+ receiverEscapeScope = escapeScope.Intersect(receiverEscapeScope);
}
escapeValues.Free();
- return Math.Max(receiverEscapeScope, rightEscapeScope);
+ return receiverEscapeScope.Intersect(rightEscapeScope);
}
- uint getPropertyEscape(
+ SafeContext getPropertyEscape(
PropertySymbol property,
- uint rightEscapeScope)
+ SafeContext rightEscapeScope)
{
var accessorKind = property.RefKind == RefKind.None ? AccessorKind.Set : AccessorKind.Get;
var methodInfo = MethodInfo.Create(property, accessorKind);
if (methodInfo.Method is null || methodInfo.Method.IsEffectivelyReadOnly)
{
- return CallingMethodScope;
+ return SafeContext.CallingMethod;
}
return rightEscapeScope;
@@ -4713,12 +4689,12 @@ uint getPropertyEscape(
#nullable disable
- private uint GetValEscape(ImmutableArray expressions, uint scopeOfTheContainingExpression)
+ private SafeContext GetValEscape(ImmutableArray expressions, SafeContext scopeOfTheContainingExpression)
{
- var result = CallingMethodScope;
+ var result = SafeContext.CallingMethod;
foreach (var expression in expressions)
{
- result = Math.Max(result, GetValEscape(expression, scopeOfTheContainingExpression));
+ result = result.Intersect(GetValEscape(expression, scopeOfTheContainingExpression));
}
return result;
@@ -4729,7 +4705,7 @@ private uint GetValEscape(ImmutableArray expressions, uint scop
/// The result indicates whether the escape is possible.
/// Additionally, the method emits diagnostics (possibly more than one, recursively) that would help identify the cause for the failure.
///
- internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeFrom, uint escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
+ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, SafeContext escapeFrom, SafeContext escapeTo, bool checkingReceiver, BindingDiagnosticBag diagnostics)
{
#if DEBUG
AssertVisited(expr);
@@ -4737,7 +4713,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF
Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter());
- if (escapeTo >= escapeFrom)
+ if (escapeFrom.IsConvertibleTo(escapeTo))
{
// escaping to same or narrower scope is ok.
return true;
@@ -4796,7 +4772,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF
case BoundKind.DeconstructValuePlaceholder:
case BoundKind.AwaitableValuePlaceholder:
case BoundKind.InterpolatedStringArgumentPlaceholder:
- if (GetPlaceholderScope((BoundValuePlaceholderBase)expr) > escapeTo)
+ if (!GetPlaceholderScope((BoundValuePlaceholderBase)expr).IsConvertibleTo(escapeTo))
{
Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, expr.Syntax);
return inUnsafeRegion;
@@ -4805,7 +4781,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF
case BoundKind.Local:
var localSymbol = ((BoundLocal)expr).LocalSymbol;
- if (GetLocalScopes(localSymbol).ValEscapeScope > escapeTo)
+ if (!GetLocalScopes(localSymbol).ValEscapeScope.IsConvertibleTo(escapeTo))
{
Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, localSymbol);
return inUnsafeRegion;
@@ -4819,7 +4795,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF
case BoundKind.StackAllocArrayCreation:
case BoundKind.ConvertedStackAllocExpression:
- if (escapeTo < CurrentMethodScope)
+ if (!SafeContext.CurrentMethod.IsConvertibleTo(escapeTo))
{
Error(diagnostics, inUnsafeRegion ? ErrorCode.WRN_EscapeStackAlloc : ErrorCode.ERR_EscapeStackAlloc, node, expr.Type);
return inUnsafeRegion;
@@ -5128,7 +5104,7 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF
if (conversion.ConversionKind == ConversionKind.CollectionExpression)
{
- if (HasLocalScope((BoundCollectionExpression)conversion.Operand) && escapeTo < CurrentMethodScope)
+ if (HasLocalScope((BoundCollectionExpression)conversion.Operand) && !SafeContext.CurrentMethod.IsConvertibleTo(escapeTo))
{
Error(diagnostics, ErrorCode.ERR_CollectionExpressionEscape, node, expr.Type);
return false;
@@ -5561,7 +5537,7 @@ private SignatureOnlyMethodSymbol GetInlineArrayConversionEquivalentSignatureMet
return equivalentSignatureMethod;
}
- private bool CheckTupleValEscape(ImmutableArray elements, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private bool CheckTupleValEscape(ImmutableArray elements, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
{
foreach (var element in elements)
{
@@ -5576,11 +5552,11 @@ private bool CheckTupleValEscape(ImmutableArray elements, uint
#nullable enable
- private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
{
foreach (var expr in initExpr.Initializers)
{
- if (GetValEscapeOfObjectMemberInitializer(expr, escapeFrom) > escapeTo)
+ if (!GetValEscapeOfObjectMemberInitializer(expr, escapeFrom).IsConvertibleTo(escapeTo))
{
Error(diagnostics, _inUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, initExpr.Syntax, expr.Syntax);
return false;
@@ -5592,7 +5568,7 @@ private bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression
#nullable disable
- private bool CheckValEscape(ImmutableArray expressions, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private bool CheckValEscape(ImmutableArray expressions, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
{
foreach (var expression in expressions)
{
@@ -5605,7 +5581,7 @@ private bool CheckValEscape(ImmutableArray expressions, uint es
return true;
}
- private bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expression, uint escapeFrom, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private bool CheckInterpolatedStringHandlerConversionEscape(BoundExpression expression, SafeContext escapeFrom, SafeContext escapeTo, BindingDiagnosticBag diagnostics)
{
var data = expression.GetInterpolatedStringHandlerData();
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
index 13230a166b2c1..7f62ca8b1feae 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
@@ -4377,13 +4377,13 @@ private void ValidateRefConditionalOperator(SyntaxNode node, BoundExpression tru
var currentScope = _localScopeDepth;
// val-escape must agree on both branches.
- uint whenTrueEscape = GetValEscape(trueExpr, currentScope);
- uint whenFalseEscape = GetValEscape(falseExpr, currentScope);
+ SafeContext whenTrueEscape = GetValEscape(trueExpr, currentScope);
+ SafeContext whenFalseEscape = GetValEscape(falseExpr, currentScope);
if (whenTrueEscape != whenFalseEscape)
{
// ask the one with narrower escape, for the wider - hopefully the errors will make the violation easier to fix.
- if (whenTrueEscape < whenFalseEscape)
+ if (!whenFalseEscape.IsConvertibleTo(whenTrueEscape))
CheckValEscape(falseExpr.Syntax, falseExpr, currentScope, whenTrueEscape, checkingReceiver: false, diagnostics: diagnostics);
else
CheckValEscape(trueExpr.Syntax, trueExpr, currentScope, whenFalseEscape, checkingReceiver: false, diagnostics: diagnostics);
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
index 0b4c03c795406..f31593f42dc8a 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
@@ -1549,12 +1549,12 @@ private void ValidateAssignment(
var leftEscape = GetRefEscape(op1, _localScopeDepth);
var rightEscape = GetRefEscape(op2, _localScopeDepth);
- if (leftEscape < rightEscape)
+ if (!rightEscape.IsConvertibleTo(leftEscape))
{
var errorCode = (rightEscape, _inUnsafeRegion) switch
{
- (ReturnOnlyScope, false) => ErrorCode.ERR_RefAssignReturnOnly,
- (ReturnOnlyScope, true) => ErrorCode.WRN_RefAssignReturnOnly,
+ ({ IsReturnOnly: true }, false) => ErrorCode.ERR_RefAssignReturnOnly,
+ ({ IsReturnOnly: true }, true) => ErrorCode.WRN_RefAssignReturnOnly,
(_, false) => ErrorCode.ERR_RefAssignNarrower,
(_, true) => ErrorCode.WRN_RefAssignNarrower
};
@@ -1570,12 +1570,13 @@ private void ValidateAssignment(
leftEscape = GetValEscape(op1, _localScopeDepth);
rightEscape = GetValEscape(op2, _localScopeDepth);
- Debug.Assert(leftEscape == rightEscape || op1.Type.IsRefLikeOrAllowsRefLikeType());
+ Debug.Assert(leftEscape.Equals(rightEscape) || op1.Type.IsRefLikeOrAllowsRefLikeType());
- // We only check if the safe-to-escape of e2 is wider than the safe-to-escape of e1 here,
- // we don't check for equality. The case where the safe-to-escape of e2 is narrower than
- // e1 is handled in the if (op1.Type.IsRefLikeType) { ... } block later.
- if (leftEscape > rightEscape)
+ // We only check if the left SafeContext is convertible to the right here
+ // in order to give a more useful diagnostic.
+ // Later on we check if right SafeContext is convertible to left,
+ // which effectively means these SafeContexts must be equal.
+ if (!leftEscape.IsConvertibleTo(rightEscape))
{
Debug.Assert(op1.Kind != BoundKind.Parameter); // If the assert fails, add a corresponding test.
diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
index d17a26686d61d..9e7ff5ea2f103 100644
--- a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
+++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
@@ -59,10 +59,10 @@ private static bool InUnsafeMethod(Symbol symbol)
private readonly bool _useUpdatedEscapeRules;
private readonly BindingDiagnosticBag _diagnostics;
private bool _inUnsafeRegion;
- private uint _localScopeDepth;
- private Dictionary? _localEscapeScopes;
- private Dictionary? _placeholderScopes;
- private uint _patternInputValEscape;
+ private SafeContext _localScopeDepth;
+ private Dictionary? _localEscapeScopes;
+ private Dictionary? _placeholderScopes;
+ private SafeContext _patternInputValEscape;
#if DEBUG
private const int MaxTrackVisited = 100; // Avoid tracking if too many expressions.
private HashSet? _visited = new HashSet();
@@ -83,7 +83,7 @@ private RefSafetyAnalysis(
// _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;
+ _localScopeDepth = SafeContext.CurrentMethod.Wider();
}
private ref struct LocalScope
@@ -95,10 +95,10 @@ public LocalScope(RefSafetyAnalysis analysis, ImmutableArray locals
{
_analysis = analysis;
_locals = locals;
- _analysis._localScopeDepth++;
+ _analysis._localScopeDepth = _analysis._localScopeDepth.Narrower();
foreach (var local in locals)
{
- _analysis.AddLocalScopes(local, refEscapeScope: _analysis._localScopeDepth, valEscapeScope: CallingMethodScope);
+ _analysis.AddLocalScopes(local, refEscapeScope: _analysis._localScopeDepth, valEscapeScope: SafeContext.CallingMethod);
}
}
@@ -108,7 +108,7 @@ public void Dispose()
{
_analysis.RemoveLocalScopes(local);
}
- _analysis._localScopeDepth--;
+ _analysis._localScopeDepth = _analysis._localScopeDepth.Wider();
}
}
@@ -133,9 +133,9 @@ public void Dispose()
private ref struct PatternInput
{
private readonly RefSafetyAnalysis _analysis;
- private readonly uint _previousInputValEscape;
+ private readonly SafeContext _previousInputValEscape;
- public PatternInput(RefSafetyAnalysis analysis, uint patternInputValEscape)
+ public PatternInput(RefSafetyAnalysis analysis, SafeContext patternInputValEscape)
{
_analysis = analysis;
_previousInputValEscape = analysis._patternInputValEscape;
@@ -151,9 +151,9 @@ public void Dispose()
private ref struct PlaceholderRegion
{
private readonly RefSafetyAnalysis _analysis;
- private readonly ArrayBuilder<(BoundValuePlaceholderBase, uint)> _placeholders;
+ private readonly ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> _placeholders;
- public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders)
+ public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders)
{
_analysis = analysis;
_placeholders = placeholders;
@@ -173,30 +173,30 @@ public void Dispose()
}
}
- private (uint RefEscapeScope, uint ValEscapeScope) GetLocalScopes(LocalSymbol local)
+ private (SafeContext RefEscapeScope, SafeContext ValEscapeScope) GetLocalScopes(LocalSymbol local)
{
Debug.Assert(_localEscapeScopes?.ContainsKey(local) == true || _symbol != local.ContainingSymbol);
return _localEscapeScopes?.TryGetValue(local, out var scopes) == true
? scopes
- : (CurrentMethodScope, CallingMethodScope);
+ : (SafeContext.CurrentMethod, SafeContext.CallingMethod);
}
- private void SetLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope)
+ private void SetLocalScopes(LocalSymbol local, SafeContext refEscapeScope, SafeContext valEscapeScope)
{
Debug.Assert(_localEscapeScopes?.ContainsKey(local) == true);
AddOrSetLocalScopes(local, refEscapeScope, valEscapeScope);
}
- private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, uint valEscapeScope)
+ private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, SafeContext valEscapeScope)
{
Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) != true);
// Consider not adding the placeholder to the dictionary if the escape scope is
// CallingMethod, and simply fallback to that value in GetPlaceholderScope().
- _placeholderScopes ??= new Dictionary();
+ _placeholderScopes ??= new Dictionary();
_placeholderScopes[placeholder] = valEscapeScope;
}
@@ -211,13 +211,13 @@ private void RemovePlaceholderScope(BoundValuePlaceholderBase placeholder)
}
#pragma warning restore IDE0060
- private uint GetPlaceholderScope(BoundValuePlaceholderBase placeholder)
+ private SafeContext GetPlaceholderScope(BoundValuePlaceholderBase placeholder)
{
Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) == true);
return _placeholderScopes?.TryGetValue(placeholder, out var scope) == true
? scope
- : CallingMethodScope;
+ : SafeContext.CallingMethod;
}
#if DEBUG
@@ -343,10 +343,10 @@ private void AssertVisited(BoundExpression expr)
this.Visit(node.DeclarationsOpt);
this.Visit(node.ExpressionOpt);
- var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
+ var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance();
if (node.AwaitOpt is { } awaitableInfo)
{
- uint valEscapeScope = node.ExpressionOpt is { } expr
+ SafeContext valEscapeScope = node.ExpressionOpt is { } expr
? GetValEscape(expr, _localScopeDepth)
: _localScopeDepth;
GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, valEscapeScope);
@@ -360,7 +360,7 @@ private void AssertVisited(BoundExpression expr)
public override BoundNode? VisitUsingLocalDeclarations(BoundUsingLocalDeclarations node)
{
- var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
+ var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance();
if (node.AwaitOpt is { } awaitableInfo)
{
GetAwaitableInstancePlaceholders(placeholders, awaitableInfo, _localScopeDepth);
@@ -432,7 +432,7 @@ private void AssertVisited(BoundExpression expr)
return base.VisitLocal(node);
}
- private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope)
+ private void AddLocalScopes(LocalSymbol local, SafeContext refEscapeScope, SafeContext valEscapeScope)
{
// From https://github.com/dotnet/csharplang/blob/main/csharp-11.0/proposals/low-level-struct-improvements.md:
//
@@ -448,10 +448,10 @@ private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEsca
{
refEscapeScope = scopedModifier == ScopedKind.ScopedRef ?
_localScopeDepth :
- CurrentMethodScope;
+ SafeContext.CurrentMethod;
valEscapeScope = scopedModifier == ScopedKind.ScopedValue ?
_localScopeDepth :
- CallingMethodScope;
+ SafeContext.CallingMethod;
}
Debug.Assert(_localEscapeScopes?.ContainsKey(local) != true);
@@ -459,9 +459,9 @@ private void AddLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEsca
AddOrSetLocalScopes(local, refEscapeScope, valEscapeScope);
}
- private void AddOrSetLocalScopes(LocalSymbol local, uint refEscapeScope, uint valEscapeScope)
+ private void AddOrSetLocalScopes(LocalSymbol local, SafeContext refEscapeScope, SafeContext valEscapeScope)
{
- _localEscapeScopes ??= new Dictionary();
+ _localEscapeScopes ??= new Dictionary();
_localEscapeScopes[local] = (refEscapeScope, valEscapeScope);
}
@@ -482,15 +482,15 @@ private void RemoveLocalScopes(LocalSymbol local)
if (node.InitializerOpt is { } initializer)
{
var localSymbol = (SourceLocalSymbol)node.LocalSymbol;
- (uint refEscapeScope, uint valEscapeScope) = GetLocalScopes(localSymbol);
+ (SafeContext refEscapeScope, SafeContext valEscapeScope) = GetLocalScopes(localSymbol);
if (_useUpdatedEscapeRules && localSymbol.Scope != ScopedKind.None)
{
- // If the local has a scoped modifier, then the lifetime is not inferred from
+ // If the local has a scoped modifier, then the SafeContext is not inferred from
// the initializer. Validate the escape values for the initializer instead.
Debug.Assert(localSymbol.RefKind == RefKind.None ||
- refEscapeScope >= GetRefEscape(initializer, _localScopeDepth));
+ GetRefEscape(initializer, _localScopeDepth).IsConvertibleTo(refEscapeScope));
if (node.DeclaredTypeOpt?.Type.IsRefLikeOrAllowsRefLikeType() == true)
{
@@ -520,7 +520,7 @@ private void RemoveLocalScopes(LocalSymbol local)
base.VisitReturnStatement(node);
if (node.ExpressionOpt is { Type: { } } expr)
{
- ValidateEscape(expr, ReturnOnlyScope, node.RefKind != RefKind.None, _diagnostics);
+ ValidateEscape(expr, SafeContext.ReturnOnly, node.RefKind != RefKind.None, _diagnostics);
}
return null;
}
@@ -530,7 +530,7 @@ private void RemoveLocalScopes(LocalSymbol local)
base.VisitYieldReturnStatement(node);
if (node.Expression is { Type: { } } expr)
{
- ValidateEscape(expr, ReturnOnlyScope, isByRef: false, _diagnostics);
+ ValidateEscape(expr, SafeContext.ReturnOnly, isByRef: false, _diagnostics);
}
return null;
}
@@ -567,12 +567,12 @@ private void RemoveLocalScopes(LocalSymbol local)
using var _ = new PatternInput(this, getDeclarationValEscape(node.DeclaredType, _patternInputValEscape));
return base.VisitDeclarationPattern(node);
- static uint getDeclarationValEscape(BoundTypeExpression typeExpression, uint valEscape)
+ static SafeContext getDeclarationValEscape(BoundTypeExpression typeExpression, SafeContext valEscape)
{
// https://github.com/dotnet/roslyn/issues/73551:
// We do not have a test that demonstrates the statement below makes a difference
- // for ref like types. If 'CallingMethodScope' is always returned, not a single test fails.
- return typeExpression.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : CallingMethodScope;
+ // for ref like types. If 'SafeContext.CallingMethod' is always returned, not a single test fails.
+ return typeExpression.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : SafeContext.CallingMethod;
}
}
@@ -593,11 +593,11 @@ static uint getDeclarationValEscape(BoundTypeExpression typeExpression, uint val
using var _ = new PatternInput(this, getPositionalValEscape(node.Symbol, _patternInputValEscape));
return base.VisitPositionalSubpattern(node);
- static uint getPositionalValEscape(Symbol? symbol, uint valEscape)
+ static SafeContext getPositionalValEscape(Symbol? symbol, SafeContext valEscape)
{
return symbol is null
? valEscape
- : symbol.GetTypeOrReturnType().IsRefLikeOrAllowsRefLikeType() ? valEscape : CallingMethodScope;
+ : symbol.GetTypeOrReturnType().IsRefLikeOrAllowsRefLikeType() ? valEscape : SafeContext.CallingMethod;
}
}
@@ -606,11 +606,11 @@ static uint getPositionalValEscape(Symbol? symbol, uint valEscape)
using var _ = new PatternInput(this, getMemberValEscape(node.Member, _patternInputValEscape));
return base.VisitPropertySubpattern(node);
- static uint getMemberValEscape(BoundPropertySubpatternMember? member, uint valEscape)
+ static SafeContext getMemberValEscape(BoundPropertySubpatternMember? member, SafeContext valEscape)
{
if (member is null) return valEscape;
valEscape = getMemberValEscape(member.Receiver, valEscape);
- return member.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : CallingMethodScope;
+ return member.Type.IsRefLikeOrAllowsRefLikeType() ? valEscape : SafeContext.CallingMethod;
}
}
@@ -640,7 +640,7 @@ private void VisitArgumentsAndGetArgumentPlaceholders(BoundExpression? receiverO
if (arg is BoundConversion { ConversionKind: ConversionKind.InterpolatedStringHandler, Operand: BoundInterpolatedString or BoundBinaryOperator } conversion)
{
var interpolationData = conversion.Operand.GetInterpolatedStringHandlerData();
- var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
+ var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance();
GetInterpolatedStringPlaceholders(placeholders, interpolationData, receiverOpt, i, arguments);
_ = new PlaceholderRegion(this, placeholders);
}
@@ -671,7 +671,7 @@ protected override void VisitArguments(BoundCall node)
}
private void GetInterpolatedStringPlaceholders(
- ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders,
+ ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders,
in InterpolatedStringHandlerData interpolationData,
BoundExpression? receiver,
int nArgumentsVisited,
@@ -682,7 +682,7 @@ private void GetInterpolatedStringPlaceholders(
foreach (var placeholder in interpolationData.ArgumentPlaceholders)
{
- uint valEscapeScope;
+ SafeContext valEscapeScope;
int argIndex = placeholder.ArgumentIndex;
switch (argIndex)
{
@@ -690,7 +690,7 @@ private void GetInterpolatedStringPlaceholders(
Debug.Assert(receiver != null);
if (receiver is null)
{
- valEscapeScope = CallingMethodScope;
+ valEscapeScope = SafeContext.CallingMethod;
}
else
{
@@ -712,7 +712,7 @@ private void GetInterpolatedStringPlaceholders(
else
{
// Error condition, see ERR_InterpolatedStringHandlerArgumentLocatedAfterInterpolatedString.
- valEscapeScope = CallingMethodScope; // Consider skipping this placeholder entirely since CallingMethodScope is the fallback in GetPlaceholderScope().
+ valEscapeScope = SafeContext.CallingMethod; // Consider skipping this placeholder entirely since SafeContext.CallingMethod is the fallback in GetPlaceholderScope().
}
break;
default:
@@ -812,7 +812,7 @@ private void VisitObjectCreationExpressionBase(BoundObjectCreationExpressionBase
continue;
}
- if (escapeFrom > GetValEscape(argument, _localScopeDepth))
+ if (!escapeFrom.IsConvertibleTo(GetValEscape(argument, _localScopeDepth)))
{
Error(_diagnostics, ErrorCode.ERR_CallArgMixing, argument.Syntax, constructor, parameter.Name);
}
@@ -881,14 +881,14 @@ private void VisitObjectCreationExpressionBase(BoundObjectCreationExpressionBase
public override BoundNode? VisitAwaitExpression(BoundAwaitExpression node)
{
this.Visit(node.Expression);
- var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
+ var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance();
GetAwaitableInstancePlaceholders(placeholders, node.AwaitableInfo, GetValEscape(node.Expression, _localScopeDepth));
using var _ = new PlaceholderRegion(this, placeholders);
this.Visit(node.AwaitableInfo);
return null;
}
- private void GetAwaitableInstancePlaceholders(ArrayBuilder<(BoundValuePlaceholderBase, uint)> placeholders, BoundAwaitableInfo awaitableInfo, uint valEscapeScope)
+ private void GetAwaitableInstancePlaceholders(ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders, BoundAwaitableInfo awaitableInfo, SafeContext valEscapeScope)
{
if (awaitableInfo.AwaitableInstancePlaceholder is { } placeholder)
{
@@ -942,7 +942,7 @@ private void VisitDeconstructionArguments(ArrayBuilder v
return;
}
- var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
+ var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance();
placeholders.Add((conversion.DeconstructionInfo.InputPlaceholder, GetValEscape(right, _localScopeDepth)));
var parameters = deconstructMethod.Parameters;
@@ -955,7 +955,7 @@ private void VisitDeconstructionArguments(ArrayBuilder v
var variable = variables[i];
var nestedVariables = variable.NestedVariables;
var arg = (BoundDeconstructValuePlaceholder)invocation.Arguments[i + offset];
- uint valEscape = nestedVariables is null
+ SafeContext valEscape = nestedVariables is null
? GetValEscape(variable.Expression, _localScopeDepth)
: _localScopeDepth;
placeholders.Add((arg, valEscape));
@@ -991,10 +991,10 @@ private void VisitDeconstructionArguments(ArrayBuilder v
private readonly struct DeconstructionVariable
{
internal readonly BoundExpression Expression;
- internal readonly uint ValEscape;
+ internal readonly SafeContext ValEscape;
internal readonly ArrayBuilder? NestedVariables;
- internal DeconstructionVariable(BoundExpression expression, uint valEscape, ArrayBuilder? nestedVariables)
+ internal DeconstructionVariable(BoundExpression expression, SafeContext valEscape, ArrayBuilder? nestedVariables)
{
Expression = expression;
ValEscape = valEscape;
@@ -1015,7 +1015,7 @@ private ArrayBuilder GetDeconstructionAssignmentVariable
DeconstructionVariable getDeconstructionAssignmentVariable(BoundExpression expr)
{
return expr is BoundTupleExpression tuple
- ? new DeconstructionVariable(expr, valEscape: uint.MaxValue, GetDeconstructionAssignmentVariables(tuple))
+ ? new DeconstructionVariable(expr, valEscape: SafeContext.Empty, GetDeconstructionAssignmentVariables(tuple))
: new DeconstructionVariable(expr, GetValEscape(expr, _localScopeDepth), null);
}
}
@@ -1042,7 +1042,7 @@ private static ImmutableArray GetDeconstructionRightParts(Bound
public override BoundNode? VisitForEachStatement(BoundForEachStatement node)
{
this.Visit(node.Expression);
- uint collectionEscape;
+ SafeContext collectionEscape;
if (node.EnumeratorInfoOpt is { InlineArraySpanType: not WellKnownType.Unknown and var spanType, InlineArrayUsedAsValue: false })
{
@@ -1078,7 +1078,7 @@ private static ImmutableArray GetDeconstructionRightParts(Bound
AddLocalScopes(local, refEscapeScope: local.RefKind == RefKind.None ? _localScopeDepth : collectionEscape, valEscapeScope: collectionEscape);
}
- var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
+ var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)>.GetInstance();
if (node.DeconstructionOpt?.TargetPlaceholder is { } targetPlaceholder)
{
placeholders.Add((targetPlaceholder, collectionEscape));
diff --git a/src/Compilers/CSharp/Portable/Binder/SafeContext.cs b/src/Compilers/CSharp/Portable/Binder/SafeContext.cs
new file mode 100644
index 0000000000000..cb8c426c07a03
--- /dev/null
+++ b/src/Compilers/CSharp/Portable/Binder/SafeContext.cs
@@ -0,0 +1,132 @@
+// 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.Diagnostics;
+
+namespace Microsoft.CodeAnalysis.CSharp
+{
+ ///
+ /// A representation of the program region in which the *referent* of a `ref` is *live*.
+ /// Limited to what is expressible in C#.
+ /// See also:
+ /// - https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#detailed-design
+ /// - https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/variables.md#972-ref-safe-contexts
+ /// - https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/structs.md#16412-safe-context-constraint
+ ///
+ ///
+ /// - A *referent* is the variable being referenced by a `ref`.
+ /// - Informally, a variable is *live* if it has storage allocated for it (either on heap or stack).
+ /// - In this design, all SafeContexts have a known relationship to all other SafeContexts.
+ ///
+ internal readonly struct SafeContext
+ {
+ private const uint CallingMethodRaw = 0;
+ private const uint ReturnOnlyRaw = 1;
+ private const uint CurrentMethodRaw = 2;
+
+ ///
+ /// For the purpose of escape verification we operate with the depth of local scopes.
+ /// The depth is a uint, with smaller number representing shallower/wider scopes.
+ /// Since sibling scopes do not intersect and a value cannot escape from one to another without
+ /// escaping to a wider scope, we can use simple depth numbering without ambiguity.
+ ///
+ private readonly uint _value;
+ private SafeContext(uint value) => _value = value;
+
+ ///
+ /// The "calling method" scope that is outside of the containing method/lambda.
+ /// If something can escape to this scope, it can escape to any scope in a given method through a ref parameter or return.
+ ///
+ public static readonly SafeContext CallingMethod = new SafeContext(CallingMethodRaw);
+
+ ///
+ /// The "return-only" scope that is outside of the containing method/lambda.
+ /// If something can escape to this scope, it can escape to any scope in a given method or can be returned, but it can't escape through a ref parameter.
+ ///
+ public static readonly SafeContext ReturnOnly = new SafeContext(ReturnOnlyRaw);
+
+ ///
+ /// The "current method" scope that is just inside the containing method/lambda.
+ /// If something can escape to this scope, it can escape to any scope in a given method, but cannot be returned.
+ ///
+ public static readonly SafeContext CurrentMethod = new SafeContext(CurrentMethodRaw);
+
+ ///
+ /// Gets a SafeContext which is "empty". i.e. which refers to a variable whose storage is never allocated.
+ ///
+ public static readonly SafeContext Empty = new SafeContext(uint.MaxValue);
+
+ ///
+ /// Gets a SafeContext which is narrower than the given SafeContext.
+ /// Used to "enter" a nested local scope.
+ ///
+ public SafeContext Narrower()
+ {
+ Debug.Assert(_value >= ReturnOnlyRaw);
+ return new SafeContext(_value + 1);
+ }
+
+ ///
+ /// Gets a SafeContext which is wider than the given SafeContext.
+ /// Used to "exit" a nested local scope.
+ ///
+ public SafeContext Wider()
+ {
+ Debug.Assert(_value >= CurrentMethodRaw);
+ return new SafeContext(_value - 1);
+ }
+
+ public bool IsCallingMethod => _value == CallingMethodRaw;
+ public bool IsReturnOnly => _value == ReturnOnlyRaw;
+ public bool IsReturnable => _value is CallingMethodRaw or ReturnOnlyRaw;
+
+ /// Returns true if a 'ref' with this SafeContext can be converted to the 'other' SafeContext. Otherwise, returns false.
+ /// Generally, a wider SafeContext is convertible to a narrower SafeContext.
+ public bool IsConvertibleTo(SafeContext other)
+ => this._value <= other._value;
+
+ ///
+ /// Returns the narrower of two SafeContexts.
+ ///
+ ///
+ /// In other words, this method returns the widest SafeContext which 'this' and 'other' are both convertible to.
+ /// If in future we added the concept of unrelated SafeContexts (e.g. to implement 'ref scoped'), this method would perhaps return a Nullable,
+ /// for the case that no SafeContext exists which both input SafeContexts are convertible to.
+ ///
+ public SafeContext Intersect(SafeContext other)
+ => this.IsConvertibleTo(other) ? other : this;
+
+ ///
+ /// Returns the wider of two SafeContexts.
+ ///
+ /// In other words, this method returns the narrowest SafeContext which can be converted to both 'this' and 'other'.
+ public SafeContext Union(SafeContext other)
+ => this.IsConvertibleTo(other) ? this : other;
+
+ /// Returns true if this SafeContext is the same as 'other' (i.e. for invariant nested conversion).
+ public bool Equals(SafeContext other)
+ => this._value == other._value;
+
+ public override bool Equals(object? obj)
+ => obj is SafeContext other && this.Equals(other);
+
+ public override int GetHashCode()
+ => unchecked((int)_value);
+
+ public static bool operator ==(SafeContext lhs, SafeContext rhs)
+ => lhs._value == rhs._value;
+
+ public static bool operator !=(SafeContext lhs, SafeContext rhs)
+ => lhs._value != rhs._value;
+
+ public override string ToString()
+ => _value switch
+ {
+ CallingMethodRaw => "SafeContext",
+ ReturnOnlyRaw => "SafeContext",
+ CurrentMethodRaw => "SafeContext",
+ _ => $"SafeContext<{_value}>"
+ };
+ }
+}
diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
index 28e5f8a9cc8c0..b68f25b9d7efe 100644
--- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
+++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
@@ -39,6 +39,7 @@
+
@@ -108,7 +109,7 @@
-->
-
+