blob: 44daee549b051b5075008192221d21914e52d4f1 [file] [log] [blame]
// Copyright (c) 2022, 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 max;
import 'dart:typed_data' show Uint8List;
import 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/code_generator.dart';
import 'package:dart2wasm/translator.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
/// Values for the type category table. Entries for masqueraded classes contain
/// the class ID of the masquerade.
class TypeCategory {
static const abstractClass = 0;
static const function = 1;
static const record = 2;
static const notMasqueraded = 3;
static const minMasqueradeClassId = 4;
static const maxMasqueradeClassId = 63; // Leaves 2 unused bits for future use
}
class InterfaceTypeEnvironment {
final Map<TypeParameter, int> _typeOffsets = {};
void _add(InterfaceType type) {
Class cls = type.classNode;
if (_typeOffsets.containsKey(cls)) {
return;
}
int i = 0;
for (TypeParameter typeParameter in cls.typeParameters) {
_typeOffsets[typeParameter] = i++;
}
}
int lookup(TypeParameter typeParameter) => _typeOffsets[typeParameter]!;
}
/// Helper class for building runtime types.
class Types {
final Translator translator;
/// Class info for `_Type`
late final ClassInfo typeClassInfo =
translator.classInfo[translator.typeClass]!;
/// Wasm value type of `List<_Type>`
late final w.ValueType typeListExpectedType = classAndFieldToType(
translator.interfaceTypeClass, FieldIndex.interfaceTypeTypeArguments);
/// Wasm value type of `List<_NamedParameter>`
late final w.ValueType namedParametersExpectedType = classAndFieldToType(
translator.functionTypeClass, FieldIndex.functionTypeNamedParameters);
/// Wasm value type of `_RecordType.names` field.
late final w.ValueType recordTypeNamesFieldExpectedType = classAndFieldToType(
translator.recordTypeClass, FieldIndex.recordTypeNames);
/// A mapping from concrete subclass `classID` to [Map]s of superclass
/// `classID` and the necessary substitutions which must be performed to test
/// for a valid subtyping relationship.
late final Map<int, Map<int, List<DartType>>> typeRules = _buildTypeRules();
/// We will build the [interfaceTypeEnvironment] when building the
/// [typeRules].
final InterfaceTypeEnvironment interfaceTypeEnvironment =
InterfaceTypeEnvironment();
/// Because we can't currently support [Map]s in our `TypeUniverse`, we have
/// to decompose [typeRules] into two [Map]s based on [List]s.
///
/// [typeRulesSupers] is a [List] where the index in the list is a subclasses'
/// `classID` and the value at that index is a [List] of superclass
/// `classID`s.
late final List<List<int>> typeRulesSupers = _buildTypeRulesSupers();
/// [typeRulesSubstitutions] is a [List] where the index in the list is a
/// subclasses' `classID` and the value at that index is a [List] indexed by
/// the index of the superclasses' `classID` in [typeRulesSuper] and the value
/// at that index is a [List] of [DartType]s which must be substituted for the
/// subtyping relationship to be valid.
late final List<List<List<DartType>>> typeRulesSubstitutions =
_buildTypeRulesSubstitutions();
/// A list which maps class ID to the classes [String] name.
late final List<String> typeNames = _buildTypeNames();
/// Type parameter offset for function types, specifying the lower end of
/// their index range for type parameter types.
Map<FunctionType, int> functionTypeParameterOffset = Map.identity();
/// Index value for function type parameter types, indexing into the type
/// parameter index range of their corresponding function type.
Map<TypeParameter, int> functionTypeParameterIndex = Map.identity();
/// An `i8` array of type category values, indexed by class ID.
late final w.Global typeCategoryTable = _buildTypeCategoryTable();
Types(this.translator);
w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
translator.classInfo[cls]!.struct.fields[fieldIndex].type.unpacked;
Iterable<Class> _getConcreteSubtypes(Class cls) =>
translator.subtypes.getSubtypesOf(cls).where((c) => !c.isAbstract);
/// Wasm value type for non-nullable `_Type` values
w.ValueType get nonNullableTypeType => typeClassInfo.nonNullableType;
InterfaceType get namedParameterType =>
InterfaceType(translator.namedParameterClass, Nullability.nonNullable);
InterfaceType get typeType =>
InterfaceType(translator.typeClass, Nullability.nonNullable);
CoreTypes get coreTypes => translator.coreTypes;
/// Builds a [Map<int, Map<int, List<DartType>>>] to store subtype
/// information. The first key is the class id of a subtype. This returns a
/// map where each key is the class id of a transitively implemented super
/// type and each value is a list of the necessary type substitutions required
/// for the subtyping relationship to be valid.
Map<int, Map<int, List<DartType>>> _buildTypeRules() {
List<ClassInfo> classes = translator.classes;
Map<int, Map<int, List<DartType>>> subtypeMap = {};
for (ClassInfo classInfo in classes) {
ClassInfo superclassInfo = classInfo;
// We don't need type rules for any class without a superclass, or for
// classes whose supertype is [Object]. The latter case will be handled
// directly in the subtype checking algorithm.
if (superclassInfo.cls == null ||
superclassInfo.cls == coreTypes.objectClass) continue;
Class superclass = superclassInfo.cls!;
// TODO(joshualitt): This includes abstract types that can't be
// instantiated, but might be needed for subtype checks. The majority of
// abstract classes are probably unnecessary though. We should filter
// these cases to reduce the size of the type rules.
Iterable<Class> subclasses = translator.subtypes
.getSubtypesOf(superclass)
.where((cls) => cls != superclass);
Iterable<InterfaceType> subtypes = subclasses.map(
(Class cls) => cls.getThisType(coreTypes, Nullability.nonNullable));
for (InterfaceType subtype in subtypes) {
interfaceTypeEnvironment._add(subtype);
List<DartType>? typeArguments = translator.hierarchy
.getTypeArgumentsAsInstanceOf(subtype, superclass)
?.map(normalize)
.toList();
ClassInfo subclassInfo = translator.classInfo[subtype.classNode]!;
Map<int, List<DartType>> substitutionMap =
subtypeMap[subclassInfo.classId] ??= {};
substitutionMap[superclassInfo.classId] = typeArguments ?? const [];
}
}
return subtypeMap;
}
List<List<int>> _buildTypeRulesSupers() {
List<List<int>> typeRulesSupers = [];
for (int classId = 0; classId < translator.classes.length; classId++) {
List<int>? superclassIds = typeRules[classId]?.keys.toList();
if (superclassIds == null) {
typeRulesSupers.add(const []);
} else {
superclassIds.sort();
typeRulesSupers.add(superclassIds);
}
}
return typeRulesSupers;
}
List<List<List<DartType>>> _buildTypeRulesSubstitutions() {
List<List<List<DartType>>> typeRulesSubstitutions = [];
for (int classId = 0; classId < translator.classes.length; classId++) {
List<int> supers = typeRulesSupers[classId];
typeRulesSubstitutions.add(supers.isEmpty ? const [] : []);
for (int j = 0; j < supers.length; j++) {
int superId = supers[j];
typeRulesSubstitutions.last.add(typeRules[classId]![superId]!);
}
}
return typeRulesSubstitutions;
}
List<String> _buildTypeNames() {
// This logic assumes `translator.classes` returns the classes indexed by
// class ID. If we ever change that logic, we will need to change this code.
List<String> typeNames = [];
for (ClassInfo classInfo in translator.classes) {
String className = classInfo.cls?.name ?? '';
typeNames.add(className);
}
return typeNames;
}
/// Builds a map of subclasses to the transitive set of superclasses they
/// implement.
/// TODO(joshualitt): This implementation is just temporary. Eventually we
/// should move to a data structure more closely resembling [typeRules].
w.ValueType makeTypeRulesSupers(w.Instructions b) {
w.ValueType expectedType =
translator.classInfo[translator.immutableListClass]!.nonNullableType;
DartType listIntType = InterfaceType(translator.immutableListClass,
Nullability.nonNullable, [translator.coreTypes.intNonNullableRawType]);
List<ListConstant> listIntConstant = [];
for (List<int> supers in typeRulesSupers) {
listIntConstant.add(ListConstant(
listIntType, supers.map((i) => IntConstant(i)).toList()));
}
DartType listListIntType = InterfaceType(
translator.immutableListClass, Nullability.nonNullable, [listIntType]);
translator.constants.instantiateConstant(
null, b, ListConstant(listListIntType, listIntConstant), expectedType);
return expectedType;
}
/// Similar to the above, but provides the substitutions required for each
/// supertype.
/// TODO(joshualitt): Like [makeTypeRulesSupers], this is just temporary.
w.ValueType makeTypeRulesSubstitutions(w.Instructions b) {
w.ValueType expectedType =
translator.classInfo[translator.immutableListClass]!.nonNullableType;
DartType listTypeType = InterfaceType(
translator.immutableListClass,
Nullability.nonNullable,
[translator.typeClass.getThisType(coreTypes, Nullability.nonNullable)]);
DartType listListTypeType = InterfaceType(
translator.immutableListClass, Nullability.nonNullable, [listTypeType]);
DartType listListListTypeType = InterfaceType(translator.immutableListClass,
Nullability.nonNullable, [listListTypeType]);
List<ListConstant> substitutionsConstantL0 = [];
for (List<List<DartType>> substitutionsL1 in typeRulesSubstitutions) {
List<ListConstant> substitutionsConstantL1 = [];
for (List<DartType> substitutionsL2 in substitutionsL1) {
substitutionsConstantL1.add(ListConstant(listTypeType,
substitutionsL2.map((t) => TypeLiteralConstant(t)).toList()));
}
substitutionsConstantL0
.add(ListConstant(listListTypeType, substitutionsConstantL1));
}
translator.constants.instantiateConstant(
null,
b,
ListConstant(listListListTypeType, substitutionsConstantL0),
expectedType);
return expectedType;
}
/// Returns a list of string type names for pretty printing types.
w.ValueType makeTypeNames(w.Instructions b) {
w.ValueType expectedType =
translator.classInfo[translator.immutableListClass]!.nonNullableType;
List<StringConstant> listStringConstant = [];
for (String name in typeNames) {
listStringConstant.add(StringConstant(name));
}
DartType listStringType = InterfaceType(
translator.immutableListClass,
Nullability.nonNullable,
[translator.coreTypes.stringNonNullableRawType]);
translator.constants.instantiateConstant(null, b,
ListConstant(listStringType, listStringConstant), expectedType);
return expectedType;
}
/// Build a global array of byte values used to categorize runtime types.
w.Global _buildTypeCategoryTable() {
Set<Class> recordClasses = Set.from(translator.recordClasses.values);
Uint8List table = Uint8List(translator.classes.length);
for (int i = 0; i < translator.classes.length; i++) {
ClassInfo info = translator.classes[i];
ClassInfo? masquerade = info.masquerade;
Class? cls = info.cls;
int category;
if (cls == null || cls.isAbstract) {
category = TypeCategory.abstractClass;
} else if (cls == translator.closureClass) {
category = TypeCategory.function;
} else if (recordClasses.contains(cls)) {
category = TypeCategory.record;
} else if (masquerade == null || masquerade.classId == i) {
category = TypeCategory.notMasqueraded;
} else {
// Masqueraded class
assert(cls.enclosingLibrary.importUri.scheme == "dart");
assert(cls.name.startsWith('_'));
assert(masquerade.classId >= TypeCategory.minMasqueradeClassId);
assert(masquerade.classId <= TypeCategory.maxMasqueradeClassId);
category = masquerade.classId;
}
table[i] = category;
}
w.DataSegment segment = translator.m.addDataSegment(table);
w.ArrayType arrayType =
translator.wasmArrayType(w.PackedType.i8, "const i8", mutable: false);
w.DefinedGlobal global = translator.m
.addGlobal(w.GlobalType(w.RefType.def(arrayType, nullable: false)));
// Initialize the global to a dummy array, since `array.new_data` is not
// a constant instruction and thus can't be used in the initializer.
global.initializer.array_new_fixed(arrayType, 0);
global.initializer.end();
// Create the actual table in the init function.
w.Instructions b = translator.initFunction.body;
b.i32_const(0);
b.i32_const(table.length);
b.array_new_data(arrayType, segment);
b.global_set(global);
return global;
}
bool isFunctionTypeParameter(TypeParameterType type) =>
type.parameter.parent == null;
bool _isTypeConstant(DartType type) {
return type is DynamicType ||
type is VoidType ||
type is NeverType ||
type is NullType ||
type is FutureOrType && _isTypeConstant(type.typeArgument) ||
(type is FunctionType &&
type.typeParameters.every((p) => _isTypeConstant(p.bound)) &&
_isTypeConstant(type.returnType) &&
type.positionalParameters.every(_isTypeConstant) &&
type.namedParameters.every((n) => _isTypeConstant(n.type))) ||
type is InterfaceType && type.typeArguments.every(_isTypeConstant) ||
(type is RecordType &&
type.positional.every(_isTypeConstant) &&
type.named.every((n) => _isTypeConstant(n.type))) ||
type is TypeParameterType && isFunctionTypeParameter(type) ||
type is InlineType &&
_isTypeConstant(type.instantiatedRepresentationType);
}
Class classForType(DartType type) {
if (type is DynamicType) {
return translator.dynamicTypeClass;
} else if (type is VoidType) {
return translator.voidTypeClass;
} else if (type is NeverType) {
// For runtime types with sound null safety, `Never?` is the same as
// `Null`.
if (type.nullability == Nullability.nullable) {
return translator.nullTypeClass;
} else {
return translator.neverTypeClass;
}
} else if (type is NullType) {
return translator.nullTypeClass;
} else if (type is FutureOrType) {
return translator.futureOrTypeClass;
} else if (type is InterfaceType) {
return translator.interfaceTypeClass;
} else if (type is FunctionType) {
return translator.functionTypeClass;
} else if (type is TypeParameterType) {
if (isFunctionTypeParameter(type)) {
return translator.functionTypeParameterTypeClass;
} else {
return translator.interfaceTypeParameterTypeClass;
}
} else if (type is InlineType) {
return classForType(type.instantiatedRepresentationType);
} else if (type is RecordType) {
return translator.recordTypeClass;
}
throw "Unexpected DartType: $type";
}
/// Allocates a `List<_Type>` from [types] and pushes it to the stack.
void _makeTypeList(CodeGenerator codeGen, Iterable<DartType> types) {
if (types.every(_isTypeConstant)) {
translator.constants.instantiateConstant(codeGen.function, codeGen.b,
translator.constants.makeTypeList(types), typeListExpectedType);
} else {
w.ValueType listType = codeGen.makeListFromExpressions(
types.map((t) => TypeLiteral(t)).toList(), typeType);
translator.convertType(codeGen.function, listType, typeListExpectedType);
}
}
void _makeInterfaceType(CodeGenerator codeGen, InterfaceType type) {
w.Instructions b = codeGen.b;
ClassInfo typeInfo = translator.classInfo[type.classNode]!;
b.i32_const(encodedNullability(type));
b.i64_const(typeInfo.classId);
_makeTypeList(codeGen, type.typeArguments);
}
void _makeRecordType(CodeGenerator codeGen, RecordType type) {
codeGen.b.i32_const(encodedNullability(type));
translator.constants.instantiateConstant(
codeGen.function,
codeGen.b,
ListConstant(
InterfaceType(
translator.coreTypes.stringClass, Nullability.nonNullable),
type.named.map((t) => StringConstant(t.name)).toList(),
),
recordTypeNamesFieldExpectedType);
_makeTypeList(
codeGen, type.positional.followedBy(type.named.map((t) => t.type)));
}
/// Normalizes a Dart type. Many rules are already applied for us, but we
/// still have to manually normalize [FutureOr].
DartType normalize(DartType type) {
if (type is! FutureOrType) return type;
final s = normalize(type.typeArgument);
// `coreTypes.isTope` and `coreTypes.isObject` take into account the
// normalization rules of `futureOr`.
if (coreTypes.isTop(type) || coreTypes.isObject(type)) {
return s;
} else if (s is NeverType) {
return InterfaceType(coreTypes.futureClass, Nullability.nonNullable,
const [const NeverType.nonNullable()]);
} else if (s is NullType) {
return InterfaceType(coreTypes.futureClass, Nullability.nullable,
const [const NullType()]);
}
// The type is normalized, and remains a `FutureOr` so now we normalize its
// nullability.
final declaredNullability = s.nullability == Nullability.nullable
? Nullability.nonNullable
: type.declaredNullability;
return FutureOrType(s, declaredNullability);
}
void _makeFutureOrType(CodeGenerator codeGen, FutureOrType type) {
w.Instructions b = codeGen.b;
b.i32_const(encodedNullability(type));
makeType(codeGen, type.typeArgument);
codeGen.call(translator.createNormalizedFutureOrType.reference);
}
void _makeFunctionType(CodeGenerator codeGen, FunctionType type) {
int typeParameterOffset = computeFunctionTypeParameterOffset(type);
w.Instructions b = codeGen.b;
b.i32_const(encodedNullability(type));
b.i64_const(typeParameterOffset);
// List<_Type> typeParameterBounds
_makeTypeList(codeGen, type.typeParameters.map((p) => p.bound));
// List<_Type> typeParameterDefaults
_makeTypeList(codeGen, type.typeParameters.map((p) => p.defaultType));
// _Type returnType
makeType(codeGen, type.returnType);
// List<_Type> positionalParameters
_makeTypeList(codeGen, type.positionalParameters);
// int requiredParameterCount
b.i64_const(type.requiredParameterCount);
// List<_NamedParameter> namedParameters
if (type.namedParameters.every((n) => _isTypeConstant(n.type))) {
translator.constants.instantiateConstant(
codeGen.function,
b,
translator.constants.makeNamedParametersList(type),
namedParametersExpectedType);
} else {
Class namedParameterClass = translator.namedParameterClass;
Constructor namedParameterConstructor =
namedParameterClass.constructors.single;
List<Expression> expressions = [];
for (NamedType n in type.namedParameters) {
expressions.add(_isTypeConstant(n.type)
? ConstantExpression(
translator.constants.makeNamedParameterConstant(n),
namedParameterType)
: ConstructorInvocation(
namedParameterConstructor,
Arguments([
StringLiteral(n.name),
TypeLiteral(n.type),
BoolLiteral(n.isRequired)
])));
}
w.ValueType namedParametersListType =
codeGen.makeListFromExpressions(expressions, namedParameterType);
translator.convertType(codeGen.function, namedParametersListType,
namedParametersExpectedType);
}
}
/// Makes a `_Type` object on the stack.
/// TODO(joshualitt): Refactor this logic to remove the dependency on
/// CodeGenerator.
w.ValueType makeType(CodeGenerator codeGen, DartType type) {
// Always ensure type is normalized before making a type.
type = normalize(type);
w.Instructions b = codeGen.b;
if (_isTypeConstant(type)) {
translator.constants.instantiateConstant(
codeGen.function, b, TypeLiteralConstant(type), nonNullableTypeType);
return nonNullableTypeType;
}
// All of the singleton types represented by canonical objects should be
// created const.
assert(type is TypeParameterType ||
type is InlineType ||
type is InterfaceType ||
type is FutureOrType ||
type is FunctionType ||
type is RecordType);
if (type is TypeParameterType) {
assert(!isFunctionTypeParameter(type));
codeGen.instantiateTypeParameter(type.parameter);
if (type.declaredNullability == Nullability.nullable) {
codeGen.call(translator.typeAsNullable.reference);
}
return nonNullableTypeType;
}
if (type is InlineType) {
return makeType(codeGen, type.instantiatedRepresentationType);
}
ClassInfo info = translator.classInfo[classForType(type)]!;
if (type is FutureOrType) {
_makeFutureOrType(codeGen, type);
return info.nonNullableType;
}
translator.functions.allocateClass(info.classId);
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
if (type is InterfaceType) {
_makeInterfaceType(codeGen, type);
} else if (type is FunctionType) {
_makeFunctionType(codeGen, type);
} else if (type is RecordType) {
_makeRecordType(codeGen, type);
} else {
throw '`$type` should have already been handled.';
}
b.struct_new(info.struct);
return info.nonNullableType;
}
/// Compute the lower end of the type parameter index range for this function
/// type. This is computed such that it avoids overlap between the index range
/// of this function type and the index ranges of all generic function types
/// nested within it that contain references to the type parameters of this
/// function type.
///
/// This will also compute the index values for all of the function's type
/// parameters, which can subsequently be queried using
/// [getFunctionTypeParameterIndex].
int computeFunctionTypeParameterOffset(FunctionType type) {
if (type.typeParameters.isEmpty) return 0;
int? offset = functionTypeParameterOffset[type];
if (offset != null) return offset;
_FunctionTypeParameterOffsetCollector(this).visitFunctionType(type);
return functionTypeParameterOffset[type]!;
}
/// Get the index value for a function type parameter, indexing into the
/// type parameter index range of its corresponding function type.
int getFunctionTypeParameterIndex(TypeParameter type) {
assert(functionTypeParameterIndex.containsKey(type),
"Type parameter offset has not been computed for function type");
return functionTypeParameterIndex[type]!;
}
/// Test value against a Dart type. Expects the value on the stack as a
/// (ref null #Top) and leaves the result on the stack as an i32.
/// TODO(joshualitt): Remove dependency on [CodeGenerator]
void emitTypeTest(
CodeGenerator codeGen, DartType type, DartType operandType) {
w.Instructions b = codeGen.b;
if (type is! InterfaceType) {
makeType(codeGen, type);
codeGen.call(translator.isSubtype.reference);
return;
}
bool isPotentiallyNullable = operandType.isPotentiallyNullable;
w.Label? resultLabel;
if (isPotentiallyNullable) {
// Store operand in a temporary variable, since Binaryen does not support
// block inputs.
w.Local operand = codeGen.addLocal(translator.topInfo.nullableType);
b.local_set(operand);
resultLabel = b.block(const [], const [w.NumType.i32]);
w.Label nullLabel = b.block(const [], const []);
b.local_get(operand);
b.br_on_null(nullLabel);
}
void _endPotentiallyNullableBlock() {
if (isPotentiallyNullable) {
b.br(resultLabel!);
b.end(); // nullLabel
b.i32_const(encodedNullability(type));
b.end(); // resultLabel
}
}
if (type.typeArguments.any((t) => t is! DynamicType)) {
// Type has at least one type argument that is not `dynamic`.
//
// In cases like `x is List<T>` where `x : Iterable<T>` (tested-against
// type is a subtype of the operand's static type and the types have same
// number of type arguments), it is not necessary to test the type
// arguments.
Class cls = translator.classForType(operandType);
InterfaceType? base = translator.hierarchy
.getTypeAsInstanceOf(type, cls,
isNonNullableByDefault:
codeGen.member.enclosingLibrary.isNonNullableByDefault)
?.withDeclaredNullability(operandType.declaredNullability);
final sameNumTypeParams = operandType is InterfaceType &&
operandType.typeArguments.length == type.typeArguments.length;
if (!(sameNumTypeParams && base == operandType)) {
makeType(codeGen, type);
codeGen.call(translator.isSubtype.reference);
_endPotentiallyNullableBlock();
return;
}
}
List<Class> concrete = _getConcreteSubtypes(type.classNode).toList();
if (type.classNode == coreTypes.objectClass) {
b.drop();
b.i32_const(1);
} else if (type.classNode == coreTypes.functionClass) {
b.ref_test(translator.closureInfo.nonNullableType);
} else if (concrete.isEmpty) {
b.drop();
b.i32_const(0);
} else if (concrete.length == 1) {
ClassInfo info = translator.classInfo[concrete.single]!;
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.i32_const(info.classId);
b.i32_eq();
} else {
w.Local idLocal = codeGen.addLocal(w.NumType.i32);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.local_set(idLocal);
w.Label done = b.block(const [], const [w.NumType.i32]);
b.i32_const(1);
for (Class cls in concrete) {
ClassInfo info = translator.classInfo[cls]!;
b.i32_const(info.classId);
b.local_get(idLocal);
b.i32_eq();
b.br_if(done);
}
b.drop();
b.i32_const(0);
b.end(); // done
}
_endPotentiallyNullableBlock();
}
int encodedNullability(DartType type) =>
type.declaredNullability == Nullability.nullable ? 1 : 0;
}
/// For a function type F = `... Function<X0, ..., Xn-1>(...)` compute offset(F)
/// such that for any function type G = `... Function<Y0, ..., Ym-1>(...)`
/// nested inside F, if G contains a reference to any type parameters of F, then
/// offset(F) >= offset(G) + m.
///
/// Conceptually, the type parameters of F are indexed from offset(F) inclusive
/// to offset(F) + n exclusive.
///
/// Also assign to each type parameter Xi the index offset(F) + i such that it
/// indexes the correct type parameter in the conceptual type parameter index
/// range of F.
///
/// This ensures that for every reference to a type parameter, its corresponding
/// function type is the innermost function type enclosing it for which the
/// index falls within the type parameter index range of the function type.
class _FunctionTypeParameterOffsetCollector extends RecursiveVisitor {
final Types types;
final List<FunctionType> _functionStack = [];
final List<Set<FunctionType>> _functionsContainingParameters = [];
final Map<TypeParameter, int> _functionForParameter = {};
_FunctionTypeParameterOffsetCollector(this.types);
@override
void visitFunctionType(FunctionType node) {
int slot = _functionStack.length;
_functionStack.add(node);
_functionsContainingParameters.add({});
for (int i = 0; i < node.typeParameters.length; i++) {
TypeParameter parameter = node.typeParameters[i];
_functionForParameter[parameter] = slot;
}
super.visitFunctionType(node);
int offset = 0;
for (FunctionType inner in _functionsContainingParameters.last) {
offset = max(
offset,
types.functionTypeParameterOffset[inner]! +
inner.typeParameters.length);
}
types.functionTypeParameterOffset[node] = offset;
for (int i = 0; i < node.typeParameters.length; i++) {
TypeParameter parameter = node.typeParameters[i];
types.functionTypeParameterIndex[parameter] = offset + i;
}
_functionsContainingParameters.removeLast();
_functionStack.removeLast();
}
@override
void visitTypeParameterType(TypeParameterType node) {
if (types.isFunctionTypeParameter(node)) {
int slot = _functionForParameter[node.parameter]!;
for (int inner = slot + 1; inner < _functionStack.length; inner++) {
_functionsContainingParameters[slot].add(_functionStack[inner]);
}
}
}
}