// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class Binder
{
///
/// 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.
/// The 0 and 1 are special scopes -
/// 0 is the "external" or "return" 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 or can be returned.
/// 1 is the "parameter" or "top" 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.
///
internal const uint ExternalScope = 0;
internal const uint TopLevelScope = 1;
// Some value kinds are semantically the same and the only distinction is how errors are reported
// for those purposes we reserve lowest 2 bits
private const int ValueKindInsignificantBits = 2;
private const BindValueKind ValueKindSignificantBitsMask = unchecked((BindValueKind)~((1 << ValueKindInsignificantBits) - 1));
///
/// Expression capabilities and requirements.
///
[Flags]
internal enum BindValueKind : byte
{
///////////////////
// All expressions can be classified according to the following 4 capabilities:
//
///
/// Expression can be an RHS of an assignment operation.
///
///
/// The following are rvalues: values, variables, null literals, properties
/// and indexers with getters, events.
///
/// The following are not rvalues:
/// namespaces, types, method groups, anonymous functions.
///
RValue = 1 << ValueKindInsignificantBits,
///
/// Expression can be the LHS of a simple assignment operation.
/// Example:
/// property with a setter
///
Assignable = 2 << ValueKindInsignificantBits,
///
/// Expression represents a location. Often referred as a "variable"
/// Examples:
/// local variable, parameter, field
///
RefersToLocation = 4 << ValueKindInsignificantBits,
///////////////////
// The rest are just combinations of the above.
//
///
/// Expression is the RHS of an assignment operation
/// and may be a method group.
/// Basically an RValue, but could be treated differently for the purpose of error reporting
///
RValueOrMethodGroup = RValue + 1,
///
/// Expression can be an LHS of a compound assignment
/// operation (such as +=).
///
CompoundAssignment = RValue | Assignable,
///
/// Expression can be the operand of an increment or decrement operation.
/// Same as CompoundAssignment, the distinction is really just for error reporting.
///
IncrementDecrement = CompoundAssignment + 1,
///
/// Expression is a r/o reference.
///
ReadonlyRef = RefersToLocation | RValue,
///
/// Expression can be the operand of an address-of operation (&).
/// Same as ReadonlyRef. The difference is just for error reporting.
///
AddressOf = ReadonlyRef + 1,
///
/// Expression is the receiver of a fixed buffer field access
/// Same as ReadonlyRef. The difference is just for error reporting.
///
FixedReceiver = ReadonlyRef + 2,
///
/// Expression is passed as a ref or out parameter or assigned to a byref variable.
///
RefOrOut = RefersToLocation | RValue | Assignable,
///
/// Expression is returned by an ordinary r/w reference.
/// Same as RefOrOut. The difference is just for error reporting.
///
RefReturn = RefOrOut + 1,
}
private static bool RequiresRValueOnly(BindValueKind kind)
{
return (kind & ValueKindSignificantBitsMask) == BindValueKind.RValue;
}
private static bool RequiresAssignmentOnly(BindValueKind kind)
{
return (kind & ValueKindSignificantBitsMask) == BindValueKind.Assignable;
}
private static bool RequiresVariable(BindValueKind kind)
{
return !RequiresRValueOnly(kind);
}
private static bool RequiresReferenceToLocation(BindValueKind kind)
{
return (kind & BindValueKind.RefersToLocation) != 0;
}
private static bool RequiresAssignableVariable(BindValueKind kind)
{
return (kind & BindValueKind.Assignable) != 0;
}
private static bool RequiresRefOrOut(BindValueKind kind)
{
return (kind & BindValueKind.RefOrOut) == BindValueKind.RefOrOut;
}
///
/// Check the expression is of the required lvalue and rvalue specified by valueKind.
/// The method returns the original expression if the expression is of the required
/// type. Otherwise, an appropriate error is added to the diagnostics bag and the
/// method returns a BoundBadExpression node. The method returns the original
/// expression without generating any error if the expression has errors.
///
private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind, DiagnosticBag diagnostics)
{
switch (expr.Kind)
{
case BoundKind.PropertyGroup:
expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics);
break;
case BoundKind.Local:
Debug.Assert(expr.Syntax.Kind() != SyntaxKind.Argument || valueKind == BindValueKind.RefOrOut);
break;
case BoundKind.OutVariablePendingInference:
case BoundKind.OutDeconstructVarPendingInference:
Debug.Assert(valueKind == BindValueKind.RefOrOut);
return expr;
case BoundKind.DiscardExpression:
Debug.Assert(valueKind == BindValueKind.Assignable || valueKind == BindValueKind.RefOrOut || diagnostics.HasAnyResolvedErrors());
return expr;
case BoundKind.IndexerAccess:
{
// Assigning to an non ref return indexer needs to set 'useSetterForDefaultArgumentGeneration' to true.
// This is for IOperation purpose.
var indexerAccess = (BoundIndexerAccess)expr;
if (valueKind == BindValueKind.Assignable && !indexerAccess.Indexer.ReturnsByRef)
{
expr = indexerAccess.Update(indexerAccess.ReceiverOpt,
indexerAccess.Indexer,
indexerAccess.Arguments,
indexerAccess.ArgumentNamesOpt,
indexerAccess.ArgumentRefKindsOpt,
indexerAccess.Expanded,
indexerAccess.ArgsToParamsOpt,
indexerAccess.BinderOpt,
useSetterForDefaultArgumentGeneration: true,
type: indexerAccess.Type);
}
}
break;
}
bool hasResolutionErrors = false;
// If this a MethodGroup where an rvalue is not expected or where the caller will not explicitly handle
// (and resolve) MethodGroups (in short, cases where valueKind != BindValueKind.RValueOrMethodGroup),
// resolve the MethodGroup here to generate the appropriate errors, otherwise resolution errors (such as
// "member is inaccessible") will be dropped.
if (expr.Kind == BoundKind.MethodGroup && valueKind != BindValueKind.RValueOrMethodGroup)
{
var methodGroup = (BoundMethodGroup)expr;
HashSet useSiteDiagnostics = null;
var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, isMethodGroupConversion: false, useSiteDiagnostics: ref useSiteDiagnostics);
diagnostics.Add(expr.Syntax, useSiteDiagnostics);
Symbol otherSymbol = null;
bool resolvedToMethodGroup = resolution.MethodGroup != null;
if (!expr.HasAnyErrors) diagnostics.AddRange(resolution.Diagnostics); // Suppress cascading.
hasResolutionErrors = resolution.HasAnyErrors;
if (hasResolutionErrors)
{
otherSymbol = resolution.OtherSymbol;
}
resolution.Free();
// It's possible the method group is not a method group at all, but simply a
// delayed lookup that resolved to a non-method member (perhaps an inaccessible
// field or property), or nothing at all. In those cases, the member should not be exposed as a
// method group, not even within a BoundBadExpression. Instead, the
// BoundBadExpression simply refers to the receiver and the resolved symbol (if any).
if (!resolvedToMethodGroup)
{
Debug.Assert(methodGroup.ResultKind != LookupResultKind.Viable);
var receiver = methodGroup.ReceiverOpt;
if ((object)otherSymbol != null && receiver?.Kind == BoundKind.TypeOrValueExpression)
{
// Since we're not accessing a method, this can't be a Color Color case, so TypeOrValueExpression should not have been used.
// CAVEAT: otherSymbol could be invalid in some way (e.g. inaccessible), in which case we would have fallen back on a
// method group lookup (to allow for extension methods), which would have required a TypeOrValueExpression.
Debug.Assert(methodGroup.LookupError != null);
// Since we have a concrete member in hand, we can resolve the receiver.
var typeOrValue = (BoundTypeOrValueExpression)receiver;
receiver = otherSymbol.IsStatic
? null // no receiver required
: typeOrValue.Data.ValueExpression;
}
return new BoundBadExpression(
expr.Syntax,
methodGroup.ResultKind,
(object)otherSymbol == null ? ImmutableArray.Empty : ImmutableArray.Create(otherSymbol),
receiver == null ? ImmutableArray.Empty : ImmutableArray.Create(receiver),
GetNonMethodMemberType(otherSymbol));
}
}
if (!hasResolutionErrors && CheckValueKind(expr.Syntax, expr, valueKind, checkingReceiver: false, diagnostics: diagnostics) ||
expr.HasAnyErrors && valueKind == BindValueKind.RValueOrMethodGroup)
{
return expr;
}
var resultKind = (valueKind == BindValueKind.RValue || valueKind == BindValueKind.RValueOrMethodGroup) ?
LookupResultKind.NotAValue :
LookupResultKind.NotAVariable;
return ToBadExpression(expr, resultKind);
}
///
/// The purpose of this method is to determine if the expression satisfies desired capabilities.
/// If it is not then this code gives an appropriate error message.
///
/// To determine the appropriate error message we need to know two things:
///
/// (1) What capabilities we need - increment it, assign, return as a readonly reference, . . . ?
///
/// (2) Are we trying to determine if the left hand side of a dot is a variable in order
/// to determine if the field or property on the right hand side of a dot is assignable?
///
/// (3) The syntax of the expression that started the analysis. (for error reporting purposes).
///
internal bool CheckValueKind(SyntaxNode node, BoundExpression expr, BindValueKind valueKind, bool checkingReceiver, DiagnosticBag diagnostics)
{
Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter());
if (expr.HasAnyErrors)
{
return false;
}
switch (expr.Kind)
{
// we need to handle properties and event in a special way even in an RValue case because of getters
case BoundKind.PropertyAccess:
case BoundKind.IndexerAccess:
return CheckPropertyValueKind(node, expr, valueKind, checkingReceiver, diagnostics);
case BoundKind.EventAccess:
return CheckEventValueKind((BoundEventAccess)expr, valueKind, diagnostics);
}
// easy out for a very common RValue case.
if (RequiresRValueOnly(valueKind))
{
return CheckNotNamespaceOrType(expr, diagnostics);
}
// constants/literals are strictly RValues
// void is not even an RValue
if ((expr.ConstantValue != null) || (expr.Type.GetSpecialTypeSafe() == SpecialType.System_Void))
{
Error(diagnostics, GetStandardLvalueError(valueKind), node);
return false;
}
switch (expr.Kind)
{
case BoundKind.NamespaceExpression:
var ns = (BoundNamespaceExpression)expr;
Error(diagnostics, ErrorCode.ERR_BadSKknown, node, ns.NamespaceSymbol, MessageID.IDS_SK_NAMESPACE.Localize(), MessageID.IDS_SK_VARIABLE.Localize());
return false;
case BoundKind.TypeExpression:
var type = (BoundTypeExpression)expr;
Error(diagnostics, ErrorCode.ERR_BadSKknown, node, type.Type, MessageID.IDS_SK_TYPE.Localize(), MessageID.IDS_SK_VARIABLE.Localize());
return false;
case BoundKind.Lambda:
case BoundKind.UnboundLambda:
// lambdas can only be used as RValues
Error(diagnostics, GetStandardLvalueError(valueKind), node);
return false;
case BoundKind.MethodGroup:
// method groups can only be used as RValues
var methodGroup = (BoundMethodGroup)expr;
Error(diagnostics, GetMethodGroupLvalueError(valueKind), node, methodGroup.Name, MessageID.IDS_MethodGroup.Localize());
return false;
case BoundKind.RangeVariable:
// range variables can only be used as RValues
var queryref = (BoundRangeVariable)expr;
Error(diagnostics, GetRangeLvalueError(valueKind), node, queryref.RangeVariableSymbol.Name);
return false;
case BoundKind.Conversion:
var conversion = (BoundConversion)expr;
// conversions are strict RValues, but unboxing has a specific error
if (conversion.ConversionKind == ConversionKind.Unboxing)
{
Error(diagnostics, ErrorCode.ERR_UnboxNotLValue, node);
return false;
}
break;
case BoundKind.ArrayAccess:
case BoundKind.PointerIndirectionOperator:
case BoundKind.PointerElementAccess:
// array elements and pointer dereferencing are readwrite variables
return true;
case BoundKind.RefValueOperator:
// The undocumented __refvalue(tr, T) expression results in a variable of type T.
// it is a readwrite variable.
return true;
case BoundKind.DynamicMemberAccess:
case BoundKind.DynamicIndexerAccess:
// dynamic expressions can be read and written to
// can even be passed by reference (which is implemented via a temp)
return true;
case BoundKind.Parameter:
var parameter = (BoundParameter)expr;
return CheckParameterValueKind(node, parameter, valueKind, checkingReceiver, diagnostics);
case BoundKind.Local:
var local = (BoundLocal)expr;
return CheckLocalValueKind(node, local, valueKind, checkingReceiver, diagnostics);
case BoundKind.ThisReference:
var thisref = (BoundThisReference)expr;
// We will already have given an error for "this" used outside of a constructor,
// instance method, or instance accessor. Assume that "this" is a variable if it is in a struct.
// SPEC: when this is used in a primary-expression within an instance constructor of a struct,
// SPEC: it is classified as a variable.
// SPEC: When this is used in a primary-expression within an instance method or instance accessor
// SPEC: of a struct, it is classified as a variable.
// Note: RValueOnly is checked at the beginning of this method. Since we are here we need more than readable.
//"this" is readonly in members of readonly structs, unless we are in a constructor.
if (!thisref.Type.IsValueType ||
(RequiresAssignableVariable(valueKind) &&
thisref.Type.IsReadOnly &&
(this.ContainingMemberOrLambda as MethodSymbol)?.MethodKind != MethodKind.Constructor))
{
// CONSIDER: the Dev10 name has angle brackets (i.e. "")
Error(diagnostics, GetThisLvalueError(valueKind), node, ThisParameterSymbol.SymbolName);
return false;
}
return true;
case BoundKind.Call:
var call = (BoundCall)expr;
return CheckCallValueKind(call, node, valueKind, checkingReceiver, diagnostics);
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
// byref conditional defers to its operands
if (conditional.IsByRef &&
(CheckValueKind(conditional.Consequence.Syntax, conditional.Consequence, valueKind, checkingReceiver: false, diagnostics: diagnostics) &
CheckValueKind(conditional.Alternative.Syntax, conditional.Alternative, valueKind, checkingReceiver: false, diagnostics: diagnostics)))
{
return true;
}
// report standard lvalue error
break;
case BoundKind.FieldAccess:
var fieldAccess = (BoundFieldAccess)expr;
return CheckFieldValueKind(node, fieldAccess, valueKind, checkingReceiver, diagnostics);
}
// At this point we should have covered all the possible cases for anything that is not a strict RValue.
Error(diagnostics, GetStandardLvalueError(valueKind), node);
return false;
}
private static bool CheckNotNamespaceOrType(BoundExpression expr, DiagnosticBag diagnostics)
{
switch (expr.Kind)
{
case BoundKind.NamespaceExpression:
Error(diagnostics, ErrorCode.ERR_BadSKknown, expr.Syntax, ((BoundNamespaceExpression)expr).NamespaceSymbol, MessageID.IDS_SK_NAMESPACE.Localize(), MessageID.IDS_SK_VARIABLE.Localize());
return false;
case BoundKind.TypeExpression:
Error(diagnostics, ErrorCode.ERR_BadSKunknown, expr.Syntax, expr.Type, MessageID.IDS_SK_TYPE.Localize());
return false;
default:
return true;
}
}
private bool CheckLocalValueKind(SyntaxNode node, BoundLocal local, BindValueKind valueKind, bool checkingReceiver, DiagnosticBag diagnostics)
{
// Local constants are never variables. Local variables are sometimes
// not to be treated as variables, if they are fixed, declared in a using,
// or declared in a foreach.
LocalSymbol localSymbol = local.LocalSymbol;
if (RequiresAssignableVariable(valueKind))
{
if (this.LockedOrDisposedVariables.Contains(localSymbol))
{
diagnostics.Add(ErrorCode.WRN_AssignmentToLockOrDispose, local.Syntax.Location, localSymbol);
}
if (!localSymbol.IsWritable)
{
ReportReadonlyLocalError(node, localSymbol, valueKind, checkingReceiver, diagnostics);
return false;
}
}
return true;
}
private static bool CheckLocalRefEscape(SyntaxNode node, BoundLocal local, uint escapeTo, bool checkingReceiver, DiagnosticBag 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 (localSymbol.RefEscapeScope <= escapeTo)
{
return true;
}
if (escapeTo == Binder.ExternalScope)
{
if (localSymbol.RefKind == RefKind.None)
{
if (checkingReceiver)
{
Error(diagnostics, ErrorCode.ERR_RefReturnLocal2, local.Syntax, localSymbol);
}
else
{
Error(diagnostics, ErrorCode.ERR_RefReturnLocal, node, localSymbol);
}
return false;
}
if (checkingReceiver)
{
Error(diagnostics, ErrorCode.ERR_RefReturnNonreturnableLocal2, local.Syntax, localSymbol);
}
else
{
Error(diagnostics, ErrorCode.ERR_RefReturnNonreturnableLocal, node, localSymbol);
}
return false;
}
Error(diagnostics, ErrorCode.ERR_EscapeLocal, node, localSymbol);
return false;
}
private bool CheckParameterValueKind(SyntaxNode node, BoundParameter parameter, BindValueKind valueKind, bool checkingReceiver, DiagnosticBag diagnostics)
{
ParameterSymbol parameterSymbol = parameter.ParameterSymbol;
// all parameters can be passed by ref/out or assigned to
// except "in" parameters, which are readonly
if (parameterSymbol.RefKind == RefKind.In && RequiresAssignableVariable(valueKind))
{
ReportReadOnlyError(parameterSymbol, node, valueKind, checkingReceiver, diagnostics);
return false;
}
if (this.LockedOrDisposedVariables.Contains(parameterSymbol))
{
// Consider: It would be more conventional to pass "symbol" rather than "symbol.Name".
// The issue is that the error SymbolDisplayFormat doesn't display parameter
// names - only their types - which works great in signatures, but not at all
// at the top level.
diagnostics.Add(ErrorCode.WRN_AssignmentToLockOrDispose, parameter.Syntax.Location, parameterSymbol.Name);
}
return true;
}
private static bool CheckParameterRefEscape(SyntaxNode node, BoundParameter parameter, uint escapeTo, bool checkingReceiver, DiagnosticBag diagnostics)
{
ParameterSymbol parameterSymbol = parameter.ParameterSymbol;
// byval parameters can escape to method's top level.
// others can be escape further, unless they are ref-like.
if (escapeTo == Binder.ExternalScope && parameterSymbol.RefKind == RefKind.None)
{
if (checkingReceiver)
{
Error(diagnostics, ErrorCode.ERR_RefReturnParameter2, parameter.Syntax, parameterSymbol.Name);
}
else
{
Error(diagnostics, ErrorCode.ERR_RefReturnParameter, node, parameterSymbol.Name);
}
return false;
}
// can ref-escape to any scope otherwise
return true;
}
private bool CheckFieldValueKind(SyntaxNode node, BoundFieldAccess fieldAccess, BindValueKind valueKind, bool checkingReceiver, DiagnosticBag diagnostics)
{
var fieldSymbol = fieldAccess.FieldSymbol;
var fieldIsStatic = fieldSymbol.IsStatic;
if (RequiresAssignableVariable(valueKind))
{
// A field is writeable unless
// (1) it is readonly and we are not in a constructor or field initializer
// (2) the receiver of the field is of value type and is not a variable or object creation expression.
// For example, if you have a class C with readonly field f of type S, and
// S has a mutable field x, then c.f.x is not a variable because c.f is not
// writable.
if (fieldSymbol.IsReadOnly)
{
var canModifyReadonly = false;
Symbol containing = this.ContainingMemberOrLambda;
if ((object)containing != null &&
fieldIsStatic == containing.IsStatic &&
(fieldIsStatic || fieldAccess.ReceiverOpt.Kind == BoundKind.ThisReference) &&
(Compilation.FeatureStrictEnabled
? fieldSymbol.ContainingType == containing.ContainingType
// We duplicate a bug in the native compiler for compatibility in non-strict mode
: fieldSymbol.ContainingType.OriginalDefinition == containing.ContainingType.OriginalDefinition))
{
if (containing.Kind == SymbolKind.Method)
{
MethodSymbol containingMethod = (MethodSymbol)containing;
MethodKind desiredMethodKind = fieldIsStatic ? MethodKind.StaticConstructor : MethodKind.Constructor;
canModifyReadonly = containingMethod.MethodKind == desiredMethodKind;
}
else if (containing.Kind == SymbolKind.Field)
{
canModifyReadonly = true;
}
}
if (!canModifyReadonly)
{
ReportReadOnlyFieldError(fieldSymbol, node, valueKind, checkingReceiver, diagnostics);
return false;
}
}
if (fieldSymbol.IsFixed)
{
Error(diagnostics, GetStandardLvalueError(valueKind), node);
return false;
}
}
// r/w fields that are static or belong to reference types are writeable and returnable
if (fieldIsStatic || fieldSymbol.ContainingType.IsReferenceType)
{
return true;
}
// for other fields defer to the receiver.
return CheckIsValidReceiverForVariable(node, fieldAccess.ReceiverOpt, valueKind, diagnostics);
}
private static bool CheckFieldRefEscape(SyntaxNode node, BoundFieldAccess fieldAccess, uint escapeFrom, uint escapeTo, bool checkingReceiver, DiagnosticBag diagnostics)
{
var fieldSymbol = fieldAccess.FieldSymbol;
// fields that are static or belong to reference types can ref escape anywhere
if (fieldSymbol.IsStatic || fieldSymbol.ContainingType.IsReferenceType)
{
return true;
}
// for other fields defer to the receiver.
return CheckRefEscape(node, fieldAccess.ReceiverOpt, escapeFrom, escapeTo, checkingReceiver: true, diagnostics: diagnostics);
}
private static bool CheckFieldLikeEventRefEscape(SyntaxNode node, BoundEventAccess eventAccess, uint escapeFrom, uint escapeTo, bool checkingReceiver, DiagnosticBag diagnostics)
{
var eventSymbol = eventAccess.EventSymbol;
// field-like events that are static or belong to reference types can ref escape anywhere
if (eventSymbol.IsStatic || eventSymbol.ContainingType.IsReferenceType)
{
return true;
}
// for other events defer to the receiver.
return CheckRefEscape(node, eventAccess.ReceiverOpt, escapeFrom, escapeTo, checkingReceiver: true, diagnostics: diagnostics);
}
private bool CheckEventValueKind(BoundEventAccess boundEvent, BindValueKind valueKind, DiagnosticBag diagnostics)
{
// Compound assignment (actually "event assignment") is allowed "everywhere", subject to the restrictions of
// accessibility, use site errors, and receiver variable-ness (for structs).
// Other operations are allowed only for field-like events and only where the backing field is accessible
// (i.e. in the declaring type) - subject to use site errors and receiver variable-ness.
BoundExpression receiver = boundEvent.ReceiverOpt;
SyntaxNode eventSyntax = GetEventName(boundEvent); //does not include receiver
EventSymbol eventSymbol = boundEvent.EventSymbol;
if (valueKind == BindValueKind.CompoundAssignment)
{
// NOTE: accessibility has already been checked by lookup.
// NOTE: availability of well-known members is checked in BindEventAssignment because
// we don't have the context to determine whether addition or subtraction is being performed.
if (receiver?.Kind == BoundKind.BaseReference && eventSymbol.IsAbstract)
{
Error(diagnostics, ErrorCode.ERR_AbstractBaseCall, boundEvent.Syntax, eventSymbol);
return false;
}
else if (ReportUseSiteDiagnostics(eventSymbol, diagnostics, eventSyntax))
{
// NOTE: BindEventAssignment checks use site errors on the specific accessor
// (since we don't know which is being used).
return false;
}
Debug.Assert(!RequiresVariableReceiver(receiver, eventSymbol));
return true;
}
else
{
if (!boundEvent.IsUsableAsField)
{
// Dev10 reports this in addition to ERR_BadAccess, but we won't even reach this point if the event isn't accessible (caught by lookup).
Error(diagnostics, GetBadEventUsageDiagnosticInfo(eventSymbol), eventSyntax);
return false;
}
else if (ReportUseSiteDiagnostics(eventSymbol, diagnostics, eventSyntax))
{
if (!CheckIsValidReceiverForVariable(eventSyntax, receiver, BindValueKind.Assignable, diagnostics))
{
return false;
}
}
else if (RequiresVariable(valueKind))
{
if (eventSymbol.IsWindowsRuntimeEvent && valueKind != BindValueKind.Assignable)
{
// NOTE: Dev11 reports ERR_RefProperty, as if this were a property access (since that's how it will be lowered).
// Roslyn reports a new, more specific, error code.
ErrorCode errorCode = valueKind == BindValueKind.RefOrOut ? ErrorCode.ERR_WinRtEventPassedByRef : GetStandardLvalueError(valueKind);
Error(diagnostics, errorCode, eventSyntax, eventSymbol);
return false;
}
else if (RequiresVariableReceiver(receiver, eventSymbol.AssociatedField) && // NOTE: using field, not event
!CheckIsValidReceiverForVariable(eventSyntax, receiver, valueKind, diagnostics))
{
return false;
}
}
return true;
}
}
private bool CheckIsValidReceiverForVariable(SyntaxNode node, BoundExpression receiver, BindValueKind kind, DiagnosticBag diagnostics)
{
Debug.Assert(receiver != null);
return Flags.Includes(BinderFlags.ObjectInitializerMember) && receiver.Kind == BoundKind.ImplicitReceiver ||
CheckValueKind(node, receiver, kind, true, diagnostics);
}
///
/// SPEC: When a property or indexer declared in a struct-type is the target of an
/// SPEC: assignment, the instance expression associated with the property or indexer
/// SPEC: access must be classified as a variable. If the instance expression is
/// SPEC: classified as a value, a compile-time error occurs. Because of 7.6.4,
/// SPEC: the same rule also applies to fields.
///
///
/// NOTE: The spec fails to impose the restriction that the event receiver must be classified
/// as a variable (unlike for properties - 7.17.1). This seems like a bug, but we have
/// production code that won't build with the restriction in place (see DevDiv #15674).
///
private static bool RequiresVariableReceiver(BoundExpression receiver, Symbol symbol)
{
return !symbol.IsStatic
&& symbol.Kind != SymbolKind.Event
&& receiver?.Type?.IsValueType == true;
}
private bool CheckCallValueKind(BoundCall call, SyntaxNode node, BindValueKind valueKind, bool checkingReceiver, DiagnosticBag diagnostics)
{
// A call can only be a variable if it returns by reference. If this is the case,
// whether or not it is a valid variable depends on whether or not the call is the
// RHS of a return or an assign by reference:
// - If call is used in a context demanding ref-returnable reference all of its ref
// inputs must be ref-returnable
var methodSymbol = call.Method;
var callSyntax = call.Syntax;
if (RequiresVariable(valueKind) && methodSymbol.RefKind == RefKind.None)
{
if (checkingReceiver)
{
// Error is associated with expression, not node which may be distinct.
Error(diagnostics, ErrorCode.ERR_ReturnNotLValue, callSyntax, methodSymbol);
}
else
{
Error(diagnostics, GetStandardLvalueError(valueKind), node);
}
return false;
}
if (RequiresAssignableVariable(valueKind) && methodSymbol.RefKind == RefKind.RefReadOnly)
{
ReportReadOnlyError(methodSymbol, node, valueKind, checkingReceiver, diagnostics);
return false;
}
return true;
}
private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindValueKind valueKind, bool checkingReceiver, DiagnosticBag diagnostics)
{
// SPEC: If the left operand is a property or indexer access, the property or indexer must
// SPEC: have a set accessor. If this is not the case, a compile-time error occurs.
// Addendum: Assignment is also allowed for get-only autoprops in their constructor
BoundExpression receiver;
SyntaxNode propertySyntax;
var propertySymbol = GetPropertySymbol(expr, out receiver, out propertySyntax);
Debug.Assert((object)propertySymbol != null);
Debug.Assert(propertySyntax != null);
if ((RequiresReferenceToLocation(valueKind) || checkingReceiver) &&
propertySymbol.RefKind == RefKind.None)
{
if (checkingReceiver)
{
// Error is associated with expression, not node which may be distinct.
// This error is reported for all values types. That is a breaking
// change from Dev10 which reports this error for struct types only,
// not for type parameters constrained to "struct".
Debug.Assert((object)propertySymbol.Type != null);
Error(diagnostics, ErrorCode.ERR_ReturnNotLValue, expr.Syntax, propertySymbol);
}
else
{
Error(diagnostics, valueKind == BindValueKind.RefOrOut ? ErrorCode.ERR_RefProperty : GetStandardLvalueError(valueKind), node, propertySymbol);
}
return false;
}
if (RequiresAssignableVariable(valueKind) && propertySymbol.RefKind == RefKind.RefReadOnly)
{
ReportReadOnlyError(propertySymbol, node, valueKind, checkingReceiver, diagnostics);
return false;
}
var requiresSet = RequiresAssignableVariable(valueKind) && propertySymbol.RefKind == RefKind.None;
if (requiresSet)
{
var setMethod = propertySymbol.GetOwnOrInheritedSetMethod();
if ((object)setMethod == null)
{
var containing = this.ContainingMemberOrLambda;
if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing))
{
Error(diagnostics, ErrorCode.ERR_AssgReadonlyProp, node, propertySymbol);
return false;
}
}
else if (receiver?.Kind == BoundKind.BaseReference && setMethod.IsAbstract)
{
Error(diagnostics, ErrorCode.ERR_AbstractBaseCall, node, propertySymbol);
return false;
}
else if (!object.Equals(setMethod.GetUseSiteDiagnostic(), propertySymbol.GetUseSiteDiagnostic()) && ReportUseSiteDiagnostics(setMethod, diagnostics, propertySyntax))
{
return false;
}
else
{
var accessThroughType = this.GetAccessThroughType(receiver);
bool failedThroughTypeCheck;
HashSet useSiteDiagnostics = null;
bool isAccessible = this.IsAccessible(setMethod, accessThroughType, out failedThroughTypeCheck, ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
if (!isAccessible)
{
if (failedThroughTypeCheck)
{
Error(diagnostics, ErrorCode.ERR_BadProtectedAccess, node, propertySymbol, accessThroughType, this.ContainingType);
}
else
{
Error(diagnostics, ErrorCode.ERR_InaccessibleSetter, node, propertySymbol);
}
return false;
}
ReportDiagnosticsIfObsolete(diagnostics, setMethod, node, receiver?.Kind == BoundKind.BaseReference);
if (RequiresVariableReceiver(receiver, setMethod) && !CheckIsValidReceiverForVariable(node, receiver, BindValueKind.Assignable, diagnostics))
{
return false;
}
}
}
var requiresGet = !RequiresAssignmentOnly(valueKind) || propertySymbol.RefKind != RefKind.None;
if (requiresGet)
{
var getMethod = propertySymbol.GetOwnOrInheritedGetMethod();
if ((object)getMethod == null)
{
Error(diagnostics, ErrorCode.ERR_PropertyLacksGet, node, propertySymbol);
return false;
}
else if (receiver?.Kind == BoundKind.BaseReference && getMethod.IsAbstract)
{
Error(diagnostics, ErrorCode.ERR_AbstractBaseCall, node, propertySymbol);
return false;
}
else if (!object.Equals(getMethod.GetUseSiteDiagnostic(), propertySymbol.GetUseSiteDiagnostic()) && ReportUseSiteDiagnostics(getMethod, diagnostics, propertySyntax))
{
return false;
}
else
{
var accessThroughType = this.GetAccessThroughType(receiver);
bool failedThroughTypeCheck;
HashSet useSiteDiagnostics = null;
bool isAccessible = this.IsAccessible(getMethod, accessThroughType, out failedThroughTypeCheck, ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
if (!isAccessible)
{
if (failedThroughTypeCheck)
{
Error(diagnostics, ErrorCode.ERR_BadProtectedAccess, node, propertySymbol, accessThroughType, this.ContainingType);
}
else
{
Error(diagnostics, ErrorCode.ERR_InaccessibleGetter, node, propertySymbol);
}
return false;
}
ReportDiagnosticsIfObsolete(diagnostics, getMethod, node, receiver?.Kind == BoundKind.BaseReference);
}
}
return true;
}
///
/// Computes the scope to which the given invocation can escape
/// NOTE: the escape scope for ref and val escapes is the same for invocations except for trivial cases (ordinary type returned by val)
/// where escape is known otherwise. Therefore we do not vave two ref/val variants of this.
///
/// 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 static uint GetInvocationEscapeScope(
Symbol symbol,
BoundExpression receiverOpt,
ImmutableArray parameters,
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
uint scopeOfTheContainingExpression,
bool isRefEscape
)
{
// SPEC: (also applies to the CheckInvocationEscape counterpart)
//
// An lvalue resulting from a ref-returning method invocation e1.M(e2, ...) is ref-safe - to - escape the smallest of the following scopes:
//• The entire enclosing method
//• the ref-safe-to-escape of all ref/out/in argument expressions(excluding the receiver)
//• the safe-to - escape of all argument expressions(including the receiver)
//
// An rvalue resulting from a method invocation e1.M(e2, ...) is safe - to - escape from the smallest of the following scopes:
//• The entire enclosing method
//• the safe-to-escape of all argument expressions(including the receiver)
//
if (symbol.IsStatic)
{
// ignore receiver when symbol is static
receiverOpt = null;
}
//by default it is safe to escape
uint escapeScope = Binder.ExternalScope;
ArrayBuilder inParametersMatchedWithArgs = null;
if (!argsOpt.IsDefault)
{
moreArguments:
for (var argIndex = 0; argIndex < argsOpt.Length; argIndex++)
{
var argument = argsOpt[argIndex];
if (argument.Kind == BoundKind.ArgListOperator)
{
Debug.Assert(argIndex == argsOpt.Length - 1, "vararg must be the last");
var argList = (BoundArgListOperator)argument;
// unwrap varargs and process as more arguments
argsOpt = argList.Arguments;
// ref kinds of varargs are not interesting here.
// __refvalue is not ref-returnable, so ref varargs can't come back from a call
argRefKindsOpt = default;
parameters = ImmutableArray.Empty;
argsToParamsOpt = default;
goto moreArguments;
}
RefKind effectiveRefKind = GetEffectiveRefKind(argIndex, argRefKindsOpt, parameters, argsToParamsOpt, ref inParametersMatchedWithArgs);
// ref escape scope is the narrowest of
// - ref escape of all byref arguments
// - val escape of all byval arguments (ref-like values can be unwrapped into refs, so treat val escape of values as possible ref escape of the result)
//
// 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)
var argEscape = effectiveRefKind != RefKind.None && isRefEscape ?
GetRefEscape(argument, scopeOfTheContainingExpression) :
GetValEscape(argument, scopeOfTheContainingExpression);
escapeScope = Math.Max(escapeScope, argEscape);
if (escapeScope >= scopeOfTheContainingExpression)
{
// no longer needed
inParametersMatchedWithArgs?.Free();
// can't get any worse
return escapeScope;
}
}
}
// handle omitted optional "in" parameters if there are any
ParameterSymbol unmatchedInParameter = TryGetunmatchedInParameterAndFreeMatchedArgs(parameters, ref inParametersMatchedWithArgs);
// unmatched "in" parameter is the same as a literal, its ref escape is scopeOfTheContainingExpression (can't get any worse)
// its val escape is ExternalScope (does not affect overal result)
if (unmatchedInParameter != null && isRefEscape)
{
return scopeOfTheContainingExpression;
}
// check receiver if ref-like
if (receiverOpt?.Type?.IsByRefLikeType == true)
{
return GetValEscape(receiverOpt, scopeOfTheContainingExpression);
}
return escapeScope;
}
///
/// Validates whether given invocation can allow its results to escape from `escapeFrom` level to `escapeTo` level.
/// 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.
///
/// 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 static bool CheckInvocationEscape(
SyntaxNode syntax,
Symbol symbol,
BoundExpression receiverOpt,
ImmutableArray parameters,
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
bool checkingReceiver,
uint escapeFrom,
uint escapeTo,
DiagnosticBag diagnostics,
bool isRefEscape
)
{
// SPEC:
// In a method invocation, the following constraints apply:
//• If there is a ref or out argument to a ref struct type (including the receiver), with safe-to-escape E1, then
// o no ref or out argument(excluding the receiver and arguments of ref-like types) may have a narrower ref-safe-to-escape than E1; and
// o no argument(including the receiver) may have a narrower safe-to-escape than E1.
if (symbol.IsStatic)
{
// ignore receiver when symbol is static
receiverOpt = null;
}
ArrayBuilder inParametersMatchedWithArgs = null;
if (!argsOpt.IsDefault)
{
moreArguments:
for (var argIndex = 0; argIndex < argsOpt.Length; argIndex++)
{
var argument = argsOpt[argIndex];
if (argument.Kind == BoundKind.ArgListOperator)
{
Debug.Assert(argIndex == argsOpt.Length - 1, "vararg must be the last");
var argList = (BoundArgListOperator)argument;
// unwrap varargs and process as more arguments
argsOpt = argList.Arguments;
// ref kinds of varargs are not interesting here.
// __refvalue is not ref-returnable, so ref varargs can't come back from a call
argRefKindsOpt = default;
parameters = ImmutableArray.Empty;
argsToParamsOpt = default;
goto moreArguments;
}
RefKind effectiveRefKind = GetEffectiveRefKind(argIndex, argRefKindsOpt, parameters, argsToParamsOpt, ref inParametersMatchedWithArgs);
// ref escape scope is the narrowest of
// - ref escape of all byref arguments
// - val escape of all byval arguments (ref-like values can be unwrapped into refs, so treat val escape of values as possible ref escape of the result)
//
// 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)
var valid = effectiveRefKind != RefKind.None && isRefEscape ?
CheckRefEscape(argument.Syntax, argument, escapeFrom, escapeTo, false, diagnostics) :
CheckValEscape(argument.Syntax, argument, escapeFrom, escapeTo, false, diagnostics);
if (!valid)
{
// no longer needed
inParametersMatchedWithArgs?.Free();
ErrorCode errorCode = GetStandardCallEscapeError(checkingReceiver);
string parameterName;
if (parameters.Length > 0)
{
var paramIndex = argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex];
parameterName = parameters[paramIndex].Name;
}
else
{
parameterName = "__arglist";
}
Error(diagnostics, errorCode, syntax, symbol, parameterName);
return false;
}
}
}
// handle omitted optional "in" parameters if there are any
ParameterSymbol unmatchedInParameter = TryGetunmatchedInParameterAndFreeMatchedArgs(parameters, ref inParametersMatchedWithArgs);
// unmatched "in" parameter is the same as a literal, its ref escape is scopeOfTheContainingExpression (can't get any worse)
// its val escape is ExternalScope (does not affect overal result)
if (unmatchedInParameter != null && isRefEscape)
{
Error(diagnostics, GetStandardCallEscapeError(checkingReceiver), syntax, symbol, unmatchedInParameter.Name);
return false;
}
// check receiver if ref-like
if (receiverOpt?.Type?.IsByRefLikeType == true)
{
return CheckValEscape(receiverOpt.Syntax, receiverOpt, escapeFrom, escapeTo, false, diagnostics);
}
return true;
}
///
/// Validates whether the invocation is valid per no-mixing rules.
/// Returns `false` when it is not valid and produces diagnostics (possibly more than one recursively) that helps to figure the reason.
///
private static bool CheckInvocationArgMixing(
SyntaxNode syntax,
Symbol symbol,
BoundExpression receiverOpt,
ImmutableArray parameters,
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
uint scopeOfTheContainingExpression,
DiagnosticBag diagnostics)
{
if (symbol.IsStatic)
{
// ignore receiver when symbol is static
receiverOpt = null;
}
// widest possible escape via writeable ref-like receiver or ref/out argument.
uint escapeTo = scopeOfTheContainingExpression;
// collect all writeable ref-like arguments, including receiver
var receiverType = receiverOpt?.Type;
if (receiverType?.IsByRefLikeType == true && receiverType?.IsReadOnly == false)
{
escapeTo = GetValEscape(receiverOpt, scopeOfTheContainingExpression);
}
if (!argsOpt.IsDefault)
{
BoundArgListOperator argList = null;
for (var argIndex = 0; argIndex < argsOpt.Length; argIndex++)
{
var argument = argsOpt[argIndex];
if (argument.Kind == BoundKind.ArgListOperator)
{
Debug.Assert(argIndex == argsOpt.Length - 1, "vararg must be the last");
argList = (BoundArgListOperator)argument;
break;
}
var refKind = argRefKindsOpt.IsDefault ? RefKind.None : argRefKindsOpt[argIndex];
if (refKind != RefKind.None && argument.Type?.IsByRefLikeType == true)
{
escapeTo = Math.Min(escapeTo, GetValEscape(argument, scopeOfTheContainingExpression));
}
}
if (argList != null)
{
var argListArgs = argList.Arguments;
var argListRefKindsOpt = argList.ArgumentRefKindsOpt;
for (var argIndex = 0; argIndex < argListArgs.Length; argIndex++)
{
var argument = argListArgs[argIndex];
var refKind = argListRefKindsOpt.IsDefault ? RefKind.None : argListRefKindsOpt[argIndex];
if (refKind != RefKind.None && argument.Type?.IsByRefLikeType == true)
{
escapeTo = Math.Min(escapeTo, GetValEscape(argument, scopeOfTheContainingExpression));
}
}
}
}
if (escapeTo == scopeOfTheContainingExpression)
{
// cannot fail. common case.
return true;
}
if (!argsOpt.IsDefault)
{
moreArguments:
for (var argIndex = 0; argIndex < argsOpt.Length; argIndex++)
{
// check val escape of all arguments
var argument = argsOpt[argIndex];
if (argument.Kind == BoundKind.ArgListOperator)
{
Debug.Assert(argIndex == argsOpt.Length - 1, "vararg must be the last");
var argList = (BoundArgListOperator)argument;
// unwrap varargs and process as more arguments
argsOpt = argList.Arguments;
parameters = ImmutableArray.Empty;
argsToParamsOpt = default;
goto moreArguments;
}
var valid = CheckValEscape(argument.Syntax, argument, scopeOfTheContainingExpression, escapeTo, false, diagnostics);
if (!valid)
{
string parameterName;
if (parameters.Length > 0)
{
var paramIndex = argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex];
parameterName = parameters[paramIndex].Name;
}
else
{
parameterName = "__arglist";
}
Error(diagnostics, ErrorCode.ERR_CallArgMixing, syntax, symbol, parameterName);
return false;
}
}
}
//NB: we do not care about unmatched "in" parameters here.
// They have "outer" val escape, so cannot be worse than escapeTo.
// check val escape of receiver if ref-like
if (receiverOpt?.Type?.IsByRefLikeType == true)
{
return CheckValEscape(receiverOpt.Syntax, receiverOpt, scopeOfTheContainingExpression, escapeTo, false, diagnostics);
}
return true;
}
///
/// Gets "effective" ref kind of an argument.
/// Generally we know if a formal argument is passed as ref/out by looking at the call site.
/// However, to distinguish "in" and regular "val" parameters we need to take a look at corresponding parameter, if such exists.
/// NOTE: there are cases like params/vararg, when a corresponding parameter may not exist, then it cannot be "in".
///
private static RefKind GetEffectiveRefKind(
int argIndex,
ImmutableArray argRefKindsOpt,
ImmutableArray parameters,
ImmutableArray argsToParamsOpt,
ref ArrayBuilder inParametersMatchedWithArgs)
{
var effectiveRefKind = argRefKindsOpt.IsDefault ? RefKind.None : argRefKindsOpt[argIndex];
if (effectiveRefKind == RefKind.None && argIndex < parameters.Length)
{
var paramIndex = argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex];
if (parameters[paramIndex].RefKind == RefKind.In)
{
effectiveRefKind = RefKind.In;
inParametersMatchedWithArgs = inParametersMatchedWithArgs ?? ArrayBuilder.GetInstance(parameters.Length, fillWithValue: false);
inParametersMatchedWithArgs[paramIndex] = true;
}
}
return effectiveRefKind;
}
///
/// Gets a "in" parameter for which there is no argument supplied, if such exists.
/// That indicates an optional "in" parameter. We treat it as an RValue passed by reference via a temporary.
/// The effective scope of such variable is the immediately containing scope.
///
private static ParameterSymbol TryGetunmatchedInParameterAndFreeMatchedArgs(ImmutableArray parameters, ref ArrayBuilder inParametersMatchedWithArgs)
{
try
{
if (!parameters.IsDefault)
{
for (int i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (parameter.IsParams)
{
break;
}
if (parameter.RefKind == RefKind.In &&
inParametersMatchedWithArgs?[i] != true &&
parameter.Type.IsByRefLikeType == false)
{
return parameter;
}
}
}
return null;
}
finally
{
inParametersMatchedWithArgs?.Free();
// make sure noone uses it after.
inParametersMatchedWithArgs = null;
}
}
private static ErrorCode GetStandardCallEscapeError(bool checkingReceiver)
{
return checkingReceiver ? ErrorCode.ERR_EscapeCall2 : ErrorCode.ERR_EscapeCall;
}
private static void ReportReadonlyLocalError(SyntaxNode node, LocalSymbol local, BindValueKind kind, bool checkingReceiver, DiagnosticBag diagnostics)
{
Debug.Assert((object)local != null);
Debug.Assert(kind != BindValueKind.RValue);
MessageID cause;
if (local.IsForEach)
{
cause = MessageID.IDS_FOREACHLOCAL;
}
else if (local.IsUsing)
{
cause = MessageID.IDS_USINGLOCAL;
}
else if (local.IsFixed)
{
cause = MessageID.IDS_FIXEDLOCAL;
}
else
{
Error(diagnostics, GetStandardLvalueError(kind), node);
return;
}
ErrorCode[] ReadOnlyLocalErrors =
{
ErrorCode.ERR_RefReadonlyLocalCause,
ErrorCode.ERR_AssgReadonlyLocalCause,
ErrorCode.ERR_RefReadonlyLocal2Cause,
ErrorCode.ERR_AssgReadonlyLocal2Cause
};
int index = (checkingReceiver ? 2 : 0) + (RequiresRefOrOut(kind) ? 0 : 1);
Error(diagnostics, ReadOnlyLocalErrors[index], node, local, cause.Localize());
}
static private ErrorCode GetThisLvalueError(BindValueKind kind)
{
switch (kind)
{
case BindValueKind.CompoundAssignment:
case BindValueKind.Assignable:
return ErrorCode.ERR_AssgReadonlyLocal;
case BindValueKind.RefOrOut:
return ErrorCode.ERR_RefReadonlyLocal;
case BindValueKind.AddressOf:
return ErrorCode.ERR_InvalidAddrOp;
case BindValueKind.IncrementDecrement:
return ErrorCode.ERR_IncrementLvalueExpected;
case BindValueKind.RefReturn:
case BindValueKind.ReadonlyRef:
return ErrorCode.ERR_RefReturnThis;
}
throw ExceptionUtilities.UnexpectedValue(kind);
}
private static ErrorCode GetRangeLvalueError(BindValueKind kind)
{
switch (kind)
{
case BindValueKind.Assignable:
case BindValueKind.CompoundAssignment:
case BindValueKind.IncrementDecrement:
return ErrorCode.ERR_QueryRangeVariableReadOnly;
case BindValueKind.AddressOf:
return ErrorCode.ERR_InvalidAddrOp;
case BindValueKind.RefReturn:
case BindValueKind.ReadonlyRef:
return ErrorCode.ERR_RefReturnRangeVariable;
}
if (RequiresReferenceToLocation(kind))
{
return ErrorCode.ERR_QueryOutRefRangeVariable;
}
throw ExceptionUtilities.UnexpectedValue(kind);
}
private static ErrorCode GetMethodGroupLvalueError(BindValueKind valueKind)
{
if (valueKind == BindValueKind.AddressOf)
{
return ErrorCode.ERR_InvalidAddrOp;
}
if (RequiresReferenceToLocation(valueKind))
{
return ErrorCode.ERR_RefReadonlyLocalCause;
}
// Cannot assign to 'W' because it is a 'method group'
return ErrorCode.ERR_AssgReadonlyLocalCause;
}
static private ErrorCode GetStandardLvalueError(BindValueKind kind)
{
switch (kind)
{
case BindValueKind.CompoundAssignment:
case BindValueKind.Assignable:
return ErrorCode.ERR_AssgLvalueExpected;
case BindValueKind.AddressOf:
return ErrorCode.ERR_InvalidAddrOp;
case BindValueKind.IncrementDecrement:
return ErrorCode.ERR_IncrementLvalueExpected;
case BindValueKind.FixedReceiver:
return ErrorCode.ERR_FixedNeedsLvalue;
case BindValueKind.RefReturn:
case BindValueKind.ReadonlyRef:
return ErrorCode.ERR_RefReturnLvalueExpected;
}
if (RequiresReferenceToLocation(kind))
{
return ErrorCode.ERR_RefLvalueExpected;
}
throw ExceptionUtilities.UnexpectedValue(kind);
}
static private ErrorCode GetStandardRValueRefEscapeError(uint escapeTo)
{
if (escapeTo == Binder.ExternalScope)
{
return ErrorCode.ERR_RefReturnLvalueExpected;
}
return ErrorCode.ERR_EscapeOther;
}
private static void ReportReadOnlyFieldError(FieldSymbol field, SyntaxNode node, BindValueKind kind, bool checkingReceiver, DiagnosticBag diagnostics)
{
Debug.Assert((object)field != null);
Debug.Assert(RequiresAssignableVariable(kind));
Debug.Assert((object)field.Type != null);
// It's clearer to say that the address can't be taken than to say that the field can't be modified
// (even though the latter message gives more explanation of why).
if (kind == BindValueKind.AddressOf)
{
Error(diagnostics, ErrorCode.ERR_InvalidAddrOp, node);
return;
}
ErrorCode[] ReadOnlyErrors =
{
ErrorCode.ERR_RefReturnReadonly,
ErrorCode.ERR_RefReadonly,
ErrorCode.ERR_AssgReadonly,
ErrorCode.ERR_RefReturnReadonlyStatic,
ErrorCode.ERR_RefReadonlyStatic,
ErrorCode.ERR_AssgReadonlyStatic,
ErrorCode.ERR_RefReturnReadonly2,
ErrorCode.ERR_RefReadonly2,
ErrorCode.ERR_AssgReadonly2,
ErrorCode.ERR_RefReturnReadonlyStatic2,
ErrorCode.ERR_RefReadonlyStatic2,
ErrorCode.ERR_AssgReadonlyStatic2
};
int index = (checkingReceiver ? 6 : 0) + (field.IsStatic ? 3 : 0) + (kind == BindValueKind.RefReturn ? 0 : (RequiresRefOrOut(kind) ? 1 : 2));
if (checkingReceiver)
{
Error(diagnostics, ReadOnlyErrors[index], node, field);
}
else
{
Error(diagnostics, ReadOnlyErrors[index], node);
}
}
private static void ReportReadOnlyError(Symbol symbol, SyntaxNode node, BindValueKind kind, bool checkingReceiver, DiagnosticBag diagnostics)
{
Debug.Assert((object)symbol != null);
Debug.Assert(RequiresAssignableVariable(kind));
// It's clearer to say that the address can't be taken than to say that the parameter can't be modified
// (even though the latter message gives more explanation of why).
if (kind == BindValueKind.AddressOf)
{
Error(diagnostics, ErrorCode.ERR_InvalidAddrOp, node);
return;
}
var symbolKind = symbol.Kind.Localize();
ErrorCode[] ReadOnlyErrors =
{
ErrorCode.ERR_RefReturnReadonlyNotField,
ErrorCode.ERR_RefReadonlyNotField,
ErrorCode.ERR_AssignReadonlyNotField,
ErrorCode.ERR_RefReturnReadonlyNotField2,
ErrorCode.ERR_RefReadonlyNotField2,
ErrorCode.ERR_AssignReadonlyNotField2,
};
int index = (checkingReceiver ? 3 : 0) + (kind == BindValueKind.RefReturn ? 0 : (RequiresRefOrOut(kind) ? 1 : 2));
Error(diagnostics, ReadOnlyErrors[index], node, symbolKind, symbol);
}
///
/// Checks whether given expression can escape from the current scope to the `escapeTo`
/// In a case if it cannot a bad expression is returned and diagnostics is produced.
///
internal BoundExpression ValidateEscape(BoundExpression expr, uint escapeTo, bool isByRef, DiagnosticBag diagnostics)
{
if (isByRef)
{
if (CheckRefEscape(expr.Syntax, expr, this.LocalScopeDepth, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{
return expr;
}
}
else
{
if (CheckValEscape(expr.Syntax, expr, this.LocalScopeDepth, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{
return expr;
}
}
return ToBadExpression(expr);
}
///
/// Computes the widest scope depth to which the given expression can escape by reference.
///
/// NOTE: in a case if expression cannot be passed by an alias (RValue and similar), the ref-escape is scopeOfTheContainingExpression
/// 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 static uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainingExpression)
{
// cannot infer anything from errors
if (expr.HasAnyErrors)
{
return Binder.ExternalScope;
}
// cannot infer anything from Void (broken code)
if (expr.Type?.GetSpecialTypeSafe() == SpecialType.System_Void)
{
return Binder.ExternalScope;
}
// constants/literals cannot ref-escape current scope
if (expr.ConstantValue != null)
{
return scopeOfTheContainingExpression;
}
// cover case that cannot refer to local state
// otherwise default to current scope (RValues, etc)
switch (expr.Kind)
{
case BoundKind.ArrayAccess:
case BoundKind.PointerIndirectionOperator:
case BoundKind.PointerElementAccess:
// array elements and pointer dereferencing are readwrite variables
return Binder.ExternalScope;
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 Binder.TopLevelScope;
case BoundKind.DiscardExpression:
// same as write-only byval local
break;
case BoundKind.DynamicMemberAccess:
case BoundKind.DynamicIndexerAccess:
// dynamic expressions can be read and written to
// can even be passed by reference (which is implemented via a temp)
// it is not valid to escape them by reference though, so treat them as RValues here
break;
case BoundKind.Parameter:
var parameter = ((BoundParameter)expr).ParameterSymbol;
// byval parameters can escape to method's top level.
// others can be escape further, unless they are ref-like.
// NOTE: "method" here means nearst containing method, lambda or nested method
return parameter.RefKind == RefKind.None || parameter.Type?.IsByRefLikeType == true ?
Binder.TopLevelScope :
Binder.ExternalScope;
case BoundKind.Local:
return ((BoundLocal)expr).LocalSymbol.RefEscapeScope;
case BoundKind.ThisReference:
var thisref = (BoundThisReference)expr;
// "this" is an RValue, unless in a struct.
if (!thisref.Type.IsValueType)
{
break;
}
//"this" is not returnable by reference in a struct.
// can ref escape to any other level
return Binder.TopLevelScope;
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
if (conditional.IsByRef)
{
// ref conditional defers to its operands
return Math.Max(GetRefEscape(conditional.Consequence, scopeOfTheContainingExpression),
GetRefEscape(conditional.Alternative, scopeOfTheContainingExpression));
}
// otherwise it is an RValue
break;
case BoundKind.FieldAccess:
var fieldAccess = (BoundFieldAccess)expr;
var fieldSymbol = fieldAccess.FieldSymbol;
// fields that are static or belong to reference types can ref escape anywhere
if (fieldSymbol.IsStatic || fieldSymbol.ContainingType.IsReferenceType)
{
return Binder.ExternalScope;
}
// for other fields defer to the receiver.
return GetRefEscape(fieldAccess.ReceiverOpt, scopeOfTheContainingExpression);
case BoundKind.EventAccess:
var eventAccess = (BoundEventAccess)expr;
if (!eventAccess.IsUsableAsField)
{
// not field-like events are RValues
break;
}
var eventSymbol = eventAccess.EventSymbol;
// field-like events that are static or belong to reference types can ref escape anywhere
if (eventSymbol.IsStatic || eventSymbol.ContainingType.IsReferenceType)
{
return Binder.ExternalScope;
}
// for other events defer to the receiver.
return GetRefEscape(eventAccess.ReceiverOpt, scopeOfTheContainingExpression);
case BoundKind.Call:
var call = (BoundCall)expr;
var methodSymbol = call.Method;
if (methodSymbol.RefKind == RefKind.None)
{
break;
}
return GetInvocationEscapeScope(
call.Method,
call.ReceiverOpt,
methodSymbol.Parameters,
call.Arguments,
call.ArgumentRefKindsOpt,
call.ArgsToParamsOpt,
scopeOfTheContainingExpression,
isRefEscape: true);
case BoundKind.IndexerAccess:
var indexerAccess = (BoundIndexerAccess)expr;
var indexerSymbol = indexerAccess.Indexer;
return GetInvocationEscapeScope(
indexerSymbol,
indexerAccess.ReceiverOpt,
indexerSymbol.Parameters,
indexerAccess.Arguments,
indexerAccess.ArgumentRefKindsOpt,
indexerAccess.ArgsToParamsOpt,
scopeOfTheContainingExpression,
isRefEscape: true);
case BoundKind.PropertyAccess:
var propertyAccess = (BoundPropertyAccess)expr;
// not passing any arguments/parameters
return GetInvocationEscapeScope(
propertyAccess.PropertySymbol,
propertyAccess.ReceiverOpt,
default,
default,
default,
default,
scopeOfTheContainingExpression,
isRefEscape: true);
}
// At this point we should have covered all the possible cases for anything that is not a strict RValue.
return scopeOfTheContainingExpression;
}
///
/// A counterpart to the GetRefEscape, which validates if given escape demand can be met by the expression.
/// 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 static bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeFrom, uint escapeTo, bool checkingReceiver, DiagnosticBag diagnostics)
{
Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter());
if (escapeTo >= escapeFrom)
{
// escaping to same or narrower scope is ok.
return true;
}
if (expr.HasAnyErrors)
{
// already an error
return true;
}
// void references cannot escape (error should be reported somewhere)
if (expr.Type?.GetSpecialTypeSafe() == SpecialType.System_Void)
{
return true;
}
// references to constants/literals cannot escape higher.
if (expr.ConstantValue != null)
{
Error(diagnostics, GetStandardRValueRefEscapeError(escapeTo), node);
return false;
}
switch (expr.Kind)
{
case BoundKind.ArrayAccess:
case BoundKind.PointerIndirectionOperator:
case BoundKind.PointerElementAccess:
// array elements and pointer dereferencing are readwrite variables
return true;
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 == Binder.ExternalScope)
{
break;
}
// it can, however, ref-escape to any other level (since TypedReference can val-escape to any other level)
return true;
case BoundKind.DiscardExpression:
// same as write-only byval local
break;
case BoundKind.DynamicMemberAccess:
case BoundKind.DynamicIndexerAccess:
// dynamic expressions can be read and written to
// can even be passed by reference (which is implemented via a temp)
// it is not valid to escape them by reference though.
break;
case BoundKind.Parameter:
var parameter = (BoundParameter)expr;
return CheckParameterRefEscape(node, parameter, escapeTo, checkingReceiver, diagnostics);
case BoundKind.Local:
var local = (BoundLocal)expr;
return CheckLocalRefEscape(node, local, escapeTo, checkingReceiver, diagnostics);
case BoundKind.ThisReference:
var thisref = (BoundThisReference)expr;
// "this" is an RValue, unless in a struct.
if (!thisref.Type.IsValueType)
{
break;
}
//"this" is not returnable by reference in a struct.
if (escapeTo == Binder.ExternalScope)
{
Error(diagnostics, ErrorCode.ERR_RefReturnStructThis, node, ThisParameterSymbol.SymbolName);
return false;
}
// can ref escape to any other level
return true;
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
if (conditional.IsByRef)
{
return CheckRefEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) &&
CheckRefEscape(conditional.Alternative.Syntax, conditional.Alternative, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
}
// report standard lvalue error
break;
case BoundKind.FieldAccess:
var fieldAccess = (BoundFieldAccess)expr;
return CheckFieldRefEscape(node, fieldAccess, escapeFrom, escapeTo, checkingReceiver, diagnostics);
case BoundKind.EventAccess:
var eventAccess = (BoundEventAccess)expr;
if (!eventAccess.IsUsableAsField)
{
// not field-like events are RValues
break;
}
return CheckFieldLikeEventRefEscape(node, eventAccess, escapeFrom, escapeTo, checkingReceiver, diagnostics);
case BoundKind.Call:
var call = (BoundCall)expr;
var methodSymbol = call.Method;
if (methodSymbol.RefKind == RefKind.None)
{
break;
}
return CheckInvocationEscape(
call.Syntax,
methodSymbol,
call.ReceiverOpt,
methodSymbol.Parameters,
call.Arguments,
call.ArgumentRefKindsOpt,
call.ArgsToParamsOpt,
checkingReceiver,
escapeFrom,
escapeTo,
diagnostics,
isRefEscape: true);
case BoundKind.IndexerAccess:
var indexerAccess = (BoundIndexerAccess)expr;
var indexerSymbol = indexerAccess.Indexer;
if (indexerSymbol.RefKind == RefKind.None)
{
break;
}
return CheckInvocationEscape(
indexerAccess.Syntax,
indexerSymbol,
indexerAccess.ReceiverOpt,
indexerSymbol.Parameters,
indexerAccess.Arguments,
indexerAccess.ArgumentRefKindsOpt,
indexerAccess.ArgsToParamsOpt,
checkingReceiver,
escapeFrom,
escapeTo,
diagnostics,
isRefEscape: true);
case BoundKind.PropertyAccess:
var propertyAccess = (BoundPropertyAccess)expr;
var propertySymbol = propertyAccess.PropertySymbol;
if (propertySymbol.RefKind == RefKind.None)
{
break;
}
// not passing any arguments/parameters
return CheckInvocationEscape(
propertyAccess.Syntax,
propertySymbol,
propertyAccess.ReceiverOpt,
default,
default,
default,
default,
checkingReceiver,
escapeFrom,
escapeTo,
diagnostics,
isRefEscape: true);
}
// At this point we should have covered all the possible cases for anything that is not a strict RValue.
Error(diagnostics, GetStandardRValueRefEscapeError(escapeTo), node);
return false;
}
internal static uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheContainingExpression)
{
uint broadest = scopeOfTheContainingExpression;
foreach (var element in expr.Arguments)
{
uint valEscape;
if (element.Kind == BoundKind.TupleLiteral)
{
valEscape = GetBroadestValEscape((BoundTupleExpression)element, scopeOfTheContainingExpression);
}
else
{
valEscape = GetValEscape(element, scopeOfTheContainingExpression);
}
broadest = Math.Min(broadest, valEscape);
}
return broadest;
}
///
/// Computes the widest scope depth to which the given expression can escape by value.
///
/// NOTE: unless the type of expression is ref-like, the result is Binder.ExternalScope since ordinary values can always be returned from methods.
///
internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpression)
{
// cannot infer anything from errors
if (expr.HasAnyErrors)
{
return Binder.ExternalScope;
}
// constants/literals cannot refer to local state
if (expr.ConstantValue != null)
{
return Binder.ExternalScope;
}
// to have local-referring values an expression must have a ref-like type
if (expr.Type?.IsByRefLikeType != true)
{
return Binder.ExternalScope;
}
// cover case that can refer to local state
// otherwise default to ExternalScope (ordinary values)
switch (expr.Kind)
{
case BoundKind.DefaultExpression:
case BoundKind.Parameter:
case BoundKind.ThisReference:
// always returnable
return Binder.ExternalScope;
case BoundKind.TupleLiteral:
var tupleLiteral = (BoundTupleLiteral)expr;
return GetTupleValEscape(tupleLiteral.Arguments, scopeOfTheContainingExpression);
case BoundKind.ConvertedTupleLiteral:
var convertedTupleLiteral = (BoundConvertedTupleLiteral)expr;
return GetTupleValEscape(convertedTupleLiteral.Arguments, scopeOfTheContainingExpression);
case BoundKind.MakeRefOperator:
case BoundKind.RefValueOperator:
// for compat reasons
// NB: it also means can`t assign stackalloc spans to a __refvalue
// we are ok with that.
return Binder.ExternalScope;
case BoundKind.DiscardExpression:
// same as uninitialized local
return Binder.ExternalScope;
case BoundKind.DeconstructValuePlaceholder:
return ((BoundDeconstructValuePlaceholder)expr).ValEscape;
case BoundKind.Local:
return ((BoundLocal)expr).LocalSymbol.ValEscapeScope;
case BoundKind.StackAllocArrayCreation:
case BoundKind.ConvertedStackAllocExpression:
return Binder.TopLevelScope;
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
var consEscape = GetValEscape(conditional.Consequence, scopeOfTheContainingExpression);
if (conditional.IsByRef)
{
// ref conditional defers to one operand.
// the other one is the same or we will be reporting errors anyways.
return consEscape;
}
// val conditional gets narrowest of its operands
return Math.Max(consEscape,
GetValEscape(conditional.Alternative, scopeOfTheContainingExpression));
case BoundKind.FieldAccess:
var fieldAccess = (BoundFieldAccess)expr;
var fieldSymbol = fieldAccess.FieldSymbol;
Debug.Assert(!fieldSymbol.IsStatic && fieldSymbol.ContainingType.IsByRefLikeType);
// for ref-like fields defer to the receiver.
return GetValEscape(fieldAccess.ReceiverOpt, scopeOfTheContainingExpression);
case BoundKind.Call:
var call = (BoundCall)expr;
return GetInvocationEscapeScope(
call.Method,
call.ReceiverOpt,
call.Method.Parameters,
call.Arguments,
call.ArgumentRefKindsOpt,
call.ArgsToParamsOpt,
scopeOfTheContainingExpression,
isRefEscape: false);
case BoundKind.IndexerAccess:
var indexerAccess = (BoundIndexerAccess)expr;
var indexerSymbol = indexerAccess.Indexer;
return GetInvocationEscapeScope(
indexerSymbol,
indexerAccess.ReceiverOpt,
indexerSymbol.Parameters,
indexerAccess.Arguments,
indexerAccess.ArgumentRefKindsOpt,
indexerAccess.ArgsToParamsOpt,
scopeOfTheContainingExpression,
isRefEscape: false);
case BoundKind.PropertyAccess:
var propertyAccess = (BoundPropertyAccess)expr;
// not passing any arguments/parameters
return GetInvocationEscapeScope(
propertyAccess.PropertySymbol,
propertyAccess.ReceiverOpt,
default,
default,
default,
default,
scopeOfTheContainingExpression,
isRefEscape: false);
case BoundKind.ObjectCreationExpression:
var objectCreation = (BoundObjectCreationExpression)expr;
var constructorSymbol = objectCreation.Constructor;
var escape = GetInvocationEscapeScope(
constructorSymbol,
null,
constructorSymbol.Parameters,
objectCreation.Arguments,
objectCreation.ArgumentRefKindsOpt,
objectCreation.ArgsToParamsOpt,
scopeOfTheContainingExpression,
isRefEscape: false);
var initializerOpt = objectCreation.InitializerExpressionOpt;
if (initializerOpt != null)
{
escape = Math.Max(escape, GetValEscape(initializerOpt, scopeOfTheContainingExpression));
}
return escape;
case BoundKind.UnaryOperator:
return GetValEscape(((BoundUnaryOperator)expr).Operand, scopeOfTheContainingExpression);
case BoundKind.Conversion:
var conversion = (BoundConversion)expr;
Debug.Assert(conversion.ConversionKind != ConversionKind.StackAllocToSpanType, "StackAllocToSpanType unexpected");
return GetValEscape(conversion.Operand, scopeOfTheContainingExpression);
case BoundKind.AssignmentOperator:
return GetValEscape(((BoundAssignmentOperator)expr).Right, scopeOfTheContainingExpression);
case BoundKind.IncrementOperator:
return GetValEscape(((BoundIncrementOperator)expr).Operand, scopeOfTheContainingExpression);
case BoundKind.CompoundAssignmentOperator:
var compound = (BoundCompoundAssignmentOperator)expr;
return Math.Max(GetValEscape(compound.Left, scopeOfTheContainingExpression),
GetValEscape(compound.Right, scopeOfTheContainingExpression));
case BoundKind.BinaryOperator:
var binary = (BoundBinaryOperator)expr;
return Math.Max(GetValEscape(binary.Left, scopeOfTheContainingExpression),
GetValEscape(binary.Right, scopeOfTheContainingExpression));
case BoundKind.UserDefinedConditionalLogicalOperator:
var uo = (BoundUserDefinedConditionalLogicalOperator)expr;
return Math.Max(GetValEscape(uo.Left, scopeOfTheContainingExpression),
GetValEscape(uo.Right, scopeOfTheContainingExpression));
case BoundKind.QueryClause:
return GetValEscape(((BoundQueryClause)expr).Value, scopeOfTheContainingExpression);
case BoundKind.RangeVariable:
return GetValEscape(((BoundRangeVariable)expr).Value, scopeOfTheContainingExpression);
case BoundKind.ObjectInitializerExpression:
var initExpr = (BoundObjectInitializerExpression)expr;
return GetValEscapeOfObjectInitializer(initExpr, scopeOfTheContainingExpression);
case BoundKind.CollectionInitializerExpression:
var colExpr = (BoundCollectionInitializerExpression)expr;
return GetValEscape(colExpr.Initializers, scopeOfTheContainingExpression);
case BoundKind.CollectionElementInitializer:
var colElement = (BoundCollectionElementInitializer)expr;
return GetValEscape(colElement.Arguments, scopeOfTheContainingExpression);
case BoundKind.ObjectInitializerMember:
// this node generally makes no sense outside of the context of containing initializer
// however binder uses it as a placeholder when binding assignments inside an object initializer
// just say it does not escape anywhere, so that we do not get false errors.
return scopeOfTheContainingExpression;
case BoundKind.ImplicitReceiver:
// binder uses this as a placeholder when binding members inside an object initializer
// just say it does not escape anywhere, so that we do not get false errors.
return scopeOfTheContainingExpression;
default:
throw ExceptionUtilities.UnexpectedValue($"{expr.Kind} expression of {expr.Type} type");
}
}
private static uint GetTupleValEscape(ImmutableArray elements, uint scopeOfTheContainingExpression)
{
uint narrowestScope = scopeOfTheContainingExpression;
foreach (var element in elements)
{
narrowestScope = Math.Max(narrowestScope, GetValEscape(element, scopeOfTheContainingExpression));
}
return narrowestScope;
}
private static uint GetValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint scopeOfTheContainingExpression)
{
var result = Binder.ExternalScope;
foreach (var expression in initExpr.Initializers)
{
if (expression.Kind == BoundKind.AssignmentOperator)
{
var assignment = (BoundAssignmentOperator)expression;
result = Math.Max(result, GetValEscape(assignment.Right, scopeOfTheContainingExpression));
var left = (BoundObjectInitializerMember)assignment.Left;
result = Math.Max(result, GetValEscape(left.Arguments, scopeOfTheContainingExpression));
}
else
{
result = Math.Max(result, GetValEscape(expression, scopeOfTheContainingExpression));
}
}
return result;
}
private static uint GetValEscape(ImmutableArray expressions, uint scopeOfTheContainingExpression)
{
var result = Binder.ExternalScope;
foreach (var expression in expressions)
{
result = Math.Max(result, GetValEscape(expression, scopeOfTheContainingExpression));
}
return result;
}
///
/// A counterpart to the GetValEscape, which validates if given escape demand can be met by the expression.
/// 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 static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeFrom, uint escapeTo, bool checkingReceiver, DiagnosticBag diagnostics)
{
Debug.Assert(!checkingReceiver || expr.Type.IsValueType || expr.Type.IsTypeParameter());
if (escapeTo >= escapeFrom)
{
// escaping to same or narrower scope is ok.
return true;
}
// cannot infer anything from errors
if (expr.HasAnyErrors)
{
return true;
}
// constants/literals cannot refer to local state
if (expr.ConstantValue != null)
{
return true;
}
// to have local-referring values an expression must have a ref-like type
if (expr.Type?.IsByRefLikeType != true)
{
return true;
}
switch (expr.Kind)
{
case BoundKind.DefaultExpression:
case BoundKind.Parameter:
case BoundKind.ThisReference:
// always returnable
return true;
case BoundKind.TupleLiteral:
var tupleLiteral = (BoundTupleLiteral)expr;
return CheckTupleValEscape(tupleLiteral.Arguments, escapeFrom, escapeTo, diagnostics);
case BoundKind.ConvertedTupleLiteral:
var convertedTupleLiteral = (BoundConvertedTupleLiteral)expr;
return CheckTupleValEscape(convertedTupleLiteral.Arguments, escapeFrom, escapeTo, diagnostics);
case BoundKind.MakeRefOperator:
case BoundKind.RefValueOperator:
// for compat reasons
return true;
case BoundKind.DiscardExpression:
// same as uninitialized local
return true;
case BoundKind.DeconstructValuePlaceholder:
var placeholder = (BoundDeconstructValuePlaceholder)expr;
if (placeholder.ValEscape > escapeTo)
{
Error(diagnostics, ErrorCode.ERR_EscapeLocal, node, placeholder.Syntax);
return false;
}
return true;
case BoundKind.Local:
var localSymbol = ((BoundLocal)expr).LocalSymbol;
if (localSymbol.ValEscapeScope > escapeTo)
{
Error(diagnostics, ErrorCode.ERR_EscapeLocal, node, localSymbol);
return false;
}
return true;
case BoundKind.StackAllocArrayCreation:
case BoundKind.ConvertedStackAllocExpression:
if (escapeTo < Binder.TopLevelScope)
{
Error(diagnostics, ErrorCode.ERR_EscapeStackAlloc, node, expr.Type);
return false;
}
return true;
case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
var consValid = CheckValEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
if (!consValid || conditional.IsByRef)
{
// ref conditional defers to one operand.
// the other one is the same or we will be reporting errors anyways.
return consValid;
}
return CheckValEscape(conditional.Alternative.Syntax, conditional.Alternative, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.FieldAccess:
var fieldAccess = (BoundFieldAccess)expr;
var fieldSymbol = fieldAccess.FieldSymbol;
Debug.Assert(!fieldSymbol.IsStatic && fieldSymbol.ContainingType.IsByRefLikeType);
// for ref-like fields defer to the receiver.
return CheckValEscape(node, fieldAccess.ReceiverOpt, escapeFrom, escapeTo, true, diagnostics);
case BoundKind.Call:
var call = (BoundCall)expr;
var methodSymbol = call.Method;
return CheckInvocationEscape(
call.Syntax,
methodSymbol,
call.ReceiverOpt,
methodSymbol.Parameters,
call.Arguments,
call.ArgumentRefKindsOpt,
call.ArgsToParamsOpt,
checkingReceiver,
escapeFrom,
escapeTo,
diagnostics,
isRefEscape: false);
case BoundKind.IndexerAccess:
var indexerAccess = (BoundIndexerAccess)expr;
var indexerSymbol = indexerAccess.Indexer;
return CheckInvocationEscape(
indexerAccess.Syntax,
indexerSymbol,
indexerAccess.ReceiverOpt,
indexerSymbol.Parameters,
indexerAccess.Arguments,
indexerAccess.ArgumentRefKindsOpt,
indexerAccess.ArgsToParamsOpt,
checkingReceiver,
escapeFrom,
escapeTo,
diagnostics,
isRefEscape: false);
case BoundKind.PropertyAccess:
var propertyAccess = (BoundPropertyAccess)expr;
// not passing any arguments/parameters
return CheckInvocationEscape(
propertyAccess.Syntax,
propertyAccess.PropertySymbol,
propertyAccess.ReceiverOpt,
default,
default,
default,
default,
checkingReceiver,
escapeFrom,
escapeTo,
diagnostics,
isRefEscape: false);
case BoundKind.ObjectCreationExpression:
var objectCreation = (BoundObjectCreationExpression)expr;
var constructorSymbol = objectCreation.Constructor;
var escape = CheckInvocationEscape(
objectCreation.Syntax,
constructorSymbol,
null,
constructorSymbol.Parameters,
objectCreation.Arguments,
objectCreation.ArgumentRefKindsOpt,
objectCreation.ArgsToParamsOpt,
checkingReceiver,
escapeFrom,
escapeTo,
diagnostics,
isRefEscape: false);
var initializerExpr = objectCreation.InitializerExpressionOpt;
if (initializerExpr != null)
{
escape = escape &&
CheckValEscape(
initializerExpr.Syntax,
initializerExpr,
escapeFrom,
escapeTo,
checkingReceiver:false,
diagnostics:diagnostics);
}
return escape;
case BoundKind.UnaryOperator:
var unary = (BoundUnaryOperator)expr;
return CheckValEscape(node, unary.Operand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.Conversion:
var conversion = (BoundConversion)expr;
Debug.Assert(conversion.ConversionKind != ConversionKind.StackAllocToSpanType, "StackAllocToSpanType unexpected");
return CheckValEscape(node, conversion.Operand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.AssignmentOperator:
var assignment = (BoundAssignmentOperator)expr;
return CheckValEscape(node, assignment.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.IncrementOperator:
var increment = (BoundIncrementOperator)expr;
return CheckValEscape(node, increment.Operand, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.CompoundAssignmentOperator:
var compound = (BoundCompoundAssignmentOperator)expr;
return CheckValEscape(compound.Left.Syntax, compound.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) &&
CheckValEscape(compound.Right.Syntax, compound.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.BinaryOperator:
var binary = (BoundBinaryOperator)expr;
return CheckValEscape(binary.Left.Syntax, binary.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) &&
CheckValEscape(binary.Right.Syntax, binary.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.UserDefinedConditionalLogicalOperator:
var uo = (BoundUserDefinedConditionalLogicalOperator)expr;
return CheckValEscape(uo.Left.Syntax, uo.Left, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) &&
CheckValEscape(uo.Right.Syntax, uo.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.QueryClause:
var clauseValue = ((BoundQueryClause)expr).Value;
return CheckValEscape(clauseValue.Syntax, clauseValue, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.RangeVariable:
var variableValue = ((BoundRangeVariable)expr).Value;
return CheckValEscape(variableValue.Syntax, variableValue, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
case BoundKind.ObjectInitializerExpression:
var initExpr = (BoundObjectInitializerExpression)expr;
return CheckValEscapeOfObjectInitializer(initExpr, escapeFrom, escapeTo, diagnostics);
// this would be correct implementation for CollectionInitializerExpression
// however it is unclear if it is reachable since the initialized type must implement IEnumerable
case BoundKind.CollectionInitializerExpression:
var colExpr = (BoundCollectionInitializerExpression)expr;
return CheckValEscape(colExpr.Initializers, escapeFrom, escapeTo, diagnostics);
// this would be correct implementation for CollectionElementInitializer
// however it is unclear if it is reachable since the initialized type must implement IEnumerable
case BoundKind.CollectionElementInitializer:
var colElement = (BoundCollectionElementInitializer)expr;
return CheckValEscape(colElement.Arguments, escapeFrom, escapeTo, diagnostics);
default:
throw ExceptionUtilities.UnexpectedValue($"{expr.Kind} expression of {expr.Type} type");
#region "cannot produce ref-like values"
// case BoundKind.ThrowExpression:
// case BoundKind.PointerIndirectionOperator:
// case BoundKind.PointerElementAccess:
// case BoundKind.ArgListOperator:
// case BoundKind.ArgList:
// case BoundKind.RefTypeOperator:
// case BoundKind.AddressOfOperator:
// case BoundKind.AsOperator:
// case BoundKind.TypeOfOperator:
// case BoundKind.ArrayAccess:
// case BoundKind.NullCoalescingOperator:
// case BoundKind.AwaitExpression:
// case BoundKind.IsOperator:
// case BoundKind.SizeOfOperator:
// case BoundKind.DynamicMemberAccess:
// case BoundKind.DynamicInvocation:
// case BoundKind.NewT:
// case BoundKind.DelegateCreationExpression:
// case BoundKind.ArrayCreation:
// case BoundKind.AnonymousObjectCreationExpression:
// case BoundKind.NameOfOperator:
// case BoundKind.InterpolatedString:
// case BoundKind.StringInsert:
// case BoundKind.DynamicIndexerAccess:
// case BoundKind.Lambda:
// case BoundKind.DynamicObjectCreationExpression:
// case BoundKind.NoPiaObjectCreationExpression:
// case BoundKind.BaseReference:
// case BoundKind.Literal:
// case BoundKind.ConditionalAccess:
// case BoundKind.IsPatternExpression:
// case BoundKind.DeconstructionAssignmentOperator:
// case BoundKind.EventAccess:
#endregion
#region "not expression that can produce a value"
// case BoundKind.FieldEqualsValue:
// case BoundKind.PropertyEqualsValue:
// case BoundKind.ParameterEqualsValue:
// case BoundKind.NamespaceExpression:
// case BoundKind.TypeExpression:
// case BoundKind.BadStatement:
// case BoundKind.MethodDefIndex:
// case BoundKind.SourceDocumentIndex:
// case BoundKind.ArgList:
// case BoundKind.ArgListOperator:
// case BoundKind.Block:
// case BoundKind.Scope:
// case BoundKind.NoOpStatement:
// case BoundKind.ReturnStatement:
// case BoundKind.YieldReturnStatement:
// case BoundKind.YieldBreakStatement:
// case BoundKind.ThrowStatement:
// case BoundKind.ExpressionStatement:
// case BoundKind.SwitchStatement:
// case BoundKind.SwitchSection:
// case BoundKind.SwitchLabel:
// case BoundKind.BreakStatement:
// case BoundKind.LocalFunctionStatement:
// case BoundKind.ContinueStatement:
// case BoundKind.PatternSwitchStatement:
// case BoundKind.PatternSwitchSection:
// case BoundKind.PatternSwitchLabel:
// case BoundKind.IfStatement:
// case BoundKind.DoStatement:
// case BoundKind.WhileStatement:
// case BoundKind.ForStatement:
// case BoundKind.ForEachStatement:
// case BoundKind.ForEachDeconstructStep:
// case BoundKind.UsingStatement:
// case BoundKind.FixedStatement:
// case BoundKind.LockStatement:
// case BoundKind.TryStatement:
// case BoundKind.CatchBlock:
// case BoundKind.LabelStatement:
// case BoundKind.GotoStatement:
// case BoundKind.LabeledStatement:
// case BoundKind.Label:
// case BoundKind.StatementList:
// case BoundKind.ConditionalGoto:
// case BoundKind.LocalDeclaration:
// case BoundKind.MultipleLocalDeclarations:
// case BoundKind.ArrayInitialization:
// case BoundKind.AnonymousPropertyDeclaration:
// case BoundKind.MethodGroup:
// case BoundKind.PropertyGroup:
// case BoundKind.EventAssignmentOperator:
// case BoundKind.Attribute:
// case BoundKind.FixedLocalCollectionInitializer:
// case BoundKind.DynamicObjectInitializerMember:
// case BoundKind.DynamicCollectionElementInitializer:
// case BoundKind.ImplicitReceiver:
// case BoundKind.FieldInitializer:
// case BoundKind.GlobalStatementInitializer:
// case BoundKind.TypeOrInstanceInitializers:
// case BoundKind.DeclarationPattern:
// case BoundKind.ConstantPattern:
// case BoundKind.WildcardPattern:
#endregion
#region "not found as an operand in no-error unlowered bound tree"
// case BoundKind.MaximumMethodDefIndex:
// case BoundKind.InstrumentationPayloadRoot:
// case BoundKind.ModuleVersionId:
// case BoundKind.ModuleVersionIdString:
// case BoundKind.Dup:
// case BoundKind.TypeOrValueExpression:
// case BoundKind.BadExpression:
// case BoundKind.ArrayLength:
// case BoundKind.MethodInfo:
// case BoundKind.FieldInfo:
// case BoundKind.SequencePoint:
// case BoundKind.SequencePointExpression:
// case BoundKind.SequencePointWithSpan:
// case BoundKind.StateMachineScope:
// case BoundKind.ConditionalReceiver:
// case BoundKind.ComplexConditionalReceiver:
// case BoundKind.PreviousSubmissionReference:
// case BoundKind.HostObjectMemberReference:
// case BoundKind.UnboundLambda:
// case BoundKind.LoweredConditionalAccess:
// case BoundKind.Sequence:
// case BoundKind.HoistedFieldAccess:
// case BoundKind.OutVariablePendingInference:
// case BoundKind.DeconstructionVariablePendingInference:
// case BoundKind.OutDeconstructVarPendingInference:
// case BoundKind.PseudoVariable:
#endregion
}
}
private static bool CheckTupleValEscape(ImmutableArray elements, uint escapeFrom, uint escapeTo, DiagnosticBag diagnostics)
{
foreach (var element in elements)
{
if (!CheckValEscape(element.Syntax, element, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{
return false;
}
}
return true;
}
private static bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint escapeFrom, uint escapeTo, DiagnosticBag diagnostics)
{
foreach (var expression in initExpr.Initializers)
{
if (expression.Kind == BoundKind.AssignmentOperator)
{
var assignment = (BoundAssignmentOperator)expression;
if (!CheckValEscape(expression.Syntax, assignment.Right, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{
return false;
}
var left = (BoundObjectInitializerMember)assignment.Left;
if (!CheckValEscape(left.Arguments, escapeFrom, escapeTo, diagnostics: diagnostics))
{
return false;
}
}
else
{
if (!CheckValEscape(expression.Syntax, expression, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{
return false;
}
}
}
return true;
}
private static bool CheckValEscape(ImmutableArray expressions, uint escapeFrom, uint escapeTo, DiagnosticBag diagnostics)
{
foreach (var expression in expressions)
{
if (!CheckValEscape(expression.Syntax, expression, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{
return false;
}
}
return true;
}
}
}