|
// 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 (nint? 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 "nint nint?.GetValueOrDefault()"
IL_000c: ldloc.1
IL_000d: ceq
IL_000f: ldloca.s V_0
IL_0011: call "bool nint?.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 "nint nint?.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 (nuint? 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 "nuint nuint?.GetValueOrDefault()"
IL_000c: ldloc.1
IL_000d: ceq
IL_000f: ldloca.s V_0
IL_0011: call "bool nuint?.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 "nuint nuint?.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);
}
}
}
|