Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should enhanced type promotion track when a type is exactly Null? #461

Closed
stereotype441 opened this issue Jul 20, 2019 · 4 comments
Closed
Labels
nnbd NNBD related issues

Comments

@stereotype441
Copy link
Member

The current (work in progress) implementation of enhanced type promotion allows a != null check to promote a type to non-nullable, e.g.:

void f(int? i) {
  if (i != null) {
    print(i.isEven); // Ok, since the type of i is promoted to `int`
  }
}

In principle, we could also allow an == null check to promote a type to Null, e.g.:

void f(int? i) {
  if (i == null) {
    print(i?.isEven); // ERROR: type `Null` has no method `isEven`
  }
}

But I'm not sure whether it's a good idea or not. Arguments in favor:

  • It's not a lot of implementation work.
  • It might help the user by rejecting code at compile time that's almost certainly buggy.

Arguments against:

  • It's not a strictly necessary feature to make NNBD useful.
  • It rejects code that wouldn't necessarily produce an error at runtime (see the example above).
  • It's not really in the spirit of type promotion; whereas other type promotions promote to a type that is strictly more useful (allowing the user to avoid inserting redundant casts), promoting to the Null type is strictly less useful, so it wouldn't allow the user to avoid inserting any casts.
@stereotype441 stereotype441 added the nnbd NNBD related issues label Jul 20, 2019
dart-bot pushed a commit to dart-lang/sdk that referenced this issue Jul 21, 2019
We now track nullability via type promotion, e.g. by promoting from
`int?` to `int` to represent a non-nullable value.

I've added a few more unit tests of flow analysis that were helpful
while preparing this CL.  I plan to add more tests in follow-up CLs.

A few behaviors are lost by this change:

- We no longer track whether a variable's value is exactly null
  (previously the tests annotated such variables as "nullable", which
  was a bit misleading).  I'm not sure whether we really want this
  feature or not; I've filed
  dart-lang/language#461 for discussion.

- Assignment of a non-null value to a nullable-typed variable no
  longer promotes it to non-nullable.  I think we want to get this
  behavior back, but we'll need to add a little bit more machinery to
  get it back (we need to allow assignments in general to promote.
  I'll address in follow-up CLs.  See
  dart-lang/language#440 for discussion.

- A promotion to non-nullable that happens during a "try" block is
  lost if there is a "finally" block.  I think we want to get this
  behavior back, but we'll need to add a little bit more machinery to
  do so, to allow promotions in general to be preserved by
  try/finally.  I'll address in follow-up CLs.

Change-Id: Ibc45d70043725697fbee063a8dcefb1179506e9b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/109780
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
@eernstg
Copy link
Member

eernstg commented Jul 22, 2019

Promotion via value comparison is a weird corner case:

Null gets a very special treatment during evaluation of e1 == e2, and we are already using that to justify promotion based on x != null (if that's true then x is definitely not null). It is also guaranteed that if x == null is true then x is definitely null.

But any expression that actually invokes operator == on a user-defined class instance can of course return whatever it wants. E.g., x == 42 can certainly return true if the value of x is an instance of SomeClass whose operator == makes that choice, so there's no guarantee that x is int would have evaluated to true. Other value comparisons like x == const <int>[] also provide no guarantees, whereas const <int>[1, 2] == x would give us a type guarantee via canonicalization (but that's again a very special case), and 42 == x could provide a guarantee because the platform controls operator == for the instance obtained from evaluating 42 (special casing again). So there is no valid argument for extending value comparison based promotion beyond null (and no need to worry about being inconsistent when we don't do that).

On the other hand, we generally promote based on specific syntactic forms when we have enough information to know that it's sound.

On balance I think it would make sense to promote x to Null on x == null: That will consistently exploit the special treatment of null with ==, and we can't make the case that, for consistency, we should go further and promote on other values than null.

PS: I would expect the response to i?.isEven to be more like "error: Accessing a member of Never", cf. nnbd static errors. In that sense it's actually more like a dead code diagnostic.

@munificent
Copy link
Member

munificent commented Jul 23, 2019

I think it's reasonable. Consider:

void f(int? i) {
  if (i != null) {
    print("not null");
    return; // <--
  }

  print(i?.isEven); // ???
}

The only way to reach the last line is when i is definitely not null. I think it would be good to tell users that this code isn't going to do anything useful, and promotion to Null would do that.

@leafpetersen
Copy link
Member

This is actually already specified here. That part of the specification should possibly be folded into the flow analysis based promotion spec though.

@lrhn
Copy link
Member

lrhn commented Feb 3, 2020

As linked above by @leafpetersen, the answer was yes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nnbd NNBD related issues
Projects
None yet
Development

No branches or pull requests

5 participants