/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Test/Emit3/Semantics/PatternMatchingTests5.cs
6 211 строк
234 KB
Rikki Gibson
Do not report hidden pattern diagnostics (#84576)
29 июл 2026, 21:10
Не верифицирован
29 июл 2026, 21:10
b8b83bd
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { [CompilerTrait(CompilerFeature.Patterns)] public class PatternMatchingTests5 : PatternMatchingTestBase { [Fact] public void ExtendedPropertyPatterns_01() { var program = @" using System; class C { public C Prop1 { get; set; } public C Prop2 { get; set; } public C Prop3 { get; set; } static bool Test1(C o) => o is { Prop1.Prop2.Prop3: null }; static bool Test2(S o) => o is { Prop1.Prop2.Prop3: null }; static bool Test3(S? o) => o is { Prop1.Prop2.Prop3: null }; static bool Test4(S0 o) => o is { Prop1.Prop2.Prop3: 420 }; public static void Main() { Console.WriteLine(Test1(new() { Prop1 = new() { Prop2 = new() { Prop3 = null }}})); Console.WriteLine(Test2(new() { Prop1 = new() { Prop2 = new() { Prop3 = null }}})); Console.WriteLine(Test3(new() { Prop1 = new() { Prop2 = new() { Prop3 = null }}})); Console.WriteLine(Test1(new() { Prop1 = new() { Prop2 = null }})); Console.WriteLine(Test2(new() { Prop1 = new() { Prop2 = null }})); Console.WriteLine(Test3(new() { Prop1 = new() { Prop2 = null }})); Console.WriteLine(Test1(new() { Prop1 = null })); Console.WriteLine(Test2(new() { Prop1 = null })); Console.WriteLine(Test3(new() { Prop1 = null })); Console.WriteLine(Test1(default)); Console.WriteLine(Test2(default)); Console.WriteLine(Test3(default)); Console.WriteLine(Test4(new() { Prop1 = new() { Prop2 = new() { Prop3 = 421 }}})); Console.WriteLine(Test4(new() { Prop1 = new() { Prop2 = new() { Prop3 = 420 }}})); } } struct S { public A? Prop1; } struct A { public B? Prop2; } struct B { public int? Prop3; } struct S0 { public A0 Prop1; } struct A0 { public B0 Prop2; } struct B0 { public int Prop3; } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, options: TestOptions.ReleaseExe); compilation.VerifyDiagnostics(); var expectedOutput = @" True True True False False False False False False False False False False True "; var verifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); verifier.VerifyIL("C.Test1", @" { // Code size 35 (0x23) .maxstack 2 .locals init (C V_0, C V_1) IL_0000: ldarg.0 IL_0001: brfalse.s IL_0021 IL_0003: ldarg.0 IL_0004: callvirt ""C C.Prop1.get"" IL_0009: stloc.0 IL_000a: ldloc.0 IL_000b: brfalse.s IL_0021 IL_000d: ldloc.0 IL_000e: callvirt ""C C.Prop2.get"" IL_0013: stloc.1 IL_0014: ldloc.1 IL_0015: brfalse.s IL_0021 IL_0017: ldloc.1 IL_0018: callvirt ""C C.Prop3.get"" IL_001d: ldnull IL_001e: ceq IL_0020: ret IL_0021: ldc.i4.0 IL_0022: ret }"); verifier.VerifyIL("C.Test2", @" { // Code size 64 (0x40) .maxstack 2 .locals init (A? V_0, B? V_1, int? V_2) IL_0000: ldarg.0 IL_0001: ldfld ""A? S.Prop1"" IL_0006: stloc.0 IL_0007: ldloca.s V_0 IL_0009: call ""bool A?.HasValue.get"" IL_000e: brfalse.s IL_003e IL_0010: ldloca.s V_0 IL_0012: call ""A A?.GetValueOrDefault()"" IL_0017: ldfld ""B? A.Prop2"" IL_001c: stloc.1 IL_001d: ldloca.s V_1 IL_001f: call ""bool B?.HasValue.get"" IL_0024: brfalse.s IL_003e IL_0026: ldloca.s V_1 IL_0028: call ""B B?.GetValueOrDefault()"" IL_002d: ldfld ""int? B.Prop3"" IL_0032: stloc.2 IL_0033: ldloca.s V_2 IL_0035: call ""bool int?.HasValue.get"" IL_003a: ldc.i4.0 IL_003b: ceq IL_003d: ret IL_003e: ldc.i4.0 IL_003f: ret }"); verifier.VerifyIL("C.Test4", @" { // Code size 24 (0x18) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldfld ""A0 S0.Prop1"" IL_0006: ldfld ""B0 A0.Prop2"" IL_000b: ldfld ""int B0.Prop3"" IL_0010: ldc.i4 0x1a4 IL_0015: ceq IL_0017: ret }"); } [Fact] public void ExtendedPropertyPatterns_02() { var program = @" class C { public C Prop1 { get; set; } public C Prop2 { get; set; } public static void Main() { _ = new C() is { Prop1: null } and { Prop1.Prop2: null }; _ = new C() is { Prop1: null, Prop1.Prop2: null }; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, options: TestOptions.ReleaseExe); compilation.VerifyDiagnostics( // (9,13): error CS8518: An expression of type 'C' can never match the provided pattern. // _ = new C() is { Prop1: null } and { Prop1.Prop2: null }; Diagnostic(ErrorCode.ERR_IsPatternImpossible, "new C() is { Prop1: null } and { Prop1.Prop2: null }").WithArguments("C").WithLocation(9, 13), // (10,13): error CS8518: An expression of type 'C' can never match the provided pattern. // _ = new C() is { Prop1: null, Prop1.Prop2: null }; Diagnostic(ErrorCode.ERR_IsPatternImpossible, "new C() is { Prop1: null, Prop1.Prop2: null }").WithArguments("C").WithLocation(10, 13) ); } [Fact] public void ExtendedPropertyPatterns_03() { var program = @" using System; class C { C _prop1; C _prop2; C Prop1 { get { Console.WriteLine(nameof(Prop1)); return _prop1; } set => _prop1 = value; } C Prop2 { get { Console.WriteLine(nameof(Prop2)); return _prop2; } set => _prop2 = value; } public static void Main() { Test(null); Test(new()); Test(new() { Prop1 = new() }); Test(new() { Prop1 = new() { Prop2 = new() } }); } static void Test(C o) { Console.WriteLine(nameof(Test)); var result = o switch { {Prop1: null} => 1, {Prop1.Prop2: null} => 2, _ => -1, }; Console.WriteLine(result); } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, options: TestOptions.ReleaseExe); compilation.VerifyDiagnostics(); var expectedOutput = @" Test -1 Test Prop1 1 Test Prop1 Prop2 2 Test Prop1 Prop2 -1"; var verifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); verifier.VerifyIL("C.Test", @" { // Code size 50 (0x32) .maxstack 1 .locals init (int V_0, C V_1) IL_0000: ldstr ""Test"" IL_0005: call ""void System.Console.WriteLine(string)"" IL_000a: ldarg.0 IL_000b: brfalse.s IL_0029 IL_000d: ldarg.0 IL_000e: callvirt ""C C.Prop1.get"" IL_0013: stloc.1 IL_0014: ldloc.1 IL_0015: brfalse.s IL_0021 IL_0017: ldloc.1 IL_0018: callvirt ""C C.Prop2.get"" IL_001d: brfalse.s IL_0025 IL_001f: br.s IL_0029 IL_0021: ldc.i4.1 IL_0022: stloc.0 IL_0023: br.s IL_002b IL_0025: ldc.i4.2 IL_0026: stloc.0 IL_0027: br.s IL_002b IL_0029: ldc.i4.m1 IL_002a: stloc.0 IL_002b: ldloc.0 IL_002c: call ""void System.Console.WriteLine(int)"" IL_0031: ret }"); } [Fact] public void ExtendedPropertyPatterns_04() { var program = @" class C { public static void Main() { _ = new C() is { Prop1<int>.Prop2: {} }; _ = new C() is { Prop1->Prop2: {} }; _ = new C() is { Prop1!.Prop2: {} }; _ = new C() is { Prop1?.Prop2: {} }; _ = new C() is { Prop1[0]: {} }; _ = new C() is { 1: {} }; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, options: TestOptions.ReleaseExe); compilation.VerifyDiagnostics( // (6,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1<int>.Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1<int>").WithLocation(6, 26), // (7,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1->Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1->Prop2").WithLocation(7, 26), // (8,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1!.Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1!").WithLocation(8, 26), // (9,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1?.Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1?.Prop2").WithLocation(9, 26), // (10,26): error CS8503: A property subpattern requires a reference to the property or field to be matched, e.g. '{ Name: Prop1[0] }' // _ = new C() is { Prop1[0]: {} }; Diagnostic(ErrorCode.ERR_PropertyPatternNameMissing, "Prop1[0]").WithArguments("Prop1[0]").WithLocation(10, 26), // (10,26): error CS0246: The type or namespace name 'Prop1' could not be found (are you missing a using directive or an assembly reference?) // _ = new C() is { Prop1[0]: {} }; Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Prop1").WithArguments("Prop1").WithLocation(10, 26), // (10,31): error CS0270: Array size cannot be specified in a variable declaration (try initializing with a 'new' expression) // _ = new C() is { Prop1[0]: {} }; Diagnostic(ErrorCode.ERR_ArraySizeInDeclaration, "[0]").WithLocation(10, 31), // (10,34): error CS1003: Syntax error, ',' expected // _ = new C() is { Prop1[0]: {} }; Diagnostic(ErrorCode.ERR_SyntaxError, ":").WithArguments(",").WithLocation(10, 34), // (10,36): error CS1003: Syntax error, ',' expected // _ = new C() is { Prop1[0]: {} }; Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(",").WithLocation(10, 36), // (11,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { 1: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "1").WithLocation(11, 26)); } [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] public void ExtendedPropertyPatterns_05() { var program = @" class C { C Field1, Field2, Field3, Field4; public void M() { _ = this is { Field1.Field2.Field3: {} }; _ = this is { Field4: {} }; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (4,7): warning CS0649: Field 'C.Field1' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field1").WithArguments("C.Field1", "null").WithLocation(4, 7), // (4,15): warning CS0649: Field 'C.Field2' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field2").WithArguments("C.Field2", "null").WithLocation(4, 15), // (4,23): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(4, 23), // (4,31): warning CS0649: Field 'C.Field4' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field4").WithArguments("C.Field4", "null").WithLocation(4, 31) ); } [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] public void ExtendedPropertyPatterns_05_NestedRecursivePattern() { var program = @" class C { C Field1, Field2, Field3, Field4; public void M() { _ = this is { Field1: { Field2.Field3.Field4: not null } }; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (4,7): warning CS0649: Field 'C.Field1' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field1").WithArguments("C.Field1", "null").WithLocation(4, 7), // (4,15): warning CS0649: Field 'C.Field2' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field2").WithArguments("C.Field2", "null").WithLocation(4, 15), // (4,23): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(4, 23), // (4,31): warning CS0649: Field 'C.Field4' is never assigned to, and will always have its default value null // C Field1, Field2, Field3, Field4; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field4").WithArguments("C.Field4", "null").WithLocation(4, 31) ); } [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] public void ExtendedPropertyPatterns_05_Properties() { var program = @" class C { C Prop1 { get; set; } C Prop2 { get; set; } C Prop3 { get; set; } C Prop4 { get; set; } public void M() { _ = this is { Prop1.Prop2.Prop3: {} }; _ = this is { Prop4: {} }; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics(); } [Fact] public void ExtendedPropertyPatterns_IOperation_Properties() { var src = @" class Program { static void M(A a) /*<bind>*/{ _ = a is { Prop1.Prop2.Prop3: null }; }/*</bind>*/ } class A { public B Prop1 => null; } class B { public C Prop2 => null; } class C { public object Prop3 => null; } "; var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyDiagnostics(); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); var isPattern = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); VerifyOperationTree(comp, model.GetOperation(isPattern), @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Prop ... op3: null }') Value: IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Prop1.Pro ... op3: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1') Member: IPropertyReferenceOperation: B A.Prop1 { get; } (OperationKind.PropertyReference, Type: B) (Syntax: 'Prop1') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Prop1') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') Member: IPropertyReferenceOperation: C B.Prop2 { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'Prop1.Prop2') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Prop1.Prop2') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') Member: IPropertyReferenceOperation: System.Object C.Prop3 { get; } (OperationKind.PropertyReference, Type: System.Object) (Syntax: 'Prop1.Prop2.Prop3') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') "); var expectedFlowGraph = @" Block[B0] - Entry Statements (0) Next (Regular) Block[B1] Block[B1] - Block Predecessors: [B0] Statements (1) IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = a is { ... p3: null };') Expression: ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: '_ = a is { ... op3: null }') Left: IDiscardOperation (Symbol: System.Boolean _) (OperationKind.Discard, Type: System.Boolean) (Syntax: '_') Right: IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Prop ... op3: null }') Value: IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Prop1.Pro ... op3: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1') Member: IPropertyReferenceOperation: B A.Prop1 { get; } (OperationKind.PropertyReference, Type: B) (Syntax: 'Prop1') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Prop1') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') Member: IPropertyReferenceOperation: C B.Prop2 { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'Prop1.Prop2') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Prop1.Prop2') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') Member: IPropertyReferenceOperation: System.Object C.Prop3 { get; } (OperationKind.PropertyReference, Type: System.Object) (Syntax: 'Prop1.Prop2.Prop3') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) (ImplicitReference) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') Next (Regular) Block[B2] Block[B2] - Exit Predecessors: [B1] Statements (0) "; VerifyFlowGraphAndDiagnosticsForTest<BlockSyntax>(src, expectedFlowGraph, DiagnosticDescription.None, parseOptions: TestOptions.Regular10); } [Fact] public void ExtendedPropertyPatterns_IOperation_FieldsInStructs() { var src = @" class Program { static void M(A a) /*<bind>*/{ _ = a is { Field1.Field2.Field3: null, Field4: null }; }/*</bind>*/ } struct A { public B? Field1; public B? Field4; } struct B { public C? Field2; } struct C { public object Field3; } "; var expectedDiagnostics = new[] { // (9,22): warning CS0649: Field 'A.Field1' is never assigned to, and will always have its default value // struct A { public B? Field1; public B? Field4; } Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field1").WithArguments("A.Field1", "").WithLocation(9, 22), // (9,40): warning CS0649: Field 'A.Field4' is never assigned to, and will always have its default value // struct A { public B? Field1; public B? Field4; } Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field4").WithArguments("A.Field4", "").WithLocation(9, 40), // (10,22): warning CS0649: Field 'B.Field2' is never assigned to, and will always have its default value // struct B { public C? Field2; } Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field2").WithArguments("B.Field2", "").WithLocation(10, 22), // (11,26): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null // struct C { public object Field3; } Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(11, 26) }; var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyDiagnostics(expectedDiagnostics); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); var isPattern = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); VerifyOperationTree(comp, model.GetOperation(isPattern), @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Fiel ... ld4: null }') Value: IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Field1.Fi ... ld4: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (2): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1') Member: IFieldReferenceOperation: B? A.Field1 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field1') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field1') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') Member: IFieldReferenceOperation: C? B.Field2 (OperationKind.FieldReference, Type: C?) (Syntax: 'Field1.Field2') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Field1.Field2') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2.Field3') Member: IFieldReferenceOperation: System.Object C.Field3 (OperationKind.FieldReference, Type: System.Object) (Syntax: 'Field1.Field2.Field3') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Field1.Field2.Field3') Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null) (Syntax: 'Field4: null') Member: IFieldReferenceOperation: B? A.Field4 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field4') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field4') Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: B?, NarrowedType: B?) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: B?, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') "); var expectedFlowGraph = @" Block[B0] - Entry Statements (0) Next (Regular) Block[B1] Block[B1] - Block Predecessors: [B0] Statements (1) IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = a is { ... d4: null };') Expression: ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: '_ = a is { ... ld4: null }') Left: IDiscardOperation (Symbol: System.Boolean _) (OperationKind.Discard, Type: System.Boolean) (Syntax: '_') Right: IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Fiel ... ld4: null }') Value: IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Field1.Fi ... ld4: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (2): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1') Member: IFieldReferenceOperation: B? A.Field1 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field1') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field1') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') Member: IFieldReferenceOperation: C? B.Field2 (OperationKind.FieldReference, Type: C?) (Syntax: 'Field1.Field2') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Field1.Field2') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2.Field3') Member: IFieldReferenceOperation: System.Object C.Field3 (OperationKind.FieldReference, Type: System.Object) (Syntax: 'Field1.Field2.Field3') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Field1.Field2.Field3') Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) (ImplicitReference) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null) (Syntax: 'Field4: null') Member: IFieldReferenceOperation: B? A.Field4 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field4') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field4') Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: B?, NarrowedType: B?) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: B?, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) (NullLiteral) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') Next (Regular) Block[B2] Block[B2] - Exit Predecessors: [B1] Statements (0) "; VerifyFlowGraphAndDiagnosticsForTest<BlockSyntax>(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.Regular10); } [Fact] public void ExtendedPropertyPatterns_Explainer() { var src = @" class Program { void M(A a) { _ = a switch // 1 { { BProp.BoolProp: true } => 1 }; _ = a switch // 2 { { BProp.IntProp: <= 0 } => 1 }; } } class A { public B BProp => null; } class B { public bool BoolProp => true; public int IntProp => 0; } "; var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyDiagnostics( // (6,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ BProp: { BoolProp: false } }' is not covered. // _ = a switch // 1 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ BProp: { BoolProp: false } }").WithLocation(6, 15), // (11,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ BProp: { IntProp: 1 } }' is not covered. // _ = a switch // 2 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ BProp: { IntProp: 1 } }").WithLocation(11, 15) ); } [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] public void ExtendedPropertyPatterns_BadMemberAccess() { var program = @" class C { C Field1, Field2, Field3; public static void Main() { _ = new C() is { Field1?.Field2: {} }; // 1 _ = new C() is { Field1!.Field2: {} }; // 2 _ = new C() is { Missing: null }; // 3 _ = new C() is { Field3.Missing: {} }; // 4 _ = new C() is { Missing1.Missing2: {} }; // 5 } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (4,7): warning CS0169: The field 'C.Field1' is never used // C Field1, Field2, Field3; Diagnostic(ErrorCode.WRN_UnreferencedField, "Field1").WithArguments("C.Field1").WithLocation(4, 7), // (4,15): warning CS0169: The field 'C.Field2' is never used // C Field1, Field2, Field3; Diagnostic(ErrorCode.WRN_UnreferencedField, "Field2").WithArguments("C.Field2").WithLocation(4, 15), // (4,23): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null // C Field1, Field2, Field3; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(4, 23), // (7,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Field1?.Field2: {} }; // 1 Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Field1?.Field2").WithLocation(7, 26), // (8,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Field1!.Field2: {} }; // 2 Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Field1!").WithLocation(8, 26), // (9,26): error CS0117: 'C' does not contain a definition for 'Missing' // _ = new C() is { Missing: null }; // 3 Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(9, 26), // (10,33): error CS0117: 'C' does not contain a definition for 'Missing' // _ = new C() is { Field3.Missing: {} }; // 4 Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(10, 33), // (11,26): error CS0117: 'C' does not contain a definition for 'Missing1' // _ = new C() is { Missing1.Missing2: {} }; // 5 Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing1").WithArguments("C", "Missing1").WithLocation(11, 26) ); } [Fact] public void ExtendedPropertyPatterns_IOperationOnMissing() { var program = @" class C { public void M() { _ = this is { Missing: null }; } } "; var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyEmitDiagnostics( // (6,23): error CS0117: 'C' does not contain a definition for 'Missing' // _ = this is { Missing: null }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(6, 23) ); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); var isPattern = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); VerifyOperationTree(comp, model.GetOperation(isPattern), @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { M ... ing: null }') Value: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ Missing: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid) (Syntax: 'Missing: null') Member: IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Missing') Children(0) Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') "); } [Fact] public void ExtendedPropertyPatterns_IOperationOnNestedMissing() { var program = @" class C { int Property { get; set; } public void M() { _ = this is { Property.Missing: null }; } } "; var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyEmitDiagnostics( // (7,32): error CS0117: 'int' does not contain a definition for 'Missing' // _ = this is { Property.Missing: null }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("int", "Missing").WithLocation(7, 32) ); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); var isPattern = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); VerifyOperationTree(comp, model.GetOperation(isPattern), @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { P ... ing: null }') Value: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ Property. ... ing: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Property') Member: IPropertyReferenceOperation: System.Int32 C.Property { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'Property') Instance Receiver: IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Property') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Property') (InputType: System.Int32, NarrowedType: System.Int32, DeclaredSymbol: null, MatchedType: System.Int32, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Property.Missing') Member: IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Property.Missing') Children(0) Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') "); } [Fact] public void ExtendedPropertyPatterns_IOperationOnTwoMissing() { var program = @" class C { public void M() { _ = this is { Missing1.Missing2: null }; } } "; var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyEmitDiagnostics( // (6,23): error CS0117: 'C' does not contain a definition for 'Missing1' // _ = this is { Missing1.Missing2: null }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing1").WithArguments("C", "Missing1").WithLocation(6, 23) ); var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); var isPattern = tree.GetRoot().DescendantNodes().OfType<IsPatternExpressionSyntax>().Single(); VerifyOperationTree(comp, model.GetOperation(isPattern), @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { M ... ng2: null }') Value: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ Missing1. ... ng2: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Missing1') Member: IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Missing1') Children(0) Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Missing1') (InputType: ?, NarrowedType: ?, DeclaredSymbol: null, MatchedType: ?, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Missing1.Missing2') Member: IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Missing1.Missing2') Children(0) Pattern: IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) Value: IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) Operand: ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') "); } [Fact, WorkItem(53484, "https://github.com/dotnet/roslyn/issues/53484")] public void ExtendedPropertyPatterns_SuppressionOnPattern() { var program = @" #nullable enable public class ContainerType { public class Type { public void M(object o) { const Type c = null!; if (o is c!) {} // a1 if (o is 1!) {} // a2 if (o is (c!)) {} // a3 if (o is (1!)) {} // a4 if (o is Type!) {} // a5 if (o is ContainerType!.Type) {} // a6 if (o is ContainerType.Type!) {} // a7 if (o is < c!) {} // a8 switch (o) { case c!: break; // b1 case 1!: break; // b2 case (c!): break; // b3 case (1!): break; // b4 case Type!: break; // b5 case ContainerType!.Type: break; // b6 case ContainerType.Type!: break; // b7 case < c!: break; // b8 } _ = o switch { c! => 0, // c1 1! => 0, // c2 (c!) => 0, // c3 (1!) => 0, // c4 Type! => 0, // c5 ContainerType!.Type => 0, // c6 ContainerType.Type! => 0, // c7 < c! => 0, // c8 }; } } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (10,22): error CS8598: The suppression operator is not allowed in this context // if (o is c!) {} // a1 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(10, 22), // (11,22): error CS8598: The suppression operator is not allowed in this context // if (o is 1!) {} // a2 Diagnostic(ErrorCode.ERR_IllegalSuppression, "1!").WithLocation(11, 22), // (12,23): error CS8598: The suppression operator is not allowed in this context // if (o is (c!)) {} // a3 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(12, 23), // (13,23): error CS8598: The suppression operator is not allowed in this context // if (o is (1!)) {} // a4 Diagnostic(ErrorCode.ERR_IllegalSuppression, "1!").WithLocation(13, 23), // (14,22): error CS8598: The suppression operator is not allowed in this context // if (o is Type!) {} // a5 Diagnostic(ErrorCode.ERR_IllegalSuppression, "Type!").WithLocation(14, 22), // (15,22): error CS8598: The suppression operator is not allowed in this context // if (o is ContainerType!.Type) {} // a6 Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType").WithLocation(15, 22), // (16,22): error CS8598: The suppression operator is not allowed in this context // if (o is ContainerType.Type!) {} // a7 Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType.Type!").WithLocation(16, 22), // (17,24): error CS8598: The suppression operator is not allowed in this context // if (o is < c!) {} // a8 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(17, 24), // (21,22): error CS8598: The suppression operator is not allowed in this context // case c!: break; // b1 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(21, 22), // (22,22): error CS8598: The suppression operator is not allowed in this context // case 1!: break; // b2 Diagnostic(ErrorCode.ERR_IllegalSuppression, "1!").WithLocation(22, 22), // (23,23): error CS8598: The suppression operator is not allowed in this context // case (c!): break; // b3 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(23, 23), // (24,23): error CS8598: The suppression operator is not allowed in this context // case (1!): break; // b4 Diagnostic(ErrorCode.ERR_IllegalSuppression, "1!").WithLocation(24, 23), // (25,22): error CS8598: The suppression operator is not allowed in this context // case Type!: break; // b5 Diagnostic(ErrorCode.ERR_IllegalSuppression, "Type!").WithLocation(25, 22), // (26,22): error CS8598: The suppression operator is not allowed in this context // case ContainerType!.Type: break; // b6 Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType").WithLocation(26, 22), // (27,22): error CS8598: The suppression operator is not allowed in this context // case ContainerType.Type!: break; // b7 Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType.Type!").WithLocation(27, 22), // (28,24): error CS8598: The suppression operator is not allowed in this context // case < c!: break; // b8 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(28, 24), // (33,17): error CS8598: The suppression operator is not allowed in this context // c! => 0, // c1 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(33, 17), // (34,17): error CS8598: The suppression operator is not allowed in this context // 1! => 0, // c2 Diagnostic(ErrorCode.ERR_IllegalSuppression, "1!").WithLocation(34, 17), // (35,18): error CS8598: The suppression operator is not allowed in this context // (c!) => 0, // c3 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(35, 18), // (36,18): error CS8598: The suppression operator is not allowed in this context // (1!) => 0, // c4 Diagnostic(ErrorCode.ERR_IllegalSuppression, "1!").WithLocation(36, 18), // (37,17): error CS8598: The suppression operator is not allowed in this context // Type! => 0, // c5 Diagnostic(ErrorCode.ERR_IllegalSuppression, "Type!").WithLocation(37, 17), // (38,17): error CS8598: The suppression operator is not allowed in this context // ContainerType!.Type => 0, // c6 Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType").WithLocation(38, 17), // (39,17): error CS8598: The suppression operator is not allowed in this context // ContainerType.Type! => 0, // c7 Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType.Type!").WithLocation(39, 17), // (40,19): error CS8598: The suppression operator is not allowed in this context // < c! => 0, // c8 Diagnostic(ErrorCode.ERR_IllegalSuppression, "c!").WithLocation(40, 19) ); } [Fact, WorkItem(53484, "https://github.com/dotnet/roslyn/issues/53484")] public void ExtendedPropertyPatterns_PointerAccessInPattern() { var program = @" public class Type { public unsafe void M(S* s) { if (0 is s->X) {} } } public struct S { public int X; } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, options: TestOptions.UnsafeDebugDll); compilation.VerifyEmitDiagnostics( // (6,18): error CS9133: A constant value of type 'int' is expected // if (0 is s->X) {} Diagnostic(ErrorCode.ERR_ConstantValueOfTypeExpected, "s->X").WithArguments("int").WithLocation(6, 18) ); } [Fact] public void ExtendedPropertyPatterns_SymbolInfo_01() { var source = @" using System; class Program { public static void Main() { P p = new P(); Console.WriteLine(p is { X.Y: {}, Y.X: {} }); } } class P { public P X { get; } public P Y; } "; var compilation = CreatePatternCompilation(source); compilation.VerifyEmitDiagnostics( // (14,14): warning CS0649: Field 'P.Y' is never assigned to, and will always have its default value null // public P Y; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Y").WithArguments("P.Y", "null").WithLocation(14, 14) ); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var subpatterns = tree.GetRoot().DescendantNodes().OfType<SubpatternSyntax>().ToArray(); Assert.Equal(2, subpatterns.Length); AssertEmpty(model.GetSymbolInfo(subpatterns[0])); AssertEmpty(model.GetSymbolInfo(subpatterns[0].ExpressionColon)); var xy = subpatterns[0].ExpressionColon.Expression; var xySymbol = model.GetSymbolInfo(xy); Assert.Equal(CandidateReason.None, xySymbol.CandidateReason); Assert.Equal("P P.Y", xySymbol.Symbol.ToTestDisplayString()); var x = ((MemberAccessExpressionSyntax)subpatterns[0].ExpressionColon.Expression).Expression; var xSymbol = model.GetSymbolInfo(x); Assert.Equal(CandidateReason.None, xSymbol.CandidateReason); Assert.Equal("P P.X { get; }", xSymbol.Symbol.ToTestDisplayString()); var yName = ((MemberAccessExpressionSyntax)subpatterns[0].ExpressionColon.Expression).Name; var yNameSymbol = model.GetSymbolInfo(yName); Assert.Equal(CandidateReason.None, yNameSymbol.CandidateReason); Assert.Equal("P P.Y", yNameSymbol.Symbol.ToTestDisplayString()); AssertEmpty(model.GetSymbolInfo(subpatterns[1])); AssertEmpty(model.GetSymbolInfo(subpatterns[1].ExpressionColon)); var yx = subpatterns[1].ExpressionColon.Expression; var yxSymbol = model.GetSymbolInfo(yx); Assert.NotEqual(default, yxSymbol); Assert.Equal(CandidateReason.None, yxSymbol.CandidateReason); Assert.Equal("P P.X { get; }", yxSymbol.Symbol.ToTestDisplayString()); var y = ((MemberAccessExpressionSyntax)subpatterns[1].ExpressionColon.Expression).Expression; var ySymbol = model.GetSymbolInfo(y); Assert.Equal(CandidateReason.None, ySymbol.CandidateReason); Assert.Equal("P P.Y", ySymbol.Symbol.ToTestDisplayString()); var xName = ((MemberAccessExpressionSyntax)subpatterns[1].ExpressionColon.Expression).Name; var xNameSymbol = model.GetSymbolInfo(xName); Assert.Equal(CandidateReason.None, xNameSymbol.CandidateReason); Assert.Equal("P P.X { get; }", xNameSymbol.Symbol.ToTestDisplayString()); } [Fact] public void ExtendedPropertyPatterns_SymbolInfo_02() { var source = @" using System; class Program { public static void Main() { P p = null; Console.WriteLine(p is { X.Y: {}, Y.X: {}, }); } } interface I1 { P X { get; } P Y { get; } } interface I2 { P X { get; } P Y { get; } } interface P : I1, I2 { // X and Y inherited ambiguously } "; var compilation = CreatePatternCompilation(source); compilation.VerifyEmitDiagnostics( // (8,34): error CS0229: Ambiguity between 'I1.X' and 'I2.X' // Console.WriteLine(p is { X.Y: {}, Y.X: {}, }); Diagnostic(ErrorCode.ERR_AmbigMember, "X").WithArguments("I1.X", "I2.X").WithLocation(8, 34), // (8,43): error CS0229: Ambiguity between 'I1.Y' and 'I2.Y' // Console.WriteLine(p is { X.Y: {}, Y.X: {}, }); Diagnostic(ErrorCode.ERR_AmbigMember, "Y").WithArguments("I1.Y", "I2.Y").WithLocation(8, 43) ); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var subpatterns = tree.GetRoot().DescendantNodes().OfType<SubpatternSyntax>().ToArray(); Assert.Equal(2, subpatterns.Length); AssertEmpty(model.GetSymbolInfo(subpatterns[0])); AssertEmpty(model.GetSymbolInfo(subpatterns[0].ExpressionColon)); var x = ((MemberAccessExpressionSyntax)subpatterns[0].ExpressionColon.Expression).Expression; var xSymbol = model.GetSymbolInfo(x); Assert.Equal(CandidateReason.Ambiguous, xSymbol.CandidateReason); Assert.Null(xSymbol.Symbol); Assert.Equal(2, xSymbol.CandidateSymbols.Length); Assert.Equal("P I1.X { get; }", xSymbol.CandidateSymbols[0].ToTestDisplayString()); Assert.Equal("P I2.X { get; }", xSymbol.CandidateSymbols[1].ToTestDisplayString()); AssertEmpty(model.GetSymbolInfo(subpatterns[1])); AssertEmpty(model.GetSymbolInfo(subpatterns[1].ExpressionColon)); var y = ((MemberAccessExpressionSyntax)subpatterns[1].ExpressionColon.Expression).Expression; var ySymbol = model.GetSymbolInfo(y); Assert.Equal(CandidateReason.Ambiguous, ySymbol.CandidateReason); Assert.Null(ySymbol.Symbol); Assert.Equal(2, ySymbol.CandidateSymbols.Length); Assert.Equal("P I1.Y { get; }", ySymbol.CandidateSymbols[0].ToTestDisplayString()); Assert.Equal("P I2.Y { get; }", ySymbol.CandidateSymbols[1].ToTestDisplayString()); } [Fact] public void ExtendedPropertyPatterns_SymbolInfo_03() { var source = @" using System; class Program { public static void Main() { P p = null; Console.WriteLine(p is { X.Y: {}, Y.X: {}, }); } } class P { } "; var compilation = CreatePatternCompilation(source); compilation.VerifyEmitDiagnostics( // (8,34): error CS0117: 'P' does not contain a definition for 'X' // Console.WriteLine(p is { X: 3, Y: 4 }); Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("P", "X").WithLocation(8, 34) ); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var subpatterns = tree.GetRoot().DescendantNodes().OfType<SubpatternSyntax>().ToArray(); Assert.Equal(2, subpatterns.Length); AssertEmpty(model.GetSymbolInfo(subpatterns[0])); AssertEmpty(model.GetSymbolInfo(subpatterns[0].ExpressionColon)); var x = subpatterns[0].ExpressionColon.Expression; var xSymbol = model.GetSymbolInfo(x); Assert.Equal(CandidateReason.None, xSymbol.CandidateReason); Assert.Null(xSymbol.Symbol); Assert.Empty(xSymbol.CandidateSymbols); AssertEmpty(model.GetSymbolInfo(subpatterns[1])); AssertEmpty(model.GetSymbolInfo(subpatterns[1].ExpressionColon)); var y = subpatterns[1].ExpressionColon.Expression; var ySymbol = model.GetSymbolInfo(y); Assert.Equal(CandidateReason.None, ySymbol.CandidateReason); Assert.Null(ySymbol.Symbol); Assert.Empty(ySymbol.CandidateSymbols); } [Fact] public void ExtendedPropertyPatterns_SymbolInfo_04() { var source = @" using System; class Program { public static void Main() { Console.WriteLine(new C() is { X.Y: {} }); Console.WriteLine(new S() is { Y.X: {} }); } } class C { public S? X { get; } } struct S { public C Y; } "; var compilation = CreatePatternCompilation(source); compilation.VerifyEmitDiagnostics( // (17,14): warning CS0649: Field 'S.Y' is never assigned to, and will always have its default value null // public C Y; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Y").WithArguments("S.Y", "null").WithLocation(17, 14) ); var tree = compilation.SyntaxTrees[0]; var model = compilation.GetSemanticModel(tree); var subpatterns = tree.GetRoot().DescendantNodes().OfType<SubpatternSyntax>().ToArray(); Assert.Equal(2, subpatterns.Length); AssertEmpty(model.GetSymbolInfo(subpatterns[0])); AssertEmpty(model.GetSymbolInfo(subpatterns[0].ExpressionColon)); var xy = subpatterns[0].ExpressionColon.Expression; var xySymbol = model.GetSymbolInfo(xy); Assert.Equal(CandidateReason.None, xySymbol.CandidateReason); Assert.Equal("C S.Y", xySymbol.Symbol.ToTestDisplayString()); var xyType = model.GetTypeInfo(xy); Assert.Equal("C", xyType.Type.ToTestDisplayString()); Assert.Equal("C", xyType.ConvertedType.ToTestDisplayString()); var x = ((MemberAccessExpressionSyntax)subpatterns[0].ExpressionColon.Expression).Expression; var xSymbol = model.GetSymbolInfo(x); Assert.Equal(CandidateReason.None, xSymbol.CandidateReason); Assert.Equal("S? C.X { get; }", xSymbol.Symbol.ToTestDisplayString()); var xType = model.GetTypeInfo(x); Assert.Equal("S?", xType.Type.ToTestDisplayString()); Assert.Equal("S?", xType.ConvertedType.ToTestDisplayString()); var yName = ((MemberAccessExpressionSyntax)subpatterns[0].ExpressionColon.Expression).Name; var yNameSymbol = model.GetSymbolInfo(yName); Assert.Equal(CandidateReason.None, yNameSymbol.CandidateReason); Assert.Equal("C S.Y", yNameSymbol.Symbol.ToTestDisplayString()); var yNameType = model.GetTypeInfo(yName); Assert.Equal("C", yNameType.Type.ToTestDisplayString()); Assert.Equal("C", yNameType.ConvertedType.ToTestDisplayString()); AssertEmpty(model.GetSymbolInfo(subpatterns[1])); AssertEmpty(model.GetSymbolInfo(subpatterns[1].ExpressionColon)); var yx = subpatterns[1].ExpressionColon.Expression; var yxSymbol = model.GetSymbolInfo(yx); Assert.NotEqual(default, yxSymbol); Assert.Equal(CandidateReason.None, yxSymbol.CandidateReason); Assert.Equal("S? C.X { get; }", yxSymbol.Symbol.ToTestDisplayString()); var yxType = model.GetTypeInfo(yx); Assert.Equal("S?", yxType.Type.ToTestDisplayString()); Assert.Equal("S?", yxType.ConvertedType.ToTestDisplayString()); var y = ((MemberAccessExpressionSyntax)subpatterns[1].ExpressionColon.Expression).Expression; var ySymbol = model.GetSymbolInfo(y); Assert.Equal(CandidateReason.None, ySymbol.CandidateReason); Assert.Equal("C S.Y", ySymbol.Symbol.ToTestDisplayString()); var yType = model.GetTypeInfo(y); Assert.Equal("C", yType.Type.ToTestDisplayString()); Assert.Equal("C", yType.ConvertedType.ToTestDisplayString()); var xName = ((MemberAccessExpressionSyntax)subpatterns[1].ExpressionColon.Expression).Name; var xNameSymbol = model.GetSymbolInfo(xName); Assert.Equal(CandidateReason.None, xNameSymbol.CandidateReason); Assert.Equal("S? C.X { get; }", xNameSymbol.Symbol.ToTestDisplayString()); var xNameType = model.GetTypeInfo(xName); Assert.Equal("S?", xNameType.Type.ToTestDisplayString()); Assert.Equal("S?", xNameType.ConvertedType.ToTestDisplayString()); var verifier = CompileAndVerify(compilation); verifier.VerifyIL("Program.Main", @" { // Code size 92 (0x5c) .maxstack 2 .locals init (C V_0, S? V_1, S V_2) IL_0000: nop IL_0001: newobj ""C..ctor()"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: brfalse.s IL_002b IL_000a: ldloc.0 IL_000b: callvirt ""S? C.X.get"" IL_0010: stloc.1 IL_0011: ldloca.s V_1 IL_0013: call ""bool S?.HasValue.get"" IL_0018: brfalse.s IL_002b IL_001a: ldloca.s V_1 IL_001c: call ""S S?.GetValueOrDefault()"" IL_0021: ldfld ""C S.Y"" IL_0026: ldnull IL_0027: cgt.un IL_0029: br.s IL_002c IL_002b: ldc.i4.0 IL_002c: call ""void System.Console.WriteLine(bool)"" IL_0031: nop IL_0032: ldloca.s V_2 IL_0034: initobj ""S"" IL_003a: ldloc.2 IL_003b: ldfld ""C S.Y"" IL_0040: stloc.0 IL_0041: ldloc.0 IL_0042: brfalse.s IL_0054 IL_0044: ldloc.0 IL_0045: callvirt ""S? C.X.get"" IL_004a: stloc.1 IL_004b: ldloca.s V_1 IL_004d: call ""bool S?.HasValue.get"" IL_0052: br.s IL_0055 IL_0054: ldc.i4.0 IL_0055: call ""void System.Console.WriteLine(bool)"" IL_005a: nop IL_005b: ret } "); } [Fact] public void ExtendedPropertyPatterns_Nullability_Properties() { var program = @" #nullable enable class C { C? Prop { get; } public void M() { if (this is { Prop.Prop: null }) { this.Prop.ToString(); this.Prop.Prop.ToString(); // 1 } if (this is { Prop.Prop: {} }) { this.Prop.ToString(); this.Prop.Prop.ToString(); } if (this is { Prop: null } && this is { Prop.Prop: null }) { this.Prop.ToString(); this.Prop.Prop.ToString(); // 2 } if (this is { Prop: null } || this is { Prop.Prop: null }) { this.Prop.ToString(); // 3 this.Prop.Prop.ToString(); // 4 } } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (9,13): warning CS8602: Dereference of a possibly null reference. // this.Prop.Prop.ToString(); // 1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "this.Prop.Prop").WithLocation(9, 13), // (20,13): warning CS8602: Dereference of a possibly null reference. // this.Prop.Prop.ToString(); // 2 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "this.Prop.Prop").WithLocation(20, 13), // (25,13): warning CS8602: Dereference of a possibly null reference. // this.Prop.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "this.Prop").WithLocation(25, 13), // (26,13): warning CS8602: Dereference of a possibly null reference. // this.Prop.Prop.ToString(); // 4 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "this.Prop.Prop").WithLocation(26, 13)); } [Fact] public void ExtendedPropertyPatterns_Nullability_AnnotatedFields() { var program = @" #nullable enable class C { public void M(C1 c1) { if (c1 is { Prop1.Prop2: null }) { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); // 1 } if (c1 is { Prop1.Prop2: {} }) { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); } if (c1 is { Prop1: null } && c1 is { Prop1.Prop2: null }) { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); // 2 } if (c1 is { Prop1: null } || c1 is { Prop1.Prop2: null }) { c1.Prop1.ToString(); // 3 c1.Prop1.Prop2.ToString(); // 4 } } } class C1 { public C2? Prop1 = null; } class C2 { public object? Prop2 = null; } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (8,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.Prop2.ToString(); // 1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(8, 13), // (19,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.Prop2.ToString(); // 2 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(19, 13), // (24,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1").WithLocation(24, 13), // (25,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.Prop2.ToString(); // 4 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(25, 13) ); } [Fact] public void ExtendedPropertyPatterns_Nullability_UnannotatedFields() { var program = @" #nullable enable class C { public void M1(C1 c1) { if (c1 is { Prop1.Prop2: null }) { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); // 1 } else { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); } } public void M2(C1 c1) { if (c1 is { Prop1.Prop2: {} }) { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); } } public void M3(C1 c1) { if (c1 is { Prop1: null } && c1 is { Prop1.Prop2: null }) { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); // 2 } else { c1.Prop1.ToString(); // 3 c1.Prop1.Prop2.ToString(); } } public void M4(C1 c1) { if (c1 is { Prop1: null } || c1 is { Prop1.Prop2: null }) { c1.Prop1.ToString(); // 4 c1.Prop1.Prop2.ToString(); // 5 } else { c1.Prop1.ToString(); c1.Prop1.Prop2.ToString(); } } } class C1 { public C2 Prop1 = null!; } class C2 { public object Prop2 = null!; } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (10,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.Prop2.ToString(); // 1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(10, 13), // (34,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.Prop2.ToString(); // 2 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(34, 13), // (38,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.ToString(); // 3 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1").WithLocation(38, 13), // (48,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.ToString(); // 4 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1").WithLocation(48, 13), // (49,13): warning CS8602: Dereference of a possibly null reference. // c1.Prop1.Prop2.ToString(); // 5 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(49, 13) ); } [Fact] public void ExtendedPropertyPatterns_ExpressionColonInPositionalPattern() { var source = @" class C { C Property { get; set; } void M() { _ = this is (Property.Property: null, Property: null); } public void Deconstruct(out C c1, out C c2) => throw null; } "; var compilation = CreateCompilation(source, parseOptions: TestOptions.Regular9); compilation.VerifyEmitDiagnostics( // (8,22): error CS1001: Identifier expected // _ = this is (Property.Property: null, Property: null); Diagnostic(ErrorCode.ERR_IdentifierExpected, "Property.Property").WithLocation(8, 22), // (8,39): error CS8773: Feature 'extended property patterns' is not available in C# 9.0. Please use language version 10.0 or greater. // _ = this is (Property.Property: null, Property: null); Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, ":").WithArguments("extended property patterns", "10.0").WithLocation(8, 39), // (8,47): error CS8517: The name 'Property' does not match the corresponding 'Deconstruct' parameter 'c2'. // _ = this is (Property.Property: null, Property: null); Diagnostic(ErrorCode.ERR_DeconstructParameterNameMismatch, "Property").WithArguments("Property", "c2").WithLocation(8, 47)); } [Fact] public void ExtendedPropertyPatterns_ExpressionColonInITuplePattern() { var source = @" class C { void M() { System.Runtime.CompilerServices.ITuple t = null; var r = t is (X.Y: 3, Y.Z: 4); } } namespace System.Runtime.CompilerServices { public interface ITuple { int Length { get; } object this[int index] { get; } } } "; var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (7,23): error CS1001: Identifier expected // var r = t is (X.Y: 3, Y.Z: 4); Diagnostic(ErrorCode.ERR_IdentifierExpected, "X.Y").WithLocation(7, 23), // (7,31): error CS1001: Identifier expected // var r = t is (X.Y: 3, Y.Z: 4); Diagnostic(ErrorCode.ERR_IdentifierExpected, "Y.Z").WithLocation(7, 31) ); } [Fact] public void ExtendedPropertyPatterns_ExpressionColonInValueTuplePattern() { var source = @" class C { void M() { _ = (1, 2) is (X.Y: 3, Y.Z: 4); } } namespace System.Runtime.CompilerServices { public interface ITuple { int Length { get; } object this[int index] { get; } } } "; var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (6,24): error CS1001: Identifier expected // _ = (1, 2) is (X.Y: 3, Y.Z: 4); Diagnostic(ErrorCode.ERR_IdentifierExpected, "X.Y").WithLocation(6, 24), // (6,32): error CS1001: Identifier expected // _ = (1, 2) is (X.Y: 3, Y.Z: 4); Diagnostic(ErrorCode.ERR_IdentifierExpected, "Y.Z").WithLocation(6, 32) ); } [Fact] public void ExtendedPropertyPatterns_ObsoleteProperty() { var program = @" using System; class C { public void M1(C1 c1) { _ = c1 is { Prop1.Prop2: null }; } } class C1 { [ObsoleteAttribute(""error Prop1"", true)] public C2 Prop1 { get; set; } } class C2 { [ObsoleteAttribute(""error Prop2"", true)] public object Prop2 { get; set; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (7,21): error CS0619: 'C1.Prop1' is obsolete: 'error Prop1' // _ = c1 is { Prop1.Prop2: null }; Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Prop1").WithArguments("C1.Prop1", "error Prop1").WithLocation(7, 21), // (7,27): error CS0619: 'C2.Prop2' is obsolete: 'error Prop2' // _ = c1 is { Prop1.Prop2: null }; Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Prop2").WithArguments("C2.Prop2", "error Prop2").WithLocation(7, 27) ); } [Fact] public void ExtendedPropertyPatterns_ObsoleteAccessor() { var program = @" using System; class C { public void M1(C1 c1) { _ = c1 is { Prop1.Prop2: null }; } } class C1 { public C2 Prop1 { [ObsoleteAttribute(""error Prop1"", true)] get; set; } } class C2 { public object Prop2 { get; [ObsoleteAttribute(""error Prop2"", true)] set; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (7,21): error CS0619: 'C1.Prop1.get' is obsolete: 'error Prop1' // _ = c1 is { Prop1.Prop2: null }; Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Prop1.Prop2").WithArguments("C1.Prop1.get", "error Prop1").WithLocation(7, 21) ); } [Fact] public void ExtendedPropertyPatterns_InaccessibleProperty() { var program = @" using System; class C { public void M1(C1 c1) { _ = c1 is { Prop1.Prop2: null }; } } class C1 { private C2 Prop1 { get; set; } } class C2 { private object Prop2 { get; set; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (7,21): error CS0122: 'C1.Prop1' is inaccessible due to its protection level // _ = c1 is { Prop1.Prop2: null }; Diagnostic(ErrorCode.ERR_BadAccess, "Prop1").WithArguments("C1.Prop1").WithLocation(7, 21) ); } [Fact] public void ExtendedPropertyPatterns_ExpressionTree() { var program = @" using System; using System.Collections.Generic; using System.Linq.Expressions; class C { public void M1(C1 c1) { Expression<Func<C1, bool>> f = (c1) => c1 is { Prop1.Prop2: null }; } } class C1 { public C2 Prop1 { get; set; } } class C2 { public object Prop2 { get; set; } } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); compilation.VerifyEmitDiagnostics( // (9,48): error CS8122: An expression tree may not contain an 'is' pattern-matching operator. // Expression<Func<C1, bool>> f = (c1) => c1 is { Prop1.Prop2: null }; Diagnostic(ErrorCode.ERR_ExpressionTreeContainsIsMatch, "c1 is { Prop1.Prop2: null }").WithLocation(9, 48) ); } [Fact] public void ExtendedPropertyPatterns_EvaluationOrder() { var program = @" if (new C() is { Prop1.True: true, Prop1.Prop2: not null }) { System.Console.WriteLine(""matched1""); } if (new C() is { Prop1.Prop2: not null, Prop1.True: true }) { System.Console.WriteLine(""matched2""); } if (new C() is { Prop1.Prop2: not null, Prop2.True: true }) { System.Console.WriteLine(""matched3""); } if (new C() is { Prop1.Prop2.Prop3.True: true }) { System.Console.WriteLine(""matched3""); } if (new C() is { Prop1: { Prop2.Prop3.True: true } }) { System.Console.WriteLine(""matched4""); } if (new C() is { Prop1.True: false, Prop1.Prop2: not null }) { throw null; } class C { public C Prop1 { get { System.Console.Write(""Prop1 ""); return this; } } public C Prop2 { get { System.Console.Write(""Prop2 ""); return this; } } public C Prop3 { get { System.Console.Write(""Prop3 ""); return this; } } public bool True { get { System.Console.Write(""True ""); return true; } } } "; CompileAndVerify(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, expectedOutput: @" Prop1 True Prop2 matched1 Prop1 Prop2 True matched2 Prop1 Prop2 Prop2 True matched3 Prop1 Prop2 Prop3 True matched3 Prop1 Prop2 Prop3 True matched4 Prop1 True"); } [Fact] public void ExtendedPropertyPatterns_StaticMembers() { var program = @" _ = new C() is { Static: null }; // 1 _ = new C() is { Instance.Static: null }; // 2 _ = new C() is { Static.Instance: null }; // 3 class C { public C Instance { get; set; } public static C Static { get; set; } } "; var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyEmitDiagnostics( // (2,18): error CS0176: Member 'C.Static' cannot be accessed with an instance reference; qualify it with a type name instead // _ = new C() is { Static: null }; // 1 Diagnostic(ErrorCode.ERR_ObjectProhibited, "Static").WithArguments("C.Static").WithLocation(2, 18), // (3,27): error CS0176: Member 'C.Static' cannot be accessed with an instance reference; qualify it with a type name instead // _ = new C() is { Instance.Static: null }; // 2 Diagnostic(ErrorCode.ERR_ObjectProhibited, "Static").WithArguments("C.Static").WithLocation(3, 27), // (4,18): error CS0176: Member 'C.Static' cannot be accessed with an instance reference; qualify it with a type name instead // _ = new C() is { Static.Instance: null }; // 3 Diagnostic(ErrorCode.ERR_ObjectProhibited, "Static").WithArguments("C.Static").WithLocation(4, 18) ); } [Fact] public void ExtendedPropertyPatterns_Exhaustiveness() { var program = @" _ = new C() switch // 1 { { Prop.True: true } => 0 }; _ = new C() switch { { Prop.True: true } => 0, { Prop.True: false } => 0 }; #nullable enable _ = new C() switch // 2 { { Prop.Prop: null } => 0 }; class C { public C Prop { get => throw null!; } public bool True { get => throw null!; } } "; var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); comp.VerifyEmitDiagnostics( // (2,13): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Prop: { True: false } }' is not covered. // _ = new C() switch // 1 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Prop: { True: false } }").WithLocation(2, 13), // (14,13): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ Prop: { Prop: not null } }' is not covered. // _ = new C() switch // 2 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ Prop: { Prop: not null } }").WithLocation(14, 13) ); } [Fact, WorkItem(55184, "https://github.com/dotnet/roslyn/issues/55184")] public void Repro55184() { var source = @" var x = """"; _ = x is { Error: { Length: > 0 } }; _ = x is { Error.Length: > 0 }; _ = x is { Length: { Error: > 0 } }; _ = x is { Length.Error: > 0 }; "; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (4,12): error CS0117: 'string' does not contain a definition for 'Error' // _ = x is { Error: { Length: > 0 } }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Error").WithArguments("string", "Error").WithLocation(4, 12), // (5,12): error CS0117: 'string' does not contain a definition for 'Error' // _ = x is { Error.Length: > 0 }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Error").WithArguments("string", "Error").WithLocation(5, 12), // (6,22): error CS0117: 'int' does not contain a definition for 'Error' // _ = x is { Length: { Error: > 0 } }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Error").WithArguments("int", "Error").WithLocation(6, 22), // (7,19): error CS0117: 'int' does not contain a definition for 'Error' // _ = x is { Length.Error: > 0 }; Diagnostic(ErrorCode.ERR_NoSuchMember, "Error").WithArguments("int", "Error").WithLocation(7, 19) ); } private const string INumberBaseDefinition = """ namespace System.Numerics; public interface INumberBase<T> where T : INumberBase<T> {} """; [Fact] public void ForbiddenOnTypeParametersConstrainedToINumberBase_01() { var source = """ #pragma warning disable 8321 // Unused local function using System.Numerics; void M<T>(T t) where T : INumberBase<T> { int o = t switch { 1 => 1, // 1 > 1 => 2, // 2 int => 3, // OK [] => 4, // 3 (_) => 5, // OK "" => 6, // OK { } => 7, // OK var x => 8, // OK _ => 9 // Ok }; } """; var comp = CreateCompilation(new[] { source, INumberBaseDefinition }); comp.VerifyDiagnostics( // (8,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // 1 => 1, // 1 Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "1").WithArguments("T").WithLocation(8, 9), // (9,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "> 1").WithArguments("T").WithLocation(9, 9), // (11,9): error CS8985: List patterns may not be used for a value of type 'T'. No suitable 'Length' or 'Count' property was found. // [] => 4, // 3 Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("T").WithLocation(11, 9), // (11,9): error CS0518: Predefined type 'System.Index' is not defined or imported // [] => 4, // 3 Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(11, 9), // (11,9): error CS0021: Cannot apply indexing with [] to an expression of type 'T' // [] => 4, // 3 Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("T").WithLocation(11, 9) ); } [Fact] public void ForbiddenOnTypeParametersConstrainedToINumberBase_02() { var source = """ #pragma warning disable 8321 // Unused local function using System.Numerics; void M<T>(T t) where T : struct, INumberBase<T> { int o = t switch { 1 => 1, // 1 > 1 => 2, // 2 int => 3, // OK [] => 4, // 3 (_) => 5, // OK "" => 6, // 4 { } => 7, // OK var x => 8, // OK _ => 9 // Ok }; } """; var comp = CreateCompilation(new[] { source, INumberBaseDefinition }); comp.VerifyDiagnostics( // (8,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // 1 => 1, // 1 Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "1").WithArguments("T").WithLocation(8, 9), // (9,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "> 1").WithArguments("T").WithLocation(9, 9), // (11,9): error CS8985: List patterns may not be used for a value of type 'T'. No suitable 'Length' or 'Count' property was found. // [] => 4, // 3 Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("T").WithLocation(11, 9), // (11,9): error CS0518: Predefined type 'System.Index' is not defined or imported // [] => 4, // 3 Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(11, 9), // (11,9): error CS0021: Cannot apply indexing with [] to an expression of type 'T' // [] => 4, // 3 Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("T").WithLocation(11, 9), // (13,9): error CS8121: An expression of type 'T' cannot be handled by a pattern of type 'string'. // "" => 6, // 4 Diagnostic(ErrorCode.ERR_PatternWrongType, @"""""").WithArguments("T", "string").WithLocation(13, 9) ); } [Fact] public void ForbiddenOnTypeParametersConstrainedToINumberBase_MultipleReferences_01() { var source = """ #pragma warning disable 8321 // Unused local function using System.Numerics; void M<T>(T t) where T : struct, INumberBase<T> { int o = t switch { 1 => 1, // 1 > 1 => 2, // 2 int => 3, // OK [] => 4, // 3 (_) => 5, // OK "" => 6, // 4 { } => 7, // OK var x => 8, // OK _ => 9 // Ok }; } """; var ref1 = CreateCompilation(INumberBaseDefinition, assemblyName: "A").EmitToImageReference(); var ref2 = CreateCompilation(INumberBaseDefinition, assemblyName: "B").EmitToImageReference(); var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); comp.VerifyDiagnostics( // (4,34): error CS0433: The type 'INumberBase<T>' exists in both 'A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' // void M<T>(T t) where T : struct, INumberBase<T> Diagnostic(ErrorCode.ERR_SameFullNameAggAgg, "INumberBase<T>").WithArguments("A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "System.Numerics.INumberBase<T>", "B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(4, 34), // (11,9): error CS8985: List patterns may not be used for a value of type 'T'. No suitable 'Length' or 'Count' property was found. // [] => 4, // 3 Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("T").WithLocation(11, 9), // (11,9): error CS0518: Predefined type 'System.Index' is not defined or imported // [] => 4, // 3 Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(11, 9), // (11,9): error CS0021: Cannot apply indexing with [] to an expression of type 'T' // [] => 4, // 3 Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("T").WithLocation(11, 9), // (13,9): error CS8121: An expression of type 'T' cannot be handled by a pattern of type 'string'. // "" => 6, // 4 Diagnostic(ErrorCode.ERR_PatternWrongType, @"""""").WithArguments("T", "string").WithLocation(13, 9) ); } [Fact] public void ForbiddenOnTypeParametersConstrainedToINumberBase_MultipleReferences_02() { var source = """ extern alias A; #pragma warning disable 8321 // Unused local function void M<T>(T t) where T : struct, A::System.Numerics.INumberBase<T> { int o = t switch { 1 => 1, // 1 > 1 => 2, // 2 int => 3, // OK [] => 4, // 3 (_) => 5, // OK "" => 6, // 4 { } => 7, // OK var x => 8, // OK _ => 9 // Ok }; } """; var ref1 = CreateCompilation(INumberBaseDefinition, assemblyName: "A").EmitToImageReference(aliases: ImmutableArray.Create("A")); var ref2 = CreateCompilation(INumberBaseDefinition, assemblyName: "B").EmitToImageReference(); var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); comp.VerifyDiagnostics( // (8,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // 1 => 1, // 1 Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "1").WithArguments("T").WithLocation(8, 9), // (9,9): error CS9060: Cannot use a numeric constant or relational pattern on 'T' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "> 1").WithArguments("T").WithLocation(9, 9), // (11,9): error CS8985: List patterns may not be used for a value of type 'T'. No suitable 'Length' or 'Count' property was found. // [] => 4, // 3 Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("T").WithLocation(11, 9), // (11,9): error CS0518: Predefined type 'System.Index' is not defined or imported // [] => 4, // 3 Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(11, 9), // (11,9): error CS0021: Cannot apply indexing with [] to an expression of type 'T' // [] => 4, // 3 Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("T").WithLocation(11, 9), // (13,9): error CS8121: An expression of type 'T' cannot be handled by a pattern of type 'string'. // "" => 6, // 4 Diagnostic(ErrorCode.ERR_PatternWrongType, @"""""").WithArguments("T", "string").WithLocation(13, 9) ); } [Theory] [InlineData("class")] [InlineData("struct")] [InlineData("interface")] public void ForbiddenOnTypesInheritingFromINumberBase(string type) { var source = $$""" #pragma warning disable 8321 // Unused local function using System.Numerics; C c = default(C); int o = c switch { 1 => 1, // 1 > 1 => 2, // 2 int => 3, // 3 [] => 4, // 4 (_) => 5, // OK "" => 6, // 5 { } => 7, // OK var x => 8, // OK _ => 9 // Ok }; {{type}} C : INumberBase<C> { } """; var comp = CreateCompilation(new[] { source, INumberBaseDefinition }); comp.VerifyDiagnostics( // (7,5): error CS0029: Cannot implicitly convert type 'int' to 'C' // 1 => 1, // 1 Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(7, 5), // (8,5): error CS8781: Relational patterns may not be used for a value of type 'C'. // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "> 1").WithArguments("C").WithLocation(8, 5), // (8,7): error CS0029: Cannot implicitly convert type 'int' to 'C' // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(8, 7), // (9,5): error CS8121: An expression of type 'C' cannot be handled by a pattern of type 'int'. // int => 3, // 3 Diagnostic(ErrorCode.ERR_PatternWrongType, "int").WithArguments("C", "int").WithLocation(9, 5), // (10,5): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. // [] => 4, // 4 Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("C").WithLocation(10, 5), // (10,5): error CS0518: Predefined type 'System.Index' is not defined or imported // [] => 4, // 4 Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(10, 5), // (10,5): error CS0021: Cannot apply indexing with [] to an expression of type 'C' // [] => 4, // 4 Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("C").WithLocation(10, 5), // (12,5): error CS0029: Cannot implicitly convert type 'string' to 'C' // "" => 6, // 5 Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""""").WithArguments("string", "C").WithLocation(12, 5) ); } [Theory] [InlineData("class")] [InlineData("struct")] [InlineData("interface")] public void ForbiddenOnTypesInheritingFromINumberBase_MultipleReferences01(string type) { var source = $$""" #pragma warning disable 8321 // Unused local function using System.Numerics; C c = default(C); int o = c switch { 1 => 1, // 1 > 1 => 2, // 2 int => 3, // 3 [] => 4, // 4 (_) => 5, // OK "" => 6, // 5 { } => 7, // OK var x => 8, // OK _ => 9 // Ok }; {{type}} C : INumberBase<C> { } """; var ref1 = CreateCompilation(INumberBaseDefinition, assemblyName: "A").EmitToImageReference(); var ref2 = CreateCompilation(INumberBaseDefinition, assemblyName: "B").EmitToImageReference(); var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); comp.VerifyDiagnostics( // (7,5): error CS0029: Cannot implicitly convert type 'int' to 'C' // 1 => 1, // 1 Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(7, 5), // (8,5): error CS8781: Relational patterns may not be used for a value of type 'C'. // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "> 1").WithArguments("C").WithLocation(8, 5), // (8,7): error CS0029: Cannot implicitly convert type 'int' to 'C' // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(8, 7), // (9,5): error CS8121: An expression of type 'C' cannot be handled by a pattern of type 'int'. // int => 3, // 3 Diagnostic(ErrorCode.ERR_PatternWrongType, "int").WithArguments("C", "int").WithLocation(9, 5), // (10,5): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. // [] => 4, // 4 Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("C").WithLocation(10, 5), // (10,5): error CS0518: Predefined type 'System.Index' is not defined or imported // [] => 4, // 4 Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(10, 5), // (10,5): error CS0021: Cannot apply indexing with [] to an expression of type 'C' // [] => 4, // 4 Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("C").WithLocation(10, 5), // (12,5): error CS0029: Cannot implicitly convert type 'string' to 'C' // "" => 6, // 5 Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""""").WithArguments("string", "C").WithLocation(12, 5), // (19,5): error CS0433: The type 'INumberBase<T>' exists in both 'A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' // INumberBase<C> Diagnostic(ErrorCode.ERR_SameFullNameAggAgg, "INumberBase<C>").WithArguments("A, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "System.Numerics.INumberBase<T>", "B, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(19, 5) ); } [Theory] [InlineData("class")] [InlineData("struct")] [InlineData("interface")] public void ForbiddenOnTypeParametersInheritingFromINumberBase_MultipleReferences02(string type) { var source = $$""" extern alias A; #pragma warning disable 8321 // Unused local function C c = default(C); int o = c switch { 1 => 1, // 1 > 1 => 2, // 2 int => 3, // 3 [] => 4, // 4 (_) => 5, // OK "" => 6, // 5 { } => 7, // OK var x => 8, // OK _ => 9 // Ok }; {{type}} C : A::System.Numerics.INumberBase<C> { } """; var ref1 = CreateCompilation(INumberBaseDefinition).EmitToImageReference(aliases: ImmutableArray.Create("A")); var ref2 = CreateCompilation(INumberBaseDefinition).EmitToImageReference(); var comp = CreateCompilation(new[] { source }, references: new[] { ref1, ref2 }); comp.VerifyDiagnostics( // (7,5): error CS0029: Cannot implicitly convert type 'int' to 'C' // 1 => 1, // 1 Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(7, 5), // (8,5): error CS8781: Relational patterns may not be used for a value of type 'C'. // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "> 1").WithArguments("C").WithLocation(8, 5), // (8,7): error CS0029: Cannot implicitly convert type 'int' to 'C' // > 1 => 2, // 2 Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", "C").WithLocation(8, 7), // (9,5): error CS8121: An expression of type 'C' cannot be handled by a pattern of type 'int'. // int => 3, // 3 Diagnostic(ErrorCode.ERR_PatternWrongType, "int").WithArguments("C", "int").WithLocation(9, 5), // (10,5): error CS8985: List patterns may not be used for a value of type 'C'. No suitable 'Length' or 'Count' property was found. // [] => 4, // 4 Diagnostic(ErrorCode.ERR_ListPatternRequiresLength, "[]").WithArguments("C").WithLocation(10, 5), // (10,5): error CS0518: Predefined type 'System.Index' is not defined or imported // [] => 4, // 4 Diagnostic(ErrorCode.ERR_PredefinedTypeNotFound, "[]").WithArguments("System.Index").WithLocation(10, 5), // (10,5): error CS0021: Cannot apply indexing with [] to an expression of type 'C' // [] => 4, // 4 Diagnostic(ErrorCode.ERR_BadIndexLHS, "[]").WithArguments("C").WithLocation(10, 5), // (12,5): error CS0029: Cannot implicitly convert type 'string' to 'C' // "" => 6, // 5 Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""""").WithArguments("string", "C").WithLocation(12, 5) ); } private const string INumberBaseBCL = """ namespace System { using System.Numerics; public class Object {} public class Void {} public class ValueType {} public class String {} public class Enum {} public struct Nullable<T> where T : struct {} public struct Byte : INumberBase<Byte> {} public struct SByte : INumberBase<SByte> {} public struct Int16 : INumberBase<Int16> {} public struct Char : INumberBase<Char> {} public struct UInt16 : INumberBase<UInt16> {} public struct Int32 : INumberBase<Int32> {} public struct UInt32 : INumberBase<UInt32> {} public struct Int64 : INumberBase<Int64> {} public struct UInt64 : INumberBase<UInt64> {} public struct Single : INumberBase<Single> {} public struct Double : INumberBase<Double> {} public struct Decimal : INumberBase<Decimal> { public Decimal(int value) {} } public struct IntPtr : INumberBase<IntPtr> {} public struct UIntPtr : INumberBase<UIntPtr> {} } """; [Theory] [InlineData("byte")] [InlineData("sbyte")] [InlineData("short")] [InlineData("ushort")] [InlineData("int")] [InlineData("uint")] [InlineData("nint")] [InlineData("nuint")] [InlineData("long")] [InlineData("ulong")] [InlineData("float")] [InlineData("double")] [InlineData("decimal")] public void MatchingOnConstantConversionsWithINumberBaseIsAllowed(string inputType) { var source = $$""" {{inputType}} i = 1; _ = i switch { 1 => 1, > 1 => 2, _ => 3 }; """; var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); comp.VerifyDiagnostics(); } [Theory] [InlineData("byte")] [InlineData("sbyte")] [InlineData("short")] [InlineData("ushort")] [InlineData("int")] [InlineData("uint")] [InlineData("nint")] [InlineData("nuint")] [InlineData("long")] [InlineData("ulong")] [InlineData("float")] [InlineData("double")] [InlineData("decimal")] public void MatchingOnConstantConversionsWithINumberBaseIsAllowed_Nullable(string inputType) { var source = $$""" {{inputType}}? i = 1; _ = i switch { 1 => 1, > 1 => 2, _ => 3 }; """; var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); comp.VerifyDiagnostics(); } [Fact] public void MatchingOnConstantConversionsWithINumberBaseIsDisallowed_TypePatternToINumberBaseInt() { var source = """ using System.Numerics; int i = 1; _ = ((INumberBase<int>)i) switch { 1 => 1, > 1 => 2, _ => 3 }; """; var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); comp.VerifyDiagnostics( // (5,5): error CS9060: Cannot use a numeric constant or relational pattern on 'INumberBase<int>' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // 1 => 1, Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "1").WithArguments("System.Numerics.INumberBase<int>").WithLocation(5, 5), // (6,5): error CS9060: Cannot use a numeric constant or relational pattern on 'INumberBase<int>' because it inherits from or extends 'INumberBase<T>'. Consider using a type pattern to narrow to a specific numeric type. // > 1 => 2, Diagnostic(ErrorCode.ERR_CannotMatchOnINumberBase, "> 1").WithArguments("System.Numerics.INumberBase<int>").WithLocation(6, 5) ); } [Theory] [InlineData("byte")] [InlineData("sbyte")] [InlineData("short")] [InlineData("ushort")] [InlineData("uint")] [InlineData("nint")] [InlineData("nuint")] [InlineData("long")] [InlineData("ulong")] [InlineData("float")] [InlineData("double")] [InlineData("decimal")] public void MatchingOnConstantConversionsWithINumberBaseIsAllowed_TypePatternToINumberBaseT(string inputType) { var source = $$""" using System.Numerics; {{inputType}} i = 1; _ = ((INumberBase<{{inputType}}>)i) switch { 1 => 1, > 1 => 2, _ => 3 }; """; var comp = CreateEmptyCompilation(new[] { source, INumberBaseBCL, INumberBaseDefinition }); comp.VerifyDiagnostics( // (5,5): error CS0029: Cannot implicitly convert type 'int' to 'System.Numerics.INumberBase<nint>' // 1 => 1, Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", $"System.Numerics.INumberBase<{inputType}>").WithLocation(5, 5), // (6,5): error CS8781: Relational patterns may not be used for a value of type 'System.Numerics.INumberBase<nint>'. // > 1 => 2, Diagnostic(ErrorCode.ERR_UnsupportedTypeForRelationalPattern, "> 1").WithArguments($"System.Numerics.INumberBase<{inputType}>").WithLocation(6, 5), // (6,7): error CS0029: Cannot implicitly convert type 'int' to 'System.Numerics.INumberBase<nint>' // > 1 => 2, Diagnostic(ErrorCode.ERR_NoImplicitConv, "1").WithArguments("int", $"System.Numerics.INumberBase<{inputType}>").WithLocation(6, 7) ); } [Fact] public void MatchingOnINumberBaseIsAllowed_ClassNotInterface() { var source = """ #pragma warning disable 8321 // Unused local function using System.Numerics; void M<T>(T t) where T : INumberBase<T> { int o = t switch { 1 => 1, > 1 => 2, _ => 3 }; } namespace System.Numerics { public class INumberBase<T> where T : INumberBase<T> { } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics(); } [Fact] public void MatchingOnINumberBaseIsAllowed_WrongArity() { var source = """ #pragma warning disable 8321 // Unused local function using System.Numerics; void M1<T1, T2>(T1 t) where T1 : INumberBase<T1, T2> { int o = t switch { 1 => 1, > 1 => 2, _ => 3 }; } void M2<T>(T t) where T : INumberBase { int o = t switch { 1 => 1, > 1 => 2, _ => 3 }; } namespace System.Numerics { public interface INumberBase<T1, T2> where T1 : INumberBase<T1, T2> { } public interface INumberBase { } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics(); } [Fact] public void MatchingOnINumberBaseIsAllowed_WrongNamespace() { var source = """ #pragma warning disable 8321 // Unused local function using System; void M<T>(T t) where T : INumberBase<T> { int o = t switch { 1 => 1, > 1 => 2, _ => 3 }; } namespace System { public interface INumberBase<T> where T : INumberBase<T> { } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics(); } [Fact] public void MatchingOnINumberBaseIsAllowed_NestedType() { var source = """ #pragma warning disable 8321 // Unused local function using static System.Numerics.Outer; void M<T>(T t) where T : INumberBase<T> { int o = t switch { 1 => 1, > 1 => 2, _ => 3 }; } namespace System.Numerics { public interface Outer { public interface INumberBase<T> where T : INumberBase<T> { } } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics(); } [Fact, WorkItem(64399, "https://github.com/dotnet/roslyn/issues/64399")] public void ShortestPathToDefaultNodeYieldsNoRemainingValues() { var source = """ public enum Enum { Zero = 0, One = 1, Two = 2, } class N { private static int M(Enum e1, Enum e2) { return (e1, e2) switch { (Enum.Two, _) => 0, (_, Enum.Two) => 0, (Enum.Zero, Enum.Zero) => 0, (Enum.Zero, Enum.One) => 0, (Enum.One, Enum.Zero) => 0, ( < 0 or > Enum.Two, _) => 0, (_, < 0 or > Enum.Two) => 0, }; } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (12,25): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One)' is not covered. // return (e1, e2) switch Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One)").WithLocation(12, 25) ); } [Fact, WorkItem(64399, "https://github.com/dotnet/roslyn/issues/64399")] public void ShortestPathToDefaultNodeYieldsNoRemainingValues_RequiringFalseWhenClause() { var source = """ public enum Enum { Zero = 0, One = 1, Two = 2, } class N { private static int M(Enum e1, Enum e2, bool b) { return (e1, e2) switch { (Enum.One, Enum.One) when b => 0, (Enum.Two, _) => 0, (_, Enum.Two) => 0, (Enum.Zero, Enum.Zero) => 0, (Enum.Zero, Enum.One) => 0, (Enum.One, Enum.Zero) => 0, ( < 0 or > Enum.Two, _) => 0, (_, < 0 or > Enum.Two) => 0, }; } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (12,25): warning CS8846: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One)' is not covered. However, a pattern with a 'when' clause might successfully match this value. // return (e1, e2) switch Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithWhen, "switch").WithArguments("(Enum.One, Enum.One)").WithLocation(12, 25) ); } [Fact, WorkItem(64399, "https://github.com/dotnet/roslyn/issues/64399")] public void ShortestPathToDefaultNodeYieldsNoRemainingValues_NullBranch() { var source = """ public enum Enum { Zero = 0, One = 1, Two = 2, } class N { private static int M(Enum e1, Enum e2, object o) { return (e1, e2, o) switch { (Enum.One, Enum.One, _) => 0, (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, string s) => 0, }; } } """; var comp = CreateCompilation(source); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t1 = t0.e1; [1] [1]: t1 == 1 ? [2] : [8] [2]: t2 = t0.e2; [3] [3]: t2 == 1 ? [4] : [5] [4]: leaf <arm> `(Enum.One, Enum.One, _) => 0` [5]: t2 == 2 ? [12] : [6] [6]: t2 == 0 ? [7] : [18] [7]: leaf <arm> `(Enum.One, Enum.Zero, _) => 0` [8]: t1 == 2 ? [9] : [10] [9]: leaf <arm> `(Enum.Two, _, _) => 0` [10]: t2 = t0.e2; [11] [11]: t2 == 2 ? [12] : [13] [12]: leaf <arm> `(_, Enum.Two, _) => 0` [13]: t1 == 0 ? [14] : [24] [14]: t2 == 0 ? [15] : [16] [15]: leaf <arm> `(Enum.Zero, Enum.Zero, _) => 0` [16]: t2 == 1 ? [17] : [18] [17]: leaf <arm> `(Enum.Zero, Enum.One, _) => 0` [18]: t3 = t0.o; [19] [19]: t3 is string ? [20] : [23] [20]: t4 = (string)t3; [21] [21]: when <true> ? [22] : <unreachable> [22]: leaf <arm> `(_, < 0 or > Enum.Two, string s) => 0` [23]: leaf <default> `(e1, e2, o) switch { (Enum.One, Enum.One, _) => 0, (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, string s) => 0, }` [24]: leaf <arm> `( < 0 or > Enum.Two, _, _) => 0` "); comp.VerifyDiagnostics( // (12,28): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Enum.One, (Enum)3, _)' is not covered. // return (e1, e2, o) switch Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(Enum.One, (Enum)3, _)").WithLocation(12, 28) ); } [Fact, WorkItem(64399, "https://github.com/dotnet/roslyn/issues/64399")] public void ShortestPathToDefaultNodeYieldsNoRemainingValues_Nullability() { var source = """ #nullable enable public enum Enum { Zero = 0, One = 1, Two = 2, } class N { private static int M(Enum e1, Enum e2, string s) { return (e1, e2, s) switch { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, string) => 0, }; } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics(); } [Fact, WorkItem(64399, "https://github.com/dotnet/roslyn/issues/64399")] public void ShortestPathToDefaultNodeYieldsNoRemainingValues_Nullability_NullableString() { var source = """ #nullable enable public enum Enum { Zero = 0, One = 1, Two = 2, } class N { private static int M1(Enum e1, Enum e2, string? s) { return (e1, e2, s) switch // 1 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, null) => 0, (_, _, null) => 1, }; } private static int M2(Enum e1, Enum e2, string? s) { return (e1, e2, s) switch // 2 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, "") => 1, }; } private static int M3(Enum e1, Enum e2, string? s) { return (e1, e2, s) switch // 3 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, string) => 1, }; } private static int M4(Enum e1, Enum e2, string? s) { return (e1, e2, s) switch // 4 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, null) => 1, }; } private static int M5(Enum e1, Enum e2, string? s) { return (e1, e2, s) switch // 5 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, "") => 1, }; } private static int M6(Enum e1, Enum e2, string? s) { return (e1, e2, s) switch // 6 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, string) => 1, }; } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (14,28): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Enum.Zero, (Enum)-1, not null)' is not covered. // return (e1, e2, s) switch // 1 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(Enum.Zero, (Enum)-1, not null)").WithLocation(14, 28), // (29,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, "A")' is not covered. // return (e1, e2, s) switch // 2 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, \"A\")").WithLocation(29, 28), // (44,28): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, null)' is not covered. // return (e1, e2, s) switch // 3 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(Enum.One, Enum.One, null)").WithLocation(44, 28), // (59,28): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Enum.Zero, (Enum)-1, not null)' is not covered. // return (e1, e2, s) switch // 4 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(Enum.Zero, (Enum)-1, not null)").WithLocation(59, 28), // (73,28): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Enum.Zero, (Enum)-1, "A")' is not covered. // return (e1, e2, s) switch // 5 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(Enum.Zero, (Enum)-1, \"A\")").WithLocation(73, 28), // (87,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, _)' is not covered. // return (e1, e2, s) switch // 6 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, _)").WithLocation(87, 28) ); } [Theory, CombinatorialData, WorkItem(64399, "https://github.com/dotnet/roslyn/issues/64399")] public void ShortestPathToDefaultNodeYieldsNoRemainingValues_Nullability_Deconstruction(bool nullableEnable) { var source = """ public enum Enum { Zero = 0, One = 1, Two = 2, } class N { private int M1() { return this switch // 1 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, not null) => 1, }; } private int M2() { return this switch // 2 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, true) => 1, (_, _, false) => 1, }; } private int M3() { return this switch // 3 { null => 1, (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, true) => 1, (_, _, false) => 1, }; } private int M4() { return this switch // 4 { null => 1, (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, true) => 1, }; } private int M5() { return this switch // 5 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, not null) => 1, }; } private int M6() { return this switch // 6 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, true) => 1, (_, < 0 or > Enum.Two, false) => 1, }; } private int M7() { return this switch // 7 { null => 1, (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, true) => 1, (_, < 0 or > Enum.Two, false) => 1, }; } private int M8() { return this switch // 8 { null => 1, (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, true) => 1, }; } void Deconstruct(out Enum e1, out Enum e2, out bool? s) => throw null!; } """; var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithNullableContextOptions(nullableEnable ? NullableContextOptions.Enable : NullableContextOptions.Disable)); if (nullableEnable) { comp.VerifyDiagnostics( // (12,21): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, null)' is not covered. // return this switch // 1 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(Enum.One, Enum.One, null)").WithLocation(12, 21), // (27,21): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, null)' is not covered. // return this switch // 2 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(Enum.One, Enum.One, null)").WithLocation(27, 21), // (43,21): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, null)' is not covered. // return this switch // 3 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(Enum.One, Enum.One, null)").WithLocation(43, 21), // (60,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, false)' is not covered. // return this switch // 4 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, false)").WithLocation(60, 21), // (76,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, _)' is not covered. // return this switch // 5 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, _)").WithLocation(76, 21), // (90,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, _)' is not covered. // return this switch // 6 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, _)").WithLocation(90, 21), // (105,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, _)' is not covered. // return this switch // 7 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, _)").WithLocation(105, 21), // (121,21): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Enum.Zero, (Enum)-1, false)' is not covered. // return this switch // 8 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(Enum.Zero, (Enum)-1, false)").WithLocation(121, 21) ); } else { comp.VerifyDiagnostics( // (60,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, false)' is not covered. // return this switch // 4 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, false)").WithLocation(60, 21), // (76,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, _)' is not covered. // return this switch // 5 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, _)").WithLocation(76, 21), // (90,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, _)' is not covered. // return this switch // 6 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, _)").WithLocation(90, 21), // (105,21): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, _)' is not covered. // return this switch // 7 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, _)").WithLocation(105, 21), // (121,21): warning CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern '(Enum.Zero, (Enum)-1, false)' is not covered. // return this switch // 8 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(Enum.Zero, (Enum)-1, false)").WithLocation(121, 21) ); } } [Theory, CombinatorialData, WorkItem(64399, "https://github.com/dotnet/roslyn/issues/64399")] public void ShortestPathToDefaultNodeYieldsNoRemainingValues_Nullability_NullableBool(bool nullableEnable) { var source = """ public enum Enum { Zero = 0, One = 1, Two = 2, } class N { private static int M1(Enum e1, Enum e2, bool? i) { return (e1, e2, i) switch // 1 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, true) => 1, }; } private static int M2(Enum e1, Enum e2, bool? i) { return (e1, e2, i) switch // 2 { (Enum.Two, _, _) => 0, (_, Enum.Two, _) => 0, (Enum.Zero, Enum.Zero, _) => 0, (Enum.Zero, Enum.One, _) => 0, (Enum.One, Enum.Zero, _) => 0, ( < 0 or > Enum.Two, _, _) => 0, (_, < 0 or > Enum.Two, _) => 0, (_, _, true) => 1, (_, _, false) => 1, }; } } """; var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithNullableContextOptions(nullableEnable ? NullableContextOptions.Enable : NullableContextOptions.Disable)); if (nullableEnable) { comp.VerifyDiagnostics( // (12,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, false)' is not covered. // return (e1, e2, i) switch // 1 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, false)").WithLocation(12, 28), // (27,28): warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, null)' is not covered. // return (e1, e2, i) switch // 2 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("(Enum.One, Enum.One, null)").WithLocation(27, 28) ); } else { comp.VerifyDiagnostics( // (12,28): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '(Enum.One, Enum.One, false)' is not covered. // return (e1, e2, i) switch // 1 Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("(Enum.One, Enum.One, false)").WithLocation(12, 28) ); } } [Fact, WorkItem(45679, "https://github.com/dotnet/roslyn/issues/45679")] public void IsExpression_SwitchDispatch_Numeric() { var source = """ class C { public static void Main() { System.Console.Write( Test(0) == false & Test(1) & Test(2) & Test(3) & Test(4) & Test(5) & Test(6) & Test(7) & Test(8) ); } public static bool Test(int a) { return (a is 1 or 2 or 3 or 4 or 5 or 6 or 7 or 8); } } """; var compilation = CompileAndVerify(source, expectedOutput: "True"); compilation.VerifyIL("C.Test", """ { // Code size 14 (0xe) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.1 IL_0002: sub IL_0003: ldc.i4.7 IL_0004: bgt.un.s IL_000a IL_0006: ldc.i4.1 IL_0007: stloc.0 IL_0008: br.s IL_000c IL_000a: ldc.i4.0 IL_000b: stloc.0 IL_000c: ldloc.0 IL_000d: ret } """); } [Fact, WorkItem(45679, "https://github.com/dotnet/roslyn/issues/45679")] public void IsExpression_SwitchDispatch_SwitchIL() { var source = """ class C { public static void Main() { System.Console.Write( Test(1, null) == false & Test(1, default(int)) & Test(2, default(bool)) & Test(3, default(double)) & Test(4, default(long)) & Test(5, default(long)) == false ); } public static bool Test(int a, object b) { return (a, b) is (1, int) or (2, bool) or (3, double) or (4, long); } } """; var compilation = CompileAndVerify(source, expectedOutput: "True"); compilation.VerifyIL("C.Test", """ { // Code size 72 (0x48) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.1 IL_0002: sub IL_0003: switch ( IL_001a, IL_0024, IL_002e, IL_0038) IL_0018: br.s IL_0044 IL_001a: ldarg.1 IL_001b: isinst "int" IL_0020: brtrue.s IL_0040 IL_0022: br.s IL_0044 IL_0024: ldarg.1 IL_0025: isinst "bool" IL_002a: brtrue.s IL_0040 IL_002c: br.s IL_0044 IL_002e: ldarg.1 IL_002f: isinst "double" IL_0034: brtrue.s IL_0040 IL_0036: br.s IL_0044 IL_0038: ldarg.1 IL_0039: isinst "long" IL_003e: brfalse.s IL_0044 IL_0040: ldc.i4.1 IL_0041: stloc.0 IL_0042: br.s IL_0046 IL_0044: ldc.i4.0 IL_0045: stloc.0 IL_0046: ldloc.0 IL_0047: ret } """); } [Fact, WorkItem(45679, "https://github.com/dotnet/roslyn/issues/45679")] public void IsExpression_SwitchDispatch_String() { var source = """ class C { public static void Main() { System.Console.Write( Test("0") == false & Test("1") & Test("2") & Test("3") & Test("4") & Test("5") & Test("6") & Test("7") & Test("8") ); } public static bool Test(string a) { return (a is "1" or "2" or "3" or "4" or "5" or "6" or "7" or "8"); } } """; var compilation = CompileAndVerify(source, expectedOutput: "True"); compilation.VerifyIL("C.Test", """ { // Code size 73 (0x49) .maxstack 2 .locals init (bool V_0, int V_1, char V_2) IL_0000: ldarg.0 IL_0001: brfalse.s IL_0045 IL_0003: ldarg.0 IL_0004: call "int string.Length.get" IL_0009: stloc.1 IL_000a: ldloc.1 IL_000b: ldc.i4.1 IL_000c: bne.un.s IL_0045 IL_000e: ldarg.0 IL_000f: ldc.i4.0 IL_0010: call "char string.this[int].get" IL_0015: stloc.2 IL_0016: ldloc.2 IL_0017: ldc.i4.s 49 IL_0019: sub IL_001a: switch ( IL_0041, IL_0041, IL_0041, IL_0041, IL_0041, IL_0041, IL_0041, IL_0041) IL_003f: br.s IL_0045 IL_0041: ldc.i4.1 IL_0042: stloc.0 IL_0043: br.s IL_0047 IL_0045: ldc.i4.0 IL_0046: stloc.0 IL_0047: ldloc.0 IL_0048: ret } """); } [Theory] [InlineData("object", "new {}")] [InlineData("dynamic", "new {}")] [InlineData("System.ValueType", "((int)x0)/2")] public void ConstantsExpectedInPatternExpression(string type, string expression) { var source = @$" class Outer {{ bool M0({type} x0) {{ return x0 is {expression}; }} }} "; CreateCompilation(source).VerifyDiagnostics( // (6,22): error CS0150: A constant value is expected // return x0 is {expression}; Diagnostic(ErrorCode.ERR_ConstantExpected, expression).WithLocation(6, 22) ); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_01() { var src = @" interface I1 { int F {get;} } class C11; class C12; class C13(int f) : C12, I1 { public int F { get { System.Console.Write(""Evaluated C13.F ""); return f; } } } class C14(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C14.F ""); return f; } } } class C15(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class Program { static void Main() { object[] s = [null, new C11(), new C12(), new C13(1), new C14(1), new C15(1), new C13(2), new C14(2), new C15(2), new C13(3), new C14(3), new C15(3)]; int[] i = [1, 2, 3]; foreach (var s1 in s) { foreach (var j in i) { var t = Test1((s1, j)); System.Console.WriteLine(t); } } } static bool Test1((object, int) u) { return u is (C12 and I1 and { F: 1 }, 2) or (I1 and { F: 1 or 2 }, 1); } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, @"[0]: t1 = t0.Item1; [1] [1]: t1 is C12 ? [2] : [9] [2]: t2 = (C12)t1; [3] [3]: t2 is I1 ? [4] : [17] [4]: t3 = (I1)t2; [5] [5]: t4 = t3.F; [6] [6]: t4 == 1 ? [7] : [13] [7]: t5 = t0.Item2; [8] [8]: t5 == 2 ? [16] : [15] [9]: t1 is I1 ? [10] : [17] [10]: t3 = (I1)t1; [11] [11]: t4 = t3.F; [12] [12]: t4 == 1 ? [14] : [13] [13]: t4 == 2 ? [14] : [17] [14]: t5 = t0.Item2; [15] [15]: t5 == 1 ? [16] : [17] [16]: leaf <isPatternSuccess> `(C12 and I1 and { F: 1 }, 2) or (I1 and { F: 1 or 2 }, 1)` [17]: leaf <isPatternFailure> `(C12 and I1 and { F: 1 }, 2) or (I1 and { F: 1 or 2 }, 1)` "); var verifier = CompileAndVerify( comp, expectedOutput: @" False False False False False False False False False Evaluated C13.F True Evaluated C13.F True Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C13.F True Evaluated C13.F False Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C13.F False Evaluated C13.F False Evaluated C13.F False Evaluated C14.F False Evaluated C14.F False Evaluated C14.F False Evaluated C15.F False Evaluated C15.F False Evaluated C15.F False ").VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { // Code size 102 (0x66) .maxstack 2 .locals init (object V_0, C12 V_1, I1 V_2, int V_3, int V_4, bool V_5) IL_0000: ldarg.0 IL_0001: ldfld ""object System.ValueTuple<object, int>.Item1"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: isinst ""C12"" IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: brfalse.s IL_0035 IL_0011: ldloc.1 IL_0012: isinst ""I1"" IL_0017: stloc.2 IL_0018: ldloc.2 IL_0019: brfalse.s IL_0060 IL_001b: ldloc.2 IL_001c: callvirt ""int I1.F.get"" IL_0021: stloc.3 IL_0022: ldloc.3 IL_0023: ldc.i4.1 IL_0024: bne.un.s IL_004a IL_0026: ldarg.0 IL_0027: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_002c: stloc.s V_4 IL_002e: ldloc.s V_4 IL_0030: ldc.i4.2 IL_0031: beq.s IL_005b IL_0033: br.s IL_0056 IL_0035: ldloc.0 IL_0036: isinst ""I1"" IL_003b: stloc.2 IL_003c: ldloc.2 IL_003d: brfalse.s IL_0060 IL_003f: ldloc.2 IL_0040: callvirt ""int I1.F.get"" IL_0045: stloc.3 IL_0046: ldloc.3 IL_0047: ldc.i4.1 IL_0048: beq.s IL_004e IL_004a: ldloc.3 IL_004b: ldc.i4.2 IL_004c: bne.un.s IL_0060 IL_004e: ldarg.0 IL_004f: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0054: stloc.s V_4 IL_0056: ldloc.s V_4 IL_0058: ldc.i4.1 IL_0059: bne.un.s IL_0060 IL_005b: ldc.i4.1 IL_005c: stloc.s V_5 IL_005e: br.s IL_0063 IL_0060: ldc.i4.0 IL_0061: stloc.s V_5 IL_0063: ldloc.s V_5 IL_0065: ret } "); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_02() { var src = @" interface I1 { int F {get;} } class C11; class C12; class C13(int f) : C12, I1 { public int F { get { System.Console.Write(""Evaluated C13.F ""); return f; } } } class C14(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C14.F ""); return f; } } } class C15(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class Program { static void Main() { object[] s = [null, new C11(), new C12(), new C13(1), new C14(1), new C15(1), new C13(2), new C14(2), new C15(2), new C13(3), new C14(3), new C15(3)]; int[] i = [1, 2, 3]; foreach (var s1 in s) { foreach (var j in i) { var t = Test1((s1, j)); System.Console.WriteLine(t); } } } static bool Test1((object, int) u) { return u is (I1 and { F: 1 or 2 }, 1) or (C12 and I1 and { F: 1 }, 2); } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, @"[0]: t1 = t0.Item1; [1] [1]: t1 is I1 ? [2] : [13] [2]: t2 = (I1)t1; [3] [3]: t3 = t2.F; [4] [4]: t3 == 1 ? [5] : [9] [5]: t4 = t0.Item2; [6] [6]: t4 == 1 ? [12] : [7] [7]: t1 is C12 ? [8] : [13] [8]: t4 == 2 ? [12] : [13] [9]: t3 == 2 ? [10] : [13] [10]: t4 = t0.Item2; [11] [11]: t4 == 1 ? [12] : [13] [12]: leaf <isPatternSuccess> `(I1 and { F: 1 or 2 }, 1) or (C12 and I1 and { F: 1 }, 2)` [13]: leaf <isPatternFailure> `(I1 and { F: 1 or 2 }, 1) or (C12 and I1 and { F: 1 }, 2)` "); var verifier = CompileAndVerify( comp, expectedOutput: @" False False False False False False False False False Evaluated C13.F True Evaluated C13.F True Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C13.F True Evaluated C13.F False Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C13.F False Evaluated C13.F False Evaluated C13.F False Evaluated C14.F False Evaluated C14.F False Evaluated C14.F False Evaluated C15.F False Evaluated C15.F False Evaluated C15.F False ").VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { // Code size 81 (0x51) .maxstack 2 .locals init (object V_0, I1 V_1, int V_2, int V_3, bool V_4) IL_0000: ldarg.0 IL_0001: ldfld ""object System.ValueTuple<object, int>.Item1"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: isinst ""I1"" IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: brfalse.s IL_004b IL_0011: ldloc.1 IL_0012: callvirt ""int I1.F.get"" IL_0017: stloc.2 IL_0018: ldloc.2 IL_0019: ldc.i4.1 IL_001a: beq.s IL_0022 IL_001c: ldloc.2 IL_001d: ldc.i4.2 IL_001e: beq.s IL_003b IL_0020: br.s IL_004b IL_0022: ldarg.0 IL_0023: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0028: stloc.3 IL_0029: ldloc.3 IL_002a: ldc.i4.1 IL_002b: beq.s IL_0046 IL_002d: ldloc.0 IL_002e: isinst ""C12"" IL_0033: brfalse.s IL_004b IL_0035: ldloc.3 IL_0036: ldc.i4.2 IL_0037: beq.s IL_0046 IL_0039: br.s IL_004b IL_003b: ldarg.0 IL_003c: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0041: stloc.3 IL_0042: ldloc.3 IL_0043: ldc.i4.1 IL_0044: bne.un.s IL_004b IL_0046: ldc.i4.1 IL_0047: stloc.s V_4 IL_0049: br.s IL_004e IL_004b: ldc.i4.0 IL_004c: stloc.s V_4 IL_004e: ldloc.s V_4 IL_0050: ret } "); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_03() { var src = @" interface I1 { int F {get;} } interface I2 : I1 { } class C11; class C12; class C13(int f) : C12, I1 { public int F { get { System.Console.Write(""Evaluated C13.F ""); return f; } } } class C14(int f) : I2 { public int F { get { System.Console.Write(""Evaluated C14.F ""); return f; } } } class C15(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class C16(int f) : C12, I2 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class Program { static void Main() { object[] s = [null, new C11(), new C12(), new C13(1), new C14(1), new C15(1), new C16(1), new C13(2), new C14(2), new C15(2), new C16(2), new C13(3), new C14(3), new C15(3), new C16(3)]; int[] i = [1, 2, 3]; foreach (var s1 in s) { foreach (var j in i) { var t = Test1((s1, j)); System.Console.WriteLine(t); } } } static bool Test1((object, int) u) { return u is (C12 and I1 and { F: 1 }, 2) or (I2 and { F: 1 or 2 }, 1); } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, @"[0]: t1 = t0.Item1; [1] [1]: t1 is C12 ? [2] : [12] [2]: t2 = (C12)t1; [3] [3]: t2 is I1 ? [4] : [20] [4]: t3 = (I1)t2; [5] [5]: t4 = t3.F; [6] [6]: t4 == 1 ? [7] : [10] [7]: t5 = t0.Item2; [8] [8]: t5 == 2 ? [19] : [9] [9]: t1 is I2 ? [18] : [20] [10]: t1 is I2 ? [11] : [20] [11]: t4 == 2 ? [17] : [20] [12]: t1 is I2 ? [13] : [20] [13]: t6 = (I2)t1; [14] [14]: t7 = t6.F; [15] [15]: t7 == 1 ? [17] : [16] [16]: t7 == 2 ? [17] : [20] [17]: t5 = t0.Item2; [18] [18]: t5 == 1 ? [19] : [20] [19]: leaf <isPatternSuccess> `(C12 and I1 and { F: 1 }, 2) or (I2 and { F: 1 or 2 }, 1)` [20]: leaf <isPatternFailure> `(C12 and I1 and { F: 1 }, 2) or (I2 and { F: 1 or 2 }, 1)` "); var verifier = CompileAndVerify( comp, expectedOutput: @" False False False False False False False False False Evaluated C13.F False Evaluated C13.F True Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False False False False Evaluated C15.F True Evaluated C15.F True Evaluated C15.F False Evaluated C13.F False Evaluated C13.F False Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False False False False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C13.F False Evaluated C13.F False Evaluated C13.F False Evaluated C14.F False Evaluated C14.F False Evaluated C14.F False False False False Evaluated C15.F False Evaluated C15.F False Evaluated C15.F False ").VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { // Code size 127 (0x7f) .maxstack 2 .locals init (object V_0, C12 V_1, I1 V_2, int V_3, int V_4, I2 V_5, int V_6, bool V_7) IL_0000: ldarg.0 IL_0001: ldfld ""object System.ValueTuple<object, int>.Item1"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: isinst ""C12"" IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: brfalse.s IL_004b IL_0011: ldloc.1 IL_0012: isinst ""I1"" IL_0017: stloc.2 IL_0018: ldloc.2 IL_0019: brfalse.s IL_0079 IL_001b: ldloc.2 IL_001c: callvirt ""int I1.F.get"" IL_0021: stloc.3 IL_0022: ldloc.3 IL_0023: ldc.i4.1 IL_0024: bne.un.s IL_003d IL_0026: ldarg.0 IL_0027: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_002c: stloc.s V_4 IL_002e: ldloc.s V_4 IL_0030: ldc.i4.2 IL_0031: beq.s IL_0074 IL_0033: ldloc.0 IL_0034: isinst ""I2"" IL_0039: brtrue.s IL_006f IL_003b: br.s IL_0079 IL_003d: ldloc.0 IL_003e: isinst ""I2"" IL_0043: brfalse.s IL_0079 IL_0045: ldloc.3 IL_0046: ldc.i4.2 IL_0047: beq.s IL_0067 IL_0049: br.s IL_0079 IL_004b: ldloc.0 IL_004c: isinst ""I2"" IL_0051: stloc.s V_5 IL_0053: ldloc.s V_5 IL_0055: brfalse.s IL_0079 IL_0057: ldloc.s V_5 IL_0059: callvirt ""int I1.F.get"" IL_005e: stloc.s V_6 IL_0060: ldloc.s V_6 IL_0062: ldc.i4.1 IL_0063: sub IL_0064: ldc.i4.1 IL_0065: bgt.un.s IL_0079 IL_0067: ldarg.0 IL_0068: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_006d: stloc.s V_4 IL_006f: ldloc.s V_4 IL_0071: ldc.i4.1 IL_0072: bne.un.s IL_0079 IL_0074: ldc.i4.1 IL_0075: stloc.s V_7 IL_0077: br.s IL_007c IL_0079: ldc.i4.0 IL_007a: stloc.s V_7 IL_007c: ldloc.s V_7 IL_007e: ret } "); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_04() { var src = @" interface I1 { int F {get;} } interface I2 : I1 { } class C11; class C12; class C13(int f) : C12, I1 { public int F { get { System.Console.Write(""Evaluated C13.F ""); return f; } } } class C14(int f) : I2 { public int F { get { System.Console.Write(""Evaluated C14.F ""); return f; } } } class C15(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class C16(int f) : C12, I2 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class Program { static void Main() { object[] s = [null, new C11(), new C12(), new C13(1), new C14(1), new C15(1), new C16(1), new C13(2), new C14(2), new C15(2), new C16(2), new C13(3), new C14(3), new C15(3), new C16(3)]; int[] i = [1, 2, 3]; foreach (var s1 in s) { foreach (var j in i) { var t = Test1((s1, j)); System.Console.WriteLine(t); } } } static bool Test1((object, int) u) { return u is (C12 and I2 and { F: 1 }, 2) or (I1 and { F: 1 or 2 }, 1); } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, @"[0]: t1 = t0.Item1; [1] [1]: t1 is C12 ? [2] : [10] [2]: t2 = (C12)t1; [3] [3]: t2 is I2 ? [4] : [10] [4]: t3 = (I2)t2; [5] [5]: t4 = t3.F; [6] [6]: t4 == 1 ? [7] : [9] [7]: t5 = t0.Item2; [8] [8]: t5 == 2 ? [17] : [16] [9]: t4 == 2 ? [15] : [18] [10]: t1 is I1 ? [11] : [18] [11]: t6 = (I1)t1; [12] [12]: t7 = t6.F; [13] [13]: t7 == 1 ? [15] : [14] [14]: t7 == 2 ? [15] : [18] [15]: t5 = t0.Item2; [16] [16]: t5 == 1 ? [17] : [18] [17]: leaf <isPatternSuccess> `(C12 and I2 and { F: 1 }, 2) or (I1 and { F: 1 or 2 }, 1)` [18]: leaf <isPatternFailure> `(C12 and I2 and { F: 1 }, 2) or (I1 and { F: 1 or 2 }, 1)` "); var verifier = CompileAndVerify( comp, expectedOutput: @" False False False False False False False False False Evaluated C13.F True Evaluated C13.F False Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C15.F True Evaluated C15.F True Evaluated C15.F False Evaluated C13.F True Evaluated C13.F False Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C15.F True Evaluated C15.F False Evaluated C15.F False Evaluated C13.F False Evaluated C13.F False Evaluated C13.F False Evaluated C14.F False Evaluated C14.F False Evaluated C14.F False Evaluated C15.F False Evaluated C15.F False Evaluated C15.F False Evaluated C15.F False Evaluated C15.F False Evaluated C15.F False ").VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { // Code size 111 (0x6f) .maxstack 2 .locals init (object V_0, C12 V_1, I2 V_2, int V_3, int V_4, I1 V_5, int V_6, bool V_7) IL_0000: ldarg.0 IL_0001: ldfld ""object System.ValueTuple<object, int>.Item1"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: isinst ""C12"" IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: brfalse.s IL_003b IL_0011: ldloc.1 IL_0012: isinst ""I2"" IL_0017: stloc.2 IL_0018: ldloc.2 IL_0019: brfalse.s IL_003b IL_001b: ldloc.2 IL_001c: callvirt ""int I1.F.get"" IL_0021: stloc.3 IL_0022: ldloc.3 IL_0023: ldc.i4.1 IL_0024: beq.s IL_002c IL_0026: ldloc.3 IL_0027: ldc.i4.2 IL_0028: beq.s IL_0057 IL_002a: br.s IL_0069 IL_002c: ldarg.0 IL_002d: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0032: stloc.s V_4 IL_0034: ldloc.s V_4 IL_0036: ldc.i4.2 IL_0037: beq.s IL_0064 IL_0039: br.s IL_005f IL_003b: ldloc.0 IL_003c: isinst ""I1"" IL_0041: stloc.s V_5 IL_0043: ldloc.s V_5 IL_0045: brfalse.s IL_0069 IL_0047: ldloc.s V_5 IL_0049: callvirt ""int I1.F.get"" IL_004e: stloc.s V_6 IL_0050: ldloc.s V_6 IL_0052: ldc.i4.1 IL_0053: sub IL_0054: ldc.i4.1 IL_0055: bgt.un.s IL_0069 IL_0057: ldarg.0 IL_0058: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_005d: stloc.s V_4 IL_005f: ldloc.s V_4 IL_0061: ldc.i4.1 IL_0062: bne.un.s IL_0069 IL_0064: ldc.i4.1 IL_0065: stloc.s V_7 IL_0067: br.s IL_006c IL_0069: ldc.i4.0 IL_006a: stloc.s V_7 IL_006c: ldloc.s V_7 IL_006e: ret } "); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_05() { var src = @" interface I1 { int F {get;} } interface I2 : I1 { } class C11; class C12; class C13(int f) : C12, I1 { public int F { get { System.Console.Write(""Evaluated C13.F ""); return f; } } } class C14(int f) : I2 { public int F { get { System.Console.Write(""Evaluated C14.F ""); return f; } } } class C15(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class C16(int f) : C12, I2 { public int F { get { System.Console.Write(""Evaluated C15.F ""); return f; } } } class Program { static void Main() { object[] s = [null, new C11(), new C12(), new C13(1), new C14(1), new C15(1), new C16(1), new C13(2), new C14(2), new C15(2), new C16(2), new C13(3), new C14(3), new C15(3), new C16(3)]; int[] i = [1, 2, 3]; foreach (var s1 in s) { foreach (var j in i) { var t = Test1((s1, j)); System.Console.WriteLine(t); } } } static int Test1((object, int) u) { return u switch { (C12 and I1 and { F: 1 and var x1 }, 2) => x1 ,(I2 and { F: (1 or 2) and var x2 }, 1) => x2, _ => -100 }; } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t1 = t0.Item1; [1] [1]: t1 is C12 ? [2] : [17] [2]: t2 = (C12)t1; [3] [3]: t2 is I1 ? [4] : [26] [4]: t3 = (I1)t2; [5] [5]: t4 = t3.F; [6] [6]: t4 == 1 ? [7] : [12] [7]: t5 = t0.Item2; [8] [8]: t5 == 2 ? [9] : [11] [9]: when <true> ? [10] : <unreachable> [10]: leaf <arm> `(C12 and I1 and { F: 1 and var x1 }, 2) => x1` [11]: t1 is I2 ? [15] : [26] [12]: t1 is I2 ? [13] : [26] [13]: t4 == 2 ? [14] : [26] [14]: t5 = t0.Item2; [15] [15]: t5 == 1 ? [16] : [26] [16]: t8 <-- t4; [24] [17]: t1 is I2 ? [18] : [26] [18]: t7 = (I2)t1; [19] [19]: t8 = t7.F; [20] [20]: t8 == 1 ? [22] : [21] [21]: t8 == 2 ? [22] : [26] [22]: t5 = t0.Item2; [23] [23]: t5 == 1 ? [24] : [26] [24]: when <true> ? [25] : <unreachable> [25]: leaf <arm> `(I2 and { F: (1 or 2) and var x2 }, 1) => x2` [26]: leaf <arm> `_ => -100` "); var verifier = CompileAndVerify( comp, expectedOutput: @" -100 -100 -100 -100 -100 -100 -100 -100 -100 Evaluated C13.F -100 Evaluated C13.F 1 Evaluated C13.F -100 Evaluated C14.F 1 Evaluated C14.F -100 Evaluated C14.F -100 -100 -100 -100 Evaluated C15.F 1 Evaluated C15.F 1 Evaluated C15.F -100 Evaluated C13.F -100 Evaluated C13.F -100 Evaluated C13.F -100 Evaluated C14.F 2 Evaluated C14.F -100 Evaluated C14.F -100 -100 -100 -100 Evaluated C15.F 2 Evaluated C15.F -100 Evaluated C15.F -100 Evaluated C13.F -100 Evaluated C13.F -100 Evaluated C13.F -100 Evaluated C14.F -100 Evaluated C14.F -100 Evaluated C14.F -100 -100 -100 -100 Evaluated C15.F -100 Evaluated C15.F -100 Evaluated C15.F -100 ").VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { // Code size 150 (0x96) .maxstack 2 .locals init (int V_0, //x1 int V_1, //x2 int V_2, object V_3, C12 V_4, I1 V_5, int V_6, I2 V_7) IL_0000: ldarg.0 IL_0001: ldfld ""object System.ValueTuple<object, int>.Item1"" IL_0006: stloc.3 IL_0007: ldloc.3 IL_0008: isinst ""C12"" IL_000d: stloc.s V_4 IL_000f: ldloc.s V_4 IL_0011: brfalse.s IL_0060 IL_0013: ldloc.s V_4 IL_0015: isinst ""I1"" IL_001a: stloc.s V_5 IL_001c: ldloc.s V_5 IL_001e: brfalse.s IL_0091 IL_0020: ldloc.s V_5 IL_0022: callvirt ""int I1.F.get"" IL_0027: stloc.0 IL_0028: ldloc.0 IL_0029: ldc.i4.1 IL_002a: bne.un.s IL_0043 IL_002c: ldarg.0 IL_002d: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0032: stloc.s V_6 IL_0034: ldloc.s V_6 IL_0036: ldc.i4.2 IL_0037: beq.s IL_0089 IL_0039: ldloc.3 IL_003a: isinst ""I2"" IL_003f: brtrue.s IL_0057 IL_0041: br.s IL_0091 IL_0043: ldloc.3 IL_0044: isinst ""I2"" IL_0049: brfalse.s IL_0091 IL_004b: ldloc.0 IL_004c: ldc.i4.2 IL_004d: bne.un.s IL_0091 IL_004f: ldarg.0 IL_0050: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0055: stloc.s V_6 IL_0057: ldloc.s V_6 IL_0059: ldc.i4.1 IL_005a: bne.un.s IL_0091 IL_005c: ldloc.0 IL_005d: stloc.1 IL_005e: br.s IL_008d IL_0060: ldloc.3 IL_0061: isinst ""I2"" IL_0066: stloc.s V_7 IL_0068: ldloc.s V_7 IL_006a: brfalse.s IL_0091 IL_006c: ldloc.s V_7 IL_006e: callvirt ""int I1.F.get"" IL_0073: stloc.1 IL_0074: ldloc.1 IL_0075: ldc.i4.1 IL_0076: sub IL_0077: ldc.i4.1 IL_0078: bgt.un.s IL_0091 IL_007a: ldarg.0 IL_007b: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0080: stloc.s V_6 IL_0082: ldloc.s V_6 IL_0084: ldc.i4.1 IL_0085: beq.s IL_008d IL_0087: br.s IL_0091 IL_0089: ldloc.0 IL_008a: stloc.2 IL_008b: br.s IL_0094 IL_008d: ldloc.1 IL_008e: stloc.2 IL_008f: br.s IL_0094 IL_0091: ldc.i4.s -100 IL_0093: stloc.2 IL_0094: ldloc.2 IL_0095: ret } "); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_06() { var src = @" #nullable enable interface I1 { C2? F { get; } } class C2 { public string? S = null; } interface I2 : I1 { } class C11; class C12; class C13 : C12, I1 { public C2? F => null; } class C14 : I2 { public C2? F => null; } class C15 : I1 { public C2? F => null; } class C16 : C12, I2 { public C2? F => null; } class Program { static int Test1((object, int) u) { return u switch { (C12 and I1 and { F: { S: ""1"" and var x1 } }, 2) => x1.Length , (I2 and { F: { S: (""1"" or ""2"") and var x2 } }, 1) => x2.Length, _ => -100 }; } static int Test2((object, int) u) { return u switch { (C12 and I1 and { F: { S: ""1"" } x3 }, 2) => x3.S.Length , (I2 and { F: { S: (""1"" or ""2"") } x4 }, 1) => x4.S.Length, _ => -100 }; } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseDll); comp.VerifyDiagnostics(); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_07() { var src = @" class C0; class C01(bool p) : C0 { public bool P => p; } class C1(bool p) : C01(p); class C02(bool p) : C0 { public bool P => p; } class C2(bool p) : C02(p); class C3(bool p) : C01(p); class C4(bool p) : C01(p); class C03(bool p) : C0 { public bool P => p; } class C5(bool p) : C03(p); class C6(bool p) : C01(p); static class Program { static void Main() { C0[] s = [null, new C1(true), new C2(true), new C3(true), new C4(true), new C5(true), new C6(true), new C1(false), new C2(false), new C3(false), new C4(false), new C5(false), new C6(false)]; foreach (var s1 in s) { var t = s1.Test1(); System.Console.WriteLine(t); } } internal static bool Test1(this C0 c) { return c is C1 { P: true } or C2 { P: true } or C3 { P: true } or C4 { P: true } or C5 { P: true } or C6 { P: true }; } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, @"[0]: t0 is C1 ? [1] : [4] [1]: t1 = (C1)t0; [2] [2]: t2 = t1.P; [3] [3]: t2 == True ? [24] : [25] [4]: t0 is C2 ? [5] : [8] [5]: t3 = (C2)t0; [6] [6]: t4 = t3.P; [7] [7]: t4 == True ? [24] : [25] [8]: t0 is C3 ? [9] : [12] [9]: t5 = (C3)t0; [10] [10]: t6 = t5.P; [11] [11]: t6 == True ? [24] : [25] [12]: t0 is C4 ? [13] : [16] [13]: t7 = (C4)t0; [14] [14]: t8 = t7.P; [15] [15]: t8 == True ? [24] : [25] [16]: t0 is C5 ? [17] : [20] [17]: t9 = (C5)t0; [18] [18]: t10 = t9.P; [19] [19]: t10 == True ? [24] : [25] [20]: t0 is C6 ? [21] : [25] [21]: t11 = (C6)t0; [22] [22]: t12 = t11.P; [23] [23]: t12 == True ? [24] : [25] [24]: leaf <isPatternSuccess> `C1 { P: true } or C2 { P: true } or C3 { P: true } or C4 { P: true } or C5 { P: true } or C6 { P: true }` [25]: leaf <isPatternFailure> `C1 { P: true } or C2 { P: true } or C3 { P: true } or C4 { P: true } or C5 { P: true } or C6 { P: true }` "); var verifier = CompileAndVerify( comp, expectedOutput: @" False True True True True True True False False False False False False ").VerifyDiagnostics(); verifier.VerifyIL("Program.Test1", @" { // Code size 135 (0x87) .maxstack 1 .locals init (C1 V_0, C2 V_1, C3 V_2, C4 V_3, C5 V_4, C6 V_5, bool V_6) IL_0000: ldarg.0 IL_0001: isinst ""C1"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: brfalse.s IL_0014 IL_000a: ldloc.0 IL_000b: callvirt ""bool C01.P.get"" IL_0010: brtrue.s IL_007c IL_0012: br.s IL_0081 IL_0014: ldarg.0 IL_0015: isinst ""C2"" IL_001a: stloc.1 IL_001b: ldloc.1 IL_001c: brfalse.s IL_0028 IL_001e: ldloc.1 IL_001f: callvirt ""bool C02.P.get"" IL_0024: brtrue.s IL_007c IL_0026: br.s IL_0081 IL_0028: ldarg.0 IL_0029: isinst ""C3"" IL_002e: stloc.2 IL_002f: ldloc.2 IL_0030: brfalse.s IL_003c IL_0032: ldloc.2 IL_0033: callvirt ""bool C01.P.get"" IL_0038: brtrue.s IL_007c IL_003a: br.s IL_0081 IL_003c: ldarg.0 IL_003d: isinst ""C4"" IL_0042: stloc.3 IL_0043: ldloc.3 IL_0044: brfalse.s IL_0050 IL_0046: ldloc.3 IL_0047: callvirt ""bool C01.P.get"" IL_004c: brtrue.s IL_007c IL_004e: br.s IL_0081 IL_0050: ldarg.0 IL_0051: isinst ""C5"" IL_0056: stloc.s V_4 IL_0058: ldloc.s V_4 IL_005a: brfalse.s IL_0067 IL_005c: ldloc.s V_4 IL_005e: callvirt ""bool C03.P.get"" IL_0063: brtrue.s IL_007c IL_0065: br.s IL_0081 IL_0067: ldarg.0 IL_0068: isinst ""C6"" IL_006d: stloc.s V_5 IL_006f: ldloc.s V_5 IL_0071: brfalse.s IL_0081 IL_0073: ldloc.s V_5 IL_0075: callvirt ""bool C01.P.get"" IL_007a: brfalse.s IL_0081 IL_007c: ldc.i4.1 IL_007d: stloc.s V_6 IL_007f: br.s IL_0084 IL_0081: ldc.i4.0 IL_0082: stloc.s V_6 IL_0084: ldloc.s V_6 IL_0086: ret } "); } [Fact] [WorkItem("https://github.com/dotnet/roslyn/issues/82063")] public void SideeffectEvaluations_08() { var src = @" interface I1 { int F {get;} } class C12; class C13(int f) : C12, I1 { public int F { get { System.Console.Write(""Evaluated C13.F ""); return f; } } } class C14(int f) : I1 { public int F { get { System.Console.Write(""Evaluated C14.F ""); return f; } } } class Program { static void Main() { I1[] s = [null, new C13(1), new C14(1), new C13(2), new C14(2), new C13(3), new C14(3)]; int[] i = [1, 2, 3]; foreach (var s1 in s) { foreach (var j in i) { var t = Test1((s1, j)); System.Console.WriteLine(t); } } } static bool Test1((I1, int) u) { return u is (C12 and I1 and { F: 1 }, 2) or ({ F: 1 or 2 }, 1); } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, @"[0]: t1 = t0.Item1; [1] [1]: t1 is C12 ? [2] : [8] [2]: t2 = (C12)t1; [3] [3]: t2 is I1 ? [4] : [9] [4]: t3 = t1.F; [5] [5]: t3 == 1 ? [6] : [11] [6]: t4 = t0.Item2; [7] [7]: t4 == 2 ? [14] : [13] [8]: t1 != null ? [9] : [15] [9]: t3 = t1.F; [10] [10]: t3 == 1 ? [12] : [11] [11]: t3 == 2 ? [12] : [15] [12]: t4 = t0.Item2; [13] [13]: t4 == 1 ? [14] : [15] [14]: leaf <isPatternSuccess> `(C12 and I1 and { F: 1 }, 2) or ({ F: 1 or 2 }, 1)` [15]: leaf <isPatternFailure> `(C12 and I1 and { F: 1 }, 2) or ({ F: 1 or 2 }, 1)` "); var verifier = CompileAndVerify( comp, expectedOutput: @" False False False Evaluated C13.F True Evaluated C13.F True Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C13.F True Evaluated C13.F False Evaluated C13.F False Evaluated C14.F True Evaluated C14.F False Evaluated C14.F False Evaluated C13.F False Evaluated C13.F False Evaluated C13.F False Evaluated C14.F False Evaluated C14.F False Evaluated C14.F False ").VerifyDiagnostics( ); verifier.VerifyIL("Program.Test1", @" { // Code size 89 (0x59) .maxstack 2 .locals init (I1 V_0, C12 V_1, int V_2, int V_3, bool V_4) IL_0000: ldarg.0 IL_0001: ldfld ""I1 System.ValueTuple<I1, int>.Item1"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: isinst ""C12"" IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: brfalse.s IL_0031 IL_0011: ldloc.1 IL_0012: isinst ""I1"" IL_0017: brfalse.s IL_0034 IL_0019: ldloc.0 IL_001a: callvirt ""int I1.F.get"" IL_001f: stloc.2 IL_0020: ldloc.2 IL_0021: ldc.i4.1 IL_0022: bne.un.s IL_003f IL_0024: ldarg.0 IL_0025: ldfld ""int System.ValueTuple<I1, int>.Item2"" IL_002a: stloc.3 IL_002b: ldloc.3 IL_002c: ldc.i4.2 IL_002d: beq.s IL_004e IL_002f: br.s IL_004a IL_0031: ldloc.0 IL_0032: brfalse.s IL_0053 IL_0034: ldloc.0 IL_0035: callvirt ""int I1.F.get"" IL_003a: stloc.2 IL_003b: ldloc.2 IL_003c: ldc.i4.1 IL_003d: beq.s IL_0043 IL_003f: ldloc.2 IL_0040: ldc.i4.2 IL_0041: bne.un.s IL_0053 IL_0043: ldarg.0 IL_0044: ldfld ""int System.ValueTuple<I1, int>.Item2"" IL_0049: stloc.3 IL_004a: ldloc.3 IL_004b: ldc.i4.1 IL_004c: bne.un.s IL_0053 IL_004e: ldc.i4.1 IL_004f: stloc.s V_4 IL_0051: br.s IL_0056 IL_0053: ldc.i4.0 IL_0054: stloc.s V_4 IL_0056: ldloc.s V_4 IL_0058: ret } "); } [Fact] public void SideeffectEvaluations_09() { var src = @" using System; class C11; class C12; struct S1 { static void Main() { object[] s = [null, (new C11()), (new C12()), (1), (""1""), (2), (""2""), (3), (""3"")]; int[] i = [1, 2, 3]; foreach (var s1 in s) { foreach (var j in i) { var t = Test1((s1, j)); System.Console.WriteLine(t); } } } static bool Test1((object, int) u) { return u is (int and 1, 2) or (System.IComparable and { AsInt: 3 }, 1); } } static class IComparableExtensions { extension(IComparable c) { public int? AsInt => c as int?; } } "; var comp = CreateCompilation(src, options: TestOptions.ReleaseExe); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, @"[0]: t1 = t0.Item1; [1] [1]: t1 is int ? [2] : [11] [2]: t2 = (int)t1; [3] [3]: t2 == 1 ? [4] : [12] [4]: t3 = t0.Item2; [5] [5]: t3 == 2 ? [19] : [6] [6]: t4 = (System.IComparable)t1; [7] [7]: t5 = t4.AsInt; [8] [8]: t5 != null ? [9] : [20] [9]: t6 = (int)t5; [10] [10]: t6 == 3 ? [18] : [20] [11]: t1 is System.IComparable ? [12] : [20] [12]: t4 = (System.IComparable)t1; [13] [13]: t5 = t4.AsInt; [14] [14]: t5 != null ? [15] : [20] [15]: t6 = (int)t5; [16] [16]: t6 == 3 ? [17] : [20] [17]: t3 = t0.Item2; [18] [18]: t3 == 1 ? [19] : [20] [19]: leaf <isPatternSuccess> `(int and 1, 2) or (System.IComparable and { AsInt: 3 }, 1)` [20]: leaf <isPatternFailure> `(int and 1, 2) or (System.IComparable and { AsInt: 3 }, 1)` ", forLowering: true); var verifier = CompileAndVerify( comp, expectedOutput: @" False False False False False False False False False False True False False False False False False False False False False True False False False False False ").VerifyDiagnostics(); verifier.VerifyIL("S1.Test1", @" { // Code size 137 (0x89) .maxstack 2 .locals init (object V_0, int V_1, System.IComparable V_2, int? V_3, bool V_4) IL_0000: ldarg.0 IL_0001: ldfld ""object System.ValueTuple<object, int>.Item1"" IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: isinst ""int"" IL_000d: brfalse.s IL_0046 IL_000f: ldloc.0 IL_0010: unbox.any ""int"" IL_0015: ldc.i4.1 IL_0016: bne.un.s IL_0052 IL_0018: ldarg.0 IL_0019: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_001e: stloc.1 IL_001f: ldloc.1 IL_0020: ldc.i4.2 IL_0021: beq.s IL_007e IL_0023: ldloc.0 IL_0024: castclass ""System.IComparable"" IL_0029: stloc.2 IL_002a: ldloc.2 IL_002b: call ""int? IComparableExtensions.get_AsInt(System.IComparable)"" IL_0030: stloc.3 IL_0031: ldloca.s V_3 IL_0033: call ""bool int?.HasValue.get"" IL_0038: brfalse.s IL_0083 IL_003a: ldloca.s V_3 IL_003c: call ""int int?.GetValueOrDefault()"" IL_0041: ldc.i4.3 IL_0042: beq.s IL_007a IL_0044: br.s IL_0083 IL_0046: ldloc.0 IL_0047: isinst ""System.IComparable"" IL_004c: stloc.2 IL_004d: ldloc.2 IL_004e: brtrue.s IL_0059 IL_0050: br.s IL_0083 IL_0052: ldloc.0 IL_0053: castclass ""System.IComparable"" IL_0058: stloc.2 IL_0059: ldloc.2 IL_005a: call ""int? IComparableExtensions.get_AsInt(System.IComparable)"" IL_005f: stloc.3 IL_0060: ldloca.s V_3 IL_0062: call ""bool int?.HasValue.get"" IL_0067: brfalse.s IL_0083 IL_0069: ldloca.s V_3 IL_006b: call ""int int?.GetValueOrDefault()"" IL_0070: ldc.i4.3 IL_0071: bne.un.s IL_0083 IL_0073: ldarg.0 IL_0074: ldfld ""int System.ValueTuple<object, int>.Item2"" IL_0079: stloc.1 IL_007a: ldloc.1 IL_007b: ldc.i4.1 IL_007c: bne.un.s IL_0083 IL_007e: ldc.i4.1 IL_007f: stloc.s V_4 IL_0081: br.s IL_0086 IL_0083: ldc.i4.0 IL_0084: stloc.s V_4 IL_0086: ldloc.s V_4 IL_0088: ret } "); } [Fact] public void ValueSet_WithTypePatternAndOrValues() { // Exercises ValueSet in combination with type tests on an object input. // The `int and (1 or 2 or 3)` pattern creates a ValueSet on the typed int temp. var source = """ class C { public static void Main() { System.Console.Write(Test(1)); System.Console.Write(Test(2)); System.Console.Write(Test(3)); System.Console.Write(Test(4)); System.Console.Write(Test("x")); System.Console.Write(Test(null)); } public static string Test(object o) { return o switch { int and (1 or 2 or 3) => "V", not null => "O", null => "N", }; } } """; CompileAndVerify(source, expectedOutput: "VVVOON"); } [Fact] public void ValueSet_FilterWithExplicitNullTest() { // Exercises ValueSet.Filter with BoundDagExplicitNullTest. // The `null` arm creates an explicit null test on the object input. // When filtering the ValueSet (inside the `int and (1 or 2 or 3)` arm) // with this null test, the ValueSet must correctly return // whenTrue = False (null can't match values), whenFalse = this. var source = """ class C { public static void Main() { System.Console.Write(Test(1)); System.Console.Write(Test(2)); System.Console.Write(Test(3)); System.Console.Write(Test(4)); System.Console.Write(Test("x")); System.Console.Write(Test(null)); } public static string Test(object o) { return o switch { null => "N", int and (1 or 2 or 3) => "V", _ => "O", }; } } """; CompileAndVerify(source, expectedOutput: "VVVOON"); } [Fact] public void ValueSet_UnionNonNullTest_Implicit() { // Captures the DAG shape for an implicit union value pattern. Unlike the // explicit type-pattern cases below, this form is not expected to create // a ValueSet on the union value input. var source = """ union S1(int, string) { static void Main() { System.Console.Write(Test(new S1(1))); System.Console.Write(Test(new S1(3))); System.Console.Write(Test(new S1(5))); System.Console.Write(Test(new S1(7))); System.Console.Write(Test(new S1("hello"))); } static int Test(S1 u) { return u switch { string => 0, 1 or 3 or 5 => 1, _ => 2 }; } } """; var comp = CreateCompilation([source, UnionAttributeSource, IUnionSource], options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t1 = t0.Value; [1] [1]: t1 != null ? [2] : [9] [2]: t1 is string ? [3] : [4] [3]: leaf <arm> `string => 0` [4]: t2 = (int)t1; [5] [5]: t2 == 1 ? [8] : [6] [6]: t2 == 3 ? [8] : [7] [7]: t2 == 5 ? [8] : [9] [8]: leaf <arm> `1 or 3 or 5 => 1` [9]: leaf <arm> `_ => 2` ", forLowering: false); CompileAndVerify(comp, expectedOutput: "11120"); } [Fact] public void ValueSet_BadConstant() { verify("C.Missing or 1", @"[0]: t0 == bad ? [2] : [1] [1]: t0 == 1 ? [2] : [3] [2]: leaf <isPatternSuccess> `C.Missing or 1` [3]: leaf <isPatternFailure> `C.Missing or 1` ", // (3,47): error CS0117: 'C' does not contain a definition for 'Missing' // static bool Test(int value) => value is C.Missing or 1; Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(3, 47)); verify("1 or C.Missing", @"[0]: t0 == 1 ? [2] : [1] [1]: t0 == bad ? [2] : [3] [2]: leaf <isPatternSuccess> `1 or C.Missing` [3]: leaf <isPatternFailure> `1 or C.Missing` ", // (3,52): error CS0117: 'C' does not contain a definition for 'Missing' // static bool Test(int value) => value is 1 or C.Missing; Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(3, 52)); void verify(string pattern, string expectedDag, DiagnosticDescription expectedDiagnostic) { var source = $$""" class C { static bool Test(int value) => value is {{pattern}}; } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics(expectedDiagnostic); VerifyDecisionDagDump<IsPatternExpressionSyntax>(comp, expectedDag, forLowering: false); } } [Fact] public void ValueSet_LoweringOrder_Numeric() { // Values are intentionally listed in descending order so the IL observes any canonicalization. var source = """ enum E { Ten = 10, Fifty = 50, } class C { public static bool SByte(sbyte value) => value is 50 or 10; public static bool Byte(byte value) => value is 50 or 10; public static bool Int16(short value) => value is 50 or 10; public static bool UInt16(ushort value) => value is 50 or 10; public static bool Int32(int value) => value is 50 or 10; public static bool UInt32(uint value) => value is 50 or 10; public static bool Int64(long value) => value is 50 or 10; public static bool UInt64(ulong value) => value is 50 or 10; public static bool Enum(E value) => value is E.Fifty or E.Ten; public static bool Char(char value) => value is 'z' or 'a'; public static bool NInt(nint value) => value is 50 or 10; public static bool NUInt(nuint value) => value is 50 or 10; public static bool Decimal(decimal value) => value is 1m or 0m; public static bool Single(float value) => value is 50f or 10f; public static bool Double(double value) => value is 50d or 10d; } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseDll); const string expectedInt32IL = """ { // Code size 18 (0x12) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 10 IL_0003: beq.s IL_000a IL_0005: ldarg.0 IL_0006: ldc.i4.s 50 IL_0008: bne.un.s IL_000e IL_000a: ldc.i4.1 IL_000b: stloc.0 IL_000c: br.s IL_0010 IL_000e: ldc.i4.0 IL_000f: stloc.0 IL_0010: ldloc.0 IL_0011: ret } """; verifier.VerifyIL("C.SByte", expectedInt32IL); verifier.VerifyIL("C.Byte", expectedInt32IL); verifier.VerifyIL("C.Int16", expectedInt32IL); verifier.VerifyIL("C.UInt16", expectedInt32IL); verifier.VerifyIL("C.Int32", expectedInt32IL); verifier.VerifyIL("C.UInt32", expectedInt32IL); verifier.VerifyIL("C.Enum", expectedInt32IL); verifier.VerifyIL("C.Int64", """ { // Code size 20 (0x14) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 10 IL_0003: conv.i8 IL_0004: beq.s IL_000c IL_0006: ldarg.0 IL_0007: ldc.i4.s 50 IL_0009: conv.i8 IL_000a: bne.un.s IL_0010 IL_000c: ldc.i4.1 IL_000d: stloc.0 IL_000e: br.s IL_0012 IL_0010: ldc.i4.0 IL_0011: stloc.0 IL_0012: ldloc.0 IL_0013: ret } """); verifier.VerifyIL("C.UInt64", """ { // Code size 20 (0x14) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 10 IL_0003: conv.i8 IL_0004: beq.s IL_000c IL_0006: ldarg.0 IL_0007: ldc.i4.s 50 IL_0009: conv.i8 IL_000a: bne.un.s IL_0010 IL_000c: ldc.i4.1 IL_000d: stloc.0 IL_000e: br.s IL_0012 IL_0010: ldc.i4.0 IL_0011: stloc.0 IL_0012: ldloc.0 IL_0013: ret } """); verifier.VerifyIL("C.Char", """ { // Code size 18 (0x12) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.s 97 IL_0003: beq.s IL_000a IL_0005: ldarg.0 IL_0006: ldc.i4.s 122 IL_0008: bne.un.s IL_000e IL_000a: ldc.i4.1 IL_000b: stloc.0 IL_000c: br.s IL_0010 IL_000e: ldc.i4.0 IL_000f: stloc.0 IL_0010: ldloc.0 IL_0011: ret } """); verifier.VerifyIL("C.NInt", """ { // Code size 23 (0x17) .maxstack 2 .locals init (bool V_0, long V_1) IL_0000: ldarg.0 IL_0001: conv.i8 IL_0002: stloc.1 IL_0003: ldloc.1 IL_0004: ldc.i4.s 10 IL_0006: conv.i8 IL_0007: beq.s IL_000f IL_0009: ldloc.1 IL_000a: ldc.i4.s 50 IL_000c: conv.i8 IL_000d: bne.un.s IL_0013 IL_000f: ldc.i4.1 IL_0010: stloc.0 IL_0011: br.s IL_0015 IL_0013: ldc.i4.0 IL_0014: stloc.0 IL_0015: ldloc.0 IL_0016: ret } """); verifier.VerifyIL("C.NUInt", """ { // Code size 23 (0x17) .maxstack 2 .locals init (bool V_0, ulong V_1) IL_0000: ldarg.0 IL_0001: conv.u8 IL_0002: stloc.1 IL_0003: ldloc.1 IL_0004: ldc.i4.s 10 IL_0006: conv.i8 IL_0007: beq.s IL_000f IL_0009: ldloc.1 IL_000a: ldc.i4.s 50 IL_000c: conv.i8 IL_000d: bne.un.s IL_0013 IL_000f: ldc.i4.1 IL_0010: stloc.0 IL_0011: br.s IL_0015 IL_0013: ldc.i4.0 IL_0014: stloc.0 IL_0015: ldloc.0 IL_0016: ret } """); verifier.VerifyIL("C.Decimal", """ { // Code size 58 (0x3a) .maxstack 6 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.i4.0 IL_0002: ldc.i4.0 IL_0003: ldc.i4.0 IL_0004: ldc.i4.0 IL_0005: ldc.i4.s 28 IL_0007: newobj "decimal..ctor(int, int, int, bool, byte)" IL_000c: call "bool decimal.op_Equality(decimal, decimal)" IL_0011: brtrue.s IL_0032 IL_0013: ldarg.0 IL_0014: ldc.i4 0x10000000 IL_0019: ldc.i4 0x3e250261 IL_001e: ldc.i4 0x204fce5e IL_0023: ldc.i4.0 IL_0024: ldc.i4.s 28 IL_0026: newobj "decimal..ctor(int, int, int, bool, byte)" IL_002b: call "bool decimal.op_Equality(decimal, decimal)" IL_0030: brfalse.s IL_0036 IL_0032: ldc.i4.1 IL_0033: stloc.0 IL_0034: br.s IL_0038 IL_0036: ldc.i4.0 IL_0037: stloc.0 IL_0038: ldloc.0 IL_0039: ret } """); verifier.VerifyIL("C.Single", """ { // Code size 24 (0x18) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.r4 10 IL_0006: beq.s IL_0010 IL_0008: ldarg.0 IL_0009: ldc.r4 50 IL_000e: bne.un.s IL_0014 IL_0010: ldc.i4.1 IL_0011: stloc.0 IL_0012: br.s IL_0016 IL_0014: ldc.i4.0 IL_0015: stloc.0 IL_0016: ldloc.0 IL_0017: ret } """); verifier.VerifyIL("C.Double", """ { // Code size 32 (0x20) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldc.r8 10 IL_000a: beq.s IL_0018 IL_000c: ldarg.0 IL_000d: ldc.r8 50 IL_0016: bne.un.s IL_001c IL_0018: ldc.i4.1 IL_0019: stloc.0 IL_001a: br.s IL_001e IL_001c: ldc.i4.0 IL_001d: stloc.0 IL_001e: ldloc.0 IL_001f: ret } """); } [Fact] public void ValueSet_LoweringOrder_StringAndSpan() { // Values are intentionally listed in descending order so the IL observes both direct and optimized dispatch. var source = """ using System; class C { public static bool String(string value) => value is "z" or "a"; public static bool Span(Span<char> value) => value is "z" or "a"; public static bool ReadOnlySpan(ReadOnlySpan<char> value) => value is "z" or "a"; public static bool LargeString(string value) => value is "g" or "f" or "e" or "d" or "c" or "b" or "a"; public static bool LargeSpan(Span<char> value) => value is "g" or "f" or "e" or "d" or "c" or "b" or "a"; public static bool LargeReadOnlySpan(ReadOnlySpan<char> value) => value is "g" or "f" or "e" or "d" or "c" or "b" or "a"; } """; var comp = CreateCompilationWithSpanAndMemoryExtensions( source, options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularPreview); var verifier = CompileAndVerify(comp); verifier.VerifyIL("C.String", """ { // Code size 34 (0x22) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldstr "z" IL_0006: call "bool string.op_Equality(string, string)" IL_000b: brtrue.s IL_001a IL_000d: ldarg.0 IL_000e: ldstr "a" IL_0013: call "bool string.op_Equality(string, string)" IL_0018: brfalse.s IL_001e IL_001a: ldc.i4.1 IL_001b: stloc.0 IL_001c: br.s IL_0020 IL_001e: ldc.i4.0 IL_001f: stloc.0 IL_0020: ldloc.0 IL_0021: ret } """); verifier.VerifyIL("C.Span", """ { // Code size 44 (0x2c) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldstr "a" IL_0006: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_000b: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_0010: brtrue.s IL_0024 IL_0012: ldarg.0 IL_0013: ldstr "z" IL_0018: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_001d: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_0022: brfalse.s IL_0028 IL_0024: ldc.i4.1 IL_0025: stloc.0 IL_0026: br.s IL_002a IL_0028: ldc.i4.0 IL_0029: stloc.0 IL_002a: ldloc.0 IL_002b: ret } """); verifier.VerifyIL("C.ReadOnlySpan", """ { // Code size 44 (0x2c) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldstr "a" IL_0006: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_000b: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_0010: brtrue.s IL_0024 IL_0012: ldarg.0 IL_0013: ldstr "z" IL_0018: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_001d: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_0022: brfalse.s IL_0028 IL_0024: ldc.i4.1 IL_0025: stloc.0 IL_0026: br.s IL_002a IL_0028: ldc.i4.0 IL_0029: stloc.0 IL_002a: ldloc.0 IL_002b: ret } """); verifier.VerifyIL("C.LargeString", """ { // Code size 69 (0x45) .maxstack 2 .locals init (bool V_0, int V_1, char V_2) IL_0000: ldarg.0 IL_0001: brfalse.s IL_0041 IL_0003: ldarg.0 IL_0004: call "int string.Length.get" IL_0009: stloc.1 IL_000a: ldloc.1 IL_000b: ldc.i4.1 IL_000c: bne.un.s IL_0041 IL_000e: ldarg.0 IL_000f: ldc.i4.0 IL_0010: call "char string.this[int].get" IL_0015: stloc.2 IL_0016: ldloc.2 IL_0017: ldc.i4.s 97 IL_0019: sub IL_001a: switch ( IL_003d, IL_003d, IL_003d, IL_003d, IL_003d, IL_003d, IL_003d) IL_003b: br.s IL_0041 IL_003d: ldc.i4.1 IL_003e: stloc.0 IL_003f: br.s IL_0043 IL_0041: ldc.i4.0 IL_0042: stloc.0 IL_0043: ldloc.0 IL_0044: ret } """); verifier.VerifyIL("C.LargeSpan", """ { // Code size 69 (0x45) .maxstack 2 .locals init (bool V_0, int V_1, char V_2) IL_0000: ldarga.s V_0 IL_0002: call "int System.Span<char>.Length.get" IL_0007: stloc.1 IL_0008: ldloc.1 IL_0009: ldc.i4.1 IL_000a: bne.un.s IL_0041 IL_000c: ldarga.s V_0 IL_000e: ldc.i4.0 IL_000f: call "ref char System.Span<char>.this[int].get" IL_0014: ldind.u2 IL_0015: stloc.2 IL_0016: ldloc.2 IL_0017: ldc.i4.s 97 IL_0019: sub IL_001a: switch ( IL_003d, IL_003d, IL_003d, IL_003d, IL_003d, IL_003d, IL_003d) IL_003b: br.s IL_0041 IL_003d: ldc.i4.1 IL_003e: stloc.0 IL_003f: br.s IL_0043 IL_0041: ldc.i4.0 IL_0042: stloc.0 IL_0043: ldloc.0 IL_0044: ret } """); verifier.VerifyIL("C.LargeReadOnlySpan", """ { // Code size 69 (0x45) .maxstack 2 .locals init (bool V_0, int V_1, char V_2) IL_0000: ldarga.s V_0 IL_0002: call "int System.ReadOnlySpan<char>.Length.get" IL_0007: stloc.1 IL_0008: ldloc.1 IL_0009: ldc.i4.1 IL_000a: bne.un.s IL_0041 IL_000c: ldarga.s V_0 IL_000e: ldc.i4.0 IL_000f: call "ref readonly char System.ReadOnlySpan<char>.this[int].get" IL_0014: ldind.u2 IL_0015: stloc.2 IL_0016: ldloc.2 IL_0017: ldc.i4.s 97 IL_0019: sub IL_001a: switch ( IL_003d, IL_003d, IL_003d, IL_003d, IL_003d, IL_003d, IL_003d) IL_003b: br.s IL_0041 IL_003d: ldc.i4.1 IL_003e: stloc.0 IL_003f: br.s IL_0043 IL_0041: ldc.i4.0 IL_0042: stloc.0 IL_0043: ldloc.0 IL_0044: ret } """); comp = CreateCompilationWithSpanAndMemoryExtensions( source, options: TestOptions.ReleaseDll, parseOptions: TestOptions.RegularPreview.WithDisableLengthBasedSwitch()); verifier = CompileAndVerify(comp); verifier.VerifyIL("C.LargeString", """ { // Code size 199 (0xc7) .maxstack 2 .locals init (bool V_0, uint V_1) IL_0000: ldarg.0 IL_0001: call "uint <PrivateImplementationDetails>.ComputeStringHash(string)" IL_0006: stloc.1 IL_0007: ldloc.1 IL_0008: ldc.i4 0xe20c2606 IL_000d: bgt.un.s IL_002c IL_000f: ldloc.1 IL_0010: ldc.i4 0xe00c22e0 IL_0015: beq.s IL_0076 IL_0017: ldloc.1 IL_0018: ldc.i4 0xe10c2473 IL_001d: beq.s IL_0085 IL_001f: ldloc.1 IL_0020: ldc.i4 0xe20c2606 IL_0025: beq.s IL_0058 IL_0027: br IL_00c3 IL_002c: ldloc.1 IL_002d: ldc.i4 0xe40c292c IL_0032: bgt.un.s IL_0046 IL_0034: ldloc.1 IL_0035: ldc.i4 0xe30c2799 IL_003a: beq.s IL_0067 IL_003c: ldloc.1 IL_003d: ldc.i4 0xe40c292c IL_0042: beq.s IL_00b2 IL_0044: br.s IL_00c3 IL_0046: ldloc.1 IL_0047: ldc.i4 0xe60c2c52 IL_004c: beq.s IL_0094 IL_004e: ldloc.1 IL_004f: ldc.i4 0xe70c2de5 IL_0054: beq.s IL_00a3 IL_0056: br.s IL_00c3 IL_0058: ldarg.0 IL_0059: ldstr "g" IL_005e: call "bool string.op_Equality(string, string)" IL_0063: brtrue.s IL_00bf IL_0065: br.s IL_00c3 IL_0067: ldarg.0 IL_0068: ldstr "f" IL_006d: call "bool string.op_Equality(string, string)" IL_0072: brtrue.s IL_00bf IL_0074: br.s IL_00c3 IL_0076: ldarg.0 IL_0077: ldstr "e" IL_007c: call "bool string.op_Equality(string, string)" IL_0081: brtrue.s IL_00bf IL_0083: br.s IL_00c3 IL_0085: ldarg.0 IL_0086: ldstr "d" IL_008b: call "bool string.op_Equality(string, string)" IL_0090: brtrue.s IL_00bf IL_0092: br.s IL_00c3 IL_0094: ldarg.0 IL_0095: ldstr "c" IL_009a: call "bool string.op_Equality(string, string)" IL_009f: brtrue.s IL_00bf IL_00a1: br.s IL_00c3 IL_00a3: ldarg.0 IL_00a4: ldstr "b" IL_00a9: call "bool string.op_Equality(string, string)" IL_00ae: brtrue.s IL_00bf IL_00b0: br.s IL_00c3 IL_00b2: ldarg.0 IL_00b3: ldstr "a" IL_00b8: call "bool string.op_Equality(string, string)" IL_00bd: brfalse.s IL_00c3 IL_00bf: ldc.i4.1 IL_00c0: stloc.0 IL_00c1: br.s IL_00c5 IL_00c3: ldc.i4.0 IL_00c4: stloc.0 IL_00c5: ldloc.0 IL_00c6: ret } """); verifier.VerifyIL("C.LargeSpan", """ { // Code size 252 (0xfc) .maxstack 2 .locals init (bool V_0, uint V_1) IL_0000: ldarg.0 IL_0001: call "uint <PrivateImplementationDetails>.ComputeSpanHash(System.Span<char>)" IL_0006: stloc.1 IL_0007: ldloc.1 IL_0008: ldc.i4 0xe20c2606 IL_000d: bgt.un.s IL_0035 IL_000f: ldloc.1 IL_0010: ldc.i4 0xe00c22e0 IL_0015: beq IL_00ba IL_001a: ldloc.1 IL_001b: ldc.i4 0xe10c2473 IL_0020: beq IL_00a6 IL_0025: ldloc.1 IL_0026: ldc.i4 0xe20c2606 IL_002b: beq IL_00e2 IL_0030: br IL_00f8 IL_0035: ldloc.1 IL_0036: ldc.i4 0xe40c292c IL_003b: bgt.un.s IL_0055 IL_003d: ldloc.1 IL_003e: ldc.i4 0xe30c2799 IL_0043: beq IL_00ce IL_0048: ldloc.1 IL_0049: ldc.i4 0xe40c292c IL_004e: beq.s IL_006a IL_0050: br IL_00f8 IL_0055: ldloc.1 IL_0056: ldc.i4 0xe60c2c52 IL_005b: beq.s IL_0092 IL_005d: ldloc.1 IL_005e: ldc.i4 0xe70c2de5 IL_0063: beq.s IL_007e IL_0065: br IL_00f8 IL_006a: ldarg.0 IL_006b: ldstr "a" IL_0070: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_0075: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_007a: brtrue.s IL_00f4 IL_007c: br.s IL_00f8 IL_007e: ldarg.0 IL_007f: ldstr "b" IL_0084: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_0089: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_008e: brtrue.s IL_00f4 IL_0090: br.s IL_00f8 IL_0092: ldarg.0 IL_0093: ldstr "c" IL_0098: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_009d: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_00a2: brtrue.s IL_00f4 IL_00a4: br.s IL_00f8 IL_00a6: ldarg.0 IL_00a7: ldstr "d" IL_00ac: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_00b1: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_00b6: brtrue.s IL_00f4 IL_00b8: br.s IL_00f8 IL_00ba: ldarg.0 IL_00bb: ldstr "e" IL_00c0: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_00c5: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_00ca: brtrue.s IL_00f4 IL_00cc: br.s IL_00f8 IL_00ce: ldarg.0 IL_00cf: ldstr "f" IL_00d4: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_00d9: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_00de: brtrue.s IL_00f4 IL_00e0: br.s IL_00f8 IL_00e2: ldarg.0 IL_00e3: ldstr "g" IL_00e8: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_00ed: call "bool System.MemoryExtensions.SequenceEqual<char>(System.Span<char>, System.ReadOnlySpan<char>)" IL_00f2: brfalse.s IL_00f8 IL_00f4: ldc.i4.1 IL_00f5: stloc.0 IL_00f6: br.s IL_00fa IL_00f8: ldc.i4.0 IL_00f9: stloc.0 IL_00fa: ldloc.0 IL_00fb: ret } """); verifier.VerifyIL("C.LargeReadOnlySpan", """ { // Code size 134 (0x86) .maxstack 2 .locals init (bool V_0) IL_0000: ldarg.0 IL_0001: ldstr "a" IL_0006: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_000b: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_0010: brtrue.s IL_007e IL_0012: ldarg.0 IL_0013: ldstr "b" IL_0018: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_001d: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_0022: brtrue.s IL_007e IL_0024: ldarg.0 IL_0025: ldstr "c" IL_002a: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_002f: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_0034: brtrue.s IL_007e IL_0036: ldarg.0 IL_0037: ldstr "d" IL_003c: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_0041: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_0046: brtrue.s IL_007e IL_0048: ldarg.0 IL_0049: ldstr "e" IL_004e: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_0053: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_0058: brtrue.s IL_007e IL_005a: ldarg.0 IL_005b: ldstr "f" IL_0060: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_0065: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_006a: brtrue.s IL_007e IL_006c: ldarg.0 IL_006d: ldstr "g" IL_0072: call "System.ReadOnlySpan<char> System.MemoryExtensions.AsSpan(string)" IL_0077: call "bool System.MemoryExtensions.SequenceEqual<char>(System.ReadOnlySpan<char>, System.ReadOnlySpan<char>)" IL_007c: brfalse.s IL_0082 IL_007e: ldc.i4.1 IL_007f: stloc.0 IL_0080: br.s IL_0084 IL_0082: ldc.i4.0 IL_0083: stloc.0 IL_0084: ldloc.0 IL_0085: ret } """); } [Fact] public void ValueSet_UnionNonNullTest_ExplicitType() { // Exercises ValueSet.Filter with BoundDagNonNullTest from unions. // The `int and (1 or 3 or 5)` pattern forces an explicit type test which // generates a non-null test on the union Value temp, before the ValueSet // on the extracted int temp. var source = """ union S1(int, string) { static void Main() { System.Console.Write(Test(new S1(1))); System.Console.Write(Test(new S1(3))); System.Console.Write(Test(new S1(5))); System.Console.Write(Test(new S1(7))); System.Console.Write(Test(new S1("hello"))); } static int Test(S1 u) { return u switch { string => 0, int and (1 or 3 or 5) => 1, _ => 2 }; } } """; var comp = CreateCompilation([source, UnionAttributeSource, IUnionSource], options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t1 = t0.Value; [1] [1]: t1 != null ? [2] : [9] [2]: t1 is string ? [3] : [4] [3]: leaf <arm> `string => 0` [4]: t2 = (int)t1; [5] [5]: t2 == 1 ? [8] : [6] [6]: t2 == 3 ? [8] : [7] [7]: t2 == 5 ? [8] : [9] [8]: leaf <arm> `int and (1 or 3 or 5) => 1` [9]: leaf <arm> `_ => 2` ", forLowering: false); CompileAndVerify(comp, expectedOutput: "11120"); } [Fact] public void ValueSet_UnionNonNullTest_LargeOrPattern() { // Exercises ValueSet with a larger set of or'd values on a union. // Ensures the ValueSet coalescing works correctly with union non-null tests. var source = """ union S1(int, string) { static void Main() { System.Console.Write(Test(new S1(1))); System.Console.Write(Test(new S1(5))); System.Console.Write(Test(new S1(10))); System.Console.Write(Test(new S1(99))); System.Console.Write(Test(new S1("x"))); } static int Test(S1 u) { return u switch { string => 0, int and (1 or 2 or 3 or 4 or 5 or 6 or 7 or 8 or 9 or 10) => 1, _ => 2, }; } } """; var comp = CreateCompilation([source, UnionAttributeSource, IUnionSource], options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); CompileAndVerify(comp, expectedOutput: "11120"); } [Fact] public void ValueSet_UnionNonNullTest_NullableUnion() { // Exercises ValueSet with a nullable union type. // The nullable adds an extra layer of null testing. var source = """ union S1(int, string) { static void Main() { System.Console.Write(Test(new S1(1))); System.Console.Write(Test(new S1(3))); System.Console.Write(Test(new S1(7))); System.Console.Write(Test(new S1("x"))); System.Console.Write(Test(null)); } static int Test(S1? u) { return u switch { null => 9, int and (1 or 3 or 5) => 1, string => 0, _ => 2, }; } } """; var comp = CreateCompilation([source, UnionAttributeSource, IUnionSource], options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t0 == null ? [4] : [1] [1]: t1 = (S1)t0; [2] [2]: t2 = t1.Value; [3] [3]: t2 == null ? [4] : [5] [4]: leaf <arm> `null => 9` [5]: t2 is int ? [6] : [12] [6]: t3 = (int)t2; [7] [7]: t3 == 1 ? [10] : [8] [8]: t3 == 3 ? [10] : [9] [9]: t3 == 5 ? [10] : [11] [10]: leaf <arm> `int and (1 or 3 or 5) => 1` [11]: leaf <arm> `_ => 2` [12]: leaf <arm> `string => 0` ", forLowering: false); CompileAndVerify(comp, expectedOutput: "11209"); } [Fact] public void ValueSet_UnionNonNullTest_WithNullArm() { // Exercises an explicit null arm together with a union value arm that can // form a ValueSet behind an explicit type pattern. var source = """ union S1(int, string) { static void Main() { System.Console.Write(Test(new S1(1))); System.Console.Write(Test(new S1(5))); System.Console.Write(Test(new S1(7))); System.Console.Write(Test(new S1("x"))); } static int Test(S1 u) { return u switch { null => 9, int and (1 or 3 or 5) => 1, string => 0, _ => 2, }; } } """; var comp = CreateCompilation([source, UnionAttributeSource, IUnionSource], options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t1 = t0.Value; [1] [1]: t1 == null ? [2] : [3] [2]: leaf <arm> `null => 9` [3]: t1 is int ? [4] : [10] [4]: t2 = (int)t1; [5] [5]: t2 == 1 ? [8] : [6] [6]: t2 == 3 ? [8] : [7] [7]: t2 == 5 ? [8] : [9] [8]: leaf <arm> `int and (1 or 3 or 5) => 1` [9]: leaf <arm> `_ => 2` [10]: leaf <arm> `string => 0` ", forLowering: false); CompileAndVerify(comp, expectedOutput: "1120"); } [Fact] public void ValueSet_UnionNonNullTest_NotPattern() { // Exercises ValueSet with negated or-patterns on unions. // `not (1 or 3 or 5)` creates a complement ValueSet. var source = """ union S1(int, string) { static void Main() { System.Console.Write(Test(new S1(1))); System.Console.Write(Test(new S1(7))); System.Console.Write(Test(new S1("x"))); } static int Test(S1 u) { return u switch { string => 0, int and not (1 or 3 or 5) => 2, _ => 1, }; } } """; var comp = CreateCompilation([source, UnionAttributeSource, IUnionSource], options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t1 = t0.Value; [1] [1]: t1 != null ? [2] : [8] [2]: t1 is string ? [3] : [4] [3]: leaf <arm> `string => 0` [4]: t2 = (int)t1; [5] [5]: t2 == 1 ? [8] : [6] [6]: t2 == 3 ? [8] : [7] [7]: t2 == 5 ? [8] : [9] [8]: leaf <arm> `_ => 1` [9]: leaf <arm> `int and not (1 or 3 or 5) => 2` ", forLowering: false); CompileAndVerify(comp, expectedOutput: "120"); } [Fact] public void ValueSet_UnionNonNullTest_MultipleArmsWithValues() { // Multiple arms with different value sets on the same union type. var source = """ union S1(int, string) { static void Main() { System.Console.Write(Test(new S1(1))); System.Console.Write(Test(new S1(4))); System.Console.Write(Test(new S1(7))); System.Console.Write(Test(new S1("x"))); } static int Test(S1 u) { return u switch { string => 0, int and (1 or 2 or 3) => 1, int and (4 or 5 or 6) => 2, _ => 3, }; } } """; var comp = CreateCompilation([source, UnionAttributeSource, IUnionSource], options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); VerifyDecisionDagDump<SwitchExpressionSyntax>(comp, @"[0]: t1 = t0.Value; [1] [1]: t1 != null ? [2] : [13] [2]: t1 is string ? [3] : [4] [3]: leaf <arm> `string => 0` [4]: t2 = (int)t1; [5] [5]: t2 == 1 ? [8] : [6] [6]: t2 == 2 ? [8] : [7] [7]: t2 == 3 ? [8] : [9] [8]: leaf <arm> `int and (1 or 2 or 3) => 1` [9]: t2 == 4 ? [12] : [10] [10]: t2 == 5 ? [12] : [11] [11]: t2 == 6 ? [12] : [13] [12]: leaf <arm> `int and (4 or 5 or 6) => 2` [13]: leaf <arm> `_ => 3` ", forLowering: false); CompileAndVerify(comp, expectedOutput: "1230"); } } }