From d4a1eac07963a5b8d06c87df4dbe57dec152de75 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 17 Oct 2025 17:15:54 -0700 Subject: [PATCH 1/3] Ensure that locals at the top level of a constructor have the same safe-context as parameters --- .../Portable/Binder/RefSafetyAnalysis.cs | 42 ++- .../Semantics/CollectionExpressionTests.cs | 138 ++++++++++ .../Test/Semantic/Semantics/RefFieldTests.cs | 239 ++++++++++++++++++ 3 files changed, 408 insertions(+), 11 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs index dc0c889e7b5d0..f14dd2fd3a9b5 100644 --- a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs +++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs @@ -21,6 +21,7 @@ internal static void Analyze(CSharpCompilation compilation, MethodSymbol symbol, var visitor = new RefSafetyAnalysis( compilation, symbol, + node, inUnsafeRegion: InUnsafeMethod(symbol), useUpdatedEscapeRules: symbol.ContainingModule.UseUpdatedEscapeRules, diagnostics); @@ -57,6 +58,7 @@ private static bool InUnsafeMethod(Symbol symbol) private readonly CSharpCompilation _compilation; private readonly MethodSymbol _symbol; + private readonly BoundNode _rootNode; private readonly bool _useUpdatedEscapeRules; private readonly BindingDiagnosticBag _diagnostics; private bool _inUnsafeRegion; @@ -72,31 +74,35 @@ private static bool InUnsafeMethod(Symbol symbol) private RefSafetyAnalysis( CSharpCompilation compilation, MethodSymbol symbol, + BoundNode rootNode, bool inUnsafeRegion, bool useUpdatedEscapeRules, BindingDiagnosticBag diagnostics) { _compilation = compilation; _symbol = symbol; + _rootNode = rootNode; _useUpdatedEscapeRules = useUpdatedEscapeRules; _diagnostics = diagnostics; _inUnsafeRegion = inUnsafeRegion; - // _localScopeDepth is incremented at each block in the method, including the - // outermost. To ensure that locals in the outermost block are considered at - // the same depth as parameters, _localScopeDepth is initialized to one less. - _localScopeDepth = SafeContext.CurrentMethod.Wider(); + _localScopeDepth = SafeContext.CurrentMethod; } private ref struct LocalScope { private readonly RefSafetyAnalysis _analysis; private readonly ImmutableArray _locals; + private readonly bool _adjustDepth; - public LocalScope(RefSafetyAnalysis analysis, ImmutableArray locals) + /// When true, narrows when the instance is created, and widens it when the instance is disposed. + public LocalScope(RefSafetyAnalysis analysis, ImmutableArray locals, bool adjustDepth = true) { _analysis = analysis; _locals = locals; - _analysis._localScopeDepth = _analysis._localScopeDepth.Narrower(); + _adjustDepth = adjustDepth; + if (adjustDepth) + _analysis._localScopeDepth = _analysis._localScopeDepth.Narrower(); + foreach (var local in locals) { _analysis.AddLocalScopes(local, refEscapeScope: _analysis._localScopeDepth, valEscapeScope: SafeContext.CallingMethod); @@ -109,7 +115,9 @@ public void Dispose() { _analysis.RemoveLocalScopes(local); } - _analysis._localScopeDepth = _analysis._localScopeDepth.Wider(); + + if (_adjustDepth) + _analysis._localScopeDepth = _analysis._localScopeDepth.Wider(); } } @@ -290,7 +298,18 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder) public override BoundNode? VisitBlock(BoundBlock node) { using var _1 = new UnsafeRegion(this, _inUnsafeRegion || node.HasUnsafeModifier); - using var _2 = new LocalScope(this, node.Locals); + + // Do not increase the depth if this is the top-level block of a method body. + // This is needed to ensure that top-level locals have the same lifetime as by-value parameters, for example. + bool adjustDepth = _rootNode switch + { + BoundConstructorMethodBody constructorBody => constructorBody.BlockBody != node && constructorBody.ExpressionBody != node, + BoundNonConstructorMethodBody methodBody => methodBody.BlockBody != node && methodBody.ExpressionBody != node, + BoundLambda lambda => lambda.Body != node, + BoundLocalFunctionStatement localFunction => localFunction.Body != node, + _ => true, + }; + using var _2 = new LocalScope(this, node.Locals, adjustDepth); return base.VisitBlock(node); } @@ -350,7 +369,7 @@ private void AssertVisited(BoundExpression expr) public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node) { var localFunction = (LocalFunctionSymbol)node.Symbol; - var analysis = new RefSafetyAnalysis(_compilation, localFunction, _inUnsafeRegion || localFunction.IsUnsafe, _useUpdatedEscapeRules, _diagnostics); + var analysis = new RefSafetyAnalysis(_compilation, localFunction, node, _inUnsafeRegion || localFunction.IsUnsafe, _useUpdatedEscapeRules, _diagnostics); analysis.Visit(node.BlockBody); analysis.Visit(node.ExpressionBody); return null; @@ -359,14 +378,15 @@ private void AssertVisited(BoundExpression expr) public override BoundNode? VisitLambda(BoundLambda node) { var lambda = node.Symbol; - var analysis = new RefSafetyAnalysis(_compilation, lambda, _inUnsafeRegion, _useUpdatedEscapeRules, _diagnostics); + var analysis = new RefSafetyAnalysis(_compilation, lambda, node, _inUnsafeRegion, _useUpdatedEscapeRules, _diagnostics); analysis.Visit(node.Body); return null; } public override BoundNode? VisitConstructorMethodBody(BoundConstructorMethodBody node) { - using var _ = new LocalScope(this, node.Locals); + // Variables in a constructor initializer like `public MyType(int x) : this(M(out int y))` have the same scope as the parameters. + using var _ = new LocalScope(this, node.Locals, adjustDepth: false); return base.VisitConstructorMethodBody(node); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs index 0a1989896e29c..01b23a0f60247 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs @@ -26680,6 +26680,144 @@ .locals init (System.ReadOnlySpan V_0, //r1 """); } + [Fact] + public void SpanAssignment_ScopedParameter_Constructor_Span() + { + string source = """ + using System; + + class C + { + public C(scoped ReadOnlySpan items) + { + int x = 0; + items = new ReadOnlySpan(ref x); + } + + void M(scoped ReadOnlySpan items) + { + int x = 0; + items = new ReadOnlySpan(ref x); + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void SpanAssignment_ScopedParameter_Constructor_CollectionExpression() + { + string source = """ + using System; + + class C + { + public C(scoped ReadOnlySpan items) + { + int x = 0; + items = [x]; + } + + void M(scoped ReadOnlySpan items) + { + int x = 0; + items = [x]; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void SpanAssignment_ScopedParameter_PrimaryConstructor_Span() + { + // Each field initializer can be considered its own local scope as variables declared within it are not visible to the other initializers. + string source = """ + using System; + + class C(scoped ReadOnlySpan items) // 1 + { + private int x = M0(M1(out int x1), items = new ReadOnlySpan(ref x1)); // 2, 3 + private int y = x1; // 4 + + static int M0(int x0, ReadOnlySpan items) => items[0]; + static int M1(out int x1) { return x1 = 0; } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,34): warning CS9113: Parameter 'items' is unread. + // class C(scoped ReadOnlySpan items) // 1 + Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "items").WithArguments("items").WithLocation(3, 34), + // (5,48): error CS8347: Cannot use a result of 'ReadOnlySpan.ReadOnlySpan(ref readonly int)' in this context because it may expose variables referenced by parameter 'reference' outside of their declaration scope + // private int x = M0(M1(out int x1), items = new ReadOnlySpan(ref x1)); // 2, 3 + Diagnostic(ErrorCode.ERR_EscapeCall, "new ReadOnlySpan(ref x1)").WithArguments("System.ReadOnlySpan.ReadOnlySpan(ref readonly int)", "reference").WithLocation(5, 48), + // (5,74): error CS8352: Cannot use variable 'x1' in this context because it may expose referenced variables outside of their declaration scope + // private int x = M0(M1(out int x1), items = new ReadOnlySpan(ref x1)); // 2, 3 + Diagnostic(ErrorCode.ERR_EscapeVariable, "x1").WithArguments("x1").WithLocation(5, 74), + // (6,21): error CS0103: The name 'x1' does not exist in the current context + // private int y = x1; // 4 + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 21)); + } + + [Fact] + public void SpanAssignment_StaticFieldInitializer() + { + // Each field initializer can be considered its own local scope as variables declared within it are not visible to the other initializers. + string source = """ + using System; + + class C + { + private static int x = M0(M1(out ReadOnlySpan items, out int x1), items = new ReadOnlySpan(ref x1)); // 1, 2 + private static int y = items[0]; // 3 + + static int M0(int x0, ReadOnlySpan items) => items[0]; + static int M1(out ReadOnlySpan span, out int x1) { span = default; return x1 = 0; } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (5,84): error CS8347: Cannot use a result of 'ReadOnlySpan.ReadOnlySpan(ref readonly int)' in this context because it may expose variables referenced by parameter 'reference' outside of their declaration scope + // private static int x = M0(M1(out ReadOnlySpan items, out int x1), items = new ReadOnlySpan(ref x1)); // 1, 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "new ReadOnlySpan(ref x1)").WithArguments("System.ReadOnlySpan.ReadOnlySpan(ref readonly int)", "reference").WithLocation(5, 84), + // (5,110): error CS8168: Cannot return local 'x1' by reference because it is not a ref local + // private static int x = M0(M1(out ReadOnlySpan items, out int x1), items = new ReadOnlySpan(ref x1)); // 1, 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "x1").WithArguments("x1").WithLocation(5, 110), + // (6,28): error CS0103: The name 'items' does not exist in the current context + // private static int y = items[0]; // 3 + Diagnostic(ErrorCode.ERR_NameNotInContext, "items").WithArguments("items").WithLocation(6, 28)); + } + + [Fact] + public void SpanAssignment_ScopedParameter_PrimaryConstructor_CollectionExpression() + { + // '[M1()]' cannot be assigned to 'items' because its backing storage lives in a narrower scope than 'items'. + string source = """ + using System; + + class C(scoped ReadOnlySpan items) + { + private int x = M0(items = [M1()]); + + static int M0(ReadOnlySpan x) => x[0]; + static int M1() => 0; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,34): warning CS9113: Parameter 'items' is unread. + // class C(scoped ReadOnlySpan items) + Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "items").WithArguments("items").WithLocation(3, 34) + // This error will be missing until changes from other PR are integrated: https://github.com/dotnet/roslyn/pull/80684 + // // (5,32): error CS9203: A collection expression of type 'ReadOnlySpan' cannot be used in this context because it may be exposed outside of the current scope. + // // private int x = M0(items = [M1()]); + // Diagnostic(ErrorCode.ERR_CollectionExpressionEscape, "[M1()]").WithArguments("System.ReadOnlySpan").WithLocation(5, 32) + ); + } + [Fact] public void SpanAssignment_WithUsingDeclaration() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index f6545643f1ca6..40eb4f339d520 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -30413,6 +30413,245 @@ ref struct R Diagnostic(ErrorCode.ERR_RefReturnParameter, "x").WithArguments("x").WithLocation(4, 28)); } + [Fact] + public void ConstructorInitializer_03() + { + // Verify that a ref to local declared in a constructor initializer cannot escape into 'this' + var source = """ + ref struct R + { + R(int x, ref int y) { } + public R() : this(M(out int x), ref x) // 1, 2 + { + } + + public static int M(out int x) => x = 1; + } + """; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,16): error CS8350: This combination of arguments to 'R.R(int, ref int)' is disallowed because it may expose variables referenced by parameter 'y' outside of their declaration scope + // public R() : this(M(out int x), ref x) // 1, 2 + Diagnostic(ErrorCode.ERR_CallArgMixing, ": this(M(out int x), ref x)").WithArguments("R.R(int, ref int)", "y").WithLocation(4, 16), + // (4,41): error CS8168: Cannot return local 'x' by reference because it is not a ref local + // public R() : this(M(out int x), ref x) // 1, 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "x").WithArguments("x").WithLocation(4, 41)); + } + + [Fact] + public void ConstructorInitializer_04() + { + // Verify that a local in a constructor initializer is assignable to a scoped ref struct parameter + var source = """ + using System; + + struct R + { + R(int x, int y) { } + public R(scoped Span span) : this(M(out int x), (span = new Span(ref x))[0]) + { + } + + public static int M(out int x) => x = 1; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ConstructorInitializer_05() + { + // Verify that a local in a constructor initializer is not assignable to a ref struct parameter + var source = """ + using System; + + struct R + { + R(int x, int y) { } + public R(Span span) : this(M(out int x), (span = new Span(ref x))[0]) + { + } + + public static int M(out int x) => x = 1; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (6,59): error CS8347: Cannot use a result of 'Span.Span(ref int)' in this context because it may expose variables referenced by parameter 'reference' outside of their declaration scope + // public R(Span span) : this(M(out int x), (span = new Span(ref x))[0]) + Diagnostic(ErrorCode.ERR_EscapeCall, "new Span(ref x)").WithArguments("System.Span.Span(ref int)", "reference").WithLocation(6, 59), + // (6,77): error CS8168: Cannot return local 'x' by reference because it is not a ref local + // public R(Span span) : this(M(out int x), (span = new Span(ref x))[0]) + Diagnostic(ErrorCode.ERR_RefReturnLocal, "x").WithArguments("x").WithLocation(6, 77)); + } + + [Fact] + public void OutParamVsConstructor_01() + { + // Expect 'this' in a constructor to behave similarly to an 'out' parameter in an ordinary method with respect to ref safety. + var source = """ + ref struct R + { + private ref readonly int _i; + private R(in int i) => _i = ref i; // ok + + void M1(out R r, in int i) + => r = new R(in i); // ok + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics(); + } + + [Fact] + public void OutParamVsConstructor_02() + { + var source = """ + ref struct R + { + private ref readonly int _i; + private R(in int i) => _i = ref i; + + public R() + { + int i = 1; + this = new R(in i); // 1, 2 + } + + void M1(out R r) + { + int i = 1; + r = new R(in i); // 3, 4 + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (9,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // this = new R(in i); // 1, 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(in i)").WithArguments("R.R(in int)", "i").WithLocation(9, 16), + // (9,25): error CS8168: Cannot return local 'i' by reference because it is not a ref local + // this = new R(in i); // 1, 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(9, 25), + // (15,13): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // r = new R(in i); // 3, 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(in i)").WithArguments("R.R(in int)", "i").WithLocation(15, 13), + // (15,22): error CS8168: Cannot return local 'i' by reference because it is not a ref local + // r = new R(in i); // 3, 4 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(15, 22)); + } + + [Fact] + public void OutParamVsConstructor_03() + { + var source = """ + ref struct R + { + private ref readonly int _i; + private R(in int i) => _i = ref i; + + public R() + { + this = new R(1); // 1, 2 + } + + void M1(out R r) + => r = new R(1); // 3, 4 + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (8,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // this = new R(1); // 1, 2 + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(8, 16), + // (8,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // this = new R(1); // 1, 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(8, 22), + // (12,16): error CS8347: Cannot use a result of 'R.R(in int)' in this context because it may expose variables referenced by parameter 'i' outside of their declaration scope + // => r = new R(1); // 3, 4 + Diagnostic(ErrorCode.ERR_EscapeCall, "new R(1)").WithArguments("R.R(in int)", "i").WithLocation(12, 16), + // (12,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // => r = new R(1); // 3, 4 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(12, 22)); + } + + [Fact] + public void OutParamVsConstructor_04() + { + var source = """ + ref struct R + { + private ref readonly int _i; + private R(in int i) => _i = ref i; + + public R() + { + int i = 1; + M1(out this, in i); // 1, 2 + } + + void M1(out R r, in int i) + => r = new R(in i); // ok + + void M2(out R r) + { + int i = 1; + M1(out r, in i); // 3, 4 + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (9,9): error CS8350: This combination of arguments to 'R.M1(out R, in int)' is disallowed because it may expose variables referenced by parameter 'i' outside of their declaration scope + // M1(out this, in i); // 1, 2 + Diagnostic(ErrorCode.ERR_CallArgMixing, "M1(out this, in i)").WithArguments("R.M1(out R, in int)", "i").WithLocation(9, 9), + // (9,25): error CS8168: Cannot return local 'i' by reference because it is not a ref local + // M1(out this, in i); // 1, 2 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(9, 25), + // (18,9): error CS8350: This combination of arguments to 'R.M1(out R, in int)' is disallowed because it may expose variables referenced by parameter 'i' outside of their declaration scope + // M1(out r, in i); // 3, 4 + Diagnostic(ErrorCode.ERR_CallArgMixing, "M1(out r, in i)").WithArguments("R.M1(out R, in int)", "i").WithLocation(18, 9), + // (18,22): error CS8168: Cannot return local 'i' by reference because it is not a ref local + // M1(out r, in i); // 3, 4 + Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(18, 22)); + } + + [Fact] + public void OutParamVsConstructor_05() + { + var source = """ + ref struct R + { + private ref readonly int _i; + private R(in int i) => _i = ref i; + + void M1(out R r, in int i) + => r = new R(in i); + + public R() + => M1(out this, 1); // 1, 2 + + void M2(out R r) + => M1(out r, 1); // 3, 4 + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net70); + comp.VerifyDiagnostics( + // (10,12): error CS8350: This combination of arguments to 'R.M1(out R, in int)' is disallowed because it may expose variables referenced by parameter 'i' outside of their declaration scope + // => M1(out this, 1); // 1, 2 + Diagnostic(ErrorCode.ERR_CallArgMixing, "M1(out this, 1)").WithArguments("R.M1(out R, in int)", "i").WithLocation(10, 12), + // (10,25): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // => M1(out this, 1); // 1, 2 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(10, 25), + // (13,12): error CS8350: This combination of arguments to 'R.M1(out R, in int)' is disallowed because it may expose variables referenced by parameter 'i' outside of their declaration scope + // => M1(out r, 1); // 3, 4 + Diagnostic(ErrorCode.ERR_CallArgMixing, "M1(out r, 1)").WithArguments("R.M1(out R, in int)", "i").WithLocation(13, 12), + // (13,22): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference + // => M1(out r, 1); // 3, 4 + Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(13, 22)); + } + [Theory] [InlineData(LanguageVersion.CSharp10)] [InlineData(LanguageVersion.CSharp11)] From 554c6fe55512c2005f9ae8b5cfa3aeeba8eaf9f8 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Wed, 22 Oct 2025 12:53:13 -0700 Subject: [PATCH 2/3] Add WorkItemAttributes --- .../Emit3/Semantics/CollectionExpressionTests.cs | 10 +++++----- .../Test/Semantic/Semantics/RefFieldTests.cs | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs index 01b23a0f60247..58d5917d87b4d 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs @@ -26680,7 +26680,7 @@ .locals init (System.ReadOnlySpan V_0, //r1 """); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void SpanAssignment_ScopedParameter_Constructor_Span() { string source = """ @@ -26705,7 +26705,7 @@ void M(scoped ReadOnlySpan items) comp.VerifyEmitDiagnostics(); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void SpanAssignment_ScopedParameter_Constructor_CollectionExpression() { string source = """ @@ -26730,7 +26730,7 @@ void M(scoped ReadOnlySpan items) comp.VerifyEmitDiagnostics(); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void SpanAssignment_ScopedParameter_PrimaryConstructor_Span() { // Each field initializer can be considered its own local scope as variables declared within it are not visible to the other initializers. @@ -26762,7 +26762,7 @@ class C(scoped ReadOnlySpan items) // 1 Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 21)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void SpanAssignment_StaticFieldInitializer() { // Each field initializer can be considered its own local scope as variables declared within it are not visible to the other initializers. @@ -26791,7 +26791,7 @@ class C Diagnostic(ErrorCode.ERR_NameNotInContext, "items").WithArguments("items").WithLocation(6, 28)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void SpanAssignment_ScopedParameter_PrimaryConstructor_CollectionExpression() { // '[M1()]' cannot be assigned to 'items' because its backing storage lives in a narrower scope than 'items'. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index 40eb4f339d520..4c999cf04176d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -30413,7 +30413,7 @@ ref struct R Diagnostic(ErrorCode.ERR_RefReturnParameter, "x").WithArguments("x").WithLocation(4, 28)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void ConstructorInitializer_03() { // Verify that a ref to local declared in a constructor initializer cannot escape into 'this' @@ -30438,7 +30438,7 @@ public R() : this(M(out int x), ref x) // 1, 2 Diagnostic(ErrorCode.ERR_RefReturnLocal, "x").WithArguments("x").WithLocation(4, 41)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void ConstructorInitializer_04() { // Verify that a local in a constructor initializer is assignable to a scoped ref struct parameter @@ -30459,7 +30459,7 @@ struct R comp.VerifyDiagnostics(); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void ConstructorInitializer_05() { // Verify that a local in a constructor initializer is not assignable to a ref struct parameter @@ -30486,7 +30486,7 @@ struct R Diagnostic(ErrorCode.ERR_RefReturnLocal, "x").WithArguments("x").WithLocation(6, 77)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void OutParamVsConstructor_01() { // Expect 'this' in a constructor to behave similarly to an 'out' parameter in an ordinary method with respect to ref safety. @@ -30504,7 +30504,7 @@ void M1(out R r, in int i) comp.VerifyDiagnostics(); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void OutParamVsConstructor_02() { var source = """ @@ -30542,7 +30542,7 @@ void M1(out R r) Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(15, 22)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void OutParamVsConstructor_03() { var source = """ @@ -30576,7 +30576,7 @@ void M1(out R r) Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(12, 22)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void OutParamVsConstructor_04() { var source = """ @@ -30617,7 +30617,7 @@ void M2(out R r) Diagnostic(ErrorCode.ERR_RefReturnLocal, "i").WithArguments("i").WithLocation(18, 22)); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80745")] public void OutParamVsConstructor_05() { var source = """ From e2ce085d87482b45891e89624e1bc3f648a084d4 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 23 Oct 2025 09:49:25 -0700 Subject: [PATCH 3/3] Fix baseline --- .../Test/Emit3/Semantics/CollectionExpressionTests.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs index 58ec7a30cf151..a79ca40918614 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/CollectionExpressionTests.cs @@ -26691,11 +26691,10 @@ class C(scoped ReadOnlySpan items) comp.VerifyEmitDiagnostics( // (3,34): warning CS9113: Parameter 'items' is unread. // class C(scoped ReadOnlySpan items) - Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "items").WithArguments("items").WithLocation(3, 34) - // This error will be missing until changes from other PR are integrated: https://github.com/dotnet/roslyn/pull/80684 - // // (5,32): error CS9203: A collection expression of type 'ReadOnlySpan' cannot be used in this context because it may be exposed outside of the current scope. - // // private int x = M0(items = [M1()]); - // Diagnostic(ErrorCode.ERR_CollectionExpressionEscape, "[M1()]").WithArguments("System.ReadOnlySpan").WithLocation(5, 32) + Diagnostic(ErrorCode.WRN_UnreadPrimaryConstructorParameter, "items").WithArguments("items").WithLocation(3, 34), + // (5,32): error CS9203: A collection expression of type 'ReadOnlySpan' cannot be used in this context because it may be exposed outside of the current scope. + // private int x = M0(items = [M1()]); + Diagnostic(ErrorCode.ERR_CollectionExpressionEscape, "[M1()]").WithArguments("System.ReadOnlySpan").WithLocation(5, 32) ); }