Issue 50672. Update flatten()

Bug: https://github.com/dart-lang/sdk/issues/50672
Change-Id: I2747b9c071aa0074f2b26cea33b8d0f11ac61e33
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274734
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
This commit is contained in:
Konstantin Shcheglov 2023-02-24 22:55:17 +00:00 committed by Commit Queue
parent fd9e7cbbdb
commit c90821350e
3 changed files with 297 additions and 53 deletions

View file

@ -33,6 +33,7 @@ import 'package:analyzer/src/dart/element/type_schema.dart';
import 'package:analyzer/src/dart/element/type_schema_elimination.dart';
import 'package:analyzer/src/dart/element/well_bounded.dart';
import 'package:analyzer/src/utilities/extensions/collection.dart';
import 'package:meta/meta.dart';
/// Fresh type parameters created to unify two lists of type parameters.
class RelatedTypeParameters {
@ -237,36 +238,53 @@ class TypeSystemImpl implements TypeSystem {
}
@override
DartType flatten(DartType type) {
if (identical(type, UnknownInferredType.instance)) {
return type;
DartType flatten(DartType T) {
if (identical(T, UnknownInferredType.instance)) {
return T;
}
// if T is S? then flatten(T) = flatten(S)?
// if T is S* then flatten(T) = flatten(S)*
NullabilitySuffix nullabilitySuffix = type.nullabilitySuffix;
final nullabilitySuffix = T.nullabilitySuffix;
if (nullabilitySuffix != NullabilitySuffix.none) {
var S = (type as TypeImpl).withNullability(NullabilitySuffix.none);
final S = (T as TypeImpl).withNullability(NullabilitySuffix.none);
return (flatten(S) as TypeImpl).withNullability(nullabilitySuffix);
}
// otherwise if T is FutureOr<S> then flatten(T) = S
// otherwise if T is Future<S> then flatten(T) = S (shortcut)
if (type is InterfaceType) {
if (type.isDartAsyncFutureOr || type.isDartAsyncFuture) {
return type.typeArguments[0];
// If T is X & S for some type variable X and type S then:
if (T is TypeParameterTypeImpl) {
final S = T.promotedBound;
if (S != null) {
// * if S has future type U then flatten(T) = flatten(U)
final futureType = this.futureType(S);
if (futureType != null) {
return flatten(futureType);
}
// * otherwise, flatten(T) = flatten(X)
return flatten(
TypeParameterTypeImpl(
element: T.element,
nullabilitySuffix: nullabilitySuffix,
),
);
}
}
// otherwise if T <: Future then let S be a type such that T <: Future<S>
// and for all R, if T <: Future<R> then S <: R; then flatten(T) = S
var futureType = type.asInstanceOf(typeProvider.futureElement);
if (futureType != null) {
return futureType.typeArguments[0];
// If T has future type Future<S> or FutureOr<S> then flatten(T) = S
// If T has future type Future<S>? or FutureOr<S>? then flatten(T) = S?
final futureType = this.futureType(T);
if (futureType is InterfaceType) {
if (futureType.isDartAsyncFuture || futureType.isDartAsyncFutureOr) {
final S = futureType.typeArguments[0] as TypeImpl;
if (futureType.nullabilitySuffix == NullabilitySuffix.question) {
return S.withNullability(NullabilitySuffix.question);
}
return S;
}
}
// otherwise flatten(T) = T
return type;
return T;
}
DartType futureOrBase(DartType type) {
@ -282,6 +300,23 @@ class TypeSystemImpl implements TypeSystem {
return type;
}
/// We say that S is the future type of a type T in the following cases,
/// using the first applicable case:
@visibleForTesting
DartType? futureType(DartType T) {
// T implements S, and there is a U such that S is Future<U>
if (T.nullabilitySuffix != NullabilitySuffix.question) {
final result = T.asInstanceOf(typeProvider.futureElement);
if (result != null) {
return result;
}
}
// T is S bounded, and there is a U such that S is FutureOr<U>,
// Future<U>?, or FutureOr<U>?.
return _futureTypeOfBounded(T);
}
/// Compute "future value type" of [T].
///
/// https://github.com/dart-lang/language/
@ -1688,6 +1723,50 @@ class TypeSystemImpl implements TypeSystem {
}).toFixedList();
}
/// `S` is the future type of a type `T` in the following cases, using the
/// first applicable case:
/// * see [futureType].
/// * `T` is `S` bounded, and there is a `U` such that `S` is `FutureOr<U>`,
/// `Future<U>?`, or `FutureOr<U>?`.
///
/// 17.15.3: For a given type `T0`, we introduce the notion of a `T0` bounded
/// type: `T0` itself is `T0` bounded; if `B` is `T0` bounded and `X` is a
/// type variable with bound `B` then `X` is `T0` bounded; finally, if `B`
/// is `T0` bounded and `X` is a type variable then `X&B` is `T0` bounded.
DartType? _futureTypeOfBounded(DartType T) {
if (T is InterfaceType) {
if (T.nullabilitySuffix != NullabilitySuffix.question) {
if (T.isDartAsyncFutureOr) {
return T;
}
} else {
if (T.isDartAsyncFutureOr || T.isDartAsyncFuture) {
return T;
}
}
}
if (T is TypeParameterTypeImpl) {
final bound = T.element.bound;
if (bound != null) {
final result = _futureTypeOfBounded(bound);
if (result != null) {
return result;
}
}
final promotedBound = T.promotedBound;
if (promotedBound != null) {
final result = _futureTypeOfBounded(promotedBound);
if (result != null) {
return result;
}
}
}
return null;
}
DartType _refineBinaryExpressionTypeLegacy(DartType leftType,
TokenType operator, DartType rightType, DartType currentType) {
if (leftType is TypeParameterType && leftType.bound.isDartCoreNum) {

View file

@ -12,6 +12,7 @@ import '../../../generated/type_system_base.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(FlattenTypeTest);
defineReflectiveTests(FutureTypeTest);
});
}
@ -21,51 +22,42 @@ class FlattenTypeTest extends AbstractTypeSystemTest {
_check(dynamicNone, 'dynamic');
}
test_interfaceType_none() {
test_interfaceType() {
_check(intNone, 'int');
_check(intQuestion, 'int?');
_check(intStar, 'int*');
}
test_interfaceType_none_hasFutureType() {
_check(futureNone(intNone), 'int');
_check(futureNone(intQuestion), 'int?');
_check(futureNone(intStar), 'int*');
// otherwise if T is FutureOr<S> then flatten(T) = S
_check(futureQuestion(intNone), 'int?');
_check(futureQuestion(intQuestion), 'int?');
_check(futureOrNone(intNone), 'int');
_check(futureOrNone(intQuestion), 'int?');
_check(futureOrNone(intStar), 'int*');
var A = class_(name: 'A', interfaces: [
futureNone(intNone),
]);
_check(interfaceTypeNone(A), 'int');
_check(futureOrQuestion(intNone), 'int?');
_check(futureOrQuestion(intQuestion), 'int?');
_check(futureOrNone(futureNone(intNone)), 'Future<int>');
_check(futureOrNone(futureNone(intQuestion)), 'Future<int?>');
_check(futureOrNone(futureNone(intStar)), 'Future<int*>');
_check(futureOrQuestion(futureNone(intNone)), 'Future<int>?');
_check(futureOrQuestion(futureNone(intQuestion)), 'Future<int?>?');
}
test_interfaceType_question() {
_check(futureQuestion(intNone), 'int?');
_check(futureQuestion(intQuestion), 'int?');
_check(futureQuestion(intStar), 'int?');
_check(futureQuestion(listNone(intNone)), 'List<int>?');
_check(futureQuestion(listQuestion(intNone)), 'List<int>?');
_check(futureQuestion(listStar(intNone)), 'List<int>?');
_check(futureOrQuestion(intNone), 'int?');
_check(futureOrQuestion(intQuestion), 'int?');
_check(futureOrQuestion(intStar), 'int?');
}
test_interfaceType_star() {
_check(futureStar(intNone), 'int*');
_check(futureStar(intQuestion), 'int*');
_check(futureStar(intStar), 'int*');
_check(futureStar(listNone(intNone)), 'List<int>*');
_check(futureStar(listQuestion(intNone)), 'List<int>*');
_check(futureStar(listStar(intNone)), 'List<int>*');
_check(futureOrStar(intNone), 'int*');
_check(futureOrStar(intQuestion), 'int*');
_check(futureOrStar(intStar), 'int*');
}
test_typeParameterType_none() {
// T extends Future<int>
_check(
typeParameterTypeNone(
typeParameter('T', bound: futureNone(intNone)),
@ -73,6 +65,15 @@ class FlattenTypeTest extends AbstractTypeSystemTest {
'int',
);
// T extends FutureOr<int>
_check(
typeParameterTypeNone(
typeParameter('T', bound: futureOrNone(intNone)),
),
'int',
);
// T & Future<int>
_check(
typeParameterTypeNone(
typeParameter('T'),
@ -80,18 +81,182 @@ class FlattenTypeTest extends AbstractTypeSystemTest {
),
'int',
);
// T & FutureOr<int>
_check(
typeParameterTypeNone(
typeParameter('T'),
promotedBound: futureOrNone(intNone),
),
'int',
);
// T extends int
_check(
typeParameterTypeNone(
typeParameter('T', bound: intNone),
),
'T',
);
// T & int
_check(
typeParameterTypeNone(
typeParameter('T'),
promotedBound: intNone,
),
'T',
);
}
test_typeParameterType_question() {
// T extends Future<int>
_check(
typeParameterTypeQuestion(
typeParameter('T', bound: futureNone(intNone)),
),
'int?',
);
// T extends FutureOr<int>
_check(
typeParameterTypeQuestion(
typeParameter('T', bound: futureOrNone(intNone)),
),
'int?',
);
}
test_unknownInferredType() {
var type = UnknownInferredType.instance;
final type = UnknownInferredType.instance;
expect(typeSystem.flatten(type), same(type));
}
void _check(DartType T, String expected) {
var result = typeSystem.flatten(T);
final result = typeSystem.flatten(T);
expect(
result.getDisplayString(withNullability: true),
expected,
);
}
}
@reflectiveTest
class FutureTypeTest extends AbstractTypeSystemTest {
test_dynamic() {
_check(dynamicNone, null);
}
test_functionType() {
_check(functionTypeNone(returnType: voidNone), null);
}
test_implements_Future() {
final A = class_(name: 'A', interfaces: [
futureNone(intNone),
]);
_check(interfaceTypeNone(A), 'Future<int>');
_check(interfaceTypeQuestion(A), null);
}
test_interfaceType() {
_check(objectNone, null);
_check(objectQuestion, null);
_check(intNone, null);
_check(intQuestion, null);
_check(listNone(intNone), null);
_check(listNone(intQuestion), null);
_check(listQuestion(intNone), null);
_check(listQuestion(intQuestion), null);
_check(futureNone(intNone), 'Future<int>');
_check(futureNone(intQuestion), 'Future<int?>');
_check(futureQuestion(intNone), 'Future<int>?');
_check(futureQuestion(intQuestion), 'Future<int?>?');
_check(futureOrNone(intNone), 'FutureOr<int>');
_check(futureOrNone(intQuestion), 'FutureOr<int?>');
_check(futureOrQuestion(intNone), 'FutureOr<int>?');
_check(futureOrQuestion(intQuestion), 'FutureOr<int?>?');
_check(futureNone(futureNone(intNone)), 'Future<Future<int>>');
_check(futureNone(futureOrNone(intNone)), 'Future<FutureOr<int>>');
_check(futureOrNone(futureNone(intNone)), 'FutureOr<Future<int>>');
_check(futureOrNone(futureOrNone(intNone)), 'FutureOr<FutureOr<int>>');
}
test_typeParameterType_none() {
// T extends Future<int>
_check(
typeParameterTypeNone(
typeParameter('T', bound: futureNone(intNone)),
),
'Future<int>',
);
// T extends FutureOr<int>
_check(
typeParameterTypeNone(
typeParameter('T', bound: futureOrNone(intNone)),
),
'FutureOr<int>',
);
// T & Future<int>
_check(
typeParameterTypeNone(
typeParameter('T'),
promotedBound: futureNone(intNone),
),
'Future<int>',
);
// T & FutureOr<int>
_check(
typeParameterTypeNone(
typeParameter('T'),
promotedBound: futureOrNone(intNone),
),
'FutureOr<int>',
);
// T extends int
_check(
typeParameterTypeNone(
typeParameter('T', bound: intNone),
),
null,
);
// T & int
_check(
typeParameterTypeNone(
typeParameter('T'),
promotedBound: intNone,
),
null,
);
}
test_unknownInferredType() {
_check(UnknownInferredType.instance, null);
}
void _check(DartType T, String? expected) {
final result = typeSystem.futureType(T);
if (result == null) {
expect(expected, isNull);
} else {
expect(
result.getDisplayString(withNullability: true),
expected,
);
}
}
}

View file

@ -179,7 +179,7 @@ void main() async {
// Promoted type variable; the value of `X` does not matter.
Future<void> g6<X>(X x) async {
if (X is FutureOr<A>) {
if (x is FutureOr<A>) {
var x2 = await x; // The future is awaited.
x2.expectStaticType<Exactly<A>>();
Expect.type<C1>(x2);
@ -203,10 +203,10 @@ void main() async {
}
}
await g7<Object?, Object?>(Future<int>.value(1));
await g7<Object?, Future<int>>(Future<int>.value(1));
// S? bounded type: called with `X == Null`.
Future<void> h1<X extends Future<A>?>(X x) {
Future<void> h1<X extends Future<A>?>(X x) async {
var x2 = await x; // Remains null.
x2.expectStaticType<Exactly<A?>>();
Expect.identical(null, x2);
@ -215,7 +215,7 @@ void main() async {
await h1<Null>(null);
// S? bounded type: called with `X == Future<A>`.
Future<void> h2<X extends Future<A>?>(X x) {
Future<void> h2<X extends Future<A>?>(X x) async {
var x2 = await x; // The future is awaited.
x2.expectStaticType<Exactly<A?>>();
Expect.type<C1>(x2);
@ -226,7 +226,7 @@ void main() async {
await h2<Future<A>>(C2());
// S? bounded type: called with `X == Null`.
Future<void> h3<X extends FutureOr<A>?>(X x) {
Future<void> h3<X extends FutureOr<A>?>(X x) async {
var x2 = await x; // Remains null.
x2.expectStaticType<Exactly<A?>>();
Expect.identical(null, x2);
@ -235,7 +235,7 @@ void main() async {
await h3<Null>(null);
// S? bounded type: called with `X == FutureOr<A>`.
Future<void> h4<X extends FutureOr<A>?>(X x) {
Future<void> h4<X extends FutureOr<A>?>(X x) async {
var x2 = await x; // The future is awaited.
x2.expectStaticType<Exactly<A?>>();
Expect.type<C1>(x2);