diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs index 69eedc88e0aab..59940d21106d3 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory_Methods.cs @@ -345,7 +345,7 @@ private static ArgumentKind GetArgumentKind(BoundExpression argument, ref BitVec } else if (argument.IsParamsCollection) { - argumentKind = ArgumentKind.ParamArray; + argumentKind = argument.Type?.IsSZArray() == true ? ArgumentKind.ParamArray : ArgumentKind.ParamCollection; } else { diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs index 9affa0a4b3272..c9b21279d7a86 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/ParamsCollectionTests.cs @@ -124,7 +124,7 @@ static void Test2() Instance Receiver: null Arguments(1): - IArgumentOperation (ArgumentKind.ParamArray, Matching Parameter: a) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Test()') + IArgumentOperation (ArgumentKind.ParamCollection, Matching Parameter: a) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Test()') ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Span, IsImplicit) (Syntax: 'Test()') Elements(0) InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) @@ -137,7 +137,7 @@ static void Test2() Instance Receiver: null Arguments(1): - IArgumentOperation (ArgumentKind.ParamArray, Matching Parameter: a) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Test(1)') + IArgumentOperation (ArgumentKind.ParamCollection, Matching Parameter: a) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Test(1)') ICollectionExpressionOperation (1 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Span, IsImplicit) (Syntax: 'Test(1)') Elements(1): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, Constant: 1, IsImplicit) (Syntax: '1') @@ -153,7 +153,7 @@ static void Test2() Instance Receiver: null Arguments(1): - IArgumentOperation (ArgumentKind.ParamArray, Matching Parameter: a) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Test(2, 3)') + IArgumentOperation (ArgumentKind.ParamCollection, Matching Parameter: a) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'Test(2, 3)') ICollectionExpressionOperation (2 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Span, IsImplicit) (Syntax: 'Test(2, 3)') Elements(2): IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int64, Constant: 2, IsImplicit) (Syntax: '2') @@ -3337,6 +3337,107 @@ .locals init (int V_0, } "); + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(m => m.Identifier.ValueText == "Test1").Single(); + + VerifyFlowGraph(comp, node, """ +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'new Program ... GetF2() } }') + Value: + IObjectCreationOperation (Constructor: Program..ctor()) (OperationKind.ObjectCreation, Type: Program) (Syntax: 'new Program ... GetF2() } }') + Arguments(0) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [1] [2] + Block[B2] - Block + Predecessors: [B1] + Statements (4) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'GetA()') + Value: + IInvocationOperation (System.Int32 Program.GetA()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'GetA()') + Instance Receiver: + null + Arguments(0) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '[GetA()]') + Value: + ICollectionExpressionOperation (0 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsImplicit) (Syntax: '[GetA()]') + Elements(0) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'F1 = GetF1()') + Left: + IFieldReferenceOperation: System.Int32 C1.F1 (OperationKind.FieldReference, Type: System.Int32) (Syntax: 'F1') + Instance Receiver: + IPropertyReferenceOperation: C1 Program.this[System.Int32 a, params MyCollection c] { get; set; } (OperationKind.PropertyReference, Type: C1) (Syntax: '[GetA()]') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: Program, IsImplicit) (Syntax: 'new Program ... GetF2() } }') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: a) (OperationKind.Argument, Type: null) (Syntax: 'GetA()') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'GetA()') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.ParamCollection, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '[GetA()]') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: MyCollection, IsImplicit) (Syntax: '[GetA()]') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Right: + IInvocationOperation (System.Int32 Program.GetF1()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'GetF1()') + Instance Receiver: + null + Arguments(0) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'F2 = GetF2()') + Left: + IFieldReferenceOperation: System.Int32 C1.F2 (OperationKind.FieldReference, Type: System.Int32) (Syntax: 'F2') + Instance Receiver: + IPropertyReferenceOperation: C1 Program.this[System.Int32 a, params MyCollection c] { get; set; } (OperationKind.PropertyReference, Type: C1) (Syntax: '[GetA()]') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: Program, IsImplicit) (Syntax: 'new Program ... GetF2() } }') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: a) (OperationKind.Argument, Type: null) (Syntax: 'GetA()') + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'GetA()') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.ParamCollection, Matching Parameter: c) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: '[GetA()]') + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: MyCollection, IsImplicit) (Syntax: '[GetA()]') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Right: + IInvocationOperation (System.Int32 Program.GetF2()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'GetF2()') + Instance Receiver: + null + Arguments(0) + Next (Regular) Block[B3] + Leaving: {R2} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = new Pro ... etF2() } };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: Program) (Syntax: '_ = new Pro ... GetF2() } }') + Left: + IDiscardOperation (Symbol: Program _) (OperationKind.Discard, Type: Program) (Syntax: '_') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: Program, IsImplicit) (Syntax: 'new Program ... GetF2() } }') + Next (Regular) Block[B4] + Leaving: {R1} +} +Block[B4] - Exit + Predecessors: [B3] + Statements (0) +"""); + verifier.VerifyIL("Program.Test2", @" { // Code size 64 (0x40) @@ -3399,6 +3500,208 @@ .locals init (int V_0, "); } + [Fact] + public void OrderOfEvaluation_04_ObjectInitializer() + { + var src = """ +using System.Collections; +using System.Collections.Generic; + +class MyCollection : IEnumerable +{ + public MyCollection() + { + System.Console.WriteLine("Create"); + } + + IEnumerator IEnumerable.GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + + public void Add(int l) + { + System.Console.WriteLine("Add"); + } +} + +class C1 +{ +} + +class Program +{ + private MyCollection _c; + + static void Main() + { + System.Console.WriteLine("---Test1"); + Test1(); + System.Console.WriteLine("---Test2"); + Test2(); + System.Console.WriteLine("---Test3"); + Test3(); + } + + static void Test1() + { + _ = new Program() { [GetA()] = { } }; + } + + static void Test2() + { + _ = new Program() { [GetA(), GetC()] = { } }; + } + + static void Test3() + { + _ = new Program() { [GetA(), GetB(), GetC()] = { } }; + } + + C1 this[int a, params MyCollection c] + { + get + { + System.Console.WriteLine("Get_this {0}", c is not null && (_c is null || (object)_c == c)); + _c = c; + return new C1(); + } + set + { + System.Console.WriteLine("Set_this {0}", (object)_c == c); + } + } + + + static int GetA() + { + System.Console.WriteLine("GetA"); + return 0; + } + + static int GetB() + { + System.Console.WriteLine("GetB"); + return 0; + } + + static int GetC() + { + System.Console.WriteLine("GetC"); + return 0; + } +} +"""; + var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); + + var verifier = CompileAndVerify( + comp, + expectedOutput: @" +---Test1 +GetA +Create +---Test2 +GetA +Create +GetC +Add +---Test3 +GetA +Create +GetB +Add +GetC +Add +").VerifyDiagnostics(); + + // Note, the collection is created even though the getter is never invoked + verifier.VerifyIL("Program.Test1", @" +{ + // Code size 19 (0x13) + .maxstack 1 + IL_0000: newobj ""Program..ctor()"" + IL_0005: pop + IL_0006: call ""int Program.GetA()"" + IL_000b: pop + IL_000c: newobj ""MyCollection..ctor()"" + IL_0011: pop + IL_0012: ret +} +"); + + var tree = comp.SyntaxTrees.Single(); + var node = tree.GetRoot().DescendantNodes().OfType().Where(m => m.Identifier.ValueText == "Test1").Single(); + + VerifyFlowGraph(comp, node, """ +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (4) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'new Program ... ()] = { } }') + Value: + IObjectCreationOperation (Constructor: Program..ctor()) (OperationKind.ObjectCreation, Type: Program) (Syntax: 'new Program ... ()] = { } }') + Arguments(0) + Initializer: + null + IInvocationOperation (System.Int32 Program.GetA()) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'GetA()') + Instance Receiver: + null + Arguments(0) + ICollectionExpressionOperation (0 elements, ConstructMethod: MyCollection..ctor()) (OperationKind.CollectionExpression, Type: MyCollection, IsImplicit) (Syntax: '[GetA()]') + Elements(0) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = new Pro ... )] = { } };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: Program) (Syntax: '_ = new Pro ... ()] = { } }') + Left: + IDiscardOperation (Symbol: Program _) (OperationKind.Discard, Type: Program) (Syntax: '_') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: Program, IsImplicit) (Syntax: 'new Program ... ()] = { } }') + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"""); + + verifier.VerifyIL("Program.Test2", @" +{ + // Code size 28 (0x1c) + .maxstack 2 + IL_0000: newobj ""Program..ctor()"" + IL_0005: pop + IL_0006: call ""int Program.GetA()"" + IL_000b: pop + IL_000c: newobj ""MyCollection..ctor()"" + IL_0011: call ""int Program.GetC()"" + IL_0016: callvirt ""void MyCollection.Add(int)"" + IL_001b: ret +} +"); + + verifier.VerifyIL("Program.Test3", @" +{ + // Code size 39 (0x27) + .maxstack 3 + IL_0000: newobj ""Program..ctor()"" + IL_0005: pop + IL_0006: call ""int Program.GetA()"" + IL_000b: pop + IL_000c: newobj ""MyCollection..ctor()"" + IL_0011: dup + IL_0012: call ""int Program.GetB()"" + IL_0017: callvirt ""void MyCollection.Add(int)"" + IL_001c: call ""int Program.GetC()"" + IL_0021: callvirt ""void MyCollection.Add(int)"" + IL_0026: ret +} +"); + } + [Fact] public void LanguageVersion_01_Declaration() { @@ -11238,7 +11541,7 @@ static void Main() Instance Receiver: IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: S1, IsImplicit) (Syntax: 'new C2()') Arguments(1): - IArgumentOperation (ArgumentKind.ParamArray, Matching Parameter: args) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'foreach (va ... }') + IArgumentOperation (ArgumentKind.ParamCollection, Matching Parameter: args) (OperationKind.Argument, Type: null, IsImplicit) (Syntax: 'foreach (va ... }') ICollectionExpressionOperation (0 elements, ConstructMethod: null) (OperationKind.CollectionExpression, Type: System.Collections.Generic.IEnumerable, IsImplicit) (Syntax: 'foreach (va ... }') Elements(0) InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) diff --git a/src/Compilers/Core/Portable/Operations/ArgumentKind.cs b/src/Compilers/Core/Portable/Operations/ArgumentKind.cs index a7879d27ff8d2..82ac63d684332 100644 --- a/src/Compilers/Core/Portable/Operations/ArgumentKind.cs +++ b/src/Compilers/Core/Portable/Operations/ArgumentKind.cs @@ -21,14 +21,20 @@ public enum ArgumentKind /// /// Argument is a param array created by compilers for the matching C# params or VB ParamArray parameter. - /// Note, the value is a an array creation expression that encapsulates all the elements, if any. + /// Note, the value is an array creation expression that encapsulates all the elements, if any. /// - ParamArray = 0x2, // PROTOTYPE(ParamsCollections): Reuse or add a special kind for non-array params collections? + ParamArray = 0x2, /// /// Argument is a default value supplied automatically by the compilers. /// - DefaultValue = 0x3 + DefaultValue = 0x3, + + /// + /// Argument is a param collection created by compilers for the matching C# params parameter. + /// Note, the value is a collection expression that encapsulates all the elements, if any. + /// + ParamCollection = 0x4, } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 203f163e6d1d5..6f162b536700e 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,5 +1,6 @@ Microsoft.CodeAnalysis.IParameterSymbol.IsParamsCollection.get -> bool Microsoft.CodeAnalysis.IParameterSymbol.IsParamsArray.get -> bool +Microsoft.CodeAnalysis.Operations.ArgumentKind.ParamCollection = 4 -> Microsoft.CodeAnalysis.Operations.ArgumentKind Microsoft.CodeAnalysis.Diagnostics.SuppressionInfo.ProgrammaticSuppressions.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.OperationKind.CollectionExpression = 127 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.Spread = 128 -> Microsoft.CodeAnalysis.OperationKind diff --git a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs index 9fda296a509b7..4a48fc7fe5c5f 100644 --- a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs +++ b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs @@ -576,7 +576,7 @@ public override void VisitFunctionPointerInvocation(IFunctionPointerInvocationOp public override void VisitArgument(IArgumentOperation operation) { Assert.Equal(OperationKind.Argument, operation.Kind); - Assert.Contains(operation.ArgumentKind, new[] { ArgumentKind.DefaultValue, ArgumentKind.Explicit, ArgumentKind.ParamArray }); + Assert.Contains(operation.ArgumentKind, new[] { ArgumentKind.DefaultValue, ArgumentKind.Explicit, ArgumentKind.ParamArray, ArgumentKind.ParamCollection }); var parameter = operation.Parameter; Assert.Same(operation.Value, operation.ChildOperations.Single());