From 1365a81c14816a4d24a011718f01a5e41a81bc80 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 3 May 2021 12:03:30 -0700 Subject: [PATCH] Allow `with` on anonymous types --- .../Portable/Binder/Binder.ValueChecks.cs | 14 +- .../Portable/Binder/Binder_WithExpression.cs | 6 +- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/MessageID.cs | 2 + .../LocalRewriter_ObjectCreationExpression.cs | 72 +- .../Lowering/SyntheticBoundNodeFactory.cs | 6 + .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Semantic/Semantics/RecordStructTests.cs | 1128 ++++++++++++++++- .../Operations/ControlFlowGraphBuilder.cs | 140 +- .../Test/Utilities/CSharp/CSharpTestBase.cs | 1 + 22 files changed, 1415 insertions(+), 22 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 9b16f3b928573..15d3794831acf 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1076,7 +1076,8 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV if (setMethod is null) { var containing = this.ContainingMemberOrLambda; - if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing)) + if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing) + && !isAllowedReadonlyOnlySet(receiver)) { Error(diagnostics, ErrorCode.ERR_AssgReadonlyProp, node, propertySymbol); return false; @@ -1203,6 +1204,17 @@ bool reportUseSite(MethodSymbol accessor) return false; } + static bool isAllowedReadonlyOnlySet(BoundExpression receiver) + { + // ok: anonymousType with { Property = ... } + if (receiver is BoundObjectOrCollectionValuePlaceholder && receiver.Type.IsAnonymousType) + { + return true; + } + + return false; + } + bool isAllowedInitOnlySet(BoundExpression receiver) { // ok: new C() { InitOnlyProperty = ... } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs index 071da76407cbf..1b5ba78f008be 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_WithExpression.cs @@ -32,10 +32,14 @@ private BoundExpression BindWithExpression(WithExpressionSyntax syntax, BindingD } MethodSymbol? cloneMethod = null; - if (receiverType.IsValueType) + if (receiverType.IsValueType && !receiverType.IsPointerOrFunctionPointer()) { CheckFeatureAvailability(syntax, MessageID.IDS_FeatureWithOnStructs, diagnostics); } + else if (receiverType.IsAnonymousType) + { + CheckFeatureAvailability(syntax, MessageID.IDS_FeatureWithOnAnonymousTypes, diagnostics); + } else if (!receiverType.IsErrorType()) { CompoundUseSiteInfo useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 95faf681b440a..6a05f90e59e15 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6576,6 +6576,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ with on structs + + with on anonymous types + positional fields in records diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 428485d9b1d18..6272644a9d2d3 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -223,6 +223,7 @@ internal enum MessageID IDS_FeatureGlobalUsing = MessageBase + 12798, IDS_FeatureInferredDelegateType = MessageBase + 12799, IDS_FeatureLambdaAttributes = MessageBase + 12800, + IDS_FeatureWithOnAnonymousTypes = MessageBase + 12801, } // Message IDs may refer to strings that need to be localized. @@ -334,6 +335,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_FeatureSealedToStringInRecord: // semantic check case MessageID.IDS_FeatureRecordStructs: case MessageID.IDS_FeatureWithOnStructs: // semantic check + case MessageID.IDS_FeatureWithOnAnonymousTypes: // semantic check case MessageID.IDS_FeaturePositionalFieldsInRecords: // semantic check case MessageID.IDS_FeatureGlobalUsing: case MessageID.IDS_FeatureInferredDelegateType: // semantic check diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs index 3ffe67f107dca..8956950564011 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -103,7 +104,9 @@ public override BoundNode VisitObjectCreationExpression(BoundObjectCreationExpre public override BoundNode VisitWithExpression(BoundWithExpression withExpr) { - Debug.Assert(withExpr.Receiver.Type!.Equals(withExpr.Type, TypeCompareKind.ConsiderEverything)); + TypeSymbol type = withExpr.Type; + BoundExpression receiver = withExpr.Receiver; + Debug.Assert(receiver.Type!.Equals(type, TypeCompareKind.ConsiderEverything)); // for a with expression of the form // @@ -116,6 +119,10 @@ public override BoundNode VisitWithExpression(BoundWithExpression withExpr) // tmp.P2 = e2; // tmp // + // if the receiver is an anonymous type, then invoke its constructor: + // + // new Type(e1, receiver.P2); + // // otherwise the receiver is a record class and we want to lower it to a call to its `Clone` method, then // set the given record properties. i.e. // @@ -124,11 +131,26 @@ public override BoundNode VisitWithExpression(BoundWithExpression withExpr) // tmp.P2 = e2; // tmp - BoundExpression expression; + BoundExpression rewrittenReceiver = VisitExpression(receiver); - if (withExpr.Type.IsValueType) + if (type.IsAnonymousType) { - expression = VisitExpression(withExpr.Receiver); + var anonymousType = (AnonymousTypeManager.AnonymousTypePublicSymbol)type; + var sideEffects = ArrayBuilder.GetInstance(); + var temps = ArrayBuilder.GetInstance(); + BoundLocal oldValue = _factory.StoreToTemp(rewrittenReceiver, out BoundAssignmentOperator boundAssignmentToTemp); + temps.Add(oldValue.LocalSymbol); + sideEffects.Add(boundAssignmentToTemp); + + BoundExpression value = _factory.New(anonymousType, getAnonymousTypeValues(withExpr, oldValue, anonymousType, sideEffects, temps)); + + return new BoundSequence(withExpr.Syntax, temps.ToImmutableAndFree(), sideEffects.ToImmutableAndFree(), value, type); + } + + BoundExpression expression; + if (type.IsValueType) + { + expression = rewrittenReceiver; } else { @@ -136,9 +158,9 @@ public override BoundNode VisitWithExpression(BoundWithExpression withExpr) Debug.Assert(withExpr.CloneMethod.ParameterCount == 0); expression = _factory.Convert( - withExpr.Type, + type, _factory.Call( - VisitExpression(withExpr.Receiver), + rewrittenReceiver, withExpr.CloneMethod)); } @@ -146,7 +168,43 @@ public override BoundNode VisitWithExpression(BoundWithExpression withExpr) withExpr.Syntax, expression, withExpr.InitializerExpression, - withExpr.Type); + type); + + ImmutableArray getAnonymousTypeValues(BoundWithExpression withExpr, BoundExpression oldValue, AnonymousTypeManager.AnonymousTypePublicSymbol anonymousType, + ArrayBuilder sideEffects, ArrayBuilder temps) + { + var dictionary = PooledDictionary.GetInstance(); + foreach (BoundExpression initializer in withExpr.InitializerExpression.Initializers) + { + var assignment = (BoundAssignmentOperator)initializer; + var left = (BoundObjectInitializerMember)assignment.Left; + Debug.Assert(left.MemberSymbol is not null); + + // We evaluate the values provided in source first + BoundLocal valueTemp = _factory.StoreToTemp(assignment.Right, out BoundAssignmentOperator boundAssignmentToTemp); + temps.Add(valueTemp.LocalSymbol); + sideEffects.Add(boundAssignmentToTemp); + + dictionary.Add((PropertySymbol)left.MemberSymbol, valueTemp); + } + + var builder = ArrayBuilder.GetInstance(anonymousType.Properties.Length); + foreach (var property in anonymousType.Properties) + { + if (dictionary.TryGetValue(property, out var initializerValue)) + { + builder.Add(initializerValue); + } + else + { + // The values that are implicitly copied over will get evaluated afterwards, in the order they are needed + builder.Add(_factory.Property(oldValue, property)); + } + } + + dictionary.Free(); + return builder.ToImmutableAndFree(); + } } [return: NotNullIfNotNull("initializerExpressionOpt")] diff --git a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index 51f1b6fc04241..947d3f817b779 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -633,6 +633,12 @@ public BoundObjectCreationExpression New(NamedTypeSymbol type, params BoundExpre public BoundObjectCreationExpression New(MethodSymbol ctor, params BoundExpression[] args) => New(ctor, args.ToImmutableArray()); + public BoundObjectCreationExpression New(NamedTypeSymbol type, ImmutableArray args) + { + var ctor = type.InstanceConstructors.Single(c => c.ParameterCount == args.Length); + return New(ctor, args); + } + public BoundObjectCreationExpression New(MethodSymbol ctor, ImmutableArray args) => new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true }; diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index ba22439afd239..b7f2b9d54ee1f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 99ccdf3859eb5..358bdc808307f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 14a9ff82800d6..e9e1d687f2a67 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 308f4e9381e0f..157af6779773a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 9c44b4d5fc97b..ce6bea4aff54f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 6f905157a203a..c4854545813e5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index fd69df4954d0a..1364c8b4fe6c0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 521dced42cada..4ab8837e90eed 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 7a59773d62eff..1ded6bf3a9fab 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index d02a42a8d7d16..30f97c0caeb08 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 41b47624917e8..c8fa868cd63a0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index db31b984b521c..d7711449e036f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index d6336ba680f10..d3b29f8474e81 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1032,6 +1032,11 @@ sealed ToString in record + + with on anonymous types + with on anonymous types + + with on structs with on structs diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index 4247015c26a1d..7524d847a8620 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -4414,7 +4414,7 @@ public record struct C public void EqualityOperators_01() { var source = @" -record struct A(int X) +record struct A(int X) { public bool Equals(ref A other) => throw null; @@ -5422,7 +5422,7 @@ public void GetDeclaredSymbolOnAnOutLocalInPropertyInitializer() record struct R(int I) { public int I { get; init; } = M(out int i); - static int M(out int i) => throw null; + static int M(out int i) => throw null; } "; var comp = CreateCompilation(src); @@ -6443,9 +6443,9 @@ public struct B { public int X { get; set; } public B M() - { + /**/{ return this with { X = 42 }; - } + }/**/ }"; var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); comp.VerifyEmitDiagnostics( @@ -6477,6 +6477,58 @@ .locals init (B V_0) var with = tree.GetRoot().DescendantNodes().OfType().Single(); var type = model.GetTypeInfo(with); Assert.Equal("B", type.Type.ToTestDisplayString()); + + var operation = model.GetOperation(with); + + VerifyOperationTree(comp, operation, @" +IWithOperation (OperationKind.With, Type: B) (Syntax: 'this with { X = 42 }') + Operand: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + CloneMethod: null + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: B) (Syntax: '{ X = 42 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 42') + Left: + IPropertyReferenceOperation: System.Int32 B.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'X') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + CaptureIds: [0] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'this') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: B) (Syntax: 'this') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 42') + Left: + IPropertyReferenceOperation: System.Int32 B.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') + Next (Return) Block[B2] + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: B, IsImplicit) (Syntax: 'this') + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); } [Fact] @@ -6834,7 +6886,11 @@ unsafe class C } }"; var comp = CreateCompilation(src, options: TestOptions.UnsafeReleaseDll, parseOptions: TestOptions.RegularPreview); - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (6,16): error CS8858: The receiver type 'int*' is not a valid record type and is not a struct type. + // return i with { }; + Diagnostic(ErrorCode.ERR_CannotClone, "i").WithArguments("int*").WithLocation(6, 16) + ); } [Fact] @@ -7205,6 +7261,1068 @@ public static void Main() ); } + [Fact] + public void WithExpr_AnonymousType_ChangeAllProperties() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + { + var a = new { A = 10, B = 20 }; + var b = a with { A = Identity(30), B = Identity(40) }; + System.Console.Write(b); + } + + static T Identity(T t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (9,17): error CS8652: The feature 'with on anonymous types' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // var b = a with { A = Identity(30), B = Identity(40) }; + Diagnostic(ErrorCode.ERR_FeatureInPreview, "a with { A = Identity(30), B = Identity(40) }").WithArguments("with on anonymous types").WithLocation(9, 17) + ); + + comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "{ A = 30, B = 40 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 37 (0x25) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: pop + IL_000a: ldc.i4.s 30 + IL_000c: call ""int C.Identity(int)"" + IL_0011: ldc.i4.s 40 + IL_0013: call ""int C.Identity(int)"" + IL_0018: stloc.0 + IL_0019: ldloc.0 + IL_001a: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_001f: call ""void System.Console.Write(object)"" + IL_0024: ret +} +"); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeAllProperties_ReverseOrder() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + { + var a = new { A = 10, B = 20 }; + var b = a with { B = Identity(40), A = Identity(30) }; + System.Console.Write(b); + } + + static T Identity(T t) => t; +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "{ A = 30, B = 40 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 37 (0x25) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: pop + IL_000a: ldc.i4.s 40 + IL_000c: call ""int C.Identity(int)"" + IL_0011: stloc.0 + IL_0012: ldc.i4.s 30 + IL_0014: call ""int C.Identity(int)"" + IL_0019: ldloc.0 + IL_001a: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_001f: call ""void System.Console.Write(object)"" + IL_0024: ret +} +"); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeNoProperty() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = M2(a) with { }; + System.Console.Write(b); + }/**/ + + static T M2(T t) + { + System.Console.Write(""M2 ""); + return t; + } +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "M2 { A = 10, B = 20 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 38 (0x26) + .maxstack 2 + .locals init (<>f__AnonymousType0 V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: call "" C.M2<>()"" + IL_000e: stloc.0 + IL_000f: ldloc.0 + IL_0010: callvirt ""int <>f__AnonymousType0.A.get"" + IL_0015: ldloc.0 + IL_0016: callvirt ""int <>f__AnonymousType0.B.get"" + IL_001b: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0020: call ""void System.Console.Write(object)"" + IL_0025: ret +} +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [4] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'M2(a)') + Value: + IInvocationOperation ( C.M2<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'M2(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'M2(a) with { }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'M2(a) with { }') + Value: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = M2(a) with { }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = M2(a) with { }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'M2(a) with { }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'M2(a) with { }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'M2(a) with { }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'M2(a) with { }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'M2(a)') + Next (Regular) Block[B4] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = a with { B = Identity(30) }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "{ A = 10, B = 30 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 34 (0x22) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: ldc.i4.s 30 + IL_000b: call ""int C.Identity(int)"" + IL_0010: stloc.0 + IL_0011: callvirt ""int <>f__AnonymousType0.A.get"" + IL_0016: ldloc.0 + IL_0017: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_001c: call ""void System.Console.Write(object)"" + IL_0021: ret +} +"); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var withExpr = tree.GetRoot().DescendantNodes().OfType().Single(); + var operation = model.GetOperation(withExpr); + + VerifyOperationTree(comp, operation, @" +IWithOperation (OperationKind.With, Type: ) (Syntax: 'a with { B ... ntity(30) }') + Operand: + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + CloneMethod: null + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: ) (Syntax: '{ B = Identity(30) }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'B = Identity(30)') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'B') + Right: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [4] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a') + Value: + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(30)') + Value: + IInvocationOperation (System.Int32 C.Identity(System.Int32 t)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'Identity(30)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: '30') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + 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) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = a with ... ntity(30) }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = a with ... ntity(30) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'a with { B ... ntity(30) }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'a with { B ... ntity(30) }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'a') + Next (Regular) Block[B4] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty_WithMethodCallForTarget() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = Identity(a) with { B = 30 }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: "{ A = 10, B = 30 }"); + verifier.VerifyIL("C.M", @" +{ + // Code size 34 (0x22) + .maxstack 2 + .locals init (int V_0) + IL_0000: ldc.i4.s 10 + IL_0002: ldc.i4.s 20 + IL_0004: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_0009: call "" C.Identity<>()"" + IL_000e: ldc.i4.s 30 + IL_0010: stloc.0 + IL_0011: callvirt ""int <>f__AnonymousType0.A.get"" + IL_0016: ldloc.0 + IL_0017: newobj ""<>f__AnonymousType0..ctor(int, int)"" + IL_001c: call ""void System.Console.Write(object)"" + IL_0021: ret +} +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [4] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (3) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a)') + Value: + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '30') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B3] + Leaving: {R4} + } + Block[B3] - Block + Predecessors: [B2] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = Identit ... { B = 30 }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = Identit ... { B = 30 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'Identity(a) ... { B = 30 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B4] + Leaving: {R3} + } + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty_WithCoalescingExpressionForTarget() + { + var src = @" +C.M(); + +public class C +{ + public static void M() + /**/{ + var a = new { A = 10, B = 20 }; + var b = (Identity(a) ?? Identity2(a)) with { B = 30 }; + System.Console.Write(b); + }/**/ + + static T Identity(T t) => t; + static T Identity2(T t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "{ A = 10, B = 30 }"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ a] [ b] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '10') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 10) (Syntax: '10') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '20') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 20) (Syntax: '20') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Left: + ILocalReferenceOperation: a (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'a = new { A ... 0, B = 20 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = 10, B = 20 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 10) (Syntax: 'A = 10') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 10, IsImplicit) (Syntax: '10') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, Constant: 20) (Syntax: 'B = 20') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = 10, B = 20 }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.Int32, Constant: 20, IsImplicit) (Syntax: '20') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} {R5} + } + .locals {R3} + { + CaptureIds: [4] [5] + .locals {R4} + { + CaptureIds: [2] + .locals {R5} + { + CaptureIds: [3] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a)') + Value: + IInvocationOperation ( C.Identity<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + Jump if True (Regular) to Block[B4] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'Identity(a)') + Operand: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Leaving: {R5} + Next (Regular) Block[B3] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(a)') + Value: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a)') + Next (Regular) Block[B5] + Leaving: {R5} + } + Block[B4] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity2(a)') + Value: + IInvocationOperation ( C.Identity2<>( t)) (OperationKind.Invocation, Type: ) (Syntax: 'Identity2(a)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'a') + ILocalReferenceOperation: a (OperationKind.LocalReference, Type: ) (Syntax: 'a') + 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) + Next (Regular) Block[B5] + Block[B5] - Block + Predecessors: [B3] [B4] + Statements (2) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '30') + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 30) (Syntax: '30') + IFlowCaptureOperation: 5 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Value: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... dentity2(a)') + Next (Regular) Block[B6] + Leaving: {R4} + } + Block[B6] - Block + Predecessors: [B5] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'b = (Identi ... { B = 30 }') + Left: + ILocalReferenceOperation: b (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'b = (Identi ... { B = 30 }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: '(Identity(a ... { B = 30 }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .A { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 5 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... dentity2(a)') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Left: + IPropertyReferenceOperation: System.Int32 .B { get; } (OperationKind.PropertyReference, Type: System.Int32, IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: '(Identity(a ... { B = 30 }') + Right: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'Identity(a) ... dentity2(a)') + Next (Regular) Block[B7] + Leaving: {R3} + } + Block[B7] - Block + Predecessors: [B6] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(b);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(b)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'b') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'b') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: b (OperationKind.LocalReference, Type: ) (Syntax: 'b') + 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) + Next (Regular) Block[B8] + Leaving: {R1} + } + Block[B8] - Exit + Predecessors: [B7] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_ChangeOneProperty_WithCoalescingExpressionForValue() + { + var src = @" +C.M(""hello"", ""world""); + +public class C +{ + public static void M(string hello, string world) + /**/{ + var x = new { A = hello, B = string.Empty }; + var y = x with { B = Identity(null) ?? Identity2(world) }; + System.Console.Write(y); + }/**/ + + static string Identity(string t) => t; + static string Identity2(string t) => t; +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: "{ A = hello, B = world }"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} {R2} +.locals {R1} +{ + Locals: [ x] [ y] + .locals {R2} + { + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (3) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'hello') + Value: + IParameterReferenceOperation: hello (OperationKind.ParameterReference, Type: System.String) (Syntax: 'hello') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'string.Empty') + Value: + IFieldReferenceOperation: System.String System.String.Empty (Static) (OperationKind.FieldReference, Type: System.String) (Syntax: 'string.Empty') + Instance Receiver: + null + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'x = new { A ... ing.Empty }') + Left: + ILocalReferenceOperation: x (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'x = new { A ... ing.Empty }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'new { A = h ... ing.Empty }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'A = hello') + Left: + IPropertyReferenceOperation: System.String .A { get; } (OperationKind.PropertyReference, Type: System.String) (Syntax: 'A') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = h ... ing.Empty }') + Right: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'hello') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'B = string.Empty') + Left: + IPropertyReferenceOperation: System.String .B { get; } (OperationKind.PropertyReference, Type: System.String) (Syntax: 'B') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'new { A = h ... ing.Empty }') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'string.Empty') + Next (Regular) Block[B2] + Leaving: {R2} + Entering: {R3} {R4} + } + .locals {R3} + { + CaptureIds: [3] [5] + .locals {R4} + { + CaptureIds: [2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'x') + Value: + ILocalReferenceOperation: x (OperationKind.LocalReference, Type: ) (Syntax: 'x') + Next (Regular) Block[B3] + Entering: {R5} + .locals {R5} + { + CaptureIds: [4] + Block[B3] - Block + Predecessors: [B2] + Statements (1) + IFlowCaptureOperation: 4 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(null)') + Value: + IInvocationOperation (System.String C.Identity(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity(null)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'null') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + 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) + Jump if True (Regular) to Block[B5] + IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'Identity(null)') + Operand: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity(null)') + Leaving: {R5} + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B3] + Statements (1) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity(null)') + Value: + IFlowCaptureReferenceOperation: 4 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'Identity(null)') + Next (Regular) Block[B6] + Leaving: {R5} + } + Block[B5] - Block + Predecessors: [B3] + Statements (1) + IFlowCaptureOperation: 3 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'Identity2(world)') + Value: + IInvocationOperation (System.String C.Identity2(System.String t)) (OperationKind.Invocation, Type: System.String) (Syntax: 'Identity2(world)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: t) (OperationKind.Argument, Type: null) (Syntax: 'world') + IParameterReferenceOperation: world (OperationKind.ParameterReference, Type: System.String) (Syntax: 'world') + 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) + Next (Regular) Block[B6] + Block[B6] - Block + Predecessors: [B4] [B5] + Statements (1) + IFlowCaptureOperation: 5 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Value: + IPropertyReferenceOperation: System.String .A { get; } (OperationKind.PropertyReference, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Instance Receiver: + IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'x') + Next (Regular) Block[B7] + Leaving: {R4} + } + Block[B7] - Block + Predecessors: [B6] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: , IsImplicit) (Syntax: 'y = x with ... y2(world) }') + Left: + ILocalReferenceOperation: y (IsDeclaration: True) (OperationKind.LocalReference, Type: , IsImplicit) (Syntax: 'y = x with ... y2(world) }') + Right: + IAnonymousObjectCreationOperation (OperationKind.AnonymousObjectCreation, Type: ) (Syntax: 'x with { B ... y2(world) }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Left: + IPropertyReferenceOperation: System.String .A { get; } (OperationKind.PropertyReference, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Right: + IFlowCaptureReferenceOperation: 5 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'x') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Left: + IPropertyReferenceOperation: System.String .B { get; } (OperationKind.PropertyReference, Type: System.String, IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: , IsImplicit) (Syntax: 'x with { B ... y2(world) }') + Right: + IFlowCaptureReferenceOperation: 3 (OperationKind.FlowCaptureReference, Type: , IsImplicit) (Syntax: 'x') + Next (Regular) Block[B8] + Leaving: {R3} + } + Block[B8] - Block + Predecessors: [B7] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'System.Console.Write(y);') + Expression: + IInvocationOperation (void System.Console.Write(System.Object value)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'System.Console.Write(y)') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: value) (OperationKind.Argument, Type: null) (Syntax: 'y') + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, IsImplicit) (Syntax: 'y') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILocalReferenceOperation: y (OperationKind.LocalReference, Type: ) (Syntax: 'y') + 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) + Next (Regular) Block[B9] + Leaving: {R1} +} +Block[B9] - Exit + Predecessors: [B8] + Statements (0) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void WithExpr_AnonymousType_BadMember() + { + var src = @" +public class C +{ + public static void M() + { + var a = new { A = 10 }; + var b = a with { Error = 20 }; + } +}"; + + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,26): error CS0117: '' does not contain a definition for 'Error' + // var b = a with { Error = 20 }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "Error").WithArguments("", "Error").WithLocation(7, 26) + ); + } + + [Fact] + public void WithExpr_AnonymousType_CannotSet() + { + var src = @" +public class C +{ + public static void M() + { + var a = new { A = 10 }; + a.A = 20; + + var b = new { B = a }; + b.B.A = 30; + } +}"; + var comp = CreateCompilation(src, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics( + // (7,9): error CS0200: Property or indexer '.A' cannot be assigned to -- it is read only + // a.A = 20; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "a.A").WithArguments(".A").WithLocation(7, 9), + // (10,9): error CS0200: Property or indexer '.A' cannot be assigned to -- it is read only + // b.B.A = 30; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "b.B.A").WithArguments(".A").WithLocation(10, 9) + ); + } + [Fact] public void AttributesOnPrimaryConstructorParameters_01() { diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index 62885c64f7492..cc37fa79f4df7 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -14,6 +14,15 @@ namespace Microsoft.CodeAnalysis.FlowAnalysis { + /// + /// Some basic concepts: + /// - Basic blocks are sequences of statements/operations with no branching. The only branching + /// allowed is at the end of the basic block. + /// - Regions group blocks together and represent the lifetime of locals and captures, loosely similar to scopes in C#. + /// There are different kinds of regions, . + /// - converts values on the stack into captures. It should be + /// called before entering a new region (to avoid doing it later thus giving those captures an incorrect lifetime). + /// internal sealed partial class ControlFlowGraphBuilder : OperationVisitor { private readonly Compilation _compilation; @@ -5624,7 +5633,12 @@ public override IOperation VisitFlowCapture(IFlowCaptureOperation operation, int public override IOperation VisitFlowCaptureReference(IFlowCaptureReferenceOperation operation, int? captureIdForResult) { - throw ExceptionUtilities.Unreachable; + Debug.Assert(operation.Parent is SimpleAssignmentOperation assignment + && assignment.Value == (object)operation + && assignment.Parent is AnonymousObjectCreationOperation); + + return new FlowCaptureReferenceOperation(((FlowCaptureReferenceOperation)operation).Id, semanticModel: null, + operation.Syntax, operation.Type, operation.GetConstantValue(), operation.IsImplicit); } public override IOperation VisitIsNull(IIsNullOperation operation, int? captureIdForResult) @@ -7130,24 +7144,134 @@ public override IOperation VisitArgument(IArgumentOperation operation, int? capt throw ExceptionUtilities.Unreachable; } - public override IOperation VisitUsingDeclaration(IUsingDeclarationOperation operation, int? argument) + public override IOperation VisitUsingDeclaration(IUsingDeclarationOperation operation, int? captureIdForResult) { throw ExceptionUtilities.Unreachable; } - public override IOperation VisitWith(IWithOperation operation, int? argument) + public override IOperation VisitWith(IWithOperation operation, int? captureIdForResult) { + if (operation.Type!.IsAnonymousType) + { + return handleAnonymousTypeWithExpression((WithOperation)operation, captureIdForResult); + } + EvalStackFrame frame = PushStackFrame(); // Initializer is removed from the tree and turned into a series of statements that assign to the cloned instance IOperation visitedInstance = VisitRequired(operation.Operand); + IOperation cloned; + if (operation.Type.IsValueType) + { + cloned = visitedInstance; + } + else + { + cloned = operation.CloneMethod is null + ? MakeInvalidOperation(visitedInstance.Type, visitedInstance) + : new InvocationOperation(operation.CloneMethod, visitedInstance, + isVirtual: true, arguments: ImmutableArray.Empty, + semanticModel: null, operation.Syntax, operation.Type, isImplicit: true); + } + + return PopStackFrame(frame, HandleObjectOrCollectionInitializer(operation.Initializer, cloned)); + + // For `old with { Property = ... }` we're going to do the same as `new { Property = ..., OtherProperty = old.OtherProperty }` + IOperation handleAnonymousTypeWithExpression(WithOperation operation, int? captureIdForResult) + { + Debug.Assert(operation.Type!.IsAnonymousType); + SpillEvalStack(); // before entering a new region, we ensure that anything that needs spilling was spilled + + // The outer region holds captures for all the values for the anonymous object creation + var outerCaptureRegion = CurrentRegionRequired; + + // The inner region holds the capture for the operand (ie. old value) + var innerCaptureRegion = new RegionBuilder(ControlFlowRegionKind.LocalLifetime); + EnterRegion(innerCaptureRegion); + + int oldValueCaptureId = GetNextCaptureId(innerCaptureRegion); + VisitAndCapture(operation.Operand, oldValueCaptureId); + // calls to VisitAndCapture may enter regions, so we reset things + LeaveRegionsUpTo(innerCaptureRegion); + + var explicitProperties = PooledDictionary.GetInstance(); + var initializers = operation.Initializer.Initializers; + var initializerBuilder = ArrayBuilder.GetInstance(initializers.Length); + + // Visit and capture all the values, and construct assignments using capture references + foreach (IOperation initializer in initializers) + { + var simpleAssignment = (ISimpleAssignmentOperation)initializer; + var propertyReference = (IPropertyReferenceOperation)simpleAssignment.Target; + + Debug.Assert(propertyReference != null); + Debug.Assert(propertyReference.Arguments.IsEmpty); + Debug.Assert(propertyReference.Instance != null); + Debug.Assert(propertyReference.Instance.Kind == OperationKind.InstanceReference); + Debug.Assert(((IInstanceReferenceOperation)propertyReference.Instance).ReferenceKind == InstanceReferenceKind.ImplicitReceiver); + + int valueCaptureId = GetNextCaptureId(outerCaptureRegion); + VisitAndCapture(simpleAssignment.Value, valueCaptureId); + LeaveRegionsUpTo(innerCaptureRegion); + var valueCaptureRef = new FlowCaptureReferenceOperation(valueCaptureId, operation.Operand.Syntax, + operation.Operand.Type, constantValue: operation.Operand.GetConstantValue()); - IOperation cloned = operation.CloneMethod is null - ? MakeInvalidOperation(visitedInstance.Type, visitedInstance) - : new InvocationOperation(operation.CloneMethod, visitedInstance, - isVirtual: true, arguments: ImmutableArray.Empty, + var property = propertyReference.Property; + var assignment = makeAssignment(property, valueCaptureRef, operation); + + explicitProperties.Add(property.Name, assignment); + } + + // Make a sequence for all properties (in order), constructing assignments for the implicitly set properties + var type = (INamedTypeSymbol)operation.Type; + foreach (IPropertySymbol property in operation.Type.GetMembers().Where(m => m.Kind == SymbolKind.Property)) + { + if (explicitProperties.TryGetValue(property.Name, out var assignment)) + { + initializerBuilder.Add(assignment); + } + else + { + // `oldInstance` + var oldInstance = new FlowCaptureReferenceOperation(oldValueCaptureId, operation.Operand.Syntax, + operation.Operand.Type, constantValue: operation.Operand.GetConstantValue()); + + // `oldInstance.Property` + var visitedValue = new PropertyReferenceOperation(property, ImmutableArray.Empty, oldInstance, + semanticModel: null, operation.Syntax, property.Type, isImplicit: true); + + int extraValueCaptureId = GetNextCaptureId(outerCaptureRegion); + AddStatement(new FlowCaptureOperation(extraValueCaptureId, operation.Syntax, visitedValue)); + + LeaveRegionsUpTo(innerCaptureRegion); + var extraValueCaptureRef = new FlowCaptureReferenceOperation(extraValueCaptureId, operation.Operand.Syntax, + operation.Operand.Type, constantValue: operation.Operand.GetConstantValue()); + + assignment = makeAssignment(property, extraValueCaptureRef, operation); + initializerBuilder.Add(assignment); + } + } + LeaveRegionsUpTo(outerCaptureRegion); + + explicitProperties.Free(); + + return new AnonymousObjectCreationOperation(initializerBuilder.ToImmutableAndFree(), semanticModel: null, operation.Syntax, operation.Type, operation.IsImplicit); + } + + // Build an operation for `.Property = ` + SimpleAssignmentOperation makeAssignment(IPropertySymbol property, IOperation capturedValue, WithOperation operation) + { + // + var implicitReceiver = new InstanceReferenceOperation(InstanceReferenceKind.ImplicitReceiver, semanticModel: null, operation.Syntax, operation.Type, isImplicit: true); - return PopStackFrame(frame, HandleObjectOrCollectionInitializer(operation.Initializer, cloned)); + // .Property + var target = new PropertyReferenceOperation(property, ImmutableArray.Empty, implicitReceiver, + semanticModel: null, operation.Syntax, property.Type, isImplicit: true); + + // .Property = + return new SimpleAssignmentOperation(isRef: false, target, capturedValue, + semanticModel: null, operation.Syntax, property.Type, constantValue: null, isImplicit: true); + } } } } diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 0fbf0ae904365..1c115f3ff7428 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -1881,6 +1881,7 @@ protected static void VerifyFlowGraphForTest(CSharpCompilation comp { var tree = compilation.SyntaxTrees[0]; SyntaxNode syntaxNode = GetSyntaxNodeOfTypeForBinding(GetSyntaxNodeList(tree)); + Debug.Assert(syntaxNode is not null, "Did you forget to place /**/ comments in your source?"); VerifyFlowGraph(compilation, syntaxNode, expectedFlowGraph); }