blob: 0cc5d548b3a53338417a7b1e52164683c66a65ef [file] [log] [blame]
// 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.md 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, int variance) {
return super
.visitInterfaceType(node, variance)
?.withDeclaredNullability(node.nullability);
}
@override
DartType visitFutureOrType(FutureOrType node, int 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, int variance) {
if (node.promotedBound == null) {
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;
} else {
DartType bound = node.promotedBound!;
bound = bound.accept1(this, variance) ?? bound;
if (bound is NeverType && bound.nullability == Nullability.nonNullable) {
return bound;
} else if (coreTypes.isTop(bound)) {
assert(!coreTypes.isBottom(bound));
assert(bound.nullability == Nullability.nullable);
return new TypeParameterType(node.parameter, node.declaredNullability);
} else if (bound is TypeParameterType &&
bound.parameter == node.parameter &&
bound.declaredNullability == node.declaredNullability &&
bound.promotedBound == null) {
assert(!coreTypes.isBottom(bound));
assert(!coreTypes.isTop(bound));
return new TypeParameterType(node.parameter, node.declaredNullability);
} else if (bound == coreTypes.objectNonNullableRawType &&
norm(coreTypes, node.parameter.bound) ==
coreTypes.objectNonNullableRawType) {
return new TypeParameterType(node.parameter, node.declaredNullability);
} else if (identical(bound, node.promotedBound)) {
// If [bound] is identical to [node.promotedBound], 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 TypeParameterType(
node.parameter, node.declaredNullability, bound);
}
}
@override
DartType? visitNeverType(NeverType node, int 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) {
if (type.promotedBound == null) {
return normalizesToNever(type.parameter.bound);
} else {
return normalizesToNever(type.promotedBound!);
}
}
return false;
}
}