blob: 4e795d6975e579bf226dc3e306028f034b90c94e [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 file.
library vm.bytecode.generics;
import 'dart:math' show min;
import 'package:kernel/ast.dart' hide MapEntry;
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'package:kernel/type_environment.dart' show TypeEnvironment;
bool hasInstantiatorTypeArguments(Class c) {
for (; c != null; c = c.superclass) {
if (c.typeParameters.isNotEmpty) {
return true;
}
}
return false;
}
bool _canReuseSuperclassTypeArguments(List<DartType> superTypeArgs,
List<TypeParameter> typeParameters, int overlap) {
for (int i = 0; i < overlap; ++i) {
final superTypeArg = superTypeArgs[superTypeArgs.length - overlap + i];
if (!(superTypeArg is TypeParameterType &&
superTypeArg.parameter == typeParameters[i])) {
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;
}
}
final substitution = Substitution.fromPairs(typeParameters, typeArgs);
List<DartType> flatTypeArgs = <DartType>[];
flatTypeArgs.addAll(superTypeArgs.map((t) => substitution.substituteType(t)));
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) {
List<DartType> defaultTypes = function.typeParameters
.map((p) => p.defaultType ?? const DynamicType())
.toList();
if (_isAllDynamic(defaultTypes)) {
return null;
}
return defaultTypes;
}
bool _isAllDynamic(List<DartType> typeArgs) {
for (var t in typeArgs) {
if (t != const DynamicType()) {
return false;
}
}
return true;
}
bool hasFreeTypeParameters(List<DartType> typeArgs) {
final findTypeParams = new FindFreeTypeParametersVisitor();
return typeArgs.any((t) => t.accept(findTypeParams));
}
class FindFreeTypeParametersVisitor extends DartTypeVisitor<bool> {
Set<TypeParameter> _declaredTypeParameters;
bool visit(DartType type) => type.accept(this);
@override
bool defaultDartType(DartType node) =>
throw 'Unexpected type ${node.runtimeType} $node';
@override
bool visitInvalidType(InvalidType node) => false;
@override
bool visitDynamicType(DynamicType node) => false;
@override
bool visitVoidType(VoidType node) => false;
@override
bool visitBottomType(BottomType node) => false;
@override
bool visitTypeParameterType(TypeParameterType node) =>
_declaredTypeParameters == null ||
!_declaredTypeParameters.contains(node.parameter);
@override
bool visitInterfaceType(InterfaceType node) =>
node.typeArguments.any((t) => t.accept(this));
@override
bool visitTypedefType(TypedefType node) =>
node.typeArguments.any((t) => t.accept(this));
@override
bool visitFunctionType(FunctionType node) {
if (node.typeParameters.isNotEmpty) {
_declaredTypeParameters ??= new Set<TypeParameter>();
_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;
}
}
/// Returns true if the given type is recursive after its type arguments
/// are flattened.
bool isRecursiveAfterFlattening(InterfaceType type) {
final visitor = new IsRecursiveAfterFlatteningVisitor();
visitor.visit(type);
return visitor.isRecursive(type);
}
class IsRecursiveAfterFlatteningVisitor extends DartTypeVisitor<void> {
Set<DartType> _visited = new Set<DartType>();
List<DartType> _stack = <DartType>[];
Set<DartType> _recursive;
bool isRecursive(DartType type) =>
_recursive != null && _recursive.contains(type);
void visit(DartType type) {
if (!_visited.add(type)) {
_recordRecursiveType(type);
return;
}
_stack.add(type);
type.accept(this);
_stack.removeLast();
_visited.remove(type);
}
void _recordRecursiveType(DartType type) {
final int start = _stack.lastIndexOf(type);
final recursive = (_recursive ??= new Set<DartType>());
for (int i = start; i < _stack.length; ++i) {
recursive.add(_stack[i]);
}
}
@override
void defaultDartType(DartType node) =>
throw 'Unexpected type ${node.runtimeType} $node';
@override
void visitInvalidType(InvalidType node) {}
@override
void visitDynamicType(DynamicType node) {}
@override
void visitVoidType(VoidType node) {}
@override
void visitBottomType(BottomType node) {}
@override
void visitTypeParameterType(TypeParameterType node) {}
@override
void visitInterfaceType(InterfaceType node) {
for (var typeArg in node.typeArguments) {
visit(typeArg);
}
if (isRecursive(node)) {
return;
}
final flatTypeArgs =
flattenInstantiatorTypeArguments(node.classNode, node.typeArguments);
for (var typeArg in flatTypeArgs.getRange(
0, flatTypeArgs.length - node.typeArguments.length)) {
visit(typeArg);
}
}
@override
void visitTypedefType(TypedefType node) => visit(node.unalias);
@override
void visitFunctionType(FunctionType node) {
for (var p in node.positionalParameters) {
visit(p);
}
for (var p in node.namedParameters) {
visit(p.type);
}
visit(node.returnType);
}
}
/// Returns static type of [expr].
DartType getStaticType(Expression expr, TypeEnvironment typeEnvironment) {
// TODO(dartbug.com/34496): Remove this try/catch once
// getStaticType() is reliable.
try {
return expr.getStaticType(typeEnvironment);
} catch (e) {
return const DynamicType();
}
}
/// 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 ||
cls == coreTypes.nullClass;
}
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,
TypeEnvironment typeEnvironment) {
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, typeEnvironment);
if (receiverStaticType is InterfaceType) {
if (receiverStaticType.typeArguments.isEmpty) {
return true;
}
if (receiverStaticType.typeArguments
.every((t) => isSealedType(t, typeEnvironment.coreTypes))) {
return true;
}
}
return false;
}
bool _hasGenericCovariantParameters(Member target) {
if (target is Field) {
return target.isGenericCovariantImpl;
} else if (target is Procedure) {
for (var param in target.function.positionalParameters) {
if (param.isGenericCovariantImpl) {
return true;
}
}
for (var param in target.function.namedParameters) {
if (param.isGenericCovariantImpl) {
return true;
}
}
return false;
} else {
throw 'Unexpected instance call target ${target.runtimeType} $target';
}
}