-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
[Breaking Change Request] Inference change when using Null values in a FutureOr context #37985
Comments
Your review and approval is required for this BREAKING CHANGE. |
Emails sent to announce@dart-lang, flutter-dev, and dart-misc . |
I don't have an opinion about the right way this should be implemented, but I agree that consistency seems wise. |
LGTM, considering I think this has no impact on users we care about. |
LGTM. |
Link to the breaking change request: #37985 Change-Id: I0fcb058e953a43a20e7b663f63bb88100f376a6b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/116762 Reviewed-by: Leaf Petersen <leafp@google.com> Commit-Queue: Dmitry Stefantsov <dmitryas@google.com>
Catching up on paperwork. This is approved. |
This has landed in bleeding edge. I will follow up on the mailing list. |
Summary
Inference works by solving subtype constraints containing free generic method parameters against concrete types. Currently, when solving the subtype constraint
Null <: FutureOr<T>
forT
, our implementations behave divergently: the analyzer produces a solution ofNull
forT
, but the CFE producesdynamic
. Both of these are valid solutions, but can have divergent downstream behavior. We propose to make the implementations behave consistently. We expect breakage to be fairly minor.Example 1: Code which breaks at runtime when using analyzer style inference.
For this example, the analyzer infers the generic type argument to the
.then
call asNull
. When theonError
call back is called, the result (true
) is cast toFutureOr<Null>
, which fails.The CFE infers the generic type argument to the
.then
call asdynamic
. When theonError
call back is called, the result (true
) is cast toFutureOr<dynamic>
, which succeeds.Example 2: Code which breaks at runtime when using CFE style inference.
For this example, the analyzer again infers the generic type argument to the
.then
call asNull
. When running under DDC (which uses the analyzer's inference), the assignment ofx
toy
therefore succeeds.The CFE infers the generic type argument to the
.then
call asdynamic
. As a result, the assignment ofx
toy
is an implicit downcast, which fails at runtime.Details
As described above, the core of the issue is that when solving the subtype constraint
Null <: FutureOr<T>
forT
, our implementations behave divergently. The analyzer tries to solve the sub-constraintNull <: Future<T>
, notices that it succeeds without producing any useful information, and proceeds to try the sub-constraintNull <: T
, which produces a solution which constrainsT
toNull
. The CFE, on the other hand, stops searching for further constraints after checkingNull <: Future<T>
, since the equation is trivially true under no assumptions onT
. More discussion can be found in this issue.Proposed change
Making the two implementations behave consistently is the paramount concern. Either choice of inference is valid. We propose to change inference in the CFE to match the behavior in the analyzer, since this behavior results in a more generally useful result, and since we seem to see less breakage from making the change in this direction. In particular, as measured on internal code, we see more test failures when changing DDC to use the CFE behavior than when changing the other platforms to use the DDC/analyzer behavior.
Expected impact
Currently, the analyzer and DDC use the analyzer style inference, and dart2js, DDK, the VM, and the AOT compilers all use the CFE based inference. After this change, all platforms will use the analyzer style inference. This can cause both static and runtime breakage in the following situations.
Any code which uses the analyzer and/or runs on DDC, and also runs on any of the CFE based tools will not see any static breakage.
Any code which runs on DDC, and also runs on any of the CFE based tools will not see any runtime breakage.
Code which never uses the analyzer and never runs on DDC, may see breakage. It is possible (but fairly unlikely) that it will see static breakage, but most likely any breakage will be in the form of runtime cast failures. We suspect that the
onError
pattern in Example 1 above is likely to be the most common failure mode: aFuture
is created with a callback that has a return type ofNull
(usually because it throws an error directly or indirectly), and anonError
callback which returns some concrete value intended to handle the thrown error. When theonError
callback is invoked and the result is cast toNull
, a runtime cast failure will result.Mitigation
In general, static or runtime failures that result from this change will be the result of inference filling in a generic type parameter with
Null
where previouslydynamic
was inferred. The previous behavior can always be restored by explicitly passingdynamic
as the type argument instead of relying on inference. Concretely, the code fromExample 1
would be changed by passing an explicit type argument to the.then
method as follows:With this modification, the code will behave (on all platforms) as it did on the CFE before this breaking change.
The text was updated successfully, but these errors were encountered: