Skip to content

Commit 64219bb

Browse files
authored
Ensure that modreq(InAttribute) is emitted for synthesized ref readonly returning methods (#80893)
Closes #76847
1 parent 3b8c432 commit 64219bb

29 files changed

+1139
-68
lines changed

docs/compilers/CSharp/Compiler Breaking Changes - DotNet 11.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,47 @@ foreach (var x in new[] { 1, 2 })
5959
```
6060

6161
See also https://github.com/dotnet/csharplang/issues/9750.
62+
63+
64+
## Scenarios requiring compiler to synthesize a `ref readonly` returning delegate now require availability of `System.Runtime.InteropServices.InAttribute` type.
65+
66+
***Introduced in Visual Studio 2026 version 18.3***
67+
68+
The C# compiler made a breaking change in order to properly emit metadata for `ref readonly` returning
69+
[synthesized delegates](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#delegate-types)
70+
71+
This can cause an "error CS0518: Predefined type 'System.Runtime.InteropServices.InAttribute' is not defined or imported"
72+
to appear in existing code, such as in the scenarios below:
73+
74+
```cs
75+
var d = this.MethodWithRefReadonlyReturn;
76+
```
77+
78+
```cs
79+
var d = ref readonly int () => ref x;
80+
```
81+
82+
If your code is impacted by this breaking change, consider adding a reference to an assembly defining `System.Runtime.InteropServices.InAttribute`
83+
to your project.
84+
85+
## Scenarios utilizing `ref readonly` local functions now require availability of `System.Runtime.InteropServices.InAttribute` type.
86+
87+
***Introduced in Visual Studio 2026 version 18.3***
88+
89+
The C# compiler made a breaking change in order to properly emit metadata for `ref readonly` returning local functions.
90+
91+
This can cause an "error CS0518: Predefined type 'System.Runtime.InteropServices.InAttribute' is not defined or imported"
92+
to appear in existing code, such as in the scenario below:
93+
94+
```cs
95+
void Method()
96+
{
97+
...
98+
ref readonly int local() => ref x;
99+
...
100+
}
101+
```
102+
103+
If your code is impacted by this breaking change, consider adding a reference to an assembly defining `System.Runtime.InteropServices.InAttribute`
104+
to your project.
105+

src/Compilers/CSharp/Portable/Binder/Binder.IdentifierUsedAsValueFinder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ private bool CheckLambda(AnonymousFunctionExpressionSyntax lambdaSyntax, Binder
177177

178178
private static ExecutableCodeBinder CreateLambdaBodyBinder(Binder enclosingBinder, UnboundLambda unboundLambda)
179179
{
180-
unboundLambda.HasExplicitReturnType(out RefKind refKind, out TypeWithAnnotations returnType);
180+
unboundLambda.HasExplicitReturnType(out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers, out TypeWithAnnotations returnType);
181181
var lambdaSymbol = new LambdaSymbol(
182182
enclosingBinder,
183183
enclosingBinder.Compilation,
@@ -186,6 +186,7 @@ private static ExecutableCodeBinder CreateLambdaBodyBinder(Binder enclosingBinde
186186
ImmutableArray<TypeWithAnnotations>.Empty,
187187
ImmutableArray<RefKind>.Empty,
188188
refKind,
189+
refCustomModifiers,
189190
returnType);
190191

191192
return new ExecutableCodeBinder(unboundLambda.Syntax, lambdaSymbol, unboundLambda.GetWithParametersBinder(lambdaSymbol, enclosingBinder));

src/Compilers/CSharp/Portable/Binder/Binder.QueryUnboundLambdaState.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ public QueryUnboundLambdaState(Binder binder, RangeVariableMap rangeVariableMap,
3535
public override SyntaxList<AttributeListSyntax> ParameterAttributes(int index) => default;
3636
public override bool HasSignature { get { return true; } }
3737

38-
public override bool HasExplicitReturnType(out RefKind refKind, out TypeWithAnnotations returnType)
38+
public override bool HasExplicitReturnType(out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers, out TypeWithAnnotations returnType)
3939
{
4040
refKind = default;
41+
refCustomModifiers = [];
4142
returnType = default;
4243
return false;
4344
}

src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2167,7 +2167,7 @@ private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedA
21672167
// But if the lambda is explicitly typed, we can bind only once.
21682168
// https://github.com/dotnet/roslyn/issues/69093
21692169
if (unboundArgument.HasExplicitlyTypedParameterList &&
2170-
unboundArgument.HasExplicitReturnType(out _, out _) &&
2170+
unboundArgument.HasExplicitReturnType(out _, out _, out _) &&
21712171
unboundArgument.FunctionType is { } functionType &&
21722172
functionType.GetInternalDelegateType() is { } delegateType)
21732173
{

src/Compilers/CSharp/Portable/Binder/Binder_Lambda.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ private UnboundLambda AnalyzeAnonymousFunction(
4949
ImmutableArray<TypeWithAnnotations> types = default;
5050
ImmutableArray<EqualsValueClauseSyntax?> defaultValues = default;
5151
RefKind returnRefKind = RefKind.None;
52+
ImmutableArray<CustomModifier> refCustomModifiers = [];
5253
TypeWithAnnotations returnType = default;
5354
ImmutableArray<SyntaxList<AttributeListSyntax>> parameterAttributes = default;
5455

@@ -81,7 +82,7 @@ private UnboundLambda AnalyzeAnonymousFunction(
8182
var paren = (ParenthesizedLambdaExpressionSyntax)syntax;
8283
if (paren.ReturnType is { } returnTypeSyntax)
8384
{
84-
(returnRefKind, returnType) = BindExplicitLambdaReturnType(returnTypeSyntax, diagnostics);
85+
(returnRefKind, refCustomModifiers, returnType) = BindExplicitLambdaReturnType(returnTypeSyntax, diagnostics);
8586
}
8687

8788
parameterSyntaxListOpt = paren.ParameterList.Parameters;
@@ -252,7 +253,7 @@ private UnboundLambda AnalyzeAnonymousFunction(
252253

253254
namesBuilder.Free();
254255

255-
return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, scopes, types, names, discardsOpt, parameterSyntaxListOpt, defaultValues, isAsync: isAsync, isStatic: isStatic);
256+
return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, refCustomModifiers, returnType, parameterAttributes, refKinds, scopes, types, names, discardsOpt, parameterSyntaxListOpt, defaultValues, isAsync: isAsync, isStatic: isStatic);
256257

257258
static ImmutableArray<bool> computeDiscards(SeparatedSyntaxList<ParameterSyntax> parameters, int underscoresCount)
258259
{
@@ -288,7 +289,7 @@ static void checkAttributes(AnonymousFunctionExpressionSyntax syntax, SyntaxList
288289

289290
}
290291

291-
private (RefKind, TypeWithAnnotations) BindExplicitLambdaReturnType(TypeSyntax syntax, BindingDiagnosticBag diagnostics)
292+
private (RefKind, ImmutableArray<CustomModifier> refCustomModifiers, TypeWithAnnotations) BindExplicitLambdaReturnType(TypeSyntax syntax, BindingDiagnosticBag diagnostics)
292293
{
293294
MessageID.IDS_FeatureLambdaReturnType.CheckFeatureAvailability(diagnostics, syntax);
294295

@@ -311,7 +312,13 @@ static void checkAttributes(AnonymousFunctionExpressionSyntax syntax, SyntaxList
311312
diagnostics.Add(ErrorCode.ERR_MethodReturnCantBeRefAny, syntax.Location, type);
312313
}
313314

314-
return (refKind, returnType);
315+
ImmutableArray<CustomModifier> refCustomModifiers = [];
316+
if (refKind == RefKind.RefReadOnly)
317+
{
318+
refCustomModifiers = [CSharpCustomModifier.CreateRequired(Binder.GetWellKnownType(Compilation, WellKnownType.System_Runtime_InteropServices_InAttribute, diagnostics, syntax.Location))];
319+
}
320+
321+
return (refKind, refCustomModifiers, returnType);
315322
}
316323

317324
private static void CheckParenthesizedLambdaParameters(

src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3536,7 +3536,7 @@ internal BoundBlock CreateBlockFromExpression(CSharpSyntaxNode node, ImmutableAr
35363536
{
35373537
Error(diagnostics, ErrorCode.ERR_ReturnInIterator, syntax);
35383538
expression = BindToTypeForErrorRecovery(expression);
3539-
statement = new BoundReturnStatement(syntax, returnRefKind, expression, @checked: CheckOverflowAtRuntime) { WasCompilerGenerated = true };
3539+
statement = new BoundReturnStatement(syntax, refKind, expression, @checked: CheckOverflowAtRuntime) { WasCompilerGenerated = true };
35403540
}
35413541
else
35423542
{
@@ -3548,7 +3548,7 @@ internal BoundBlock CreateBlockFromExpression(CSharpSyntaxNode node, ImmutableAr
35483548
{
35493549
expression = CreateReturnConversion(syntax, diagnostics, expression, refKind, returnType);
35503550
}
3551-
statement = new BoundReturnStatement(syntax, returnRefKind, expression, @checked: CheckOverflowAtRuntime) { WasCompilerGenerated = true };
3551+
statement = new BoundReturnStatement(syntax, refKind, expression, @checked: CheckOverflowAtRuntime) { WasCompilerGenerated = true };
35523552
}
35533553
}
35543554
else if (expression.Type?.SpecialType == SpecialType.System_Void)

src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1482,7 +1482,7 @@ private static LambdaConversionResult IsAnonymousFunctionCompatibleWithDelegate(
14821482
return LambdaConversionResult.BadTargetType;
14831483
}
14841484

1485-
if (anonymousFunction.HasExplicitReturnType(out var refKind, out var returnType))
1485+
if (anonymousFunction.HasExplicitReturnType(out var refKind, refCustomModifiers: out _, out var returnType))
14861486
{
14871487
if (invokeMethod.RefKind != refKind ||
14881488
!invokeMethod.ReturnType.Equals(returnType.Type, TypeCompareKind.AllIgnoreOptions))

src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1616,7 +1616,7 @@ private void ExplicitReturnTypeInference(BoundExpression source, TypeWithAnnotat
16161616
}
16171617

16181618
UnboundLambda anonymousFunction = (UnboundLambda)source;
1619-
if (!anonymousFunction.HasExplicitReturnType(out _, out TypeWithAnnotations anonymousFunctionReturnType))
1619+
if (!anonymousFunction.HasExplicitReturnType(out _, out _, out TypeWithAnnotations anonymousFunctionReturnType))
16201620
{
16211621
return;
16221622
}

src/Compilers/CSharp/Portable/BoundTree/BoundExpressionExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,16 @@ public static bool HasDynamicType(this BoundExpression node)
138138

139139
var delegateType = expr.GetFunctionType()?.GetInternalDelegateType();
140140
delegateType?.AddUseSiteInfo(ref useSiteInfo);
141+
142+
// Report bad or missing System.Runtime.InteropServices.InAttribute for a 'ref readonly' returning delegate
143+
// unless the delegate type is inferred from a lambda or a local fuction. In those cases the check is performed
144+
// while binding the lambda or local function declaration.
145+
if (expr is BoundMethodGroup { Methods: not [{ MethodKind: MethodKind.LocalFunction }] } &&
146+
delegateType is { DelegateInvokeMethod.OriginalDefinition: SynthesizedDelegateInvokeMethod { RefKind: RefKind.RefReadOnly } })
147+
{
148+
delegateType.DeclaringCompilation.GetWellKnownType(WellKnownType.System_Runtime_InteropServices_InAttribute).AddUseSiteInfo(ref useSiteInfo);
149+
}
150+
141151
return delegateType;
142152
}
143153

src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,7 @@
11491149
<Field Name="Flavor" Type="NoOpStatementFlavor"/>
11501150
</Node>
11511151

1152-
<Node Name="BoundReturnStatement" Base="BoundStatement">
1152+
<Node Name="BoundReturnStatement" Base="BoundStatement" HasValidate="true">
11531153
<Field Name="RefKind" Type="RefKind"/>
11541154
<Field Name="ExpressionOpt" Type="BoundExpression?"/>
11551155
<!--

0 commit comments

Comments
 (0)