diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md index 816c2435e2a28..448a91b5ab6f5 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md @@ -407,3 +407,30 @@ partial interface I class C : I; ``` + +## Missing `ParamCollectionAttribute` is reported in more cases + +***Introduced in Visual Studio 2026 version 18.0*** + +If you are compiling a `.netmodule` (note that this doesn't apply to normal DLL/EXE compilations), +and have a lambda or a local function with a `params` collection parameter, +and the `ParamCollectionAttribute` is not found, a compilation error is now reported +(because the attribute now must be [emitted](https://github.com/dotnet/roslyn/issues/79752) on the synthesized method +but the attribute type itself is not synthesized by the compiler into a `.netmodule`). +You can work around that by defining the attribute yourself. + +```cs +using System; +using System.Collections.Generic; +class C +{ + void M() + { + Func, int> lam = (params IList xs) => xs.Count; // error if ParamCollectionAttribute does not exist + lam([1, 2, 3]); + + int func(params IList xs) => xs.Count; // error if ParamCollectionAttribute does not exist + func(4, 5, 6); + } +} +``` diff --git a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs index b1fba6ed796dc..9ef1d2a16543a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs @@ -843,7 +843,7 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType, bool inExpressionTr var lambdaParameters = lambdaSymbol.Parameters; ParameterHelpers.EnsureRefKindAttributesExist(compilation, lambdaParameters, diagnostics, modifyCompilation: false); - // Not emitting ParamCollectionAttribute/ParamArrayAttribute for lambdas + ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, lambdaParameters, diagnostics, modifyCompilation: false); if (returnType.HasType) { diff --git a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs index 106a3fa207cef..5ce6f07042455 100644 --- a/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs +++ b/src/Compilers/CSharp/Portable/Lowering/ClosureConversion/SynthesizedClosureMethod.cs @@ -122,7 +122,8 @@ private void EnsureAttributesExist(TypeCompilationState compilationState) } ParameterHelpers.EnsureRefKindAttributesExist(moduleBuilder, Parameters); - // Not emitting ParamCollectionAttribute/ParamArrayAttribute for these methods because it is not a SynthesizedDelegateInvokeMethod + + ParameterHelpers.EnsureParamCollectionAttributeExists(moduleBuilder, Parameters); if (moduleBuilder.Compilation.ShouldEmitNativeIntegerAttributes()) { diff --git a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs index 9e74f7c63b03b..3f9f01b270eb7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SynthesizedMethodBaseSymbol.cs @@ -129,7 +129,8 @@ private ImmutableArray MakeParameters() p.ExplicitDefaultConstantValue, // the synthesized parameter doesn't need to have the same ref custom modifiers as the base refCustomModifiers: default, - baseParameterForAttributes: inheritAttributes ? p : null)); + baseParameterForAttributes: inheritAttributes ? p : null, + isParams: this is SynthesizedClosureMethod && p.IsParams)); } var extraSynthed = ExtraSynthesizedRefParameters; diff --git a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs index bf51ba240150e..8d4508fffce40 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Compilation_WellKnownMembers.cs @@ -537,9 +537,9 @@ internal void EnsureRequiresLocationAttributeExists(BindingDiagnosticBag? diagno EnsureEmbeddableAttributeExists(EmbeddableAttributes.RequiresLocationAttribute, diagnostics, location, modifyCompilation); } - internal void EnsureParamCollectionAttributeExistsAndModifyCompilation(BindingDiagnosticBag? diagnostics, Location location) + internal void EnsureParamCollectionAttributeExists(BindingDiagnosticBag? diagnostics, Location location, bool modifyCompilation) { - EnsureEmbeddableAttributeExists(EmbeddableAttributes.ParamCollectionAttribute, diagnostics, location, modifyCompilation: true); + EnsureEmbeddableAttributeExists(EmbeddableAttributes.ParamCollectionAttribute, diagnostics, location, modifyCompilation: modifyCompilation); } internal void EnsureIsByRefLikeAttributeExists(BindingDiagnosticBag? diagnostics, Location location, bool modifyCompilation) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs index 8d0d5e2afa5a3..08ee6f078c9c6 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/LocalFunctionSymbol.cs @@ -126,7 +126,7 @@ internal void GetDeclarationDiagnostics(BindingDiagnosticBag addTo) var compilation = DeclaringCompilation; ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, addTo, modifyCompilation: false); - // Not emitting ParamCollectionAttribute/ParamArrayAttribute for local functions + ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, addTo, modifyCompilation: false); ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, Parameters, addTo, modifyCompilation: false); ParameterHelpers.EnsureScopedRefAttributeExists(compilation, Parameters, addTo, modifyCompilation: false); ParameterHelpers.EnsureNullableAttributeExists(compilation, this, Parameters, addTo, modifyCompilation: false); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index 68f7f3719b950..f5d244f233d06 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -370,11 +370,19 @@ private static void EnsureRefKindAttributesExist(CSharpCompilation compilation, } } - internal static void EnsureParamCollectionAttributeExistsAndModifyCompilation(CSharpCompilation compilation, ImmutableArray parameters, BindingDiagnosticBag diagnostics) + internal static void EnsureParamCollectionAttributeExists(PEModuleBuilder moduleBuilder, ImmutableArray parameters) { if (parameters.LastOrDefault(static (p) => p.IsParamsCollection) is { } parameter) { - compilation.EnsureParamCollectionAttributeExistsAndModifyCompilation(diagnostics, GetParameterLocation(parameter)); + moduleBuilder.EnsureParamCollectionAttributeExists(null, null); + } + } + + internal static void EnsureParamCollectionAttributeExists(CSharpCompilation compilation, ImmutableArray parameters, BindingDiagnosticBag diagnostics, bool modifyCompilation) + { + if (parameters.LastOrDefault(static (p) => p.IsParamsCollection) is { } parameter) + { + compilation.EnsureParamCollectionAttributeExists(diagnostics, GetParameterLocation(parameter), modifyCompilation); } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs index b90a8cc9d701b..05406f282aa69 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbolBase.cs @@ -89,7 +89,7 @@ internal sealed override void AfterAddingTypeMembersChecks(ConversionsBase conve var compilation = DeclaringCompilation; ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, diagnostics, modifyCompilation: true); - ParameterHelpers.EnsureParamCollectionAttributeExistsAndModifyCompilation(compilation, Parameters, diagnostics); + ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); ParameterHelpers.EnsureScopedRefAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); ParameterHelpers.EnsureNullableAttributeExists(compilation, this, Parameters, diagnostics, modifyCompilation: true); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs index 397c3b6d232b3..181aef159ba15 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceDelegateMethodSymbol.cs @@ -323,7 +323,7 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, } ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, diagnostics, modifyCompilation: true); - ParameterHelpers.EnsureParamCollectionAttributeExistsAndModifyCompilation(compilation, Parameters, diagnostics); + ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); if (compilation.ShouldEmitNativeIntegerAttributes(ReturnType)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs index d90d172bb8ee0..d970039e679c7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodOrUserDefinedOperatorSymbol.cs @@ -245,7 +245,7 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, } ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, diagnostics, modifyCompilation: true); - ParameterHelpers.EnsureParamCollectionAttributeExistsAndModifyCompilation(compilation, Parameters, diagnostics); + ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); if (compilation.ShouldEmitNativeIntegerAttributes(ReturnType)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index f5ac6800e9b70..5debe9e38b07b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -1044,7 +1044,7 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, } ParameterHelpers.EnsureRefKindAttributesExist(compilation, Parameters, diagnostics, modifyCompilation: true); - ParameterHelpers.EnsureParamCollectionAttributeExistsAndModifyCompilation(compilation, Parameters, diagnostics); + ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, Parameters, diagnostics, modifyCompilation: true); if (compilation.ShouldEmitNativeIntegerAttributes(Type)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs index 171a739d176d3..340bc952de2f3 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedParameterSymbol.cs @@ -194,11 +194,11 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor)); } - if (this.IsParamsArray && this.ContainingSymbol is SynthesizedDelegateInvokeMethod) + if (this.IsParamsArray) { AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_ParamArrayAttribute__ctor)); } - else if (this.IsParamsCollection && this.ContainingSymbol is SynthesizedDelegateInvokeMethod) + else if (this.IsParamsCollection) { AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeParamCollectionAttribute(this)); } @@ -363,6 +363,8 @@ public SynthesizedComplexParameterSymbol( Debug.Assert(isParams || !refCustomModifiers.IsEmpty || baseParameterForAttributes is object || defaultValue is not null || hasUnscopedRefAttribute); Debug.Assert(baseParameterForAttributes is null || baseParameterForAttributes.ExplicitDefaultConstantValue == defaultValue); Debug.Assert(baseParameterForAttributes is null || baseParameterForAttributes.RefKind == refKind); + Debug.Assert(!isParams || container is SynthesizedDelegateInvokeMethod or SynthesizedClosureMethod, + "If this fails, make sure we don't synthesize ParamsArrayAttribute for symbols we don't intend to."); _refCustomModifiers = refCustomModifiers; _baseParameterForAttributes = baseParameterForAttributes; diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs index 2a80d5a0d1968..608dbdaf139d6 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ParamsCollectionTests.cs @@ -20,15 +20,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Semantics { public class ParamsCollectionTests : CompilingTestBase { - private const string ParamCollectionAttributeSource = @" -namespace System.Runtime.CompilerServices -{ - public sealed class ParamCollectionAttribute : Attribute - { - public ParamCollectionAttribute() { } - } -} -"; + private static string ParamCollectionAttributeSource => TestSources.ParamsCollectionAttribute; private static void VerifyParamsAndAttribute(ParameterSymbol parameter, bool isParamArray = false, bool isParamCollection = false) { @@ -2741,10 +2733,8 @@ static void Main() symbolValidator: (m) => { MethodSymbol l1 = m.GlobalNamespace.GetMember("Program.<>c.
b__0_0"); - AssertEx.Equal("void Program.<>c.
b__0_0(System.Collections.Generic.IEnumerable x)", l1.ToTestDisplayString()); - VerifyParamsAndAttribute(l1.Parameters.Last()); - - Assert.Empty(((NamespaceSymbol)m.GlobalNamespace.GetMember("System.Runtime.CompilerServices")).GetMembers("ParamCollectionAttribute")); + AssertEx.Equal("void Program.<>c.
b__0_0(params System.Collections.Generic.IEnumerable x)", l1.ToTestDisplayString()); + VerifyParamsAndAttribute(l1.Parameters.Last(), isParamCollection: true); }).VerifyDiagnostics( // (7,72): warning CS9100: Parameter 1 has params modifier in lambda but not in target delegate type. // System.Action> l = (params IEnumerable x) => {}; @@ -2797,8 +2787,8 @@ static void Main() symbolValidator: (m) => { MethodSymbol l1 = m.GlobalNamespace.GetMember("Program.<>c.
b__0_0"); - AssertEx.Equal("void Program.<>c.
b__0_0(System.Int64[] x)", l1.ToTestDisplayString()); - VerifyParamsAndAttribute(l1.Parameters.Last()); + AssertEx.Equal("void Program.<>c.
b__0_0(params System.Int64[] x)", l1.ToTestDisplayString()); + VerifyParamsAndAttribute(l1.Parameters.Last(), isParamArray: true); }).VerifyDiagnostics( // (5,50): warning CS9100: Parameter 1 has params modifier in lambda but not in target delegate type. // System.Action l = (params long[] x) => {}; @@ -2943,10 +2933,8 @@ static void Main() symbolValidator: (m) => { MethodSymbol l1 = m.GlobalNamespace.GetMember("Program.
g__local|0_0"); - AssertEx.Equal("void Program.
g__local|0_0(System.Collections.Generic.IEnumerable x)", l1.ToTestDisplayString()); - VerifyParamsAndAttribute(l1.Parameters.Last()); - - Assert.Empty(((NamespaceSymbol)m.GlobalNamespace.GetMember("System.Runtime.CompilerServices")).GetMembers("ParamCollectionAttribute")); + AssertEx.Equal("void Program.
g__local|0_0(params System.Collections.Generic.IEnumerable x)", l1.ToTestDisplayString()); + VerifyParamsAndAttribute(l1.Parameters.Last(), isParamCollection: true); }).VerifyDiagnostics(); var tree = comp.SyntaxTrees.Single(); @@ -2992,8 +2980,8 @@ static void Main() symbolValidator: (m) => { MethodSymbol l1 = m.GlobalNamespace.GetMember("Program.
g__local|0_0"); - AssertEx.Equal("void Program.
g__local|0_0(System.Int64[] x)", l1.ToTestDisplayString()); - VerifyParamsAndAttribute(l1.Parameters.Last()); + AssertEx.Equal("void Program.
g__local|0_0(params System.Int64[] x)", l1.ToTestDisplayString()); + VerifyParamsAndAttribute(l1.Parameters.Last(), isParamArray: true); }).VerifyDiagnostics(); } @@ -3078,7 +3066,7 @@ .locals init (<>y__InlineArray2 V_0, IL_006c: ldloca.s V_0 IL_006e: ldc.i4.2 IL_006f: call ""System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray2, CustomHandler>(in <>y__InlineArray2, int)"" - IL_0074: call ""void Program.<
$>g__M|0_0(scoped System.ReadOnlySpan)"" + IL_0074: call ""void Program.<
$>g__M|0_0(params System.ReadOnlySpan)"" IL_0079: ret } "); @@ -4533,15 +4521,13 @@ void verify(CSharpCompilation comp, bool attributeIsEmbedded) AssertEx.Equal("void <>f__AnonymousDelegate1.Invoke(params T1[] arg)", delegateInvokeMethod2.ToTestDisplayString()); VerifyParamsAndAttribute(delegateInvokeMethod2.Parameters.Last(), isParamArray: true); - // Note, no attributes on lambdas - MethodSymbol l1 = m.GlobalNamespace.GetMember("Program.<>c.
b__0_0"); - AssertEx.Equal("void Program.<>c.
b__0_0(scoped System.ReadOnlySpan a)", l1.ToTestDisplayString()); - VerifyParamsAndAttribute(l1.Parameters.Last()); + AssertEx.Equal("void Program.<>c.
b__0_0(params System.ReadOnlySpan a)", l1.ToTestDisplayString()); + VerifyParamsAndAttribute(l1.Parameters.Last(), isParamCollection: true); MethodSymbol l2 = m.GlobalNamespace.GetMember("Program.<>c.
b__0_1"); - AssertEx.Equal("void Program.<>c.
b__0_1(System.Int64[] a)", l2.ToTestDisplayString()); - VerifyParamsAndAttribute(l2.Parameters.Last()); + AssertEx.Equal("void Program.<>c.
b__0_1(params System.Int64[] a)", l2.ToTestDisplayString()); + VerifyParamsAndAttribute(l2.Parameters.Last(), isParamArray: true); if (attributeIsEmbedded) { @@ -4686,15 +4672,13 @@ void verify(CSharpCompilation comp, bool attributeIsEmbedded) AssertEx.Equal("void <>f__AnonymousDelegate1.Invoke(params T1[] arg)", delegateInvokeMethod2.ToTestDisplayString()); VerifyParamsAndAttribute(delegateInvokeMethod2.Parameters.Last(), isParamArray: true); - // Note, no attributes on local functions - MethodSymbol l1 = m.GlobalNamespace.GetMember("Program.
g__Test1|0_0"); - AssertEx.Equal("void Program.
g__Test1|0_0(scoped System.ReadOnlySpan a)", l1.ToTestDisplayString()); - VerifyParamsAndAttribute(l1.Parameters.Last()); + AssertEx.Equal("void Program.
g__Test1|0_0(params System.ReadOnlySpan a)", l1.ToTestDisplayString()); + VerifyParamsAndAttribute(l1.Parameters.Last(), isParamCollection: true); MethodSymbol l2 = m.GlobalNamespace.GetMember("Program.
g__Test2|0_1"); - AssertEx.Equal("void Program.
g__Test2|0_1(System.Int64[] a)", l2.ToTestDisplayString()); - VerifyParamsAndAttribute(l2.Parameters.Last()); + AssertEx.Equal("void Program.
g__Test2|0_1(params System.Int64[] a)", l2.ToTestDisplayString()); + VerifyParamsAndAttribute(l2.Parameters.Last(), isParamArray: true); if (attributeIsEmbedded) { @@ -12970,9 +12954,9 @@ void Test1(params IEnumerable a) { } src, "<>f__AnonymousDelegate0.Invoke", "void <>f__AnonymousDelegate0.Invoke(params System.Collections.Generic.IEnumerable arg)", - // (7,18): error CS0518: Predefined type 'System.Runtime.CompilerServices.ParamCollectionAttribute' is not defined or imported - // var x1 = Test1; - Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "Test1").WithArguments("System.Runtime.CompilerServices.ParamCollectionAttribute").WithLocation(7, 18) + // (11,20): error CS0518: Predefined type 'System.Runtime.CompilerServices.ParamCollectionAttribute' is not defined or imported + // void Test1(params IEnumerable a) { } + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "params IEnumerable a").WithArguments("System.Runtime.CompilerServices.ParamCollectionAttribute").WithLocation(11, 20) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs index acdc4f26fa467..0029ab5e4d20b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -19463,6 +19463,10 @@ .method assembly hidebysig int32[] ys ) cil managed { + .param [2] + .custom instance void [{{s_libPrefix}}]System.ParamArrayAttribute::.ctor() = ( + 01 00 00 00 + ) // Method begins at RVA 0x20b0 // Code size 1 (0x1) .maxstack 8 @@ -19520,6 +19524,10 @@ int32[] ys .custom instance void [{{s_libPrefix}}]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .param [2] + .custom instance void [{{s_libPrefix}}]System.ParamArrayAttribute::.ctor() = ( + 01 00 00 00 + ) // Method begins at RVA 0x2067 // Code size 1 (0x1) .maxstack 8 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 254772b5d70b2..da008944ba9aa 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -17581,7 +17581,7 @@ .locals init (CustomHandler V_0) IL_0053: call ""void CustomHandler.AppendFormatted(object, int, string)"" IL_0058: ldloc.0 IL_0059: stelem ""CustomHandler"" - IL_005e: call ""void Program.<
$>g__M|0_0(CustomHandler[])"" + IL_005e: call ""void Program.<
$>g__M|0_0(params CustomHandler[])"" IL_0063: ret } "); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs index ef06559513b05..9de58d02601f9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs @@ -8546,6 +8546,340 @@ public void ParamsArray_ThisModifier_02() Diagnostic(ErrorCode.ERR_ThisInBadContext, "this").WithLocation(1, 19)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute() + { + var source = """ + using System; + using System.Linq; + using System.Reflection; + + var lam = (params int[] xs) => xs.Length; + Console.WriteLine(lam(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(Program) + .GetNestedType("<>c", BindingFlags.NonPublic) + .GetMethod("<
$>b__0_0", BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + """; + CompileAndVerify(source, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.ParamArrayAttribute + """) + .VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("Program.<>c.<
$>b__0_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Int32[] xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.True(parameter.IsParamsArray); + Assert.False(parameter.IsParamsCollection); + + Assert.DoesNotContain("ParamArrayAttribute", module.TypeNames); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute_ExtensionMethod() + { + var source = """ + using System; + using System.Linq; + using System.Reflection; + + object.M(); + + static class E + { + extension(object) + { + public static void M() + { + var lam = (params int[] xs) => xs.Length; + Console.WriteLine(lam(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(E) + .GetNestedType("<>c", BindingFlags.NonPublic) + .GetMethod("b__1_0", BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + } + } + } + """; + CompileAndVerify(source, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.ParamArrayAttribute + """) + .VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("E.<>c.b__1_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Int32[] xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.True(parameter.IsParamsArray); + Assert.False(parameter.IsParamsCollection); + + Assert.DoesNotContain("ParamArrayAttribute", module.TypeNames); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute_Missing() + { + var source = """ + var lam = (params int[] xs) => xs.Length; + """; + var comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_ParamArrayAttribute); + comp.VerifyDiagnostics( + // (1,12): error CS0656: Missing compiler required member 'System.ParamArrayAttribute..ctor' + // var lam = (params int[] xs) => xs.Length; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "params").WithArguments("System.ParamArrayAttribute", ".ctor").WithLocation(1, 12)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute_Missing_ExtensionMethod() + { + var source = """ + static class E + { + extension(object) + { + public static void M() + { + var lam = (params int[] xs) => xs.Length; + } + } + } + """; + var comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_ParamArrayAttribute); + comp.VerifyDiagnostics( + // (7,24): error CS0656: Missing compiler required member 'System.ParamArrayAttribute..ctor' + // var lam = (params int[] xs) => xs.Length; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "params").WithArguments("System.ParamArrayAttribute", ".ctor").WithLocation(7, 24)); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute(bool includeAttribute) + { + var source = """ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + var lam = (params IList xs) => xs.Count; + Console.WriteLine(lam(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(Program) + .GetNestedType("<>c", BindingFlags.NonPublic) + .GetMethod("<
$>b__0_0", BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + """; + + var r = CreateCompilation(includeAttribute ? TestSources.ParamsCollectionAttribute : "").VerifyDiagnostics().ToMetadataReference(); + + CompileAndVerify(source, [r], + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.Runtime.CompilerServices.ParamCollectionAttribute + """) + .VerifyDiagnostics(); + + void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("Program.<>c.<
$>b__0_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Collections.Generic.IList xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.False(parameter.IsParamsArray); + Assert.True(parameter.IsParamsCollection); + + Assert.Equal(includeAttribute, !module.TypeNames.Contains("ParamCollectionAttribute")); + } + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_ExtensionMethod(bool includeAttribute) + { + var source = """ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + object.M(); + + static class E + { + extension(object) + { + public static void M() + { + var lam = (params IList xs) => xs.Count; + Console.WriteLine(lam(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(E) + .GetNestedType("<>c", BindingFlags.NonPublic) + .GetMethod("b__1_0", BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + } + } + } + """; + + var r = CreateCompilation(includeAttribute ? TestSources.ParamsCollectionAttribute : "").VerifyDiagnostics().ToMetadataReference(); + + CompileAndVerify(source, [r], + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.Runtime.CompilerServices.ParamCollectionAttribute + """) + .VerifyDiagnostics(); + + void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("E.<>c.b__1_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Collections.Generic.IList xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.False(parameter.IsParamsArray); + Assert.True(parameter.IsParamsCollection); + + Assert.Equal(includeAttribute, !module.TypeNames.Contains("ParamCollectionAttribute")); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_Missing() + { + var source = """ + using System.Collections.Generic; + class C + { + void M() + { + var lam = (params IList xs) => xs.Count; + } + } + """; + CreateCompilation(source, options: TestOptions.ReleaseModule).VerifyDiagnostics( + // (6,20): error CS0518: Predefined type 'System.Runtime.CompilerServices.ParamCollectionAttribute' is not defined or imported + // var lam = (params IList xs) => xs.Count; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "params IList xs").WithArguments("System.Runtime.CompilerServices.ParamCollectionAttribute").WithLocation(6, 20)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_Missing_NoSynthesizedDelegate() + { + var source = """ + using System; + using System.Collections.Generic; + class C + { + void M() + { + Func, int> lam = (params IList xs) => xs.Count; + lam([1, 2, 3]); + } + } + """; + CreateCompilation(source, options: TestOptions.ReleaseModule).VerifyDiagnostics( + // (7,38): error CS0518: Predefined type 'System.Runtime.CompilerServices.ParamCollectionAttribute' is not defined or imported + // Func, int> lam = (params IList xs) => xs.Count; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "params IList xs").WithArguments("System.Runtime.CompilerServices.ParamCollectionAttribute").WithLocation(7, 38)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_Missing_ExtensionMethod() + { + var source = """ + using System.Collections.Generic; + static class E + { + extension(object) + { + public static void M() + { + var lam = (params IList xs) => xs.Count; + } + } + } + """; + CreateCompilation([source, ExtensionMarkerAttributeDefinition], options: TestOptions.ReleaseModule).VerifyDiagnostics( + // (8,24): error CS0518: Predefined type 'System.Runtime.CompilerServices.ParamCollectionAttribute' is not defined or imported + // var lam = (params IList xs) => xs.Count; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "params IList xs").WithArguments("System.Runtime.CompilerServices.ParamCollectionAttribute").WithLocation(8, 24)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_Speculative() + { + // Compile without a need for the attribute. + var source1 = """ + class C + { + void M() + { + } + } + """; + var comp = CreateCompilation(source1); + var tree1 = comp.SyntaxTrees.Single(); + var model1 = comp.GetSemanticModel(tree1); + + // Speculatively bind a lambda that needs the attribute. + var source2 = """ + class C + { + void M() + { + var lam = (params System.Collections.Generic.IList xs) => xs.Count; + } + } + """; + var tree2 = CSharpSyntaxTree.ParseText(source2); + var method1 = tree1.GetRoot().DescendantNodes().OfType().Single(); + var method2 = tree2.GetRoot().DescendantNodes().OfType().Single(); + Assert.True(model1.TryGetSpeculativeSemanticModelForMethodBody(method1.Body.SpanStart, method2, out var model2)); + var lambda = tree2.GetRoot().DescendantNodes().OfType().Single(); + var symbol = model2.GetSymbolInfo(lambda).Symbol; + Assert.NotNull(symbol); + Assert.True(symbol.GetParameters().Single().IsParamsCollection); + + // The original compilation does not synthesize the attribute when emitted. + Assert.Equal(TypeKind.Error, comp.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_ParamCollectionAttribute).TypeKind); + CompileAndVerify(comp, + symbolValidator: static (module) => + { + Assert.DoesNotContain("ParamCollectionAttribute", module.TypeNames); + }) + .VerifyDiagnostics(); + } + [Fact] public void ImplicitlyTypedLambdaWithModifier_CSharp13() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index 7706b5b47a3a3..225ccc6466943 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -2601,6 +2601,319 @@ public void ParamsArray_Symbol_MultipleParamsArrays() Assert.True(methods[1].Parameters[2].IsParams); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute() + { + var source = """ + using System; + using System.Linq; + using System.Reflection; + + int fun(params int[] xs) => xs.Length; + Console.WriteLine(fun(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(Program) + .GetMethod("<
$>g__fun|0_0", BindingFlags.Static | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + """; + CompileAndVerify(source, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.ParamArrayAttribute + """) + .VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("Program.<
$>g__fun|0_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Int32[] xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.True(parameter.IsParamsArray); + Assert.False(parameter.IsParamsCollection); + + Assert.DoesNotContain("ParamArrayAttribute", module.TypeNames); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute_ExtensionMethod() + { + var source = """ + using System; + using System.Linq; + using System.Reflection; + + object.M(); + + static class E + { + extension(object) + { + public static void M() + { + int fun(params int[] xs) => xs.Length; + Console.WriteLine(fun(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(E) + .GetMethod("g__fun|1_0", BindingFlags.Static | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + } + } + } + """; + CompileAndVerify(source, + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.ParamArrayAttribute + """) + .VerifyDiagnostics(); + + static void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("E.g__fun|1_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Int32[] xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.True(parameter.IsParamsArray); + Assert.False(parameter.IsParamsCollection); + + Assert.DoesNotContain("ParamArrayAttribute", module.TypeNames); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute_Missing() + { + var source = """ + int fun(params int[] xs) => xs.Length; + fun(4, 5, 6); + """; + var comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_ParamArrayAttribute); + comp.VerifyDiagnostics( + // (1,9): error CS0656: Missing compiler required member 'System.ParamArrayAttribute..ctor' + // int fun(params int[] xs) => xs.Length; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "params int[] xs").WithArguments("System.ParamArrayAttribute", ".ctor").WithLocation(1, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsArray_Attribute_Missing_ExtensionMethod() + { + var source = """ + static class E + { + extension(object) + { + public static void M() + { + int fun(params int[] xs) => xs.Length; + fun(4, 5, 6); + } + } + } + """; + var comp = CreateCompilation(source); + comp.MakeTypeMissing(WellKnownType.System_ParamArrayAttribute); + comp.VerifyDiagnostics( + // (7,21): error CS0656: Missing compiler required member 'System.ParamArrayAttribute..ctor' + // int fun(params int[] xs) => xs.Length; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "params int[] xs").WithArguments("System.ParamArrayAttribute", ".ctor").WithLocation(7, 21)); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute(bool includeAttribute) + { + var source = """ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + int fun(params IList xs) => xs.Count; + Console.WriteLine(fun(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(Program) + .GetMethod("<
$>g__fun|0_0", BindingFlags.Static | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + """; + + var r = CreateCompilation(includeAttribute ? TestSources.ParamsCollectionAttribute : "").VerifyDiagnostics().ToMetadataReference(); + + CompileAndVerify(source, [r], + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.Runtime.CompilerServices.ParamCollectionAttribute + """) + .VerifyDiagnostics(); + + void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("Program.<
$>g__fun|0_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Collections.Generic.IList xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.False(parameter.IsParamsArray); + Assert.True(parameter.IsParamsCollection); + + Assert.Equal(includeAttribute, !module.TypeNames.Contains("ParamCollectionAttribute")); + } + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_ExtensionMethod(bool includeAttribute) + { + var source = """ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + object.M(); + + static class E + { + extension(object) + { + public static void M() + { + int fun(params IList xs) => xs.Count; + Console.WriteLine(fun(4, 5, 6)); + Console.WriteLine(string.Join("\n", typeof(E) + .GetMethod("g__fun|1_0", BindingFlags.Static | BindingFlags.NonPublic) + .GetParameters() + .Single() + .CustomAttributes + .Select(a => a.AttributeType))); + } + } + } + """; + + var r = CreateCompilation(includeAttribute ? TestSources.ParamsCollectionAttribute : "").VerifyDiagnostics().ToMetadataReference(); + + CompileAndVerify(source, [r], + options: TestOptions.DebugExe.WithMetadataImportOptions(MetadataImportOptions.All), + symbolValidator: validate, + expectedOutput: """ + 3 + System.Runtime.CompilerServices.ParamCollectionAttribute + """) + .VerifyDiagnostics(); + + void validate(ModuleSymbol module) + { + var lambda = module.GlobalNamespace.GetMember("E.g__fun|1_0"); + var parameter = lambda.GetParameters().Single(); + AssertEx.Equal("params System.Collections.Generic.IList xs", parameter.ToTestDisplayString()); + Assert.True(parameter.IsParams); + Assert.False(parameter.IsParamsArray); + Assert.True(parameter.IsParamsCollection); + + Assert.Equal(includeAttribute, !module.TypeNames.Contains("ParamCollectionAttribute")); + } + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_Missing() + { + var source = """ + using System.Collections.Generic; + class C + { + void M() + { + int func(params IList xs) => xs.Count; + func(4, 5, 6); + } + } + """; + CreateCompilation(source, options: TestOptions.ReleaseModule).VerifyDiagnostics( + // (6,18): error CS0518: Predefined type 'System.Runtime.CompilerServices.ParamCollectionAttribute' is not defined or imported + // int func(params IList xs) => xs.Count; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "params IList xs").WithArguments("System.Runtime.CompilerServices.ParamCollectionAttribute").WithLocation(6, 18)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_Missing_ExtensionMethod() + { + var source = """ + using System.Collections.Generic; + static class E + { + extension(object) + { + public static void M() + { + int func(params IList xs) => xs.Count; + func(4, 5, 6); + } + } + } + """; + CreateCompilation([source, ExtensionMarkerAttributeDefinition], options: TestOptions.ReleaseModule).VerifyDiagnostics( + // (8,22): error CS0518: Predefined type 'System.Runtime.CompilerServices.ParamCollectionAttribute' is not defined or imported + // int func(params IList xs) => xs.Count; + Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "params IList xs").WithArguments("System.Runtime.CompilerServices.ParamCollectionAttribute").WithLocation(8, 22)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79752")] + public void ParamsCollection_Attribute_Speculative() + { + // Compile without a need for the attribute. + var source1 = """ + class C + { + void M() + { + } + } + """; + var comp = CreateCompilation(source1); + var tree1 = comp.SyntaxTrees.Single(); + var model1 = comp.GetSemanticModel(tree1); + + // Speculatively bind a lambda that needs the attribute. + var source2 = """ + class C + { + void M() + { + int func(params System.Collections.Generic.IList xs) => xs.Count; + } + } + """; + var tree2 = CSharpSyntaxTree.ParseText(source2); + var method1 = tree1.GetRoot().DescendantNodes().OfType().Single(); + var method2 = tree2.GetRoot().DescendantNodes().OfType().Single(); + Assert.True(model1.TryGetSpeculativeSemanticModelForMethodBody(method1.Body.SpanStart, method2, out var model2)); + var func = tree2.GetRoot().DescendantNodes().OfType().Single(); + var symbol = model2.GetDeclaredSymbol(func); + Assert.NotNull(symbol); + Assert.True(symbol.Parameters.Single().IsParamsCollection); + + // The original compilation does not synthesize the attribute when emitted. + Assert.Equal(TypeKind.Error, comp.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_ParamCollectionAttribute).TypeKind); + CompileAndVerify(comp, + symbolValidator: static (module) => + { + Assert.DoesNotContain("ParamCollectionAttribute", module.TypeNames); + }) + .VerifyDiagnostics(); + } + [Fact] public void BadRefWithDefault() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs index 1e01cc6afd4e9..1a66643b57f2b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RawInterpolationTests_Handler.cs @@ -12737,7 +12737,7 @@ .locals init (CustomHandler V_0) IL_0053: call ""void CustomHandler.AppendFormatted(object, int, string)"" IL_0058: ldloc.0 IL_0059: stelem ""CustomHandler"" - IL_005e: call ""void Program.<
$>g__M|0_0(CustomHandler[])"" + IL_005e: call ""void Program.<
$>g__M|0_0(params CustomHandler[])"" IL_0063: ret } "); diff --git a/src/Compilers/Test/Utilities/CSharp/TestSources.cs b/src/Compilers/Test/Utilities/CSharp/TestSources.cs index 6214eca453ee7..4b51d39a6bd47 100644 --- a/src/Compilers/Test/Utilities/CSharp/TestSources.cs +++ b/src/Compilers/Test/Utilities/CSharp/TestSources.cs @@ -556,5 +556,15 @@ public static bool SequenceEqual (this Span span, ReadOnlySpan other) w public static Span AsSpan(this T[] array) => new Span(array); } }"; + + public static readonly string ParamsCollectionAttribute = """ + namespace System.Runtime.CompilerServices + { + public sealed class ParamCollectionAttribute : Attribute + { + public ParamCollectionAttribute() { } + } + } + """; } }