diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
index c16043abf69c1c4f8a592fbb9df597af25ec4855..0504cc9d31cae28979c3cd7d661dd8db986f6fe8 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
@@ -773,7 +773,10 @@ protected BoundExpression BindInferredVariableInitializer(DiagnosticBag diagnost
}
else
{
- valueKind = BindValueKind.RefOrOut;
+ valueKind = variableRefKind == RefKind.RefReadOnly
+ ? BindValueKind.ReadonlyRef
+ : BindValueKind.RefOrOut;
+
if (initializer == null)
{
Error(diagnostics, ErrorCode.ERR_ByReferenceVariableMustBeInitialized, node);
@@ -836,19 +839,10 @@ protected BoundExpression BindInferredVariableInitializer(DiagnosticBag diagnost
// might own nested scope.
bool hasErrors = localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics);
- if (localSymbol.RefKind == RefKind.RefReadOnly)
- {
- Debug.Assert(typeSyntax.Parent is RefTypeSyntax);
- var refKeyword = typeSyntax.Parent.GetFirstToken();
- diagnostics.Add(ErrorCode.ERR_UnexpectedToken, refKeyword.GetLocation(), refKeyword.ToString());
- }
- else
+ var containingMethod = this.ContainingMemberOrLambda as MethodSymbol;
+ if (containingMethod != null && containingMethod.IsAsync && localSymbol.RefKind != RefKind.None)
{
- var containingMethod = this.ContainingMemberOrLambda as MethodSymbol;
- if (containingMethod != null && containingMethod.IsAsync && localSymbol.RefKind != RefKind.None)
- {
- Error(diagnostics, ErrorCode.ERR_BadAsyncLocalType, declarator);
- }
+ Error(diagnostics, ErrorCode.ERR_BadAsyncLocalType, declarator);
}
EqualsValueClauseSyntax equalsClauseSyntax = declarator.Initializer;
diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs
index d9da41aa6e4665a1022cd9f9f271f420a4633e18..d591bc14ae6b2d83534aa0d6ca4ce395c54d5385 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/EmitAddress.cs
@@ -40,13 +40,11 @@ private LocalDefinition EmitAddress(BoundExpression expression, AddressKind addr
break;
case BoundKind.Local:
- EmitLocalAddress((BoundLocal)expression);
- break;
+ return EmitLocalAddress((BoundLocal)expression, addressKind);
case BoundKind.Dup:
Debug.Assert(((BoundDup)expression).RefKind != RefKind.None, "taking address of a stack value?");
- _builder.EmitOpCode(ILOpCode.Dup);
- break;
+ return EmitDupAddress((BoundDup)expression, addressKind);
case BoundKind.ConditionalReceiver:
// do nothing receiver ref must be already pushed
@@ -213,10 +211,18 @@ private void EmitComplexConditionalReceiverAddress(BoundComplexConditionalReceiv
_builder.MarkLabel(doneLabel);
}
- private void EmitLocalAddress(BoundLocal localAccess)
+ ///
+ /// May introduce a temp which it will return. (otherwise returns null)
+ ///
+ private LocalDefinition EmitLocalAddress(BoundLocal localAccess, AddressKind addressKind)
{
var local = localAccess.LocalSymbol;
+ if (!HasHome(localAccess, needWriteable: addressKind != AddressKind.ReadOnly))
+ {
+ return EmitAddressOfTempClone(localAccess);
+ }
+
if (IsStackLocal(local))
{
if (local.RefKind != RefKind.None)
@@ -234,6 +240,22 @@ private void EmitLocalAddress(BoundLocal localAccess)
{
_builder.EmitLocalAddress(GetLocal(localAccess));
}
+
+ return null;
+ }
+
+ ///
+ /// May introduce a temp which it will return. (otherwise returns null)
+ ///
+ private LocalDefinition EmitDupAddress(BoundDup dup, AddressKind addressKind)
+ {
+ if (!HasHome(dup, needWriteable: addressKind != AddressKind.ReadOnly))
+ {
+ return EmitAddressOfTempClone(dup);
+ }
+
+ _builder.EmitOpCode(ILOpCode.Dup);
+ return null;
}
private void EmitPseudoVariableAddress(BoundPseudoVariable expression)
@@ -345,9 +367,11 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
((BoundParameter)expression).ParameterSymbol.RefKind != RefKind.RefReadOnly;
case BoundKind.Local:
- // locals have home unless they are byval stack locals
+ // locals have home unless they are byval stack locals or ref-readonly
+ // locals in a mutating call
var local = ((BoundLocal)expression).LocalSymbol;
- return !IsStackLocal(local) || local.RefKind != RefKind.None;
+ return !((IsStackLocal(local) && local.RefKind == RefKind.None) ||
+ (needWriteable && local.RefKind == RefKind.RefReadOnly));
case BoundKind.Call:
var methodRefKind = ((BoundCall)expression).Method.RefKind;
@@ -356,9 +380,9 @@ private bool HasHome(BoundExpression expression, bool needWriteable)
case BoundKind.Dup:
//NB: Dup represents locals that do not need IL slot
- // ref locals are currently always writeable, so we do not need to care about "needWriteable"
- Debug.Assert(((BoundDup)expression).RefKind != RefKind.RefReadOnly);
- return ((BoundDup)expression).RefKind != RefKind.None;
+ var dupRefKind = ((BoundDup)expression).RefKind;
+ return dupRefKind == RefKind.Ref ||
+ (!needWriteable && dupRefKind == RefKind.RefReadOnly);
case BoundKind.FieldAccess:
return HasHome((BoundFieldAccess)expression, needWriteable);
@@ -540,7 +564,7 @@ private LocalDefinition EmitParameterAddress(BoundParameter parameter, AddressKi
{
ParameterSymbol parameterSymbol = parameter.ParameterSymbol;
- if (!HasHome(parameter, addressKind != AddressKind.ReadOnly))
+ if (!HasHome(parameter, needWriteable: addressKind != AddressKind.ReadOnly))
{
// accessing a parameter that is not writable
return EmitAddressOfTempClone(parameter);
diff --git a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs
index a7866cef40996c4bcec6ddf519474c435dd9b835..7274ae7d9e47ef4b50389a76ff8a0d6b49901424 100644
--- a/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs
+++ b/src/Compilers/CSharp/Portable/CodeGen/Optimizer.cs
@@ -971,7 +971,9 @@ private static bool IsIndirectAssignment(BoundAssignmentOperator node)
{
var lhs = node.Left;
- Debug.Assert(node.RefKind == RefKind.None || (lhs as BoundLocal)?.LocalSymbol.RefKind == RefKind.Ref,
+ Debug.Assert(node.RefKind == RefKind.None || lhs is BoundLocal local &&
+ (local.LocalSymbol.RefKind == node.RefKind ||
+ local.LocalSymbol.RefKind == RefKind.RefReadOnly),
"only ref locals can be a target of a ref assignment");
switch (lhs.Kind)
@@ -1034,6 +1036,7 @@ private static bool IsIndirectAssignment(BoundAssignmentOperator node)
throw ExceptionUtilities.UnexpectedValue(lhs.Kind);
}
}
+
private static bool IsIndirectOrInstanceFieldAssignment(BoundAssignmentOperator node)
{
var lhs = node.Left;
diff --git a/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs
index 5e4d7a7e3315a2c056994360ec618f302e6b1f42..1964995dd474dd8e178fe519b0f9457850d5a292 100644
--- a/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/LocalSymbol.cs
@@ -268,7 +268,7 @@ internal virtual bool IsWritable
case LocalDeclarationKind.UsingVariable:
return false;
default:
- return true;
+ return RefKind != RefKind.RefReadOnly;
}
}
}
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs
index d165b8dc024d7ea2f3050cd4d88e6561e3536791..a0eb06901ae81bb3ed0dc1e3a56d1c9782639ff1 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReadonlyReturnTests.cs
@@ -1,10 +1,5 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Linq;
-using Microsoft.CodeAnalysis.CSharp.Symbols;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
@@ -15,6 +10,377 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests
[CompilerTrait(CompilerFeature.ReadOnlyReferences)]
public class CodeGenRefReadOnlyReturnTests : CompilingTestBase
{
+ [Fact]
+ public void RefReadonlyLocalToField()
+ {
+ var comp = CompileAndVerify(@"
+struct S
+{
+ public int X;
+ public S(int x) => X = x;
+
+ public void AddOne() => this.X++;
+}
+
+readonly struct S2
+{
+ public readonly int X;
+ public S2(int x) => X = x;
+
+ public void AddOne() { }
+}
+
+class C
+{
+ static S s1 = new S(0);
+ readonly static S s2 = new S(0);
+
+ static S2 s3 = new S2(0);
+ readonly S2 s4 = new S2(0);
+
+ ref readonly S M()
+ {
+ ref readonly S rs1 = ref s1;
+ rs1.AddOne();
+ ref readonly S rs2 = ref s2;
+ rs2.AddOne();
+
+ ref readonly S2 rs3 = ref s3;
+ rs3.AddOne();
+ ref readonly S2 rs4 = ref s4;
+ rs4.AddOne();
+
+ return ref rs1;
+ }
+}");
+ comp.VerifyIL("C.M", @"
+{
+ // Code size 65 (0x41)
+ .maxstack 2
+ .locals init (S V_0,
+ S V_1,
+ S2 V_2)
+ IL_0000: ldsflda ""S C.s1""
+ IL_0005: dup
+ IL_0006: ldobj ""S""
+ IL_000b: stloc.0
+ IL_000c: ldloca.s V_0
+ IL_000e: call ""void S.AddOne()""
+ IL_0013: ldsfld ""S C.s2""
+ IL_0018: stloc.0
+ IL_0019: ldloca.s V_0
+ IL_001b: ldobj ""S""
+ IL_0020: stloc.1
+ IL_0021: ldloca.s V_1
+ IL_0023: call ""void S.AddOne()""
+ IL_0028: ldsflda ""S2 C.s3""
+ IL_002d: call ""void S2.AddOne()""
+ IL_0032: ldarg.0
+ IL_0033: ldfld ""S2 C.s4""
+ IL_0038: stloc.2
+ IL_0039: ldloca.s V_2
+ IL_003b: call ""void S2.AddOne()""
+ IL_0040: ret
+}");
+ }
+
+ [Fact]
+ public void CallsOnRefReadonlyCopyReceiver()
+ {
+ var comp = CompileAndVerify(@"
+using System;
+
+struct S
+{
+ public int X;
+ public S(int x) => X = x;
+
+ public void AddOne() => this.X++;
+}
+
+class C
+{
+ public static void Main()
+ {
+ S s = new S(0);
+ ref readonly S rs = ref s;
+ Console.WriteLine(rs.X);
+ rs.AddOne();
+ Console.WriteLine(rs.X);
+ rs.AddOne();
+ rs.AddOne();
+ rs.AddOne();
+ }
+}", expectedOutput: @"0
+0");
+ comp.VerifyIL("C.Main", @"
+{
+ // Code size 88 (0x58)
+ .maxstack 2
+ .locals init (S V_0, //s
+ S V_1)
+ IL_0000: ldloca.s V_0
+ IL_0002: ldc.i4.0
+ IL_0003: call ""S..ctor(int)""
+ IL_0008: ldloca.s V_0
+ IL_000a: dup
+ IL_000b: ldfld ""int S.X""
+ IL_0010: call ""void System.Console.WriteLine(int)""
+ IL_0015: dup
+ IL_0016: ldobj ""S""
+ IL_001b: stloc.1
+ IL_001c: ldloca.s V_1
+ IL_001e: call ""void S.AddOne()""
+ IL_0023: dup
+ IL_0024: ldfld ""int S.X""
+ IL_0029: call ""void System.Console.WriteLine(int)""
+ IL_002e: dup
+ IL_002f: ldobj ""S""
+ IL_0034: stloc.1
+ IL_0035: ldloca.s V_1
+ IL_0037: call ""void S.AddOne()""
+ IL_003c: dup
+ IL_003d: ldobj ""S""
+ IL_0042: stloc.1
+ IL_0043: ldloca.s V_1
+ IL_0045: call ""void S.AddOne()""
+ IL_004a: ldobj ""S""
+ IL_004f: stloc.1
+ IL_0050: ldloca.s V_1
+ IL_0052: call ""void S.AddOne()""
+ IL_0057: ret
+}");
+ // This should generate similar IL to the previous
+ comp = CompileAndVerify(@"
+using System;
+
+struct S
+{
+ public int X;
+ public S(int x) => X = x;
+
+ public void AddOne() => this.X++;
+}
+
+class C
+{
+ public static void Main()
+ {
+ S s = new S(0);
+ ref S sr = ref s;
+ var temp = sr;
+ temp.AddOne();
+ Console.WriteLine(temp.X);
+ temp = sr;
+ temp.AddOne();
+ Console.WriteLine(temp.X);
+ }
+}", expectedOutput: @"1
+1");
+ comp.VerifyIL("C.Main", @"
+{
+ // Code size 60 (0x3c)
+ .maxstack 2
+ .locals init (S V_0, //s
+ S V_1) //temp
+ IL_0000: ldloca.s V_0
+ IL_0002: ldc.i4.0
+ IL_0003: call ""S..ctor(int)""
+ IL_0008: ldloca.s V_0
+ IL_000a: dup
+ IL_000b: ldobj ""S""
+ IL_0010: stloc.1
+ IL_0011: ldloca.s V_1
+ IL_0013: call ""void S.AddOne()""
+ IL_0018: ldloc.1
+ IL_0019: ldfld ""int S.X""
+ IL_001e: call ""void System.Console.WriteLine(int)""
+ IL_0023: ldobj ""S""
+ IL_0028: stloc.1
+ IL_0029: ldloca.s V_1
+ IL_002b: call ""void S.AddOne()""
+ IL_0030: ldloc.1
+ IL_0031: ldfld ""int S.X""
+ IL_0036: call ""void System.Console.WriteLine(int)""
+ IL_003b: ret
+}");
+ }
+
+ [Fact]
+ public void RefReadOnlyParamCopyReceiver()
+ {
+ var comp = CompileAndVerify(@"
+using System;
+
+struct S
+{
+ public int X;
+ public S(int x) => X = x;
+
+ public void AddOne() => this.X++;
+}
+
+class C
+{
+ public static void Main()
+ {
+ M(new S(0));
+ }
+ static void M(ref readonly S rs)
+ {
+ Console.WriteLine(rs.X);
+ rs.AddOne();
+ Console.WriteLine(rs.X);
+ }
+}", expectedOutput: @"0
+0");
+ comp.VerifyIL(@"C.M", @"
+{
+ // Code size 37 (0x25)
+ .maxstack 1
+ .locals init (S V_0)
+ IL_0000: ldarg.0
+ IL_0001: ldfld ""int S.X""
+ IL_0006: call ""void System.Console.WriteLine(int)""
+ IL_000b: ldarg.0
+ IL_000c: ldobj ""S""
+ IL_0011: stloc.0
+ IL_0012: ldloca.s V_0
+ IL_0014: call ""void S.AddOne()""
+ IL_0019: ldarg.0
+ IL_001a: ldfld ""int S.X""
+ IL_001f: call ""void System.Console.WriteLine(int)""
+ IL_0024: ret
+}");
+ }
+
+ [Fact]
+ public void CarryThroughLifetime()
+ {
+ var comp = CompileAndVerify(@"
+class C
+{
+ static ref readonly int M(ref int p)
+ {
+ ref readonly int rp = ref p;
+ return ref rp;
+ }
+}", verify: false);
+ comp.VerifyIL("C.M", @"
+{
+ // Code size 2 (0x2)
+ .maxstack 1
+ IL_0000: ldarg.0
+ IL_0001: ret
+}");
+ }
+
+ [Fact]
+ public void TempForReadonly()
+ {
+ var comp = CompileAndVerify(@"
+using System;
+class C
+{
+ public static void Main()
+ {
+ void L(ref readonly int p)
+ {
+ Console.WriteLine(p);
+ }
+ for (int i = 0; i < 3; i++)
+ {
+ L(10);
+ L(i);
+ }
+ }
+}", expectedOutput: @"10
+0
+10
+1
+10
+2");
+ comp.VerifyIL("C.Main()", @"
+{
+ // Code size 30 (0x1e)
+ .maxstack 2
+ .locals init (int V_0, //i
+ int V_1)
+ IL_0000: ldc.i4.0
+ IL_0001: stloc.0
+ IL_0002: br.s IL_0019
+ IL_0004: ldc.i4.s 10
+ IL_0006: stloc.1
+ IL_0007: ldloca.s V_1
+ IL_0009: call ""void C.g__L|0_0(ref readonly int)""
+ IL_000e: ldloca.s V_0
+ IL_0010: call ""void C.g__L|0_0(ref readonly int)""
+ IL_0015: ldloc.0
+ IL_0016: ldc.i4.1
+ IL_0017: add
+ IL_0018: stloc.0
+ IL_0019: ldloc.0
+ IL_001a: ldc.i4.3
+ IL_001b: blt.s IL_0004
+ IL_001d: ret
+}");
+ }
+
+ [Fact]
+ public void RefReturnAssign()
+ {
+ var verifier = CompileAndVerify(@"
+class C
+{
+ static void M()
+ {
+ ref readonly int x = ref Helper();
+ int y = x + 1;
+ }
+
+ static ref readonly int Helper()
+ => ref (new int[1])[0];
+}");
+ verifier.VerifyIL("C.M()", @"
+{
+ // Code size 11 (0xb)
+ .maxstack 1
+ .locals init (int V_0)
+ IL_0000: call ""ref readonly int C.Helper()""
+ IL_0005: ldind.i4
+ IL_0006: stloc.0
+ IL_0007: ldloca.s V_0
+ IL_0009: pop
+ IL_000a: ret
+}");
+ }
+
+ [Fact]
+ public void RefReturnAssign2()
+ {
+ var verifier = CompileAndVerify(@"
+class C
+{
+ static void M()
+ {
+ ref readonly int x = ref Helper();
+ int y = x + 1;
+ }
+
+ static ref int Helper()
+ => ref (new int[1])[0];
+}");
+ verifier.VerifyIL("C.M()", @"
+{
+ // Code size 7 (0x7)
+ .maxstack 1
+ IL_0000: call ""ref int C.Helper()""
+ IL_0005: pop
+ IL_0006: ret
+}");
+ }
+
+
[Fact]
public void RefReturnArrayAccess()
{
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefLocalsAndReturnsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefLocalsAndReturnsTests.cs
index cee235464bb53ea5d1d7c784b6e09a5c6d699228..fb3573e2dd8fd2010b2cb83be7dd3b92a61a079d 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefLocalsAndReturnsTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefLocalsAndReturnsTests.cs
@@ -22,6 +22,361 @@ public class RefLocalsAndReturnsTests : CompilingTestBase
return CreateCompilationWithMscorlib45(text);
}
+ [Fact]
+ public void RefReadonlyOnlyIn72()
+ {
+ var tree = SyntaxFactory.ParseSyntaxTree(@"
+class C
+{
+ void M()
+ {
+ int x = 0;
+ ref readonly int y = ref x;
+ }
+}", options: TestOptions.Regular7_1);
+ var comp = CreateStandardCompilation(tree);
+ comp.VerifyDiagnostics(
+ // (7,13): error CS8302: Feature 'readonly references' is not available in C# 7.1. Please use language version 7.2 or greater.
+ // ref readonly int y = ref x;
+ Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_1, "readonly").WithArguments("readonly references", "7.2").WithLocation(7, 13));
+ }
+
+ [Fact]
+ public void CovariantConversionRefReadonly()
+ {
+ var comp = CreateStandardCompilation(@"
+class C
+{
+ void M()
+ {
+ string s = string.Empty;
+ ref readonly object x = ref s;
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (7,37): error CS8173: The expression must be of type 'object' because it is being assigned by reference
+ // ref readonly object x = ref s;
+ Diagnostic(ErrorCode.ERR_RefAssignmentMustHaveIdentityConversion, "s").WithArguments("object").WithLocation(7, 37));
+ }
+
+ [Fact]
+ public void ImplicitNumericRefReadonlyConversion()
+ {
+ var comp = CreateStandardCompilation(@"
+class C
+{
+ void M()
+ {
+ int x = 0;
+ ref readonly long y = ref x;
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (7,35): error CS8173: The expression must be of type 'long' because it is being assigned by reference
+ // ref readonly long y = ref x;
+ Diagnostic(ErrorCode.ERR_RefAssignmentMustHaveIdentityConversion, "x").WithArguments("long").WithLocation(7, 35));
+ }
+
+ [Fact]
+ public void RefReadonlyLocalToLiteral()
+ {
+ var comp = CreateStandardCompilation(@"
+class C
+{
+ void M()
+ {
+ ref readonly int x = ref 42;
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (6,34): error CS8156: An expression cannot be used in this context because it may not be returned by reference
+ // ref readonly int x = ref 42;
+ Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "42").WithLocation(6, 34));
+ }
+
+ [Fact]
+ public void RefReadonlyNoCaptureInLambda()
+ {
+ var comp = CreateStandardCompilation(@"
+using System;
+class C
+{
+ void M()
+ {
+ ref readonly int x = ref (new int[1])[0];
+ Action a = () =>
+ {
+ int i = x;
+ };
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (10,21): error CS8175: Cannot use ref local 'x' inside an anonymous method, lambda expression, or query expression
+ // int i = x;
+ Diagnostic(ErrorCode.ERR_AnonDelegateCantUseLocal, "x").WithArguments("x").WithLocation(10, 21));
+ }
+
+ [Fact]
+ public void RefReadonlyInLambda()
+ {
+ var comp = CreateStandardCompilation(@"
+using System;
+class C
+{
+ void M()
+ {
+ Action a = () =>
+ {
+ ref readonly int x = ref (new int[1])[0];
+ int i = x;
+ };
+ }
+}");
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void RefReadonlyNoCaptureInLocalFunction()
+ {
+ var comp = CreateStandardCompilation(@"
+class C
+{
+ void M()
+ {
+ ref readonly int x = ref (new int[1])[0];
+ void Local()
+ {
+ int i = x;
+ }
+ Local();
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (9,21): error CS8175: Cannot use ref local 'x' inside an anonymous method, lambda expression, or query expression
+ // int i = x;
+ Diagnostic(ErrorCode.ERR_AnonDelegateCantUseLocal, "x").WithArguments("x").WithLocation(9, 21));
+ }
+
+ [Fact]
+ public void RefReadonlyInLocalFunction()
+ {
+ var comp = CreateStandardCompilation(@"
+class C
+{
+ void M()
+ {
+ void Local()
+ {
+ ref readonly int x = ref (new int[1])[0];
+ int i = x;
+ }
+ Local();
+ }
+}");
+ comp.VerifyDiagnostics();
+ }
+
+ [Fact]
+ public void RefReadonlyInAsync()
+ {
+ var comp = CreateCompilationWithMscorlib46(@"
+using System.Threading.Tasks;
+class C
+{
+ async Task M()
+ {
+ ref readonly int x = ref (new int[1])[0];
+ int i = x;
+ await Task.FromResult(false);
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (7,26): error CS8177: Async methods cannot have by reference locals
+ // ref readonly int x = ref (new int[1])[0];
+ Diagnostic(ErrorCode.ERR_BadAsyncLocalType, "x = ref (new int[1])[0]").WithLocation(7, 26));
+ }
+
+ [Fact]
+ public void RefReadonlyInIterator()
+ {
+ var comp = CreateStandardCompilation(@"
+using System.Collections.Generic;
+class C
+{
+ IEnumerable M()
+ {
+ ref readonly int x = ref (new int[1])[0];
+ int i = x;
+ yield return i;
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (7,26): error CS8176: Iterators cannot have by reference locals
+ // ref readonly int x = ref (new int[1])[0];
+ Diagnostic(ErrorCode.ERR_BadIteratorLocalType, "x").WithLocation(7, 26));
+ }
+
+ [Fact]
+ public void RefReadonlyLocalNotWritable()
+ {
+ var comp = CreateStandardCompilation(@"
+struct S
+{
+ public int X;
+ public S(int x) => X = x;
+
+ public void AddOne() => this.X++;
+}
+
+class C
+{
+ void M()
+ {
+ S s = new S(0);
+ ref readonly S rs = ref s;
+ s.X++;
+ rs.X++;
+ s.AddOne();
+ rs.AddOne();
+ s.X = 0;
+ rs.X = 0;
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (17,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer
+ // rs.X++;
+ Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "rs.X").WithLocation(17, 9),
+ // (21,9): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
+ // rs.X = 0;
+ Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "rs.X").WithLocation(21, 9));
+ }
+
+ [Fact]
+ public void StripReadonlyInReturn()
+ {
+ var comp = CreateStandardCompilation(@"
+class C
+{
+ ref int M(ref int p)
+ {
+ ref readonly int rp = ref p;
+ return ref rp;
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (7,20): error CS8156: An expression cannot be used in this context because it may not be returned by reference
+ // return ref rp;
+ Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "rp").WithLocation(7, 20));
+ }
+
+ [Fact]
+ public void MixingRefParams()
+ {
+ var comp = CreateStandardCompilation(@"
+class C
+{
+ void M()
+ {
+ void L(ref int x, ref readonly int y)
+ {
+ L(ref x, y);
+ L(ref y, x);
+ L(ref x, ref x);
+
+ ref readonly int xr = ref x;
+ L(ref x, xr);
+ L(ref x, ref xr);
+ L(ref xr, y);
+ }
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (9,19): error CS8329: Cannot use variable 'ref readonly int' as a ref or out value because it is a readonly variable
+ // L(ref y, x);
+ Diagnostic(ErrorCode.ERR_RefReadonlyNotField, "y").WithArguments("variable", "ref readonly int").WithLocation(9, 19),
+ // (10,26): error CS1615: Argument 2 may not be passed with the 'ref' keyword
+ // L(ref x, ref x);
+ Diagnostic(ErrorCode.ERR_BadArgExtraRef, "x").WithArguments("2", "ref").WithLocation(10, 26),
+ // (14,26): error CS1510: A ref or out value must be an assignable variable
+ // L(ref x, ref xr);
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "xr").WithLocation(14, 26),
+ // (15,19): error CS1510: A ref or out value must be an assignable variable
+ // L(ref xr, y);
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "xr").WithLocation(15, 19));
+ }
+
+ [Fact]
+ public void AssignRefReadonlyToRefParam()
+ {
+ var comp = CreateCompilationRef(@"
+class C
+{
+ void M()
+ {
+ void L(ref int p) { }
+
+ L(ref 42);
+ int x = 0;
+ ref readonly int xr = ref x;
+ L(xr);
+ L(ref xr);
+
+ ref readonly int L2() => ref (new int[1])[0];
+
+ L(L2());
+ L(ref L2());
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (8,15): error CS1510: A ref or out value must be an assignable variable
+ // L(ref 42);
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "42").WithLocation(8, 15),
+ // (11,11): error CS1620: Argument 1 must be passed with the 'ref' keyword
+ // L(xr);
+ Diagnostic(ErrorCode.ERR_BadArgRef, "xr").WithArguments("1", "ref").WithLocation(11, 11),
+ // (12,15): error CS1510: A ref or out value must be an assignable variable
+ // L(ref xr);
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "xr").WithLocation(12, 15),
+ // (16,11): error CS1620: Argument 1 must be passed with the 'ref' keyword
+ // L(L2());
+ Diagnostic(ErrorCode.ERR_BadArgRef, "L2()").WithArguments("1", "ref").WithLocation(16, 11),
+ // (17,15): error CS8406: Cannot use method 'L2()' as a ref or out value because it is a readonly variable
+ // L(ref L2());
+ Diagnostic(ErrorCode.ERR_RefReadonlyNotField, "L2()").WithArguments("method", "L2()").WithLocation(17, 15));
+ }
+
+ [Fact]
+ public void AssignRefReadonlyLocalToRefLocal()
+ {
+ var comp = CreateCompilationRef(@"
+class C
+{
+ void M()
+ {
+ ref readonly int L() => ref (new int[1])[0];
+
+ ref int w = ref L();
+ ref readonly int x = ref L();
+ ref int y = x;
+ ref int z = ref x;
+ }
+}");
+ comp.VerifyDiagnostics(
+ // (8,25): error CS8406: Cannot use method 'L()' as a ref or out value because it is a readonly variable
+ // ref int w = ref L();
+ Diagnostic(ErrorCode.ERR_RefReadonlyNotField, "L()").WithArguments("method", "L()").WithLocation(8, 25),
+ // (10,17): error CS8172: Cannot initialize a by-reference variable with a value
+ // ref int y = x;
+ Diagnostic(ErrorCode.ERR_InitializeByReferenceVariableWithValue, "y = x").WithLocation(10, 17),
+ // (10,21): error CS1510: A ref or out value must be an assignable variable
+ // ref int y = x;
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "x").WithLocation(10, 21),
+ // (11,25): error CS1510: A ref or out value must be an assignable variable
+ // ref int z = ref x;
+ Diagnostic(ErrorCode.ERR_RefLvalueExpected, "x").WithLocation(11, 25)
+ );
+ }
+
[Fact]
public void RefLocalMissingInitializer()
{
diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyReturnsTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyReturnsTests.cs
index 02346b6db86ab9b5171e50427144955a5d482709..b33ce2f9a2b39c55dba54beec293c5d4857a5b09 100644
--- a/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyReturnsTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyReturnsTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// 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 Xunit;
using System.Linq;
@@ -138,9 +138,6 @@ static void Use(T dummy)
";
var comp = CreateCompilationWithMscorlib45(text, new[] { ValueTupleRef, SystemRuntimeFacadeRef });
comp.VerifyDiagnostics(
- // (7,9): error CS1073: Unexpected token 'ref'
- // ref readonly int local = ref (new int[1])[0];
- Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(7, 9),
// (9,10): error CS1073: Unexpected token 'ref'
// (ref readonly int, ref readonly int Alice)? t = null;
Diagnostic(ErrorCode.ERR_UnexpectedToken, "ref").WithArguments("ref").WithLocation(9, 10),