-
Notifications
You must be signed in to change notification settings - Fork 226
Description
Thanks to @osa1 for bringing up this topic!
The expression e1 ?? e2
infers the type of e2
in the context type schema of the whole expression, if there is one. However, when there is no context type schema for the entire expression (that is, it's _
), the static type of e1
is used as the context type schema for e2
.
This implies that we get a more imprecise inference result when the type of e1
isn't greater or equal than the type of e2
. For example, e1
could be a List<...>
and e2
could be an iterable:
Iterable<X> getIterable<X>() => <X>[];
void main() {
final List<int>? xs = null;
var ys = xs ?? getIterable();
print(ys.runtimeType); // `List<dynamic>`.
}
We could change the computation of the context type schema for e2
when e1 ?? e2
occurs in the empty context _
:
Let
S
be the static type ofe1
(inferred in the empty context_
). LetS1
be the union-free erasure ofS
(so ifS
isList<T>?
thenS1
isList<T>
, and similarly forFutureOr
). IfS1
is not an interface type then the context type fore2
isNonNull(S)
. Otherwise, letS2
be the unique type in the superinterface graph ofS1
which has the greatest depth, and is also a supertype of the most special generic instantiation of the type ofe2
after union-free erasure. If there is no such type thene2
again gets context typeNonNull(S)
. Otherwise, letS3
beS2
with the union types ofS
added back in. ThenNonNull(S3)
is used as the context type for the inference ofe2
.
Edit: Computing S2
may incur approximately the same amount of work as performing type inference, so we might want to do something which is simpler, and a conservative approximation.
Note that S
is actually a type, not a type schema, because it is the result of inference on e1
. Also note that "unique, with the greatest depth" is the same criterion as the last item in the computation of UP
, that is, the Dart 1 algorithm for the computation of the "least" upper bound. However, the treatment of type arguments in the type of e2
is different: They are essentially ignored. So we're looking for the most special (hence: most informative) supertype of the type of e1
for which we can hope to make the static type of e2
a subtype using inference.
In the example above, S
is List<int>?
, S1
is List<int>
, S2
is Iterable<int>
, and S3
is Iterable<int>?
, which means that Iterable<int>
is used as the context type for the inference of getIterable()
, yielding getIterable<int>()
.
See #4518 for a situation where this really matters.
@dart-lang/language-team, WDYT?