From 8d8779a15fb15bfc5b794ab657dccd1a58c4dad0 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:43:48 -0800 Subject: [PATCH 1/8] Collection expressions: IOperation and Add method with params array --- .../Portable/Binder/Binder_Conversions.cs | 17 +- .../Semantics/CollectionExpressionTests.cs | 385 ++++++++++++++++++ 2 files changed, 401 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 4678033b6d84a..b9ed79f7cdbb6 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -818,12 +818,27 @@ internal bool HasCollectionExpressionApplicableAddMethod(SyntaxNode syntax, Type { return element switch { - BoundCollectionElementInitializer collectionInitializer => collectionInitializer.Arguments[collectionInitializer.InvokedAsExtensionMethod ? 1 : 0], + BoundCollectionElementInitializer collectionInitializer => getCollectionInitializerElement(collectionInitializer), BoundDynamicCollectionElementInitializer dynamicInitializer => dynamicInitializer.Arguments[0], _ => null, }; } return element; + + static BoundExpression getCollectionInitializerElement(BoundCollectionElementInitializer collectionInitializer) + { + int argIndex = collectionInitializer.InvokedAsExtensionMethod ? 1 : 0; + var arg = collectionInitializer.Arguments[argIndex]; + if (collectionInitializer.Expanded && collectionInitializer.AddMethod.Parameters[argIndex].IsParams) + { + if (arg is BoundArrayCreation { IsParamsArray: true, InitializerOpt.Initializers: [var element] }) + { + return element; + } + Debug.Assert(false); + } + return arg; + } } internal bool TryGetCollectionIterationType(ExpressionSyntax syntax, TypeSymbol collectionType, out TypeWithAnnotations iterationType) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 76313d3fb4c28..a0bddb5f79f54 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33158,6 +33158,391 @@ public void Add(object obj) { } """); } + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_01() + { + string source = """ + using System; + using System.Collections.Generic; + static class Extensions + { + public static void Add(this ICollection collection, params T[] elements) + { + foreach (T element in elements) + collection.Add(element); + } + } + class Program + { + static Dictionary CreateDictionary(ICollection> collection) + { + return /**/[..collection]/**/; + } + static void Main() + { + var v = new KeyValuePair[] { new("a", "b"), new("c", "d") }; + var d = CreateDictionary(v); + foreach (var kvp in d) + Console.Write("({0}, {1}), ", kvp.Key, kvp.Value); + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "(a, b), (c, d), "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: System.Collections.Generic.Dictionary..ctor()) (OperationKind.CollectionExpression, Type: System.Collections.Generic.Dictionary) (Syntax: '[..collection]') + Elements(1): + ISpreadOperation (ElementType: System.Collections.Generic.KeyValuePair) (OperationKind.Spread, Type: null) (Syntax: '..collection') + Operand: + IParameterReferenceOperation: collection (OperationKind.ParameterReference, Type: System.Collections.Generic.ICollection>) (Syntax: 'collection') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_02() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(params T[] x) => _list.AddRange(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_03() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + internal void __AddRange(T[] x) { _list.AddRange(x); } + } + static class Extensions + { + public static void Add(this MyCollection c, params T[] x) { c.__AddRange(x); } + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [Fact] + public void Add_ParamsArray_04() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(T x, params T[] y) => _list.Add(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [Fact] + public void Add_ParamsArray_05() + { + string source = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + internal void __Add(T x) { _list.Add(x); } + } + static class Extensions + { + public static void Add(this MyCollection c, T x, params T[] y) { c.__Add(x); } + } + class Program + { + static void Main() + { + int x = 1; + MyCollection y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([source, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'x') + ISpreadOperation (ElementType: System.Int32) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Boxing) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_06() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(params MyCollection[] x) => _list.AddRange(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + MyCollection x = []; + MyCollection[] y = []; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[[]], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'x') + ISpreadOperation (ElementType: MyCollection) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection x = /**/[[]]/**/; + x.Report(); + } + } + """; + + comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[[]]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection[], IsImplicit) (Syntax: '[]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection[]) (Syntax: '[]') + Elements(0) + """); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_07() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + struct MyCollection : IEnumerable + { + private List _list; + public void Add(params MyCollection?[] x) => GetList().AddRange(x); + public IEnumerator GetEnumerator() => GetList().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + private List GetList() => _list ??= new(); + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + MyCollection x = []; + MyCollection[] y = []; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[[]], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection?, IsImplicit) (Syntax: 'x') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: MyCollection) (Syntax: 'x') + ISpreadOperation (ElementType: MyCollection) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: MyCollection[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (ImplicitNullable) + """); + + string sourceB2 = """ + class Program + { + static void Main() + { + MyCollection? x = /**/[[]]/**/; + x.Value.Report(); + } + } + """; + + comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (1 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[[]]') + Elements(1): + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: MyCollection?[], IsImplicit) (Syntax: '[]') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: MyCollection?[]) (Syntax: '[]') + Elements(0) + """); + } + [Fact] public void SynthesizedReadOnlyList_SingleElement() { From d37a75527bb9a83a73e34a33d2f0abb983b75a2a Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:01:31 -0800 Subject: [PATCH 2/8] Add test --- .../Semantics/CollectionExpressionTests.cs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index a0bddb5f79f54..9263d196a844d 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33543,6 +33543,81 @@ static void Main() """); } + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_08() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + class MyCollection : IEnumerable + { + private List _list = new(); + public void Add(params object[] x) => _list.AddRange(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + """; + + string sourceB1 = """ + class Program + { + static void Main() + { + object x = 1; + object[] y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object) (Syntax: 'x') + ISpreadOperation (ElementType: System.Object) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + + string sourceB2 = """ + class Program + { + static void Main() + { + object[] x = [1]; + object[][] y = [[2, 3]]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'x') + ISpreadOperation (ElementType: System.Object[]) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[][]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + } + [Fact] public void SynthesizedReadOnlyList_SingleElement() { From 5abf7042a01de491ec97422528d2a02a03f18289 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 11 Mar 2024 00:41:56 -0700 Subject: [PATCH 3/8] PR feedback --- .../Portable/Binder/Binder_Conversions.cs | 1 + .../Semantics/CollectionExpressionTests.cs | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index b9ed79f7cdbb6..7732b72ba1821 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -829,6 +829,7 @@ static BoundExpression getCollectionInitializerElement(BoundCollectionElementIni { int argIndex = collectionInitializer.InvokedAsExtensionMethod ? 1 : 0; var arg = collectionInitializer.Arguments[argIndex]; + Debug.Assert(!collectionInitializer.DefaultArguments[argIndex]); if (collectionInitializer.Expanded && collectionInitializer.AddMethod.Parameters[argIndex].IsParams) { if (arg is BoundArrayCreation { IsParamsArray: true, InitializerOpt.Initializers: [var element] }) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 9263d196a844d..ae6fea7739dec 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33191,7 +33191,39 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "(a, b), (c, d), "); + var verifier = CompileAndVerify(comp, expectedOutput: "(a, b), (c, d), "); + + verifier.VerifyIL("Extensions.Add(this System.Collections.Generic.ICollection, params T[])", """ + { + // Code size 32 (0x20) + .maxstack 2 + .locals init (T[] V_0, + int V_1, + T V_2) //element + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: br.s IL_0019 + IL_0006: ldloc.0 + IL_0007: ldloc.1 + IL_0008: ldelem "T" + IL_000d: stloc.2 + IL_000e: ldarg.0 + IL_000f: ldloc.2 + IL_0010: callvirt "void System.Collections.Generic.ICollection.Add(T)" + IL_0015: ldloc.1 + IL_0016: ldc.i4.1 + IL_0017: add + IL_0018: stloc.1 + IL_0019: ldloc.1 + IL_001a: ldloc.0 + IL_001b: ldlen + IL_001c: conv.i4 + IL_001d: blt.s IL_0006 + IL_001f: ret + } + """); VerifyOperationTreeForTest(comp, """ From 67ebbebc56ffca134cea35ac53cf813ffa564abe Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:01:43 -0700 Subject: [PATCH 4/8] Replace check for IsParams --- .../Portable/Binder/Binder_Conversions.cs | 2 +- .../Semantics/CollectionExpressionTests.cs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 7732b72ba1821..de7b7039e5dd6 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -830,7 +830,7 @@ static BoundExpression getCollectionInitializerElement(BoundCollectionElementIni int argIndex = collectionInitializer.InvokedAsExtensionMethod ? 1 : 0; var arg = collectionInitializer.Arguments[argIndex]; Debug.Assert(!collectionInitializer.DefaultArguments[argIndex]); - if (collectionInitializer.Expanded && collectionInitializer.AddMethod.Parameters[argIndex].IsParams) + if (collectionInitializer.Expanded && argIndex == collectionInitializer.AddMethod.ParameterCount - 1) { if (arg is BoundArrayCreation { IsParamsArray: true, InitializerOpt.Initializers: [var element] }) { diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index ae6fea7739dec..3b7f679267dbb 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33650,6 +33650,56 @@ static void Main() """); } + [WorkItem("https://github.com/dotnet/roslyn/issues/72461")] + [Fact] + public void Add_ParamsArray_09() + { + string sourceA = """ + using System.Collections; + using System.Collections.Generic; + abstract class MyCollectionBase + { + public abstract void Add(params object[] x); + } + class MyCollection : MyCollectionBase, IEnumerable + { + private List _list = new(); + public override void Add(object[] x) => _list.AddRange(x); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + """; + + string sourceB = """ + class Program + { + static void Main() + { + object x = 1; + object[] y = [2, 3]; + MyCollection z = /**/[x, ..y]/**/; + z.Report(); + } + } + """; + + var comp = CreateCompilation([sourceB, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + + VerifyOperationTreeForTest(comp, + """ + ICollectionExpressionOperation (2 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection) (Syntax: '[x, ..y]') + Elements(2): + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: System.Object) (Syntax: 'x') + ISpreadOperation (ElementType: System.Object) (OperationKind.Spread, Type: null) (Syntax: '..y') + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: System.Object[]) (Syntax: 'y') + ElementConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (Identity) + """); + } + [Fact] public void SynthesizedReadOnlyList_SingleElement() { From 362866b71e311794964804d12e273cda83894f3c Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 11 Mar 2024 12:41:56 -0700 Subject: [PATCH 5/8] Update override test --- .../Semantics/CollectionExpressionTests.cs | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 3b7f679267dbb..f0c9fadd61ca5 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33654,14 +33654,20 @@ static void Main() [Fact] public void Add_ParamsArray_09() { - string sourceA = """ - using System.Collections; - using System.Collections.Generic; - abstract class MyCollectionBase + string sourceA1 = """ + public abstract class MyCollectionBase { - public abstract void Add(params object[] x); + public abstract void Add(object[] x); } - class MyCollection : MyCollectionBase, IEnumerable + """; + string assemblyName = GetUniqueName(); + var comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(1, 0, 0, 0)), sourceA1, references: TargetFrameworkUtil.StandardReferences); + var refA1 = comp.EmitToImageReference(); + + string sourceB = """ + using System.Collections; + using System.Collections.Generic; + public class MyCollection : MyCollectionBase, IEnumerable { private List _list = new(); public override void Add(object[] x) => _list.AddRange(x); @@ -33669,8 +33675,19 @@ class MyCollection : MyCollectionBase, IEnumerable IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } """; + comp = CreateCompilation(sourceB, references: [refA1]); + var refB = comp.EmitToImageReference(); - string sourceB = """ + string sourceA2 = """ + public abstract class MyCollectionBase + { + public abstract void Add(params object[] x); + } + """; + comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(2, 0, 0, 0)), sourceA2, references: TargetFrameworkUtil.StandardReferences); + var refA2 = comp.EmitToImageReference(); + + string sourceC = """ class Program { static void Main() @@ -33683,9 +33700,8 @@ static void Main() } """; - var comp = CreateCompilation([sourceB, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); + comp = CreateCompilation([sourceC, s_collectionExtensions], references: [refA2, refB], options: TestOptions.ReleaseExe); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); VerifyOperationTreeForTest(comp, """ From 4c5b402f4f489b3e12fe646473384829027469c5 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:26:38 -0700 Subject: [PATCH 6/8] Update tests --- .../Semantics/CollectionExpressionTests.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index f0c9fadd61ca5..4b7df72a0726e 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33580,12 +33580,20 @@ static void Main() public void Add_ParamsArray_08() { string sourceA = """ + using System; using System.Collections; using System.Collections.Generic; class MyCollection : IEnumerable { private List _list = new(); - public void Add(params object[] x) => _list.AddRange(x); + public void Add(params object[] x) + { + Console.Write("Add: "); + foreach (var i in x) + Console.Write("{0}, ", i); + Console.WriteLine(); + _list.AddRange(x); + } public IEnumerator GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } @@ -33606,7 +33614,12 @@ static void Main() var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + CompileAndVerify(comp, expectedOutput: """ + Add: 1, + Add: 2, + Add: 3, + [1, 2, 3], + """); VerifyOperationTreeForTest(comp, """ @@ -33635,7 +33648,11 @@ static void Main() comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[1, 2, 3], "); + CompileAndVerify(comp, expectedOutput: """ + Add: 1, + Add: 2, 3, + [1, 2, 3], + """); VerifyOperationTreeForTest(comp, """ From 720a7b489c5d85a97a0c2a2fd4559b7269ffbe5d Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:14:54 -0700 Subject: [PATCH 7/8] Formatting --- .../CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 4b7df72a0726e..19277f774abd7 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33678,7 +33678,7 @@ public abstract class MyCollectionBase } """; string assemblyName = GetUniqueName(); - var comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(1, 0, 0, 0)), sourceA1, references: TargetFrameworkUtil.StandardReferences); + var comp = CreateCompilation(new AssemblyIdentity(assemblyName, new Version(1, 0, 0, 0)), sourceA1, references: TargetFrameworkUtil.StandardReferences); var refA1 = comp.EmitToImageReference(); string sourceB = """ From 2f2fe7beb4e182d79290389eedb1811f76e090b9 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:41:33 -0700 Subject: [PATCH 8/8] Update tests --- .../Semantics/CollectionExpressionTests.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 19277f774abd7..6d1de665c6ce7 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -33432,12 +33432,19 @@ static void Main() public void Add_ParamsArray_06() { string sourceA = """ + using System; using System.Collections; using System.Collections.Generic; class MyCollection : IEnumerable { private List _list = new(); - public void Add(params MyCollection[] x) => _list.AddRange(x); + public void Add(params MyCollection[] x) + { + Console.Write("Add: "); + x.Report(); + Console.WriteLine(); + _list.AddRange(x); + } public IEnumerator GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } @@ -33458,7 +33465,10 @@ static void Main() var comp = CreateCompilation([sourceB1, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[[]], "); + CompileAndVerify(comp, expectedOutput: """ + Add: [[]], + [[]], + """); VerifyOperationTreeForTest(comp, """ @@ -33485,7 +33495,10 @@ static void Main() comp = CreateCompilation([sourceB2, sourceA, s_collectionExtensions], options: TestOptions.ReleaseExe); comp.VerifyEmitDiagnostics(); - CompileAndVerify(comp, expectedOutput: "[], "); + CompileAndVerify(comp, expectedOutput: """ + Add: [], + [], + """); VerifyOperationTreeForTest(comp, """