diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
index c4f1e0c15f0f4..c2d0eba493a98 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
@@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -19,6 +20,145 @@ namespace Microsoft.CodeAnalysis.CSharp
{
internal partial class Binder
{
+#nullable enable
+
+ private enum EscapeLevel : uint
+ {
+ CallingMethod = Binder.CallingMethodScope,
+ ReturnOnly = Binder.ReturnOnlyScope,
+ }
+
+ ///
+ /// The destination in a method arguments must match (MAMM) check. This is
+ /// created primarily for ref and out arguments of a ref struct. It also applies
+ /// to function pointer this and arglist arguments.
+ ///
+ private readonly struct MixableDestination
+ {
+ internal BoundExpression Argument { get; }
+
+ ///
+ /// In the case this is the argument for a ref / out parameter this will refer
+ /// to the corresponding parameter. This will be null in cases like arguments
+ /// passed to an arglist.
+ ///
+ internal ParameterSymbol? Parameter { get; }
+
+ ///
+ /// This destination can only be written to by arguments that have an equal or
+ /// wider escape level. An destination that is
+ /// can never be written to by an argument that has a level of .
+ ///
+ internal EscapeLevel EscapeLevel { get; }
+
+ internal MixableDestination(ParameterSymbol parameter, BoundExpression argument)
+ {
+ Debug.Assert(parameter.RefKind.IsWritableReference() && parameter.Type.IsRefLikeType);
+ Debug.Assert(GetParameterValEscapeLevel(parameter).HasValue);
+ Argument = argument;
+ Parameter = parameter;
+ EscapeLevel = GetParameterValEscapeLevel(parameter)!.Value;
+ }
+
+ internal MixableDestination(BoundExpression argument, EscapeLevel escapeLevel)
+ {
+ Argument = argument;
+ Parameter = null;
+ EscapeLevel = escapeLevel;
+ }
+
+ internal bool IsAssignableFrom(EscapeLevel level) => EscapeLevel switch
+ {
+ EscapeLevel.CallingMethod => level == EscapeLevel.CallingMethod,
+ EscapeLevel.ReturnOnly => true,
+ _ => throw ExceptionUtilities.UnexpectedValue(EscapeLevel)
+ };
+
+ public override string? ToString() => (Parameter, Argument, EscapeLevel).ToString();
+ }
+
+ ///
+ /// Represents an argument being analyzed for escape analysis purposes. This represents the
+ /// argument as written. For example a `ref x` will only be represented by a single
+ /// .
+ ///
+ private readonly struct EscapeArgument
+ {
+ ///
+ /// This will be null in cases like arglist or a function pointer receiver.
+ ///
+ internal ParameterSymbol? Parameter { get; }
+
+ internal BoundExpression Argument { get; }
+
+ internal RefKind RefKind { get; }
+
+ internal EscapeArgument(ParameterSymbol? parameter, BoundExpression argument, RefKind refKind, bool isArgList = false)
+ {
+ Debug.Assert(!isArgList || parameter is null);
+ Argument = argument;
+ Parameter = parameter;
+ RefKind = refKind;
+ }
+
+ public void Deconstruct(out ParameterSymbol? parameter, out BoundExpression argument, out RefKind refKind)
+ {
+ parameter = Parameter;
+ argument = Argument;
+ refKind = RefKind;
+ }
+
+ public override string? ToString() => Parameter is { } p
+ ? p.ToString()
+ : Argument.ToString();
+ }
+
+ ///
+ /// Represents a value being analyzed for escape analysis purposes. This represents the value
+ /// as it contributes to escape analysis which means arguments can show up multiple times. For
+ /// example `ref x` will be represented as both a val and ref escape.
+ ///
+ private readonly struct EscapeValue
+ {
+ ///
+ /// This will be null in cases like arglist or a function pointer receiver.
+ ///
+ internal ParameterSymbol? Parameter { get; }
+
+ internal BoundExpression Argument { get; }
+
+ ///
+ /// This is _only_ useful when calculating MAMM as it dictates to what level the value
+ /// escaped to. That allows it to be filtered against the parameters it could possibly
+ /// write to.
+ ///
+ internal EscapeLevel EscapeLevel { get; }
+
+ internal bool IsRefEscape { get; }
+
+ internal EscapeValue(ParameterSymbol? parameter, BoundExpression argument, EscapeLevel escapeLevel, bool isRefEscape)
+ {
+ Argument = argument;
+ Parameter = parameter;
+ EscapeLevel = escapeLevel;
+ IsRefEscape = isRefEscape;
+ }
+
+ public void Deconstruct(out ParameterSymbol? parameter, out BoundExpression argument, out EscapeLevel escapeLevel, out bool isRefEscape)
+ {
+ parameter = Parameter;
+ argument = Argument;
+ escapeLevel = EscapeLevel;
+ isRefEscape = IsRefEscape;
+ }
+
+ public override string? ToString() => Parameter is { } p
+ ? p.ToString()
+ : Argument.ToString();
+ }
+
+#nullable disable
+
///
/// 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.
@@ -782,39 +922,47 @@ private bool CheckParameterValueKind(SyntaxNode node, BoundParameter parameter,
return true;
}
- private uint GetParameterValEscape(ParameterSymbol parameter)
+ private static EscapeLevel? EscapeLevelFromScope(uint scope) => scope switch
{
- if (UseUpdatedEscapeRules)
- {
- return parameter.EffectiveScope == DeclarationScope.ValueScoped ?
- Binder.CurrentMethodScope :
- Binder.CallingMethodScope;
- }
- else
+ Binder.ReturnOnlyScope => EscapeLevel.ReturnOnly,
+ Binder.CallingMethodScope => EscapeLevel.CallingMethod,
+ _ => null,
+ };
+
+ private static uint GetParameterValEscape(ParameterSymbol parameter)
+ {
+ return parameter switch
{
- return Binder.CallingMethodScope;
- }
+ { EffectiveScope: DeclarationScope.ValueScoped } => Binder.CurrentMethodScope,
+ { RefKind: RefKind.Out, UseUpdatedEscapeRules: true } => Binder.ReturnOnlyScope,
+ _ => Binder.CallingMethodScope
+ };
}
- private uint GetParameterRefEscape(ParameterSymbol parameter)
+ private static EscapeLevel? GetParameterValEscapeLevel(ParameterSymbol parameter) =>
+ EscapeLevelFromScope(GetParameterValEscape(parameter));
+
+ private static uint GetParameterRefEscape(ParameterSymbol parameter)
{
return parameter switch
{
- { RefKind: RefKind.None } or { EffectiveScope: not DeclarationScope.Unscoped } => Binder.CurrentMethodScope,
- { Type.IsRefLikeType: true } => Binder.ReturnOnlyScope,
- _ => Binder.CallingMethodScope
+ { RefKind: RefKind.None } => Binder.CurrentMethodScope,
+ { EffectiveScope: DeclarationScope.RefScoped } => Binder.CurrentMethodScope,
+ _ => Binder.ReturnOnlyScope
};
}
- private bool CheckParameterValEscape(SyntaxNode node, BoundParameter parameter, uint escapeTo, BindingDiagnosticBag diagnostics)
+ private static EscapeLevel? GetParameterRefEscapeLevel(ParameterSymbol parameter) =>
+ EscapeLevelFromScope(GetParameterRefEscape(parameter));
+
+ private bool CheckParameterValEscape(SyntaxNode node, ParameterSymbol parameter, uint escapeTo, BindingDiagnosticBag diagnostics)
{
Debug.Assert(escapeTo is Binder.CallingMethodScope or Binder.ReturnOnlyScope);
if (UseUpdatedEscapeRules)
{
- var parameterSymbol = parameter.ParameterSymbol;
- if (GetParameterValEscape(parameterSymbol) > escapeTo)
+ if (GetParameterValEscape(parameter) > escapeTo)
{
- Error(diagnostics, this.InUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, parameterSymbol);
+ Error(diagnostics, this.InUnsafeRegion ? ErrorCode.WRN_EscapeVariable : ErrorCode.ERR_EscapeVariable, node, parameter);
return this.InUnsafeRegion;
}
return true;
@@ -1503,7 +1651,7 @@ bool isRefEscape
//by default it is safe to escape
uint escapeScope = Binder.CallingMethodScope;
- var argsAndParams = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind)>.GetInstance();
+ var escapeArguments = ArrayBuilder.GetInstance();
GetInvocationArgumentsForEscape(
symbol,
receiver: null, // receiver handled explicitly below
@@ -1514,11 +1662,12 @@ bool isRefEscape
// ref kinds of varargs are not interesting here.
// __refvalue is not ref-returnable, so ref varargs can't come back from a call
ignoreArglistRefKinds: true,
- argsAndParams);
+ mixableArguments: null,
+ escapeArguments);
try
{
- foreach (var (parameter, argument, effectiveRefKind) in argsAndParams)
+ foreach (var (parameter, argument, effectiveRefKind) in escapeArguments)
{
// ref escape scope is the narrowest of
// - ref escape of all byref arguments
@@ -1542,7 +1691,7 @@ bool isRefEscape
}
finally
{
- argsAndParams.Free();
+ escapeArguments.Free();
}
// check receiver if ref-like
@@ -1567,7 +1716,7 @@ private uint GetInvocationEscapeWithUpdatedRules(
//by default it is safe to escape
uint escapeScope = Binder.CallingMethodScope;
- var argsAndParamsAll = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, bool IsRefEscape)>.GetInstance();
+ var argsAndParamsAll = ArrayBuilder.GetInstance();
GetFilteredInvocationArgumentsForEscapeWithUpdatedRules(
symbol,
receiver,
@@ -1576,7 +1725,6 @@ private uint GetInvocationEscapeWithUpdatedRules(
argRefKindsOpt,
argsToParamsOpt,
isRefEscape,
- checkingMethodArgumentsMustMatch: false,
ignoreArglistRefKinds: true, // https://github.com/dotnet/roslyn/issues/63325: for compatibility with C#10 implementation.
argsAndParamsAll);
foreach (var argAndParam in argsAndParamsAll)
@@ -1642,7 +1790,7 @@ bool isRefEscape
receiver = null;
}
- var argsAndParams = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind)>.GetInstance();
+ var escapeArguments = ArrayBuilder.GetInstance();
GetInvocationArgumentsForEscape(
symbol,
receiver: null, // receiver handled explicitly below
@@ -1653,11 +1801,12 @@ bool isRefEscape
// ref kinds of varargs are not interesting here.
// __refvalue is not ref-returnable, so ref varargs can't come back from a call
ignoreArglistRefKinds: true,
- argsAndParams);
+ mixableArguments: null,
+ escapeArguments);
try
{
- foreach (var (parameter, argument, effectiveRefKind) in argsAndParams)
+ foreach (var (parameter, argument, effectiveRefKind) in escapeArguments)
{
// ref escape scope is the narrowest of
// - ref escape of all byref arguments
@@ -1679,7 +1828,7 @@ bool isRefEscape
}
finally
{
- argsAndParams.Free();
+ escapeArguments.Free();
}
// check receiver if ref-like
@@ -1707,7 +1856,7 @@ private bool CheckInvocationEscapeWithUpdatedRules(
{
bool result = true;
- var argsAndParamsAll = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, bool IsRefEscape)>.GetInstance();
+ var argsAndParamsAll = ArrayBuilder.GetInstance();
GetFilteredInvocationArgumentsForEscapeWithUpdatedRules(
symbol,
receiver,
@@ -1716,7 +1865,6 @@ private bool CheckInvocationEscapeWithUpdatedRules(
argRefKindsOpt,
argsToParamsOpt,
isRefEscape,
- checkingMethodArgumentsMustMatch: false,
ignoreArglistRefKinds: true, // https://github.com/dotnet/roslyn/issues/63325: for compatibility with C#10 implementation.
argsAndParamsAll);
foreach (var argAndParam in argsAndParamsAll)
@@ -1759,11 +1907,18 @@ private static void GetInvocationArgumentsForEscape(
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
bool ignoreArglistRefKinds,
- ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind)> argsAndParams)
+ ArrayBuilder? mixableArguments,
+ ArrayBuilder escapeArguments)
{
if (receiver is { })
{
- argsAndParams.Add(getReceiver(symbol, receiver));
+ var tuple = getReceiver(symbol, receiver);
+ escapeArguments.Add(tuple);
+
+ if (mixableArguments is not null && isMixableParameter(tuple.Parameter))
+ {
+ mixableArguments.Add(new MixableDestination(tuple.Parameter, receiver));
+ }
}
if (!argsOpt.IsDefault)
@@ -1779,7 +1934,8 @@ private static void GetInvocationArgumentsForEscape(
getArgList(
argList.Arguments,
ignoreArglistRefKinds ? default : argList.ArgumentRefKindsOpt,
- argsAndParams);
+ mixableArguments,
+ escapeArguments);
break;
}
@@ -1787,6 +1943,11 @@ private static void GetInvocationArgumentsForEscape(
parameters[argsToParamsOpt.IsDefault ? argIndex : argsToParamsOpt[argIndex]] :
null;
+ if (mixableArguments is not null && isMixableParameter(parameter))
+ {
+ mixableArguments.Add(new MixableDestination(parameter, argument));
+ }
+
var refKind = parameter?.RefKind ?? RefKind.None;
if (!argRefKindsOpt.IsDefault)
{
@@ -1798,15 +1959,20 @@ private static void GetInvocationArgumentsForEscape(
refKind = RefKind.In;
}
- argsAndParams.Add((parameter, argument, refKind));
+ escapeArguments.Add(new EscapeArgument(parameter, argument, refKind));
}
}
- static (ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind) getReceiver(Symbol symbol, BoundExpression receiver)
+ static bool isMixableParameter([NotNullWhen(true)] ParameterSymbol? parameter) =>
+ parameter is not null &&
+ parameter.Type.IsRefLikeType &&
+ parameter.RefKind.IsWritableReference();
+
+ static EscapeArgument getReceiver(Symbol symbol, BoundExpression receiver)
{
if (symbol is FunctionPointerMethodSymbol)
{
- return (null, receiver, RefKind.None);
+ return new EscapeArgument(parameter: null, receiver, RefKind.None);
}
var method = symbol switch
{
@@ -1824,19 +1990,25 @@ private static void GetInvocationArgumentsForEscape(
refKind = thisParameter.RefKind;
}
- return (thisParameter, receiver, refKind);
+ return new EscapeArgument(thisParameter, receiver, refKind);
}
static void getArgList(
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
- ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind)> argsAndParams)
+ ArrayBuilder? mixableArguments,
+ ArrayBuilder escapeArguments)
{
for (int argIndex = 0; argIndex < argsOpt.Length; argIndex++)
{
var argument = argsOpt[argIndex];
var refKind = argRefKindsOpt.IsDefault ? RefKind.None : argRefKindsOpt[argIndex];
- argsAndParams.Add((null, argument, refKind));
+ escapeArguments.Add(new EscapeArgument(parameter: null, argument, refKind, isArgList: true));
+
+ if (refKind == RefKind.Ref && mixableArguments is not null)
+ {
+ mixableArguments.Add(new MixableDestination(argument, EscapeLevel.CallingMethod));
+ }
}
}
}
@@ -1854,19 +2026,34 @@ private void GetFilteredInvocationArgumentsForEscapeWithUpdatedRules(
ImmutableArray argsOpt,
ImmutableArray argRefKindsOpt,
ImmutableArray argsToParamsOpt,
- bool isRefEscape,
- bool checkingMethodArgumentsMustMatch,
+ bool isInvokedWithRef,
bool ignoreArglistRefKinds,
- ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, bool IsRefEscape)> result)
+ ArrayBuilder escapeValues)
{
- if (!symbol.RequiresInstanceReceiver())
+ // This code is attempting to implement the following portion of the spec. Essentially if we're not
+ // either invoking a method by ref or have a ref struct return then there is no need to consider the
+ // argument escape scopes when calculating the return escape scope.
+ //
+ // > A value resulting from a method invocation `e1.M(e2, ...)` is *safe-to-escape* from the narrowest of the following scopes:
+ // > 1. The *calling method*
+ // > 2. When the return is a `ref struct` the *safe-to-escape* contributed by all argument expressions
+ // > 3. When the return is a `ref struct` the *ref-safe-to-escape* contributed by all `ref` arguments
+ //
+ // The `ref` calling rules can be simplified to:
+ //
+ // > A value resulting from a method invocation `ref e1.M(e2, ...)` is *ref-safe-to-escape* the narrowest of the following scopes:
+ // > 1. The *calling method*
+ // > 2. The *safe-to-escape* contributed by all argument expressions
+ // > 3. The *ref-safe-to-escape* contributed by all `ref` arguments
+
+ // If we're not invoking with ref or returning a ref struct then the spec does not consider
+ // any arguments hence the filter is always empty.
+ if (!isInvokedWithRef && !hasRefLikeReturn(symbol))
{
- // ignore receiver when symbol is static
- receiver = null;
+ return;
}
- var argsAndParams = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind)>.GetInstance();
- GetInvocationArgumentsForEscape(
+ GetEscapeValuesForUpdatedRules(
symbol,
receiver,
parameters,
@@ -1874,73 +2061,101 @@ private void GetFilteredInvocationArgumentsForEscapeWithUpdatedRules(
argRefKindsOpt,
argsToParamsOpt,
ignoreArglistRefKinds,
- argsAndParams);
+ mixableArguments: null,
+ escapeValues);
- // https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#rules-method-invocation
- //
- // A value resulting from a method invocation `e1.M(e2, ...)` is *safe-to-escape* from the smallest of the following scopes:
- // 1. The *calling method*
- // 2. The *safe-to-escape* contributed by all argument expressions
- // 3. ...
- //
- // A value resulting from a method invocation `ref e1.M(e2, ...)` is *ref-safe-to-escape* the smallest of the following scopes:
- // 1. The *safe-to-escape* of the rvalue of `e1.M(e2, ...)`
- // 2. ...
- foreach (var (parameter, argument, _) in argsAndParams)
- {
- // For a given argument `a` that is passed to parameter `p`:
- // 1. ...
- // 2. If `p` is `scoped` then `a` does not contribute *safe-to-escape* when considering arguments.
- if (parameter?.EffectiveScope == DeclarationScope.ValueScoped)
+ static bool hasRefLikeReturn(Symbol symbol)
+ {
+ switch (symbol)
{
- continue;
+ case MethodSymbol method:
+ if (method.MethodKind == MethodKind.Constructor)
+ {
+ return method.ContainingType.IsRefLikeType;
+ }
+
+ return method.ReturnType.IsRefLikeType;
+ case PropertySymbol property:
+ return property.Type.IsRefLikeType;
+ default:
+ return false;
}
- result.Add((parameter, argument, IsRefEscape: false));
}
+ }
- // A value resulting from a method invocation `e1.M(e2, ...)` is *safe-to-escape* from the smallest of the following scopes:
- // 1. ...
- // 2. ...
- // 3. When the return is a `ref struct` then *ref-safe-to-escape* contributed by all `ref` arguments
- //
- // A value resulting from a method invocation `ref e1.M(e2, ...)` is *ref-safe-to-escape* the smallest of the following scopes:
- // 1. ...
- // 2. The *ref-safe-to-escape* contributed by all `ref` arguments
- if (isRefEscape || checkingMethodArgumentsMustMatch || hasRefStructType(symbol))
+ ///
+ /// Returns the set of to an invocation that impact ref analysis.
+ /// This will filter out everything that could never meaningfully contribute to ref analysis. For
+ /// example:
+ /// - For ref arguments it will return an for both ref and
+ /// value escape (if appropriate based on scoped-ness of associated parameters).
+ /// - It will remove value escape for args which correspond to scoped parameters.
+ /// - It will remove value escape for non-ref struct.
+ /// - It will remove ref escape for args which correspond to scoped refs.
+ /// Optionally this will also return all of the that
+ /// result from this invocation. That is useful for MAMM analysis.
+ ///
+ private void GetEscapeValuesForUpdatedRules(
+ Symbol symbol,
+ BoundExpression? receiver,
+ ImmutableArray parameters,
+ ImmutableArray argsOpt,
+ ImmutableArray argRefKindsOpt,
+ ImmutableArray argsToParamsOpt,
+ bool ignoreArglistRefKinds,
+ ArrayBuilder? mixableArguments,
+ ArrayBuilder escapeValues)
+ {
+ if (!symbol.RequiresInstanceReceiver())
+ {
+ // ignore receiver when symbol is static
+ receiver = null;
+ }
+
+ var escapeArguments = ArrayBuilder.GetInstance();
+ GetInvocationArgumentsForEscape(
+ symbol,
+ receiver,
+ parameters,
+ argsOpt,
+ argRefKindsOpt,
+ argsToParamsOpt,
+ ignoreArglistRefKinds,
+ mixableArguments,
+ escapeArguments);
+
+ foreach (var (parameter, argument, refKind) in escapeArguments)
{
- foreach (var (parameter, argument, effectiveRefKind) in argsAndParams)
+ // This means it's part of an __arglist or function pointer receiver.
+ if (parameter is null)
{
- if (effectiveRefKind == RefKind.None)
+ if (refKind != RefKind.None)
{
- continue;
+ escapeValues.Add(new EscapeValue(parameter: null, argument, EscapeLevel.ReturnOnly, isRefEscape: true));
}
- // For a given argument `a` that is passed to parameter `p`:
- // 1. If `p` is `scoped ref` then `a` does not contribute *ref-safe-to-escape* when considering arguments.
- // 2. ...
- if (parameter?.EffectiveScope == DeclarationScope.RefScoped || (checkingMethodArgumentsMustMatch && parameter?.Type.IsRefLikeType == true))
+
+ if (argument.Type?.IsRefLikeType == true)
{
- continue;
+ escapeValues.Add(new EscapeValue(parameter: null, argument, EscapeLevel.CallingMethod, isRefEscape: false));
}
- result.Add((parameter, argument, IsRefEscape: true));
+
+ continue;
}
- }
- argsAndParams.Free();
+ if (parameter.Type.IsRefLikeType && GetParameterValEscapeLevel(parameter) is { } valEscapeLevel)
+ {
+ escapeValues.Add(new EscapeValue(parameter, argument, valEscapeLevel, isRefEscape: false));
+ }
- static bool hasRefStructType(Symbol symbol)
- {
- switch (symbol)
+ // It's important to check values then references. Flipping will change the set of errors
+ // produced by MAMM because of the CheckRefEscape / CheckValEscape calls.
+ if (parameter.RefKind != RefKind.None && GetParameterRefEscapeLevel(parameter) is { } refEscapeLevel)
{
- case MethodSymbol method:
- return method.MethodKind == MethodKind.Constructor ?
- method.ContainingType.IsRefLikeType :
- method.ReturnType.IsRefLikeType;
- case PropertySymbol property:
- return property.Type.IsRefLikeType;
- default:
- return false;
+ escapeValues.Add(new EscapeValue(parameter, argument, refEscapeLevel, isRefEscape: true));
}
}
+
+ escapeArguments.Free();
}
private static string GetInvocationParameterName(ParameterSymbol? parameter)
@@ -2021,7 +2236,7 @@ private bool CheckInvocationArgMixing(
escapeTo = GetValEscape(receiverOpt, scopeOfTheContainingExpression);
}
- var argsAndParams = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind)>.GetInstance();
+ var escapeArguments = ArrayBuilder.GetInstance();
GetInvocationArgumentsForEscape(
symbol,
receiver: null, // receiver handled explicitly below
@@ -2030,11 +2245,12 @@ private bool CheckInvocationArgMixing(
argRefKindsOpt: default,
argsToParamsOpt,
ignoreArglistRefKinds: false,
- argsAndParams);
+ mixableArguments: null,
+ escapeArguments);
try
{
- foreach (var (_, argument, refKind) in argsAndParams)
+ foreach (var (_, argument, refKind) in escapeArguments)
{
if (refKind.IsWritableReference() && argument.Type?.IsRefLikeType == true)
{
@@ -2048,7 +2264,7 @@ private bool CheckInvocationArgMixing(
return true;
}
- foreach (var (parameter, argument, _) in argsAndParams)
+ foreach (var (parameter, argument, _) in escapeArguments)
{
var valid = CheckValEscape(argument.Syntax, argument, scopeOfTheContainingExpression, escapeTo, false, diagnostics);
@@ -2062,7 +2278,7 @@ private bool CheckInvocationArgMixing(
}
finally
{
- argsAndParams.Free();
+ escapeArguments.Free();
}
// check val escape of receiver if ref-like
@@ -2086,103 +2302,59 @@ private bool CheckInvocationArgMixingWithUpdatedRules(
uint scopeOfTheContainingExpression,
BindingDiagnosticBag diagnostics)
{
- // https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#rules-method-invocation
- //
- // For any method invocation `e.M(a1, a2, ... aN)`
- // 1. Calculate the *safe-to-escape* of the method return.
- // - Ignore the *ref-safe-to-escape* of arguments to `in / ref / out` parameters of `ref struct` types. The corresponding parameters *ref-safe-to-escape* are at most *return only* and hence cannot be returned via `ref` or `out` parameters.
- // - Assume the method has a `ref struct` return type.
- // 2. All `ref` or `out` arguments of `ref struct` types must be assignable by a value with that *safe-to-escape*.
- // This applies even when the `ref` argument matches a `scoped ref` parameter.
-
- if (!symbol.RequiresInstanceReceiver())
- {
- // ignore receiver when symbol is static
- receiverOpt = null;
- }
-
- // 1. Calculate the *safe-to-escape* of the method return.
- // - Ignore the *ref-safe-to-escape* of arguments to `in / ref / out` parameters of `ref struct` types. The corresponding parameters *ref-safe-to-escape* are at most *return only* and hence cannot be returned via `ref` or `out` parameters.
- // - Assume the method has a `ref struct` return type.
- uint escapeScope = Binder.CallingMethodScope;
- (ParameterSymbol? Parameter, BoundExpression Argument, bool IsRefEscape)? argAndParamToEscape = null;
- var argsAndParamsAll = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, bool IsRefEscape)>.GetInstance();
- GetFilteredInvocationArgumentsForEscapeWithUpdatedRules(
+ var mixableArguments = ArrayBuilder.GetInstance();
+ var escapeValues = ArrayBuilder.GetInstance();
+ GetEscapeValuesForUpdatedRules(
symbol,
receiverOpt,
parameters,
argsOpt,
argRefKindsOpt,
argsToParamsOpt,
- isRefEscape: false,
- checkingMethodArgumentsMustMatch: true,
ignoreArglistRefKinds: false,
- argsAndParamsAll);
- foreach (var argAndParam in argsAndParamsAll)
- {
- var argument = argAndParam.Argument;
- uint argEscape = argAndParam.IsRefEscape ?
- GetRefEscape(argument, scopeOfTheContainingExpression) :
- GetValEscape(argument, scopeOfTheContainingExpression);
-
- if (argEscape > escapeScope)
- {
- escapeScope = argEscape;
- argAndParamToEscape = argAndParam;
- }
- }
- argsAndParamsAll.Free();
+ mixableArguments,
+ escapeValues);
- if (argAndParamToEscape is null)
+ var valid = true;
+ foreach (var mixableArg in mixableArguments)
{
- return true;
- }
-
- var argsAndParams = ArrayBuilder<(ParameterSymbol? Parameter, BoundExpression Argument, RefKind RefKind)>.GetInstance();
- GetInvocationArgumentsForEscape(
- symbol,
- receiverOpt,
- parameters,
- argsOpt,
- argRefKindsOpt: default,
- argsToParamsOpt,
- ignoreArglistRefKinds: false,
- argsAndParams);
-
- // 2. All `ref` or `out` arguments of `ref struct` types must be assignable by a value with that *safe-to-escape*.
- // This applies even when the `ref` argument matches a `scoped ref` parameter.
- bool result = true;
- foreach (var (_, argument, refKind) in argsAndParams)
- {
- if (!refKind.IsWritableReference() || argument.Type?.IsRefLikeType != true)
+ var toArgEscape = GetValEscape(mixableArg.Argument, scopeOfTheContainingExpression);
+ foreach (var (fromParameter, fromArg, escapeKind, isRefEscape) in escapeValues)
{
- continue;
- }
+ if (mixableArg.Parameter is not null && object.ReferenceEquals(mixableArg.Parameter, fromParameter))
+ {
+ continue;
+ }
- uint escapeTo = GetValEscape(argument, scopeOfTheContainingExpression);
- var (parameterToEscape, argToEscape, isRefEscape) = argAndParamToEscape.GetValueOrDefault();
+ // This checks to see if the EscapeValue could ever be assigned to this argument based
+ // on comparing the EscapeLevel of both. If this could never be assigned due to
+ // this then we don't need to consider it for MAMM analysis.
+ if (!mixableArg.IsAssignableFrom(escapeKind))
+ {
+ continue;
+ }
- bool valid = isRefEscape ?
- CheckRefEscape(argToEscape.Syntax, argToEscape, scopeOfTheContainingExpression, escapeTo, false, diagnostics) :
- CheckValEscape(argToEscape.Syntax, argToEscape, scopeOfTheContainingExpression, escapeTo, false, diagnostics);
+ valid = isRefEscape
+ ? CheckRefEscape(fromArg.Syntax, fromArg, scopeOfTheContainingExpression, toArgEscape, checkingReceiver: false, diagnostics)
+ : CheckValEscape(fromArg.Syntax, fromArg, scopeOfTheContainingExpression, toArgEscape, checkingReceiver: false, diagnostics);
- if (!valid)
- {
- // For consistency with C#10 implementation, we don't report ERR_CallArgMixing
- // for the receiver. (In both implementations, the call to CheckValEscape() above
- // will have reported a specific escape error for the receiver though.)
- if ((object)argToEscape != receiverOpt)
+ if (!valid)
{
- string parameterName = GetInvocationParameterName(parameterToEscape);
+ string parameterName = GetInvocationParameterName(fromParameter);
Error(diagnostics, ErrorCode.ERR_CallArgMixing, syntax, symbol, parameterName);
+ break;
}
- result = false;
+ }
+
+ if (!valid)
+ {
break;
}
}
- argsAndParams.Free();
- return result;
+ mixableArguments.Free();
+ escapeValues.Free();
+ return valid;
}
private static bool IsReceiverRefReadOnly(Symbol methodOrPropertySymbol) => methodOrPropertySymbol switch
@@ -3046,9 +3218,12 @@ internal uint GetValEscape(BoundExpression expr, uint scopeOfTheContainingExpres
// otherwise default to ExternalScope (ordinary values)
switch (expr.Kind)
{
+ case BoundKind.ThisReference:
+ var thisParam = ((MethodSymbol)this.ContainingMember()).ThisParameter;
+ Debug.Assert(thisParam.Type.Equals(((BoundThisReference)expr).Type, TypeCompareKind.ConsiderEverything));
+ return GetParameterValEscape(thisParam);
case BoundKind.DefaultLiteral:
case BoundKind.DefaultExpression:
- case BoundKind.ThisReference:
case BoundKind.Utf8String:
// always returnable
return Binder.CallingMethodScope;
@@ -3459,15 +3634,19 @@ internal bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeF
switch (expr.Kind)
{
+ case BoundKind.ThisReference:
+ var thisParam = ((MethodSymbol)this.ContainingMember()).ThisParameter;
+ Debug.Assert(thisParam.Type.Equals(((BoundThisReference)expr).Type, TypeCompareKind.ConsiderEverything));
+ return CheckParameterValEscape(node, thisParam, escapeTo, diagnostics);
+
case BoundKind.DefaultLiteral:
case BoundKind.DefaultExpression:
- case BoundKind.ThisReference:
case BoundKind.Utf8String:
// always returnable
return true;
case BoundKind.Parameter:
- return CheckParameterValEscape(node, (BoundParameter)expr, escapeTo, diagnostics);
+ return CheckParameterValEscape(node, ((BoundParameter)expr).ParameterSymbol, escapeTo, diagnostics);
case BoundKind.TupleLiteral:
case BoundKind.ConvertedTupleLiteral:
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs
index 2ef72385eadf3..350d74c1d5b2a 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs
@@ -13817,6 +13817,9 @@ public ref struct S1
comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute, InterpolatedStringHandlerArgumentAttribute }, parseOptions: TestOptions.Regular11, targetFramework: TargetFramework.NetCoreApp);
comp.VerifyDiagnostics(
+ // (10,100): error CS8352: Cannot use variable 'out CustomHandler' in this context because it may expose referenced variables outside of their declaration scope
+ // public CustomHandler(int literalLength, int formattedCount, ref S1 s1) : this() { s1.Handler = this; }
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler").WithLocation(10, 100),
// (15,9): error CS8350: This combination of arguments to 'CustomHandler.M2(ref S1, CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope
// M2(ref s1, $"{s2}");
Diagnostic(ErrorCode.ERR_CallArgMixing, @"M2(ref s1, $""{s2}"")").WithArguments("CustomHandler.M2(ref S1, CustomHandler)", "handler").WithLocation(15, 9),
@@ -13862,6 +13865,9 @@ static void M(ref S s, [InterpolatedStringHandlerArgument(""s"")] CustomHandler
comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute, InterpolatedStringHandlerArgumentAttribute }, parseOptions: TestOptions.Regular11, targetFramework: TargetFramework.NetCoreApp);
comp.VerifyDiagnostics(
+ // (5,97): error CS8352: Cannot use variable 'out CustomHandler' in this context because it may expose referenced variables outside of their declaration scope
+ // public CustomHandler(int literalLength, int formattedCount, ref S s) : this() { s.Handler = this; }
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler").WithLocation(5, 97),
// (17,15): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference
// M(ref s, $"{1}");
Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "s").WithLocation(17, 15),
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs
index 46138bacd21be..3be739ff34b48 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs
@@ -11278,6 +11278,9 @@ public ref struct S1
var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute, InterpolatedStringHandlerArgumentAttribute }, targetFramework: TargetFramework.NetCoreApp);
comp.VerifyDiagnostics(
+ // (10,107): error CS8352: Cannot use variable 'out CustomHandler' in this context because it may expose referenced variables outside of their declaration scope
+ // public CustomHandler(int literalLength, int formattedCount, scoped ref S1 s1) : this() { s1.Handler = this; }
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler").WithLocation(10, 107),
// (15,9): error CS8350: This combination of arguments to 'CustomHandler.M2(ref S1, CustomHandler)' is disallowed because it may expose variables referenced by parameter 'handler' outside of their declaration scope
// M2(ref s1, $"""{s2}""");
Diagnostic(ErrorCode.ERR_CallArgMixing, @"M2(ref s1, $""""""{s2}"""""")").WithArguments("CustomHandler.M2(ref S1, CustomHandler)", "handler").WithLocation(15, 9),
@@ -11312,7 +11315,10 @@ static void M(ref S s, [InterpolatedStringHandlerArgument(""s"")] CustomHandler
}";
var comp = CreateCompilation(new[] { code, InterpolatedStringHandlerAttribute, InterpolatedStringHandlerArgumentAttribute }, targetFramework: TargetFramework.NetCoreApp);
- comp.VerifyDiagnostics();
+ comp.VerifyDiagnostics(
+ // (5,104): error CS8352: Cannot use variable 'out CustomHandler' in this context because it may expose referenced variables outside of their declaration scope
+ // public CustomHandler(int literalLength, int formattedCount, scoped ref S s) : this() { s.Handler = this; }
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "this").WithArguments("out CustomHandler").WithLocation(5, 104));
}
[Theory, WorkItem(54703, "https://github.com/dotnet/roslyn/issues/54703")]
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
index 609d0c623dc40..1e8f361c744dc 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
@@ -1703,15 +1703,8 @@ ref struct S1
Diagnostic(ErrorCode.ERR_CallArgMixing, "MayAssign1(__arglist(ref inner, ref rOuter))").WithArguments("Program.MayAssign1(__arglist)", "__arglist").WithLocation(23, 9)
);
- // Breaking change in C#11: A ref to ref struct argument is considered
- // an unscoped reference when passed to an __arglist.
+ // Same errors modulo the scoped
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
- // (17,34): error CS8168: Cannot return local 'rOuter' by reference because it is not a ref local
- // MayAssign2(__arglist(ref rOuter, ref rOuter));
- Diagnostic(ErrorCode.ERR_RefReturnLocal, "rOuter").WithArguments("rOuter").WithLocation(17, 34),
- // (17,9): error CS8350: This combination of arguments to 'Program.MayAssign2(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // MayAssign2(__arglist(ref rOuter, ref rOuter));
- Diagnostic(ErrorCode.ERR_CallArgMixing, "MayAssign2(__arglist(ref rOuter, ref rOuter))").WithArguments("Program.MayAssign2(__arglist)", "__arglist").WithLocation(17, 9),
// (20,46): error CS8352: Cannot use variable 'rInner' in this context because it may expose referenced variables outside of their declaration scope
// MayAssign2(__arglist(ref rOuter, ref rInner));
Diagnostic(ErrorCode.ERR_EscapeVariable, "rInner").WithArguments("rInner").WithLocation(20, 46),
@@ -2878,13 +2871,13 @@ ref struct S1
";
var comp = CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
comp.VerifyDiagnostics(
- // (16,30): error CS8526: Cannot use variable 'local' in this context because it may expose referenced variables outside of their declaration scope
+ // (16,30): error CS8352: Cannot use variable 'local' in this context because it may expose referenced variables outside of their declaration scope
// sp = MayWrap(ref local);
Diagnostic(ErrorCode.ERR_EscapeVariable, "local").WithArguments("local").WithLocation(16, 30),
- // (16,18): error CS8521: Cannot use a result of 'Program.MayWrap(ref Span)' in this context because it may expose variables referenced by parameter 'arg' outside of their declaration scope
+ // (16,18): error CS8347: Cannot use a result of 'Program.MayWrap(ref Span)' in this context because it may expose variables referenced by parameter 'arg' outside of their declaration scope
// sp = MayWrap(ref local);
Diagnostic(ErrorCode.ERR_EscapeCall, "MayWrap(ref local)").WithArguments("Program.MayWrap(ref System.Span)", "arg").WithLocation(16, 18),
- // (22,20): error CS8526: Cannot use variable 'sp1' in this context because it may expose referenced variables outside of their declaration scope
+ // (22,20): error CS8352: Cannot use variable 'sp1' in this context because it may expose referenced variables outside of their declaration scope
// return sp1;
Diagnostic(ErrorCode.ERR_EscapeVariable, "sp1").WithArguments("sp1").WithLocation(22, 20)
);
@@ -2949,6 +2942,7 @@ public void MemberOfReadonlyRefLikeEscape(LanguageVersion languageVersion)
{
var text = @"
using System;
+ using System.Diagnostics.CodeAnalysis;
public static class Program
{
public static void Main()
@@ -2957,8 +2951,13 @@ public static void Main()
Span value1 = stackalloc int[1];
new SR().TryGet(out value1);
- // error, TryGet can write into the instance
+ // Ok, the new value can be copied into SW but not the
+ // ref to the value
new SW().TryGet(out value1);
+
+ // Error as the ref of this can escape into value2
+ Span value2 = default;
+ new SW().TryGet2(out value2);
}
}
@@ -2976,16 +2975,37 @@ public void TryGet(out Span result)
{
result = default;
}
+
+ [UnscopedRef]
+ public void TryGet2(out Span result)
+ {
+ result = default;
+ }
}
";
- CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
- // (12,33): error CS8526: Cannot use variable 'value1' in this context because it may expose referenced variables outside of their declaration scope
- // new SW().TryGet(out value1);
- Diagnostic(ErrorCode.ERR_EscapeVariable, "value1").WithArguments("value1").WithLocation(12, 33),
- // (12,13): error CS8524: This combination of arguments to 'SW.TryGet(out Span)' is disallowed because it may expose variables referenced by parameter 'result' outside of their declaration scope
- // new SW().TryGet(out value1);
- Diagnostic(ErrorCode.ERR_CallArgMixing, "new SW().TryGet(out value1)").WithArguments("SW.TryGet(out System.Span)", "result").WithLocation(12, 13)
- );
+ if (languageVersion == LanguageVersion.CSharp10)
+ {
+ CreateCompilationWithMscorlibAndSpan(new[] { text, UnscopedRefAttributeDefinition }, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
+ // (14,13): error CS8350: This combination of arguments to 'SW.TryGet(out Span)' is disallowed because it may expose variables referenced by parameter 'result' outside of their declaration scope
+ // new SW().TryGet(out value1);
+ Diagnostic(ErrorCode.ERR_CallArgMixing, "new SW().TryGet(out value1)").WithArguments("SW.TryGet(out System.Span)", "result").WithLocation(14, 13),
+ // (14,33): error CS8352: Cannot use variable 'value1' in this context because it may expose referenced variables outside of their declaration scope
+ // new SW().TryGet(out value1);
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "value1").WithArguments("value1").WithLocation(14, 33)
+ );
+
+ }
+ else
+ {
+ CreateCompilationWithMscorlibAndSpan(new[] { text, UnscopedRefAttributeDefinition }, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
+ // (18,13): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference
+ // new SW().TryGet2(out value2);
+ Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "new SW()").WithLocation(18, 13),
+ // (18,13): error CS8350: This combination of arguments to 'SW.TryGet2(out Span)' is disallowed because it may expose variables referenced by parameter 'this' outside of their declaration scope
+ // new SW().TryGet2(out value2);
+ Diagnostic(ErrorCode.ERR_CallArgMixing, "new SW().TryGet2(out value2)").WithArguments("SW.TryGet2(out System.Span)", "this").WithLocation(18, 13)
+ );
+ }
}
[WorkItem(21911, "https://github.com/dotnet/roslyn/issues/21911")]
@@ -3503,17 +3523,28 @@ public static class Extensions
public static void Deconstruct(ref this Span self, out Span x, out Span y) => throw null;
}
";
- CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
- // (8,9): error CS1510: A ref or out value must be an assignable variable
- // (global, global) = global;
- Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(global, global) = global").WithLocation(8, 9),
- // (8,9): error CS8352: Cannot use variable '(global, global) = global' in this context because it may expose referenced variables outside of their declaration scope
- // (global, global) = global;
- Diagnostic(ErrorCode.ERR_EscapeVariable, "(global, global) = global").WithArguments("(global, global) = global").WithLocation(8, 9),
- // (8,28): error CS8350: This combination of arguments to 'Extensions.Deconstruct(ref Span, out Span, out Span)' is disallowed because it may expose variables referenced by parameter 'x' outside of their declaration scope
- // (global, global) = global;
- Diagnostic(ErrorCode.ERR_CallArgMixing, "global").WithArguments("Extensions.Deconstruct(ref System.Span, out System.Span, out System.Span)", "x").WithLocation(8, 28)
- );
+ if (languageVersion == LanguageVersion.CSharp10)
+ {
+ CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
+ // (8,9): error CS1510: A ref or out value must be an assignable variable
+ // (global, global) = global;
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(global, global) = global").WithLocation(8, 9),
+ // (8,9): error CS8352: Cannot use variable '(global, global) = global' in this context because it may expose referenced variables outside of their declaration scope
+ // (global, global) = global;
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "(global, global) = global").WithArguments("(global, global) = global").WithLocation(8, 9),
+ // (8,28): error CS8350: This combination of arguments to 'Extensions.Deconstruct(ref Span, out Span, out Span)' is disallowed because it may expose variables referenced by parameter 'x' outside of their declaration scope
+ // (global, global) = global;
+ Diagnostic(ErrorCode.ERR_CallArgMixing, "global").WithArguments("Extensions.Deconstruct(ref System.Span, out System.Span, out System.Span)", "x").WithLocation(8, 28)
+ );
+ }
+ else
+ {
+ CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
+ // (8,9): error CS1510: A ref or out value must be an assignable variable
+ // (global, global) = global;
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(global, global) = global").WithLocation(8, 9)
+ );
+ }
}
[Fact]
@@ -3550,10 +3581,7 @@ public static class Extensions
CreateCompilationWithMscorlibAndSpan(text, options: TestOptions.UnsafeDebugDll, parseOptions: TestOptions.Regular11).VerifyDiagnostics(
// (8,9): error CS1510: A ref or out value must be an assignable variable
// (global, global) = global;
- Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(global, global) = global").WithLocation(8, 9),
- // (8,9): warning CS9077: Use of variable '(global, global) = global' in this context may expose referenced variables outside of their declaration scope
- // (global, global) = global;
- Diagnostic(ErrorCode.WRN_EscapeVariable, "(global, global) = global").WithArguments("(global, global) = global").WithLocation(8, 9)
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(global, global) = global").WithLocation(8, 9)
);
}
@@ -4080,11 +4108,25 @@ public ref struct S
";
// Tracking issue: https://github.com/dotnet/roslyn/issues/22361
- CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
- // (9,9): error CS8352: Cannot use variable 'local1' in this context because it may expose referenced variables outside of their declaration scope
- // local1.M(out S local2);
- Diagnostic(ErrorCode.ERR_EscapeVariable, "local1").WithArguments("local1").WithLocation(9, 9)
- );
+ if (languageVersion == LanguageVersion.CSharp10)
+ {
+ CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
+ // (9,9): error CS8352: Cannot use variable 'local1' in this context because it may expose referenced variables outside of their declaration scope
+ // local1.M(out S local2);
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "local1").WithArguments("local1").WithLocation(9, 9)
+ );
+ }
+ else
+ {
+ CreateCompilationWithMscorlibAndSpan(text, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)).VerifyDiagnostics(
+ // (9,9): error CS8352: Cannot use variable 'local1' in this context because it may expose referenced variables outside of their declaration scope
+ // local1.M(out S local2); // we'd want this to succeed, but determine the safe-to-escape scope for local2 based on the invocation that declared it
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "local1").WithArguments("local1").WithLocation(9, 9),
+ // (9,9): error CS8350: This combination of arguments to 'S.M(out S)' is disallowed because it may expose variables referenced by parameter 'this' outside of their declaration scope
+ // local1.M(out S local2); // we'd want this to succeed, but determine the safe-to-escape scope for local2 based on the invocation that declared it
+ Diagnostic(ErrorCode.ERR_CallArgMixing, "local1.M(out S local2)").WithArguments("S.M(out S)", "this").WithLocation(9, 9)
+ );
+ }
}
[Theory]
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
index 22e7a208a720f..420dc442e4148 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs
@@ -68,7 +68,7 @@ static void M1(T t1)
}
static void M2(ref T t2)
{
- S s2;
+ scoped S s2;
s2 = new S(ref t2);
s2 = new S { F1 = t2 };
}
@@ -111,7 +111,10 @@ void M5(S s5)
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "ref T").WithArguments("ref fields", "11.0").WithLocation(3, 12),
// (4,12): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater.
// public ref readonly T F2;
- Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "ref readonly T").WithArguments("ref fields", "11.0").WithLocation(4, 12));
+ Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "ref readonly T").WithArguments("ref fields", "11.0").WithLocation(4, 12),
+ // (25,9): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater.
+ // scoped S s2;
+ Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "scoped").WithArguments("ref fields", "11.0").WithLocation(25, 9));
comp = CreateEmptyCompilation(sourceA, references: new[] { mscorlibRefWithRefFields });
comp.VerifyEmitDiagnostics();
@@ -129,7 +132,7 @@ static void M1(T t)
}
static void M2(ref T t)
{
- S s2;
+ scoped S s2;
s2 = new S(ref t);
s2 = new S { F1 = t };
}
@@ -147,6 +150,9 @@ static void M3(S s)
// (8,25): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater.
// s1 = new S { F1 = t };
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "F1").WithArguments("ref fields", "11.0").WithLocation(8, 25),
+ // (12,9): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater.
+ // scoped S s2;
+ Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "scoped").WithArguments("ref fields", "11.0").WithLocation(12, 9),
// (14,25): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater.
// s2 = new S { F1 = t };
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "F1").WithArguments("ref fields", "11.0").WithLocation(14, 25),
@@ -2107,6 +2113,12 @@ static R F2(R r2)
// (3,5): error CS8936: Feature 'ref fields' is not available in C# 10.0. Please use language version 11.0 or greater.
// ref T F;
Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion10, "ref T").WithArguments("ref fields", "11.0").WithLocation(3, 5),
+ // (7,9): error CS9079: Cannot ref-assign 't' to 'F' because 't' can only escape the current method through a return statement.
+ // F = ref t;
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "F = ref t").WithArguments("F", "t").WithLocation(7, 9),
+ // (12,9): error CS9079: Cannot ref-assign 't' to 'F' because 't' can only escape the current method through a return statement.
+ // r1.F = ref t;
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "r1.F = ref t").WithArguments("F", "t").WithLocation(12, 9),
// (18,9): error CS8374: Cannot ref-assign 't' to 'F' because 't' has a narrower escape scope than 'F'.
// r2.F = ref t;
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "r2.F = ref t").WithArguments("F", "t").WithLocation(18, 9)
@@ -2114,6 +2126,12 @@ static R F2(R r2)
comp = CreateCompilation(source, runtimeFeature: RuntimeFlag.ByRefFields);
comp.VerifyEmitDiagnostics(
+ // (7,9): error CS9079: Cannot ref-assign 't' to 'F' because 't' can only escape the current method through a return statement.
+ // F = ref t;
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "F = ref t").WithArguments("F", "t").WithLocation(7, 9),
+ // (12,9): error CS9079: Cannot ref-assign 't' to 'F' because 't' can only escape the current method through a return statement.
+ // r1.F = ref t;
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "r1.F = ref t").WithArguments("F", "t").WithLocation(12, 9),
// (18,9): error CS8374: Cannot ref-assign 't' to 'F' because 't' has a narrower escape scope than 'F'.
// r2.F = ref t;
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "r2.F = ref t").WithArguments("F", "t").WithLocation(18, 9)
@@ -2276,7 +2294,7 @@ static void Main()
static void Test(ref int i)
{
- var r = new R();
+ scoped var r = new R();
r.Field = ref i;
Console.WriteLine(r.Field);
}
@@ -3838,50 +3856,14 @@ static void F1(ref R a, __arglist) { }
static void F51(scoped ref R x, scoped ref R y) { F1(ref x, __arglist(ref y)); } // 6
}";
var comp = CreateCompilation(source);
- comp.VerifyEmitDiagnostics(
- // (7,41): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F00(ref R x, ref R y) { F0(__arglist(ref x, ref y)); } // 1
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(7, 41),
- // (7,58): error CS9077: Cannot return a parameter by reference 'x' through a ref parameter; it can only be returned in a return statement
- // static void F00(ref R x, ref R y) { F0(__arglist(ref x, ref y)); } // 1
- Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "x").WithArguments("x").WithLocation(7, 58),
- // (8,41): error CS8350: This combination of arguments to 'Program.F1(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F01(ref R x, ref R y) { F1(ref x, __arglist(ref y)); } // 2
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x, __arglist(ref y))").WithArguments("Program.F1(ref R, __arglist)", "__arglist").WithLocation(8, 41),
- // (8,65): error CS9077: Cannot return a parameter by reference 'y' through a ref parameter; it can only be returned in a return statement
- // static void F01(ref R x, ref R y) { F1(ref x, __arglist(ref y)); } // 2
- Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "y").WithArguments("y").WithLocation(8, 65),
- // (9,48): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F20(ref R x, scoped ref R y) { F0(__arglist(ref x, ref y)); } // 3
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(9, 48),
- // (9,72): error CS9075: Cannot return a parameter by reference 'y' because it is scoped to the current method
- // static void F20(ref R x, scoped ref R y) { F0(__arglist(ref x, ref y)); } // 3
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "y").WithArguments("y").WithLocation(9, 72),
- // (10,48): error CS8350: This combination of arguments to 'Program.F1(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F21(ref R x, scoped ref R y) { F1(ref x, __arglist(ref y)); } // 4
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x, __arglist(ref y))").WithArguments("Program.F1(ref R, __arglist)", "__arglist").WithLocation(10, 48),
- // (10,72): error CS9075: Cannot return a parameter by reference 'y' because it is scoped to the current method
- // static void F21(ref R x, scoped ref R y) { F1(ref x, __arglist(ref y)); } // 4
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "y").WithArguments("y").WithLocation(10, 72),
- // (11,55): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F50(scoped ref R x, scoped ref R y) { F0(__arglist(ref x, ref y)); } // 5
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(11, 55),
- // (11,72): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method
- // static void F50(scoped ref R x, scoped ref R y) { F0(__arglist(ref x, ref y)); } // 5
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(11, 72),
- // (12,55): error CS8350: This combination of arguments to 'Program.F1(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F51(scoped ref R x, scoped ref R y) { F1(ref x, __arglist(ref y)); } // 6
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x, __arglist(ref y))").WithArguments("Program.F1(ref R, __arglist)", "__arglist").WithLocation(12, 55),
- // (12,79): error CS9075: Cannot return a parameter by reference 'y' because it is scoped to the current method
- // static void F51(scoped ref R x, scoped ref R y) { F1(ref x, __arglist(ref y)); } // 6
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "y").WithArguments("y").WithLocation(12, 79));
+ comp.VerifyEmitDiagnostics();
}
[Fact]
public void MethodArgumentsMustMatch_07_1()
{
var source =
-@"using System.Diagnostics.CodeAnalysis;
+@"
ref struct R { }
class Program
{
@@ -3896,43 +3878,7 @@ static void F1(scoped ref R a, __arglist) { }
static void F51(ref R x, ref R y) { F1(ref x, __arglist(ref y)); } // 6
}";
var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition });
- comp.VerifyEmitDiagnostics(
- // (8,72): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method
- // static void F00(scoped ref R x, scoped ref R y) { F0(__arglist(ref x, ref y)); } // 1
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(8, 72),
- // (8,55): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F00(scoped ref R x, scoped ref R y) { F0(__arglist(ref x, ref y)); } // 1
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(8, 55),
- // (9,79): error CS9075: Cannot return a parameter by reference 'y' because it is scoped to the current method
- // static void F01(scoped ref R x, scoped ref R y) { F1(ref x, __arglist(ref y)); } // 2
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "y").WithArguments("y").WithLocation(9, 79),
- // (9,55): error CS8350: This combination of arguments to 'Program.F1(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F01(scoped ref R x, scoped ref R y) { F1(ref x, __arglist(ref y)); } // 2
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x, __arglist(ref y))").WithArguments("Program.F1(ref R, __arglist)", "__arglist").WithLocation(9, 55),
- // (10,65): error CS9075: Cannot return a parameter by reference 'x' because it is scoped to the current method
- // static void F20(scoped ref R x, ref R y) { F0(__arglist(ref x, ref y)); } // 3
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "x").WithArguments("x").WithLocation(10, 65),
- // (10,48): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F20(scoped ref R x, ref R y) { F0(__arglist(ref x, ref y)); } // 3
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(10, 48),
- // (11,72): error CS9077: Cannot return a parameter by reference 'y' through a ref parameter; it can only be returned in a return statement
- // static void F21(scoped ref R x, ref R y) { F1(ref x, __arglist(ref y)); } // 4
- Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "y").WithArguments("y").WithLocation(11, 72),
- // (11,48): error CS8350: This combination of arguments to 'Program.F1(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F21(scoped ref R x, ref R y) { F1(ref x, __arglist(ref y)); } // 4
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x, __arglist(ref y))").WithArguments("Program.F1(ref R, __arglist)", "__arglist").WithLocation(11, 48),
- // (12,58): error CS9077: Cannot return a parameter by reference 'x' through a ref parameter; it can only be returned in a return statement
- // static void F50(ref R x, ref R y) { F0(__arglist(ref x, ref y)); } // 5
- Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "x").WithArguments("x").WithLocation(12, 58),
- // (12,41): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F50(ref R x, ref R y) { F0(__arglist(ref x, ref y)); } // 5
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(12, 41),
- // (13,65): error CS9077: Cannot return a parameter by reference 'y' through a ref parameter; it can only be returned in a return statement
- // static void F51(ref R x, ref R y) { F1(ref x, __arglist(ref y)); } // 6
- Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "y").WithArguments("y").WithLocation(13, 65),
- // (13,41): error CS8350: This combination of arguments to 'Program.F1(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F51(ref R x, ref R y) { F1(ref x, __arglist(ref y)); } // 6
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F1(ref x, __arglist(ref y))").WithArguments("Program.F1(ref R, __arglist)", "__arglist").WithLocation(13, 41));
+ comp.VerifyEmitDiagnostics();
}
[Fact]
@@ -3973,31 +3919,7 @@ static R F0(__arglist)
comp.VerifyEmitDiagnostics(
// (17,9): error CS8374: Cannot ref-assign 'r.B' to 'RB' because 'r.B' has a narrower escape scope than 'RB'.
// r.RB = ref r.B; // 1
- Diagnostic(ErrorCode.ERR_RefAssignNarrower, "r.RB = ref r.B").WithArguments("RB", "r.B").WithLocation(17, 9),
- // (21,55): error CS9075: Cannot return a parameter by reference 'y' because it is scoped to the current method
- // static void F1(scoped ref R y) { F0(__arglist(ref y)); } // 2
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "y").WithArguments("y").WithLocation(21, 55),
- // (21,38): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F1(scoped ref R y) { F0(__arglist(ref y)); } // 2
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(21, 38),
- // (22,48): error CS9077: Cannot return a parameter by reference 'y' through a ref parameter; it can only be returned in a return statement
- // static void F2(ref R y) { F0(__arglist(ref y)); } // 3
- Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "y").WithArguments("y").WithLocation(22, 48),
- // (22,31): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static void F2(ref R y) { F0(__arglist(ref y)); } // 3
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(22, 31),
- // (24,59): error CS9075: Cannot return a parameter by reference 'y' because it is scoped to the current method
- // static R F3(scoped ref R y) { return F0(__arglist(ref y)); } // 4
- Diagnostic(ErrorCode.ERR_RefReturnScopedParameter, "y").WithArguments("y").WithLocation(24, 59),
- // (24,42): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static R F3(scoped ref R y) { return F0(__arglist(ref y)); } // 4
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(24, 42),
- // (25,52): error CS9077: Cannot return a parameter by reference 'y' through a ref parameter; it can only be returned in a return statement
- // static R F4(ref R y) { return F0(__arglist(ref y)); } // 5
- Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "y").WithArguments("y").WithLocation(25, 52),
- // (25,35): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // static R F4(ref R y) { return F0(__arglist(ref y)); } // 5
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(25, 35));
+ Diagnostic(ErrorCode.ERR_RefAssignNarrower, "r.RB = ref r.B").WithArguments("RB", "r.B").WithLocation(17, 9));
}
[Fact]
@@ -4063,18 +3985,6 @@ static void F1()
comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (13,9): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // F0(__arglist(ref x)); // 1
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(13, 9),
- // (13,26): error CS8168: Cannot return local 'x' by reference because it is not a ref local
- // F0(__arglist(ref x)); // 1
- Diagnostic(ErrorCode.ERR_RefReturnLocal, "x").WithArguments("x").WithLocation(13, 26),
- // (15,9): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // F0(__arglist(ref x, ref x)); // 2
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref x))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(15, 9),
- // (15,26): error CS8168: Cannot return local 'x' by reference because it is not a ref local
- // F0(__arglist(ref x, ref x)); // 2
- Diagnostic(ErrorCode.ERR_RefReturnLocal, "x").WithArguments("x").WithLocation(15, 26),
// (16,9): error CS8350: This combination of arguments to 'Program.F0(__arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
// F0(__arglist(ref x, ref y)); // 3
Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(__arglist(ref x, ref y))").WithArguments("Program.F0(__arglist)", "__arglist").WithLocation(16, 9),
@@ -4120,12 +4030,6 @@ static void F1()
comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (13,9): error CS8350: This combination of arguments to 'Program.F0(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
- // F0(ref x, __arglist(ref x)); // 1
- Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(ref x, __arglist(ref x))").WithArguments("Program.F0(ref R, __arglist)", "__arglist").WithLocation(13, 9),
- // (13,33): error CS8168: Cannot return local 'x' by reference because it is not a ref local
- // F0(ref x, __arglist(ref x)); // 1
- Diagnostic(ErrorCode.ERR_RefReturnLocal, "x").WithArguments("x").WithLocation(13, 33),
// (14,9): error CS8350: This combination of arguments to 'Program.F0(ref R, __arglist)' is disallowed because it may expose variables referenced by parameter '__arglist' outside of their declaration scope
// F0(ref x, __arglist(ref y)); // 2
Diagnostic(ErrorCode.ERR_CallArgMixing, "F0(ref x, __arglist(ref y))").WithArguments("Program.F0(ref R, __arglist)", "__arglist").WithLocation(14, 9),
@@ -4232,6 +4136,46 @@ static void F2(ref R x2, ref R y2)
Diagnostic(ErrorCode.ERR_EscapeVariable, "y1").WithArguments("y1").WithLocation(13, 16));
}
+ ///
+ /// Ensure that readonly members / types are properly accounted for
+ ///
+ [Fact]
+ public void MethodArgumentsMustMatch_13()
+ {
+ var source = """
+ ref struct RWRS
+ {
+ public void M1(RS rs) { }
+ public readonly void M2(RS rs) { }
+ }
+ readonly ref struct RORS
+ {
+ public void M3(RS rs) { }
+ }
+ ref struct RS
+ {
+ static void Test()
+ {
+ scoped RS local = default;
+ RWRS rwLocal = default;
+ rwLocal.M1(local); // 1
+ rwLocal.M2(local);
+ RORS roLocal = default;
+ roLocal.M3(local);
+ }
+ }
+ """;
+
+ var comp = CreateCompilation(new[] { source });
+ comp.VerifyDiagnostics(
+ // (16,9): error CS8350: This combination of arguments to 'RWRS.M1(RS)' is disallowed because it may expose variables referenced by parameter 'rs' outside of their declaration scope
+ // rwLocal.M1(local); // 1
+ Diagnostic(ErrorCode.ERR_CallArgMixing, "rwLocal.M1(local)").WithArguments("RWRS.M1(RS)", "rs").WithLocation(16, 9),
+ // (16,20): error CS8352: Cannot use variable 'local' in this context because it may expose referenced variables outside of their declaration scope
+ // rwLocal.M1(local); // 1
+ Diagnostic(ErrorCode.ERR_EscapeVariable, "local").WithArguments("local").WithLocation(16, 20));
+ }
+
[Theory]
[InlineData(LanguageVersion.CSharp10)]
[InlineData(LanguageVersion.CSharp11)]
@@ -4336,20 +4280,20 @@ static R F1()
{
var r1 = new R();
int i = 1;
- r1.F(in i); // 1
+ r1.F(in i);
return r1;
}
static R F2()
{
var r2 = new R();
int i = 2;
- r2.F(i); // 2
+ r2.F(i);
return r2;
}
static R F3()
{
var r3 = new R();
- r3.F(3); // 3
+ r3.F(3);
return r3;
}
}";
@@ -4358,25 +4302,7 @@ static R F3()
comp.VerifyEmitDiagnostics();
comp = CreateCompilation(source);
- comp.VerifyEmitDiagnostics(
- // (11,9): error CS8350: This combination of arguments to 'R.F(in int)' is disallowed because it may expose variables referenced by parameter 't' outside of their declaration scope
- // r1.F(in i); // 1
- Diagnostic(ErrorCode.ERR_CallArgMixing, "r1.F(in i)").WithArguments("R.F(in int)", "t").WithLocation(11, 9),
- // (11,17): error CS8168: Cannot return local 'i' by reference because it is not a ref local
- // r1.F(in i); // 1
- Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(11, 17),
- // (18,9): error CS8350: This combination of arguments to 'R.F(in int)' is disallowed because it may expose variables referenced by parameter 't' outside of their declaration scope
- // r2.F(i); // 2
- Diagnostic(ErrorCode.ERR_CallArgMixing, "r2.F(i)").WithArguments("R.F(in int)", "t").WithLocation(18, 9),
- // (18,14): error CS8168: Cannot return local 'i' by reference because it is not a ref local
- // r2.F(i); // 2
- Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(18, 14),
- // (24,9): error CS8350: This combination of arguments to 'R.F(in int)' is disallowed because it may expose variables referenced by parameter 't' outside of their declaration scope
- // r3.F(3); // 3
- Diagnostic(ErrorCode.ERR_CallArgMixing, "r3.F(3)").WithArguments("R.F(in int)", "t").WithLocation(24, 9),
- // (24,14): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference
- // r3.F(3); // 3
- Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "3").WithLocation(24, 14));
+ comp.VerifyEmitDiagnostics();
}
[Theory]
@@ -4588,7 +4514,7 @@ static void M2(T t2)
}
static void M3(ref T t3)
{
- S s3;
+ scoped S s3;
s3 = new S(ref t3);
s3 = new S { F = t3 };
}
@@ -4663,7 +4589,7 @@ static void M2(T t2)
}
static void M3(ref T t3)
{
- S s3;
+ scoped S s3;
s3 = new S(ref t3);
s3 = new S { F = t3 };
}
@@ -6005,66 +5931,73 @@ public void AssignRefTo_RefField()
class Program
{
static void AssignValueToValue(S s, T tValue) { s.F = ref tValue; } // 1
- static void AssignRefToValue(S s, ref T tRef) { s.F = ref tRef; }
- static void AssignOutToValue(S s, out T tOut) { tOut = default; s.F = ref tOut; } // 2
- static void AssignInToValue(S s, in T tIn) { s.F = ref tIn; } // 3
+ static void AssignRefToValue(S s, ref T tRef) { s.F = ref tRef; } // 2
+ static void AssignOutToValue(S s, out T tOut) { tOut = default; s.F = ref tOut; } // 3
+ static void AssignInToValue(S s, in T tIn) { s.F = ref tIn; } // 4
- static void AssignValueToRef(ref S sRef, T tValue) { sRef.F = ref tValue; } // 4
- static void AssignRefToRef(ref S sRef, ref T tRef) { sRef.F = ref tRef; }
- static void AssignOutToRef(ref S sRef, out T tOut) { tOut = default; sRef.F = ref tOut; } // 5
- static void AssignInToRef(ref S sRef, in T tIn) { sRef.F = ref tIn; } // 6
+ static void AssignValueToRef(ref S sRef, T tValue) { sRef.F = ref tValue; } // 5
+ static void AssignRefToRef(ref S sRef, ref T tRef) { sRef.F = ref tRef; } // 6
+ static void AssignOutToRef(ref S sRef, out T tOut) { tOut = default; sRef.F = ref tOut; } // 7
+ static void AssignInToRef(ref S sRef, in T tIn) { sRef.F = ref tIn; } // 8
- static void AssignValueToOut(out S sOut, T tValue) { sOut = default; sOut.F = ref tValue; } // 7
+ static void AssignValueToOut(out S sOut, T tValue) { sOut = default; sOut.F = ref tValue; } // 9
static void AssignRefToOut(out S sOut, ref T tRef) { sOut = default; sOut.F = ref tRef; }
- static void AssignOutToOut(out S sOut, out T tOut) { sOut = default; tOut = default; sOut.F = ref tOut; } // 8
- static void AssignInToOut(out S sOut, in T tIn) { sOut = default; sOut.F = ref tIn; } // 9
+ static void AssignOutToOut(out S sOut, out T tOut) { sOut = default; tOut = default; sOut.F = ref tOut; } // 10
+ static void AssignInToOut(out S sOut, in T tIn) { sOut = default; sOut.F = ref tIn; } // 11
- static void AssignValueToIn(in S sIn, T tValue) { sIn.F = ref tValue; } // 10
- static void AssignRefToIn(in S sIn, ref T tRef) { sIn.F = ref tRef; } // 11
- static void AssignOutToIn(in S sIn, out T tOut) { tOut = default; sIn.F = ref tOut; } // 12
- static void AssignInToIn(in S sIn, in T tIn) { sIn.F = ref tIn; } // 13
+ static void AssignValueToIn(in S sIn, T tValue) { sIn.F = ref tValue; } // 12
+ static void AssignRefToIn(in S sIn, ref T tRef) { sIn.F = ref tRef; } // 13
+ static void AssignOutToIn(in S sIn, out T tOut) { tOut = default; sIn.F = ref tOut; } // 14
+ static void AssignInToIn(in S sIn, in T tIn) { sIn.F = ref tIn; } // 15
}";
var comp = CreateCompilation(source, runtimeFeature: RuntimeFlag.ByRefFields);
comp.VerifyEmitDiagnostics(
// (9,59): error CS8374: Cannot ref-assign 'tValue' to 'F' because 'tValue' has a narrower escape scope than 'F'.
// static void AssignValueToValue(S s, T tValue) { s.F = ref tValue; } // 1
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s.F = ref tValue").WithArguments("F", "tValue").WithLocation(9, 59),
+ // (10,59): error CS9079: Cannot ref-assign 'tRef' to 'F' because 'tRef' can only escape the current method through a return statement.
+ // static void AssignRefToValue(S s, ref T tRef) { s.F = ref tRef; } // 2
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "s.F = ref tRef").WithArguments("F", "tRef").WithLocation(10, 59),
// (11,75): error CS8374: Cannot ref-assign 'tOut' to 'F' because 'tOut' has a narrower escape scope than 'F'.
- // static void AssignOutToValue(S s, out T tOut) { tOut = default; s.F = ref tOut; } // 2
+ // static void AssignOutToValue(S s, out T tOut) { tOut = default; s.F = ref tOut; } // 3
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s.F = ref tOut").WithArguments("F", "tOut").WithLocation(11, 75),
// (12,69): error CS8331: Cannot assign to variable 'in T' or use it as the right hand side of a ref assignment because it is a readonly variable
- // static void AssignInToValue(S s, in T tIn) { s.F = ref tIn; } // 3
+ // static void AssignInToValue(S s, in T tIn) { s.F = ref tIn; } // 4
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "tIn").WithArguments("variable", "in T").WithLocation(12, 69),
// (14,64): error CS8374: Cannot ref-assign 'tValue' to 'F' because 'tValue' has a narrower escape scope than 'F'.
- // static void AssignValueToRef(ref S sRef, T tValue) { sRef.F = ref tValue; } // 4
+ // static void AssignValueToRef(ref S sRef, T tValue) { sRef.F = ref tValue; } // 5
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "sRef.F = ref tValue").WithArguments("F", "tValue").WithLocation(14, 64),
+ // (15,64): error CS9079: Cannot ref-assign 'tRef' to 'F' because 'tRef' can only escape the current method through a return statement.
+ // static void AssignRefToRef(ref S sRef, ref T tRef) { sRef.F = ref tRef; } // 6
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "sRef.F = ref tRef").WithArguments("F", "tRef").WithLocation(15, 64),
// (16,80): error CS8374: Cannot ref-assign 'tOut' to 'F' because 'tOut' has a narrower escape scope than 'F'.
- // static void AssignOutToRef(ref S sRef, out T tOut) { tOut = default; sRef.F = ref tOut; } // 5
+ // static void AssignOutToRef(ref S sRef, out T tOut) { tOut = default; sRef.F = ref tOut; } // 7
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "sRef.F = ref tOut").WithArguments("F", "tOut").WithLocation(16, 80),
// (17,77): error CS8331: Cannot assign to variable 'in T' or use it as the right hand side of a ref assignment because it is a readonly variable
- // static void AssignInToRef(ref S sRef, in T tIn) { sRef.F = ref tIn; } // 6
+ // static void AssignInToRef(ref S sRef, in T tIn) { sRef.F = ref tIn; } // 8
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "tIn").WithArguments("variable", "in T").WithLocation(17, 77),
// (19,80): error CS8374: Cannot ref-assign 'tValue' to 'F' because 'tValue' has a narrower escape scope than 'F'.
- // static void AssignValueToOut(out S sOut, T tValue) { sOut = default; sOut.F = ref tValue; } // 7
+ // static void AssignValueToOut(out S sOut, T tValue) { sOut = default; sOut.F = ref tValue; } // 9
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "sOut.F = ref tValue").WithArguments("F", "tValue").WithLocation(19, 80),
// (21,96): error CS8374: Cannot ref-assign 'tOut' to 'F' because 'tOut' has a narrower escape scope than 'F'.
- // static void AssignOutToOut(out S sOut, out T tOut) { sOut = default; tOut = default; sOut.F = ref tOut; } // 8
+ // static void AssignOutToOut(out S sOut, out T tOut) { sOut = default; tOut = default; sOut.F = ref tOut; } // 10
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "sOut.F = ref tOut").WithArguments("F", "tOut").WithLocation(21, 96),
// (22,93): error CS8331: Cannot assign to variable 'in T' or use it as the right hand side of a ref assignment because it is a readonly variable
- // static void AssignInToOut(out S sOut, in T tIn) { sOut = default; sOut.F = ref tIn; } // 9
+ // static void AssignInToOut(out S sOut, in T tIn) { sOut = default; sOut.F = ref tIn; } // 11
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField, "tIn").WithArguments("variable", "in T").WithLocation(22, 93),
// (24,61): error CS8332: Cannot assign to a member of variable 'in S' or use it as the right hand side of a ref assignment because it is a readonly variable
- // static void AssignValueToIn(in S sIn, T tValue) { sIn.F = ref tValue; } // 10
+ // static void AssignValueToIn(in S sIn, T tValue) { sIn.F = ref tValue; } // 12
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "sIn.F").WithArguments("variable", "in S").WithLocation(24, 61),
// (25,61): error CS8332: Cannot assign to a member of variable 'in S' or use it as the right hand side of a ref assignment because it is a readonly variable
- // static void AssignRefToIn(in S sIn, ref T tRef) { sIn.F = ref tRef; } // 11
+ // static void AssignRefToIn(in S sIn, ref T tRef) { sIn.F = ref tRef; } // 13
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "sIn.F").WithArguments("variable", "in S").WithLocation(25, 61),
// (26,77): error CS8332: Cannot assign to a member of variable 'in S' or use it as the right hand side of a ref assignment because it is a readonly variable
- // static void AssignOutToIn(in S sIn, out T tOut) { tOut = default; sIn.F = ref tOut; } // 12
+ // static void AssignOutToIn(in S sIn, out T tOut) { tOut = default; sIn.F = ref tOut; } // 14
Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "sIn.F").WithArguments("variable", "in S").WithLocation(26, 77),
// (27,61): error CS8332: Cannot assign to a member of variable 'in S' or use it as the right hand side of a ref assignment because it is a readonly variable
- // static void AssignInToIn(in S sIn, in T tIn) { sIn.F = ref tIn; } // 13
- Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "sIn.F").WithArguments("variable", "in S").WithLocation(27, 61));
+ // static void AssignInToIn(in S sIn, in T tIn) { sIn.F = ref tIn; } // 15
+ Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "sIn.F").WithArguments("variable", "in S").WithLocation(27, 61)
+ );
// Valid cases from above.
source =
@@ -6078,10 +6011,6 @@ ref struct S
class Program
{
- static void AssignRefToValue(S s, ref T tRef) { s.F = ref tRef; }
-
- static void AssignRefToRef(ref S sRef, ref T tRef) { sRef.F = ref tRef; }
-
static void AssignRefToOut(out S sOut, ref T tRef) { sOut = default; sOut.F = ref tRef; }
static void Main()
@@ -6089,16 +6018,6 @@ static void Main()
int x, y;
scoped S s;
- x = 1; y = 2;
- s = new S(ref x);
- AssignRefToValue(s, ref y);
- Console.WriteLine(s.F);
-
- x = 3; y = 4;
- s = new S(ref x);
- AssignRefToRef(ref s, ref y);
- Console.WriteLine(s.F);
-
x = 5; y = 6;
s = new S(ref x);
AssignRefToOut(out s, ref y);
@@ -6107,28 +6026,9 @@ static void Main()
}";
comp = CreateCompilation(source, options: TestOptions.ReleaseExe, runtimeFeature: RuntimeFlag.ByRefFields);
var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(
-@"1
-4
-6"));
+@"6"));
+
verifier.VerifyILMultiple(
- "Program.AssignRefToValue",
-@"{
- // Code size 9 (0x9)
- .maxstack 2
- IL_0000: ldarga.s V_0
- IL_0002: ldarg.1
- IL_0003: stfld ""ref T S.F""
- IL_0008: ret
-}",
- "Program.AssignRefToRef",
-@"{
- // Code size 8 (0x8)
- .maxstack 2
- IL_0000: ldarg.0
- IL_0001: ldarg.1
- IL_0002: stfld ""ref T S.F""
- IL_0007: ret
-}",
"Program.AssignRefToOut",
@"{
// Code size 15 (0xf)
@@ -6179,15 +6079,27 @@ class Program
// (9,59): error CS8374: Cannot ref-assign 'tValue' to 'F' because 'tValue' has a narrower escape scope than 'F'.
// static void AssignValueToValue(S s, T tValue) { s.F = ref tValue; } // 1
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s.F = ref tValue").WithArguments("F", "tValue").WithLocation(9, 59),
+ // (10,59): error CS9079: Cannot ref-assign 'tRef' to 'F' because 'tRef' can only escape the current method through a return statement.
+ // static void AssignRefToValue(S s, ref T tRef) { s.F = ref tRef; }
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "s.F = ref tRef").WithArguments("F", "tRef").WithLocation(10, 59),
// (11,75): error CS8374: Cannot ref-assign 'tOut' to 'F' because 'tOut' has a narrower escape scope than 'F'.
// static void AssignOutToValue(S s, out T tOut) { tOut = default; s.F = ref tOut; } // 2
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "s.F = ref tOut").WithArguments("F", "tOut").WithLocation(11, 75),
+ // (12,59): error CS9079: Cannot ref-assign 'tIn' to 'F' because 'tIn' can only escape the current method through a return statement.
+ // static void AssignInToValue(S s, in T tIn) { s.F = ref tIn; }
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "s.F = ref tIn").WithArguments("F", "tIn").WithLocation(12, 59),
// (14,64): error CS8374: Cannot ref-assign 'tValue' to 'F' because 'tValue' has a narrower escape scope than 'F'.
// static void AssignValueToRef(ref S sRef, T tValue) { sRef.F = ref tValue; } // 3
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "sRef.F = ref tValue").WithArguments("F", "tValue").WithLocation(14, 64),
+ // (15,64): error CS9079: Cannot ref-assign 'tRef' to 'F' because 'tRef' can only escape the current method through a return statement.
+ // static void AssignRefToRef(ref S sRef, ref T tRef) { sRef.F = ref tRef; }
+ Diagnostic(ErrorCode.ERR_RefAssignReturnOnly, "sRef.F = ref tRef").WithArguments("F", "tRef").WithLocation(15, 64),
// (16,80): error CS8374: Cannot ref-assign 'tOut' to 'F' because 'tOut' has a narrower escape scope than 'F'.
// static void AssignOutToRef(ref S sRef, out T tOut) { tOut = default; sRef.F = ref tOut; } // 4
Diagnostic(ErrorCode.ERR_RefAssignNarrower, "sRef.F = ref tOut").WithArguments("F", "tOut").WithLocation(16, 80),
+ // (17,64): error CS9079: Cannot ref-assign 'tIn' to 'F' because 'tIn' can only escape the current method through a return statement.
+ // static void AssignInToRef