diff --git a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs index d64b6cf5073bc..8d0500816c4fb 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Constructors.cs @@ -60,6 +60,7 @@ private static bool NeedsByValueFieldAccess(BoundExpression? receiver, FieldSymb { if (fieldSymbol.IsStatic || !fieldSymbol.ContainingType.IsValueType || + fieldSymbol.RefKind != RefKind.None || receiver == null) // receiver may be null in error cases { return false; diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenForEachTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenForEachTests.cs index f1ee673602927..fe3ac4dac44c2 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenForEachTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenForEachTests.cs @@ -5327,5 +5327,254 @@ public static class Extensions }"; CompileAndVerify(source, parseOptions: TestOptions.Regular9, expectedOutput: "123123"); } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_01( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + foreach (var r in new E(arr)) + { + r.V.F++; + } + + foreach (var v in arr) Console.Write(v.F); + + {{eRef}} struct E(V[] arr) + { + int i; + public E GetEnumerator() => this; + public R Current => new(ref arr[i - 1]); + public bool MoveNext() => i++ < arr.Length; + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + } + + struct V + { + public int F; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.Fails, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "111").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_02( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + foreach (var r in new E(arr)) + { + r.V.F += 2; + } + + foreach (var v in arr) Console.Write(v.F); + + {{eRef}} struct E(V[] arr) + { + int i; + public E GetEnumerator() => this; + public R Current => new(ref arr[i - 1]); + public bool MoveNext() => i++ < arr.Length; + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + } + + struct V + { + public int F; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.Fails, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "222").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_03( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + foreach (var r in new E(arr)) + { + r.V.S.Inc(); + } + + foreach (var v in arr) Console.Write(v.S.F); + + {{eRef}} struct E(V[] arr) + { + int i; + public E GetEnumerator() => this; + public R Current => new(ref arr[i - 1]); + public bool MoveNext() => i++ < arr.Length; + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + } + + struct V + { + public S S; + } + + struct S + { + public int F; + public void Inc() => F++; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.Fails, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "111").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_04( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + foreach (var r in new E(arr)) + { + r.V.F++; + } + + foreach (var v in arr) Console.Write(v.F); + + {{eRef}} struct E(V[] arr) + { + int i; + public E GetEnumerator() => this; + public R Current => new(ref arr[i - 1]); + public bool MoveNext() => i++ < arr.Length; + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref readonly V V = ref v; + } + + struct V + { + public int F; + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (7,5): error CS8332: Cannot assign to a member of field 'V' or use it as the right hand side of a ref assignment because it is a readonly variable + // r.V.F++; + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "r.V.F").WithArguments("field", "V").WithLocation(7, 5)); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_05( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + foreach (ref var r in new E(arr)) + { + r.S.F++; + } + + foreach (var v in arr) Console.Write(v.S.F); + + {{eRef}} struct E(V[] arr) + { + int i; + public E GetEnumerator() => this; + public {{vReadonly}} ref V Current => ref arr[i - 1]; + public bool MoveNext() => i++ < arr.Length; + } + + struct V + { + public S S; + } + + struct S + { + public int F; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.Skipped, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "111").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_06( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly, + [CombinatorialValues("readonly", "")] string vReadonlyInner) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + foreach (ref readonly var r in new E(arr)) + { + r.S.F++; + } + + foreach (var v in arr) Console.Write(v.S.F); + + {{eRef}} struct E(V[] arr) + { + int i; + public E GetEnumerator() => this; + public {{vReadonly}} ref {{vReadonlyInner}} V Current => ref arr[i - 1]; + public bool MoveNext() => i++ < arr.Length; + } + + struct V + { + public S S; + } + + struct S + { + public int F; + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (7,5): error CS1654: Cannot modify members of 'r' because it is a 'foreach iteration variable' + // r.S.F++; + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal2Cause, "r.S.F").WithArguments("r", "foreach iteration variable").WithLocation(7, 5)); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs index a0775de65acd8..9f922cac43a54 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenUsingStatementTests.cs @@ -4,6 +4,7 @@ #nullable disable +using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -3136,5 +3137,140 @@ ref struct S } } } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_01( + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V v = default; + + using (var d = new R(ref v)) + { + d.V.F++; + } + + Console.Write(v.F); + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + public void Dispose() { } + } + + struct V + { + public int F; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "1").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_02( + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V v = default; + + using (var d = new R(ref v)) + { + d.V.F += 2; + } + + Console.Write(v.F); + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + public void Dispose() { } + } + + struct V + { + public int F; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "2").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_03( + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V v = default; + + using (var d = new R(ref v)) + { + d.V.S.Inc(); + } + + Console.Write(v.S.F); + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + public void Dispose() { } + } + + struct V + { + public S S; + } + + struct S + { + public int F; + public void Inc() => F++; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "1").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_04( + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V v = default; + + using (var d = new R(ref v)) + { + d.V.F++; + } + + Console.Write(v.F); + + ref struct R(ref V v) + { + public {{vReadonly}} ref readonly V V = ref v; + public void Dispose() { } + } + + struct V + { + public int F; + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (7,5): error CS8332: Cannot assign to a member of field 'V' or use it as the right hand side of a ref assignment because it is a readonly variable + // d.V.F++; + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "d.V.F").WithArguments("field", "V").WithLocation(7, 5)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs index 34656039663f6..9cb7edb734d5d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs @@ -4556,5 +4556,188 @@ End Class Console.WriteLine(string.Join(string.Empty, test)); ", references: new[] { vb.EmitToImageReference() }, expectedOutput: "2"); } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_01( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + _ = from r in new E(arr) + select r.V.F++; + + foreach (var v in arr) Console.Write(v.F); + + delegate void D(R r); + + {{eRef}} struct E(V[] arr) + { + public int Select(D a) + { + for (var i = 0; i < arr.Length; i++) + { + a(new(ref arr[i])); + } + return 0; + } + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + } + + struct V + { + public int F; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "111").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_02( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + _ = from r in new E(arr) + select r.V.F += 2; + + foreach (var v in arr) Console.Write(v.F); + + delegate void D(R r); + + {{eRef}} struct E(V[] arr) + { + public int Select(D a) + { + for (var i = 0; i < arr.Length; i++) + { + a(new(ref arr[i])); + } + return 0; + } + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + } + + struct V + { + public int F; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "222").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_03( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + _ = from r in new E(arr) + select r.V.S.Inc(); + + foreach (var v in arr) Console.Write(v.S.F); + + delegate void D(R r); + + {{eRef}} struct E(V[] arr) + { + public int Select(D a) + { + for (var i = 0; i < arr.Length; i++) + { + a(new(ref arr[i])); + } + return 0; + } + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref V V = ref v; + } + + struct V + { + public S S; + } + + struct S + { + public int F; + public void Inc() => F++; + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net70, + verify: Verification.FailsPEVerify, + expectedOutput: ExecutionConditionUtil.IsDesktop ? null : "111").VerifyDiagnostics(); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/73741")] + public void MutatingThroughRefFields_04( + [CombinatorialValues("ref", "")] string eRef, + [CombinatorialValues("readonly", "")] string vReadonly) + { + var source = $$""" + using System; + + V[] arr = new V[3]; + + _ = from r in new E(arr) + select r.V.F++; + + foreach (var v in arr) Console.Write(v.F); + + delegate void D(R r); + + {{eRef}} struct E(V[] arr) + { + public int Select(D a) + { + for (var i = 0; i < arr.Length; i++) + { + a(new(ref arr[i])); + } + return 0; + } + } + + ref struct R(ref V v) + { + public {{vReadonly}} ref readonly V V = ref v; + } + + struct V + { + public int F; + } + """; + CreateCompilation(source, targetFramework: TargetFramework.Net70).VerifyDiagnostics( + // (6,12): error CS8332: Cannot assign to a member of field 'V' or use it as the right hand side of a ref assignment because it is a readonly variable + // select r.V.F++; + Diagnostic(ErrorCode.ERR_AssignReadonlyNotField2, "r.V.F").WithArguments("field", "V").WithLocation(6, 12)); + } } }