[vm/aot/tfa] Handle classes which implement Future along with another generic type in a cast to FutureOr

When handling a type check to FutureOr in TFA, there is a case
when a ConcreteType is a subtype of Future. In such case, it was
assumed that ConcreteType has only 1 type argument.
This is not true if class extends/implements/mixes-in Future along
with another generic class.

This change adds the logic to query offset of Future type arguments in
the type arguments of a class and also check if the type argument is
known.

TEST=pkg/vm/testcases/transformations/type_flow/transformer/regress_flutter81068.dart
Fixes https://github.com/flutter/flutter/issues/81068

Change-Id: I970e649823bafec433fc21a286498acc0126b331
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/196546
Auto-Submit: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Alexander Markov 2021-04-26 10:28:47 +00:00 committed by commit-bot@chromium.org
parent 417e3ef225
commit 47eff41aa0
3 changed files with 60 additions and 1 deletions

View file

@ -843,8 +843,16 @@ class ConcreteType extends Type implements Comparable<ConcreteType> {
if (rhs is FutureOrType) {
if (typeHierarchy.isSubtype(
cls.classNode, typeHierarchy.coreTypes.futureClass)) {
Type typeArg;
if (typeArgs == null) {
typeArg = const UnknownType();
} else {
final interfaceOffset = typeHierarchy.genericInterfaceOffsetFor(
cls.classNode, typeHierarchy.coreTypes.futureClass);
typeArg = typeArgs[interfaceOffset];
}
final RuntimeType lhs =
typeArgs == null ? RuntimeType(DynamicType(), null) : typeArgs[0];
typeArg is RuntimeType ? typeArg : RuntimeType(DynamicType(), null);
return lhs.isSubtypeOfRuntimeType(
typeHierarchy, runtimeType.typeArgs[0]);
} else {

View file

@ -0,0 +1,21 @@
// Copyright (c) 2021, 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.
// Regression test for https://github.com/flutter/flutter/issues/81068.
// Verifies that TFA doesn't crash when handling a class which
// implements Future and another generic class and a cast to FutureOr.
import 'dart:async';
class A<T> {}
class B<T> extends A<String> implements Future<T> {
noSuchMethod(Invocation i) => throw 'Not implemented';
}
B createB<T>() => B<T>();
void main() {
print(createB<int>() as FutureOr<double>);
}

View file

@ -0,0 +1,30 @@
library #lib;
import self as self;
import "dart:core" as core;
import "dart:async" as asy;
import "dart:async";
abstract class A<T extends core::Object* = dynamic> extends core::Object {
synthetic constructor •() → self::A<self::A::T*>*
: super core::Object::•()
;
}
class B<T extends core::Object* = dynamic> extends self::A<core::String*> implements asy::Future<self::B::T*> {
synthetic constructor •() → self::B<self::B::T*>*
: super self::A::•()
;
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasTearOffUses:false,methodOrSetterSelectorId:1,getterSelectorId:2] method noSuchMethod(core::Invocation* i) → dynamic
return throw "Not implemented";
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:3,getterSelectorId:4] no-such-method-forwarder method then<R extends core::Object* = dynamic>((self::B::T*) →* FutureOr<self::B::then::R*>* onValue, {core::Function* onError = #C1}) → asy::Future<self::B::then::R*>*
return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C2, 0, core::List::unmodifiable<core::Type*>([@vm.inferred-type.metadata=dart.core::_GrowableList<dart.core::Type*>] core::_GrowableList::_literal1<core::Type*>(self::B::then::R*)), core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(onValue)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(<core::Symbol*, dynamic>{#C3: onError}))) as{TypeError,ForDynamic} asy::Future<self::B::then::R*>*;
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:5,getterSelectorId:6] no-such-method-forwarder method catchError(core::Function* onError, {[@vm.inferred-type.metadata=dart.core::Null? (value: null)] (core::Object*) →* core::bool* test = #C1}) → asy::Future<self::B::T*>*
return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C4, 0, #C5, core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(onError)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(<core::Symbol*, dynamic>{#C6: test}))) as{TypeError,ForDynamic} asy::Future<self::B::T*>*;
[@vm.procedure-attributes.metadata=methodOrSetterCalledDynamically:false,getterCalledDynamically:false,hasThisUses:false,hasTearOffUses:false,methodOrSetterSelectorId:7,getterSelectorId:8] no-such-method-forwarder method whenComplete(() →* FutureOr<void>* action) → asy::Future<self::B::T*>*
return [@vm.direct-call.metadata=#lib::B.noSuchMethod] [@vm.inferred-type.metadata=! (skip check)] this.{self::B::noSuchMethod}(new core::_InvocationMirror::_withType(#C7, 0, #C5, core::List::unmodifiable<dynamic>([@vm.inferred-type.metadata=dart.core::_GrowableList<dynamic>] core::_GrowableList::_literal1<dynamic>(action)), [@vm.inferred-type.metadata=dart.collection::UnmodifiableMapView<dart.core::Symbol*, dynamic>] core::Map::unmodifiable<core::Symbol*, dynamic>(#C9))) as{TypeError,ForDynamic} asy::Future<self::B::T*>*;
}
static method createB<T extends core::Object* = dynamic>() → self::B<dynamic>*
return new self::B::•<self::createB::T*>();
static method main() → void {
core::print([@vm.inferred-type.metadata=#lib::B<?>] self::createB<core::int*>() as FutureOr<core::double*>*);
}