blob: c8c9805394aac5fd1d0c23c2935f655bf3ff89f7 [file] [log] [blame]
library kernel.transformations.vip_instantiate_generics;
import '../class_hierarchy.dart';
import '../core_types.dart';
import '../type_algebra.dart';
import '../type_environment.dart';
import '../visitor.dart';
import '../ast.dart';
import '../clone.dart';
// TODO: instantiate static invocations and factory methods too.
Program transformProgram(
CoreTypes coreTypes, ClassHierarchy hierarchy, Program program) {
new FixPointGenericInstantiator(coreTypes, program).processProgram();
return program;
}
enum TypeKind { Reference, Integer, Double }
class TypeKindAnalyzer {
final Program program;
final CoreTypes coreTypes;
Map<TypeParameter, TypeKind> substitution;
TypeKindAnalyzer(this.program, this.coreTypes);
TypeKind toTypeKind(DartType type, {TreeNode where}) {
if (type is InterfaceType) {
final classNode = type.classNode;
if (classNode == coreTypes.numClass) {
return TypeKind.Reference;
}
if (classNode == coreTypes.intClass) {
return TypeKind.Integer;
} else if (classNode == coreTypes.doubleClass) {
return TypeKind.Double;
} else {
return TypeKind.Reference;
}
} else if (type is FunctionType) {
return TypeKind.Reference;
} else if (type is BottomType || type is VoidType || type is DynamicType) {
return TypeKind.Reference;
} else if (type is TypeParameterType &&
substitution?.containsKey(type.parameter) == true) {
return substitution[type.parameter];
} else {
// report(program, where, "Can't establish whether ${type} is integer, double or reference");
throw "Can't establish whether ${type} is integer, double or reference";
}
}
}
class Generic<T> {
final T original;
final Set<TypeParameter> typeParameters;
final Set<InterfaceType> nestedTypes = new Set<InterfaceType>();
final Set<MethodInstantiation> invocations = new Set<MethodInstantiation>();
final Set<TypeKindVector> instantiations = new Set<TypeKindVector>();
Generic(this.original, this.typeParameters);
toString() {
if (original is Class) {
Class o = original as Class;
return "generic class ${o.enclosingLibrary.name}::${o.name}";
} else {
Procedure o = original as Procedure;
final parentName =
o.enclosingClass?.name ?? o.enclosingLibrary?.name ?? '';
return "generic proc ${parentName}::${o.name}";
}
}
void dump([String prefix = '']) {
if (instantiations.isEmpty) {
return;
}
print('${prefix}-- ${this}');
print('${prefix}## ${instantiations}');
// for (var type in nestedTypes) {
// print('${prefix} type ${type}');
// }
// for (var invocation in invocations) {
// print('${prefix} invoke ${invocation}');
// }
}
}
class GenericClass extends Generic<Class> {
final List<GenericProcedure> nestedGenerics = new List<GenericProcedure>();
GenericClass(Class original, Set<TypeParameter> typeParameters)
: super(original, typeParameters);
@override
void dump([String prefix = '']) {
super.dump(prefix);
for (var proc in nestedGenerics) {
proc.dump(prefix + ' ');
}
}
}
class GenericProcedure extends Generic<Procedure> {
final GenericClass parent;
GenericProcedure(Procedure original, Set<TypeParameter> typeParameters,
{this.parent})
: super(original, typeParameters);
}
class FixPointGenericInstantiator extends RecursiveVisitor {
final Program program;
final TypeKindAnalyzer typeKindAnalyzer;
TypeEnvironment environment;
final Set<Instantiation> instantiations = new Set<Instantiation>();
List<Instantiation> worklist = <Instantiation>[];
final Map<TreeNode, Generic> generics = <TreeNode, Generic>{};
final Map<String, List<GenericProcedure>> genericMethods =
<String, List<GenericProcedure>>{};
CoreTypes get coreTypes => typeKindAnalyzer.coreTypes;
FixPointGenericInstantiator(CoreTypes coreTypes, this.program)
: typeKindAnalyzer = new TypeKindAnalyzer(program, coreTypes) {
_typeSubstitutor = new TypeSubstitutor(this);
}
void processWorklist() {
while (worklist.isNotEmpty) {
final current = worklist;
worklist = <Instantiation>[];
for (var insn in current) {
if (insn.klass is GenericClass) {
instantiateClass(insn);
} else if (insn.klass is String) {
instantiateMethods(insn);
} else if (insn.klass is Procedure) {
instantiateProcedure(insn);
} else {
throw "Unexexpected ${insn}";
}
}
}
}
Map<TypeParameter, DartType> makeSubstitution(
List<TypeParameter> typeParameters, List<TypeKind> kinds) {
final typeSubstitution = <TypeParameter, DartType>{};
for (var i = 0; i < typeParameters.length; i++) {
final param = typeParameters[i];
switch (kinds[i]) {
case TypeKind.Reference:
break;
case TypeKind.Integer:
typeSubstitution[param] = coreTypes.intClass.rawType;
break;
case TypeKind.Double:
typeSubstitution[param] = coreTypes.doubleClass.rawType;
break;
}
}
return typeSubstitution;
}
void instantiateMethods(Instantiation<String> insn) {
for (var generic in genericMethods[insn.klass] ?? const <Null>[]) {
instantiateProcedureImpl(generic, insn.types);
}
}
void instantiateProcedure(Instantiation<Procedure> insn) {
final generic = generics[insn.klass] as GenericProcedure;
instantiateProcedureImpl(generic, insn.types);
}
void instantiateProcedureImpl(GenericProcedure generic, TypeKindVector v) {
generic.instantiations.add(v);
final s = Substitution.fromMap(
makeSubstitution(generic.original.function.typeParameters, v.kinds));
final hostS = [Substitution.empty];
if (generic.parent != null) {
hostS.addAll(generic.parent.instantiations
.map((v) => substitutionForClassInstantiation(generic.parent, v)));
}
for (var s2 in hostS) {
final s3 = Substitution.combine(s, s2);
for (var type in generic.nestedTypes) {
final newType = s3.substituteType(type);
if (type != newType) {
recordClassInstantiation(newType);
}
}
}
}
Substitution substitutionFromClassInstantiation(
Instantiation<GenericClass> insn) =>
Substitution.fromMap(makeSubstitution(
insn.klass.original.typeParameters, insn.types.kinds));
Substitution substitutionForClassInstantiation(
GenericClass klass, TypeKindVector v) =>
Substitution
.fromMap(makeSubstitution(klass.original.typeParameters, v.kinds));
Substitution Function(TypeKindVector) substitutionFromMethodInstantiation(
GenericProcedure generic) {
return (TypeKindVector v) => Substitution.fromMap(
makeSubstitution(generic.original.function.typeParameters, v.kinds));
}
List<DartType> substituteList(List<DartType> types, Substitution s) {
List<DartType> result = null;
for (var i = 0; i < types.length; i++) {
final type = types[i];
final newType = s.substituteType(type);
if (type != newType && result == null) {
result = new List<DartType>(types.length);
result.setRange(0, i, types);
}
if (result != null) {
result[i] = newType;
}
}
return result;
}
void instantiateClass(Instantiation<GenericClass> insn) {
insn.klass.instantiations.add(insn.types);
final s = substitutionFromClassInstantiation(insn);
for (var type in insn.klass.nestedTypes) {
final newType = s.substituteType(type);
if (type != newType) {
recordClassInstantiation(newType);
}
}
for (var invoke in insn.klass.invocations) {
final args = substituteList(invoke.typeArguments, s);
if (args == null) {
continue;
}
assert(anyPrimitives(args));
recordMethodInstantiation(new MethodInstantiation(invoke.name, args));
}
for (var generic in insn.klass.nestedGenerics) {
for (var s2 in [Substitution.empty]..addAll(generic.instantiations
.map(substitutionFromMethodInstantiation(generic)))) {
final s3 = Substitution.combine(s, s2);
for (var type in generic.nestedTypes) {
final newType = s3.substituteType(type);
if (type != newType) {
recordClassInstantiation(newType);
}
}
}
}
for (var procedure in insn.klass.original.procedures) {
if (procedure.isFactory) {
GenericProcedure generic = generics[procedure];
if (generic.instantiations.add(insn.types)) {
instantiateProcedureImpl(generic, insn.types);
}
}
}
}
GenericClass currentClass;
GenericProcedure currentMethod;
static bool anyTypeParameters(List<DartType> types) =>
types.any((dt) => dt is TypeParameterType);
bool anyPrimitives(List<DartType> types) =>
types.any((dt) => typeKindAnalyzer.toTypeKind(dt) != TypeKind.Reference);
void handleInterfaceType(InterfaceType type, {TreeNode where}) {
if (anyTypeParameters(type.typeArguments)) {
final Generic current = (currentMethod != null &&
type.typeArguments.any((dt) =>
dt is TypeParameterType &&
currentMethod.typeParameters.contains(dt.parameter)))
? currentMethod
: currentClass;
current.nestedTypes.add(type);
} else if (anyPrimitives(type.typeArguments)) {
// Instantiate eagerly.
recordClassInstantiation(type);
}
}
void handleMethodInstantiation(MethodInstantiation method) {
if (anyTypeParameters(method.typeArguments)) {
final Generic current = (currentMethod != null &&
method.typeArguments.any((dt) =>
dt is TypeParameterType &&
currentMethod.typeParameters.contains(dt.parameter)))
? currentMethod
: currentClass;
if (method.name == 'runUnary') {
print('${current} <-- ${method}');
}
current.invocations.add(method);
} else if (anyPrimitives(method.typeArguments)) {
// Instantiate eagerly.
recordMethodInstantiation(method);
}
}
void recordClassInstantiation(InterfaceType type) {
final generic = toGenericClass(type.classNode);
final insn = new Instantiation<GenericClass>(
generic, new TypeKindVector(typesToKinds(type.typeArguments)));
assert(insn.types.kinds.any((k) => k != TypeKind.Reference));
// Check if already instantiated.
if (!instantiations.add(insn)) {
return;
}
worklist.add(insn);
}
void recordMethodInstantiation<T>(MethodInstantiation<T> method) {
final kinds = new TypeKindVector(typesToKinds(method.typeArguments));
final name = method.name;
final insn = name is String
? new Instantiation<String>(name, kinds)
: new Instantiation<Procedure>(name as Procedure, kinds);
assert(insn.types.kinds.any((k) => k != TypeKind.Reference));
if (!instantiations.add(insn)) {
return;
}
worklist.add(insn);
}
GenericClass toGenericClass(Class klass) {
return generics.putIfAbsent(klass,
() => new GenericClass(klass, new Set.from(klass.typeParameters)));
}
void processClass(Class klass) {
if (klass.typeParameters.isNotEmpty) {
currentClass = toGenericClass(klass);
}
if (klass.supertype != null) {
handleInterfaceType(klass.supertype.asInterfaceType, where: klass);
}
for (var st in klass.implementedTypes) {
handleInterfaceType(st.asInterfaceType, where: klass);
}
for (var constructor in klass.constructors) {
constructor.accept(this);
}
for (var procedure in klass.procedures) {
if (procedure.function.typeParameters.isNotEmpty) {
currentMethod = generics.putIfAbsent(
procedure,
() => new GenericProcedure(
procedure, new Set.from(procedure.function.typeParameters),
parent: !procedure.isStatic ? currentClass : null));
if (!procedure.isStatic) {
genericMethods
.putIfAbsent(procedure.name.name, () => <GenericProcedure>[])
.add(currentMethod);
if (currentClass != null) {
currentClass.nestedGenerics.add(currentMethod);
}
}
}
procedure.accept(this);
currentMethod = null;
}
klass.fields.forEach((f) => f.accept(this));
currentClass = null;
}
static String _letter(TypeKind kind) {
switch (kind) {
case TypeKind.Reference:
return "R";
case TypeKind.Double:
return "D";
case TypeKind.Integer:
return "I";
}
}
static String _abbreviateList(List<TypeKind> v) => v.map(_letter).join();
static String _abbreviate(TypeKindVector v) => _abbreviateList(v.kinds);
static String _className(Instantiation<Class> insn) =>
"${insn.klass.name}%${_abbreviate(insn.types)}";
final Map<Instantiation<Class>, Class> mapping =
<Instantiation<Class>, Class>{};
final Map<Class, Instantiation<Class>> instantiatedClasses =
<Class, Instantiation<Class>>{};
_createInstantiation(Instantiation<Class> instantiation) {
final clone = new Class(
name: _className(instantiation),
isAbstract: instantiation.klass.isAbstract,
fileUri: instantiation.klass.fileUri);
instantiation.klass.enclosingLibrary.addClass(clone);
mapping[instantiation] = clone;
instantiatedClasses[clone] = instantiation;
}
_substitutionFromInstantiation(Instantiation<Class> instantiation) =>
Substitution.fromMap(makeSubstitution(
instantiation.klass.typeParameters, instantiation.types.kinds));
Map<Instantiation<Class>, Map<TreeNode, TreeNode>> outlineMappings =
<Instantiation<Class>, Map<TreeNode, TreeNode>>{};
Map<Instantiation<Class>, Map<TypeParameter, DartType>> typeSubstitutions =
<Instantiation<Class>, Map<TypeParameter, DartType>>{};
static String findNativeName(Member procedure) {
// Native procedures are marked as external and have an annotation,
// which looks like this:
//
// import 'dart:_internal' as internal;
//
// @internal.ExternalName("<name-of-native>")
// external Object foo(arg0, ...);
//
if (procedure.isExternal) {
for (final Expression annotation in procedure.annotations) {
if (annotation is ConstructorInvocation) {
final Class klass = annotation.target.enclosingClass;
if (klass.name == 'ExternalName' &&
klass.enclosingLibrary.importUri.toString() == 'dart:_internal') {
assert(annotation.arguments.positional.length == 1);
return (annotation.arguments.positional[0] as StringLiteral).value;
}
} else if (annotation is ConstantExpression) {
final Constant constant = annotation.constant;
if (constant is InstanceConstant) {
final Class klass = constant.klass;
if (klass.name == 'ExternalName' &&
klass.enclosingLibrary.importUri.toString() ==
'dart:_internal') {
assert(constant.fieldValues.length == 1);
return (constant.fieldValues.values.first as StringConstant)
.value;
}
}
}
}
throw 'External procedure ${procedure} has no @ExternalName("...") annotation!';
}
return null;
}
TreeNode createNativeName(String name) {
return new ConstructorInvocation(
coreTypes.getMember('dart:_internal', 'ExternalName', ''),
new Arguments([new StringLiteral(name)]));
}
TypeSubstitutor _typeSubstitutor;
_createOutline(Instantiation<Class> instantiation) {
final outlineMapping =
outlineMappings[instantiation] ??= <TreeNode, TreeNode>{};
final original = instantiation.klass;
final cloned = mapping[instantiation];
final substitutionMapping = typeSubstitutions[instantiation] ??=
makeSubstitution(
instantiation.klass.typeParameters, instantiation.types.kinds);
final substitution = Substitution.fromMap(substitutionMapping);
DartType substituteType(DartType type) =>
_typeSubstitutor.visit(substitution.substituteType(type));
substituteSupertype(Supertype type) {
if (type != null) {
type = _typeSubstitutor
.visitSupertype(substitution.substituteSupertype(type));
}
return type;
}
TypeParameter cloneTypeParameter(TypeParameter param) {
var newNode = new TypeParameter(param.name);
substitutionMapping[param] = new TypeParameterType(newNode);
newNode.bound = _typeSubstitutor.visit(param.bound);
return newNode;
}
cloned.typeParameters
.addAll(original.typeParameters.map((TypeParameter param) {
return !substitutionMapping.containsKey(param)
? cloneTypeParameter(param)
: null;
}).where((p) => p != null));
for (var param in cloned.typeParameters) param.parent = cloned;
cloned.supertype = substituteSupertype(original.supertype);
cloned.mixedInType = substituteSupertype(original.mixedInType);
cloned.implementedTypes
.addAll(original.implementedTypes.map(substituteSupertype));
cloneConstructor(Constructor node) {
return outlineMapping[node] = new Constructor(null,
name: node.name,
isConst: node.isConst,
isExternal: node.isExternal,
transformerFlags: node.transformerFlags)
..fileEndOffset = node.fileEndOffset;
}
cloneProcedure(Procedure node) {
final Procedure proc = new Procedure(node.name, node.kind, null,
isAbstract: node.isAbstract,
isStatic: node.isStatic,
isExternal: node.isExternal,
isConst: node.isConst,
transformerFlags: node.transformerFlags,
fileUri: node.fileUri)
..fileEndOffset = node.fileEndOffset;
final FunctionNode func = node.function;
Map<TypeParameter, DartType> s = substitutionMapping;
List<TypeParameter> typeParameters;
if (func.typeParameters.isNotEmpty) {
s = new Map<TypeParameter, DartType>.from(s);
if (node.isFactory) {
typeParameters = cloned.typeParameters
.map((p) => new TypeParameter(p.name, p.bound))
.toList();
var j = 0;
for (var i = 0; i < func.typeParameters.length; i++) {
final type = substitutionMapping[original.typeParameters[i]];
s[func.typeParameters[i]] = type is TypeParameterType
? new TypeParameterType(typeParameters[j++])
: type;
}
assert(j == typeParameters.length);
} else {
typeParameters = func.typeParameters
.map((p) =>
new TypeParameter(p.name, _typeSubstitutor.visit(p.bound)))
.toList();
for (var i = 0; i < func.typeParameters.length; i++) {
s[func.typeParameters[i]] =
new TypeParameterType(typeParameters[i]);
}
}
}
final cloner = new CloneVisitor(
typeSubstitution: s, typeSubstitutor: _typeSubstitutor.visit);
var positional = func.positionalParameters.map(cloner.clone).toList();
var named = func.namedParameters.map(cloner.clone).toList();
proc.function = new FunctionNode(null,
typeParameters: typeParameters,
positionalParameters: positional,
namedParameters: named,
requiredParameterCount: func.requiredParameterCount,
returnType: cloner.visitType(func.returnType),
asyncMarker: func.asyncMarker,
dartAsyncMarker: func.dartAsyncMarker)
..fileEndOffset = node.fileEndOffset;
proc.function.parent = proc;
return outlineMapping[node] = proc;
}
cloneField(Field node) {
return outlineMapping[node] = new Field(node.name,
type: substituteType(node.type),
initializer: null,
isFinal: node.isFinal,
isConst: node.isConst,
isStatic: node.isStatic,
hasImplicitGetter: node.hasImplicitGetter,
hasImplicitSetter: node.hasImplicitSetter,
transformerFlags: node.transformerFlags,
fileUri: node.fileUri)
..fileEndOffset = node.fileEndOffset;
}
cloned.constructors.addAll(original.constructors.map(cloneConstructor));
setParents(cloned.constructors, cloned);
cloned.procedures.addAll(original.procedures.map(cloneProcedure));
setParents(cloned.procedures, cloned);
cloned.fields.addAll(original.fields.map(cloneField));
setParents(cloned.fields, cloned);
}
final Map<Instantiation<Procedure>, Procedure> instantiatedMethods =
<Instantiation<Procedure>, Procedure>{};
final Map<Procedure, Instantiation<Procedure>> methodToInstantiation =
new Map<Procedure, Instantiation<Procedure>>();
Map<TypeParameter, DartType> _createSubstitutionForMethod(
Map<TypeParameter, DartType> host,
Instantiation<Procedure> instantiation,
List<TypeParameter> clone) {
final FunctionNode original = instantiation.klass.function;
Map<TypeParameter, DartType> s =
makeSubstitution(original.typeParameters, instantiation.types.kinds);
if (host != null) {
s.addAll(host);
}
var j = 0;
for (var i = 0; i < original.typeParameters.length; i++) {
s[original.typeParameters[i]] ??= new TypeParameterType(clone[j++]);
}
assert(j == clone.length);
return s;
}
_createProcedureOutline(Instantiation<Procedure> instantiation) {
final Procedure proc = instantiation.klass;
assert(!proc.isFactory);
print('${instantiation}');
cloneProcedure(Procedure node) {
final Procedure proc = new Procedure(
new Name("${node.name.name}%${_abbreviate(instantiation.types)}",
node.name.library),
node.kind,
null,
isAbstract: node.isAbstract,
isStatic: node.isStatic,
isExternal: node.isExternal,
isConst: node.isConst,
transformerFlags: node.transformerFlags,
fileUri: node.fileUri)
..fileEndOffset = node.fileEndOffset;
final FunctionNode func = node.function;
List<TypeParameter> typeParameters = [];
for (var i = 0; i < func.typeParameters.length; i++) {
if (instantiation.types.kinds[i] == TypeKind.Reference) {
typeParameters.add(new TypeParameter(
func.typeParameters[i].name, func.typeParameters[i].bound));
}
}
final Map<TypeParameter, DartType> s =
_createSubstitutionForMethod(null, instantiation, typeParameters);
final cloner = new CloneVisitor(
typeSubstitution: s, typeSubstitutor: _typeSubstitutor.visit);
var positional = func.positionalParameters.map(cloner.clone).toList();
var named = func.namedParameters.map(cloner.clone).toList();
proc.function = new FunctionNode(null,
typeParameters: typeParameters,
positionalParameters: positional,
namedParameters: named,
requiredParameterCount: func.requiredParameterCount,
returnType: cloner.visitType(func.returnType),
asyncMarker: func.asyncMarker,
dartAsyncMarker: func.dartAsyncMarker)
..fileEndOffset = node.fileEndOffset;
proc.function.parent = proc;
methodToInstantiation[proc] = instantiation;
instantiatedMethods[instantiation] = proc;
return proc;
}
final clone = cloneProcedure(proc);
if (proc.enclosingClass != null) {
proc.enclosingClass.addMember(clone);
} else {
proc.enclosingLibrary.addMember(clone);
}
}
_fillProcedureOutline(Instantiation<Procedure> instantiation) {
final procClone = instantiatedMethods[instantiation];
assert(procClone != null);
Member redirect(TreeNode receiver, Member m, Arguments arguments) {
if (m is Constructor) {} else if (m is Procedure) {
if (m.isFactory) {
return m;
}
}
return m;
}
fillProcedure(Procedure node) {
final FunctionNode funcClone = procClone.function;
Map<TypeParameter, DartType> s = _createSubstitutionForMethod(
null, instantiation, funcClone.typeParameters);
final cloner = new RedirectingCloneVisitor(this,
typeSubstitution: s,
memberSubstitutor: redirect,
typeSubstitutor: _typeSubstitutor.visit);
final originalFunction = instantiation.klass.function;
for (var i = 0; i < originalFunction.positionalParameters.length; i++) {
cloner.variables[originalFunction.positionalParameters[i]] =
funcClone.positionalParameters[i];
}
for (var i = 0; i < originalFunction.namedParameters.length; i++) {
cloner.variables[originalFunction.namedParameters[i]] =
funcClone.namedParameters[i];
}
funcClone.body = cloner.cloneFunctionNodeBody(originalFunction);
funcClone.body?.parent = funcClone;
}
fillProcedure(instantiation.klass);
}
_fillMembers(Instantiation<Class> instantiation) {
final original = instantiation.klass;
final cloned = mapping[instantiation];
final outlineMapping = outlineMappings[instantiation];
final Map<TypeParameter, DartType> substitution =
typeSubstitutions[instantiation];
environment.thisType = cloned.thisType;
Member redirect(TreeNode receiver, Member m, Arguments arguments) {
if (m is Constructor) {} else if (m is Procedure) {
if (m.isFactory) {
return m;
}
}
return m;
}
final clone = new RedirectingCloneVisitor(this,
typeSubstitution: substitution,
memberSubstitutor: redirect,
typeSubstitutor: (t) => _typeSubstitutor.visit(t)).clone;
fillConstructor(Constructor node) {
final Constructor nodeClone = outlineMapping[node];
nodeClone.function = clone(node.function);
nodeClone.function?.parent = nodeClone;
nodeClone.initializers = node.initializers.map(clone).toList();
setParents(nodeClone.initializers, nodeClone);
}
fillProcedure(Procedure node) {
final Procedure procClone = outlineMapping[node];
final FunctionNode funcClone = procClone.function;
final Instantiation<Procedure> methodInstantiation =
methodToInstantiation[node];
Map<TypeParameter, DartType> s = substitution;
if (node.isFactory) {
assert(node.function.typeParameters.length ==
original.typeParameters.length);
assert(substitution.length <= original.typeParameters.length);
s = new Map<TypeParameter, DartType>();
var j = 0;
for (var i = 0; i < node.function.typeParameters.length; i++) {
final type = substitution[original.typeParameters[i]];
assert(type != null);
s[node.function.typeParameters[i]] = type is TypeParameterType
? new TypeParameterType(funcClone.typeParameters[j++])
: type;
}
assert(j == funcClone.typeParameters.length);
} else if (methodInstantiation != null) {
s = _createSubstitutionForMethod(
substitution, methodInstantiation, funcClone.typeParameters);
} else if (node.function.typeParameters.isNotEmpty) {
s = new Map<TypeParameter, DartType>.from(substitution);
for (var i = 0; i < node.function.typeParameters.length; i++) {
s[node.function.typeParameters[i]] =
new TypeParameterType(funcClone.typeParameters[i]);
}
}
final originalFunction = methodInstantiation != null
? methodInstantiation.klass.function
: node.function;
final externalName = findNativeName(node);
if (externalName != null) {
final tp = <TypeParameter>[];
if (!node.isFactory) {
tp.addAll(original.typeParameters);
}
tp.addAll(originalFunction.typeParameters);
final suffix =
_abbreviateList(typesToKinds(tp.map((t) => s[t]).toList()));
procClone.addAnnotation(createNativeName('${externalName}%${suffix}'));
}
final cloner = new RedirectingCloneVisitor(this,
typeSubstitution: s,
memberSubstitutor: redirect,
typeSubstitutor: _typeSubstitutor.visit);
for (var i = 0; i < originalFunction.positionalParameters.length; i++) {
cloner.variables[originalFunction.positionalParameters[i]] =
funcClone.positionalParameters[i];
}
for (var i = 0; i < originalFunction.namedParameters.length; i++) {
cloner.variables[originalFunction.namedParameters[i]] =
funcClone.namedParameters[i];
}
funcClone.body = cloner.cloneFunctionNodeBody(originalFunction);
funcClone.body?.parent = funcClone;
}
fillField(Field node) {
final Field nodeClone = outlineMapping[node];
nodeClone.initializer =
node.initializer != null ? clone(node.initializer) : null;
nodeClone.initializer?.parent = nodeClone;
}
original.constructors.forEach(fillConstructor);
original.procedures.forEach(fillProcedure);
original.fields.forEach(fillField);
}
void processProgram() {
collectGenerics();
processWorklist();
for (var generic in generics.values) {
if (generic is GenericProcedure) {
print('${generic}: ${generic.instantiations}');
}
}
// Ready to instantiate all classes and methods.
for (var generic in generics.values) {
if (generic is GenericClass) {
final klass = generic.original;
for (var v in generic.instantiations) {
final instantiation = new Instantiation(klass, v);
_createInstantiation(instantiation);
}
} else if (generic is GenericProcedure &&
generic.parent == null &&
!generic.original.isFactory) {
// Instantiate.
}
}
for (var generic in generics.values) {
if (generic is GenericClass) {
final klass = generic.original;
// First instantiate all instance method generics.
for (var method in generic.nestedGenerics) {
for (var v in method.instantiations) {
final instantiation = new Instantiation(method.original, v);
_createProcedureOutline(instantiation);
}
}
for (var v in generic.instantiations) {
final instantiation = new Instantiation(klass, v);
_createOutline(instantiation);
}
} else if (generic is GenericProcedure &&
generic.parent == null &&
!generic.original.isFactory) {
// Instantiate.
for (var v in generic.instantiations) {
_createProcedureOutline(new Instantiation(generic.original, v));
}
}
}
environment = new TypeEnvironment(
coreTypes, new ClosedWorldClassHierarchy(program),
strongMode: true);
for (var generic in generics.values) {
if (generic is GenericClass) {
final klass = generic.original;
for (var v in generic.instantiations) {
final instantiation = new Instantiation(klass, v);
_fillMembers(instantiation);
}
} else if (generic is GenericProcedure &&
generic.parent == null &&
!generic.original.isFactory) {
for (var v in generic.instantiations) {
_fillProcedureOutline(new Instantiation(generic.original, v));
}
}
}
// All generics are instantiated. Now fix the code.
final visitor = new RedirectingVisitor(this);
for (var library in program.libraries) {
for (var klass in library.classes) {
environment.thisType = klass.thisType;
try {
klass.accept(visitor);
} catch (e) {
print('Error while visiting ${klass}');
rethrow;
}
}
for (var procedure in library.procedures) {
procedure.accept(visitor);
}
for (var field in library.fields) {
field.accept(visitor);
}
}
}
void collectGenerics() {
for (var library in program.libraries) {
library.classes.toList(growable: false).forEach(processClass);
for (var procedure in library.procedures) {
if (procedure.function.typeParameters.isNotEmpty) {
currentMethod = generics.putIfAbsent(
procedure,
() => new GenericProcedure(
procedure, new Set.from(procedure.function.typeParameters)));
}
procedure.accept(this);
currentMethod = null;
}
for (var field in library.fields) {
field.accept(this);
}
}
}
@override
visitFunctionNode(FunctionNode node) {
if (node.typeParameters.isNotEmpty && node.parent is! Member) {
throw "Generic closures are currently unsupported";
}
return super.visitFunctionNode(node);
}
@override
visitConstructorInvocation(ConstructorInvocation node) {
final List<DartType> types = node.arguments.types;
if (types.isNotEmpty) {
final Class klass = node.target.enclosingClass;
handleInterfaceType(new InterfaceType(klass, types.toList()),
where: node);
}
return super.visitConstructorInvocation(node);
}
@override
visitMethodInvocation(MethodInvocation node) {
final List<DartType> types = node.arguments.types;
if (types.isNotEmpty) {
handleMethodInstantiation(
new MethodInstantiation<String>(node.name.name, types));
}
return super.visitMethodInvocation(node);
}
@override
visitStaticInvocation(StaticInvocation node) {
final List<DartType> types = node.arguments.types;
if (types.isNotEmpty) {
handleMethodInstantiation(
new MethodInstantiation<Procedure>(node.target, types));
if (node.target.isFactory) {
handleInterfaceType(
new InterfaceType(node.target.enclosingClass, types));
}
}
return super.visitStaticInvocation(node);
}
@override
visitDirectMethodInvocation(DirectMethodInvocation node) {
final List<DartType> types = node.arguments.types;
if (types.isNotEmpty) {
print('method invocation -> ${node}');
}
return super.visitDirectMethodInvocation(node);
}
@override
visitSuperMethodInvocation(SuperMethodInvocation node) {
final List<DartType> types = node.arguments.types;
if (types.isNotEmpty) {
print('super method invocation -> ${node}');
}
return super.visitSuperMethodInvocation(node);
}
List<TypeKind> typesToKinds(List<DartType> types,
{ignoreTypeParameters: true}) =>
types.map((t) {
return (ignoreTypeParameters && t is TypeParameterType)
? TypeKind.Reference
: typeKindAnalyzer.toTypeKind(t);
}).toList(growable: false);
static bool containsPrimitives(List<TypeKind> kinds) =>
kinds.any((k) => k != TypeKind.Reference);
Class instantiate(Class klass, List<DartType> typeArguments) {
assert(!instantiatedClasses.containsKey(klass), "not implemented yet");
final kinds = typesToKinds(typeArguments);
final clone =
mapping[new Instantiation(klass, new TypeKindVector(kinds))] ?? klass;
if (clone != klass) {
var j = 0;
for (var i = 0; i < typeArguments.length; i++) {
if (kinds[i] == TypeKind.Reference) {
typeArguments[j++] = typeArguments[i];
}
}
typeArguments.length = j;
}
return clone;
}
Name instantiateName(Name name, List<DartType> typeArguments) {
assert(!name.name.contains('%'), 'not implemented yet');
final kinds = typesToKinds(typeArguments);
if (kinds.any((k) => k != TypeKind.Reference)) {
name = new Name("${name.name}%${_abbreviateList(kinds)}", name.library);
var j = 0;
for (var i = 0; i < typeArguments.length; i++) {
if (kinds[i] == TypeKind.Reference) {
typeArguments[j++] = typeArguments[i];
}
}
typeArguments.length = j;
}
return name;
}
final lookupCache = new LookupCache();
}
class TypeSubstitutor extends DartTypeVisitor<DartType> {
final FixPointGenericInstantiator owner;
TypeSubstitutor(this.owner);
int changed = 0;
Class instantiate(Class klass, List<DartType> typeArguments) {
final clone = owner.instantiate(klass, typeArguments);
if (clone != klass) changed++;
return clone;
}
DartType visit(DartType node) {
changed = 0;
return node.accept(this);
}
@override
InterfaceType visitInterfaceType(InterfaceType node) {
if (node.typeArguments.isEmpty) {
return node;
}
final oldChanged = changed;
final typeArguments = node.typeArguments.map(visit).toList();
final classNode = instantiate(node.classNode, typeArguments);
if (changed != oldChanged) {
changed++;
return new InterfaceType(classNode, typeArguments);
}
return node;
}
@override
FunctionType visitFunctionType(FunctionType node) {
final oldChanged = changed;
final List<TypeParameter> typeParameters = node.typeParameters;
final int requiredParameterCount = node.requiredParameterCount;
final List<DartType> positionalParameters =
node.positionalParameters.map(visit).toList();
final List<NamedType> namedParameters =
node.namedParameters.map(visitNamedType).toList();
final DartType returnType = visit(node.returnType);
if (oldChanged != changed) {
return new FunctionType(positionalParameters, returnType,
requiredParameterCount: requiredParameterCount,
namedParameters: namedParameters,
typeParameters: typeParameters);
}
return node;
}
@override
TypeParameterType visitTypeParameterType(TypeParameterType node) {
// TODO
return node;
}
@override
TypedefType visitTypedefType(TypedefType node) {
// TODO
return node;
}
@override
InvalidType visitInvalidType(InvalidType node) => node;
@override
DynamicType visitDynamicType(DynamicType node) => node;
@override
VoidType visitVoidType(VoidType node) => node;
@override
BottomType visitBottomType(BottomType node) => node;
@override
VectorType visitVectorType(VectorType node) => node;
NamedType visitNamedType(NamedType type) {
final oldChanged = changed;
final DartType t = visit(type.type);
if (oldChanged != changed) {
return new NamedType(type.name, t);
} else {
return type;
}
}
Supertype visitSupertype(Supertype node) {
changed = 0;
if (node.typeArguments.isEmpty) {
return node;
}
final oldChanged = changed;
final List<DartType> typeArguments = node.typeArguments.map(visit).toList();
final Class classNode = instantiate(node.classNode, typeArguments);
if (oldChanged != changed) {
return new Supertype(classNode, typeArguments);
}
return node;
}
}
class GenericInstantiator extends Transformer {
final FixPointGenericInstantiator owner;
GenericInstantiator(this.owner);
}
class TypeKindVector {
int _hashCode;
final List<TypeKind> kinds;
TypeKindVector(this.kinds);
bool operator ==(other) {
return other is TypeKindVector && _listEquals(this.kinds, other.kinds);
}
int get hashCode => _hashCode ??= _listHash(kinds);
toString() =>
'<' + kinds.map(FixPointGenericInstantiator._letter).join(', ') + '>';
}
class Instantiation<ClassT> {
final ClassT klass;
final TypeKindVector types;
Instantiation(this.klass, this.types);
bool operator ==(other) {
return other is Instantiation<ClassT> &&
this.klass == other.klass &&
this.types == other.types;
}
int get hashCode => klass.hashCode ^ types.hashCode;
String toString() => '${klass}<${types}>';
}
class MethodInstantiation<T> {
final T name;
final List<DartType> typeArguments;
MethodInstantiation(this.name, this.typeArguments);
@override
bool operator ==(other) {
return other is MethodInstantiation &&
other.name == this.name &&
_listEquals(other.typeArguments, this.typeArguments);
}
@override
int get hashCode => name.hashCode ^ _listHash(typeArguments);
toString() => '${name}<${typeArguments}>';
}
bool _listEquals<T>(List<T> a, List<T> b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
int _listHash<T>(List<T> a) {
int result = a.length;
for (var i = 0; i < a.length; i++) {
result |= a[i].hashCode;
}
return result;
}
class Redirector {
final FixPointGenericInstantiator owner;
Redirector(this.owner);
Class get currentClass => owner.environment.thisType.classNode;
Field lookupField(Class klass, Name name) {
return klass.fields.firstWhere((f) => f.name == name);
}
Constructor lookupConstructor(Class klass, Name name) {
return klass.constructors.firstWhere((ctor) => ctor.name == name);
}
Procedure lookupStatic(Class klass, Name name) {
return klass.procedures
.firstWhere((proc) => proc.isStatic && proc.name == name);
}
ConstructorInvocation redirectConstructorInvocation(
ConstructorInvocation node) {
final types = node.arguments.types;
if (types.isNotEmpty) {
final Class klass = node.target.enclosingClass;
final instantiated = owner.instantiate(klass, types);
if (instantiated != klass) {
node.target = lookupConstructor(instantiated, node.target.name);
}
}
return node;
}
FieldInitializer redirectFieldInitializer(FieldInitializer node) {
node.field = lookupField(currentClass, node.field.name);
return node;
}
SuperInitializer redirectSuperInitializer(SuperInitializer node) {
node.target = lookupConstructor(currentClass.superclass, node.target.name);
return node;
}
PropertyGet redirectPropertyGet(PropertyGet node) {
if (node.interfaceTarget != null) {
final type = node.receiver.getStaticType(owner.environment);
if (type is InterfaceType) {
final target = owner.lookupCache.lookupInterfaceSelector(
type.classNode, new Selector(SelectorKind.Getter, node.name));
if (target == null) {
print('Was ${node.interfaceTarget} on type ${type} in ${node}');
}
assert(target != null);
node.interfaceTarget = target;
}
}
return node;
}
PropertySet redirectPropertySet(PropertySet node) {
if (node.interfaceTarget != null) {
final type = node.receiver.getStaticType(owner.environment);
if (type is InterfaceType) {
final target = owner.lookupCache.lookupInterfaceSelector(
type.classNode, new Selector(SelectorKind.Setter, node.name));
if (target == null) {
print('Was ${node.interfaceTarget} on type ${type} in ${node}');
}
assert(target != null);
node.interfaceTarget = target;
}
}
return node;
}
DirectPropertyGet redirectDirectPropertyGet(DirectPropertyGet node) {
assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
return node;
}
DirectPropertySet redirectDirectPropertySet(DirectPropertySet node) {
assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
return node;
}
SuperPropertyGet redirectSuperPropertyGet(SuperPropertyGet node) {
assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
return node;
}
SuperPropertySet redirectSuperPropertySet(SuperPropertySet node) {
assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
return node;
}
StaticGet redirectStaticGet(StaticGet node) {
return node;
}
StaticSet redirectStaticSet(StaticSet node) {
return node;
}
MethodInvocation redirectMethodInvocation(MethodInvocation node) {
if (node.arguments.types.isNotEmpty) {
node.name = owner.instantiateName(node.name, node.arguments.types);
}
assert((node.interfaceTarget != null) || node.interfaceTarget == null);
if (node.interfaceTarget != null) {
final type = node.receiver.getStaticType(owner.environment);
if (type is InterfaceType) {
final target = owner.lookupCache.lookupInterfaceSelector(
type.classNode, new Selector(SelectorKind.Method, node.name));
if (target == null) {
print(
'Was ${node.interfaceTarget} on type ${type} in ${node} [${node.name}]');
}
assert(target != null);
node.interfaceTarget = target;
}
}
return node;
}
DirectMethodInvocation redirectDirectMethodInvocation(
DirectMethodInvocation node) {
assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
return node;
}
SuperMethodInvocation redirectSuperMethodInvocation(
SuperMethodInvocation node) {
assert(false, '[$currentClass] Unsupported: ${node} [${node.runtimeType}]');
return node;
}
StaticInvocation redirectStaticInvocation(StaticInvocation node) {
final types = node.arguments.types;
if (types.isNotEmpty) {
if (node.target.isFactory) {
final Class klass = node.target.enclosingClass;
final instantiated = owner.instantiate(klass, types);
if (instantiated != klass) {
node.target = lookupStatic(instantiated, node.target.name);
}
} else {
// TODO(vegorov) redirect static method properly.
}
} else {
// TODO(vegorov) redirect self methods to the node.
}
return node;
}
}
class RedirectingCloneVisitor extends CloneVisitor {
final FixPointGenericInstantiator owner;
final Redirector redirector;
RedirectingCloneVisitor(this.owner,
{Map<TypeParameter, DartType> typeSubstitution,
memberSubstitutor,
typeSubstitutor})
: redirector = new Redirector(owner),
super(
typeSubstitution: typeSubstitution,
memberSubstitutor: memberSubstitutor,
typeSubstitutor: typeSubstitutor);
@override
visitIsExpression(IsExpression node) {
final IsExpression clone = super.visitIsExpression(node);
if (!owner.environment.isSubtypeOf(
clone.operand.getStaticType(owner.environment), clone.type)) {
return new BoolLiteral(false);
}
return clone;
}
@override
visitIfStatement(IfStatement node) {
final cond = clone(node.condition);
if (cond is BoolLiteral) {
return cloneOptional(cond.value ? node.then : node.otherwise) ??
new EmptyStatement();
}
return new IfStatement(
cond, cloneOptional(node.then), cloneOptional(node.otherwise));
}
@override
visitConstructorInvocation(ConstructorInvocation node) => redirector
.redirectConstructorInvocation(super.visitConstructorInvocation(node));
@override
visitFieldInitializer(FieldInitializer node) =>
redirector.redirectFieldInitializer(super.visitFieldInitializer(node));
@override
visitSuperInitializer(SuperInitializer node) =>
redirector.redirectSuperInitializer(super.visitSuperInitializer(node));
@override
visitPropertyGet(PropertyGet node) =>
redirector.redirectPropertyGet(super.visitPropertyGet(node));
@override
visitPropertySet(PropertySet node) =>
redirector.redirectPropertySet(super.visitPropertySet(node));
@override
visitDirectPropertyGet(DirectPropertyGet node) =>
redirector.redirectDirectPropertyGet(super.visitDirectPropertyGet(node));
@override
visitDirectPropertySet(DirectPropertySet node) =>
redirector.redirectDirectPropertySet(super.visitDirectPropertySet(node));
@override
visitSuperPropertyGet(SuperPropertyGet node) =>
redirector.redirectSuperPropertyGet(super.visitSuperPropertyGet(node));
@override
visitSuperPropertySet(SuperPropertySet node) =>
redirector.redirectSuperPropertySet(super.visitSuperPropertySet(node));
@override
visitStaticGet(StaticGet node) =>
redirector.redirectStaticGet(super.visitStaticGet(node));
@override
visitStaticSet(StaticSet node) =>
redirector.redirectSuperPropertySet(super.visitStaticSet(node));
@override
visitMethodInvocation(MethodInvocation node) =>
redirector.redirectMethodInvocation(super.visitMethodInvocation(node));
@override
visitDirectMethodInvocation(DirectMethodInvocation node) =>
redirector.redirectDirectMethodInvocation(super.visitDirectMethodInvocation(node));
@override
visitSuperMethodInvocation(SuperMethodInvocation node) =>
redirector.redirectSuperMethodInvocation(super.visitSuperMethodInvocation(node));
@override
visitStaticInvocation(StaticInvocation node) =>
redirector.redirectStaticInvocation(super.visitStaticInvocation(node));
}
class RedirectingVisitor extends Transformer {
final FixPointGenericInstantiator owner;
final Redirector redirector;
RedirectingVisitor(this.owner) : redirector = new Redirector(owner);
@override
Class visitClass(Class node) {
if (owner.instantiatedClasses.containsKey(node)) {
return node;
}
return super.visitClass(node);
}
@override
Procedure visitProcedure(Procedure node) {
if (owner.methodToInstantiation.containsKey(node)) {
return node;
}
try {
return super.visitProcedure(node);
} catch (e) {
print('Error while visiting ${node}');
rethrow;
}
}
@override
DartType visitDartType(DartType type) {
return owner._typeSubstitutor.visit(type);
}
@override
visitConstructorInvocation(ConstructorInvocation node) {
return redirector
.redirectConstructorInvocation(super.visitConstructorInvocation(node));
}
@override
visitStaticInvocation(StaticInvocation node) {
return redirector
.redirectStaticInvocation(super.visitStaticInvocation(node));
}
@override
visitPropertyGet(PropertyGet node) {
return redirector.redirectPropertyGet(super.visitPropertyGet(node));
}
@override
visitPropertySet(PropertySet node) {
return redirector.redirectPropertySet(super.visitPropertySet(node));
}
@override
visitMethodInvocation(MethodInvocation node) {
return redirector
.redirectMethodInvocation(super.visitMethodInvocation(node));
}
}
class Selector {
final SelectorKind kind;
final Name name;
Selector(this.kind, this.name);
factory Selector.fromProcedure(Procedure procedure) {
SelectorKind kind;
switch (procedure.kind) {
case ProcedureKind.Getter:
kind = SelectorKind.Getter;
break;
case ProcedureKind.Setter:
kind = SelectorKind.Setter;
break;
case ProcedureKind.Operator:
case ProcedureKind.Method:
case ProcedureKind.Factory:
kind = SelectorKind.Method;
break;
}
return new Selector(kind, procedure.name);
}
Selector get asGetter => new Selector(SelectorKind.Getter, name);
Selector get asMethod => new Selector(SelectorKind.Method, name);
Selector modifyKindToAccess(Member target) {
if (target is Procedure) {
if (kind == SelectorKind.Method && target.kind == ProcedureKind.Getter) {
return asGetter; // This is a call-though-getter.
} else if (kind == SelectorKind.Getter &&
target.kind == ProcedureKind.Method) {
return asMethod; // This is a tear-off.
}
} else if (target is Field) {
if (kind == SelectorKind.Method) {
return asGetter; // This is a call-though-field.
}
}
return this;
}
int get hashCode => kind.hashCode ^ name.hashCode;
bool operator ==(other) =>
other is Selector && other.kind == kind && other.name == name;
String toString() {
if (kind == SelectorKind.Getter) return 'get:${name}';
if (kind == SelectorKind.Setter) return 'set:${name}';
assert(kind == SelectorKind.Method);
return '$name';
}
}
enum SelectorKind {
Method,
Getter,
Setter,
}
class LookupCache {
final Map<Class, Map<Selector, Member>> lookupCache =
<Class, Map<Selector, Member>>{};
final Map<Class, Map<Selector, Member>> interfaceLookupCache =
<Class, Map<Selector, Member>>{};
Member lookupSelector(Class klass, Selector selector) {
return lookupCache
.putIfAbsent(klass, () => <Selector, Member>{})
.putIfAbsent(selector, () {
while (klass != null) {
for (final Member member in klass.members) {
if (member.name == selector.name) {
if (member is Field) {
// A field shadows getter/call and setter iff non-final/non-const.
if (selector.kind == SelectorKind.Method) {
ensureIsFunctionType(
member.type, 'call-through-field usage $selector');
return member;
}
if (selector.kind == SelectorKind.Getter) {
return member;
}
if (selector.kind == SelectorKind.Setter &&
(!member.isFinal && !member.isConst)) {
return member;
}
} else if (member is Procedure) {
// A procedure shadows getter/call but not setter.
if (selector.kind == SelectorKind.Getter &&
member.kind == ProcedureKind.Method) {
ensureProcedureCanBeTornOff(member);
return member;
}
if (selector.kind == SelectorKind.Getter &&
member.kind == ProcedureKind.Getter) {
return member;
}
if (selector.kind == SelectorKind.Setter &&
member.kind == ProcedureKind.Setter) {
return member;
}
if (selector.kind == SelectorKind.Method &&
(member.kind == ProcedureKind.Method ||
member.kind == ProcedureKind.Operator ||
member.kind == ProcedureKind.Factory)) {
return member;
}
if (selector.kind == SelectorKind.Method &&
(member.kind == ProcedureKind.Getter)) {
ensureIsFunctionType(member.function.returnType,
'call-through-field usage $selector');
return member;
}
} else {
throw "unreachable";
}
}
}
klass = klass.superclass;
}
return null;
});
}
Member lookupInterfaceSelector(Class klass, Selector selector) {
return interfaceLookupCache
.putIfAbsent(klass, () => <Selector, Member>{})
.putIfAbsent(selector, () {
Member member = lookupSelector(klass, selector);
if (member != null) return member;
if (klass.superclass != null) {
member = lookupInterfaceSelector(klass.superclass, selector);
if (member != null) return member;
}
for (final Supertype superType in klass.implementedTypes) {
member = lookupInterfaceSelector(superType.classNode, selector);
if (member != null) return member;
}
return null;
});
}
}
void ensureProcedureCanBeTornOff(Procedure procedure) {
// final FunctionNode function = procedure.function;
// if (function.requiredParameterCount != function.positionalParameters.length ||
// function.namedParameters.isNotEmpty) {
// throw 'No tear-off support for functions with optional parameters!';
// }
}
void ensureIsFunctionType(DartType type, String msg) {
// if (type is! FunctionType) {
// throw 'Expected a [FunctionType] for $msg but got "$type".';
// }
}