mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:09:49 +00:00
ca220074db
Issue https://github.com/dart-lang/sdk/issues/54628 raised the issue that the static analysis of `await e` behaves in unexpected ways when the static type of `e` is a subtype of a future related type, rather than just a plain `Future<T>` or `FutureOr<T>` for some `T`. This CL adds a test that assumes the update to the language specification which is performed by the PR https://github.com/dart-lang/language/pull/3574. Hence, this CL should ONLY BE LANDED if and when that PR is landed. Change-Id: Ib8acb77e24ffbceb0b4034d3b23a0f4fef8e3d1c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/354020 Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Erik Ernst <eernst@google.com>
441 lines
13 KiB
Dart
441 lines
13 KiB
Dart
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// We say that a type T derives a future type F in the following cases,
|
|
// using the first applicable case:
|
|
//
|
|
// 1. If T is a type which is introduced by a class, mixin, or enum
|
|
// declaration, and if T or a direct or indirect superinterface of T
|
|
// is Future<U> for some U, then T derives the future type Future<U>.
|
|
// 2. If T is the type FutureOr<U> for some U, then T derives the future
|
|
// type FutureOr<U>.
|
|
// 3. If T is S? for some S, and S derives the future type F, then T derives
|
|
// the future type F?.
|
|
// 4. If T is a type variable with bound B, and B derives the future type F,
|
|
// then T derives the future type F.
|
|
// - (There is no rule for the case where T is of the form X&S because this
|
|
// will never occur.)
|
|
//
|
|
// In Dart of today, rule 1 is extended with a case for extension types.
|
|
//
|
|
// Many test cases below use an expression of the form `await e` where the
|
|
// static type of `e` is such that the rule in the definition of _flatten_
|
|
// which is used is one of the following:
|
|
//
|
|
// - If T derives a future type Future<S> or FutureOr<S> then flatten(T) = S.
|
|
// - If T derives a future type Future<S>? or FutureOr<S>? then flatten(T) = S?.
|
|
//
|
|
// This implies that the test which is concerned with the behavior of _flatten_
|
|
// is pretty directly also a test of the behavior of "derives a future type".
|
|
//
|
|
// To see that the "derives a future type" algorithm is applied correctly,
|
|
// we also need to check types of the form `X & B`, for which the "derives a
|
|
// future type" algorithm is applied to `B`. This kind of test is also needed
|
|
// for one more reason: If an implementation is not updated at all (so it
|
|
// uses the old definition of _flatten_) then it would succeed in many `T?`
|
|
// tests without ever invoking the "derives a future type" algorithm at all.
|
|
// With a type of the form `X & B`, both the old and the new definition must
|
|
// use "derives a future type".
|
|
//
|
|
// Comments of the form '// 3.2 ...' are used to indicate which rules are
|
|
// usde to find the future type which is derived: Rule 3 is used first, then
|
|
// a recursive invocation uses rule 2, and similarly for other digit
|
|
// sequences.
|
|
|
|
import 'dart:async';
|
|
import '../static_type_helper.dart';
|
|
|
|
mixin class FakeFutureHelper<T> {
|
|
Future<T> get _realFuture;
|
|
Future<R> then<R>(FutureOr<R> Function(T) onValue, {Function? onError}) =>
|
|
_realFuture.then<R>(onValue, onError: onError);
|
|
Future<T> catchError(Function onError, {bool Function(Object)? test}) =>
|
|
_realFuture.catchError(onError, test: test);
|
|
Future<T> whenComplete(FutureOr<void> Function() onDone) =>
|
|
_realFuture.whenComplete(onDone);
|
|
noSuchMethod(i) => throw UnimplementedError("Not needed for test");
|
|
}
|
|
|
|
class SubFuture extends FakeFutureHelper<int> implements Future<int> {
|
|
final Future<int> _realFuture = Future<int>.value(0);
|
|
}
|
|
|
|
mixin MixFuture implements Future<int> {
|
|
final Future<int> _realFuture = Future<int>.value(0);
|
|
}
|
|
|
|
class WithMixFuture extends Object with FakeFutureHelper<int>, MixFuture {
|
|
final Future<int> _realFuture = Future<int>.value(0);
|
|
}
|
|
|
|
enum EnumFuture with FakeFutureHelper<int> implements Future<int> {
|
|
one,
|
|
two;
|
|
|
|
Future<int> get _realFuture => this == one ? _futureOne : _futureTwo;
|
|
|
|
static final Future<int> _futureOne = Future<int>.value(1);
|
|
static final Future<int> _futureTwo = Future<int>.value(2);
|
|
}
|
|
|
|
// Note that the representation type is different from the superinterface.
|
|
extension type ExtensionFuture(Future<String> _) implements Future<String?> {}
|
|
|
|
extension type ExtensionSubFuture(SubFuture _) implements SubFuture {}
|
|
|
|
void main() async {
|
|
{
|
|
// 1, derives `Future<String>`.
|
|
var x = Future<String>.value("Whatever");
|
|
(await x).expectStaticType<Exactly<String>>();
|
|
}
|
|
{
|
|
// 1, derives `Future<int>`.
|
|
var x = SubFuture();
|
|
(await x).expectStaticType<Exactly<int>>();
|
|
}
|
|
{
|
|
// 1, derives `Future<int>`.
|
|
MixFuture x = WithMixFuture();
|
|
(await x).expectStaticType<Exactly<int>>();
|
|
}
|
|
{
|
|
// 1, derives `Future<int>`.
|
|
var x = EnumFuture.one;
|
|
(await x).expectStaticType<Exactly<int>>();
|
|
}
|
|
{
|
|
// 1, derives `Future<String?>`.
|
|
var x = ExtensionFuture(Future<String>.value("Whatever"));
|
|
(await x).expectStaticType<Exactly<String?>>();
|
|
}
|
|
{
|
|
// 1, derives `Future<int>`.
|
|
var x = ExtensionSubFuture(SubFuture());
|
|
(await x).expectStaticType<Exactly<int>>();
|
|
}
|
|
{
|
|
// 2, derives `FutureOr<bool>`.
|
|
FutureOr<bool> x = true;
|
|
(await x).expectStaticType<Exactly<bool>>();
|
|
}
|
|
{
|
|
// 2, derives `FutureOr<bool?>`.
|
|
FutureOr<bool?> x = true;
|
|
(await x).expectStaticType<Exactly<bool?>>();
|
|
}
|
|
{
|
|
// 3.1, derives `Future<String>?`.
|
|
Future<String>? x;
|
|
(await x).expectStaticType<Exactly<String?>>();
|
|
}
|
|
{
|
|
// 3.1, derives `Future<int>?`.
|
|
SubFuture? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.1, derives `Future<int>?`.
|
|
MixFuture? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.1, derives `Future<int>?`.
|
|
EnumFuture? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.1, derives `Future<String?>?`.
|
|
ExtensionFuture? x;
|
|
(await x).expectStaticType<Exactly<String?>>();
|
|
}
|
|
{
|
|
// 3.1, derives `Future<int>?`.
|
|
ExtensionSubFuture? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.2, derives `FutureOr<bool>?`.
|
|
FutureOr<bool>? x;
|
|
(await x).expectStaticType<Exactly<bool?>>();
|
|
}
|
|
{
|
|
// 3.2, derives `FutureOr<bool?>?`.
|
|
FutureOr<bool?>? x;
|
|
(await x).expectStaticType<Exactly<bool?>>();
|
|
}
|
|
|
|
await f1<Future<int>, SubFuture, ExtensionFuture, ExtensionSubFuture,
|
|
FutureOr<Object>, Future<Never>?, Future<Symbol>, Future<Symbol>>(
|
|
Future<int>.value(10),
|
|
SubFuture(),
|
|
ExtensionFuture(Future<String>.value('f1')),
|
|
ExtensionSubFuture(SubFuture()),
|
|
20,
|
|
null,
|
|
Future.value(#foo));
|
|
|
|
await f2<Object?, Future<Never>, FutureOr<Object>, Future<Never>?,
|
|
Future<Symbol>, Future<Symbol>>(true);
|
|
|
|
await C1<Future<int>, SubFuture, ExtensionFuture, ExtensionSubFuture,
|
|
FutureOr<Object>, Future<Never>?, Future<Symbol>, Future<Symbol>>()
|
|
.f1(
|
|
Future<int>.value(10),
|
|
SubFuture(),
|
|
ExtensionFuture(Future<String>.value('f1')),
|
|
ExtensionSubFuture(SubFuture()),
|
|
20,
|
|
null,
|
|
Future.value(#foo));
|
|
|
|
await C2<Object?, Future<Never>, FutureOr<Object>, Future<Never>?,
|
|
Future<Symbol>, Future<Symbol>>()
|
|
.f2(true);
|
|
}
|
|
|
|
// Test non-promoted types that involve function type parameters.
|
|
Future<void> f1<
|
|
X1a extends Future<int>,
|
|
X1b extends SubFuture,
|
|
X1c extends ExtensionFuture,
|
|
X1d extends ExtensionSubFuture,
|
|
X2 extends FutureOr<Object>,
|
|
X3 extends Future<Never>?,
|
|
X4 extends Y,
|
|
Y extends Future<Symbol>>(
|
|
X1a x1a, X1b x1b, X1c x1c, X1d x1d, X2 x2, X3 x3, X4 x4) async {
|
|
// 4.1, `X1a` derives `Future<int>`.
|
|
(await x1a).expectStaticType<Exactly<int>>();
|
|
|
|
// 4.1, `X1b` derives `Future<int>`.
|
|
(await x1b).expectStaticType<Exactly<int>>();
|
|
|
|
// 4.1, `X1c` derives `Future<String?>`.
|
|
(await x1c).expectStaticType<Exactly<String?>>();
|
|
|
|
// 4.1, `X1d` derives `Future<int>`.
|
|
(await x1d).expectStaticType<Exactly<int>>();
|
|
|
|
// 4.2, `X2` derives `FutureOr<Object>`.
|
|
(await x2).expectStaticType<Exactly<Object>>();
|
|
|
|
// 4.3.1, `X3` derives `Future<Never>?`.
|
|
(await x3).expectStaticType<Exactly<Never?>>();
|
|
|
|
// 4.4.1, `X4` derives `Future<Symbol>`.
|
|
(await x4).expectStaticType<Exactly<Symbol>>();
|
|
|
|
{
|
|
// 3.4.1, `X1a?` derives `Future<int>?`.
|
|
X1a? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.4.1, `X1b?` derives `Future<int>?`.
|
|
X1b? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.4.1, `X1c?` derives `Future<String?>?`.
|
|
X1c? x;
|
|
(await x).expectStaticType<Exactly<String?>>();
|
|
}
|
|
{
|
|
// 3.4.1, `X1d?` derives `Future<int>?`.
|
|
X1d? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.4.2, `X2?` derives `FutureOr<Object>?`.
|
|
X2? x;
|
|
(await x).expectStaticType<Exactly<Object?>>();
|
|
}
|
|
{
|
|
// 3.4.3.1, `X3?` derives `Future<Never>??`, i.e., `Future<Never>?`.
|
|
X3? x;
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
{
|
|
// 3.4.4.1, `X4?` derives `Future<Symbol>?`.
|
|
X4? x;
|
|
(await x).expectStaticType<Exactly<Symbol?>>();
|
|
}
|
|
}
|
|
|
|
// Test promoted types that involve function type parameters. In this
|
|
// case the type of `x` is of the form `X & B`, which calls for the
|
|
// future type that `B` derives (if any). So the type of `x` may be
|
|
// `X & X1?`, but the relevant question is then: which future type does
|
|
// `X1?` derive?
|
|
Future<void> f2<
|
|
X,
|
|
X1 extends Future<Never>,
|
|
X2 extends FutureOr<Object>,
|
|
X3 extends Future<Never>?,
|
|
X4 extends Y,
|
|
Y extends Future<Symbol>>(X x) async {
|
|
if (x is X1) {
|
|
// 4.1, `X1` derives `Future<Never>`.
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X2) {
|
|
// 4.2, `X2` derives `FutureOr<Object>`.
|
|
(await x).expectStaticType<Exactly<Object>>();
|
|
}
|
|
|
|
if (x is X3) {
|
|
// 4.3.1, `X3` derives `Future<Never>?`.
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X4) {
|
|
// 4.4.1, `X4` derives `Future<Symbol>`.
|
|
(await x).expectStaticType<Exactly<Symbol>>();
|
|
}
|
|
|
|
if (x is X1?) {
|
|
// 3.4.1, `X1?` derives `Future<Never>?`.
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X2?) {
|
|
// 3.4.2, `X2?` derives `FutureOr<Object>?`.
|
|
(await x).expectStaticType<Exactly<Object?>>();
|
|
}
|
|
|
|
if (x is X3?) {
|
|
// 3.4.3.1, `X3?` derives `Future<Never>??`, i.e., `Future<Never>?`.
|
|
X3? x;
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X4?) {
|
|
// 3.4.4.1, `X4?` derives `Future<Symbol>?`.
|
|
(await x).expectStaticType<Exactly<Symbol?>>();
|
|
}
|
|
}
|
|
|
|
// Test non-promoted types that involve class type parameters.
|
|
class C1<
|
|
X1a extends Future<int>,
|
|
X1b extends SubFuture,
|
|
X1c extends ExtensionFuture,
|
|
X1d extends ExtensionSubFuture,
|
|
X2 extends FutureOr<Object>,
|
|
X3 extends Future<Never>?,
|
|
X4 extends Y,
|
|
Y extends Future<Symbol>> {
|
|
Future<void> f1(
|
|
X1a x1a, X1b x1b, X1c x1c, X1d x1d, X2 x2, X3 x3, X4 x4) async {
|
|
// 4.1, `X1a` derives `Future<int>`.
|
|
(await x1a).expectStaticType<Exactly<int>>();
|
|
|
|
// 4.1, `X1b` derives `Future<int>`.
|
|
(await x1b).expectStaticType<Exactly<int>>();
|
|
|
|
// 4.1, `X1c` derives `Future<String?>`.
|
|
(await x1c).expectStaticType<Exactly<String?>>();
|
|
|
|
// 4.1, `X1d` derives `Future<int>`.
|
|
(await x1d).expectStaticType<Exactly<int>>();
|
|
|
|
// 4.2, `X2` derives `FutureOr<Object>`.
|
|
(await x2).expectStaticType<Exactly<Object>>();
|
|
|
|
// 4.3.1, `X3` derives `Future<Never>?`.
|
|
(await x3).expectStaticType<Exactly<Never?>>();
|
|
|
|
// 4.4.1, `X4` derives `Future<Symbol>`.
|
|
(await x4).expectStaticType<Exactly<Symbol>>();
|
|
|
|
{
|
|
// 3.4.1, `X1a?` derives `Future<int>?`.
|
|
X1a? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.4.1, `X1b?` derives `Future<int>?`.
|
|
X1b? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.4.1, `X1c?` derives `Future<String?>?`.
|
|
X1c? x;
|
|
(await x).expectStaticType<Exactly<String?>>();
|
|
}
|
|
{
|
|
// 3.4.1, `X1d?` derives `Future<int>?`.
|
|
X1d? x;
|
|
(await x).expectStaticType<Exactly<int?>>();
|
|
}
|
|
{
|
|
// 3.4.2, `X2?` derives `FutureOr<Object>?`.
|
|
X2? x;
|
|
(await x).expectStaticType<Exactly<Object?>>();
|
|
}
|
|
{
|
|
// 3.4.3.1, `X3?` derives `Future<Never>??`, i.e., `Future<Never>?`.
|
|
X3? x;
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
{
|
|
// 3.4.4.1, `X4?` derives `Future<Symbol>?`.
|
|
X4? x;
|
|
(await x).expectStaticType<Exactly<Symbol?>>();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test promoted types that involve type parameters. In this case the
|
|
// type of `x` is of the form `X & B`, which calls for the future type
|
|
// that `B` derives (if any). So the type of `x` may be `X & X1?`, but
|
|
// the relevant question is then: which future type does `X1?` derive?
|
|
class C2<X, X1 extends Future<Never>, X2 extends FutureOr<Object>,
|
|
X3 extends Future<Never>?, X4 extends Y, Y extends Future<Symbol>> {
|
|
Future<void> f2(X x) async {
|
|
if (x is X1) {
|
|
// 4.1, `X1` derives `Future<Never>`.
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X2) {
|
|
// 4.2, `X2` derives `FutureOr<Object>`.
|
|
(await x).expectStaticType<Exactly<Object>>();
|
|
}
|
|
|
|
if (x is X3) {
|
|
// 4.3.1, `X3` derives `Future<Never>?`.
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X4) {
|
|
// 4.4.1, `X4` derives `Future<Symbol>`.
|
|
(await x).expectStaticType<Exactly<Symbol>>();
|
|
}
|
|
|
|
if (x is X1?) {
|
|
// 3.4.1, `X1?` derives `Future<Never>?`.
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X2?) {
|
|
// 3.4.2, `X2?` derives `FutureOr<Object>?`.
|
|
(await x).expectStaticType<Exactly<Object?>>();
|
|
}
|
|
|
|
if (x is X3?) {
|
|
// 3.4.3.1, `X3?` derives `Future<Never>??`, i.e., `Future<Never>?`.
|
|
X3? x;
|
|
(await x).expectStaticType<Exactly<Never?>>();
|
|
}
|
|
|
|
if (x is X4?) {
|
|
// 3.4.4.1, `X4?` derives `Future<Symbol>?`.
|
|
(await x).expectStaticType<Exactly<Symbol?>>();
|
|
}
|
|
}
|
|
}
|