diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index df47e679fd665..a23da4551f2f3 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -4624,6 +4624,13 @@ uint getIndexerEscape( uint receiverEscapeScope = CallingMethodScope; foreach (var escapeValue in escapeValues) { + // This is a call to an indexer so the ref escape scope can only impact the escape value if it + // can be assigned to `this`. Return Only can't do this. + if (escapeValue.IsRefEscape && escapeValue.EscapeLevel != EscapeLevel.CallingMethod) + { + continue; + } + uint escapeScope = escapeValue.IsRefEscape ? GetRefEscape(escapeValue.Argument, scopeOfTheContainingExpression) : GetValEscape(escapeValue.Argument, scopeOfTheContainingExpression); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index d81418ff4961c..a160608b25aa9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -3369,6 +3369,89 @@ static void Test() ); } + [Fact] + public void RefLikeEscapeMixingObjectInitializer4() + { + var tree = SyntaxFactory.ParseSyntaxTree(""" + using System; + + public ref struct S1 + { + public S1(ref Span span) { } + int this[in Span span] { get => 0; set {} } + + static void Test() + { + Span stackSpan = stackalloc int[10]; + Span heapSpan = default; + + var local1 = new S1(ref heapSpan) { [heapSpan] = 0 }; + var local2 = new S1(ref heapSpan) { [stackSpan] = 0 }; // 1 + var local3 = new S1(ref stackSpan) { [heapSpan] = 0 }; + var local4 = new S1(ref stackSpan) { [stackSpan] = 0 }; + } + } + + public ref struct S2 + { + public S2(scoped ref Span span) { } + int this[Span span] { get => 0; set {} } + + static void Test() + { + Span stackSpan = stackalloc int[10]; + Span heapSpan = default; + + var local1 = new S2(ref heapSpan) { [heapSpan] = 0 }; + var local2 = new S2(ref heapSpan) { [stackSpan] = 0 }; + var local3 = new S2(ref stackSpan) { [heapSpan] = 0 }; + var local4 = new S2(ref stackSpan) { [stackSpan] = 0 }; + } + } + + public ref struct S3 + { + public S3(ref Span span) { } + int this[scoped Span span] { get => 0; set {} } + + static void Test() + { + Span stackSpan = stackalloc int[10]; + Span heapSpan = default; + + var local1 = new S3(ref heapSpan) { [heapSpan] = 0 }; + var local2 = new S3(ref heapSpan) { [stackSpan] = 0 }; + var local3 = new S3(ref stackSpan) { [heapSpan] = 0 }; + var local4 = new S3(ref stackSpan) { [stackSpan] = 0 }; + } + } + + public struct S5 + { + public S5(Span span) { } + Span this[int index] { get => default; set { } } + + static void Test() + { + Span stackSpan = stackalloc int[] { 13 }; + Span heapSpan = default; + + S5 local; + local = new S5(stackSpan) { [0] = stackSpan }; + local = new S5(stackSpan) { [0] = heapSpan }; + local = new S5(heapSpan) { [0] = stackSpan }; + local = new S5(heapSpan) { [0] = heapSpan }; + } + } + """, TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp11)); + var comp = CreateCompilationWithSpan(tree, TestOptions.UnsafeDebugDll); + comp.VerifyEmitDiagnostics( + // (14,33): error CS8350: This combination of arguments to 'S1.S1(ref Span)' is disallowed because it may expose variables referenced by parameter 'span' outside of their declaration scope + // var local2 = new S1(ref heapSpan) { [stackSpan] = 0 }; // 1 + Diagnostic(ErrorCode.ERR_CallArgMixing, "heapSpan").WithArguments("S1.S1(ref System.Span)", "span").WithLocation(14, 33) + ); + } + [Theory] [InlineData(LanguageVersion.CSharp10)] [InlineData(LanguageVersion.CSharp11)] @@ -3946,6 +4029,103 @@ static void Test() ); } + [Fact] + public void ObjectInitializer_Indexer_In_Escape() + { + var code = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public ref struct S1 + { + public S1() { } + string this[in int x] { get => default; set { } } + + static void Test(int[] array, int x) + { + int y = 0; + S1 local; + + local = new() { [in array[0]] = "" }; + local = new() { [in x] = "" }; + local = new() { [in y] = "" }; + local = new() { [0] = "" }; + } + } + + public ref struct S2 + { + public S2() { } + string this[[UnscopedRef] in int x] { get => default; set { } } + + static void Test(int x, in int y, [UnscopedRef] in int z) + { + S2 local = default; + local = new() { [x] = "" }; // 1 + local = new() { [y] = "" }; // 2 + local = new() { [z] = "" }; + local[x] = ""; // 3 + local[y] = ""; // 4 + local[z] = ""; + } + } + """; + + var comp = CreateCompilationWithSpan([code, UnscopedRefAttributeDefinition], TestOptions.UnsafeDebugDll, TestOptions.Regular11); + comp.VerifyEmitDiagnostics( + // (29,23): error CS8352: Cannot use variable '[x] = ""' in this context because it may expose referenced variables outside of their declaration scope + // local = new() { [x] = "" }; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, @"{ [x] = """" }").WithArguments(@"[x] = """"").WithLocation(29, 23), + // (30,23): error CS8352: Cannot use variable '[y] = ""' in this context because it may expose referenced variables outside of their declaration scope + // local = new() { [y] = "" }; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, @"{ [y] = """" }").WithArguments(@"[y] = """"").WithLocation(30, 23), + // (32,9): error CS8350: This combination of arguments to 'S2.this[in int]' is disallowed because it may expose variables referenced by parameter 'x' outside of their declaration scope + // local[x] = ""; // 3 + Diagnostic(ErrorCode.ERR_CallArgMixing, "local[x]").WithArguments("S2.this[in int]", "x").WithLocation(32, 9), + // (32,15): error CS8166: Cannot return a parameter by reference 'x' because it is not a ref parameter + // local[x] = ""; // 3 + Diagnostic(ErrorCode.ERR_RefReturnParameter, "x").WithArguments("x").WithLocation(32, 15), + // (33,9): error CS8350: This combination of arguments to 'S2.this[in int]' is disallowed because it may expose variables referenced by parameter 'x' outside of their declaration scope + // local[y] = ""; // 4 + Diagnostic(ErrorCode.ERR_CallArgMixing, "local[y]").WithArguments("S2.this[in int]", "x").WithLocation(33, 9), + // (33,15): error CS9077: Cannot return a parameter by reference 'y' through a ref parameter; it can only be returned in a return statement + // local[y] = ""; // 4 + Diagnostic(ErrorCode.ERR_RefReturnOnlyParameter, "y").WithArguments("y").WithLocation(33, 15) + ); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/66056")] + public void ObjectInitializer_Indexer_Unscoped() + { + var code = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public ref struct S1 + { + public S1() { } + string this[[UnscopedRef] in int x] { get => default; set { } } + + static S1 Test1(int x) => new S1() { [in x] = "" }; // 1 + static S1 Test2(in int x) => new S1() { [in x] = "" }; + static S1 Test3(scoped in int x) => new S1() { [in x] = "" }; // 2 + static S1 Test3(int[] x) => new S1() { [in x[0]] = "" }; + } + + """; + + var comp = CreateCompilationWithSpan([code, UnscopedRefAttributeDefinition], TestOptions.UnsafeDebugDll, TestOptions.Regular11); + comp.VerifyEmitDiagnostics( + // (9,40): error CS8352: Cannot use variable '[in x] = ""' in this context because it may expose referenced variables outside of their declaration scope + // static S1 Test1(int x) => new S1() { [in x] = "" }; // 1 + Diagnostic(ErrorCode.ERR_EscapeVariable, @"{ [in x] = """" }").WithArguments(@"[in x] = """"").WithLocation(9, 40), + // (11,50): error CS8352: Cannot use variable '[in x] = ""' in this context because it may expose referenced variables outside of their declaration scope + // static S1 Test3(scoped in int x) => new S1() { [in x] = "" }; // 2 + Diagnostic(ErrorCode.ERR_EscapeVariable, @"{ [in x] = """" }").WithArguments(@"[in x] = """"").WithLocation(11, 50) + ); + } + [Theory] [InlineData(LanguageVersion.CSharp10)] [InlineData(LanguageVersion.CSharp11)]