/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenOptimizedNullableOperators.cs
5 120 строк
175 KB
Jared Parsons
Do not use nint / nuint in IL baselines (#78246)
28 апр 2025, 22:12
Не верифицирован
28 апр 2025, 22:12
afb3565
Код
Авторство
О чём код?
// 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 Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.UnitTests.Emit; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { public partial class CodeGenOptimizedNullableOperatorTests : CSharpTestBase { [Fact] public void TestNullableBoxingConversionsAlwaysNull() { // The native compiler does not optimize this case; Roslyn does. We know // that the result of boxing default(int?) to object is the same as casting // literal null to object, so we do not need to allocate space on the stack // for the nullable int, initialize it, and then box that to a null ref. string[] sources = { @"class Program { static void Main() { System.Console.WriteLine((object)default(int?)); } } ", @"class Program { static void Main() { System.Console.WriteLine((object)(new int?())); } } ", @"class Program { static void Main() { System.Console.WriteLine((object)(int?)null); } } "}; string expectedOutput = ""; string expectedIL = @"{ // Code size 7 (0x7) .maxstack 1 IL_0000: ldnull IL_0001: call ""void System.Console.WriteLine(object)"" IL_0006: ret }"; foreach (string source in sources) { var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.Main", expectedIL); } } [Fact] public void TestNullableBoxingConversionNeverNull() { // The native compiler does not optimize this case; Roslyn does. We know // that the result of boxing default(int?) to object is the same as casting // literal null to object, so we do not need to allocate space on the stack // for the nullable int, initialize it, and then box that to a null ref. string[] sources = { @"class Program { static void Main() { System.Console.WriteLine((object)new int?(123)); } } ", @"class Program { static void Main() { System.Console.WriteLine((object)(int?)123); } } "}; string expectedOutput = "123"; string expectedIL = @"{ // Code size 13 (0xd) .maxstack 1 IL_0000: ldc.i4.s 123 IL_0002: box ""int"" IL_0007: call ""void System.Console.WriteLine(object)"" IL_000c: ret }"; foreach (string source in sources) { var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.Main", expectedIL); } } [Fact] public void TestNullableConversionAlwaysNull() { // A built-in nullable conversion whose argument is known to always be null // can simply be optimized away to be the null result. string source = @" class Program { static long? M() { return new int?(); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 10 (0xa) .maxstack 1 .locals init (long? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""long?"" IL_0008: ldloc.0 IL_0009: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestNullableConversionNeverNull() { // A built-in nullable conversion whose argument is known to be non-null // can be generated by converting the value to the underlying target type, // and then converting that to nullable, without generating the nullable source // or checking to see if it has a value. string source = @" class Program { static long? M(int x) { return new int?(x); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: ldarg.0 IL_0001: conv.i8 IL_0002: newobj ""long?..ctor(long)"" IL_0007: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestLiftedUserDefinedConversionAlwaysNull() { // A user-defined nullable conversion whose argument is known to always be null // can simply be optimized away to be the null result. string source = @" struct S { public static implicit operator S(int x) { return new S(); } } class Program { static S? M() { return new int?(); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 10 (0xa) .maxstack 1 .locals init (S? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""S?"" IL_0008: ldloc.0 IL_0009: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestLiftedUserDefinedConversionNeverNull() { // A user-defined nullable conversion whose argument is known to never be null // can have the nullable ctor, temporary store and value test optimized away. string source = @" struct S { public static implicit operator S(int x) { return new S(); } public static implicit operator string(S s) { return s.ToString(); } } class Program { static S? M1(int x) { return new int?(x); } static S M2(int x) { return (S)(new int?(x)); } static string M3(int x) { // The non-null conversion optimizer should chain well. That is, // we first optimize (string)(S?)(new int?(x)) to (string)(new S?((S)x)), and then // to (string)(S)x. return (string)(S?)(new int?(x)); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 12 (0xc) .maxstack 1 IL_0000: ldarg.0 IL_0001: call ""S S.op_Implicit(int)"" IL_0006: newobj ""S?..ctor(S)"" IL_000b: ret }"; string expectedIL2 = @"{ // Code size 7 (0x7) .maxstack 1 IL_0000: ldarg.0 IL_0001: call ""S S.op_Implicit(int)"" IL_0006: ret }"; string expectedIL3 = @"{ // Code size 12 (0xc) .maxstack 1 IL_0000: ldarg.0 IL_0001: call ""S S.op_Implicit(int)"" IL_0006: call ""string S.op_Implicit(S)"" IL_000b: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); } [Fact] public void TestNullableUnaryOpsAlwaysNull() { // A unary operator whose argument is known to always be null // can simply be optimized away to be the null result. string source = @" class Program { static int? M() { return ~(new int?()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 10 (0xa) .maxstack 1 .locals init (int? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""int?"" IL_0008: ldloc.0 IL_0009: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics( // (6,16): warning CS0458: The result of the expression is always 'null' of type 'int?' // return ~(new int?()); Diagnostic(ErrorCode.WRN_AlwaysNull, "~(new int?())").WithArguments("int?")); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestNullableUnaryOpsAlwaysNullChained() { // A unary operator whose argument is known to always be null // can simply be optimized away to be the null result. These // optimizations should "chain" naturally. Here we combine // a built-in conversion, two unary operations, and a boxing. // The net result should simply be a null reference. // // The native compiler does not handle these "chained" optimizations, // interestingly enough; the native compiler will optimize away only the // innermost one; it is then not treated as "always null" and is checked // for nullity unnecessarily. // // Fortunately, the warning logic does not do a deep analysis; it only // reports a single warning. string source = @" class Program { static object M() { return ~-(new short?()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 2 (0x2) .maxstack 1 IL_0000: ldnull IL_0001: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics( // (6,17): warning CS0458: The result of the expression is always 'null' of type 'int?' // return ~-(new short?()); Diagnostic(ErrorCode.WRN_AlwaysNull, "-(new short?())").WithArguments("int?")); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestNullableUnaryOpsNeverNull() { // A unary operator whose argument is known to never be null // can be optimized to avoid the null check. string source = @" class Program { static int N() { return 123; } static int? M() { // This can be optimized to new int?(~N()) return ~(new int?(N())); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 12 (0xc) .maxstack 1 IL_0000: call ""int Program.N()"" IL_0005: not IL_0006: newobj ""int?..ctor(int)"" IL_000b: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestNullableUnaryOpsNeverNullChained() { // A unary operator whose argument is known to never be null // can simply be optimized away to be the null result. These // optimizations should "chain" naturally. Here we combine // two unary operations and a boxing. As you can see, we eliminate // all the null checks and the "new S?" ctor. string source = @" struct S { public static S operator +(S s) { return s; } public static S operator -(S s) { return s; } public static S operator ~(S s) { return s; } } class Program { static object M(S s) { return ~-(new S?(s)); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 17 (0x11) .maxstack 1 IL_0000: ldarg.0 IL_0001: call ""S S.op_UnaryNegation(S)"" IL_0006: call ""S S.op_OnesComplement(S)"" IL_000b: box ""S"" IL_0010: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestLiftedUnaryOpOnTopOfLifted() { // Here's an optimization that the dev10 compiler does not do. If we have a // lifted unary operator "on top" of another lifted operation, then the unary // operation can be "distributed" to both branches of the underlying lifted operation. // // For example, suppose we have // // return -(N1() + N2()); // // Where N1() and N2() return int?. The dev10 compiler does this in two steps: first it // computes the int? result of the addition, and then it does a fully lifted negation: // // int? t1 = N1(); // int? t2 = N2(); // int? t3 = t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?(); // return t3.HasValue ? new int?(-t3.Value)) : new int?(); // // But t3 is completely unnecessary here. We could realize this as: // // return -(t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?()) // // which is the same as distributing the conversion to the consequence and alternative: // // return (t1.HasValue && t2.HasValue ? - new int?(t1.Value + t2.Value) ): - ( new int?() ) ) // // and now we can optimize the consequence and alternative down to // // return (t1.HasValue && t2.HasValue ? new int?(-(t1.Value + t2.Value) ): new int?() ) // // And the int? t3 disappears entirely. // // This optimization has the nice property that it composes well with itself, as we'll see. string source = @" struct S { public static S operator -(S s) { return s; } public static S operator ~(S s) { return s; } public static S operator +(S s1, S s2) { return s1; } } class Program { static int? N1() { return 1; } static int? N2() { return 2; } static S? N3() { return null; } static S? N4() { return null; } static int? M1() { return -(N1() + N2()); } static S? M2() { return -~(N3() + N4()); } static void Main() { } } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 61 (0x3d) .maxstack 2 .locals init (int? V_0, int? V_1, int? V_2) IL_0000: call ""int? Program.N1()"" IL_0005: stloc.0 IL_0006: call ""int? Program.N2()"" IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call ""bool int?.HasValue.get"" IL_0013: ldloca.s V_1 IL_0015: call ""bool int?.HasValue.get"" IL_001a: and IL_001b: brtrue.s IL_0027 IL_001d: ldloca.s V_2 IL_001f: initobj ""int?"" IL_0025: ldloc.2 IL_0026: ret IL_0027: ldloca.s V_0 IL_0029: call ""int int?.GetValueOrDefault()"" IL_002e: ldloca.s V_1 IL_0030: call ""int int?.GetValueOrDefault()"" IL_0035: add IL_0036: neg IL_0037: newobj ""int?..ctor(int)"" IL_003c: ret } "; string expectedIL2 = @"{ // Code size 74 (0x4a) .maxstack 2 .locals init (S? V_0, S? V_1, S? V_2) IL_0000: call ""S? Program.N3()"" IL_0005: stloc.0 IL_0006: call ""S? Program.N4()"" IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call ""bool S?.HasValue.get"" IL_0013: ldloca.s V_1 IL_0015: call ""bool S?.HasValue.get"" IL_001a: and IL_001b: brtrue.s IL_0027 IL_001d: ldloca.s V_2 IL_001f: initobj ""S?"" IL_0025: ldloc.2 IL_0026: ret IL_0027: ldloca.s V_0 IL_0029: call ""S S?.GetValueOrDefault()"" IL_002e: ldloca.s V_1 IL_0030: call ""S S?.GetValueOrDefault()"" IL_0035: call ""S S.op_Addition(S, S)"" IL_003a: call ""S S.op_OnesComplement(S)"" IL_003f: call ""S S.op_UnaryNegation(S)"" IL_0044: newobj ""S?..ctor(S)"" IL_0049: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); } [Fact] public void TestLiftedBinaryOpWithConstantOnTopOfLifted() { // If we have a lifted binary operation "on top" of a lifted operation, // and the right side of the outer operation is a constant, then we // can eliminate several temporaries. // // For example, suppose we have // // return N1() * N2() + 1; // // Where N1() and N2() return int?. We could do this the obvious way: // // int? n1 = N1(); // int? n2 = N2(); // int? r = n1.HasValue && n2.HasValue ? new int?(n1.Value * n2.Value) : new int?(); // int v = 1; // return r.HasValue ? new int?(r.Value + v)) : new int?(); // // But r and v are both unnecessary. We could instead realize this as: // // int? n1 = N1(); // int? n2 = N2(); // return n1.HasValue && n2.HasValue ? new int?(n1.Value * n2.Value + 1) : new int?(); // // We want to do this optimization in particular because it makes codegen for i++ // and i+=1 better. // // The dev10 compiler does this optimization *only* on i++ and not on expressions // like N1() * N2() + 1 or sh+=1; string source = @" class Program { static int? N1() { return 1; } static int? N2() { return 2; } static short? sh; static int? M1() { return N1() * N2() + 1; } static short? M2() { return sh++; } static short? M3() { return ++sh; } static short? M4() { return sh += 1; } static void Main() { } } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 62 (0x3e) .maxstack 2 .locals init (int? V_0, int? V_1, int? V_2) IL_0000: call ""int? Program.N1()"" IL_0005: stloc.0 IL_0006: call ""int? Program.N2()"" IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call ""bool int?.HasValue.get"" IL_0013: ldloca.s V_1 IL_0015: call ""bool int?.HasValue.get"" IL_001a: and IL_001b: brtrue.s IL_0027 IL_001d: ldloca.s V_2 IL_001f: initobj ""int?"" IL_0025: ldloc.2 IL_0026: ret IL_0027: ldloca.s V_0 IL_0029: call ""int int?.GetValueOrDefault()"" IL_002e: ldloca.s V_1 IL_0030: call ""int int?.GetValueOrDefault()"" IL_0035: mul IL_0036: ldc.i4.1 IL_0037: add IL_0038: newobj ""int?..ctor(int)"" IL_003d: ret }"; string expectedIL2 = @"{ // Code size 48 (0x30) .maxstack 3 .locals init (short? V_0, short? V_1) IL_0000: ldsfld ""short? Program.sh"" IL_0005: dup IL_0006: stloc.0 IL_0007: ldloca.s V_0 IL_0009: call ""bool short?.HasValue.get"" IL_000e: brtrue.s IL_001b IL_0010: ldloca.s V_1 IL_0012: initobj ""short?"" IL_0018: ldloc.1 IL_0019: br.s IL_002a IL_001b: ldloca.s V_0 IL_001d: call ""short short?.GetValueOrDefault()"" IL_0022: ldc.i4.1 IL_0023: add IL_0024: conv.i2 IL_0025: newobj ""short?..ctor(short)"" IL_002a: stsfld ""short? Program.sh"" IL_002f: ret }"; string expectedIL3 = @"{ // Code size 48 (0x30) .maxstack 2 .locals init (short? V_0, short? V_1) IL_0000: ldsfld ""short? Program.sh"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool short?.HasValue.get"" IL_000d: brtrue.s IL_001a IL_000f: ldloca.s V_1 IL_0011: initobj ""short?"" IL_0017: ldloc.1 IL_0018: br.s IL_0029 IL_001a: ldloca.s V_0 IL_001c: call ""short short?.GetValueOrDefault()"" IL_0021: ldc.i4.1 IL_0022: add IL_0023: conv.i2 IL_0024: newobj ""short?..ctor(short)"" IL_0029: dup IL_002a: stsfld ""short? Program.sh"" IL_002f: ret }"; string expectedIL4 = @"{ // Code size 48 (0x30) .maxstack 2 .locals init (short? V_0, short? V_1) IL_0000: ldsfld ""short? Program.sh"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool short?.HasValue.get"" IL_000d: brtrue.s IL_001a IL_000f: ldloca.s V_1 IL_0011: initobj ""short?"" IL_0017: ldloc.1 IL_0018: br.s IL_0029 IL_001a: ldloca.s V_0 IL_001c: call ""short short?.GetValueOrDefault()"" IL_0021: ldc.i4.1 IL_0022: add IL_0023: conv.i2 IL_0024: newobj ""short?..ctor(short)"" IL_0029: dup IL_002a: stsfld ""short? Program.sh"" IL_002f: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); comp.VerifyIL("Program.M4", expectedIL4); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56875")] public void LiftedBinaryOp_OneNonNull_Custom() { var source = """ using System; C.M(DateTime.MaxValue); C.M(default(DateTime)); C.M(new DateTime(42)); C.M(null); class C { static void Write(bool b) => Console.Write(b ? 1 : 0); public static void M(DateTime? d) { M1(d); M2(d); M3(d); Console.Write(' '); } static void M1(DateTime? d) { Write(d == DateTime.MaxValue); } static void M2(DateTime? d) { Write(d != DateTime.MaxValue); } static void M3(DateTime? d) { Write(DateTime.MaxValue != d); } } """; var expectedOutput = "100 011 011 011"; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: expectedOutput); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M1", """ { // Code size 39 (0x27) .maxstack 2 .locals init (System.DateTime? V_0, System.DateTime V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldsfld "System.DateTime System.DateTime.MaxValue" IL_0007: stloc.1 IL_0008: ldloca.s V_0 IL_000a: call "bool System.DateTime?.HasValue.get" IL_000f: brtrue.s IL_0014 IL_0011: ldc.i4.0 IL_0012: br.s IL_0021 IL_0014: ldloca.s V_0 IL_0016: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_001b: ldloc.1 IL_001c: call "bool System.DateTime.op_Equality(System.DateTime, System.DateTime)" IL_0021: call "void C.Write(bool)" IL_0026: ret } """); verifier.VerifyIL("C.M3", """ { // Code size 39 (0x27) .maxstack 2 .locals init (System.DateTime V_0, System.DateTime? V_1) IL_0000: ldsfld "System.DateTime System.DateTime.MaxValue" IL_0005: stloc.0 IL_0006: ldarg.0 IL_0007: stloc.1 IL_0008: ldloca.s V_1 IL_000a: call "bool System.DateTime?.HasValue.get" IL_000f: brtrue.s IL_0014 IL_0011: ldc.i4.1 IL_0012: br.s IL_0021 IL_0014: ldloc.0 IL_0015: ldloca.s V_1 IL_0017: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_001c: call "bool System.DateTime.op_Inequality(System.DateTime, System.DateTime)" IL_0021: call "void C.Write(bool)" IL_0026: ret } """); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: expectedOutput); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M1", """ { // Code size 41 (0x29) .maxstack 2 .locals init (System.DateTime? V_0, System.DateTime V_1) IL_0000: nop IL_0001: ldarg.0 IL_0002: stloc.0 IL_0003: ldsfld "System.DateTime System.DateTime.MaxValue" IL_0008: stloc.1 IL_0009: ldloca.s V_0 IL_000b: call "bool System.DateTime?.HasValue.get" IL_0010: brtrue.s IL_0015 IL_0012: ldc.i4.0 IL_0013: br.s IL_0022 IL_0015: ldloca.s V_0 IL_0017: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_001c: ldloc.1 IL_001d: call "bool System.DateTime.op_Equality(System.DateTime, System.DateTime)" IL_0022: call "void C.Write(bool)" IL_0027: nop IL_0028: ret } """); verifier.VerifyIL("C.M3", """ { // Code size 41 (0x29) .maxstack 2 .locals init (System.DateTime V_0, System.DateTime? V_1) IL_0000: nop IL_0001: ldsfld "System.DateTime System.DateTime.MaxValue" IL_0006: stloc.0 IL_0007: ldarg.0 IL_0008: stloc.1 IL_0009: ldloca.s V_1 IL_000b: call "bool System.DateTime?.HasValue.get" IL_0010: brtrue.s IL_0015 IL_0012: ldc.i4.1 IL_0013: br.s IL_0022 IL_0015: ldloc.0 IL_0016: ldloca.s V_1 IL_0018: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_001d: call "bool System.DateTime.op_Inequality(System.DateTime, System.DateTime)" IL_0022: call "void C.Write(bool)" IL_0027: nop IL_0028: ret } """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56875")] public void LiftedBinaryOp_OneNonNull_BuiltIn() { var source = """ using System; C.M(int.MaxValue); C.M(int.MinValue); C.M(0); C.M(42); C.M(null); class C { static void Write(bool b) => Console.Write(b ? 1 : 0); public static void M(int? x) { M1(x); M2(x); M3(x); Console.Write(' '); } static void M1(int? x) { Write(x == int.MaxValue); } static void M2(int? x) { Write(x != int.MaxValue); } static void M3(int? x) { Write(int.MaxValue != x); } } """; var expectedOutput = "100 011 011 011 011"; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: expectedOutput); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M1", """ { // Code size 20 (0x14) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "int int?.GetValueOrDefault()" IL_0007: ldc.i4 0x7fffffff IL_000c: ceq IL_000e: call "void C.Write(bool)" IL_0013: ret } """); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: expectedOutput); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M1", """ { // Code size 22 (0x16) .maxstack 2 IL_0000: nop IL_0001: ldarga.s V_0 IL_0003: call "int int?.GetValueOrDefault()" IL_0008: ldc.i4 0x7fffffff IL_000d: ceq IL_000f: call "void C.Write(bool)" IL_0014: nop IL_0015: ret } """); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56875")] public void LiftedBinaryOp_BothUnknown_Custom() { var source = """ using System; C.M(DateTime.MaxValue, null); C.M(null, DateTime.MinValue); C.M(new DateTime(42), null); C.M(null, null); class C { static void Write(bool b) => Console.Write(b ? 1 : 0); public static void M(DateTime? d1, DateTime? d2) { M1(d1, d2); M2(d1, d2); Console.Write(' '); } static void M1(DateTime? d1, DateTime? d2) { Write(d1 == d2); } static void M2(DateTime? d1, DateTime? d2) { Write(d1 != d2); } } """; var expectedOutput = "01 01 01 10"; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: expectedOutput); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M1", """ { // Code size 60 (0x3c) .maxstack 2 .locals init (System.DateTime? V_0, System.DateTime? V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldarg.1 IL_0003: stloc.1 IL_0004: ldloca.s V_0 IL_0006: call "bool System.DateTime?.HasValue.get" IL_000b: ldloca.s V_1 IL_000d: call "bool System.DateTime?.HasValue.get" IL_0012: beq.s IL_0017 IL_0014: ldc.i4.0 IL_0015: br.s IL_0036 IL_0017: ldloca.s V_0 IL_0019: call "bool System.DateTime?.HasValue.get" IL_001e: brtrue.s IL_0023 IL_0020: ldc.i4.1 IL_0021: br.s IL_0036 IL_0023: ldloca.s V_0 IL_0025: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_002a: ldloca.s V_1 IL_002c: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_0031: call "bool System.DateTime.op_Equality(System.DateTime, System.DateTime)" IL_0036: call "void C.Write(bool)" IL_003b: ret } """); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: expectedOutput); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M1", """ { // Code size 62 (0x3e) .maxstack 2 .locals init (System.DateTime? V_0, System.DateTime? V_1) IL_0000: nop IL_0001: ldarg.0 IL_0002: stloc.0 IL_0003: ldarg.1 IL_0004: stloc.1 IL_0005: ldloca.s V_0 IL_0007: call "bool System.DateTime?.HasValue.get" IL_000c: ldloca.s V_1 IL_000e: call "bool System.DateTime?.HasValue.get" IL_0013: beq.s IL_0018 IL_0015: ldc.i4.0 IL_0016: br.s IL_0037 IL_0018: ldloca.s V_0 IL_001a: call "bool System.DateTime?.HasValue.get" IL_001f: brtrue.s IL_0024 IL_0021: ldc.i4.1 IL_0022: br.s IL_0037 IL_0024: ldloca.s V_0 IL_0026: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_002b: ldloca.s V_1 IL_002d: call "System.DateTime System.DateTime?.GetValueOrDefault()" IL_0032: call "bool System.DateTime.op_Equality(System.DateTime, System.DateTime)" IL_0037: call "void C.Write(bool)" IL_003c: nop IL_003d: ret } """); } [Fact] public void TestNullableComparisonOpsBothAlwaysNull() { // An ==, !=, <, >, <= or >= operation where both operands // are null is always true for equality, and always false otherwise. // Note that the native compiler has a bug; it does not produce the warning // "comparing null with S? always produces false" -- it incorrectly warns // that it produces a null of type bool? ! Roslyn does not reproduce this bug. string source = @" struct S // User-defined relational ops { public static bool operator ==(S x, S y) { return true; } public static bool operator !=(S x, S y) { return true; } public static bool operator <(S x, S y) { return true; } public static bool operator >(S x, S y) { return true; } public static bool operator <=(S x, S y) { return true; } public static bool operator >=(S x, S y) { return true; } public override bool Equals(object x) { return true; } public override int GetHashCode() { return 0; } } struct T // no user-defined relational ops { } class Program { static bool M1() { return new int?() == new short?(); } static bool M2() { return default(double?) != new short?(); } static bool M3() { return ((int?)null) < new decimal?(); } static bool M4() { return new S?() == new S?(); } static bool M5() { return default(S?) != new S?(); } static bool M6() { return ((S?)null) < new S?(); } static bool M7() // Special case for equality with null literal and no overloaded operators. { return default(T?) == null; } static bool M8() { return null != new T?(); } static void Main() {} } "; string expectedOutput = ""; string expectedILTrue = @"{ // Code size 2 (0x2) .maxstack 1 IL_0000: ldc.i4.1 IL_0001: ret }"; string expectedILFalse = @"{ // Code size 2 (0x2) .maxstack 1 IL_0000: ldc.i4.0 IL_0001: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics( // (25,16): warning CS0464: Comparing with null of type 'decimal?' always produces 'false' // return ((int?)null) < new decimal?(); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < new decimal?()").WithArguments("decimal?"), // (37,16): warning CS0464: Comparing with null of type 'S?' always produces 'false' // return ((S?)null) < new S?(); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < new S?()").WithArguments("S?")); comp.VerifyIL("Program.M1", expectedILTrue); comp.VerifyIL("Program.M2", expectedILFalse); comp.VerifyIL("Program.M3", expectedILFalse); comp.VerifyIL("Program.M4", expectedILTrue); comp.VerifyIL("Program.M5", expectedILFalse); comp.VerifyIL("Program.M6", expectedILFalse); comp.VerifyIL("Program.M7", expectedILTrue); comp.VerifyIL("Program.M8", expectedILFalse); } [Fact] public void TestNullableComparisonNonNullWithLiteralNull() { // We can optimize this away to simply evaluating N() for its side effects // and returning false. string source = @" struct S {} class Program { static S N() { System.Console.WriteLine(123); return new S(); } static bool M() { return new S?(N()) == null; } static void Main() {M();} } "; string expectedOutput = "123"; string expectedIL = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""S Program.N()"" IL_0005: pop IL_0006: ldc.i4.0 IL_0007: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyIL("Program.M", expectedIL); } [Fact] public void TestNullableComparisonOpsBothNeverNull() { // An ==, !=, <, >, <= or >= operation where both operands // are not null simply drops the lifting logic entirely. string source = @" struct S // User-defined relational ops { public static bool operator ==(S x, S y) { return true; } public static bool operator !=(S x, S y) { return true; } public static bool operator <(S x, S y) { return true; } public static bool operator >(S x, S y) { return true; } public static bool operator <=(S x, S y) { return true; } public static bool operator >=(S x, S y) { return true; } public override bool Equals(object x) { return true; } public override int GetHashCode() { return 0; } } class Program { static int N1() { return 123; } static short N2() { return 123; } static double N3() { return 123; } static decimal N4() { return 123; } static S N5() { return new S(); } static bool M1() { // Notice that there are two optimizations here and in the next few cases. // First we optimize the conversion from short? to int? so that the right // hand side is new int?((int)N2()). Second, we optimize the comparison to // N1() == (int)N2(), eliminating all the lifting. return new int?(N1()) == new short?(N2()); } static bool M2() { return new double?(N3()) != new short?(N2()); } static bool M3() { return new int?(N1()) < new decimal?(N4()); } static bool M4() { return new S?(N5()) == new S?(N5()); } static bool M5() { return new S?(N5()) != new S?(N5()); } static bool M6() { return new S?(N5()) < new S?(N5()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 13 (0xd) .maxstack 2 IL_0000: call ""int Program.N1()"" IL_0005: call ""short Program.N2()"" IL_000a: ceq IL_000c: ret }"; string expectedIL2 = @"{ // Code size 17 (0x11) .maxstack 2 IL_0000: call ""double Program.N3()"" IL_0005: call ""short Program.N2()"" IL_000a: conv.r8 IL_000b: ceq IL_000d: ldc.i4.0 IL_000e: ceq IL_0010: ret }"; string expectedIL3 = @"{ // Code size 21 (0x15) .maxstack 2 IL_0000: call ""int Program.N1()"" IL_0005: call ""decimal decimal.op_Implicit(int)"" IL_000a: call ""decimal Program.N4()"" IL_000f: call ""bool decimal.op_LessThan(decimal, decimal)"" IL_0014: ret } "; string expectedIL4 = @"{ // Code size 16 (0x10) .maxstack 2 IL_0000: call ""S Program.N5()"" IL_0005: call ""S Program.N5()"" IL_000a: call ""bool S.op_Equality(S, S)"" IL_000f: ret }"; string expectedIL5 = @"{ // Code size 16 (0x10) .maxstack 2 IL_0000: call ""S Program.N5()"" IL_0005: call ""S Program.N5()"" IL_000a: call ""bool S.op_Inequality(S, S)"" IL_000f: ret }"; string expectedIL6 = @"{ // Code size 16 (0x10) .maxstack 2 IL_0000: call ""S Program.N5()"" IL_0005: call ""S Program.N5()"" IL_000a: call ""bool S.op_LessThan(S, S)"" IL_000f: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); comp.VerifyIL("Program.M4", expectedIL4); comp.VerifyIL("Program.M5", expectedIL5); comp.VerifyIL("Program.M6", expectedIL6); } [Fact, WorkItem(663, "https://github.com/dotnet/roslyn/issues/663")] public void TestNullableComparisonOpsOneNullOneNonNull() { // An ==, !=, <, >, <= or >= operation where one operand is null and the // other is non-null is always false except for inequality, which is true. // We can skip the lifting and just generate the side effect. // Note that Roslyn produces considerably more warnings here than the // native compiler; the native compiler only produces warnings for // "((int?)null) < new decimal?(N3())" and "((S?)null) < new S?(N4())". // For compatibility Roslyn reports the same diagnostics by default, // but in "strict" mode (which will be part of the "warning waves" once // we do that) Roslyn will report warnings for // new S() == null and new S() != null. string source = @" struct S // User-defined relational ops { public static bool operator ==(S x, S y) { return true; } public static bool operator !=(S x, S y) { return true; } public static bool operator <(S x, S y) { return true; } public static bool operator >(S x, S y) { return true; } public static bool operator <=(S x, S y) { return true; } public static bool operator >=(S x, S y) { return true; } public override bool Equals(object x) { return true; } public override int GetHashCode() { return 0; } } class Program { static int N1() { return 1; } static short N2() { return 1; } static decimal N3() { return 1; } static S N4() { return new S(); } static bool M1() { return new int?(N1()) == new short?(); } static bool M2() { return default(double?) != new short?(N2()); } static bool M3() { return ((int?)null) < new decimal?(N3()); } static bool M4() { return new S?() == new S?(N4()); } static bool M5() { return default(S?) != new S?(N4()); } static bool M6() { return ((S?)null) < new S?(N4()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""int Program.N1()"" IL_0005: pop IL_0006: ldc.i4.0 IL_0007: ret }"; string expectedIL2 = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""short Program.N2()"" IL_0005: pop IL_0006: ldc.i4.1 IL_0007: ret }"; string expectedIL3 = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""decimal Program.N3()"" IL_0005: pop IL_0006: ldc.i4.0 IL_0007: ret }"; string expectedIL4 = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""S Program.N4()"" IL_0005: pop IL_0006: ldc.i4.0 IL_0007: ret }"; string expectedIL5 = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""S Program.N4()"" IL_0005: pop IL_0006: ldc.i4.1 IL_0007: ret }"; string expectedIL6 = expectedIL4; CompileAndVerify(source, expectedOutput: expectedOutput, options: TestOptions.ReleaseExe.WithWarningLevel(4)).VerifyDiagnostics( // (21,16): warning CS0472: The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'short?' // return new int?(N1()) == new short?(); Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "new int?(N1()) == new short?()").WithArguments("false", "int", "short?").WithLocation(21, 16), // (25,16): warning CS0472: The result of the expression is always 'true' since a value of type 'double' is never equal to 'null' of type 'double?' // return default(double?) != new short?(N2()); Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "default(double?) != new short?(N2())").WithArguments("true", "double", "double?").WithLocation(25, 16), // (29,16): warning CS0464: Comparing with null of type 'int?' always produces 'false' // return ((int?)null) < new decimal?(N3()); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < new decimal?(N3())").WithArguments("int?").WithLocation(29, 16), // (41,16): warning CS0464: Comparing with null of type 'S?' always produces 'false' // return ((S?)null) < new S?(N4()); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < new S?(N4())").WithArguments("S?").WithLocation(41, 16) ); var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics( // (21,16): warning CS0472: The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'short?' // return new int?(N1()) == new short?(); Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "new int?(N1()) == new short?()").WithArguments("false", "int", "short?").WithLocation(21, 16), // (25,16): warning CS0472: The result of the expression is always 'true' since a value of type 'double' is never equal to 'null' of type 'double?' // return default(double?) != new short?(N2()); Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "default(double?) != new short?(N2())").WithArguments("true", "double", "double?").WithLocation(25, 16), // (29,16): warning CS0464: Comparing with null of type 'int?' always produces 'false' // return ((int?)null) < new decimal?(N3()); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < new decimal?(N3())").WithArguments("int?").WithLocation(29, 16), // (33,16): warning CS8073: The result of the expression is always 'false' since a value of type 'S' is never equal to 'null' of type 'S?' // return new S?() == new S?(N4()); Diagnostic(ErrorCode.WRN_NubExprIsConstBool2, "new S?() == new S?(N4())").WithArguments("false", "S", "S?").WithLocation(33, 16), // (37,16): warning CS8073: The result of the expression is always 'true' since a value of type 'S' is never equal to 'null' of type 'S?' // return default(S?) != new S?(N4()); Diagnostic(ErrorCode.WRN_NubExprIsConstBool2, "default(S?) != new S?(N4())").WithArguments("true", "S", "S?").WithLocation(37, 16), // (41,16): warning CS0464: Comparing with null of type 'S?' always produces 'false' // return ((S?)null) < new S?(N4()); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < new S?(N4())").WithArguments("S?").WithLocation(41, 16) ); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); comp.VerifyIL("Program.M4", expectedIL4); comp.VerifyIL("Program.M5", expectedIL5); comp.VerifyIL("Program.M6", expectedIL6); } [Fact] public void TestNullableComparisonOpsOneNullOneUnknown() { // An <, >, <= or >= operation where one operand is null and the // other is unknown is always false; we can skip the lifting and // generate the side effect. // // An == or != operation where one operand is null and the other is // unknown turns into a call to HasValue. // // As mentioned above, the native compiler gets one of the warnings wrong; // Roslyn gets it right. string source = @" struct S // User-defined relational ops { public static bool operator ==(S x, S y) { return true; } public static bool operator !=(S x, S y) { return true; } public static bool operator <(S x, S y) { return true; } public static bool operator >(S x, S y) { return true; } public static bool operator <=(S x, S y) { return true; } public static bool operator >=(S x, S y) { return true; } public override bool Equals(object x) { return true; } public override int GetHashCode() { return 0; } } class Program { static int? N1() { return 1; } static short? N2() { return 1; } static decimal? N3() { return 1; } static S? N4() { return new S(); } static bool M1() { return N1() == new short?(); } static bool M2() { return default(double?) != N2(); } static bool M3() { return ((int?)null) < N3(); } static bool M4() { return new S?() == N4(); } static bool M5() { return default(S?) != N4(); } static bool M6() { return ((S?)null) < N4(); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 17 (0x11) .maxstack 2 .locals init (int? V_0) IL_0000: call ""int? Program.N1()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool int?.HasValue.get"" IL_000d: ldc.i4.0 IL_000e: ceq IL_0010: ret }"; // TODO: Roslyn and the native compiler both produce this sub-optimal code for // TODO: "default(double?) != N2()". We are essentially generating: // TODO: // TODO: short? temp1 = N2(); // TODO: double? temp2 = temp1.HasValue ? new double?((double)temp1.GetValueOrDefault()) : new double?(); // TODO: return temp2.HasValue; // TODO: // TODO: We could be instead simply generating // TODO: // TODO: return N2().HasValue(); string expectedIL2 = @"{ // Code size 48 (0x30) .maxstack 1 .locals init (short? V_0, double? V_1) IL_0000: call ""short? Program.N2()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool short?.HasValue.get"" IL_000d: brtrue.s IL_001a IL_000f: ldloca.s V_1 IL_0011: initobj ""double?"" IL_0017: ldloc.1 IL_0018: br.s IL_0027 IL_001a: ldloca.s V_0 IL_001c: call ""short short?.GetValueOrDefault()"" IL_0021: conv.r8 IL_0022: newobj ""double?..ctor(double)"" IL_0027: stloc.1 IL_0028: ldloca.s V_1 IL_002a: call ""bool double?.HasValue.get"" IL_002f: ret }"; string expectedIL3 = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""decimal? Program.N3()"" IL_0005: pop IL_0006: ldc.i4.0 IL_0007: ret }"; string expectedIL4 = @"{ // Code size 17 (0x11) .maxstack 2 .locals init (S? V_0) IL_0000: call ""S? Program.N4()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool S?.HasValue.get"" IL_000d: ldc.i4.0 IL_000e: ceq IL_0010: ret }"; string expectedIL5 = @"{ // Code size 14 (0xe) .maxstack 1 .locals init (S? V_0) IL_0000: call ""S? Program.N4()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool S?.HasValue.get"" IL_000d: ret }"; string expectedIL6 = @"{ // Code size 8 (0x8) .maxstack 1 IL_0000: call ""S? Program.N4()"" IL_0005: pop IL_0006: ldc.i4.0 IL_0007: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics( // (29,16): warning CS0464: Comparing with null of type 'int?' always produces 'false' // return ((int?)null) < N3(); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < N3()").WithArguments("int?"), // (41,16): warning CS0464: Comparing with null of type 'S?' always produces 'false' // return ((S?)null) < N4(); Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < N4()").WithArguments("S?") ); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); comp.VerifyIL("Program.M4", expectedIL4); comp.VerifyIL("Program.M5", expectedIL5); comp.VerifyIL("Program.M6", expectedIL6); } [Fact] public void TestNullableComparisonOpsOneNonNullOneUnknown() { // When we have a lifted comparison where we know that one side // is definitely not null, but know nothing about the other, then // we make a slight modification to the code generation. For example, // suppose X() and Y() return int?. For "return X() < Y();" we would generate: // int? x = X(); // int? y = Y(); // return x.GetValueOrDefault() < y.GetValueOrDefault() ? x.HasValue & y.HasValue : false; // // But suppose Z() returns int. For X() < Z(), rather than converting Z() to int? and doing the // same codegen as before, we simplify the codegen to: // // int? x = X(); // int z = Z(); // return x.GetValueOrDefault() < z ? x.HasValue : false; // // We apply this same pattern to all lifted comparison operators. string source = @" struct S // User-defined relational ops { public static bool operator ==(S x, S y) { return true; } public static bool operator !=(S x, S y) { return true; } public static bool operator <(S x, S y) { return true; } public static bool operator >(S x, S y) { return true; } public static bool operator <=(S x, S y) { return true; } public static bool operator >=(S x, S y) { return true; } public override bool Equals(object x) { return true; } public override int GetHashCode() { return 0; } } class Program { static int? N1() { return 1; } static short? N2() { return 1; } static decimal? N3() { return 1; } static S? N4() { return new S(); } static int V1() { return 1; } static short V2() { return 1; } static decimal V3() { return 1; } static S V4() { return new S(); } static bool M1() { return N1() == new short?(V2()); } static bool M2() { return N1() < new decimal?(V3()); } static bool M3() { return new S?(V4()) == N4(); } static bool M4() { return new S?(V4()) != N4(); } static bool M5() { return new S?(V4()) < N4(); } static void Main() { } } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 31 (0x1f) .maxstack 2 .locals init (int? V_0, int V_1) IL_0000: call ""int? Program.N1()"" IL_0005: stloc.0 IL_0006: call ""short Program.V2()"" IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call ""int int?.GetValueOrDefault()"" IL_0013: ldloc.1 IL_0014: ceq IL_0016: ldloca.s V_0 IL_0018: call ""bool int?.HasValue.get"" IL_001d: and IL_001e: ret }"; // TODO: We do a worse job than the native compiler here. Find out why. string expectedIL2 = @"{ // Code size 72 (0x48) .maxstack 2 .locals init (decimal? V_0, decimal V_1, int? V_2, decimal? V_3) IL_0000: call ""int? Program.N1()"" IL_0005: stloc.2 IL_0006: ldloca.s V_2 IL_0008: call ""bool int?.HasValue.get"" IL_000d: brtrue.s IL_001a IL_000f: ldloca.s V_3 IL_0011: initobj ""decimal?"" IL_0017: ldloc.3 IL_0018: br.s IL_002b IL_001a: ldloca.s V_2 IL_001c: call ""int int?.GetValueOrDefault()"" IL_0021: call ""decimal decimal.op_Implicit(int)"" IL_0026: newobj ""decimal?..ctor(decimal)"" IL_002b: stloc.0 IL_002c: call ""decimal Program.V3()"" IL_0031: stloc.1 IL_0032: ldloca.s V_0 IL_0034: call ""decimal decimal?.GetValueOrDefault()"" IL_0039: ldloc.1 IL_003a: call ""bool decimal.op_LessThan(decimal, decimal)"" IL_003f: ldloca.s V_0 IL_0041: call ""bool decimal?.HasValue.get"" IL_0046: and IL_0047: ret }"; string expectedIL3 = @"{ // Code size 37 (0x25) .maxstack 2 .locals init (S V_0, S? V_1) IL_0000: call ""S Program.V4()"" IL_0005: stloc.0 IL_0006: call ""S? Program.N4()"" IL_000b: stloc.1 IL_000c: ldloca.s V_1 IL_000e: call ""bool S?.HasValue.get"" IL_0013: brtrue.s IL_0017 IL_0015: ldc.i4.0 IL_0016: ret IL_0017: ldloc.0 IL_0018: ldloca.s V_1 IL_001a: call ""S S?.GetValueOrDefault()"" IL_001f: call ""bool S.op_Equality(S, S)"" IL_0024: ret }"; string expectedIL4 = @"{ // Code size 37 (0x25) .maxstack 2 .locals init (S V_0, S? V_1) IL_0000: call ""S Program.V4()"" IL_0005: stloc.0 IL_0006: call ""S? Program.N4()"" IL_000b: stloc.1 IL_000c: ldloca.s V_1 IL_000e: call ""bool S?.HasValue.get"" IL_0013: brtrue.s IL_0017 IL_0015: ldc.i4.1 IL_0016: ret IL_0017: ldloc.0 IL_0018: ldloca.s V_1 IL_001a: call ""S S?.GetValueOrDefault()"" IL_001f: call ""bool S.op_Inequality(S, S)"" IL_0024: ret }"; string expectedIL5 = @"{ // Code size 37 (0x25) .maxstack 2 .locals init (S V_0, S? V_1) IL_0000: call ""S Program.V4()"" IL_0005: stloc.0 IL_0006: call ""S? Program.N4()"" IL_000b: stloc.1 IL_000c: ldloca.s V_1 IL_000e: call ""bool S?.HasValue.get"" IL_0013: brtrue.s IL_0017 IL_0015: ldc.i4.0 IL_0016: ret IL_0017: ldloc.0 IL_0018: ldloca.s V_1 IL_001a: call ""S S?.GetValueOrDefault()"" IL_001f: call ""bool S.op_LessThan(S, S)"" IL_0024: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); comp.VerifyIL("Program.M4", expectedIL4); comp.VerifyIL("Program.M5", expectedIL5); } [Fact] public void TestLiftedConversionOnTopOfLifted() { // Here's an optimization that the dev10 compiler does not do except in some special cases. // If we have a lifted conversion "on top" of another lifted operation, then the lifted // conversion can be "distributed" to both branches of the underlying lifted operation. // // For example, suppose we have // // return (double?)(N1() + N2()); // // Where N1 and N2 return int?. The dev10 compiler does this in two steps: first it // computes the int? result of the addition, and then it converts that int? to double? // with a lifted conversion. Basically, it generates: // // int? t1 = N1(); // int? t2 = N2(); // int? t3 = t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?(); // double? t4 = t3.HasValue ? new double?((double)t3.Value)) : new double?(); // // But t3 is completely unnecessary here. We observe that the lifted conversion: // // (double?) (t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?()) // // Is the same as distributing the conversion to the consequence and alternative: // // (t1.HasValue && t2.HasValue ? (double?)( new int?(t1.Value + t2.Value) ): (double?) ( new int?() ) ) // // And now we can optimize the consequence and alternative down to // // (t1.HasValue && t2.HasValue ? new double?((double)(t1.Value + t2.Value) ): new double?() ) // // And the int? t3 disappears entirely. // // This optimization has the nice property that it composes well with itself. string source = @" struct S { public static S operator -(S s) { return s; } public static implicit operator int(S s) { return 1; } } class Program { static int? N1() { return 1; } static int? N2() { return 1; } static S? N3() { return null; } // Start with a nice simple case; we have a lifted numeric conversion on top of a // lifted addition. static long? M1() { return N1() + N2(); } // This should also work with lifted user-defined conversions, and work on top // of a lifted unary operator. static int? M2() { return -N3(); } static void Main() { } } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 61 (0x3d) .maxstack 2 .locals init (int? V_0, int? V_1, long? V_2) IL_0000: call ""int? Program.N1()"" IL_0005: stloc.0 IL_0006: call ""int? Program.N2()"" IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call ""bool int?.HasValue.get"" IL_0013: ldloca.s V_1 IL_0015: call ""bool int?.HasValue.get"" IL_001a: and IL_001b: brtrue.s IL_0027 IL_001d: ldloca.s V_2 IL_001f: initobj ""long?"" IL_0025: ldloc.2 IL_0026: ret IL_0027: ldloca.s V_0 IL_0029: call ""int int?.GetValueOrDefault()"" IL_002e: ldloca.s V_1 IL_0030: call ""int int?.GetValueOrDefault()"" IL_0035: add IL_0036: conv.i8 IL_0037: newobj ""long?..ctor(long)"" IL_003c: ret }"; string expectedIL2 = @"{ // Code size 48 (0x30) .maxstack 1 .locals init (S? V_0, int? V_1) IL_0000: call ""S? Program.N3()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool S?.HasValue.get"" IL_000d: brtrue.s IL_0019 IL_000f: ldloca.s V_1 IL_0011: initobj ""int?"" IL_0017: ldloc.1 IL_0018: ret IL_0019: ldloca.s V_0 IL_001b: call ""S S?.GetValueOrDefault()"" IL_0020: call ""S S.op_UnaryNegation(S)"" IL_0025: call ""int S.op_Implicit(S)"" IL_002a: newobj ""int?..ctor(int)"" IL_002f: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); } [Fact] public void TestNullableBoolBinOpsBothAlwaysNull() { // x & y and x | y are null if both operands are null. string source = @" class Program { static bool? M1() { return new bool?() | new bool?(); } static bool? M2() { return (bool?)null & default(bool?); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 10 (0xa) .maxstack 1 .locals init (bool? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""bool?"" IL_0008: ldloc.0 IL_0009: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL); comp.VerifyIL("Program.M2", expectedIL); } [Fact] public void TestNullableBoolBinOpsBothNotNull() { // x & y and x | y can be reduced to their non-lifted forms // if both operands are known to be non-null. // // Roslyn does a slightly better job than the native compiler here. // The native compiler effectively generates code as though you'd written: // // bool temp1 = N(); // bool? temp2 = new bool?(N() & N()); // return temp1 ? new bool?(true) : temp2; // // Whereas Roslyn simply generates code as though you'd written: // // return new bool?(N() | N() & N()) string source = @" class Program { static bool N() { return true; } static bool? M1() { return new bool?(N()) | new bool?(N()) & new bool?(N()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 23 (0x17) .maxstack 3 IL_0000: call ""bool Program.N()"" IL_0005: call ""bool Program.N()"" IL_000a: call ""bool Program.N()"" IL_000f: and IL_0010: or IL_0011: newobj ""bool?..ctor(bool)"" IL_0016: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL); } [Fact] public void TestNullableBoolBinOpsOneNull() { // codegen for x & y and x | y can be simplified if one operand is known to be null, // and simplified even further if the other operand is known to be non-null. string source = @" class Program { static bool? N() { return false; } static bool B() { return false; } static bool? M1() { // Generated as temp = N(), temp.GetValueOrDefault() ? null : temp return N() & new bool?(); } static bool? M2() { // Generated as temp = N(), temp.GetValueOrDefault() ? null : temp return default(bool?) & N(); } static bool? M3() { // Generated as temp = N(), temp.GetValueOrDefault() ? temp : null return N() | new bool?(); } static bool? M4() { // Generated as temp = N(), temp.GetValueOrDefault() ? temp : null return default(bool?) | N(); } static bool? M5() { // Generated as B() ? null : new bool?(false) return new bool?(B()) & new bool?(); } static bool? M6() { // Generated as B() ? null : new bool?(false) return default(bool?) & new bool?(B()); } static bool? M7() { // Generated as B() ? new bool?(true) : null return new bool?(B()) | new bool?(); } static bool? M8() { // Generated as B() ? new bool?(true) : null return default(bool?) | new bool?(B()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 27 (0x1b) .maxstack 1 .locals init (bool? V_0, bool? V_1) IL_0000: call ""bool? Program.N()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool bool?.GetValueOrDefault()"" IL_000d: brtrue.s IL_0011 IL_000f: ldloc.0 IL_0010: ret IL_0011: ldloca.s V_1 IL_0013: initobj ""bool?"" IL_0019: ldloc.1 IL_001a: ret }"; string expectedIL2 = expectedIL1; string expectedIL3 = @"{ // Code size 27 (0x1b) .maxstack 1 .locals init (bool? V_0, bool? V_1) IL_0000: call ""bool? Program.N()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool bool?.GetValueOrDefault()"" IL_000d: brtrue.s IL_0019 IL_000f: ldloca.s V_1 IL_0011: initobj ""bool?"" IL_0017: ldloc.1 IL_0018: ret IL_0019: ldloc.0 IL_001a: ret }"; string expectedIL4 = expectedIL3; string expectedIL5 = @"{ // Code size 24 (0x18) .maxstack 1 .locals init (bool? V_0) IL_0000: call ""bool Program.B()"" IL_0005: brtrue.s IL_000e IL_0007: ldc.i4.0 IL_0008: newobj ""bool?..ctor(bool)"" IL_000d: ret IL_000e: ldloca.s V_0 IL_0010: initobj ""bool?"" IL_0016: ldloc.0 IL_0017: ret }"; string expectedIL6 = expectedIL5; string expectedIL7 = @"{ // Code size 24 (0x18) .maxstack 1 .locals init (bool? V_0) IL_0000: call ""bool Program.B()"" IL_0005: brtrue.s IL_0011 IL_0007: ldloca.s V_0 IL_0009: initobj ""bool?"" IL_000f: ldloc.0 IL_0010: ret IL_0011: ldc.i4.1 IL_0012: newobj ""bool?..ctor(bool)"" IL_0017: ret } "; string expectedIL8 = expectedIL7; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); comp.VerifyIL("Program.M4", expectedIL4); comp.VerifyIL("Program.M5", expectedIL5); comp.VerifyIL("Program.M6", expectedIL6); comp.VerifyIL("Program.M7", expectedIL7); comp.VerifyIL("Program.M8", expectedIL8); } [Fact] public void TestNullableBoolBinOpsOneNonNull() { // Codegen for x & y and x | y can be simplified if one operand is known to be non null. // Note that we have already considered the case where one operand is null and the // other is non null, in the test case above. string source = @" class Program { static bool? N() { return false; } static bool B() { return false; } static bool? M1() { return new bool?(B()) & N(); } static bool? M2() { return N() & new bool?(B()); } static bool? M3() { return new bool?(B()) | N(); } static bool? M4() { return N() | new bool?(B()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 22 (0x16) .maxstack 2 .locals init (bool? V_0) IL_0000: call ""bool Program.B()"" IL_0005: call ""bool? Program.N()"" IL_000a: stloc.0 IL_000b: brtrue.s IL_0014 IL_000d: ldc.i4.0 IL_000e: newobj ""bool?..ctor(bool)"" IL_0013: ret IL_0014: ldloc.0 IL_0015: ret }"; string expectedIL2 = @"{ // Code size 22 (0x16) .maxstack 1 .locals init (bool? V_0) IL_0000: call ""bool? Program.N()"" IL_0005: stloc.0 IL_0006: call ""bool Program.B()"" IL_000b: brtrue.s IL_0014 IL_000d: ldc.i4.0 IL_000e: newobj ""bool?..ctor(bool)"" IL_0013: ret IL_0014: ldloc.0 IL_0015: ret }"; string expectedIL3 = @"{ // Code size 22 (0x16) .maxstack 2 .locals init (bool? V_0) IL_0000: call ""bool Program.B()"" IL_0005: call ""bool? Program.N()"" IL_000a: stloc.0 IL_000b: brtrue.s IL_000f IL_000d: ldloc.0 IL_000e: ret IL_000f: ldc.i4.1 IL_0010: newobj ""bool?..ctor(bool)"" IL_0015: ret }"; string expectedIL4 = @"{ // Code size 22 (0x16) .maxstack 1 .locals init (bool? V_0) IL_0000: call ""bool? Program.N()"" IL_0005: stloc.0 IL_0006: call ""bool Program.B()"" IL_000b: brtrue.s IL_000f IL_000d: ldloc.0 IL_000e: ret IL_000f: ldc.i4.1 IL_0010: newobj ""bool?..ctor(bool)"" IL_0015: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); comp.VerifyIL("Program.M3", expectedIL3); comp.VerifyIL("Program.M4", expectedIL4); } [Fact] public void TestNullableBinOpsBothAlwaysNull() { // x op y is null if both ops are null for the binary operators // * / % + - << >> and for non-bool & ^ | string source = @" class Program { static long? M1() { return new int?() + new long?(); } static decimal? M2() { return (short?)null * default(decimal?); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 10 (0xa) .maxstack 1 .locals init (long? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""long?"" IL_0008: ldloc.0 IL_0009: ret }"; string expectedIL2 = @"{ // Code size 10 (0xa) .maxstack 1 .locals init (decimal? V_0) IL_0000: ldloca.s V_0 IL_0002: initobj ""decimal?"" IL_0008: ldloc.0 IL_0009: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics( // (6,16): warning CS0458: The result of the expression is always 'null' of type 'long?' // return new int?() + new long?(); Diagnostic(ErrorCode.WRN_AlwaysNull, "new int?() + new long?()").WithArguments("long?"), // (10,16): warning CS0458: The result of the expression is always 'null' of type 'decimal?' // return (short?)null * default(decimal?); Diagnostic(ErrorCode.WRN_AlwaysNull, "(short?)null * default(decimal?)").WithArguments("decimal?")); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); } [Fact] public void TestNullableBinOpsBothNonNull() { // Lifted x op y is generated as non-lifted if both operands are known to be non-null // for operators * / % + - << >> and for non-bool & ^ | // // Roslyn does a far better job of this optimization than the native compiler. string source = @" class Program { static int N() { return 1; } static int? M1() { return new int?(N()) * new int?(N()) / new int?(N()) % new int?(N()) + new int?(N()) - new int?(N()) << new int?(N()) >> new int?(N()) & new int?(N()) ^ new int?(N()) | new int?(N()); } static void Main() {} } "; string expectedOutput = ""; string expectedIL = @"{ // Code size 77 (0x4d) .maxstack 3 IL_0000: call ""int Program.N()"" IL_0005: call ""int Program.N()"" IL_000a: mul IL_000b: call ""int Program.N()"" IL_0010: div IL_0011: call ""int Program.N()"" IL_0016: rem IL_0017: call ""int Program.N()"" IL_001c: add IL_001d: call ""int Program.N()"" IL_0022: sub IL_0023: call ""int Program.N()"" IL_0028: ldc.i4.s 31 IL_002a: and IL_002b: shl IL_002c: call ""int Program.N()"" IL_0031: ldc.i4.s 31 IL_0033: and IL_0034: shr IL_0035: call ""int Program.N()"" IL_003a: and IL_003b: call ""int Program.N()"" IL_0040: xor IL_0041: call ""int Program.N()"" IL_0046: or IL_0047: newobj ""int?..ctor(int)"" IL_004c: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL); } [Fact] public void TestNullableBinOpsOneNull() { // If we have null + N() or null + new int?(B()) // then we simply generate M() as a side effect and result in null. string source = @" class Program { static int? N() { return 1; } static int B() { return 1; } static int? M1() { return new int?() + N(); } static int? M2() { return new int?(B()) * default(int?); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 16 (0x10) .maxstack 1 .locals init (int? V_0) IL_0000: call ""int? Program.N()"" IL_0005: pop IL_0006: ldloca.s V_0 IL_0008: initobj ""int?"" IL_000e: ldloc.0 IL_000f: ret }"; string expectedIL2 = @"{ // Code size 16 (0x10) .maxstack 1 .locals init (int? V_0) IL_0000: call ""int Program.B()"" IL_0005: pop IL_0006: ldloca.s V_0 IL_0008: initobj ""int?"" IL_000e: ldloc.0 IL_000f: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics( // (9,16): warning CS0458: The result of the expression is always 'null' of type 'int?' // return new int?() + N(); Diagnostic(ErrorCode.WRN_AlwaysNull, "new int?() + N()").WithArguments("int?"), // (13,16): warning CS0458: The result of the expression is always 'null' of type 'int?' // return new int?(B()) * default(int?); Diagnostic(ErrorCode.WRN_AlwaysNull, "new int?(B()) * default(int?)").WithArguments("int?")); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); } [Fact] public void TestNullableBinOpsOneNonNull() { // If one side of the nullable binop is non-null then we skip calling HasValue and GetValueOrDefault // on that side. string source = @" class Program { static int B() { return 1; } static int? N() { return 1; } static int? M1() { return new int?(B()) + N(); } static int? M2() { return new int?(1) + N(); } static void Main() {} } "; string expectedOutput = ""; string expectedIL1 = @"{ // Code size 46 (0x2e) .maxstack 2 .locals init (int V_0, int? V_1, int? V_2) IL_0000: call ""int Program.B()"" IL_0005: stloc.0 IL_0006: call ""int? Program.N()"" IL_000b: stloc.1 IL_000c: ldloca.s V_1 IL_000e: call ""bool int?.HasValue.get"" IL_0013: brtrue.s IL_001f IL_0015: ldloca.s V_2 IL_0017: initobj ""int?"" IL_001d: ldloc.2 IL_001e: ret IL_001f: ldloc.0 IL_0020: ldloca.s V_1 IL_0022: call ""int int?.GetValueOrDefault()"" IL_0027: add IL_0028: newobj ""int?..ctor(int)"" IL_002d: ret }"; // TODO: Roslyn does a slightly worse job here than the native compiler does. // TODO: The native compiler knows that the constant need not be stored in a temporary. // TODO: We will clean this up in a later checkin. // TODO: When we do so, add tests for ++ -- +=, etc. string expectedIL2 = @" { // Code size 40 (0x28) .maxstack 2 .locals init (int? V_0, int? V_1) IL_0000: call ""int? Program.N()"" IL_0005: stloc.0 IL_0006: ldloca.s V_0 IL_0008: call ""bool int?.HasValue.get"" IL_000d: brtrue.s IL_0019 IL_000f: ldloca.s V_1 IL_0011: initobj ""int?"" IL_0017: ldloc.1 IL_0018: ret IL_0019: ldc.i4.1 IL_001a: ldloca.s V_0 IL_001c: call ""int int?.GetValueOrDefault()"" IL_0021: add IL_0022: newobj ""int?..ctor(int)"" IL_0027: ret }"; var comp = CompileAndVerify(source, expectedOutput: expectedOutput); comp.VerifyDiagnostics(); comp.VerifyIL("Program.M1", expectedIL1); comp.VerifyIL("Program.M2", expectedIL2); } [Fact] public void TestNullableBinOpsOneZero() { // If one side of the nullable binop is non-null then we skip calling HasValue and GetValueOrDefault // on that side. string source = @" class Program { static void Main() { int? N = 42; System.Console.WriteLine(0 + N); System.Console.WriteLine(N + 0); System.Console.WriteLine(0 - N); System.Console.WriteLine(N - 0); System.Console.WriteLine(0 * N); System.Console.WriteLine(N * 0); System.Console.WriteLine(1 * N); System.Console.WriteLine(N * 1); } } "; var comp = CompileAndVerify(source, expectedOutput: @" 42 42 -42 42 0 0 42 42") .VerifyIL("Program.Main", @" { // Code size 349 (0x15d) .maxstack 3 .locals init (int? V_0, int? V_1) IL_0000: ldc.i4.s 42 IL_0002: newobj ""int?..ctor(int)"" IL_0007: dup IL_0008: stloc.0 IL_0009: ldloca.s V_0 IL_000b: call ""bool int?.HasValue.get"" IL_0010: brtrue.s IL_001d IL_0012: ldloca.s V_1 IL_0014: initobj ""int?"" IL_001a: ldloc.1 IL_001b: br.s IL_0029 IL_001d: ldloca.s V_0 IL_001f: call ""int int?.GetValueOrDefault()"" IL_0024: newobj ""int?..ctor(int)"" IL_0029: box ""int?"" IL_002e: call ""void System.Console.WriteLine(object)"" IL_0033: dup IL_0034: stloc.0 IL_0035: ldloca.s V_0 IL_0037: call ""bool int?.HasValue.get"" IL_003c: brtrue.s IL_0049 IL_003e: ldloca.s V_1 IL_0040: initobj ""int?"" IL_0046: ldloc.1 IL_0047: br.s IL_0055 IL_0049: ldloca.s V_0 IL_004b: call ""int int?.GetValueOrDefault()"" IL_0050: newobj ""int?..ctor(int)"" IL_0055: box ""int?"" IL_005a: call ""void System.Console.WriteLine(object)"" IL_005f: dup IL_0060: stloc.0 IL_0061: ldloca.s V_0 IL_0063: call ""bool int?.HasValue.get"" IL_0068: brtrue.s IL_0075 IL_006a: ldloca.s V_1 IL_006c: initobj ""int?"" IL_0072: ldloc.1 IL_0073: br.s IL_0083 IL_0075: ldc.i4.0 IL_0076: ldloca.s V_0 IL_0078: call ""int int?.GetValueOrDefault()"" IL_007d: sub IL_007e: newobj ""int?..ctor(int)"" IL_0083: box ""int?"" IL_0088: call ""void System.Console.WriteLine(object)"" IL_008d: dup IL_008e: stloc.0 IL_008f: ldloca.s V_0 IL_0091: call ""bool int?.HasValue.get"" IL_0096: brtrue.s IL_00a3 IL_0098: ldloca.s V_1 IL_009a: initobj ""int?"" IL_00a0: ldloc.1 IL_00a1: br.s IL_00af IL_00a3: ldloca.s V_0 IL_00a5: call ""int int?.GetValueOrDefault()"" IL_00aa: newobj ""int?..ctor(int)"" IL_00af: box ""int?"" IL_00b4: call ""void System.Console.WriteLine(object)"" IL_00b9: dup IL_00ba: stloc.0 IL_00bb: ldloca.s V_0 IL_00bd: call ""bool int?.HasValue.get"" IL_00c2: brtrue.s IL_00cf IL_00c4: ldloca.s V_1 IL_00c6: initobj ""int?"" IL_00cc: ldloc.1 IL_00cd: br.s IL_00d5 IL_00cf: ldc.i4.0 IL_00d0: newobj ""int?..ctor(int)"" IL_00d5: box ""int?"" IL_00da: call ""void System.Console.WriteLine(object)"" IL_00df: dup IL_00e0: stloc.0 IL_00e1: ldloca.s V_0 IL_00e3: call ""bool int?.HasValue.get"" IL_00e8: brtrue.s IL_00f5 IL_00ea: ldloca.s V_1 IL_00ec: initobj ""int?"" IL_00f2: ldloc.1 IL_00f3: br.s IL_00fb IL_00f5: ldc.i4.0 IL_00f6: newobj ""int?..ctor(int)"" IL_00fb: box ""int?"" IL_0100: call ""void System.Console.WriteLine(object)"" IL_0105: dup IL_0106: stloc.0 IL_0107: ldloca.s V_0 IL_0109: call ""bool int?.HasValue.get"" IL_010e: brtrue.s IL_011b IL_0110: ldloca.s V_1 IL_0112: initobj ""int?"" IL_0118: ldloc.1 IL_0119: br.s IL_0127 IL_011b: ldloca.s V_0 IL_011d: call ""int int?.GetValueOrDefault()"" IL_0122: newobj ""int?..ctor(int)"" IL_0127: box ""int?"" IL_012c: call ""void System.Console.WriteLine(object)"" IL_0131: stloc.0 IL_0132: ldloca.s V_0 IL_0134: call ""bool int?.HasValue.get"" IL_0139: brtrue.s IL_0146 IL_013b: ldloca.s V_1 IL_013d: initobj ""int?"" IL_0143: ldloc.1 IL_0144: br.s IL_0152 IL_0146: ldloca.s V_0 IL_0148: call ""int int?.GetValueOrDefault()"" IL_014d: newobj ""int?..ctor(int)"" IL_0152: box ""int?"" IL_0157: call ""void System.Console.WriteLine(object)"" IL_015c: ret }"); } [Fact] public void TestNullableBinOpsOneZero1() { // If one side of the nullable binop is non-null then we skip calling HasValue and GetValueOrDefault // on that side. string source = @" class Program { static void Main() { System.Console.WriteLine(0 + (int?)42); System.Console.WriteLine((int?)42 + 0); System.Console.WriteLine(0 - (int?)42); System.Console.WriteLine((int?)42 - 0); System.Console.WriteLine(0 * (int?)42); System.Console.WriteLine((int?)42 * 0); System.Console.WriteLine(1 * (int?)42); System.Console.WriteLine((int?)42 * 1); } } "; var comp = CompileAndVerify(source, expectedOutput: @" 42 42 -42 42 0 0 42 42") .VerifyIL("Program.Main", @" { // Code size 97 (0x61) .maxstack 2 IL_0000: ldc.i4.s 42 IL_0002: box ""int"" IL_0007: call ""void System.Console.WriteLine(object)"" IL_000c: ldc.i4.s 42 IL_000e: box ""int"" IL_0013: call ""void System.Console.WriteLine(object)"" IL_0018: ldc.i4.0 IL_0019: ldc.i4.s 42 IL_001b: sub IL_001c: box ""int"" IL_0021: call ""void System.Console.WriteLine(object)"" IL_0026: ldc.i4.s 42 IL_0028: box ""int"" IL_002d: call ""void System.Console.WriteLine(object)"" IL_0032: ldc.i4.0 IL_0033: box ""int"" IL_0038: call ""void System.Console.WriteLine(object)"" IL_003d: ldc.i4.0 IL_003e: box ""int"" IL_0043: call ""void System.Console.WriteLine(object)"" IL_0048: ldc.i4.s 42 IL_004a: box ""int"" IL_004f: call ""void System.Console.WriteLine(object)"" IL_0054: ldc.i4.s 42 IL_0056: box ""int"" IL_005b: call ""void System.Console.WriteLine(object)"" IL_0060: ret }"); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_ET([CombinatorialValues("x == true", "true == x")] string code) { var source = $$""" C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => {{code}}; } """; var output = "100"; var il = """ { // Code size 8 (0x8) .maxstack 1 IL_0000: ldarga.s V_0 IL_0002: call "bool bool?.GetValueOrDefault()" IL_0007: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_EF1() { var source = """ C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => x == false; } """; var output = "010"; var il = """ { // Code size 23 (0x17) .maxstack 2 .locals init (bool? V_0, bool V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: stloc.1 IL_0004: ldloca.s V_0 IL_0006: call "bool bool?.GetValueOrDefault()" IL_000b: ldloc.1 IL_000c: ceq IL_000e: ldloca.s V_0 IL_0010: call "bool bool?.HasValue.get" IL_0015: and IL_0016: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_EF2() { var source = """ C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => false == x; } """; var output = "010"; var il = """ { // Code size 21 (0x15) .maxstack 2 .locals init (bool? V_0) IL_0000: ldc.i4.0 IL_0001: ldarg.0 IL_0002: stloc.0 IL_0003: ldloca.s V_0 IL_0005: call "bool bool?.GetValueOrDefault()" IL_000a: ceq IL_000c: ldloca.s V_0 IL_000e: call "bool bool?.HasValue.get" IL_0013: and IL_0014: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_NT([CombinatorialValues("x != true", "true != x")] string code) { var source = $$""" C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => {{code}}; } """; var output = "011"; var il = """ { // Code size 11 (0xb) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "bool bool?.GetValueOrDefault()" IL_0007: ldc.i4.0 IL_0008: ceq IL_000a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_NF1() { var source = """ C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => x != false; } """; var output = "101"; var il = """ { // Code size 26 (0x1a) .maxstack 2 .locals init (bool? V_0, bool V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: stloc.1 IL_0004: ldloca.s V_0 IL_0006: call "bool bool?.GetValueOrDefault()" IL_000b: ldloc.1 IL_000c: ceq IL_000e: ldloca.s V_0 IL_0010: call "bool bool?.HasValue.get" IL_0015: and IL_0016: ldc.i4.0 IL_0017: ceq IL_0019: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_NF2() { var source = """ C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => false != x; } """; var output = "101"; var il = """ { // Code size 24 (0x18) .maxstack 2 .locals init (bool? V_0) IL_0000: ldc.i4.0 IL_0001: ldarg.0 IL_0002: stloc.0 IL_0003: ldloca.s V_0 IL_0005: call "bool bool?.GetValueOrDefault()" IL_000a: ceq IL_000c: ldloca.s V_0 IL_000e: call "bool bool?.HasValue.get" IL_0013: and IL_0014: ldc.i4.0 IL_0015: ceq IL_0017: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_EN([CombinatorialValues("x == null", "null == x")] string code) { var source = $$""" C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => {{code}}; } """; var output = "001"; var il = """ { // Code size 11 (0xb) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "bool bool?.HasValue.get" IL_0007: ldc.i4.0 IL_0008: ceq IL_000a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_NET([CombinatorialValues("!(x == true)", "!!!(x == true)")] string code) { var source = $$""" C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => {{code}}; } """; var output = "011"; var il = """ { // Code size 11 (0xb) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "bool bool?.GetValueOrDefault()" IL_0007: ldc.i4.0 IL_0008: ceq IL_000a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Boolean_NNT([CombinatorialValues("x != true", "!!(x != true)")] string code) { var source = $$""" C.Run(true); C.Run(false); C.Run(null); class C { public static void Run(bool? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(bool? x) => {{code}}; } """; var output = "011"; var il = """ { // Code size 11 (0xb) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "bool bool?.GetValueOrDefault()" IL_0007: ldc.i4.0 IL_0008: ceq IL_000a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Int_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(int? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(int? x) => x == 1; } """; var output = "0100"; var il = """ { // Code size 11 (0xb) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "int int?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: ceq IL_000a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Int_1E() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(int? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(int? x) => 1 == x; } """; var output = "0100"; var il = """ { // Code size 11 (0xb) .maxstack 2 IL_0000: ldc.i4.1 IL_0001: ldarga.s V_0 IL_0003: call "int int?.GetValueOrDefault()" IL_0008: ceq IL_000a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Int_N1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(int? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(int? x) => x != 1; } """; var output = "1011"; var il = """ { // Code size 14 (0xe) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "int int?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: ceq IL_000a: ldc.i4.0 IL_000b: ceq IL_000d: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Int_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(int? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(int? x) => x == 0; } """; var output = "1000"; var il = """ { // Code size 23 (0x17) .maxstack 2 .locals init (int? V_0, int V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: stloc.1 IL_0004: ldloca.s V_0 IL_0006: call "int int?.GetValueOrDefault()" IL_000b: ldloc.1 IL_000c: ceq IL_000e: ldloca.s V_0 IL_0010: call "bool int?.HasValue.get" IL_0015: and IL_0016: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Int_0E() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(int? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(int? x) => 0 == x; } """; var output = "1000"; var il = """ { // Code size 21 (0x15) .maxstack 2 .locals init (int? V_0) IL_0000: ldc.i4.0 IL_0001: ldarg.0 IL_0002: stloc.0 IL_0003: ldloca.s V_0 IL_0005: call "int int?.GetValueOrDefault()" IL_000a: ceq IL_000c: ldloca.s V_0 IL_000e: call "bool int?.HasValue.get" IL_0013: and IL_0014: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Int_L1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(int? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(int? x) => x < 1; } """; var output = "1000"; var il = """ { // Code size 23 (0x17) .maxstack 2 .locals init (int? V_0, int V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.1 IL_0003: stloc.1 IL_0004: ldloca.s V_0 IL_0006: call "int int?.GetValueOrDefault()" IL_000b: ldloc.1 IL_000c: clt IL_000e: ldloca.s V_0 IL_0010: call "bool int?.HasValue.get" IL_0015: and IL_0016: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Int_GE1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(int? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(int? x) => x >= -1; } """; var output = "1110"; var il = """ { // Code size 26 (0x1a) .maxstack 2 .locals init (int? V_0, int V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.m1 IL_0003: stloc.1 IL_0004: ldloca.s V_0 IL_0006: call "int int?.GetValueOrDefault()" IL_000b: ldloc.1 IL_000c: clt IL_000e: ldc.i4.0 IL_000f: ceq IL_0011: ldloca.s V_0 IL_0013: call "bool int?.HasValue.get" IL_0018: and IL_0019: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Double_E0([CombinatorialValues("0d", "0.0")] string rhs) { var source = $$""" C.Run(0d); C.Run(0.0); C.Run(1.0); C.Run(1.2); C.Run(null); class C { public static void Run(double? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(double? x) => x == {{rhs}}; } """; var output = "11000"; var il = """ { // Code size 31 (0x1f) .maxstack 2 .locals init (double? V_0, double V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.r8 0 IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call "double double?.GetValueOrDefault()" IL_0013: ldloc.1 IL_0014: ceq IL_0016: ldloca.s V_0 IL_0018: call "bool double?.HasValue.get" IL_001d: and IL_001e: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Double_G1() { var source = """ C.Run(0d); C.Run(0.0); C.Run(1.0); C.Run(1.2); C.Run(null); class C { public static void Run(double? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(double? x) => x > 1.0; } """; var output = "00010"; var il = """ { // Code size 31 (0x1f) .maxstack 2 .locals init (double? V_0, double V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.r8 1 IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call "double double?.GetValueOrDefault()" IL_0013: ldloc.1 IL_0014: cgt IL_0016: ldloca.s V_0 IL_0018: call "bool double?.HasValue.get" IL_001d: and IL_001e: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Double_LE1() { var source = """ C.Run(0d); C.Run(0.0); C.Run(1.0); C.Run(1.2); C.Run(null); class C { public static void Run(double? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(double? x) => x <= 1.0; } """; var output = "11100"; var il = """ { // Code size 34 (0x22) .maxstack 2 .locals init (double? V_0, double V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.r8 1 IL_000b: stloc.1 IL_000c: ldloca.s V_0 IL_000e: call "double double?.GetValueOrDefault()" IL_0013: ldloc.1 IL_0014: cgt.un IL_0016: ldc.i4.0 IL_0017: ceq IL_0019: ldloca.s V_0 IL_001b: call "bool double?.HasValue.get" IL_0020: and IL_0021: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Decimal_E0() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => x == 0m; } """; var output = "1100011"; var il = """ { // Code size 32 (0x20) .maxstack 2 .locals init (decimal? V_0, decimal V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_1 IL_0004: initobj "decimal" IL_000a: ldloca.s V_0 IL_000c: call "decimal decimal?.GetValueOrDefault()" IL_0011: ldloc.1 IL_0012: call "bool decimal.op_Equality(decimal, decimal)" IL_0017: ldloca.s V_0 IL_0019: call "bool decimal?.HasValue.get" IL_001e: and IL_001f: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] [WorkItem("https://github.com/dotnet/roslyn/issues/73510")] public void NullableConstant_Decimal_E00() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => x == 0.0m; } """; var output = "1100011"; var il = """ { // Code size 36 (0x24) .maxstack 6 .locals init (decimal? V_0, decimal V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_1 IL_0004: ldc.i4.0 IL_0005: ldc.i4.0 IL_0006: ldc.i4.0 IL_0007: ldc.i4.0 IL_0008: ldc.i4.1 IL_0009: call "decimal..ctor(int, int, int, bool, byte)" IL_000e: ldloca.s V_0 IL_0010: call "decimal decimal?.GetValueOrDefault()" IL_0015: ldloc.1 IL_0016: call "bool decimal.op_Equality(decimal, decimal)" IL_001b: ldloca.s V_0 IL_001d: call "bool decimal?.HasValue.get" IL_0022: and IL_0023: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] [WorkItem("https://github.com/dotnet/roslyn/issues/73510")] public void NullableConstant_Decimal_00E() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => 0.0m == x; } """; var output = "1100011"; var il = """ { // Code size 33 (0x21) .maxstack 5 .locals init (decimal? V_0) IL_0000: ldc.i4.0 IL_0001: ldc.i4.0 IL_0002: ldc.i4.0 IL_0003: ldc.i4.0 IL_0004: ldc.i4.1 IL_0005: newobj "decimal..ctor(int, int, int, bool, byte)" IL_000a: ldarg.0 IL_000b: stloc.0 IL_000c: ldloca.s V_0 IL_000e: call "decimal decimal?.GetValueOrDefault()" IL_0013: call "bool decimal.op_Equality(decimal, decimal)" IL_0018: ldloca.s V_0 IL_001a: call "bool decimal?.HasValue.get" IL_001f: and IL_0020: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] [WorkItem("https://github.com/dotnet/roslyn/issues/73510")] public void NullableConstant_Decimal_E000() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => x == 0.00m; } """; var output = "1100011"; var il = """ { // Code size 36 (0x24) .maxstack 6 .locals init (decimal? V_0, decimal V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_1 IL_0004: ldc.i4.0 IL_0005: ldc.i4.0 IL_0006: ldc.i4.0 IL_0007: ldc.i4.0 IL_0008: ldc.i4.2 IL_0009: call "decimal..ctor(int, int, int, bool, byte)" IL_000e: ldloca.s V_0 IL_0010: call "decimal decimal?.GetValueOrDefault()" IL_0015: ldloc.1 IL_0016: call "bool decimal.op_Equality(decimal, decimal)" IL_001b: ldloca.s V_0 IL_001d: call "bool decimal?.HasValue.get" IL_0022: and IL_0023: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] [WorkItem("https://github.com/dotnet/roslyn/issues/73510")] public void NullableConstant_Decimal_000E() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => 0.00m == x; } """; var output = "1100011"; var il = """ { // Code size 33 (0x21) .maxstack 5 .locals init (decimal? V_0) IL_0000: ldc.i4.0 IL_0001: ldc.i4.0 IL_0002: ldc.i4.0 IL_0003: ldc.i4.0 IL_0004: ldc.i4.2 IL_0005: newobj "decimal..ctor(int, int, int, bool, byte)" IL_000a: ldarg.0 IL_000b: stloc.0 IL_000c: ldloca.s V_0 IL_000e: call "decimal decimal?.GetValueOrDefault()" IL_0013: call "bool decimal.op_Equality(decimal, decimal)" IL_0018: ldloca.s V_0 IL_001a: call "bool decimal?.HasValue.get" IL_001f: and IL_0020: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Decimal_NE00() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => x != 0.0m; } """; var output = "0011100"; var il = """ { // Code size 39 (0x27) .maxstack 6 .locals init (decimal? V_0, decimal V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_1 IL_0004: ldc.i4.0 IL_0005: ldc.i4.0 IL_0006: ldc.i4.0 IL_0007: ldc.i4.0 IL_0008: ldc.i4.1 IL_0009: call "decimal..ctor(int, int, int, bool, byte)" IL_000e: ldloca.s V_0 IL_0010: call "decimal decimal?.GetValueOrDefault()" IL_0015: ldloc.1 IL_0016: call "bool decimal.op_Equality(decimal, decimal)" IL_001b: ldloca.s V_0 IL_001d: call "bool decimal?.HasValue.get" IL_0022: and IL_0023: ldc.i4.0 IL_0024: ceq IL_0026: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Decimal_00NE() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => 0.0m != x; } """; var output = "0011100"; var il = """ { // Code size 36 (0x24) .maxstack 5 .locals init (decimal? V_0) IL_0000: ldc.i4.0 IL_0001: ldc.i4.0 IL_0002: ldc.i4.0 IL_0003: ldc.i4.0 IL_0004: ldc.i4.1 IL_0005: newobj "decimal..ctor(int, int, int, bool, byte)" IL_000a: ldarg.0 IL_000b: stloc.0 IL_000c: ldloca.s V_0 IL_000e: call "decimal decimal?.GetValueOrDefault()" IL_0013: call "bool decimal.op_Equality(decimal, decimal)" IL_0018: ldloca.s V_0 IL_001a: call "bool decimal?.HasValue.get" IL_001f: and IL_0020: ldc.i4.0 IL_0021: ceq IL_0023: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Decimal_GE0() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => x >= 0m; } """; var output = "1111011"; var il = """ { // Code size 32 (0x20) .maxstack 2 .locals init (decimal? V_0, decimal V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_1 IL_0004: initobj "decimal" IL_000a: ldloca.s V_0 IL_000c: call "decimal decimal?.GetValueOrDefault()" IL_0011: ldloc.1 IL_0012: call "bool decimal.op_GreaterThanOrEqual(decimal, decimal)" IL_0017: ldloca.s V_0 IL_0019: call "bool decimal?.HasValue.get" IL_001e: and IL_001f: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Decimal_GE00() { var source = """ C.Run(0m); C.Run(0.0m); C.Run(1m); C.Run(2m); C.Run(null); C.Run(default(decimal)); C.Run(decimal.Zero); class C { public static void Run(decimal? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(decimal? x) => x >= 0.0m; } """; var output = "1111011"; var il = """ { // Code size 36 (0x24) .maxstack 6 .locals init (decimal? V_0, decimal V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_1 IL_0004: ldc.i4.0 IL_0005: ldc.i4.0 IL_0006: ldc.i4.0 IL_0007: ldc.i4.0 IL_0008: ldc.i4.1 IL_0009: call "decimal..ctor(int, int, int, bool, byte)" IL_000e: ldloca.s V_0 IL_0010: call "decimal decimal?.GetValueOrDefault()" IL_0015: ldloc.1 IL_0016: call "bool decimal.op_GreaterThanOrEqual(decimal, decimal)" IL_001b: ldloca.s V_0 IL_001d: call "bool decimal?.HasValue.get" IL_0022: and IL_0023: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Byte_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(byte? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(byte? x) => x == (byte)0; } """; var output = "1000"; var il = """ { // Code size 56 (0x38) .maxstack 2 .locals init (int? V_0, int V_1, byte? V_2, int? V_3) IL_0000: ldarg.0 IL_0001: stloc.2 IL_0002: ldloca.s V_2 IL_0004: call "bool byte?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_3 IL_000d: initobj "int?" IL_0013: ldloc.3 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_2 IL_0018: call "byte byte?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.0 IL_0023: ldc.i4.0 IL_0024: stloc.1 IL_0025: ldloca.s V_0 IL_0027: call "int int?.GetValueOrDefault()" IL_002c: ldloc.1 IL_002d: ceq IL_002f: ldloca.s V_0 IL_0031: call "bool int?.HasValue.get" IL_0036: and IL_0037: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Byte_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(byte? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(byte? x) => x == 1; } """; var output = "0100"; var il = """ { // Code size 46 (0x2e) .maxstack 2 .locals init (byte? V_0, int? V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 IL_0004: call "bool byte?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_1 IL_000d: initobj "int?" IL_0013: ldloc.1 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_0 IL_0018: call "byte byte?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.1 IL_0023: ldloca.s V_1 IL_0025: call "int int?.GetValueOrDefault()" IL_002a: ldc.i4.1 IL_002b: ceq IL_002d: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Char_E0() { var source = """ C.Run('\0'); C.Run(default(char)); C.Run('A'); C.Run('\x1'); C.Run(null); class C { public static void Run(char? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(char? x) => x == '\0'; } """; var output = "11000"; var il = """ { // Code size 56 (0x38) .maxstack 2 .locals init (int? V_0, int V_1, char? V_2, int? V_3) IL_0000: ldarg.0 IL_0001: stloc.2 IL_0002: ldloca.s V_2 IL_0004: call "bool char?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_3 IL_000d: initobj "int?" IL_0013: ldloc.3 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_2 IL_0018: call "char char?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.0 IL_0023: ldc.i4.0 IL_0024: stloc.1 IL_0025: ldloca.s V_0 IL_0027: call "int int?.GetValueOrDefault()" IL_002c: ldloc.1 IL_002d: ceq IL_002f: ldloca.s V_0 IL_0031: call "bool int?.HasValue.get" IL_0036: and IL_0037: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Char_E1() { var source = """ C.Run('\0'); C.Run(default(char)); C.Run('A'); C.Run('\x1'); C.Run(null); class C { public static void Run(char? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(char? x) => x == '\x1'; } """; var output = "00010"; var il = """ { // Code size 56 (0x38) .maxstack 2 .locals init (int? V_0, int V_1, char? V_2, int? V_3) IL_0000: ldarg.0 IL_0001: stloc.2 IL_0002: ldloca.s V_2 IL_0004: call "bool char?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_3 IL_000d: initobj "int?" IL_0013: ldloc.3 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_2 IL_0018: call "char char?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.0 IL_0023: ldc.i4.1 IL_0024: stloc.1 IL_0025: ldloca.s V_0 IL_0027: call "int int?.GetValueOrDefault()" IL_002c: ldloc.1 IL_002d: ceq IL_002f: ldloca.s V_0 IL_0031: call "bool int?.HasValue.get" IL_0036: and IL_0037: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_UInt_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(uint? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(uint? x) => x == 0U; } """; var output = "1000"; var il = """ { // Code size 23 (0x17) .maxstack 2 .locals init (uint? V_0, uint V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: stloc.1 IL_0004: ldloca.s V_0 IL_0006: call "uint uint?.GetValueOrDefault()" IL_000b: ldloc.1 IL_000c: ceq IL_000e: ldloca.s V_0 IL_0010: call "bool uint?.HasValue.get" IL_0015: and IL_0016: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_UInt_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(uint? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(uint? x) => x == 1U; } """; var output = "0100"; var il = """ { // Code size 11 (0xb) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "uint uint?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: ceq IL_000a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Short_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(short? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(short? x) => x == (short)0; } """; var output = "1000"; var il = """ { // Code size 56 (0x38) .maxstack 2 .locals init (int? V_0, int V_1, short? V_2, int? V_3) IL_0000: ldarg.0 IL_0001: stloc.2 IL_0002: ldloca.s V_2 IL_0004: call "bool short?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_3 IL_000d: initobj "int?" IL_0013: ldloc.3 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_2 IL_0018: call "short short?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.0 IL_0023: ldc.i4.0 IL_0024: stloc.1 IL_0025: ldloca.s V_0 IL_0027: call "int int?.GetValueOrDefault()" IL_002c: ldloc.1 IL_002d: ceq IL_002f: ldloca.s V_0 IL_0031: call "bool int?.HasValue.get" IL_0036: and IL_0037: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Short_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(short? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(short? x) => x == 1; } """; var output = "0100"; var il = """ { // Code size 46 (0x2e) .maxstack 2 .locals init (short? V_0, int? V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 IL_0004: call "bool short?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_1 IL_000d: initobj "int?" IL_0013: ldloc.1 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_0 IL_0018: call "short short?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.1 IL_0023: ldloca.s V_1 IL_0025: call "int int?.GetValueOrDefault()" IL_002a: ldc.i4.1 IL_002b: ceq IL_002d: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_UShort_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(ushort? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(ushort? x) => x == (ushort)0; } """; var output = "1000"; var il = """ { // Code size 56 (0x38) .maxstack 2 .locals init (int? V_0, int V_1, ushort? V_2, int? V_3) IL_0000: ldarg.0 IL_0001: stloc.2 IL_0002: ldloca.s V_2 IL_0004: call "bool ushort?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_3 IL_000d: initobj "int?" IL_0013: ldloc.3 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_2 IL_0018: call "ushort ushort?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.0 IL_0023: ldc.i4.0 IL_0024: stloc.1 IL_0025: ldloca.s V_0 IL_0027: call "int int?.GetValueOrDefault()" IL_002c: ldloc.1 IL_002d: ceq IL_002f: ldloca.s V_0 IL_0031: call "bool int?.HasValue.get" IL_0036: and IL_0037: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_UShort_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(ushort? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(ushort? x) => x == 1; } """; var output = "0100"; var il = """ { // Code size 46 (0x2e) .maxstack 2 .locals init (ushort? V_0, int? V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 IL_0004: call "bool ushort?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_1 IL_000d: initobj "int?" IL_0013: ldloc.1 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_0 IL_0018: call "ushort ushort?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.1 IL_0023: ldloca.s V_1 IL_0025: call "int int?.GetValueOrDefault()" IL_002a: ldc.i4.1 IL_002b: ceq IL_002d: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Long_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(long? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(long? x) => x == 0L; } """; var output = "1000"; var il = """ { // Code size 24 (0x18) .maxstack 2 .locals init (long? V_0, long V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: conv.i8 IL_0004: stloc.1 IL_0005: ldloca.s V_0 IL_0007: call "long long?.GetValueOrDefault()" IL_000c: ldloc.1 IL_000d: ceq IL_000f: ldloca.s V_0 IL_0011: call "bool long?.HasValue.get" IL_0016: and IL_0017: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Long_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(long? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(long? x) => x == 1L; } """; var output = "0100"; var il = """ { // Code size 12 (0xc) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "long long?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: conv.i8 IL_0009: ceq IL_000b: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_ULong_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(ulong? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(ulong? x) => x == 0UL; } """; var output = "1000"; var il = """ { // Code size 24 (0x18) .maxstack 2 .locals init (ulong? V_0, ulong V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: conv.i8 IL_0004: stloc.1 IL_0005: ldloca.s V_0 IL_0007: call "ulong ulong?.GetValueOrDefault()" IL_000c: ldloc.1 IL_000d: ceq IL_000f: ldloca.s V_0 IL_0011: call "bool ulong?.HasValue.get" IL_0016: and IL_0017: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_ULong_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(ulong? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(ulong? x) => x == 1UL; } """; var output = "0100"; var il = """ { // Code size 12 (0xc) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "ulong ulong?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: conv.i8 IL_0009: ceq IL_000b: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_NInt_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(nint? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(nint? x) => x == (nint)0; } """; var output = "1000"; var il = """ { // Code size 24 (0x18) .maxstack 2 .locals init (System.IntPtr? V_0, System.IntPtr V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: conv.i IL_0004: stloc.1 IL_0005: ldloca.s V_0 IL_0007: call "System.IntPtr System.IntPtr?.GetValueOrDefault()" IL_000c: ldloc.1 IL_000d: ceq IL_000f: ldloca.s V_0 IL_0011: call "bool System.IntPtr?.HasValue.get" IL_0016: and IL_0017: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_NInt_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(nint? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(nint? x) => x == (nint)1; } """; var output = "0100"; var il = """ { // Code size 12 (0xc) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "System.IntPtr System.IntPtr?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: conv.i IL_0009: ceq IL_000b: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_NUInt_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(nuint? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(nuint? x) => x == (nuint)0; } """; var output = "1000"; var il = """ { // Code size 24 (0x18) .maxstack 2 .locals init (System.UIntPtr? V_0, System.UIntPtr V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: conv.i IL_0004: stloc.1 IL_0005: ldloca.s V_0 IL_0007: call "System.UIntPtr System.UIntPtr?.GetValueOrDefault()" IL_000c: ldloc.1 IL_000d: ceq IL_000f: ldloca.s V_0 IL_0011: call "bool System.UIntPtr?.HasValue.get" IL_0016: and IL_0017: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_NUInt_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(nuint? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(nuint? x) => x == (nuint)1; } """; var output = "0100"; var il = """ { // Code size 12 (0xc) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "System.UIntPtr System.UIntPtr?.GetValueOrDefault()" IL_0007: ldc.i4.1 IL_0008: conv.i IL_0009: ceq IL_000b: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_SByte_E0() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(sbyte? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(sbyte? x) => x == (sbyte)0; } """; var output = "1000"; var il = """ { // Code size 56 (0x38) .maxstack 2 .locals init (int? V_0, int V_1, sbyte? V_2, int? V_3) IL_0000: ldarg.0 IL_0001: stloc.2 IL_0002: ldloca.s V_2 IL_0004: call "bool sbyte?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_3 IL_000d: initobj "int?" IL_0013: ldloc.3 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_2 IL_0018: call "sbyte sbyte?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.0 IL_0023: ldc.i4.0 IL_0024: stloc.1 IL_0025: ldloca.s V_0 IL_0027: call "int int?.GetValueOrDefault()" IL_002c: ldloc.1 IL_002d: ceq IL_002f: ldloca.s V_0 IL_0031: call "bool int?.HasValue.get" IL_0036: and IL_0037: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_SByte_E1() { var source = """ C.Run(0); C.Run(1); C.Run(2); C.Run(null); class C { public static void Run(sbyte? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(sbyte? x) => x == 1; } """; var output = "0100"; var il = """ { // Code size 46 (0x2e) .maxstack 2 .locals init (sbyte? V_0, int? V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 IL_0004: call "bool sbyte?.HasValue.get" IL_0009: brtrue.s IL_0016 IL_000b: ldloca.s V_1 IL_000d: initobj "int?" IL_0013: ldloc.1 IL_0014: br.s IL_0022 IL_0016: ldloca.s V_0 IL_0018: call "sbyte sbyte?.GetValueOrDefault()" IL_001d: newobj "int?..ctor(int)" IL_0022: stloc.1 IL_0023: ldloca.s V_1 IL_0025: call "int int?.GetValueOrDefault()" IL_002a: ldc.i4.1 IL_002b: ceq IL_002d: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Single_E0() { var source = """ C.Run(0f); C.Run(0.0f); C.Run(default(float)); C.Run(1f); C.Run(2f); C.Run(null); class C { public static void Run(float? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(float? x) => x == 0f; } """; var output = "111000"; var il = """ { // Code size 27 (0x1b) .maxstack 2 .locals init (float? V_0, float V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldc.r4 0 IL_0007: stloc.1 IL_0008: ldloca.s V_0 IL_000a: call "float float?.GetValueOrDefault()" IL_000f: ldloc.1 IL_0010: ceq IL_0012: ldloca.s V_0 IL_0014: call "bool float?.HasValue.get" IL_0019: and IL_001a: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52629")] public void NullableConstant_Single_E1() { var source = """ C.Run(0f); C.Run(0.0f); C.Run(default(float)); C.Run(1f); C.Run(2f); C.Run(null); class C { public static void Run(float? x) { System.Console.Write(M(x) ? 1 : 0); } static bool M(float? x) => x == 1f; } """; var output = "000100"; var il = """ { // Code size 15 (0xf) .maxstack 2 IL_0000: ldarga.s V_0 IL_0002: call "float float?.GetValueOrDefault()" IL_0007: ldc.r4 1 IL_000c: ceq IL_000e: ret } """; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); verifier = CompileAndVerify(source, options: TestOptions.DebugExe, expectedOutput: output); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", il); } } }