Files
sdk/pkg/dart2bytecode/lib/generics.dart
T
Chloe Stefantsova 3393befc99 [cfe] Remove constructors for alpha renaming
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>
2025-01-07 01:16:48 -08:00

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';
}
}