Skip to content

Test plan: null coalescing assignment a ??= b #29168

@cston

Description

@cston

Language proposal: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/null-coalescing-assignment.md
LDM notes: https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-07-16.md
Implementation: https://github.com/dotnet/roslyn/tree/features/null-operator-enhancements

  • Check language version 8.0
    • Upgrade Project available
  • Parsing
    • parses regardless of language version
    • has same precedence as simple assignment
    • associativity
    • ??= is a token
  • a ??= b is an r-value of type A, even if A is nullable and B is not
  • a must be a reference type or nullable value type or unconstrained type parameter
  • Test is a is null, operator== is ignored
  • Conversions of b only; no conversions of A
    • implicit conversion from B to A or A0 (where A is A0?)
    • implicit user-defined conversion from B to A or A0
      • operator can be defined in either A or B
    • null conversion
    • default conversion
      • test conversion of default is to A not A0; issue warning since conversion to Nullable<A0> was probably not intended
  • a is an l-value and r-value
    • by-val or ref local, possibly captured
    • by-val or ref parameter, possibly captured
    • writable field
    • read/write property or readable ref property
    • read/write indexer or readable ref indexer
    • read/write COM indexed property
    • read/write event
    • ref returning invocation
    • member of dynamic receiver
    • this is not allowed, for class and struct
    • pointers are not allowed
  • b is an r-value expression including
    • by-val or ref local, possibly captured
    • by-val or ref parameter, possibly captured
  • a ?? b is supported for unconstrained type parameters (no reference or value type constraints)
    • A is an unconstrained type parameter and B is implicitly convertible to A, with result type A
    • A is an unconstrained type parameter and A is implicitly convertible to B, with result type B
    • A is an unconstrained type parameter and B is dynamic, with result type dynamic
  • a ??= b is supported for unconstrained type parameters (no reference or value type constraints)
    • A is an unconstrained type parameter and B is implicitly convertible to A, with result type A
  • await is supported within a and b
    • with side-effects in a and b
  • ref
    • ref a ??= ref b; // error
    • ref a ??= b; // error
  • in parameter: void M(in object x) { }
    • M(a ??= b); // ok
    • M(in (a ??= b)); // error
  • a ??= throw expr is not allowed
  • Val escape
    • Span<int>? a = expr; a ??= stackAllocIntoSpan;
  • Flow analysis
    • Variables definitely assigned in a are definitely assigned before b and after expression
    • Variables only assigned in b are not definitely assigned after expression
    • out var in b is definitely assigned in remainder of b, but not definitely assigned after ??=
  • Lowering
    • a ??= b is lowered to tmpA ?? (tmpA = b)
    • Side-effects are executed exactly once for a, even if a ??= b is used as an expression
    • Side-effects are only executed for b if a is null
  • SemanticModel
    • GetTypeInfo includes the type and converted type of b
    • IOperation
    • CFG
  • IDE
    • Auto-formatting does not add space between ?? and =

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions