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

Inference error with ternary operator and null safety #45941

Closed
goderbauer opened this issue May 6, 2021 · 4 comments
Closed

Inference error with ternary operator and null safety #45941

goderbauer opened this issue May 6, 2021 · 4 comments

Comments

@goderbauer
Copy link
Contributor

I would expect the following code to be valid and error free. However, if null safety is enabled, it gives the error mentioned in the comment below. (Without null safety, no error is reported.)

PSW foo(bool bar) {
  final PSW result = bar ? CNB() : AB(); // ERROR: A value of type 'W' can't be assigned to a variable of type 'PSW'.
  return result;
}

class CNB extends SW implements PSW { }

class AB extends SW implements PSW { }

class PSW implements W { }

class SW extends W { }

class W { }

This came up in flutter/flutter#81969.

@stereotype441
Copy link
Member

This is behaving as designed. What's happening is that when compiling the subexpression bar ? CNB() : AB(), the compiler needs to assign it a unique static type which both CNB() and AB() satisfy. It does so using the "least upper bound" algorithm to look for a type that is a supertype of both CNB and AB. The class hierarchy looks like this:

   W
  / \
SW   PSW
 | \/ |
 | /\ |
CNB  AB

As you can see from the class hierarchy, either SW or PSW would be a suitable type. But the least upper bound algorithm doesn't know which one of those you want. So to avoid the ambiguity, it walks up the class hierarchy one more step and picks W to be the static type of the resulting expression.

Before null safety, trying to assign an expression of type W to a variable of type PSW caused an implicit downcast. A lot of people dislike implicit downcasts in general because they can hide mistakes, and because they are a source of runtime errors that aren't obivous from looking at the code.

Because of that (and other reasons specific to null safety) we drastically limited implicit downcasts with the introduction of null safety: now we only do implicit downcasting if the expression in question has a static type of dynamic. So that's why you're getting an error now. If you want to get the behavior you used to have before null safety, you need to write the cast explicitly, for example:

  final PSW result = (bar ? CNB() : AB()) as PSW;

@stereotype441
Copy link
Member

I filed dart-lang/language#1618 with an idea for a future improvement of the least upper bound algorithm that would avoid this problem.

@goderbauer
Copy link
Contributor Author

Interesting! Thanks for the in-depth explanation!

I am going to close this issue then since dart-lang/language#1618 sounds more actionable.

@eernstg
Copy link
Member

eernstg commented Feb 2, 2022

@goderbauer, it might be helpful to note that it is also possible to give a bit of guidance to the least upper bound algorithm by upcasting one of the branches of the conditional expression such that the superinterface graph doesn't have that ambiguity:

final result = bar ? CNB() as PSW : AB(); // OK, LUB of `PSW` and `AB` is `PSW`.

We could include the declared type of result in order to double check that the type of the initializing expression is now PSW, but it does not make any difference.

In any case, we might prefer this approach because it is statically safe, and faster (there is no downcast).

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

No branches or pull requests

3 participants