8aa400bf5f
Part of https://github.com/dart-lang/sdk/issues/54902 Change-Id: Ie406b59baf3efdeaac577f5181320b28c5076068 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/361781 Reviewed-by: Paul Berry <paulberry@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Chloe Stefantsova <cstefantsova@google.com>
164 lines
6.3 KiB
Dart
164 lines
6.3 KiB
Dart
// Copyright (c) 2019, 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.
|
|
|
|
import '../ast.dart';
|
|
import '../core_types.dart';
|
|
import '../type_algebra.dart';
|
|
|
|
import 'replacement_visitor.dart';
|
|
|
|
/// Returns normalization of [type].
|
|
DartType norm(CoreTypes coreTypes, DartType type) {
|
|
return type.accept1(new _Norm(coreTypes), Variance.covariant) ?? type;
|
|
}
|
|
|
|
/// Returns normalization of [supertype].
|
|
Supertype normSupertype(CoreTypes coreTypes, Supertype supertype) {
|
|
if (supertype.typeArguments.isEmpty) return supertype;
|
|
_Norm normVisitor = new _Norm(coreTypes);
|
|
List<DartType>? typeArguments = null;
|
|
for (int i = 0; i < supertype.typeArguments.length; ++i) {
|
|
DartType? typeArgument =
|
|
supertype.typeArguments[i].accept1(normVisitor, Variance.covariant);
|
|
if (typeArgument != null) {
|
|
typeArguments ??= supertype.typeArguments.toList();
|
|
typeArguments[i] = typeArgument;
|
|
}
|
|
}
|
|
if (typeArguments == null) return supertype;
|
|
return new Supertype(supertype.classNode, typeArguments);
|
|
}
|
|
|
|
/// Visitor implementing the NORM algorithm.
|
|
///
|
|
/// Visitor's methods return null if the type is unchanged by the NORM
|
|
/// algorithm. The algorithm is specified at
|
|
/// https://github.com/dart-lang/language/blob/master/resources/type-system/normalization.md
|
|
class _Norm extends ReplacementVisitor {
|
|
final CoreTypes coreTypes;
|
|
|
|
_Norm(this.coreTypes);
|
|
|
|
@override
|
|
DartType? visitInterfaceType(InterfaceType node, Variance variance) {
|
|
return super
|
|
.visitInterfaceType(node, variance)
|
|
?.withDeclaredNullability(node.nullability);
|
|
}
|
|
|
|
@override
|
|
DartType visitFutureOrType(FutureOrType node, Variance variance) {
|
|
DartType typeArgument = node.typeArgument;
|
|
typeArgument = typeArgument.accept1(this, variance) ?? typeArgument;
|
|
if (coreTypes.isTop(typeArgument)) {
|
|
assert(typeArgument.nullability == Nullability.nullable ||
|
|
typeArgument.nullability == Nullability.legacy);
|
|
// [typeArgument] is nullable because it's a top type. No need to unite
|
|
// the nullabilities of [node] and [typeArgument].
|
|
return typeArgument;
|
|
} else if (typeArgument is InterfaceType &&
|
|
typeArgument.classNode == coreTypes.objectClass &&
|
|
typeArgument.nullability == Nullability.nonNullable) {
|
|
assert(!coreTypes.isTop(typeArgument));
|
|
// [typeArgument] is non-nullable, so the union of that and the
|
|
// nullability of [node] is the nullability of [node].
|
|
return typeArgument.withDeclaredNullability(node.nullability);
|
|
} else if (typeArgument is NeverType &&
|
|
typeArgument.nullability == Nullability.nonNullable) {
|
|
assert(!coreTypes.isTop(typeArgument));
|
|
assert(!coreTypes.isObject(typeArgument));
|
|
// [typeArgument] is non-nullable, so the union of that and the
|
|
// nullability of [node] is the nullability of [node].
|
|
return new InterfaceType(
|
|
coreTypes.futureClass, node.nullability, <DartType>[typeArgument]);
|
|
} else if (coreTypes.isNull(typeArgument)) {
|
|
assert(!coreTypes.isTop(typeArgument));
|
|
assert(!coreTypes.isObject(typeArgument));
|
|
assert(!coreTypes.isBottom(typeArgument));
|
|
return new InterfaceType(
|
|
coreTypes.futureClass,
|
|
uniteNullabilities(typeArgument.nullability, node.nullability),
|
|
<DartType>[typeArgument]);
|
|
}
|
|
assert(!coreTypes.isTop(typeArgument));
|
|
assert(!coreTypes.isObject(typeArgument));
|
|
assert(!coreTypes.isBottom(typeArgument));
|
|
assert(!coreTypes.isNull(typeArgument));
|
|
// TODO(johnniwinther): We should return `null` if [typeArgument] is
|
|
// the same as `node.typeArgument`.
|
|
return new FutureOrType(typeArgument, node.nullability);
|
|
}
|
|
|
|
@override
|
|
DartType? visitTypeParameterType(TypeParameterType node, Variance variance) {
|
|
DartType bound = node.parameter.bound;
|
|
if (normalizesToNever(bound)) {
|
|
DartType result = NeverType.fromNullability(node.nullability);
|
|
return result.accept1(this, variance) ?? result;
|
|
}
|
|
assert(!coreTypes.isBottom(bound));
|
|
// If the bound isn't Never, the type is already normalized.
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
DartType? visitStructuralParameterType(
|
|
StructuralParameterType node, Variance variance) {
|
|
DartType bound = node.parameter.bound;
|
|
if (normalizesToNever(bound)) {
|
|
DartType result = NeverType.fromNullability(node.nullability);
|
|
return result.accept1(this, variance) ?? result;
|
|
}
|
|
assert(!coreTypes.isBottom(bound));
|
|
// If the bound isn't Never, the type is already normalized.
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
DartType? visitIntersectionType(IntersectionType node, Variance variance) {
|
|
DartType right = node.right;
|
|
right = right.accept1(this, variance) ?? right;
|
|
if (right is NeverType && right.nullability == Nullability.nonNullable) {
|
|
return right;
|
|
} else if (coreTypes.isTop(right)) {
|
|
assert(!coreTypes.isBottom(right));
|
|
assert(right.nullability == Nullability.nullable);
|
|
return node.left;
|
|
} else if (right is TypeParameterType &&
|
|
right.parameter == node.left.parameter &&
|
|
right.declaredNullability == node.left.declaredNullability) {
|
|
assert(!coreTypes.isBottom(right));
|
|
assert(!coreTypes.isTop(right));
|
|
return node.left;
|
|
} else if (right == coreTypes.objectNonNullableRawType &&
|
|
norm(coreTypes, node.left.parameter.bound) ==
|
|
coreTypes.objectNonNullableRawType) {
|
|
return node.left;
|
|
} else if (identical(right, node.right)) {
|
|
// If [bound] is identical to [node.right], then the NORM
|
|
// algorithms didn't change the promoted bound, so the [node] is
|
|
// unchanged as well, and we return null to indicate that.
|
|
return null;
|
|
}
|
|
return new IntersectionType(node.left, right);
|
|
}
|
|
|
|
@override
|
|
DartType? visitNeverType(NeverType node, Variance variance) {
|
|
if (node.nullability == Nullability.nullable) return const NullType();
|
|
return null;
|
|
}
|
|
|
|
bool normalizesToNever(DartType type) {
|
|
if (type is NeverType && type.nullability == Nullability.nonNullable) {
|
|
return true;
|
|
} else if (type is TypeParameterType) {
|
|
return normalizesToNever(type.parameter.bound);
|
|
} else if (type is IntersectionType) {
|
|
return normalizesToNever(type.right);
|
|
}
|
|
return false;
|
|
}
|
|
}
|