blob: 0b2004540960debc30a9a3f87f72e121cdd837c2 [file] [log] [blame] [edit]
// 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.
import 'dart:math' show min;
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'package:kernel/type_environment.dart' show StaticTypeContext;
bool hasInstantiatorTypeArguments(Class cls) {
for (Class? c = cls; c != null; c = c.superclass) {
if (c.typeParameters.isNotEmpty) {
return true;
}
}
return false;
}
List<DartType> getTypeParameterTypes(List<TypeParameter> typeParameters) {
if (typeParameters.isEmpty) {
return const <DartType>[];
}
final types = List<DartType>.generate(typeParameters.length, (int i) {
final tp = typeParameters[i];
return TypeParameterType.withDefaultNullability(tp);
});
return types;
}
bool _canReuseSuperclassTypeArguments(List<DartType> superTypeArgs,
List<TypeParameter> typeParameters, int overlap) {
for (int i = 0; i < overlap; ++i) {
final superTypeArg = superTypeArgs[superTypeArgs.length - overlap + i];
final typeParam = typeParameters[i];
if (!(superTypeArg is TypeParameterType &&
superTypeArg.parameter == typeParameters[i] &&
superTypeArg.nullability == typeParam.computeNullabilityFromBound())) {
return false;
}
}
return true;
}
List<DartType> flattenInstantiatorTypeArguments(
Class instantiatedClass, List<DartType> typeArgs) {
final typeParameters = instantiatedClass.typeParameters;
assert(typeArgs.length == typeParameters.length);
final supertype = instantiatedClass.supertype;
if (supertype == null) {
return typeArgs;
}
final superTypeArgs = flattenInstantiatorTypeArguments(
supertype.classNode, supertype.typeArguments);
// Shrink type arguments by reusing portion of superclass type arguments
// if there is an overlapping. This optimization should be consistent with
// VM in order to correctly reuse instantiator type arguments.
int overlap = min(superTypeArgs.length, typeArgs.length);
for (; overlap > 0; --overlap) {
if (_canReuseSuperclassTypeArguments(
superTypeArgs, typeParameters, overlap)) {
break;
}
}
assert(typeParameters.length == typeArgs.length);
final substitution = Substitution.fromPairs(typeParameters, typeArgs);
List<DartType> flatTypeArgs = <DartType>[];
for (var type in superTypeArgs) {
flatTypeArgs.add(substitution.substituteType(type));
}
flatTypeArgs.addAll(typeArgs.getRange(overlap, typeArgs.length));
return flatTypeArgs;
}
List<DartType>? getInstantiatorTypeArguments(
Class instantiatedClass, List<DartType> typeArgs) {
final flatTypeArgs =
flattenInstantiatorTypeArguments(instantiatedClass, typeArgs);
if (isAllDynamic(flatTypeArgs)) {
return null;
}
return flatTypeArgs;
}
List<DartType>? getDefaultFunctionTypeArguments(FunctionNode function) {
final typeParameters = function.typeParameters;
if (typeParameters.isEmpty) {
return null;
}
bool dynamicOnly = true;
for (var tp in typeParameters) {
if (tp.defaultType != const DynamicType()) {
dynamicOnly = false;
break;
}
}
if (dynamicOnly) {
return null;
}
List<DartType> defaultTypes = <DartType>[];
for (var tp in typeParameters) {
defaultTypes.add(tp.defaultType);
}
return defaultTypes;
}
bool isAllDynamic(List<DartType> typeArgs) {
for (var t in typeArgs) {
if (t != const DynamicType()) {
return false;
}
}
return true;
}
bool isInstantiatedGenericType(DartType type) =>
(type is InterfaceType) &&
type.typeArguments.isNotEmpty &&
!hasFreeTypeParameters(type.typeArguments);
bool hasFreeTypeParameters(List<DartType> typeArgs) {
final findTypeParams = new FindFreeTypeParametersVisitor();
return typeArgs.any((t) => t.accept(findTypeParams));
}
class FindFreeTypeParametersVisitor implements DartTypeVisitor<bool> {
Set<StructuralParameter>? _declaredTypeParameters;
bool visit(DartType type) => type.accept(this);
@override
bool visitDynamicType(DynamicType node) => false;
@override
bool visitVoidType(VoidType node) => false;
@override
bool visitNeverType(NeverType node) => false;
@override
bool visitNullType(NullType node) => false;
@override
bool visitTypeParameterType(TypeParameterType node) => true;
@override
bool visitStructuralParameterType(StructuralParameterType node) {
final declaredTypeParameters = _declaredTypeParameters;
return declaredTypeParameters == null ||
!declaredTypeParameters.contains(node.parameter);
}
@override
bool visitInterfaceType(InterfaceType node) =>
node.typeArguments.any((t) => t.accept(this));
@override
bool visitFutureOrType(FutureOrType node) => node.typeArgument.accept(this);
@override
bool visitTypedefType(TypedefType node) =>
node.typeArguments.any((t) => t.accept(this));
@override
bool visitExtensionType(ExtensionType node) =>
node.extensionTypeErasure.accept(this);
@override
bool visitFunctionType(FunctionType node) {
if (node.typeParameters.isNotEmpty) {
final declaredTypeParameters =
(_declaredTypeParameters ??= Set<StructuralParameter>());
declaredTypeParameters.addAll(node.typeParameters);
}
final bool result = node.positionalParameters.any((t) => t.accept(this)) ||
node.namedParameters.any((p) => p.type.accept(this)) ||
node.returnType.accept(this);
if (node.typeParameters.isNotEmpty) {
_declaredTypeParameters!.removeAll(node.typeParameters);
}
return result;
}
@override
bool visitRecordType(RecordType node) =>
node.positional.any((t) => t.accept(this)) ||
node.named.any((nt) => nt.type.accept(this));
bool unexpectedDartType(DartType node) =>
throw 'Unexpected type ${node.runtimeType} $node';
bool visitAuxiliaryType(AuxiliaryType node) => unexpectedDartType(node);
bool visitInvalidType(InvalidType node) => unexpectedDartType(node);
bool visitIntersectionType(IntersectionType node) => unexpectedDartType(node);
}
/// Returns static type of [expr].
DartType getStaticType(Expression expr, StaticTypeContext staticTypeContext) =>
expr.getStaticType(staticTypeContext);
/// Returns `true` if [type] cannot be extended in user code.
bool isSealedType(DartType type, CoreTypes coreTypes) {
if (type is InterfaceType) {
final cls = type.classNode;
return cls == coreTypes.intClass ||
cls == coreTypes.doubleClass ||
cls == coreTypes.boolClass ||
cls == coreTypes.stringClass;
} else if (type is NullType) {
return true;
}
return false;
}
/// Returns true if an instance call to [interfaceTarget] with given
/// [receiver] can omit argument type checks needed due to generic-covariant
/// parameters.
bool isUncheckedCall(Member? interfaceTarget, Expression receiver,
StaticTypeContext staticTypeContext) {
if (interfaceTarget == null) {
// Dynamic call cannot be unchecked.
return false;
}
if (!_hasGenericCovariantParameters(interfaceTarget)) {
// Unchecked call makes sense only if there are generic-covariant parameters.
return false;
}
// Calls via [this] do not require checks.
if (receiver is ThisExpression) {
return true;
}
DartType receiverStaticType = getStaticType(receiver, staticTypeContext);
if (receiverStaticType is InterfaceType) {
final typeArguments = receiverStaticType.typeArguments;
if (typeArguments.isEmpty) {
return true;
}
final typeParameters = receiverStaticType.classNode.typeParameters;
assert(typeArguments.length == typeParameters.length);
for (int i = 0; i < typeArguments.length; ++i) {
switch (typeParameters[i].variance) {
case Variance.covariant:
if (!isSealedType(
typeArguments[i], staticTypeContext.typeEnvironment.coreTypes)) {
return false;
}
break;
case Variance.invariant:
break;
case Variance.contravariant:
return false;
default:
throw 'Unexpected variance ${typeParameters[i].variance} of '
'${typeParameters[i]} in ${receiverStaticType.classNode}';
}
}
return true;
}
return false;
}
/// If receiver type at run time matches static type we can omit argument type
/// checks. This condition can be efficiently tested if static receiver type is
/// fully instantiated (e.g. doesn't have type parameters).
/// [isInstantiatedInterfaceCall] tests if an instance call to
/// [interfaceTarget] with given [staticReceiverType] may benefit from
/// this optimization.
bool isInstantiatedInterfaceCall(
Member? interfaceTarget, DartType staticReceiverType) {
// Providing instantiated receiver type wouldn't help in case of a
// dynamic call or call without any parameter type checks.
if (interfaceTarget == null ||
!_hasGenericCovariantParameters(interfaceTarget)) {
return false;
}
return isInstantiatedGenericType(staticReceiverType);
}
bool _hasGenericCovariantParameters(Member target) {
if (target is Field) {
return target.isCovariantByClass;
} else if (target is Procedure) {
for (var param in target.function.positionalParameters) {
if (param.isCovariantByClass) {
return true;
}
}
for (var param in target.function.namedParameters) {
if (param.isCovariantByClass) {
return true;
}
}
return false;
} else {
throw 'Unexpected instance call target ${target.runtimeType} $target';
}
}