// 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 getTypeParameterTypes(List typeParameters) { if (typeParameters.isEmpty) { return const []; } final types = List.generate(typeParameters.length, (int i) { final tp = typeParameters[i]; return TypeParameterType.withDefaultNullability(tp); }); return types; } bool _canReuseSuperclassTypeArguments( List superTypeArgs, List 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 flattenInstantiatorTypeArguments( Class instantiatedClass, List 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 flatTypeArgs = []; for (var type in superTypeArgs) { flatTypeArgs.add(substitution.substituteType(type)); } flatTypeArgs.addAll(typeArgs.getRange(overlap, typeArgs.length)); return flatTypeArgs; } List? getInstantiatorTypeArguments( Class instantiatedClass, List typeArgs, ) { final flatTypeArgs = flattenInstantiatorTypeArguments( instantiatedClass, typeArgs, ); if (isAllDynamic(flatTypeArgs)) { return null; } return flatTypeArgs; } List? 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 defaultTypes = []; for (var tp in typeParameters) { defaultTypes.add(tp.defaultType); } return defaultTypes; } bool isAllDynamic(List 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 typeArgs) { final findTypeParams = new FindFreeTypeParametersVisitor(); return typeArgs.any((t) => t.accept(findTypeParams)); } class FindFreeTypeParametersVisitor with DartTypeVisitorExperimentExclusionMixin implements DartTypeVisitor { Set? _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()); 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 (isThisExpression(receiver)) { 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'; } }