3393befc99
In this CL methods StructuralParameterType.forAlphaRenaming, StructuralParameterType.forAlphaRenamingFromTypeParameter, TypeParameterType.forAlphaRenaming, and TypeParameterType.forAlphaRenamingFromStructuralParameter are removed, and their call sites are replaced with invocations of other constructors of StructuralParameterType and TypeParameterType. The reason for this change is call sites having more information for correct computing of type nullabilities. In addition to the primary update, the following related changes are made in this CL. * Method StructuralParameterType.computeDefaultNullabilityForLibrary is renamed into StructuralParameterType.computeDefaultNullability, and TypeParameterType.computeDefaultNullabilityForLibrary is renamed into TypeParameterType.computeDefaultNullability. The parameter `library` is removed from both methods, since it's no longer needed. * The static methods named `computeNullabilityFromBound` are removed from `StructuralParameterType` and `TypeParameterType` and re-introduced as instance members in classes `StructuralParameter` and `TestParameter` respectively TEST=existing Change-Id: I26cccf17ccc9bda1e8b750196f427325b544a7ac Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402820 Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Mayank Patke <fishythefish@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
313 lines
9.5 KiB
Dart
313 lines
9.5 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 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';
|
|
}
|
|
}
|