-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[Breaking Change] Make type inference of e1 ?? e2
consistent.
#55436
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
Comments
e1 ?? e2
consistent.e1 ?? e2
consistent.
This is just about how the type on the RHS of As in: T? nope<T>() => null;
void main() {
var x = nope<int>() ?? ''; // x is inferred to be Object rather than int, even after this change
} Assuming I understand correctly, SGTM. |
lgtm |
(Shouldn't the context type of void main() async {
List<int>? maybeList = null as dynamic;
var x = maybeList ?? (await Future.value([]));
print([x].runtimeType);
} gives |
Correct, it's just about how the RHS of
Yes. In fact, this code is unaffected by the change for two reasons: (1) because the type of the RHS ( |
Yeah, considering that this is an example of an aspirational context, I agree. It makes sense to do something as close as possible to what we think the user probably wants, and NonNull(S) seems like a better model of what the user probably wants than S.
Agreed 😃 |
@grouma ping |
cc @leonsenft who will handle breaking change requests for ACX going forward. |
LGTM assuming
holds. |
I will recheck before landing the change. |
@stereotype441 lock it in! |
The rules used by the compiler front end to perform type inference on if-null expressions (expressions of the form
e1 ?? e2
) will be changed to match the behavior of the analyzer. The change is as follows: when the context for the entiree1 ?? e2
expression isdynamic
, the context fore2
will be the static type ofe1
.This change is expected to have low impact on real-world code. But in principle it could cause compile-time errors or changes in runtime behavior.
Background
When the compiler needs to perform type inference on an expression, it does so using a type schema known as the "context". A type schema is a generalization of the normal Dart type syntax, in which
_
(called the "unknown type") can appear where a type is expected.In the analyzer, any time an expression would be analyzed with a context of
dynamic
, the context is coerced to_
before performing the analysis; this causes contexts ofdynamic
and_
to behave identically1. In the compiler front end,dynamic
is coerced to_
when analyzing a generic invocation, butdynamic
and_
behave differently for a few expression types. This breaking change addresses one of those differences, which is in the behavior of if-null expressions (expressions of the forme1 ?? e2
).The current behavior for if-null expressions is as follows. If the context for
e1 ?? e2
isK
, then type inference first analyzese1
using the contextK?
. Then, it analyzese2
using the contextL
, whereL
is computed as follows:K
is_
, thenL
is the static type ofe1
.K
isdynamic
, then in the compiler front end,L
isdynamic
; in the analyzer,L
is the static type ofe1
.2L
isK
.Intended Change
The above rules will be changed so that in the compiler front end, if
K
isdynamic
, thenL
will be the static type ofe1
, as it is in the analyzer.Justification
Any difference in type inference behavior between the analyzer and the compiler front end is a bug. To reduce the impact of the bug fix, it makes sense to standardize on either the analyzer's behavior or the compiler front end's behavior. In this case, standardizing on the analyzer behavior is better, since the analyzer is more self-consistent (it always treats contexts of
dynamic
and_
the same). Once this change and #55418 are made, there will not be any scenarios in which the compiler front end treatsdynamic
and_
contexts differently.Expected Impact
A prototype of this change caused zero test failures in Google's internal codebase, so the impact is expected to be low for real-world code.
But it is theoretically possible for a program to behave differently with the change. Here is a contrived example:
Today, this program prints
List<dynamic>
and then runs to completion; with the change, it will printList<int>
and then terminate with an exception.y
has an explicit type ofdynamic
, the contextdynamic
is used for type inference of the expressionx ?? []
.x ?? []
is to type inferx
using a context ofdynamic?
(which is the same asdynamic
). Sincex
is simply a reference to a local variable, its static type is the type of that local variable,List<int>?
.[]
. Today, the compiler front end does this using a context ofdynamic
, so the list is inferred to be<dynamic>[]
. With the change, the list will be inferred using a context ofList<int>?
, so it will be inferred to be<int>[]
.print(y.runtimeType)
will printList<int>
instead ofList<dynamic>
.y
will cause a runtime failure.Mitigation
In the unlikely event that some real-world customer code is affected, the effect will be limited to type inference. So the old behavior can be restored by supplying explicit types. For example, the above example could be changed to:
(Note that
[]
has been changed to<dynamic>[]
.)Footnotes
This coercion doesn't happen when
dynamic
appears more deeply inside the context; for example, a context ofList<dynamic>
is not changed toList<_>
. ↩This is a consequence of the fact that the analyzer coerces
dynamic
to_
, therefore rule 1 applies. ↩The text was updated successfully, but these errors were encountered: