Skip to content

Instantly share code, notes, and snippets.

@jcouv
Last active March 6, 2019 22:09
Show Gist options
  • Save jcouv/2885a376d0678b66ed54a343d687b244 to your computer and use it in GitHub Desktop.
Save jcouv/2885a376d0678b66ed54a343d687b244 to your computer and use it in GitHub Desktop.

Proposal: A switch with a pure test sets the state of the tested variable to maybe-null at the entrance of the switch.

In that context, I think Mads is pushing back on {} counting as a “pure test” in a switch. If I understood, he is saying a {} => … branch on its own doesn’t open the possibility of a null (it’s not a pure test). But a {} => … branch followed by another branch would be.

Note: if we treat {} as a pure test, then we could offer a fixer to change it to _ if you didn't want to imply a null test.

Resolved in LDM 3/6/2019: {} and derived is Base are not "pure tests".

Where should the null-state be set to MaybeNull?

void M(object x)
{
  _ = x switch
  {
    var y when Foo() => x.ToString(), // warn for dereference?
    {} => x.ToString(),
    null => "",
  };
}

Switch expression

Switch expression with {} case, but no other branch:

void M(object x)
{
  // x is treated a maybe-null in the switch because of the pure test `{}`
  _ = x switch
  {
    1  => x.ToString(); // dereference is ok because pattern `1` tells us `x` isn't null in this branch
    {} => x.ToString(); // dereference is ok because of pattern `{}`
    // a branch is missing here (incomplete for nullability warning)
  };
  x.ToString(); // ok, because non-throwing branches all have non-null `x`
}

Switch expression with {} case followed by some other branch:

void M(object x)
{
  // x is treated a maybe-null in the switch because of the pure test `{}`
  _ = x switch
  {
    1  => x.ToString(); // ok to dereference because of pattern `1`
    {} => x.ToString(); // ok to dereference because of pattern `{}`
    anything /* ex: null, _, var y */ => x.ToString(); // warn on dereferencing x
  };
  x.ToString(); // warn because some branches exiting switch have maybe-null `x`
}

Switch expression with nested {} test followed by some other branch:

void M()
{
  A a = new A() { B = "" };

  // `a.B` is treated a maybe-null in the switch because of the pure test `{}`
  _ = a switch
  {
    A { B is {} } => a.B.ToString(); // ok to dereference because of patterns
    _ => a.B.ToString(); // warn
  };
  a.B.ToString(); // warn
}

Incomplete switch expression:

void M(object? x)
{
  _ = x switch
  {
    object _ => ...
    // incomplete for nullability
  };
  x.ToString(); // warn
}

Pure test affects all branches in a switch expression:

void M(object? x)
{
  // x is treated a maybe-null in the switch because of the pure test `{}`
  _ = x switch
  {
    var y when Foo() => y.ToString(), // warn for dereference
    {} => ""
    // incomplete for nullability
  };
  x.ToString(); // warn
}

Note only cases that are pure null tests have this effect. So cases like _, var y, or C {} do not change the state of x when exiting the switch expression.

Switch statement

Switch statement with {} case:

void M(object x)
{
  switch (x)
  {
    case 1:
      x.ToString(); // ok
      break;
    case {}: // opens up a null branch (implicit default branch below)
      x.ToString(); // ok
      break;
  }
  x.ToString(); // warn
}

Switch statement with null case:

void M(object x)
{
  switch (x)
  {
    case 1:
      x.ToString(); // ok
      break;
    case null: // opens up a null branch (present branch)
      x.ToString(); // warn
      break;
  }
  x.ToString(); // warn
}

Note only cases that are pure null tests have this effect. So cases like _, var y, or C {} do not change the state of x when exiting the switch statement.

Null tests

Pure tests:

  1. x == null
  2. x != null
  3. (Type)x == null
  4. (Type)x != null
  5. x is null
  6. x is { }
  7. derived is Base
  8. x?.F
  9. x?[i]
  10. x ?? y
  11. bool [EqualsBehavior]MyEquals(object other)

Not pure tests:

  1. x is Y // where Y is not a base of X
  2. x is Y y
  3. x is Y _
  4. x is C { Property = 3 }
  5. TryGetValue([NotNullWhenTrue])
  6. [NotNulllWhenFalse] string.IsNullOrEmpty(s)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment