[Breaking Change] Make the type schema for null-aware spread operations consistent #54828
Labels
area-meta
Cross-cutting, high-level issues (for tracking many other implementation issues, ...).
breaking-change-approved
breaking-change-request
This tracks requests for feedback on breaking changes
The type schema used by the compiler front end to perform type inference on the operand of a null-aware spread operator (
...?
) in map and set literals will be made nullable, to match the behavior of list literals (and to match the behavior of the analyzer).Although this is technically a breaking change, it's not expected to have any effect on real-world code.
Background
When the compiler needs to perform type inference on the operand of a spread operator (
...
or...?
), it uses a type schema that's based on the context of the surrounding literal. For example, consider the following code:Since no element type is specified for the empty list
[]
, the compiler needs to infer one. It does so by taking the return type off
(List<List<int>>
), extracting its element type (List<int>
), wrapping it inIterable
(Iterable<List<int>>
), and then using that as the type schema for performing type inference onvalues.map((x) => [])
. This in turn causes[]
to be type inferred using the type schemaList<int>
, so[]
is interpreted as<int>[]
.If the spread operator had been null-aware (
...?
instead of...
), then the type schema used to type infervalues.map((x) => [])
would have beenIterable<List<int>>?
instead ofIterable<List<int>>
. (In other words, a nullable type schema would have been used, because the null-aware spread operator can handle the possibility of its operand evaluating tonull
). This doesn't have any effect onvalues.map((x) => [])
(becausemap
always returns a non-null value), but it's possible to construct contrived examples where there's a user-visible effect. For example, here's some highly contrived code that detects the type schema using a generic function:This prints:
Which shows that type inference is using a type schema of
Iterable<int>?
when inferring the generic type parameter fordetectTypeSchema
in the declaration ofx2
.However, as of the current version of Dart, the same thing does not happen with set or map literals. This code:
prints this result:
This is clearly an oversight; the compiler should use a nullable type schema for null-aware spread operators in all kinds of collection literals, not just lists.
Intended change
The type schema used by the compiler front end to perform type inference on the operand of a null-aware spread operator (
...?
) in map and set literals will be made nullable, to match what currently happens in list literals. This will make the compiler front end behavior consistent with that of the analyzer.This change will cause the test code above to print the following result:
Justification
In addition to the advantages of making the compiler front-end more self-consistent, and making it more consistent with the analyzer, the change makes reasonable sense from first principles. With few exceptions, the intention of the type schema is to capture what values would be permissible for a given expression to take on. Since null-aware spread operations accept
null
, it makes sense for them to use a nullable type schema.Expected impact
A prototype of this change caused zero test failures in Google's internal codebase, so the impact is expected to be extremely low for real-world code.
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, in the code above, the change exerts its effect through the type that's inferred for the generic function
detectTypeSchema
. To restore the old functionality, the user simply needs to specify explicit types for the generic function. For example:The text was updated successfully, but these errors were encountered: