e07e723707
In the new model `ThisExpression` is replaced with a `VariableGet` referring to `ThisVariable`. This CL introduces the model-agnostic predicate `isThisExpression`, which is applied to replace is-checks on `ThisExpression`. The changes in this CL allow to unskip the test `type_ops.dart` in `bytecode_generator_test.dart`. This CL is also a follow-up to https://dart-review.googlesource.com/c/sdk/+/488780/1/pkg/front_end/lib/src/type_inference/inference_visitor_base.dart#3481 Part of https://github.com/dart-lang/sdk/issues/61572 Change-Id: I9d56ea6a648dc8d299d9a9692b4ffa4459221147 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/489100 Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
337 lines
9.7 KiB
Dart
337 lines
9.7 KiB
Dart
// 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
|
|
with DartTypeVisitorExperimentExclusionMixin<bool>
|
|
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 (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';
|
|
}
|
|
}
|