|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class NullConditionalAssignmentTests : SemanticModelTestBase
{
[Fact]
public void LangVersion_01()
{
var source = """
class C
{
string f;
static void M(C c)
{
c?.f = "a";
}
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular13);
comp.VerifyEmitDiagnostics(
// (3,12): warning CS0414: The field 'C.f' is assigned but its value is never used
// string f;
Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "f").WithArguments("C.f").WithLocation(3, 12),
// (6,14): error CS8652: The feature 'null conditional assignment' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
// c?.f = "a";
Diagnostic(ErrorCode.ERR_FeatureInPreview, "=").WithArguments("null conditional assignment").WithLocation(6, 14));
comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (3,12): warning CS0414: The field 'C.f' is assigned but its value is never used
// string f;
Diagnostic(ErrorCode.WRN_UnreferencedFieldAssg, "f").WithArguments("C.f").WithLocation(3, 12));
}
[Fact]
public void LangVersion_02()
{
// The only thing we want to diagnose is a member binding expression as the LHS of any assignment.
// Nested assignments within conditional access args, etc. have always been allowed.
var source = """
class C
{
public string this[string s] { get => s; set { } }
static void M(C c)
{
string s = "a";
_ = c?[s = "b"];
c?[s = "b"] = "c"; // 1
}
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular13);
comp.VerifyEmitDiagnostics(
// (7,16): warning CS0219: The variable 's' is assigned but its value is never used
// string s = "a";
Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "s").WithArguments("s").WithLocation(7, 16),
// (9,21): error CS8652: The feature 'null conditional assignment' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
// c?[s = "b"] = "c"; // 1
Diagnostic(ErrorCode.ERR_FeatureInPreview, "=").WithArguments("null conditional assignment").WithLocation(9, 21));
comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (7,16): warning CS0219: The variable 's' is assigned but its value is never used
// string s = "a";
Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "s").WithArguments("s").WithLocation(7, 16));
}
[Theory]
[InlineData(SyntaxKind.BarEqualsToken)]
[InlineData(SyntaxKind.AmpersandEqualsToken)]
[InlineData(SyntaxKind.CaretEqualsToken)]
[InlineData(SyntaxKind.LessThanLessThanEqualsToken)]
[InlineData(SyntaxKind.GreaterThanGreaterThanEqualsToken)]
[InlineData(SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken)]
[InlineData(SyntaxKind.PlusEqualsToken)]
[InlineData(SyntaxKind.MinusEqualsToken)]
[InlineData(SyntaxKind.AsteriskEqualsToken)]
[InlineData(SyntaxKind.SlashEqualsToken)]
[InlineData(SyntaxKind.PercentEqualsToken)]
[InlineData(SyntaxKind.EqualsToken)]
[InlineData(SyntaxKind.QuestionQuestionEqualsToken)]
public void LangVersion_03(SyntaxKind kind)
{
string op = SyntaxFacts.GetText(kind);
string source = $$"""
class C
{
public object F;
public static void M(C c)
{
c?.F {{op}} new object();
}
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular13);
comp.GetEmitDiagnostics()
.Where(diag => diag.Code == (int)ErrorCode.ERR_FeatureInPreview)
.Verify(
// (7,14): error CS8652: The feature 'null conditional assignment' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
// c?.F |= new object();
Diagnostic(ErrorCode.ERR_FeatureInPreview, op).WithArguments("null conditional assignment").WithLocation(7, 14));
comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview);
comp.GetEmitDiagnostics()
.Where(diag => diag.Code == (int)ErrorCode.ERR_FeatureInPreview)
.Verify();
}
[Fact]
public void FieldAccessAssignment_01()
{
var source = """
using System;
class C
{
int f;
static void Main()
{
var c = new C();
M1(c, 1);
M2(c, 2);
c = null;
M3(c, 3);
M4(c, 4);
}
static void M1(C c, int i)
{
c?.f = i;
Console.Write(c.f);
}
static void M2(C c, int i)
{
Console.Write(c?.f = i);
}
static void M3(C c, int i)
{
c?.f = i;
Console.Write(c?.f is null);
}
static void M4(C c, int i)
{
Console.Write((c?.f = i) is null);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "12TrueTrue");
verifier.VerifyIL("C.M1", """
{
// Code size 22 (0x16)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000a
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: stfld "int C.f"
IL_000a: ldarg.0
IL_000b: ldfld "int C.f"
IL_0010: call "void System.Console.Write(int)"
IL_0015: ret
}
""");
verifier.VerifyIL("C.M2", """
{
// Code size 40 (0x28)
.maxstack 3
.locals init (int? V_0,
int V_1)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000e
IL_0003: ldloca.s V_0
IL_0005: initobj "int?"
IL_000b: ldloc.0
IL_000c: br.s IL_001d
IL_000e: ldarg.0
IL_000f: ldarg.1
IL_0010: dup
IL_0011: stloc.1
IL_0012: stfld "int C.f"
IL_0017: ldloc.1
IL_0018: newobj "int?..ctor(int)"
IL_001d: box "int?"
IL_0022: call "void System.Console.Write(object)"
IL_0027: ret
}
""");
verifier.VerifyIL("C.M3", """
{
// Code size 52 (0x34)
.maxstack 2
.locals init (int? V_0,
int? V_1)
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000a
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: stfld "int C.f"
IL_000a: ldarg.0
IL_000b: brtrue.s IL_0018
IL_000d: ldloca.s V_1
IL_000f: initobj "int?"
IL_0015: ldloc.1
IL_0016: br.s IL_0023
IL_0018: ldarg.0
IL_0019: ldfld "int C.f"
IL_001e: newobj "int?..ctor(int)"
IL_0023: stloc.0
IL_0024: ldloca.s V_0
IL_0026: call "bool int?.HasValue.get"
IL_002b: ldc.i4.0
IL_002c: ceq
IL_002e: call "void System.Console.Write(bool)"
IL_0033: ret
}
""");
verifier.VerifyIL("C.M4", """
{
// Code size 46 (0x2e)
.maxstack 3
.locals init (int? V_0,
int? V_1,
int V_2)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000e
IL_0003: ldloca.s V_1
IL_0005: initobj "int?"
IL_000b: ldloc.1
IL_000c: br.s IL_001d
IL_000e: ldarg.0
IL_000f: ldarg.1
IL_0010: dup
IL_0011: stloc.2
IL_0012: stfld "int C.f"
IL_0017: ldloc.2
IL_0018: newobj "int?..ctor(int)"
IL_001d: stloc.0
IL_001e: ldloca.s V_0
IL_0020: call "bool int?.HasValue.get"
IL_0025: ldc.i4.0
IL_0026: ceq
IL_0028: call "void System.Console.Write(bool)"
IL_002d: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void FieldAccessAssignment_StructReceiver_01()
{
// NB: assignment of a 'readonly' setter is permitted even when property access receiver is not a variable
// See also https://github.com/dotnet/csharplang/issues/9174
var source = """
using System;
struct S
{
int f;
int P { get; set; }
readonly int RP { get => 0; set { } }
static void M1(S? s)
{
s?.f = 1; // 1
s?.P = 2; // 2
s?.RP = 2;
}
static void M2(S? s)
{
Console.Write(s?.f = 4); // 3
Console.Write(s?.P = 5); // 4
Console.Write(s?.RP = 6);
}
static void M2(S s)
{
s?.f = 7; // 5
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (11,11): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// s?.f = 1; // 1
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".f").WithLocation(11, 11),
// (12,11): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// s?.P = 2; // 2
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".P").WithLocation(12, 11),
// (18,25): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// Console.Write(s?.f = 4); // 3
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".f").WithLocation(18, 25),
// (19,25): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// Console.Write(s?.P = 5); // 4
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".P").WithLocation(19, 25),
// (25,10): error CS0023: Operator '?' cannot be applied to operand of type 'S'
// s?.f = 7; // 5
Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "S").WithLocation(25, 10));
}
[Fact]
public void FieldAccessAssignment_StructReceiver_02()
{
// NB: assignment of a 'readonly' setter is permitted even when property access receiver is not a variable
// See also https://github.com/dotnet/csharplang/issues/9174
var source = """
using System;
class C
{
public int F;
}
struct S
{
C c;
readonly int RP { get => c.F; set => c.F = value; }
static void Main()
{
M1(new S() { c = new C() });
M2(new S() { c = new C() });
M1(null);
M2(null);
}
static void M1(S? s)
{
s?.RP = 1;
Console.Write(s?.RP ?? 3);
}
static void M2(S? s)
{
Console.Write((s?.RP = 2) ?? 4);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "1234");
verifier.VerifyIL("S.M1", """
{
// Code size 58 (0x3a)
.maxstack 2
.locals init (S V_0)
IL_0000: ldarga.s V_0
IL_0002: call "bool S?.HasValue.get"
IL_0007: brfalse.s IL_0019
IL_0009: ldarga.s V_0
IL_000b: call "S S?.GetValueOrDefault()"
IL_0010: stloc.0
IL_0011: ldloca.s V_0
IL_0013: ldc.i4.1
IL_0014: call "readonly void S.RP.set"
IL_0019: ldarga.s V_0
IL_001b: call "bool S?.HasValue.get"
IL_0020: brtrue.s IL_0025
IL_0022: ldc.i4.3
IL_0023: br.s IL_0034
IL_0025: ldarga.s V_0
IL_0027: call "S S?.GetValueOrDefault()"
IL_002c: stloc.0
IL_002d: ldloca.s V_0
IL_002f: call "readonly int S.RP.get"
IL_0034: call "void System.Console.Write(int)"
IL_0039: ret
}
""");
verifier.VerifyIL("S.M2", """
{
// Code size 37 (0x25)
.maxstack 3
.locals init (int V_0,
S V_1)
IL_0000: ldarga.s V_0
IL_0002: call "bool S?.HasValue.get"
IL_0007: brtrue.s IL_000c
IL_0009: ldc.i4.4
IL_000a: br.s IL_001f
IL_000c: ldarga.s V_0
IL_000e: call "S S?.GetValueOrDefault()"
IL_0013: stloc.1
IL_0014: ldloca.s V_1
IL_0016: ldc.i4.2
IL_0017: dup
IL_0018: stloc.0
IL_0019: call "readonly void S.RP.set"
IL_001e: ldloc.0
IL_001f: call "void System.Console.Write(int)"
IL_0024: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void IndexerAssignment_StructReceiver_01()
{
// See also https://github.com/dotnet/csharplang/issues/9174
var source = """
using System;
struct S
{
int f;
public int this[int i] { get => f; set => f = i; }
static void M(S? s)
{
s?[1] = 2; // 1
Console.WriteLine(s?[3] = 4); // 2
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (10,11): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// s?[1] = 2; // 1
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "[1]").WithLocation(10, 11),
// (11,29): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// Console.WriteLine(s?[3] = 4); // 2
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "[3]").WithLocation(11, 29));
}
[Fact]
public void IndexerAssignment_StructReceiver_02()
{
// See also https://github.com/dotnet/csharplang/issues/9174
var source = """
using System;
class C { public int f; }
struct S
{
public C C = new C();
public S() { }
public int this[int i] { get => C.f; readonly set => C.f = i; }
static void Main()
{
M1(new S { C = { f = 1 } });
M1(null);
M2(new S { C = { f = 2 } });
M2(null);
}
static void M1(S? s)
{
s?[3] = 4;
Console.Write(s?[1] ?? 5);
}
static void M2(S? s)
{
Console.Write((s?[6] = 7) ?? 8);
}
}
""";
CompileAndVerify(source, expectedOutput: "3578");
}
[Fact]
public void DeconstructionLeft()
{
var source = """
using System;
class C
{
int F;
static void M1(C c1, C c2)
{
(c1?.F, c2?.F) = (1, 2); // 1
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (5,9): warning CS0649: Field 'C.F' is never assigned to, and will always have its default value 0
// int F;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F").WithArguments("C.F", "0").WithLocation(5, 9),
// (9,10): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// (c1?.F, c2?.F) = (1, 2); // 1
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "c1?.F").WithLocation(9, 10),
// (9,17): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// (c1?.F, c2?.F) = (1, 2); // 1
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "c2?.F").WithLocation(9, 17));
}
[Fact]
public void RefAssignment_01()
{
var source = """
using System;
ref struct RS
{
ref int RF;
static void M1()
{
int i = 0;
var rs = new RS { RF = ref i };
rs?.RF = ref i; // 1
RS? nrs = rs; // 2
nrs?.RF = ref i; // 3
nrs?.RF = i;
}
}
""";
var comp = CreateCompilation(source, targetFramework: TargetFramework.Net90);
comp.VerifyEmitDiagnostics(
// (11,11): error CS0023: Operator '?' cannot be applied to operand of type 'RS'
// rs?.RF = ref i; // 1
Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "RS").WithLocation(11, 11),
// (13,9): error CS9244: The type 'RS' may not be a ref struct or a type parameter allowing ref structs in order to use it as parameter 'T' in the generic type or method 'Nullable<T>'
// RS? nrs = rs; // 2
Diagnostic(ErrorCode.ERR_NotRefStructConstraintNotSatisfied, "RS?").WithArguments("System.Nullable<T>", "T", "RS").WithLocation(13, 9),
// (14,13): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// nrs?.RF = ref i; // 3
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".RF").WithLocation(14, 13));
}
[Fact]
public void AssignRefReturningMethod_01()
{
var source = """
using System;
class C
{
static int F;
ref int M() => ref F;
static void Main()
{
M1(new C());
M2(new C());
M1(null);
M2(null);
}
static void M1(C c)
{
c?.M() = 1;
Console.Write(c?.M() ?? 3);
}
static void M2(C c)
{
Console.Write((c?.M() = 2) ?? 4);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "1234");
verifier.VerifyIL("C.M1", """
{
// Code size 30 (0x1e)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000b
IL_0003: ldarg.0
IL_0004: call "ref int C.M()"
IL_0009: ldc.i4.1
IL_000a: stind.i4
IL_000b: ldarg.0
IL_000c: brtrue.s IL_0011
IL_000e: ldc.i4.3
IL_000f: br.s IL_0018
IL_0011: ldarg.0
IL_0012: call "ref int C.M()"
IL_0017: ldind.i4
IL_0018: call "void System.Console.Write(int)"
IL_001d: ret
}
""");
verifier.VerifyIL("C.M2", """
{
// Code size 23 (0x17)
.maxstack 3
.locals init (int V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldc.i4.4
IL_0004: br.s IL_0011
IL_0006: ldarg.0
IL_0007: call "ref int C.M()"
IL_000c: ldc.i4.2
IL_000d: dup
IL_000e: stloc.0
IL_000f: stind.i4
IL_0010: ldloc.0
IL_0011: call "void System.Console.Write(int)"
IL_0016: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void PropertyAccessAssignment_01()
{
var source = """
using System;
class C
{
int P { get; set; }
static void Main()
{
var c = new C();
M1(c, 1);
M2(c, 2);
c = null;
M3(c, 3);
M4(c, 4);
}
static void M1(C c, int i)
{
c?.P = i;
Console.Write(c.P);
}
static void M2(C c, int i)
{
Console.Write(c?.P = i);
}
static void M3(C c, int i)
{
c?.P = i;
Console.Write(c?.P is null);
}
static void M4(C c, int i)
{
Console.Write((c?.P = i) is null);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "12TrueTrue");
verifier.VerifyIL("C.M1", """
{
// Code size 22 (0x16)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000a
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: call "void C.P.set"
IL_000a: ldarg.0
IL_000b: callvirt "int C.P.get"
IL_0010: call "void System.Console.Write(int)"
IL_0015: ret
}
""");
verifier.VerifyIL("C.M2", """
{
// Code size 40 (0x28)
.maxstack 3
.locals init (int? V_0,
int V_1)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000e
IL_0003: ldloca.s V_0
IL_0005: initobj "int?"
IL_000b: ldloc.0
IL_000c: br.s IL_001d
IL_000e: ldarg.0
IL_000f: ldarg.1
IL_0010: dup
IL_0011: stloc.1
IL_0012: call "void C.P.set"
IL_0017: ldloc.1
IL_0018: newobj "int?..ctor(int)"
IL_001d: box "int?"
IL_0022: call "void System.Console.Write(object)"
IL_0027: ret
}
""");
verifier.VerifyIL("C.M3", """
{
// Code size 52 (0x34)
.maxstack 2
.locals init (int? V_0,
int? V_1)
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000a
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: call "void C.P.set"
IL_000a: ldarg.0
IL_000b: brtrue.s IL_0018
IL_000d: ldloca.s V_1
IL_000f: initobj "int?"
IL_0015: ldloc.1
IL_0016: br.s IL_0023
IL_0018: ldarg.0
IL_0019: call "int C.P.get"
IL_001e: newobj "int?..ctor(int)"
IL_0023: stloc.0
IL_0024: ldloca.s V_0
IL_0026: call "bool int?.HasValue.get"
IL_002b: ldc.i4.0
IL_002c: ceq
IL_002e: call "void System.Console.Write(bool)"
IL_0033: ret
}
""");
verifier.VerifyIL("C.M4", """
{
// Code size 46 (0x2e)
.maxstack 3
.locals init (int? V_0,
int? V_1,
int V_2)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_000e
IL_0003: ldloca.s V_1
IL_0005: initobj "int?"
IL_000b: ldloc.1
IL_000c: br.s IL_001d
IL_000e: ldarg.0
IL_000f: ldarg.1
IL_0010: dup
IL_0011: stloc.2
IL_0012: call "void C.P.set"
IL_0017: ldloc.2
IL_0018: newobj "int?..ctor(int)"
IL_001d: stloc.0
IL_001e: ldloca.s V_0
IL_0020: call "bool int?.HasValue.get"
IL_0025: ldc.i4.0
IL_0026: ceq
IL_0028: call "void System.Console.Write(bool)"
IL_002d: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void PropertyAccessAssignment_02()
{
// init prop
var source = """
var c = new C();
c?.Prop = "a"; // 1
class C
{
public string Prop { get; init; }
}
""";
var comp = CreateCompilation([source, IsExternalInitTypeDefinition]);
comp.VerifyEmitDiagnostics(
// (2,3): error CS8852: Init-only property or indexer 'C.Prop' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
// c?.Prop = "a"; // 1
Diagnostic(ErrorCode.ERR_AssignmentInitOnly, ".Prop").WithArguments("C.Prop").WithLocation(2, 3));
}
[Fact]
public void EventAssignment_01()
{
var source = """
using System;
class C
{
public event Action E;
static void Main()
{
M(new C());
M(null);
}
static void M(C c)
{
var handlerB = () => Console.Write("b");
var handlerC = () => Console.Write("c");
try
{
c?.E();
}
catch (NullReferenceException)
{
Console.Write("a");
}
ConditionalAddHandler(c, handlerB);
c?.E();
ConditionalAddHandler(c, handlerC);
c?.E();
ConditionalRemoveHandler(c, handlerB);
c?.E();
ConditionalRemoveHandler(c, handlerC);
try
{
c?.E();
}
catch (NullReferenceException)
{
Console.Write("d");
}
}
static void ConditionalAddHandler(C c, Action a)
{
c?.E += a;
}
static void ConditionalRemoveHandler(C c, Action a)
{
c?.E -= a;
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "abbccd");
verifier.VerifyDiagnostics();
verifier.VerifyIL("C.ConditionalAddHandler", """
{
// Code size 11 (0xb)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000a
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: call "void C.E.add"
IL_000a: ret
}
""");
verifier.VerifyIL("C.ConditionalRemoveHandler", """
{
// Code size 11 (0xb)
.maxstack 2
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000a
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: call "void C.E.remove"
IL_000a: ret
}
""");
}
[Fact]
public void ExpressionTree()
{
var source = """
using System;
using System.Linq.Expressions;
Expression<Func<C, string>> s = c => c?.F = "a"; // 1, 2
class C { public string F; }
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (4,38): error CS8072: An expression tree lambda may not contain a null propagating operator.
// Expression<Func<C, string>> s = c => c?.F = "a"; // 1, 2
Diagnostic(ErrorCode.ERR_NullPropagatingOpInExpressionTree, @"c?.F = ""a""").WithLocation(4, 38),
// (4,40): error CS0832: An expression tree may not contain an assignment operator
// Expression<Func<C, string>> s = c => c?.F = "a"; // 1, 2
Diagnostic(ErrorCode.ERR_ExpressionTreeContainsAssignment, @".F = ""a""").WithLocation(4, 40));
}
[Fact]
public void Dynamic_01()
{
var source = """
using System;
dynamic d = new C();
d?.F = "a";
Console.Write(d.F);
Console.Write(d?.F = "b");
d = null;
d?.F = "c";
Console.Write(d?.F ?? "<null>");
Console.Write((d?.F = "d") ?? "<null>");
class C { public string F = null!; }
""";
var verifier = CompileAndVerify(source, targetFramework: TargetFramework.StandardAndCSharp, expectedOutput: "ab<null><null>");
verifier.VerifyDiagnostics();
}
[Fact]
public void PointerDereference_01()
{
var source = """
struct S
{
public int F;
static unsafe void M(S?* x)
{
*x?.F = 1;
}
}
""";
var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll);
comp.VerifyEmitDiagnostics(
// (4,16): warning CS0649: Field 'S.F' is never assigned to, and will always have its default value 0
// public int F;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F").WithArguments("S.F", "0").WithLocation(4, 16),
// (8,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
// *x?.F = 1;
Diagnostic(ErrorCode.ERR_IllegalStatement, "*x?.F = 1").WithLocation(8, 9),
// (8,11): error CS0023: Operator '?' cannot be applied to operand of type 'S?*'
// *x?.F = 1;
Diagnostic(ErrorCode.ERR_BadUnaryOp, "?").WithArguments("?", "S?*").WithLocation(8, 11));
}
[Fact]
public void PointerDereference_02()
{
var source = """
using System;
struct S
{
public int F;
static unsafe void M1(S?* x)
{
(*x)?.F = 1; // 1
}
static unsafe void M2(S?* x)
{
Console.Write((*x)?.F = 3); // 2
}
}
""";
var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll);
comp.VerifyEmitDiagnostics(
// (9,14): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// (*x)?.F = 1; // 1
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".F").WithLocation(9, 14),
// (14,28): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// Console.Write((*x)?.F = 3); // 2
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".F").WithLocation(14, 28));
}
[Fact]
public void PointerDereference_03()
{
var source = """
using System;
unsafe struct S
{
public int* F;
static void M1(S? x)
{
*x?.F = 1; // 1, 2
}
}
""";
var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll);
comp.VerifyEmitDiagnostics(
// (9,9): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
// *x?.F = 1; // 1, 2
Diagnostic(ErrorCode.ERR_IllegalStatement, "*x?.F = 1").WithLocation(9, 9),
// (9,12): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// *x?.F = 1; // 1, 2
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".F").WithLocation(9, 12));
}
[Fact]
public void PointerDereference_04()
{
var source = """
using System;
unsafe struct S
{
public static int SF;
public int P { get => SF; readonly set => SF = value; }
static void M1(S?* x)
{
(*x)?.P = 1;
Console.Write((*x)?.P ?? 2);
}
static void M2(S?* x)
{
Console.Write(((*x)?.P = 3) ?? 4);
}
static void Main()
{
S s = default;
S? s1 = s;
S?* s1p = &s1;
M1(s1p);
M2(s1p);
s1 = null;
M1(s1p);
M2(s1p);
}
}
""";
var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped, expectedOutput: "1324");
verifier.VerifyDiagnostics();
}
[Fact]
public void PointerDereference_05()
{
// Similar to _04 but accessing reference type field 'C' instead of using a readonly setter
var source = """
using System;
class C { public int F; }
unsafe struct S
{
public C C;
#pragma warning disable 8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
static void M1(S?* x)
{
(*x)?.C.F = 1;
Console.Write((*x)?.C.F ?? 2);
}
static void M2(S?* x)
{
Console.Write(((*x)?.C.F = 3) ?? 4);
}
static void Main()
{
var s = new S { C = new C() };
S? s1 = s;
S?* s1p = &s1;
M1(s1p);
M2(s1p);
s1 = null;
M1(s1p);
M2(s1p);
}
}
""";
var verifier = CompileAndVerify(source, options: TestOptions.UnsafeDebugExe, verify: Verification.Skipped, expectedOutput: "1324");
verifier.VerifyDiagnostics();
}
[Fact]
public void CompoundAssignment_01()
{
var source = """
using System;
class C
{
int f;
static void Main()
{
M1(new C());
M1(null);
M2(new C());
M2(null);
}
static void M1(C c)
{
c?.f += 1;
Console.Write(c?.f ?? 2);
}
static void M2(C c)
{
Console.Write((c?.f += 3) ?? 4);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "1234");
verifier.VerifyIL("C.M1", """
{
// Code size 35 (0x23)
.maxstack 3
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0011
IL_0003: ldarg.0
IL_0004: dup
IL_0005: ldfld "int C.f"
IL_000a: ldc.i4.1
IL_000b: add
IL_000c: stfld "int C.f"
IL_0011: ldarg.0
IL_0012: brtrue.s IL_0017
IL_0014: ldc.i4.2
IL_0015: br.s IL_001d
IL_0017: ldarg.0
IL_0018: ldfld "int C.f"
IL_001d: call "void System.Console.Write(int)"
IL_0022: ret
}
""");
verifier.VerifyIL("C.M2", """
{
// Code size 29 (0x1d)
.maxstack 3
.locals init (int V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldc.i4.4
IL_0004: br.s IL_0017
IL_0006: ldarg.0
IL_0007: dup
IL_0008: ldfld "int C.f"
IL_000d: ldc.i4.3
IL_000e: add
IL_000f: dup
IL_0010: stloc.0
IL_0011: stfld "int C.f"
IL_0016: ldloc.0
IL_0017: call "void System.Console.Write(int)"
IL_001c: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void CompoundAssignment_02()
{
// Logical operator
// Note that there are no conditional versions of the "conditional logical operators"
// So, the set of behaviors we can observe through exhaustive testing is limited.
var source = """
using System;
class C
{
int f;
static void Main()
{
M1(new C() { f = 1 });
M1(null);
M2(new C() { f = 2 });
M2(null);
}
static void M1(C c)
{
c?.f |= 4;
Console.Write(c?.f ?? 8);
}
static void M2(C c)
{
Console.Write((c?.f |= 4) ?? 8);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "5868");
verifier.VerifyIL("C.M1", """
{
// Code size 35 (0x23)
.maxstack 3
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0011
IL_0003: ldarg.0
IL_0004: dup
IL_0005: ldfld "int C.f"
IL_000a: ldc.i4.4
IL_000b: or
IL_000c: stfld "int C.f"
IL_0011: ldarg.0
IL_0012: brtrue.s IL_0017
IL_0014: ldc.i4.8
IL_0015: br.s IL_001d
IL_0017: ldarg.0
IL_0018: ldfld "int C.f"
IL_001d: call "void System.Console.Write(int)"
IL_0022: ret
}
""");
verifier.VerifyIL("C.M2", """
{
// Code size 29 (0x1d)
.maxstack 3
.locals init (int V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldc.i4.8
IL_0004: br.s IL_0017
IL_0006: ldarg.0
IL_0007: dup
IL_0008: ldfld "int C.f"
IL_000d: ldc.i4.4
IL_000e: or
IL_000f: dup
IL_0010: stloc.0
IL_0011: stfld "int C.f"
IL_0016: ldloc.0
IL_0017: call "void System.Console.Write(int)"
IL_001c: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void CompoundAssignment_03()
{
// Shift operator
var source = """
using System;
class C
{
int f;
static void Main()
{
M1(new C() { f = 1 });
M1(null);
M2(new C() { f = 2 });
M2(null);
}
static void M1(C c)
{
c?.f <<= 1;
Console.Write(c?.f ?? 8);
}
static void M2(C c)
{
Console.Write((c?.f <<= 1) ?? 8);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "2848");
verifier.VerifyIL("C.M1", """
{
// Code size 35 (0x23)
.maxstack 3
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0011
IL_0003: ldarg.0
IL_0004: dup
IL_0005: ldfld "int C.f"
IL_000a: ldc.i4.1
IL_000b: shl
IL_000c: stfld "int C.f"
IL_0011: ldarg.0
IL_0012: brtrue.s IL_0017
IL_0014: ldc.i4.8
IL_0015: br.s IL_001d
IL_0017: ldarg.0
IL_0018: ldfld "int C.f"
IL_001d: call "void System.Console.Write(int)"
IL_0022: ret
}
""");
verifier.VerifyIL("C.M2", """
{
// Code size 29 (0x1d)
.maxstack 3
.locals init (int V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldc.i4.8
IL_0004: br.s IL_0017
IL_0006: ldarg.0
IL_0007: dup
IL_0008: ldfld "int C.f"
IL_000d: ldc.i4.1
IL_000e: shl
IL_000f: dup
IL_0010: stloc.0
IL_0011: stfld "int C.f"
IL_0016: ldloc.0
IL_0017: call "void System.Console.Write(int)"
IL_001c: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void FieldAccessAssignment_Nested_01()
{
var source = """
using System;
class C
{
int f;
static void Main()
{
var c = new C();
int x = 1;
c?.f = x = 2;
Console.Write(c.f);
Console.Write(x);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "22");
verifier.VerifyIL("C.Main", """
{
// Code size 39 (0x27)
.maxstack 4
.locals init (int V_0) //x
IL_0000: newobj "C..ctor()"
IL_0005: ldc.i4.1
IL_0006: stloc.0
IL_0007: dup
IL_0008: dup
IL_0009: brtrue.s IL_000e
IL_000b: pop
IL_000c: br.s IL_0016
IL_000e: ldc.i4.2
IL_000f: dup
IL_0010: stloc.0
IL_0011: stfld "int C.f"
IL_0016: ldfld "int C.f"
IL_001b: call "void System.Console.Write(int)"
IL_0020: ldloc.0
IL_0021: call "void System.Console.Write(int)"
IL_0026: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void FieldAccessAssignment_Nested_02()
{
var source = """
using System;
class C
{
int f;
static void Main()
{
C c = null;
int x = 1;
c?.f = x = 2;
Console.Write(c?.f is null);
Console.Write(x);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "True1");
verifier.VerifyIL("C.Main", """
{
// Code size 66 (0x42)
.maxstack 4
.locals init (int V_0, //x
int? V_1,
int? V_2)
IL_0000: ldnull
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: dup
IL_0004: dup
IL_0005: brtrue.s IL_000a
IL_0007: pop
IL_0008: br.s IL_0012
IL_000a: ldc.i4.2
IL_000b: dup
IL_000c: stloc.0
IL_000d: stfld "int C.f"
IL_0012: dup
IL_0013: brtrue.s IL_0021
IL_0015: pop
IL_0016: ldloca.s V_2
IL_0018: initobj "int?"
IL_001e: ldloc.2
IL_001f: br.s IL_002b
IL_0021: ldfld "int C.f"
IL_0026: newobj "int?..ctor(int)"
IL_002b: stloc.1
IL_002c: ldloca.s V_1
IL_002e: call "bool int?.HasValue.get"
IL_0033: ldc.i4.0
IL_0034: ceq
IL_0036: call "void System.Console.Write(bool)"
IL_003b: ldloc.0
IL_003c: call "void System.Console.Write(int)"
IL_0041: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void FieldAccessAssignment_Nested_03()
{
var source = """
using System;
class C
{
string f;
static void Main()
{
TestNestedCondAssignment(null, null);
TestNestedCondAssignment(new C(), null);
TestNestedCondAssignment(null, new C());
TestNestedCondAssignment(new C(), new C());
}
static void TestNestedCondAssignment(C c1, C c2)
{
GetReceiver(1, c1)?.f = GetReceiver(2, c2)?.f = GetAssignValue();
Report(c1, c2);
}
static C GetReceiver(int id, C c)
{
Console.WriteLine($"GetReceiver {id}: {c?.f ?? "<null>"}");
return c;
}
static string GetAssignValue()
{
Console.WriteLine($"GetAssignValue");
return "a";
}
static void Report(C c1, C c2)
{
Console.WriteLine($"Report: c1?.f: {c1?.f ?? "<null>"}; c2?.f: {c2?.f ?? "<null>"}");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: """
GetReceiver 1: <null>
Report: c1?.f: <null>; c2?.f: <null>
GetReceiver 1: <null>
GetReceiver 2: <null>
Report: c1?.f: <null>; c2?.f: <null>
GetReceiver 1: <null>
Report: c1?.f: <null>; c2?.f: <null>
GetReceiver 1: <null>
GetReceiver 2: <null>
GetAssignValue
Report: c1?.f: a; c2?.f: a
""");
verifier.VerifyIL("C.TestNestedCondAssignment", """
{
// Code size 53 (0x35)
.maxstack 4
.locals init (string V_0)
IL_0000: ldc.i4.1
IL_0001: ldarg.0
IL_0002: call "C C.GetReceiver(int, C)"
IL_0007: dup
IL_0008: brtrue.s IL_000d
IL_000a: pop
IL_000b: br.s IL_002d
IL_000d: ldc.i4.2
IL_000e: ldarg.1
IL_000f: call "C C.GetReceiver(int, C)"
IL_0014: dup
IL_0015: brtrue.s IL_001b
IL_0017: pop
IL_0018: ldnull
IL_0019: br.s IL_0028
IL_001b: call "string C.GetAssignValue()"
IL_0020: dup
IL_0021: stloc.0
IL_0022: stfld "string C.f"
IL_0027: ldloc.0
IL_0028: stfld "string C.f"
IL_002d: ldarg.0
IL_002e: ldarg.1
IL_002f: call "void C.Report(C, C)"
IL_0034: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void FieldAccessAssignment_Nested_04()
{
// similar to _03 except the value of the assignment expression is used.
var source = """
using System;
class C
{
string f;
static void Main()
{
TestNestedCondAssignment(null, null);
TestNestedCondAssignment(new C(), null);
TestNestedCondAssignment(null, new C());
TestNestedCondAssignment(new C(), new C());
}
static void TestNestedCondAssignment(C c1, C c2)
{
Report(GetReceiver(1, c1)?.f = GetReceiver(2, c2)?.f = GetAssignValue(), c1, c2);
}
static C GetReceiver(int id, C c)
{
Console.WriteLine($"GetReceiver {id}: {c?.f ?? "<null>"}");
return c;
}
static string GetAssignValue()
{
Console.WriteLine($"GetAssignValue");
return "a";
}
static void Report(string result, C c1, C c2)
{
Console.WriteLine($"Report: result: {result ?? "<null>"}; c1?.f: {c1?.f ?? "<null>"}; c2?.f: {c2?.f ?? "<null>"}");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: """
GetReceiver 1: <null>
Report: result: <null>; c1?.f: <null>; c2?.f: <null>
GetReceiver 1: <null>
GetReceiver 2: <null>
Report: result: <null>; c1?.f: <null>; c2?.f: <null>
GetReceiver 1: <null>
Report: result: <null>; c1?.f: <null>; c2?.f: <null>
GetReceiver 1: <null>
GetReceiver 2: <null>
GetAssignValue
Report: result: a; c1?.f: a; c2?.f: a
""");
verifier.VerifyIL("C.TestNestedCondAssignment", """
{
// Code size 57 (0x39)
.maxstack 4
.locals init (string V_0)
IL_0000: ldc.i4.1
IL_0001: ldarg.0
IL_0002: call "C C.GetReceiver(int, C)"
IL_0007: dup
IL_0008: brtrue.s IL_000e
IL_000a: pop
IL_000b: ldnull
IL_000c: br.s IL_0031
IL_000e: ldc.i4.2
IL_000f: ldarg.1
IL_0010: call "C C.GetReceiver(int, C)"
IL_0015: dup
IL_0016: brtrue.s IL_001c
IL_0018: pop
IL_0019: ldnull
IL_001a: br.s IL_0029
IL_001c: call "string C.GetAssignValue()"
IL_0021: dup
IL_0022: stloc.0
IL_0023: stfld "string C.f"
IL_0028: ldloc.0
IL_0029: dup
IL_002a: stloc.0
IL_002b: stfld "string C.f"
IL_0030: ldloc.0
IL_0031: ldarg.0
IL_0032: ldarg.1
IL_0033: call "void C.Report(string, C, C)"
IL_0038: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void PropertyAccessAssignment_Nested_01()
{
var source = """
using System;
class C(string id)
{
public string Id => id;
C Prop
{
get
{
Console.WriteLine($"Prop.get {id}");
return field;
}
set
{
Console.WriteLine($"Prop.set {id}");
field = value;
}
}
static void Main()
{
TestNestedCondAccess(null);
TestNestedCondAccess(new C("1"));
TestNestedCondAccess(new C("2") { Prop = new C("3") });
TestNestedCondAccess(new C("4") { Prop = new C("5") { Prop = new C("6") } });
}
static void TestNestedCondAccess(C c)
{
Console.WriteLine($"TestNestedCondAccess {c?.Id ?? "<null>"}");
c?.Prop?.Prop = GetAssignValue();
}
static C GetAssignValue()
{
Console.WriteLine("GetAssignValue");
return new C("7");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: """
TestNestedCondAccess <null>
TestNestedCondAccess 1
Prop.get 1
Prop.set 2
TestNestedCondAccess 2
Prop.get 2
GetAssignValue
Prop.set 3
Prop.set 5
Prop.set 4
TestNestedCondAccess 4
Prop.get 4
GetAssignValue
Prop.set 5
""");
verifier.VerifyDiagnostics();
verifier.VerifyIL("C.TestNestedCondAccess", """
{
// Code size 61 (0x3d)
.maxstack 3
IL_0000: ldstr "TestNestedCondAccess "
IL_0005: ldarg.0
IL_0006: brtrue.s IL_000b
IL_0008: ldnull
IL_0009: br.s IL_0011
IL_000b: ldarg.0
IL_000c: call "string C.Id.get"
IL_0011: dup
IL_0012: brtrue.s IL_001a
IL_0014: pop
IL_0015: ldstr "<null>"
IL_001a: call "string string.Concat(string, string)"
IL_001f: call "void System.Console.WriteLine(string)"
IL_0024: ldarg.0
IL_0025: brfalse.s IL_003c
IL_0027: ldarg.0
IL_0028: call "C C.Prop.get"
IL_002d: dup
IL_002e: brtrue.s IL_0032
IL_0030: pop
IL_0031: ret
IL_0032: call "C C.GetAssignValue()"
IL_0037: call "void C.Prop.set"
IL_003c: ret
}
""");
}
[Fact]
public void PropertyAccessAssignment_Nested_02()
{
// Similar to _01 except the assignment expression is used.
var source = """
using System;
class C
{
public C(string id) => Id = id;
public string Id { get; }
C Prop
{
get
{
Console.WriteLine($"Prop.get {Id} => {field?.Id ?? "<null>"}");
return field;
}
set
{
Console.WriteLine($"Prop.set {Id} = {value.Id}");
field = value;
}
}
static void Main()
{
TestNestedCondAccess(null);
TestNestedCondAccess(new C("1"));
TestNestedCondAccess(new C("2") { Prop = new C("3") });
TestNestedCondAccess(new C("4") { Prop = new C("5") { Prop = new C("6") } });
}
static void TestNestedCondAccess(C c)
{
Report(c?.Prop?.Prop = GetAssignValue());
}
static C GetAssignValue()
{
Console.WriteLine("GetAssignValue");
return new C("7");
}
static void Report(C c)
{
Console.WriteLine($"AssignmentResult {c?.Id ?? "<null>"}");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: """
AssignmentResult <null>
Prop.get 1 => <null>
AssignmentResult <null>
Prop.set 2 = 3
Prop.get 2 => 3
GetAssignValue
Prop.set 3 = 7
AssignmentResult 7
Prop.set 5 = 6
Prop.set 4 = 5
Prop.get 4 => 5
GetAssignValue
Prop.set 5 = 7
AssignmentResult 7
""");
verifier.VerifyDiagnostics();
verifier.VerifyIL("C.TestNestedCondAccess", """
{
// Code size 38 (0x26)
.maxstack 3
.locals init (C V_0)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldnull
IL_0004: br.s IL_0020
IL_0006: ldarg.0
IL_0007: call "C C.Prop.get"
IL_000c: dup
IL_000d: brtrue.s IL_0013
IL_000f: pop
IL_0010: ldnull
IL_0011: br.s IL_0020
IL_0013: call "C C.GetAssignValue()"
IL_0018: dup
IL_0019: stloc.0
IL_001a: call "void C.Prop.set"
IL_001f: ldloc.0
IL_0020: call "void C.Report(C)"
IL_0025: ret
}
""");
}
[Fact]
public void TypeParameter_01()
{
var source = """
class C<T>
{
public T t;
public static void M(C<T> c)
{
c?.t = default;
var x = c?.t = default; // 1
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (7,19): error CS8978: 'T' cannot be made nullable.
// var x = c?.t = default; // 1
Diagnostic(ErrorCode.ERR_CannotBeMadeNullable, ".t = default").WithArguments("T").WithLocation(7, 19));
}
[Fact]
public void TypeParameter_02()
{
var source = """
using System;
class Program
{
public static void Main()
{
C<string>.M(new C<string>());
}
}
class C<T> where T : class
{
public T t;
public static void M(C<T> c)
{
c?.t = null;
var x = c?.t = null;
Console.Write(c.t is null);
Console.Write(x is null);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "TrueTrue");
verifier.VerifyDiagnostics();
verifier.VerifyIL("C<T>.M", """
{
// Code size 80 (0x50)
.maxstack 3
.locals init (T V_0)
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000f
IL_0003: ldarg.0
IL_0004: ldflda "T C<T>.t"
IL_0009: initobj "T"
IL_000f: ldarg.0
IL_0010: brtrue.s IL_001d
IL_0012: ldloca.s V_0
IL_0014: initobj "T"
IL_001a: ldloc.0
IL_001b: br.s IL_002f
IL_001d: ldarg.0
IL_001e: ldloca.s V_0
IL_0020: initobj "T"
IL_0026: ldloc.0
IL_0027: dup
IL_0028: stloc.0
IL_0029: stfld "T C<T>.t"
IL_002e: ldloc.0
IL_002f: ldarg.0
IL_0030: ldfld "T C<T>.t"
IL_0035: box "T"
IL_003a: ldnull
IL_003b: ceq
IL_003d: call "void System.Console.Write(bool)"
IL_0042: box "T"
IL_0047: ldnull
IL_0048: ceq
IL_004a: call "void System.Console.Write(bool)"
IL_004f: ret
}
""");
}
[Fact]
public void UseResult_ReferenceType()
{
var source = """
using System;
class Program
{
public static void Main()
{
C.M(new C());
C.M(null);
}
}
class C
{
public string t;
public static void M(C c)
{
c?.t = "a";
var x = c?.t = "a";
Console.Write(c?.t ?? "b");
Console.Write(x ?? "b");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "aabb");
verifier.VerifyDiagnostics();
}
[Fact]
public void UseResult_ElementAssignment()
{
var source = """
using System;
class Program
{
public static void Main()
{
C.M(new C());
C.M(null);
}
}
class C
{
private string t;
public string this[string s] { get => t; set => t = value; }
public static void M(C c)
{
c?["a"] = "b";
var x = c?["a"] = "b";
Console.Write(c?.t ?? "c");
Console.Write(x ?? "c");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "bbcc");
verifier.VerifyDiagnostics();
}
[Fact]
public void SideEffects_01()
{
// Arguments to an indexer assignment are conditionally evaluated
var source = """
using System;
class C
{
public string this[string s] { get => s; set { Console.Write($"(set {value})"); } }
public static string GetString()
{
Console.Write("GetString()");
return "a";
}
public static void M(C c)
{
Console.Write((c?[GetString()] = "b") ?? "c");
}
public static void Main()
{
M(new C());
M(null);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "GetString()(set b)bc");
verifier.VerifyDiagnostics();
}
[Fact]
public void SideEffects_02()
{
// Arguments to an invocation assignment are conditionally evaluated
var source = """
using System;
class C
{
public static string _s;
public ref string M(string s) { Console.Write($"M({s})"); return ref _s; }
public static string GetString()
{
Console.Write("GetString()");
return "a";
}
public static void M(C c)
{
Console.Write((c?.M(GetString()) = "b") ?? "c");
}
public static void Main()
{
M(new C());
M(null);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "GetString()M(a)bc");
verifier.VerifyDiagnostics();
}
[Fact]
public void Await_01()
{
var source = """
using System.Threading.Tasks;
using System;
class C
{
public string F;
static async Task M(Task<C> tc)
{
(await tc)?.F = "a";
}
public static async Task Main()
{
var c = new C();
await M(Task.FromResult(c));
Console.Write(c.F);
c = null;
await M(Task.FromResult(c));
Console.Write(c?.F ?? "b");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "ab");
verifier.VerifyDiagnostics();
}
[Fact]
public void TypeParameter_03()
{
var source = """
using System;
class Program
{
public static void Main()
{
C<int>.M(new C<int>(), 1);
}
}
class C<T> where T : struct
{
public T t;
public static void M(C<T> c, T param)
{
c?.t = param;
var x = c?.t = param;
Console.Write(c?.t);
Console.Write(x);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "11");
verifier.VerifyDiagnostics();
verifier.VerifyIL("C<T>.M", """
{
// Code size 85 (0x55)
.maxstack 3
.locals init (T? V_0,
T V_1)
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000a
IL_0003: ldarg.0
IL_0004: ldarg.1
IL_0005: stfld "T C<T>.t"
IL_000a: ldarg.0
IL_000b: brtrue.s IL_0018
IL_000d: ldloca.s V_0
IL_000f: initobj "T?"
IL_0015: ldloc.0
IL_0016: br.s IL_0027
IL_0018: ldarg.0
IL_0019: ldarg.1
IL_001a: dup
IL_001b: stloc.1
IL_001c: stfld "T C<T>.t"
IL_0021: ldloc.1
IL_0022: newobj "T?..ctor(T)"
IL_0027: ldarg.0
IL_0028: brtrue.s IL_0035
IL_002a: ldloca.s V_0
IL_002c: initobj "T?"
IL_0032: ldloc.0
IL_0033: br.s IL_0040
IL_0035: ldarg.0
IL_0036: ldfld "T C<T>.t"
IL_003b: newobj "T?..ctor(T)"
IL_0040: box "T?"
IL_0045: call "void System.Console.Write(object)"
IL_004a: box "T?"
IL_004f: call "void System.Console.Write(object)"
IL_0054: ret
}
""");
}
[Fact]
public void TypeParameter_04()
{
var source = """
using System;
C c = null;
F1(c);
c = new C();
F1(c);
Console.WriteLine($"Assigned value: {c.P}");
S s = default;
s = F1(s);
Console.WriteLine($"Assigned value: {s.P}");
partial class Program
{
static T F1<T>(T t) where T : I
{
t?.P = GetValue(1);
return t;
}
static int GetValue(int i)
{
Console.WriteLine($"GetValue {i}");
return i;
}
}
interface I
{
int P { get; set; }
}
class C : I
{
public int P { get; set; }
}
struct S : I
{
public int P { get; set; }
}
""";
var verifier = CompileAndVerify(source, expectedOutput: """
GetValue 1
Assigned value: 1
GetValue 1
Assigned value: 1
""");
verifier.VerifyDiagnostics();
verifier.VerifyIL("Program.F1<T>", """
{
// Code size 56 (0x38)
.maxstack 2
.locals init (T V_0)
IL_0000: ldarga.s V_0
IL_0002: ldloca.s V_0
IL_0004: initobj "T"
IL_000a: ldloc.0
IL_000b: box "T"
IL_0010: brtrue.s IL_0025
IL_0012: ldobj "T"
IL_0017: stloc.0
IL_0018: ldloca.s V_0
IL_001a: ldloc.0
IL_001b: box "T"
IL_0020: brtrue.s IL_0025
IL_0022: pop
IL_0023: br.s IL_0036
IL_0025: ldc.i4.1
IL_0026: call "int Program.GetValue(int)"
IL_002b: constrained. "T"
IL_0031: callvirt "void I.P.set"
IL_0036: ldarg.0
IL_0037: ret
}
""");
}
[Fact]
public void TypeParameter_05()
{
var source = """
class C
{
static void F2<T>(T? t) where T : struct, I
{
t?.P = 1; // 1
}
}
interface I
{
int P { get; set; }
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (5,11): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// t?.P = 1; // 1
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, ".P").WithLocation(5, 11));
}
[Fact]
public void Parentheses_Assignment_LHS_01()
{
var source = """
using System;
class C
{
int F;
static void M(C c)
{
(c?.F) = 1; // 1
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (5,9): warning CS0649: Field 'C.F' is never assigned to, and will always have its default value 0
// int F;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F").WithArguments("C.F", "0").WithLocation(5, 9),
// (8,10): error CS0131: The left-hand side of an assignment must be a variable, property or indexer
// (c?.F) = 1; // 1
Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "c?.F").WithLocation(8, 10));
}
[Fact]
public void NullCoalescingAssignment_01()
{
var source = """
using System;
class C
{
string F;
static void Main()
{
M1(null);
M2(null);
M1(new C());
M2(new C());
M1(new C() { F = "b" });
M2(new C() { F = "b" });
}
static string GetAssignValue()
{
Console.WriteLine("GetAssignValue");
return "a";
}
static void M1(C c)
{
c?.F ??= GetAssignValue();
Report(c?.F);
}
static void M2(C c)
{
Report(c?.F ??= GetAssignValue());
}
static void Report(string value)
{
Console.WriteLine(value ?? "<null>");
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: """
<null>
<null>
GetAssignValue
a
GetAssignValue
a
b
b
""");
verifier.VerifyIL("C.M1", """
{
// Code size 42 (0x2a)
.maxstack 2
.locals init (C V_0)
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0018
IL_0003: ldarg.0
IL_0004: stloc.0
IL_0005: ldloc.0
IL_0006: ldfld "string C.F"
IL_000b: brtrue.s IL_0018
IL_000d: ldloc.0
IL_000e: call "string C.GetAssignValue()"
IL_0013: stfld "string C.F"
IL_0018: ldarg.0
IL_0019: brtrue.s IL_001e
IL_001b: ldnull
IL_001c: br.s IL_0024
IL_001e: ldarg.0
IL_001f: ldfld "string C.F"
IL_0024: call "void C.Report(string)"
IL_0029: ret
}
""");
verifier.VerifyIL("C.M2", """
{
// Code size 38 (0x26)
.maxstack 3
.locals init (C V_0,
string V_1)
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0006
IL_0003: ldnull
IL_0004: br.s IL_0020
IL_0006: ldarg.0
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldfld "string C.F"
IL_000e: dup
IL_000f: brtrue.s IL_0020
IL_0011: pop
IL_0012: ldloc.0
IL_0013: call "string C.GetAssignValue()"
IL_0018: dup
IL_0019: stloc.1
IL_001a: stfld "string C.F"
IL_001f: ldloc.1
IL_0020: call "void C.Report(string)"
IL_0025: ret
}
""");
verifier.VerifyDiagnostics();
}
[Fact]
public void NullCoalescingAssignValue_01()
{
// rhs of assignment is a '??' expr.
var source = """
class C
{
int F;
static void M(C c)
{
int i = c?.F = 1 ?? 2; // 1
int j = (c?.F = 3) ?? 4; // ok
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (7,24): error CS0019: Operator '??' cannot be applied to operands of type 'int' and 'int'
// int i = c?.F = 1 ?? 2; // 1
Diagnostic(ErrorCode.ERR_BadBinaryOps, "1 ?? 2").WithArguments("??", "int", "int").WithLocation(7, 24));
}
[Fact]
public void DefiniteAssignment_01()
{
// nb: there are no interesting cases involving struct fields
// since those will all have non-nullable value type receivers
// Instead we can exercise AnalyzeDataFlow
var source = """
class C
{
string F;
static void M(C c)
{
c?.F = "a";
}
}
""";
var comp = CreateCompilation(source);
var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var node = tree.GetRoot().DescendantNodes().OfType<ExpressionStatementSyntax>().Single();
var analysis = model.AnalyzeDataFlow(node);
Assert.Empty(analysis.AlwaysAssigned);
Assert.Equal("C c", analysis.ReadInside.Single().ToTestDisplayString());
Assert.Empty(analysis.WrittenInside);
var expr = tree.GetRoot().DescendantNodes().OfType<AssignmentExpressionSyntax>().Single();
analysis = model.AnalyzeDataFlow(expr);
Assert.Empty(analysis.AlwaysAssigned);
Assert.Empty(analysis.ReadInside);
Assert.Empty(analysis.WrittenInside);
}
[Fact]
public void DefiniteAssignment_02()
{
// Show similarity with an equivalent case that doesn't use a conditional access
var source = """
class C
{
string F;
static void M(C c)
{
if (c != null)
c.F = "a";
}
}
""";
var comp = CreateCompilation(source);
var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var node = tree.GetRoot().DescendantNodes().OfType<IfStatementSyntax>().Single();
var analysis = model.AnalyzeDataFlow(node);
Assert.Empty(analysis.AlwaysAssigned);
Assert.Equal("C c", analysis.ReadInside.Single().ToTestDisplayString());
Assert.Empty(analysis.WrittenInside);
var expr = tree.GetRoot().DescendantNodes().OfType<AssignmentExpressionSyntax>().Single();
analysis = model.AnalyzeDataFlow(node);
Assert.Empty(analysis.AlwaysAssigned);
Assert.Equal("C c", analysis.ReadInside.Single().ToTestDisplayString());
Assert.Empty(analysis.WrittenInside);
}
[Fact]
public void DefiniteAssignment_03()
{
var source = """
#nullable enable
class C
{
ref string M1(int p) => throw null!;
static void M2(C? c)
{
int a;
c?.M1(a = 42) = "b";
a.ToString(); // 1
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (10,9): error CS0165: Use of unassigned local variable 'a'
// a.ToString(); // 1
Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(10, 9));
}
[Fact]
public void DefiniteAssignment_04()
{
var source = """
#nullable enable
using System;
class C
{
static string? _s;
ref string? M1(int p1, string p2)
{
Console.Write(p1);
Console.Write(p2);
return ref _s;
}
static void M2(C? c)
{
int a;
c?.M1(a = 42, a.ToString()) = "b";
Console.Write(_s);
}
static void Main()
{
M2(new C());
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "4242b");
verifier.VerifyDiagnostics();
}
[Fact]
public void NullableAnalysis_01()
{
var source = """
#nullable enable
class C
{
string? F;
static void M1(C c)
{
c.F.ToString(); // 1
c?.F = "a";
c.F.ToString(); // 2
}
static void M2(C c)
{
c?.F = "a";
c.F.ToString(); // 3, 4
}
static void M3(C c)
{
if ((c?.F = "a") != null)
{
c.F.ToString();
}
c.F.ToString(); // 5, 6
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (9,9): warning CS8602: Dereference of a possibly null reference.
// c.F.ToString(); // 1
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c.F").WithLocation(9, 9),
// (11,9): warning CS8602: Dereference of a possibly null reference.
// c.F.ToString(); // 2
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(11, 9),
// (17,9): warning CS8602: Dereference of a possibly null reference.
// c.F.ToString(); // 3, 4
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(17, 9),
// (17,9): warning CS8602: Dereference of a possibly null reference.
// c.F.ToString(); // 3, 4
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c.F").WithLocation(17, 9),
// (26,9): warning CS8602: Dereference of a possibly null reference.
// c.F.ToString(); // 5, 6
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c").WithLocation(26, 9),
// (26,9): warning CS8602: Dereference of a possibly null reference.
// c.F.ToString(); // 5, 6
Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c.F").WithLocation(26, 9));
}
[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/77741")]
public void NullableAnalysis_02()
{
// Problem: the conditional receiver and its field are getting their own slots.
// But, when the assignment '.F = null' is processed, we do look up the slot for 'c' thru 'NullableWalker._lastConditionalAccessSlot'.
// Thus the state for 'c.F' gets updated to maybe-null, but the state for '<placeholder>.F' remains not-null.
// When we get a slot for RHS of next 'c?.F' expression, we get the slot for '<placeholder>.F', and see the .F as having not-null state.
// Thus, the expected warning is missing.
// We may want to solve this by ensuring we don't create a slot for the placeholder, and that getting a slot for the placeholder always gives the slot for the original receiver instead.
var source = """
#nullable enable
class C
{
string? F;
static void M1()
{
var c = new C { F = "a" };
c.F.ToString();
c?.F = null;
c?.F.ToString(); // 1
}
}
""";
var comp = CreateCompilation(source);
// Expected warning is missing here. https://github.com/dotnet/roslyn/issues/77741
comp.VerifyEmitDiagnostics();
}
[Fact]
public void NullableAnalysis_03()
{
var source = """
#nullable enable
using System;
class C
{
string? F;
static void M(C? c)
{
c?.F = c.F;
Console.Write(c?.F ?? "<null>");
}
static void Main()
{
M(new C() { F = "a" });
M(null);
}
}
""";
var verifier = CompileAndVerify(source, expectedOutput: "a<null>");
verifier.VerifyDiagnostics();
}
[Fact]
public void NullableAnalysis_04()
{
var source = """
#nullable enable
class C
{
public string? F = null;
static void M(C? c1, C c2)
{
c1?.F! = "a"; // 1
c2.F! = "a"; // 2
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyDiagnostics(
// (9,12): error CS8598: The suppression operator is not allowed in this context
// c1?.F! = "a"; // 1
Diagnostic(ErrorCode.ERR_IllegalSuppression, ".F").WithLocation(9, 12),
// (10,9): error CS8598: The suppression operator is not allowed in this context
// c2.F! = "a"; // 2
Diagnostic(ErrorCode.ERR_IllegalSuppression, "c2.F").WithLocation(10, 9));
}
[Fact]
public void IncrementDecrement_01()
{
var source = """
class C
{
int F;
static void M(C c)
{
c?.F++;
--c?.F;
}
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
// (3,9): warning CS0649: Field 'C.F' is never assigned to, and will always have its default value 0
// int F;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F").WithArguments("C.F", "0").WithLocation(3, 9),
// (6,9): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer
// c?.F++;
Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "c?.F").WithLocation(6, 9),
// (7,11): error CS1059: The operand of an increment or decrement operator must be a variable, property or indexer
// --c?.F;
Diagnostic(ErrorCode.ERR_IncrementLvalueExpected, "c?.F").WithLocation(7, 11));
}
[Fact]
public void ControlFlowGraph_01()
{
// Verify that conditional accesses and expressions are rewritten in CFG.
var source = """
class C
{
public string F;
static void M(C? c, bool b)
{
c?.F = b ? "1" : "2";
}
}
""";
var comp = CreateCompilation(source);
var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);
var methodDecl = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().Single();
var (graph, symbol) = ControlFlowGraphVerifier.GetControlFlowGraph(methodDecl.Body, model);
ControlFlowGraphVerifier.VerifyGraph(comp, """
Block[B0] - Entry
Statements (0)
Next (Regular) Block[B1]
Entering: {R1} {R2}
.locals {R1}
{
CaptureIds: [1] [2]
.locals {R2}
{
CaptureIds: [0]
Block[B1] - Block
Predecessors: [B0]
Statements (1)
IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c')
Value:
IParameterReferenceOperation: c (OperationKind.ParameterReference, Type: C) (Syntax: 'c')
Jump if True (Regular) to Block[B7]
IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'c')
Operand:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c')
Leaving: {R2} {R1}
Next (Regular) Block[B2]
Block[B2] - Block
Predecessors: [B1]
Statements (1)
IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '.F')
Value:
IFieldReferenceOperation: System.String C.F (OperationKind.FieldReference, Type: System.String) (Syntax: '.F')
Instance Receiver:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'c')
Next (Regular) Block[B3]
Leaving: {R2}
}
Block[B3] - Block
Predecessors: [B2]
Statements (0)
Jump if False (Regular) to Block[B5]
IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b')
Next (Regular) Block[B4]
Block[B4] - Block
Predecessors: [B3]
Statements (1)
IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '"1"')
Value:
ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "1") (Syntax: '"1"')
Next (Regular) Block[B6]
Block[B5] - Block
Predecessors: [B3]
Statements (1)
IFlowCaptureOperation: 2 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '"2"')
Value:
ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "2") (Syntax: '"2"')
Next (Regular) Block[B6]
Block[B6] - Block
Predecessors: [B4] [B5]
Statements (1)
IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'c?.F = b ? "1" : "2";')
Expression:
ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: '.F = b ? "1" : "2"')
Left:
IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: '.F')
Right:
IFlowCaptureReferenceOperation: 2 (OperationKind.FlowCaptureReference, Type: System.String, IsImplicit) (Syntax: 'b ? "1" : "2"')
Next (Regular) Block[B7]
Leaving: {R1}
}
Block[B7] - Exit
Predecessors: [B1] [B6]
Statements (0)
""",
graph, symbol);
}
[Fact]
public void ControlFlowGraph_02()
{
// Verify that conditional accesses and expressions are rewritten in CFG.
var source = """
class C
{
public string F;
static void M(bool b, C? c1, C? c2)
{
(b ? c1 : c2)?.F = "a";
}
}
""";
var comp = CreateCompilation(source);
var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);
var methodDecl = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>().Single();
var (graph, symbol) = ControlFlowGraphVerifier.GetControlFlowGraph(methodDecl.Body, model);
ControlFlowGraphVerifier.VerifyGraph(comp, """
Block[B0] - Entry
Statements (0)
Next (Regular) Block[B1]
Entering: {R1}
.locals {R1}
{
CaptureIds: [0]
Block[B1] - Block
Predecessors: [B0]
Statements (0)
Jump if False (Regular) to Block[B3]
IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b')
Next (Regular) Block[B2]
Block[B2] - Block
Predecessors: [B1]
Statements (1)
IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c1')
Value:
IParameterReferenceOperation: c1 (OperationKind.ParameterReference, Type: C) (Syntax: 'c1')
Next (Regular) Block[B4]
Block[B3] - Block
Predecessors: [B1]
Statements (1)
IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'c2')
Value:
IParameterReferenceOperation: c2 (OperationKind.ParameterReference, Type: C) (Syntax: 'c2')
Next (Regular) Block[B4]
Block[B4] - Block
Predecessors: [B2] [B3]
Statements (0)
Jump if True (Regular) to Block[B6]
IIsNullOperation (OperationKind.IsNull, Type: System.Boolean, IsImplicit) (Syntax: 'b ? c1 : c2')
Operand:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'b ? c1 : c2')
Leaving: {R1}
Next (Regular) Block[B5]
Block[B5] - Block
Predecessors: [B4]
Statements (1)
IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '(b ? c1 : c2)?.F = "a";')
Expression:
ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: '.F = "a"')
Left:
IFieldReferenceOperation: System.String C.F (OperationKind.FieldReference, Type: System.String) (Syntax: '.F')
Instance Receiver:
IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: C, IsImplicit) (Syntax: 'b ? c1 : c2')
Right:
ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "a") (Syntax: '"a"')
Next (Regular) Block[B6]
Leaving: {R1}
}
Block[B6] - Exit
Predecessors: [B4] [B5]
Statements (0)
""",
graph, symbol);
}
}
}
|