Skip to content

Commit 0b2145c

Browse files
authored
Propagate params to lambdas and local functions (#79880)
* Propagate `params` to lambdas and local functions * Update pre-existing tests * Remove unused parameter * Fixup more pre-existing tests * Test extension methods * Make property `abstract` * Remove superfluous blank line * Improve code and tests * Reuse test source in ParamsCollectionTests * Document the break * Add speculative attribute tests * Improve * Improve
1 parent ae161d6 commit 0b2145c

19 files changed

+741
-53
lines changed

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,30 @@ partial interface I
407407

408408
class C : I;
409409
```
410+
411+
## Missing `ParamCollectionAttribute` is reported in more cases
412+
413+
***Introduced in Visual Studio 2026 version 18.0***
414+
415+
If you are compiling a `.netmodule` (note that this doesn't apply to normal DLL/EXE compilations),
416+
and have a lambda or a local function with a `params` collection parameter,
417+
and the `ParamCollectionAttribute` is not found, a compilation error is now reported
418+
(because the attribute now must be [emitted](https://github.com/dotnet/roslyn/issues/79752) on the synthesized method
419+
but the attribute type itself is not synthesized by the compiler into a `.netmodule`).
420+
You can work around that by defining the attribute yourself.
421+
422+
```cs
423+
using System;
424+
using System.Collections.Generic;
425+
class C
426+
{
427+
void M()
428+
{
429+
Func<IList<int>, int> lam = (params IList<int> xs) => xs.Count; // error if ParamCollectionAttribute does not exist
430+
lam([1, 2, 3]);
431+
432+
int func(params IList<int> xs) => xs.Count; // error if ParamCollectionAttribute does not exist
433+
func(4, 5, 6);
434+
}
435+
}
436+
```

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType, bool inExpressionTr
852852

853853
var lambdaParameters = lambdaSymbol.Parameters;
854854
ParameterHelpers.EnsureRefKindAttributesExist(compilation, lambdaParameters, diagnostics, modifyCompilation: false);
855-
// Not emitting ParamCollectionAttribute/ParamArrayAttribute for lambdas
855+
ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, lambdaParameters, diagnostics, modifyCompilation: false);
856856

857857
if (returnType.HasType)
858858
{

src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ private void EnsureAttributesExist(TypeCompilationState compilationState)
122122
}
123123

124124
ParameterHelpers.EnsureRefKindAttributesExist(moduleBuilder, Parameters);
125-
// Not emitting ParamCollectionAttribute/ParamArrayAttribute for these methods because it is not a SynthesizedDelegateInvokeMethod
125+
126+
ParameterHelpers.EnsureParamCollectionAttributeExists(moduleBuilder, Parameters);
126127

127128
if (moduleBuilder.Compilation.ShouldEmitNativeIntegerAttributes())
128129
{

src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ private ImmutableArray<ParameterSymbol> MakeParameters()
129129
p.ExplicitDefaultConstantValue,
130130
// the synthesized parameter doesn't need to have the same ref custom modifiers as the base
131131
refCustomModifiers: default,
132-
baseParameterForAttributes: inheritAttributes ? p : null));
132+
baseParameterForAttributes: inheritAttributes ? p : null,
133+
isParams: this is SynthesizedClosureMethod && p.IsParams));
133134
}
134135

135136
var extraSynthed = ExtraSynthesizedRefParameters;

src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,9 +537,9 @@ internal void EnsureRequiresLocationAttributeExists(BindingDiagnosticBag? diagno
537537
EnsureEmbeddableAttributeExists(EmbeddableAttributes.RequiresLocationAttribute, diagnostics, location, modifyCompilation);
538538
}
539539

540-
internal void EnsureParamCollectionAttributeExistsAndModifyCompilation(BindingDiagnosticBag? diagnostics, Location location)
540+
internal void EnsureParamCollectionAttributeExists(BindingDiagnosticBag? diagnostics, Location location, bool modifyCompilation)
541541
{
542-
EnsureEmbeddableAttributeExists(EmbeddableAttributes.ParamCollectionAttribute, diagnostics, location, modifyCompilation: true);
542+
EnsureEmbeddableAttributeExists(EmbeddableAttributes.ParamCollectionAttribute, diagnostics, location, modifyCompilation: modifyCompilation);
543543
}
544544

545545
internal void EnsureIsByRefLikeAttributeExists(BindingDiagnosticBag? diagnostics, Location location, bool modifyCompilation)

src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ internal void GetDeclarationDiagnostics(BindingDiagnosticBag addTo)
126126

127127
var compilation = DeclaringCompilation;
128128
ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, addTo, modifyCompilation: false);
129-
// Not emitting ParamCollectionAttribute/ParamArrayAttribute for local functions
129+
ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, addTo, modifyCompilation: false);
130130
ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, Parameters, addTo, modifyCompilation: false);
131131
ParameterHelpers.EnsureScopedRefAttributeExists(compilation, Parameters, addTo, modifyCompilation: false);
132132
ParameterHelpers.EnsureNullableAttributeExists(compilation, this, Parameters, addTo, modifyCompilation: false);

src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,19 @@ private static void EnsureRefKindAttributesExist(CSharpCompilation compilation,
370370
}
371371
}
372372

373-
internal static void EnsureParamCollectionAttributeExistsAndModifyCompilation(CSharpCompilation compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag diagnostics)
373+
internal static void EnsureParamCollectionAttributeExists(PEModuleBuilder moduleBuilder, ImmutableArray<ParameterSymbol> parameters)
374374
{
375375
if (parameters.LastOrDefault(static (p) => p.IsParamsCollection) is { } parameter)
376376
{
377-
compilation.EnsureParamCollectionAttributeExistsAndModifyCompilation(diagnostics, GetParameterLocation(parameter));
377+
moduleBuilder.EnsureParamCollectionAttributeExists(null, null);
378+
}
379+
}
380+
381+
internal static void EnsureParamCollectionAttributeExists(CSharpCompilation compilation, ImmutableArray<ParameterSymbol> parameters, BindingDiagnosticBag diagnostics, bool modifyCompilation)
382+
{
383+
if (parameters.LastOrDefault(static (p) => p.IsParamsCollection) is { } parameter)
384+
{
385+
compilation.EnsureParamCollectionAttributeExists(diagnostics, GetParameterLocation(parameter), modifyCompilation);
378386
}
379387
}
380388

src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ internal sealed override void AfterAddingTypeMembersChecks(ConversionsBase conve
8989

9090
var compilation = DeclaringCompilation;
9191
ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, diagnostics, modifyCompilation: true);
92-
ParameterHelpers.EnsureParamCollectionAttributeExistsAndModifyCompilation(compilation, Parameters, diagnostics);
92+
ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true);
9393
ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true);
9494
ParameterHelpers.EnsureScopedRefAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true);
9595
ParameterHelpers.EnsureNullableAttributeExists(compilation, this, Parameters, diagnostics, modifyCompilation: true);

src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions,
323323
}
324324

325325
ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, diagnostics, modifyCompilation: true);
326-
ParameterHelpers.EnsureParamCollectionAttributeExistsAndModifyCompilation(compilation, Parameters, diagnostics);
326+
ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true);
327327

328328
if (compilation.ShouldEmitNativeIntegerAttributes(ReturnType))
329329
{

src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions,
245245
}
246246

247247
ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, diagnostics, modifyCompilation: true);
248-
ParameterHelpers.EnsureParamCollectionAttributeExistsAndModifyCompilation(compilation, Parameters, diagnostics);
248+
ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true);
249249

250250
if (compilation.ShouldEmitNativeIntegerAttributes(ReturnType))
251251
{

0 commit comments

Comments
 (0)