|
// 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
{
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 } }"{ 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 } }"{ 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 } }"{ 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 specifc 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 specifc 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 specifc 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 specifc 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 specifc 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 specifc 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 specifc 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 specifc 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);
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)-1, _)' is not covered.
// return (e1, e2, o) switch
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveWithUnnamedEnumValue, "switch").WithArguments("(Enum.One, (Enum)-1, _)").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 'null' is not covered.
// return this switch // 1
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("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 'null' is not covered.
// return this switch // 2
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustiveForNull, "switch").WithArguments("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)
);
}
}
}
|