diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index bf80fde6324d4..cd849a9163804 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -95,6 +95,8 @@ internal IOperation CreateInternal(BoundNode boundNode) return CreateBoundDynamicIndexerAccessExpressionOperation((BoundDynamicIndexerAccess)boundNode); case BoundKind.ObjectCreationExpression: return CreateBoundObjectCreationExpressionOperation((BoundObjectCreationExpression)boundNode); + case BoundKind.WithExpression: + return CreateBoundWithExpressionOperation((BoundWithExpression)boundNode); case BoundKind.DynamicObjectCreationExpression: return CreateBoundDynamicObjectCreationExpressionOperation((BoundDynamicObjectCreationExpression)boundNode); case BoundKind.ObjectInitializerExpression: @@ -672,6 +674,16 @@ private IOperation CreateBoundObjectCreationExpressionOperation(BoundObjectCreat return new CSharpLazyObjectCreationOperation(this, boundObjectCreationExpression, constructor.GetPublicSymbol(), _semanticModel, syntax, type, constantValue, isImplicit); } + private IOperation CreateBoundWithExpressionOperation(BoundWithExpression boundWithExpression) + { + MethodSymbol constructor = boundWithExpression.CloneMethod; + SyntaxNode syntax = boundWithExpression.Syntax; + ITypeSymbol type = boundWithExpression.Type.GetPublicSymbol(); + Optional constantValue = ConvertToOptional(boundWithExpression.ConstantValue); + bool isImplicit = boundWithExpression.WasCompilerGenerated; + return new CSharpLazyWithExpressionOperation(this, boundWithExpression, constructor.GetPublicSymbol(), _semanticModel, syntax, type, constantValue, isImplicit); + } + private IDynamicObjectCreationOperation CreateBoundDynamicObjectCreationExpressionOperation(BoundDynamicObjectCreationExpression boundDynamicObjectCreationExpression) { ImmutableArray argumentNames = boundDynamicObjectCreationExpression.ArgumentNamesOpt.NullToEmpty(); diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs index 91f2f0f43065d..94726685db4f9 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationNodes.cs @@ -1037,6 +1037,29 @@ protected override ImmutableArray CreateArguments() } } + internal sealed class CSharpLazyWithExpressionOperation : LazyWithExpressionOperation + { + private readonly CSharpOperationFactory _operationFactory; + private readonly BoundWithExpression _withExpression; + + internal CSharpLazyWithExpressionOperation(CSharpOperationFactory operationFactory, BoundWithExpression withExpression, IMethodSymbol cloneMethod, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) : + base(cloneMethod, semanticModel, syntax, type, constantValue, isImplicit) + { + _operationFactory = operationFactory; + _withExpression = withExpression; + } + + protected override IObjectOrCollectionInitializerOperation CreateInitializer() + { + return (IObjectOrCollectionInitializerOperation)_operationFactory.Create(_withExpression.InitializerExpression); + } + + protected override IOperation CreateValue() + { + return _operationFactory.Create(_withExpression.Receiver); + } + } + internal sealed class CSharpLazyAnonymousObjectCreationOperation : LazyAnonymousObjectCreationOperation { private readonly CSharpOperationFactory _operationFactory; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index aa5319cfd9bdd..5c31cfe19e949 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -653,6 +653,50 @@ public static void Main() }"; var comp = CreateCompilation(src); comp.VerifyDiagnostics(); + + var root = comp.SyntaxTrees[0].GetRoot(); + var main = root.DescendantNodes().OfType().First(); + Assert.Equal("Main", main.Identifier.ToString()); + VerifyFlowGraph(comp, main, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [C c] + Block[B1] - Block + Predecessors: [B0] + Statements (2) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsImplicit) (Syntax: 'c = new C(0)') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = new C(0)') + Right: + IObjectCreationOperation (Constructor: C..ctor(System.Int32 X)) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C(0)') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: '0') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + 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) + Initializer: + null + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c = c with { };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C) (Syntax: 'c = c with { }') + Left: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Right: + IInvocationOperation ( C C.Clone()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { }') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Arguments(0) + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"); } [Fact] @@ -1252,6 +1296,144 @@ .maxstack 3 IL_002d: pop IL_002e: ret }"); + + var comp = (CSharpCompilation)verifier.Compilation; + var tree = comp.SyntaxTrees.First(); + var root = tree.GetRoot(); + var model = comp.GetSemanticModel(tree); + + var withExpr1 = root.DescendantNodes().OfType().First(); + comp.VerifyOperationTree(withExpr1, @" +IWithExpressionOperation (OperationKind.WithExpression, Type: C) (Syntax: 'c with { Y ... = W(""X"") }') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + CloneMethod: C C.Clone() + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C) (Syntax: '{ Y = W(""Y"" ... = W(""X"") }') + Initializers(2): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'Y = W(""Y"")') + Left: + IPropertyReferenceOperation: System.Int32 C.Y { get; init; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'Y') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Y') + Right: + IInvocationOperation (System.Int32 C.W(System.String s)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'W(""Y"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: s) (OperationKind.Argument, Type: null) (Syntax: '""Y""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""Y"") (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) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = W(""X"")') + Left: + IPropertyReferenceOperation: System.Int32 C.X { get; init; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'X') + Right: + IInvocationOperation (System.Int32 C.W(System.String s)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'W(""X"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: s) (OperationKind.Argument, Type: null) (Syntax: '""X""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""X"") (Syntax: '""X""') + 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 main = root.DescendantNodes().OfType().Skip(1).First(); + Assert.Equal("Main", main.Identifier.ToString()); + VerifyFlowGraph(comp, main, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [C c] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsImplicit) (Syntax: 'c = new C(0, 1, 2)') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = new C(0, 1, 2)') + Right: + IObjectCreationOperation (Constructor: C..ctor(System.Int32 X, System.Int32 Y, System.Int32 Z)) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C(0, 1, 2)') + Arguments(3): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: '0') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + 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.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + 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.Explicit, Matching Parameter: Z) (OperationKind.Argument, Type: null) (Syntax: '2') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + 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) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [0] [1] + Block[B2] - Block + Predecessors: [B1] + Statements (5) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') + Value: + IInvocationOperation ( C C.Clone()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Arguments(0) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'Y = W(""Y"")') + Left: + IPropertyReferenceOperation: System.Int32 C.Y { get; init; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'Y') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') + Right: + IInvocationOperation (System.Int32 C.W(System.String s)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'W(""Y"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: s) (OperationKind.Argument, Type: null) (Syntax: '""Y""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""Y"") (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) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = W(""X"")') + Left: + IPropertyReferenceOperation: System.Int32 C.X { get; init; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') + Right: + IInvocationOperation (System.Int32 C.W(System.String s)) (OperationKind.Invocation, Type: System.Int32) (Syntax: 'W(""X"")') + Instance Receiver: + null + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: s) (OperationKind.Argument, Type: null) (Syntax: '""X""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""X"") (Syntax: '""X""') + 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) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c = c with ... = W(""X"") };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C) (Syntax: 'c = c with ... = W(""X"") }') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c with { Y ... = W(""X"") }') + Next (Regular) Block[B3] + Leaving: {R2} {R1} + } +} +Block[B3] - Exit + Predecessors: [B2] + Statements (0) +"); } [Fact] @@ -1606,7 +1788,7 @@ public void WithExprCloneReturnDifferent() { var src = @" class B -{ +{ public int X { get; init; } } class C : B @@ -1647,6 +1829,187 @@ public static void Main() var xId = withExpr.DescendantNodes().Single(id => id.ToString() == "X"); var symbolInfo = model.GetSymbolInfo(xId); Assert.True(x.ISymbol.Equals(symbolInfo.Symbol)); + + comp.VerifyOperationTree(withExpr, @" +IWithExpressionOperation (OperationKind.WithExpression, Type: C) (Syntax: 'c with { X = 2 }') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + CloneMethod: C C.Clone() + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C) (Syntax: '{ X = 2 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 2') + Left: + IPropertyReferenceOperation: System.Int32 C.X { get; init; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'X') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2')"); + + var main = root.DescendantNodes().OfType().Single(); + Assert.Equal("Main", main.Identifier.ToString()); + VerifyFlowGraph(comp, main, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [C c] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsImplicit) (Syntax: 'c = new C(0, ""a"")') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = new C(0, ""a"")') + Right: + IObjectCreationOperation (Constructor: C..ctor(System.Int32 X, System.String Y)) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C(0, ""a"")') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: '0') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0) (Syntax: '0') + 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.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: '""a""') + ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: ""a"") (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) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [0] [1] + Block[B2] - Block + Predecessors: [B1] + Statements (4) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c with { X = 2 }') + Value: + IInvocationOperation ( C C.Clone()) (OperationKind.Invocation, Type: C, IsImplicit) (Syntax: 'c with { X = 2 }') + Instance Receiver: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + Arguments(0) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 2') + Left: + IPropertyReferenceOperation: System.Int32 C.X { get; init; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c with { X = 2 }') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c = c with { X = 2 };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C) (Syntax: 'c = c with { X = 2 }') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c with { X = 2 }') + Next (Regular) Block[B3] + Leaving: {R2} {R1} + } +} +Block[B3] - Exit + Predecessors: [B2] + Statements (0) +"); + } + + [Fact] + public void NoCloneMethod() + { + var src = @" +class C +{ + int X { get; set; } + + public static void Main() + { + var c = new C(); + c = c with { X = 2 }; + } +}"; + var comp = CreateCompilation(src); + var tree = comp.SyntaxTrees[0]; + var root = tree.GetRoot(); + var withExpr = root.DescendantNodes().OfType().Single(); + + comp.VerifyOperationTree(withExpr, @" +IWithExpressionOperation (OperationKind.WithExpression, Type: C, IsInvalid) (Syntax: 'c with { X = 2 }') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C, IsInvalid) (Syntax: 'c') + CloneMethod: null + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C) (Syntax: '{ X = 2 }') + Initializers(1): + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 2') + Left: + IPropertyReferenceOperation: System.Int32 C.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ImplicitReceiver) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'X') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2')"); + + var main = root.DescendantNodes().OfType().Single(); + Assert.Equal("Main", main.Identifier.ToString()); + VerifyFlowGraph(comp, main, expectedFlowGraph: @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [C c] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsImplicit) (Syntax: 'c = new C()') + Left: + ILocalReferenceOperation: c (IsDeclaration: True) (OperationKind.LocalReference, Type: C, IsImplicit) (Syntax: 'c = new C()') + Right: + IObjectCreationOperation (Constructor: C..ctor()) (OperationKind.ObjectCreation, Type: C) (Syntax: 'new C()') + Arguments(0) + Initializer: + null + Next (Regular) Block[B2] + Entering: {R2} + .locals {R2} + { + CaptureIds: [0] [1] + Block[B2] - Block + Predecessors: [B1] + Statements (4) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: 'c') + Value: + IInvalidOperation (OperationKind.Invalid, Type: C, IsInvalid, IsImplicit) (Syntax: 'c') + Children(1): + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C, IsInvalid) (Syntax: 'c') + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Int32) (Syntax: 'X = 2') + Left: + IPropertyReferenceOperation: System.Int32 C.X { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'X') + Instance Receiver: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'c') + Right: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: 'c = c with { X = 2 };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: C, IsInvalid) (Syntax: 'c = c with { X = 2 }') + Left: + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: C, IsInvalid, IsImplicit) (Syntax: 'c') + Next (Regular) Block[B3] + Leaving: {R2} {R1} + } +} +Block[B3] - Exit + Predecessors: [B2] + Statements (0) +"); } [Fact] @@ -1688,6 +2051,34 @@ public static void Main() // } Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(11, 1) ); + + var tree = comp.SyntaxTrees[0]; + var root = tree.GetRoot(); + var model = comp.GetSemanticModel(tree); + VerifyClone(model); + + var withExpr1 = root.DescendantNodes().OfType().First(); + comp.VerifyOperationTree(withExpr1, @" +IWithExpressionOperation (OperationKind.WithExpression, Type: C, IsInvalid) (Syntax: 'c with { 5 }') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + CloneMethod: C C.Clone() + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C, IsInvalid) (Syntax: '{ 5 }') + Initializers(1): + IInvalidOperation (OperationKind.Invalid, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: '5') + Children(1): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 5, IsInvalid) (Syntax: '5')"); + + var withExpr2 = root.DescendantNodes().OfType().Skip(1).Single(); + comp.VerifyOperationTree(withExpr2, @" +IWithExpressionOperation (OperationKind.WithExpression, Type: C, IsInvalid) (Syntax: 'c with { ') + Value: + ILocalReferenceOperation: c (OperationKind.LocalReference, Type: C) (Syntax: 'c') + CloneMethod: C C.Clone() + Initializer: + IObjectOrCollectionInitializerOperation (OperationKind.ObjectOrCollectionInitializer, Type: C, IsInvalid) (Syntax: '{ ') + Initializers(0)"); } [Fact] @@ -2040,7 +2431,7 @@ static void M1(B b1) B b2 = b1 with { X = ""hello"" }; B b3 = b1 with { Y = ""world"" }; B b4 = b2 with { Y = ""world"" }; - + b1.X.ToString(); // 1 b1.Y.ToString(); // 2 b2.X.ToString(); @@ -2265,7 +2656,7 @@ class C public string Y { get; init; } public long Z; public event Action E; - + public C Clone() => new C { X = this.X, Y = this.Y, @@ -2310,7 +2701,7 @@ public class C public int X { get; set; } public string Y { get; init; } public long Z; - + public C Clone() => new C { X = this.X, Y = this.Y, diff --git a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs index 5fc736ecc9544..ca965e31e6273 100644 --- a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs @@ -252,5 +252,7 @@ public enum OperationKind TypePattern = 0x6f, /// Indicates an . RelationalPattern = 0x70, + /// Indicates an . + WithExpression = 0x71, } } diff --git a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs index 9a015d1d16df0..d95cb1430b056 100644 --- a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs @@ -1082,7 +1082,7 @@ public interface IArrayCreationOperation : IOperation /// Current usage: /// (1) C# this or base expression. /// (2) VB Me, MyClass, or MyBase expression. - /// (3) C# object or collection initializers. + /// (3) C# object or collection or 'with' expression initializers. /// (4) VB With statements, object or collection initializers. /// /// @@ -2876,6 +2876,32 @@ public interface IRelationalPatternOperation : IPatternOperation /// IOperation Value { get; } } + /// + /// Represents cloning of an object instance. + /// + /// Current usage: + /// (1) C# with expression. + /// + /// + /// + /// This interface is reserved for implementation by its associated APIs. We reserve the right to + /// change it in the future. + /// + public interface IWithExpressionOperation : IOperation + { + /// + /// Value to be cloned. + /// + IOperation Value { get; } + /// + /// Clone method to be invoked on the value. + /// + IMethodSymbol CloneMethod { get; } + /// + /// With collection initializer. + /// + IObjectOrCollectionInitializerOperation Initializer { get; } + } #endregion #region Implementations @@ -8529,6 +8555,73 @@ public override IOperation Value } } } + internal abstract partial class BaseWithExpressionOperation : Operation, IWithExpressionOperation + { + internal BaseWithExpressionOperation(IMethodSymbol cloneMethod, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(OperationKind.WithExpression, semanticModel, syntax, type, constantValue, isImplicit) + { + CloneMethod = cloneMethod; + } + public abstract IOperation Value { get; } + public IMethodSymbol CloneMethod { get; } + public abstract IObjectOrCollectionInitializerOperation Initializer { get; } + public override IEnumerable Children + { + get + { + if (Value is object) yield return Value; + if (Initializer is object) yield return Initializer; + } + } + public override void Accept(OperationVisitor visitor) => visitor.VisitWithExpression(this); + public override TResult Accept(OperationVisitor visitor, TArgument argument) => visitor.VisitWithExpression(this, argument); + } + internal sealed partial class WithExpressionOperation : BaseWithExpressionOperation, IWithExpressionOperation + { + internal WithExpressionOperation(IOperation value, IMethodSymbol cloneMethod, IObjectOrCollectionInitializerOperation initializer, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(cloneMethod, semanticModel, syntax, type, constantValue, isImplicit) + { + Value = SetParentOperation(value, this); + Initializer = SetParentOperation(initializer, this); + } + public override IOperation Value { get; } + public override IObjectOrCollectionInitializerOperation Initializer { get; } + } + internal abstract partial class LazyWithExpressionOperation : BaseWithExpressionOperation, IWithExpressionOperation + { + private IOperation _lazyValue = s_unset; + private IObjectOrCollectionInitializerOperation _lazyInitializer = s_unsetObjectOrCollectionInitializer; + internal LazyWithExpressionOperation(IMethodSymbol cloneMethod, SemanticModel semanticModel, SyntaxNode syntax, ITypeSymbol type, Optional constantValue, bool isImplicit) + : base(cloneMethod, semanticModel, syntax, type, constantValue, isImplicit){ } + protected abstract IOperation CreateValue(); + public override IOperation Value + { + get + { + if (_lazyValue == s_unset) + { + IOperation value = CreateValue(); + SetParentOperation(value, this); + Interlocked.CompareExchange(ref _lazyValue, value, s_unset); + } + return _lazyValue; + } + } + protected abstract IObjectOrCollectionInitializerOperation CreateInitializer(); + public override IObjectOrCollectionInitializerOperation Initializer + { + get + { + if (_lazyInitializer == s_unsetObjectOrCollectionInitializer) + { + IObjectOrCollectionInitializerOperation initializer = CreateInitializer(); + SetParentOperation(initializer, this); + Interlocked.CompareExchange(ref _lazyInitializer, initializer, s_unsetObjectOrCollectionInitializer); + } + return _lazyInitializer; + } + } + } #endregion #region Visitors public abstract partial class OperationVisitor @@ -8655,6 +8748,7 @@ internal virtual void VisitNoneOperation(IOperation operation) { /* no-op */ } public virtual void VisitBinaryPattern(IBinaryPatternOperation operation) => DefaultVisit(operation); public virtual void VisitTypePattern(ITypePatternOperation operation) => DefaultVisit(operation); public virtual void VisitRelationalPattern(IRelationalPatternOperation operation) => DefaultVisit(operation); + public virtual void VisitWithExpression(IWithExpressionOperation operation) => DefaultVisit(operation); } public abstract partial class OperationVisitor { @@ -8780,6 +8874,7 @@ public abstract partial class OperationVisitor public virtual TResult VisitBinaryPattern(IBinaryPatternOperation operation, TArgument argument) => DefaultVisit(operation, argument); public virtual TResult VisitTypePattern(ITypePatternOperation operation, TArgument argument) => DefaultVisit(operation, argument); public virtual TResult VisitRelationalPattern(IRelationalPatternOperation operation, TArgument argument) => DefaultVisit(operation, argument); + public virtual TResult VisitWithExpression(IWithExpressionOperation operation, TArgument argument) => DefaultVisit(operation, argument); } #endregion } diff --git a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs index 3c5928bffd331..fd2e98beb8891 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -6982,5 +6982,19 @@ public override IOperation VisitUsingDeclaration(IUsingDeclarationOperation oper throw ExceptionUtilities.Unreachable; } + public override IOperation VisitWithExpression(IWithExpressionOperation operation, int? argument) + { + EvalStackFrame frame = PushStackFrame(); + // Initializer is removed from the tree and turned into a series of statements that assign to the cloned instance + IOperation visitedInstance = Visit(operation.Value); + + IOperation cloned = operation.CloneMethod is null + ? MakeInvalidOperation(visitedInstance.Type, visitedInstance) + : new InvocationOperation(operation.CloneMethod, visitedInstance, + isVirtual: false, arguments: ImmutableArray.Empty, + semanticModel: null, operation.Syntax, operation.Type, operation.ConstantValue, isImplicit: true); + + return PopStackFrame(frame, HandleObjectOrCollectionInitializer(operation.Initializer, cloned)); + } } } diff --git a/src/Compilers/Core/Portable/Operations/InstanceReferenceKind.cs b/src/Compilers/Core/Portable/Operations/InstanceReferenceKind.cs index 5cd1fe3653ea8..5782f13deba26 100644 --- a/src/Compilers/Core/Portable/Operations/InstanceReferenceKind.cs +++ b/src/Compilers/Core/Portable/Operations/InstanceReferenceKind.cs @@ -16,7 +16,8 @@ public enum InstanceReferenceKind ContainingTypeInstance, /// /// Reference to the object being initialized in C# or VB object or collection initializer, - /// anonymous type creation initializer, or to the object being referred to in a VB With statement. + /// anonymous type creation initializer, or to the object being referred to in a VB With statement, + /// or the C# 'with' expression initializer. /// ImplicitReceiver, /// diff --git a/src/Compilers/Core/Portable/Operations/OperationCloner.cs b/src/Compilers/Core/Portable/Operations/OperationCloner.cs index bcc5120a356c9..5cadb85797c2e 100644 --- a/src/Compilers/Core/Portable/Operations/OperationCloner.cs +++ b/src/Compilers/Core/Portable/Operations/OperationCloner.cs @@ -662,5 +662,10 @@ public override IOperation VisitUsingDeclaration(IUsingDeclarationOperation oper { return new UsingDeclarationOperation(Visit(operation.DeclarationGroup), operation.IsAsynchronous, ((Operation)operation).OwningSemanticModel, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); } + + public override IOperation VisitWithExpression(IWithExpressionOperation operation, object argument) + { + return new WithExpressionOperation(Visit(operation.Value), operation.CloneMethod, Visit(operation.Initializer), ((Operation)operation).OwningSemanticModel, operation.Syntax, operation.Type, operation.ConstantValue, operation.IsImplicit); + } } } diff --git a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml index 8685d4fcee797..d7ded194729d4 100644 --- a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml +++ b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml @@ -13,6 +13,10 @@ UnusedOperationKinds indicates kinds that are currently skipped by the OperationKind enum. They can be used by future nodes by inserting those nodes at the correct point in the list. + + When implementing new operations, run tests with additional IOperation validation enabled. + You can test with `Build.cmd -testIOperation`. + Or to repro in VS, you can do `set ROSLYN_TEST_IOPERATION=true` then `devenv`. --> @@ -1142,7 +1146,7 @@ Current usage: (1) C# this or base expression. (2) VB Me, MyClass, or MyBase expression. - (3) C# object or collection initializers. + (3) C# object or collection or 'with' expression initializers. (4) VB With statements, object or collection initializers. @@ -3001,4 +3005,30 @@ + + + + Represents cloning of an object instance. + + Current usage: + (1) C# with expression. + + + + + + Value to be cloned. + + + + + Clone method to be invoked on the value. + + + + + With collection initializer. + + + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index d02297b4f8806..5f3ab9b30b9ca 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -13,6 +13,7 @@ Microsoft.CodeAnalysis.OperationKind.BinaryPattern = 110 -> Microsoft.CodeAnalys Microsoft.CodeAnalysis.OperationKind.NegatedPattern = 109 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.RelationalPattern = 112 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.OperationKind.TypePattern = 111 -> Microsoft.CodeAnalysis.OperationKind +Microsoft.CodeAnalysis.OperationKind.WithExpression = 113 -> Microsoft.CodeAnalysis.OperationKind Microsoft.CodeAnalysis.Operations.CommonConversion.IsNullable.get -> bool Microsoft.CodeAnalysis.Operations.IBinaryPatternOperation Microsoft.CodeAnalysis.Operations.IBinaryPatternOperation.LeftPattern.get -> Microsoft.CodeAnalysis.Operations.IPatternOperation @@ -25,6 +26,10 @@ Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation.OperatorKind.get - Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation.Value.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.Operations.ITypePatternOperation Microsoft.CodeAnalysis.Operations.ITypePatternOperation.MatchedType.get -> Microsoft.CodeAnalysis.ITypeSymbol +Microsoft.CodeAnalysis.Operations.IWithExpressionOperation +Microsoft.CodeAnalysis.Operations.IWithExpressionOperation.CloneMethod.get -> Microsoft.CodeAnalysis.IMethodSymbol +Microsoft.CodeAnalysis.Operations.IWithExpressionOperation.Initializer.get -> Microsoft.CodeAnalysis.Operations.IObjectOrCollectionInitializerOperation +Microsoft.CodeAnalysis.Operations.IWithExpressionOperation.Value.get -> Microsoft.CodeAnalysis.IOperation Microsoft.CodeAnalysis.SymbolKind.FunctionPointer = 20 -> Microsoft.CodeAnalysis.SymbolKind Microsoft.CodeAnalysis.TypeKind.FunctionPointer = 13 -> Microsoft.CodeAnalysis.TypeKind static Microsoft.CodeAnalysis.AnalyzerConfigSet.Create(TList analyzerConfigs, out System.Collections.Immutable.ImmutableArray diagnostics) -> Microsoft.CodeAnalysis.AnalyzerConfigSet @@ -32,9 +37,11 @@ virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBinaryPattern(Mi virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitNegatedPattern(Microsoft.CodeAnalysis.Operations.INegatedPatternOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRelationalPattern(Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTypePattern(Microsoft.CodeAnalysis.Operations.ITypePatternOperation operation) -> void +virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitWithExpression(Microsoft.CodeAnalysis.Operations.IWithExpressionOperation operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBinaryPattern(Microsoft.CodeAnalysis.Operations.IBinaryPatternOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitNegatedPattern(Microsoft.CodeAnalysis.Operations.INegatedPatternOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRelationalPattern(Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitTypePattern(Microsoft.CodeAnalysis.Operations.ITypePatternOperation operation, TArgument argument) -> TResult +virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitWithExpression(Microsoft.CodeAnalysis.Operations.IWithExpressionOperation operation, TArgument argument) -> TResult virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitFunctionPointerType(Microsoft.CodeAnalysis.IFunctionPointerTypeSymbol symbol) -> void virtual Microsoft.CodeAnalysis.SymbolVisitor.VisitFunctionPointerType(Microsoft.CodeAnalysis.IFunctionPointerTypeSymbol symbol) -> TResult diff --git a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs index 3983954f86743..fd7ba0cee3fa2 100644 --- a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs +++ b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs @@ -468,6 +468,7 @@ private static (SyntaxToken openingDelimeter, SyntaxToken closingDelimiter) GetD return (bracketedArgumentList.OpenBracketToken, bracketedArgumentList.CloseBracketToken); case SyntaxKind.ObjectInitializerExpression: + case SyntaxKind.WithInitializerExpression: var initializerExpressionSyntax = (InitializerExpressionSyntax)currentNode; return (initializerExpressionSyntax.OpenBraceToken, initializerExpressionSyntax.CloseBraceToken); diff --git a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs index ccd84b21d7022..996d7accd6e3b 100644 --- a/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs +++ b/src/EditorFeatures/CSharpTest/AutomaticCompletion/AutomaticBraceCompletionTests.cs @@ -18,6 +18,60 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AutomaticCompletion { public class AutomaticBraceCompletionTests : AbstractAutomaticBraceCompletionTests { + [WpfFact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void WithExpressionBracesSameLine() + { + var code = @" +class C +{ + void M(C c) + { + c = c with $$ + } +}"; + + var expected = @" +class C +{ + void M(C c) + { + c = c with { } + } +}"; + using var session = CreateSession(code); + Assert.NotNull(session); + + CheckStart(session.Session); + CheckText(session.Session, expected); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] + public void WithExpressionBracesSameLine_Enter() + { + var code = @" +class C +{ + void M(C c) + { + c = c with $$ + } +}"; + var expected = @" +class C +{ + void M(C c) + { + c = c with + { + + } + } +}"; + using var session = CreateSession(code); + CheckStart(session.Session); + CheckReturn(session.Session, 12, expected); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.AutomaticCompletion)] public void Creation() { diff --git a/src/EditorFeatures/CSharpTest/CodeActions/InlineTemporary/InlineTemporaryTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/InlineTemporary/InlineTemporaryTests.cs index 3244a83e481ac..4998e17631e29 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/InlineTemporary/InlineTemporaryTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/InlineTemporary/InlineTemporaryTests.cs @@ -5172,6 +5172,42 @@ C M2() }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)] + public async Task InlineIntoWithExpression() + { + await TestInRegularAndScriptAsync(@" +record Person(string Name) +{ + void M(Person p) + { + string [||]x = """"; + _ = p with { Name = x }; + } +} + +namespace System.Runtime.CompilerServices +{ + public sealed class IsExternalInit + { + } +}", +@" +record Person(string Name) +{ + void M(Person p) + { + _ = p with { Name = """" }; + } +} + +namespace System.Runtime.CompilerServices +{ + public sealed class IsExternalInit + { + } +}", parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview)); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInlineTemporary)] [WorkItem(44263, "https://github.com/dotnet/roslyn/issues/44263")] public async Task Call_TopLevelStatement() diff --git a/src/EditorFeatures/CSharpTest/Formatting/CSharpFormattingEngineTestBase.cs b/src/EditorFeatures/CSharpTest/Formatting/CSharpFormattingEngineTestBase.cs index cd5843ea50156..91fb8213fc40e 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CSharpFormattingEngineTestBase.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CSharpFormattingEngineTestBase.cs @@ -5,12 +5,15 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Editor.UnitTests.Formatting; using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting { [UseExportProvider] public class CSharpFormattingEngineTestBase : CoreFormatterTestsBase { + protected CSharpFormattingEngineTestBase(ITestOutputHelper output) : base(output) { } + protected override string GetLanguageName() => LanguageNames.CSharp; diff --git a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs index 584fd9f71d6ec..8747734fb9d90 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests.cs @@ -19,11 +19,14 @@ using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; using Roslyn.Test.Utilities; using Xunit; +using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting { public class FormattingEngineTests : CSharpFormattingEngineTestBase { + public FormattingEngineTests(ITestOutputHelper output) : base(output) { } + private static Dictionary SmartIndentButDoNotFormatWhileTyping() { return new Dictionary @@ -2055,6 +2058,100 @@ void MyMethod() await AssertFormatWithBaseIndentAsync(expected, code, baseIndentation: 4); } + [WpfFact] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task WithExpression() + { + var code = @"[| +record C(int Property) +{ + void M() + { + _ = this with { Property = 1 } ; + } +} +|]"; + var expected = @" +record C(int Property) +{ + void M() + { + _ = this with { Property = 1 }; + } +} +"; + + await AssertFormatWithBaseIndentAsync(expected, code, baseIndentation: 4); + } + + [WpfFact] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task WithExpression_MultiLine() + { + var code = @"[| +record C(int Property, int Property2) +{ + void M() + { + _ = this with +{ +Property = 1, +Property2 = 2 +} ; + } +} +|]"; + var expected = @" +record C(int Property, int Property2) +{ + void M() + { + _ = this with + { + Property = 1, + Property2 = 2 + }; + } +} +"; + + await AssertFormatWithBaseIndentAsync(expected, code, baseIndentation: 4); + } + + [WpfFact] + [Trait(Traits.Feature, Traits.Features.Formatting)] + public async Task WithExpression_MultiLine_UserPositionedBraces() + { + var code = @"[| +record C(int Property, int Property2) +{ + void M() + { + _ = this with + { + Property = 1, + Property2 = 2 + } ; + } +} +|]"; + var expected = @" +record C(int Property, int Property2) +{ + void M() + { + _ = this with + { + Property = 1, + Property2 = 2 + }; + } +} +"; + + await AssertFormatWithBaseIndentAsync(expected, code, baseIndentation: 4); + } + [WorkItem(25003, "https://github.com/dotnet/roslyn/issues/25003")] [WpfFact, Trait(Traits.Feature, Traits.Features.Formatting)] public void SeparateGroups_KeepMultipleLinesBetweenGroups() diff --git a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests_Venus.cs b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests_Venus.cs index 19b24ccdb4788..33676d0f1266e 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests_Venus.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/FormattingEngineTests_Venus.cs @@ -6,11 +6,14 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; +using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting { public class FormattingEngineTests_Venus : CSharpFormattingEngineTestBase { + public FormattingEngineTests_Venus(ITestOutputHelper output) : base(output) { } + [WpfFact, Trait(Traits.Feature, Traits.Features.Formatting), Trait(Traits.Feature, Traits.Features.Venus)] public async Task SimpleOneLineNugget() { diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs index 527476bbc5e2e..56c1cf732bb73 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs @@ -20,12 +20,15 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Xunit; +using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting.Indentation { [UseExportProvider] public class CSharpFormatterTestsBase : CSharpFormattingEngineTestBase { + public CSharpFormatterTestsBase(ITestOutputHelper output) : base(output) { } + protected const string HtmlMarkup = @" <%{|S1:|}%> diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterEnterOnTokenTests.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterEnterOnTokenTests.cs index 06766c7f478c1..35e13fd1cb5db 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterEnterOnTokenTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterEnterOnTokenTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; +using Xunit.Abstractions; using static Microsoft.CodeAnalysis.Formatting.FormattingOptions2; using IndentStyle = Microsoft.CodeAnalysis.Formatting.FormattingOptions.IndentStyle; @@ -20,6 +21,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting.Indentation { public class SmartIndenterEnterOnTokenTests : CSharpFormatterTestsBase { + public SmartIndenterEnterOnTokenTests(ITestOutputHelper output) : base(output) { } + [WorkItem(537808, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/537808")] [WpfFact] [Trait(Traits.Feature, Traits.Features.SmartIndent)] diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs index 73f71bb6f5c86..6de015865cfec 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartIndenterTests.cs @@ -10,6 +10,7 @@ using Microsoft.VisualStudio.Text; using Roslyn.Test.Utilities; using Xunit; +using Xunit.Abstractions; using static Microsoft.CodeAnalysis.Formatting.FormattingOptions2; using IndentStyle = Microsoft.CodeAnalysis.Formatting.FormattingOptions.IndentStyle; @@ -17,6 +18,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting.Indentation { public partial class SmartIndenterTests : CSharpFormatterTestsBase { + public SmartIndenterTests(ITestOutputHelper output) : base(output) { } + [WpfFact] [Trait(Traits.Feature, Traits.Features.SmartIndent)] public void EmptyFile() diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatTokenTests.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatTokenTests.cs index 701dceae84f2d..11c82b126bb82 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatTokenTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatTokenTests.cs @@ -10,11 +10,16 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; +using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Formatting.Indentation { public class SmartTokenFormatterFormatTokenTests : CSharpFormatterTestsBase { + public SmartTokenFormatterFormatTokenTests(ITestOutputHelper output) : base(output) + { + } + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/44423)")] [Trait(Traits.Feature, Traits.Features.SmartTokenFormatting)] [WorkItem(44423, "https://github.com/dotnet/roslyn/issues/44423")] diff --git a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs index 81752809be0ee..741795f97340b 100644 --- a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs +++ b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs @@ -26,11 +26,16 @@ using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; +using Xunit.Abstractions; namespace Microsoft.CodeAnalysis.Editor.UnitTests.Formatting { public abstract class CoreFormatterTestsBase { + private readonly ITestOutputHelper _output; + public CoreFormatterTestsBase(ITestOutputHelper output) + => this._output = output; + protected abstract string GetLanguageName(); protected abstract SyntaxNode ParseCompilationUnit(string expected); @@ -218,12 +223,16 @@ internal void AssertFormatWithTransformation(Workspace workspace, string expecte Assert.True(newRootNodeFromString.IsEquivalentTo(newRootNode)); } - internal static void AssertFormat(Workspace workspace, string expected, OptionSet optionSet, IEnumerable rules, ITextBuffer clonedBuffer, SyntaxNode root, IEnumerable spans) + internal void AssertFormat(Workspace workspace, string expected, OptionSet optionSet, IEnumerable rules, ITextBuffer clonedBuffer, SyntaxNode root, IEnumerable spans) { var result = Formatter.GetFormattedTextChanges(root, spans, workspace, optionSet, rules, CancellationToken.None); var actual = ApplyResultAndGetFormattedText(clonedBuffer, result); - Assert.Equal(expected, actual); + if (actual != expected) + { + _output.WriteLine(actual); + Assert.Equal(expected, actual); + } } protected void AssertFormatWithPasteOrReturn(string expectedWithMarker, string codeWithMarker, bool allowDocumentChanges, bool isPaste = true) diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb b/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb index 3345274fd4a9c..04296ede22ca6 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/FormattingEngineTests_Venus.vb @@ -3,11 +3,16 @@ ' See the LICENSE file in the project root for more information. Imports Microsoft.CodeAnalysis.Text +Imports Xunit.Abstractions Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting Public Class FormattingEngineTests_Venus Inherits VisualBasicFormatterTestBase + Public Sub New(output As ITestOutputHelper) + MyBase.New(output) + End Sub + Public Async Function SimpleOneLineNugget() As Threading.Tasks.Task Dim code = Imports System diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb index 10db3856e0b80..180b0ab5a50a6 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartIndenterTests.vb @@ -8,6 +8,7 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.VisualStudio.Text +Imports Xunit.Abstractions Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting.Indentation <[UseExportProvider]> @@ -23,6 +24,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting.Indenta .NormalizedValue Private Shared s_baseIndentationOfNugget As Integer = 8 + Public Sub New(output As ITestOutputHelper) + MyBase.New(output) + End Sub + Public Sub TestEmptyFile() diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb index 16efa8bbc81b2..93ff0eb085c46 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb @@ -12,12 +12,17 @@ Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text.Shared.Extensions Imports Microsoft.VisualStudio.Text Imports Roslyn.Test.EditorUtilities +Imports Xunit.Abstractions Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting <[UseExportProvider]> Public Class VisualBasicFormatterTestBase Inherits CoreFormatterTestsBase + Public Sub New(output As ITestOutputHelper) + MyBase.New(output) + End Sub + Protected Overrides Function GetLanguageName() As String Return LanguageNames.VisualBasic End Function diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb index ee785bf0f7285..aaaaf4093d9fa 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormattingEngineTests.vb @@ -4,11 +4,16 @@ Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Options +Imports Xunit.Abstractions Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting Public Class VisualBasicFormattingEngineTests Inherits VisualBasicFormatterTestBase + Public Sub New(output As ITestOutputHelper) + MyBase.New(output) + End Sub + Private Function SeparateImportDirectiveGroups() As Dictionary(Of OptionKey, Object) Return New Dictionary(Of OptionKey, Object) From { {New OptionKey(GenerationOptions.SeparateImportDirectiveGroups, LanguageNames.VisualBasic), True} diff --git a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs index ecfe237e1ab7f..405239e65e689 100644 --- a/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs +++ b/src/Test/Utilities/Portable/Compilation/OperationTreeVerifier.cs @@ -1973,6 +1973,18 @@ public override void VisitReDimClause(IReDimClauseOperation operation) VisitArray(operation.DimensionSizes, "DimensionSizes", logElementCount: true); } + public override void VisitWithExpression(IWithExpressionOperation operation) + { + LogString(nameof(IWithExpressionOperation)); + LogCommonPropertiesAndNewLine(operation); + Visit(operation.Value, "Value"); + Indent(); + LogSymbol(operation.CloneMethod, nameof(operation.CloneMethod)); + LogNewLine(); + Unindent(); + Visit(operation.Initializer, "Initializer"); + } + #endregion } } diff --git a/src/Test/Utilities/Portable/Compilation/TestOperationVisitor.cs b/src/Test/Utilities/Portable/Compilation/TestOperationVisitor.cs index 29b801210aaf7..255cc37c7e1a5 100644 --- a/src/Test/Utilities/Portable/Compilation/TestOperationVisitor.cs +++ b/src/Test/Utilities/Portable/Compilation/TestOperationVisitor.cs @@ -7,11 +7,9 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.VisualBasic; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -1521,5 +1519,13 @@ public override void VisitUsingDeclaration(IUsingDeclarationOperation operation) _ = operation.IsAsynchronous; _ = operation.IsImplicit; } + + public override void VisitWithExpression(IWithExpressionOperation operation) + { + Assert.Equal(OperationKind.WithExpression, operation.Kind); + _ = operation.CloneMethod; + IEnumerable children = SpecializedCollections.SingletonEnumerable(operation.Value).Concat(operation.Initializer); + AssertEx.Equal(children, operation.Children); + } } } diff --git a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs index aff67ea7728ea..9a6d9415d6db0 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.Expander.cs @@ -865,7 +865,7 @@ private static bool IsPropertyNameOfObjectInitializer(SimpleNameSyntax identifie while (parent != null) { - if (parent.Kind() == SyntaxKind.ObjectInitializerExpression) + if (parent.IsKind(SyntaxKind.ObjectInitializerExpression, SyntaxKind.WithInitializerExpression)) { return currentNode.Kind() == SyntaxKind.SimpleAssignmentExpression && object.Equals(((AssignmentExpressionSyntax)currentNode).Left, identifierName); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index 7f8c9f893db62..092e2e8c146a6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -208,6 +208,10 @@ public static (SyntaxToken openBrace, SyntaxToken closeBrace) GetBraces(this Syn return (switchExpression.OpenBraceToken, switchExpression.CloseBraceToken); case PropertyPatternClauseSyntax property: return (property.OpenBraceToken, property.CloseBraceToken); +#if !CODE_STYLE + case WithExpressionSyntax withExpr: + return (withExpr.Initializer.OpenBraceToken, withExpr.Initializer.CloseBraceToken); +#endif } return default;