diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index c16043abf69c1..0504cc9d31cae 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 d9da41aa6e466..d591bc14ae6b2 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 a7866cef40996..7274ae7d9e47e 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 5e4d7a7e3315a..1964995dd474d 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 d165b8dc024d7..a0eb06901ae81 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 cee235464bb53..fb3573e2dd8fd 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 02346b6db86ab..b33ce2f9a2b39 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),