From 59a0608b2ffbdf0ddb77d5415df38f3fc3f2fce5 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Thu, 28 Oct 2021 22:17:44 +0330 Subject: [PATCH] List patterns: IOperation (#56008) --- .../Operations/CSharpOperationFactory.cs | 15 +- .../IOperationTests_IIsPatternExpression.cs | 368 ++++++++++++++++++ .../Generated/OperationKind.Generated.cs | 4 + .../Generated/Operations.Generated.cs | 148 +++++++ .../Operations/ControlFlowGraphBuilder.cs | 26 ++ .../Operations/OperationInterfaces.xml | 54 +++ .../Compilation/ControlFlowGraphVerifier.cs | 2 + .../Core/Compilation/OperationTreeVerifier.cs | 23 ++ .../Core/Compilation/TestOperationVisitor.cs | 33 ++ 9 files changed, 669 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 36f6d6fa3aea2..d9a14df50fcf1 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -2269,8 +2269,12 @@ private IOperation CreateBoundTypePatternOperation(BoundTypePattern boundTypePat private IOperation CreateBoundSlicePatternOperation(BoundSlicePattern boundNode) { - // PROTOTYPE(list-patterns) IOperation - return new DiscardPatternOperation( + return new SlicePatternOperation( + sliceSymbol: boundNode.Pattern is null ? null : + (boundNode.InputType.IsSZArray() + ? (Symbol?)_semanticModel.Compilation.CommonGetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__GetSubArray_T) + : (Symbol?)boundNode.SliceMethod ?? boundNode.IndexerAccess?.Indexer).GetPublicSymbol(), + pattern: (IPatternOperation?)Create(boundNode.Pattern), inputType: boundNode.InputType.GetPublicSymbol(), narrowedType: boundNode.NarrowedType.GetPublicSymbol(), _semanticModel, @@ -2280,8 +2284,11 @@ private IOperation CreateBoundSlicePatternOperation(BoundSlicePattern boundNode) private IOperation CreateBoundListPatternOperation(BoundListPattern boundNode) { - // PROTOTYPE(list-patterns) IOperation - return new DiscardPatternOperation( + return new ListPatternOperation( + lengthSymbol: boundNode.LengthProperty.GetPublicSymbol(), + indexerSymbol: (boundNode.IndexerSymbol ?? boundNode.IndexerAccess?.Indexer).GetPublicSymbol(), + patterns: boundNode.Subpatterns.SelectAsArray((p, fac) => (IPatternOperation)fac.Create(p), this), + declaredSymbol: boundNode.Variable.GetPublicSymbol(), inputType: boundNode.InputType.GetPublicSymbol(), narrowedType: boundNode.NarrowedType.GetPublicSymbol(), _semanticModel, diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs index ad4eb2c03d86b..01f90e7d41272 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs @@ -2084,5 +2084,373 @@ void M(object o) VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularWithPatternCombinators); } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_Array_01() + { + string source = @" +class X +{ + void M(int[] o) + { + _ = /**/o is [42, ..]/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'o is [42, ..]') + Value: + IParameterReferenceOperation: o (OperationKind.ParameterReference, Type: System.Int32[]) (Syntax: 'o') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null) (Syntax: '[42, ..]') (InputType: System.Int32[], NarrowedType: System.Int32[], DeclaredSymbol: null, LengthSymbol: System.Int32 System.Array.Length { get; }, IndexerSymbol: null) + Patterns (2): + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '42') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') + ISlicePatternOperation (OperationKind.SlicePattern, Type: null) (Syntax: '..') (InputType: System.Int32[], NarrowedType: System.Int32[], SliceSymbol: null + Pattern: + null +"; + var expectedDiagnostics = DiagnosticDescription.None; + + var comp = CreateCompilation(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_Array_02() + { + string source = @" +class X +{ + void M(int[] o) + { + _ = /**/o is [42, .. var slice] list/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'o is [42, . ... slice] list') + Value: + IParameterReferenceOperation: o (OperationKind.ParameterReference, Type: System.Int32[]) (Syntax: 'o') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null) (Syntax: '[42, .. var slice] list') (InputType: System.Int32[], NarrowedType: System.Int32[], DeclaredSymbol: System.Int32[] list, LengthSymbol: System.Int32 System.Array.Length { get; }, IndexerSymbol: null) + Patterns (2): + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '42') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') + ISlicePatternOperation (OperationKind.SlicePattern, Type: null) (Syntax: '.. var slice') (InputType: System.Int32[], NarrowedType: System.Int32[], SliceSymbol: T[] System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray(T[] array, System.Range range) + Pattern: + IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null) (Syntax: 'var slice') (InputType: System.Int32[], NarrowedType: System.Int32[], DeclaredSymbol: System.Int32[]? slice, MatchesNull: True) +"; + var expectedDiagnostics = DiagnosticDescription.None; + + var comp = CreateCompilationWithIndexAndRange(new[] { source, TestSources.GetSubArray }); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_Span_01() + { + string source = @" +class X +{ + void M(System.Span o) + { + _ = /**/o is [.., 42]/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'o is [.., 42]') + Value: + IParameterReferenceOperation: o (OperationKind.ParameterReference, Type: System.Span) (Syntax: 'o') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null) (Syntax: '[.., 42]') (InputType: System.Span, NarrowedType: System.Span, DeclaredSymbol: null, LengthSymbol: System.Int32 System.Span.Length { get; }, IndexerSymbol: ref System.Int32 System.Span.this[System.Int32 i] { get; }) + Patterns (2): + ISlicePatternOperation (OperationKind.SlicePattern, Type: null) (Syntax: '..') (InputType: System.Span, NarrowedType: System.Span, SliceSymbol: null + Pattern: + null + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '42') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') +"; + var expectedDiagnostics = DiagnosticDescription.None; + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_Span_02() + { + string source = @" +class X +{ + void M(System.Span o) + { + _ = /**/o is [.. var slice, 42] list/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'o is [.. va ... e, 42] list') + Value: + IParameterReferenceOperation: o (OperationKind.ParameterReference, Type: System.Span) (Syntax: 'o') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null) (Syntax: '[.. var slice, 42] list') (InputType: System.Span, NarrowedType: System.Span, DeclaredSymbol: System.Span list, LengthSymbol: System.Int32 System.Span.Length { get; }, IndexerSymbol: ref System.Int32 System.Span.this[System.Int32 i] { get; }) + Patterns (2): + ISlicePatternOperation (OperationKind.SlicePattern, Type: null) (Syntax: '.. var slice') (InputType: System.Span, NarrowedType: System.Span, SliceSymbol: System.Span System.Span.Slice(System.Int32 offset, System.Int32 length) + Pattern: + IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null) (Syntax: 'var slice') (InputType: System.Span, NarrowedType: System.Span, DeclaredSymbol: System.Span slice, MatchesNull: True) + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '42') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42) (Syntax: '42') +"; + var expectedDiagnostics = DiagnosticDescription.None; + + var comp = CreateCompilationWithIndexAndRangeAndSpan(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_MissingMember_Length() + { + string source = @" +class X +{ + public int this[int i] => throw null; + + void M() + { + _ = /**/this is []/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is []') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: X) (Syntax: 'this') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null, IsInvalid) (Syntax: '[]') (InputType: X, NarrowedType: X, DeclaredSymbol: null, LengthSymbol: null, IndexerSymbol: null) + Patterns (0) +"; + var expectedDiagnostics = new[] + { + // (8,31): error CS9200: List patterns may not be used for a value of type 'X'. + // _ = /**/this is []/**/; + Diagnostic(ErrorCode.ERR_UnsupportedTypeForListPattern, "[]").WithArguments("X").WithLocation(8, 31) + }; + + var comp = CreateCompilation(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_MissingMember_Indexer() + { + string source = @" +class X +{ + public int Count { get; } + + void M() + { + _ = /**/this is []/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is []') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: X) (Syntax: 'this') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null, IsInvalid) (Syntax: '[]') (InputType: X, NarrowedType: X, DeclaredSymbol: null, LengthSymbol: System.Int32 X.Count { get; }, IndexerSymbol: null) + Patterns (0) +"; + var expectedDiagnostics = new[] + { + // (8,31): error CS9200: List patterns may not be used for a value of type 'X'. + // _ = /**/this is []/**/; + Diagnostic(ErrorCode.ERR_UnsupportedTypeForListPattern, "[]").WithArguments("X").WithLocation(8, 31) + }; + + var comp = CreateCompilation(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_MissingMember_Slice() + { + string source = @" +class X +{ + public int this[int i] => throw null; + public int Count => throw null; + + void M() + { + _ = /**/this is [.. 0]/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is [.. 0]') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: X) (Syntax: 'this') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null, IsInvalid) (Syntax: '[.. 0]') (InputType: X, NarrowedType: X, DeclaredSymbol: null, LengthSymbol: System.Int32 X.Count { get; }, IndexerSymbol: System.Int32 X.this[System.Int32 i] { get; }) + Patterns (1): + ISlicePatternOperation (OperationKind.SlicePattern, Type: null, IsInvalid) (Syntax: '.. 0') (InputType: X, NarrowedType: X, SliceSymbol: null + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null, IsInvalid) (Syntax: '0') (InputType: ?, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 0, IsInvalid) (Syntax: '0') +"; + var expectedDiagnostics = new[] + { + // (9,32): error CS9201: Slice patterns may not be used for a value of type 'X'. + // _ = /**/this is [.. 0]/**/; + Diagnostic(ErrorCode.ERR_UnsupportedTypeForSlicePattern, ".. 0").WithArguments("X").WithLocation(9, 32) + }; + + var comp = CreateCompilation(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_ErrorCases_01() + { + string source = @" +class X +{ + void M(int[] a) + { + _ = /**/a is ../**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'a is ..') + Value: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Int32[]) (Syntax: 'a') + Pattern: + ISlicePatternOperation (OperationKind.SlicePattern, Type: null, IsInvalid) (Syntax: '..') (InputType: System.Int32[], NarrowedType: System.Int32[], SliceSymbol: null + Pattern: + null +"; + var expectedDiagnostics = new[] + { + // (6,28): error CS9202: Slice patterns may only be used once and directly inside a list pattern. + // _ = /**/a is ../**/; + Diagnostic(ErrorCode.ERR_MisplacedSlicePattern, "..").WithLocation(6, 28) + }; + + var comp = CreateCompilation(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_ErrorCases_02() + { + string source = @" +class X +{ + void M(int[] a) + { + _ = /**/a is .. 42/**/; + } +} +"; + string expectedOperationTree = @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'a is .. 42') + Value: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: System.Int32[]) (Syntax: 'a') + Pattern: + ISlicePatternOperation (OperationKind.SlicePattern, Type: null, IsInvalid) (Syntax: '.. 42') (InputType: System.Int32[], NarrowedType: System.Int32[], SliceSymbol: null + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null, IsInvalid) (Syntax: '42') (InputType: System.Int32[], NarrowedType: System.Int32[]) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Int32[], IsInvalid, IsImplicit) (Syntax: '42') + Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 42, IsInvalid) (Syntax: '42') +"; + var expectedDiagnostics = new[] + { + // (6,28): error CS9202: Slice patterns may only be used once and directly inside a list pattern. + // _ = /**/a is .. 42/**/; + Diagnostic(ErrorCode.ERR_MisplacedSlicePattern, ".. 42").WithLocation(6, 28), + // (6,31): error CS0029: Cannot implicitly convert type 'int' to 'int[]' + // _ = /**/a is .. 42/**/; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "42").WithArguments("int", "int[]").WithLocation(6, 31) + }; + + var comp = CreateCompilation(source); + VerifyOperationTreeAndDiagnosticsForTest(comp, expectedOperationTree, expectedDiagnostics); + } + + [CompilerTrait(CompilerFeature.IOperation)] + [Fact] + public void TestIsPatternExpression_ListPatterns_ControlFlow_01() + { + string source = @" +class C +{ + void M(int[] o) + /**/ + { + if (o is [1, .. var slice, 2]) { } + }/**/ +} +"; + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Int32[]? slice] + Block[B1] - Block + Predecessors: [B0] + Statements (0) + Jump if False (Regular) to Block[B2] + IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'o is [1, .. ... r slice, 2]') + Value: + IParameterReferenceOperation: o (OperationKind.ParameterReference, Type: System.Int32[]) (Syntax: 'o') + Pattern: + IListPatternOperation (OperationKind.ListPattern, Type: null) (Syntax: '[1, .. var slice, 2]') (InputType: System.Int32[], NarrowedType: System.Int32[], DeclaredSymbol: null, LengthSymbol: System.Int32 System.Array.Length { get; }, IndexerSymbol: null) + Patterns (3): + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '1') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ISlicePatternOperation (OperationKind.SlicePattern, Type: null) (Syntax: '.. var slice') (InputType: System.Int32[], NarrowedType: System.Int32[], SliceSymbol: null + Pattern: + IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null) (Syntax: 'var slice') (InputType: System.Int32[], NarrowedType: System.Int32[], DeclaredSymbol: System.Int32[]? slice, MatchesNull: True) + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: '2') (InputType: System.Int32, NarrowedType: System.Int32) + Value: + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + Leaving: {R1} + Next (Regular) Block[B2] + Leaving: {R1} +} +Block[B2] - Exit + Predecessors: [B1*2] + Statements (0) +"; + + var expectedDiagnostics = DiagnosticDescription.None; + + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + } } } diff --git a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs index 54e5d98317ec5..7281c2e3d7942 100644 --- a/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/OperationKind.Generated.cs @@ -255,5 +255,9 @@ public enum OperationKind RelationalPattern = 0x70, /// Indicates an . With = 0x71, + /// Indicates an . + ListPattern = 0x72, + /// Indicates an . + SlicePattern = 0x73, } } diff --git a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs index 9b6a2dc961b59..43073e1ce0eb5 100644 --- a/src/Compilers/Core/Portable/Generated/Operations.Generated.cs +++ b/src/Compilers/Core/Portable/Generated/Operations.Generated.cs @@ -3320,6 +3320,60 @@ public interface IWithOperation : IOperation /// IObjectOrCollectionInitializerOperation Initializer { get; } } + /// + /// Represents a C# list pattern. + /// + /// + /// This node is associated with the following operation kinds: + /// + /// + /// + /// This interface is reserved for implementation by its associated APIs. We reserve the right to + /// change it in the future. + /// + public interface IListPatternOperation : IPatternOperation + { + /// + /// The Length or Count property that is being used to fetch the length value. + /// Returns null if no such property is found. + /// + ISymbol? LengthSymbol { get; } + /// + /// The indexer that is being used to fetch elements. + /// Returns null for an array input. + /// + ISymbol? IndexerSymbol { get; } + /// + /// Returns subpatterns contained within the list pattern. + /// + ImmutableArray Patterns { get; } + /// + /// Symbol declared by the pattern, if any. + /// + ISymbol? DeclaredSymbol { get; } + } + /// + /// Represents a C# slice pattern. + /// + /// + /// This node is associated with the following operation kinds: + /// + /// + /// + /// This interface is reserved for implementation by its associated APIs. We reserve the right to + /// change it in the future. + /// + public interface ISlicePatternOperation : IPatternOperation + { + /// + /// The range indexer or the Slice method used to fetch the slice value. + /// + ISymbol? SliceSymbol { get; } + /// + /// The pattern that the slice value is matched with, if any. + /// + IPatternOperation? Pattern { get; } + } #endregion #region Implementations @@ -7602,6 +7656,86 @@ protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int prev public override void Accept(OperationVisitor visitor) => visitor.VisitWith(this); public override TResult? Accept(OperationVisitor visitor, TArgument argument) where TResult : default => visitor.VisitWith(this, argument); } + internal sealed partial class ListPatternOperation : BasePatternOperation, IListPatternOperation + { + internal ListPatternOperation(ISymbol? lengthSymbol, ISymbol? indexerSymbol, ImmutableArray patterns, ISymbol? declaredSymbol, ITypeSymbol inputType, ITypeSymbol narrowedType, SemanticModel? semanticModel, SyntaxNode syntax, bool isImplicit) + : base(inputType, narrowedType, semanticModel, syntax, isImplicit) + { + LengthSymbol = lengthSymbol; + IndexerSymbol = indexerSymbol; + Patterns = SetParentOperation(patterns, this); + DeclaredSymbol = declaredSymbol; + } + public ISymbol? LengthSymbol { get; } + public ISymbol? IndexerSymbol { get; } + public ImmutableArray Patterns { get; } + public ISymbol? DeclaredSymbol { get; } + protected override IOperation GetCurrent(int slot, int index) + => slot switch + { + 0 when index < Patterns.Length + => Patterns[index], + _ => throw ExceptionUtilities.UnexpectedValue((slot, index)), + }; + protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int previousSlot, int previousIndex) + { + switch (previousSlot) + { + case -1: + if (!Patterns.IsEmpty) return (true, 0, 0); + else goto case 0; + case 0 when previousIndex + 1 < Patterns.Length: + return (true, 0, previousIndex + 1); + case 0: + case 1: + return (false, 1, 0); + default: + throw ExceptionUtilities.UnexpectedValue((previousSlot, previousIndex)); + } + } + public override ITypeSymbol? Type => null; + internal override ConstantValue? OperationConstantValue => null; + public override OperationKind Kind => OperationKind.ListPattern; + public override void Accept(OperationVisitor visitor) => visitor.VisitListPattern(this); + public override TResult? Accept(OperationVisitor visitor, TArgument argument) where TResult : default => visitor.VisitListPattern(this, argument); + } + internal sealed partial class SlicePatternOperation : BasePatternOperation, ISlicePatternOperation + { + internal SlicePatternOperation(ISymbol? sliceSymbol, IPatternOperation? pattern, ITypeSymbol inputType, ITypeSymbol narrowedType, SemanticModel? semanticModel, SyntaxNode syntax, bool isImplicit) + : base(inputType, narrowedType, semanticModel, syntax, isImplicit) + { + SliceSymbol = sliceSymbol; + Pattern = SetParentOperation(pattern, this); + } + public ISymbol? SliceSymbol { get; } + public IPatternOperation? Pattern { get; } + protected override IOperation GetCurrent(int slot, int index) + => slot switch + { + 0 when Pattern != null + => Pattern, + _ => throw ExceptionUtilities.UnexpectedValue((slot, index)), + }; + protected override (bool hasNext, int nextSlot, int nextIndex) MoveNext(int previousSlot, int previousIndex) + { + switch (previousSlot) + { + case -1: + if (Pattern != null) return (true, 0, 0); + else goto case 0; + case 0: + case 1: + return (false, 1, 0); + default: + throw ExceptionUtilities.UnexpectedValue((previousSlot, previousIndex)); + } + } + public override ITypeSymbol? Type => null; + internal override ConstantValue? OperationConstantValue => null; + public override OperationKind Kind => OperationKind.SlicePattern; + public override void Accept(OperationVisitor visitor) => visitor.VisitSlicePattern(this); + public override TResult? Accept(OperationVisitor visitor, TArgument argument) where TResult : default => visitor.VisitSlicePattern(this, argument); + } #endregion #region Cloner internal sealed partial class OperationCloner : OperationVisitor @@ -8160,6 +8294,16 @@ public override IOperation VisitWith(IWithOperation operation, object? argument) var internalOperation = (WithOperation)operation; return new WithOperation(Visit(internalOperation.Operand), internalOperation.CloneMethod, Visit(internalOperation.Initializer), internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.Type, internalOperation.IsImplicit); } + public override IOperation VisitListPattern(IListPatternOperation operation, object? argument) + { + var internalOperation = (ListPatternOperation)operation; + return new ListPatternOperation(internalOperation.LengthSymbol, internalOperation.IndexerSymbol, VisitArray(internalOperation.Patterns), internalOperation.DeclaredSymbol, internalOperation.InputType, internalOperation.NarrowedType, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.IsImplicit); + } + public override IOperation VisitSlicePattern(ISlicePatternOperation operation, object? argument) + { + var internalOperation = (SlicePatternOperation)operation; + return new SlicePatternOperation(internalOperation.SliceSymbol, Visit(internalOperation.Pattern), internalOperation.InputType, internalOperation.NarrowedType, internalOperation.OwningSemanticModel, internalOperation.Syntax, internalOperation.IsImplicit); + } } #endregion @@ -8289,6 +8433,8 @@ internal virtual void VisitNoneOperation(IOperation operation) { /* no-op */ } public virtual void VisitTypePattern(ITypePatternOperation operation) => DefaultVisit(operation); public virtual void VisitRelationalPattern(IRelationalPatternOperation operation) => DefaultVisit(operation); public virtual void VisitWith(IWithOperation operation) => DefaultVisit(operation); + public virtual void VisitListPattern(IListPatternOperation operation) => DefaultVisit(operation); + public virtual void VisitSlicePattern(ISlicePatternOperation operation) => DefaultVisit(operation); } public abstract partial class OperationVisitor { @@ -8415,6 +8561,8 @@ public abstract partial class OperationVisitor 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? VisitWith(IWithOperation operation, TArgument argument) => DefaultVisit(operation, argument); + public virtual TResult? VisitListPattern(IListPatternOperation operation, TArgument argument) => DefaultVisit(operation, argument); + public virtual TResult? VisitSlicePattern(ISlicePatternOperation 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 f61124b1be1b6..27291eefa9e8e 100644 --- a/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs +++ b/src/Compilers/Core/Portable/Operations/ControlFlowGraphBuilder.cs @@ -6907,6 +6907,32 @@ public override IOperation VisitDeclarationPattern(IDeclarationPatternOperation IsImplicit(operation)); } + public override IOperation VisitSlicePattern(ISlicePatternOperation operation, int? argument) + { + return new SlicePatternOperation( + operation.SliceSymbol, + (IPatternOperation?)Visit(operation.Pattern), + operation.InputType, + operation.NarrowedType, + semanticModel: null, + operation.Syntax, + IsImplicit(operation)); + } + + public override IOperation VisitListPattern(IListPatternOperation operation, int? argument) + { + return new ListPatternOperation( + operation.LengthSymbol, + operation.IndexerSymbol, + operation.Patterns.SelectAsArray((p, @this) => (IPatternOperation)@this.VisitRequired(p), this), + operation.DeclaredSymbol, + operation.InputType, + operation.NarrowedType, + semanticModel: null, + operation.Syntax, + IsImplicit(operation)); + } + public override IOperation VisitRecursivePattern(IRecursivePatternOperation operation, int? argument) { return new RecursivePatternOperation( diff --git a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml index 0e5bd4528bf3d..83d818d0a9b64 100644 --- a/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml +++ b/src/Compilers/Core/Portable/Operations/OperationInterfaces.xml @@ -3057,4 +3057,58 @@ + + + Represents a C# list pattern. + + + + + The Length or Count property that is being used to fetch the length value. + Returns null if no such property is found. + + + + + + + The indexer that is being used to fetch elements. + Returns null for an array input. + + + + + + + Returns subpatterns contained within the list pattern. + + + + + + Symbol declared by the pattern, if any. + + + + + + + Represents a C# slice pattern. + + + + + + The range indexer or the Slice method used to fetch the slice value. + + + + + + + The pattern that the slice value is matched with, if any. + + + + diff --git a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs index 35032082bdf93..65d79156d5f4b 100644 --- a/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/ControlFlowGraphVerifier.cs @@ -1904,6 +1904,8 @@ propertyReference.Parent is ISimpleAssignmentOperation simpleAssignment && case OperationKind.NegatedPattern: case OperationKind.BinaryPattern: case OperationKind.TypePattern: + case OperationKind.SlicePattern: + case OperationKind.ListPattern: return true; } diff --git a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs index 0c2ee0e26dfeb..d34c95ebf104f 100644 --- a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs @@ -1843,6 +1843,29 @@ public override void VisitDeclarationPattern(IDeclarationPatternOperation operat LogNewLine(); } + public override void VisitSlicePattern(ISlicePatternOperation operation) + { + LogString(nameof(ISlicePatternOperation)); + LogPatternProperties(operation); + LogSymbol(operation.SliceSymbol, $", {nameof(operation.SliceSymbol)}"); + LogNewLine(); + + Visit(operation.Pattern, $"{nameof(operation.Pattern)}"); + } + + public override void VisitListPattern(IListPatternOperation operation) + { + LogString(nameof(IListPatternOperation)); + LogPatternProperties(operation); + LogSymbol(operation.DeclaredSymbol, $", {nameof(operation.DeclaredSymbol)}"); + LogSymbol(operation.LengthSymbol, $", {nameof(operation.LengthSymbol)}"); + LogSymbol(operation.IndexerSymbol, $", {nameof(operation.IndexerSymbol)}"); + LogString(")"); + LogNewLine(); + + VisitArray(operation.Patterns, $"{nameof(operation.Patterns)} ", true, true); + } + public override void VisitRecursivePattern(IRecursivePatternOperation operation) { LogString(nameof(IRecursivePatternOperation)); diff --git a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs index 40aa23c5b2db6..226d59c99cec8 100644 --- a/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs +++ b/src/Compilers/Test/Core/Compilation/TestOperationVisitor.cs @@ -1220,6 +1220,39 @@ public override void VisitDeclarationPattern(IDeclarationPatternOperation operat Assert.Empty(operation.Children); } + public override void VisitSlicePattern(ISlicePatternOperation operation) + { + Assert.Equal(OperationKind.SlicePattern, operation.Kind); + VisitPatternCommon(operation); + + if (operation.Pattern != null) + { + Assert.Same(operation.Pattern, operation.Children.Single()); + } + else + { + Assert.Empty(operation.Children); + } + } + + public override void VisitListPattern(IListPatternOperation operation) + { + Assert.Equal(OperationKind.ListPattern, operation.Kind); + VisitPatternCommon(operation); + var designation = (operation.Syntax as CSharp.Syntax.ListPatternSyntax)?.Designation; + if (designation.IsKind(CSharp.SyntaxKind.SingleVariableDesignation)) + { + Assert.NotNull(operation.DeclaredSymbol); + } + else + { + Assert.Null(operation.DeclaredSymbol); + } + + IEnumerable children = operation.Patterns.Cast(); + AssertEx.Equal(children, operation.Children); + } + public override void VisitRecursivePattern(IRecursivePatternOperation operation) { Assert.Equal(OperationKind.RecursivePattern, operation.Kind);