blob: 1cd1160475bf60a8768b7fe237e594748a1ef882 [file] [log] [blame]
// Copyright (c) 2016, 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.
library kernel.transformations.mixin_full_resolution;
import '../ast.dart';
import '../class_hierarchy.dart';
import '../clone.dart';
import '../core_types.dart';
import '../type_algebra.dart';
Program transformProgram(Program program) {
new MixinFullResolution().transform(program);
return program;
}
/// Replaces all mixin applications with regular classes, cloning all fields
/// and procedures from the mixed-in class, cloning all constructors from the
/// base class.
///
/// Super calls (as well as super initializer invocations) are also resolved
/// to their targets in this pass.
class MixinFullResolution {
ClassHierarchy hierarchy;
CoreTypes coreTypes;
void transform(Program program) {
var transformedClasses = new Set<Class>();
// Desugar all mixin application classes by copying in fields/methods from
// the mixin and constructors from the base class.
var processedClasses = new Set<Class>();
for (var library in program.libraries) {
for (var class_ in library.classes) {
transformClass(processedClasses, transformedClasses, class_);
}
}
hierarchy = new ClassHierarchy(program);
coreTypes = new CoreTypes(program);
// Resolve all super call expressions and super initializers.
for (var library in program.libraries) {
for (var class_ in library.classes) {
final bool hasTransformedSuperclass =
transformedClasses.contains(class_.superclass);
for (var procedure in class_.procedures) {
if (procedure.containsSuperCalls) {
new SuperCallResolutionTransformer(
hierarchy, coreTypes, class_.superclass)
.visit(procedure);
}
}
for (var constructor in class_.constructors) {
if (constructor.containsSuperCalls) {
new SuperCallResolutionTransformer(
hierarchy, coreTypes, class_.superclass)
.visit(constructor);
}
if (hasTransformedSuperclass && constructor.initializers.length > 0) {
new SuperInitializerResolutionTransformer(class_.superclass)
.transformInitializers(constructor.initializers);
}
}
}
}
}
transformClass(Set<Class> processedClasses, Set<Class> transformedClasses,
Class class_) {
// If this class was already handled then so were all classes up to the
// [Object] class.
if (!processedClasses.add(class_)) return;
// Ensure super classes have been transformed before this class.
if (class_.superclass != null) {
transformClass(processedClasses, transformedClasses, class_.superclass);
}
// If this is not a mixin application we don't need to make forwarding
// constructors in this class.
if (!class_.isMixinApplication) return;
transformedClasses.add(class_);
// Clone fields and methods from the mixin class.
var substitution = getSubstitutionMap(class_.mixedInType);
var cloner = new CloneVisitor(typeSubstitution: substitution);
for (var field in class_.mixin.fields) {
class_.addMember(cloner.clone(field));
}
for (var procedure in class_.mixin.procedures) {
class_.addMember(cloner.clone(procedure));
}
// For each generative constructor in the superclass we make a
// corresponding forwarding constructor in the subclass.
// Named mixin applications already have constructors, so only build the
// constructors for anonymous mixin applications.
if (class_.constructors.isEmpty) {
var superclassSubstitution = getSubstitutionMap(class_.supertype);
var superclassCloner =
new CloneVisitor(typeSubstitution: superclassSubstitution);
for (var superclassConstructor in class_.superclass.constructors) {
var forwardingConstructor =
buildForwardingConstructor(superclassCloner, superclassConstructor);
class_.constructors.add(forwardingConstructor..parent = class_);
}
}
// This class implements the mixin type.
class_.implementedTypes.add(class_.mixedInType);
// This class is now a normal class.
class_.mixedInType = null;
}
Constructor buildForwardingConstructor(
CloneVisitor cloner, Constructor superclassConstructor) {
var superFunction = superclassConstructor.function;
// We keep types and default values for the parameters but always mark the
// parameters as final (since we just forward them to the super
// constructor).
VariableDeclaration cloneVariable(VariableDeclaration variable) {
VariableDeclaration clone = cloner.clone(variable);
clone.isFinal = true;
return clone;
}
// Build a [FunctionNode] which has the same parameters as the one in the
// superclass constructor.
var positionalParameters =
superFunction.positionalParameters.map(cloneVariable).toList();
var namedParameters =
superFunction.namedParameters.map(cloneVariable).toList();
var function = new FunctionNode(new EmptyStatement(),
positionalParameters: positionalParameters,
namedParameters: namedParameters,
requiredParameterCount: superFunction.requiredParameterCount,
returnType: const VoidType());
// Build a [SuperInitializer] which takes all positional/named parameters
// and forward them to the super class constructor.
var positionalArguments = <Expression>[];
for (var variable in positionalParameters) {
positionalArguments.add(new VariableGet(variable));
}
var namedArguments = <NamedExpression>[];
for (var variable in namedParameters) {
namedArguments
.add(new NamedExpression(variable.name, new VariableGet(variable)));
}
var superInitializer = new SuperInitializer(superclassConstructor,
new Arguments(positionalArguments, named: namedArguments));
// Assemble the constructor.
return new Constructor(function,
name: superclassConstructor.name,
initializers: <Initializer>[superInitializer]);
}
}
class SuperCallResolutionTransformer extends Transformer {
final ClassHierarchy hierarchy;
final CoreTypes coreTypes;
final Class lookupClass;
Constructor _invocationMirrorConstructor; // cached
Procedure _listFrom; // cached
SuperCallResolutionTransformer(
this.hierarchy, this.coreTypes, this.lookupClass);
TreeNode visit(TreeNode node) => node.accept(this);
visitSuperPropertyGet(SuperPropertyGet node) {
Member target = hierarchy.getDispatchTarget(lookupClass, node.name);
if (target != null) {
return new DirectPropertyGet(new ThisExpression(), target);
} else {
return _callNoSuchMethod(node.name.name, new Arguments.empty(), node,
isGetter: true);
}
}
visitSuperPropertySet(SuperPropertySet node) {
Member target =
hierarchy.getDispatchTarget(lookupClass, node.name, setter: true);
if (target != null) {
return new DirectPropertySet(
new ThisExpression(), target, visit(node.value));
} else {
// Call has to return right-hand-side.
VariableDeclaration rightHandSide =
new VariableDeclaration.forValue(visit(node.value));
Expression result = _callNoSuchMethod(
node.name.name, new Arguments([new VariableGet(rightHandSide)]), node,
isSetter: true);
VariableDeclaration call = new VariableDeclaration.forValue(result);
return new Let(
rightHandSide, new Let(call, new VariableGet(rightHandSide)));
}
}
visitSuperMethodInvocation(SuperMethodInvocation node) {
Member target = hierarchy.getDispatchTarget(lookupClass, node.name);
Arguments visitedArguments = visit(node.arguments);
if (target is Procedure &&
!target.isAccessor &&
_callIsLegal(target.function, visitedArguments)) {
return new DirectMethodInvocation(
new ThisExpression(), target, visitedArguments)
..fileOffset = node.fileOffset;
} else if (target == null || (target is Procedure && !target.isAccessor)) {
// Target not found at all, or call was illegal.
return _callNoSuchMethod(node.name.name, visitedArguments, node,
isSuper: true);
} else if (target != null) {
return new MethodInvocation(
new DirectPropertyGet(new ThisExpression(), target),
new Name('call'),
visitedArguments)..fileOffset = node.fileOffset;
}
}
/// Create a call to no such method.
Expression _callNoSuchMethod(
String methodName, Arguments methodArguments, TreeNode node,
{isSuper: false, isGetter: false, isSetter: false}) {
Member noSuchMethod =
hierarchy.getDispatchTarget(lookupClass, new Name("noSuchMethod"));
String methodNameUsed = (isGetter)
? "get:$methodName"
: (isSetter) ? "set:$methodName" : methodName;
if (noSuchMethod != null &&
noSuchMethod.function.positionalParameters.length == 1 &&
noSuchMethod.function.namedParameters.isEmpty) {
// We have a correct noSuchMethod method.
ConstructorInvocation invocation = _createInvocation(
methodNameUsed, methodArguments, isSuper, new ThisExpression());
return new DirectMethodInvocation(
new ThisExpression(), noSuchMethod, new Arguments([invocation]))
..fileOffset = node.fileOffset;
} else {
// Incorrect noSuchMethod method: Call noSuchMethod on Object
// with Invocation of noSuchMethod as the method that did not exist.
noSuchMethod = hierarchy.getDispatchTarget(
hierarchy.rootClass, new Name("noSuchMethod"));
ConstructorInvocation invocation = _createInvocation(
methodNameUsed, methodArguments, isSuper, new ThisExpression());
ConstructorInvocation invocationPrime = _createInvocation("noSuchMethod",
new Arguments([invocation]), false, new ThisExpression());
return new DirectMethodInvocation(
new ThisExpression(), noSuchMethod, new Arguments([invocationPrime]))
..fileOffset = node.fileOffset;
}
}
/// Creates an "new _InvocationMirror(...)" invocation.
ConstructorInvocation _createInvocation(String methodName,
Arguments callArguments, bool isSuperInvocation, Expression receiver) {
if (_invocationMirrorConstructor == null) {
Class clazz = coreTypes.getCoreClass('dart:core', '_InvocationMirror');
_invocationMirrorConstructor = clazz.constructors[0];
}
// The _InvocationMirror constructor takes the following arguments:
// * Method name (a string).
// * An arguments descriptor - a list consisting of:
// - number of arguments (including receiver).
// - number of positional arguments (including receiver).
// - pairs (2 entries in the list) of
// * named arguments name.
// * index of named argument in arguments list.
// * A list of arguments, where the first ones are the positional arguments.
// * Whether it's a super invocation or not.
int numPositionalArguments = callArguments.positional.length + 1;
int numArguments = numPositionalArguments + callArguments.named.length;
List<Expression> argumentsDescriptor = [
new IntLiteral(numArguments),
new IntLiteral(numPositionalArguments)
];
List<Expression> arguments = [];
arguments.add(receiver);
for (Expression pos in callArguments.positional) {
arguments.add(pos);
}
for (NamedExpression named in callArguments.named) {
argumentsDescriptor.add(new StringLiteral(named.name));
argumentsDescriptor.add(new IntLiteral(arguments.length));
arguments.add(named.value);
}
return new ConstructorInvocation(
_invocationMirrorConstructor,
new Arguments([
new StringLiteral(methodName),
_fixedLengthList(argumentsDescriptor),
_fixedLengthList(arguments),
new BoolLiteral(isSuperInvocation)
]));
}
/// Create a fixed length list containing given expressions.
Expression _fixedLengthList(List<Expression> list) {
if (_listFrom == null) {
Class clazz = coreTypes.getCoreClass('dart:core', 'List');
_listFrom = clazz.procedures.firstWhere((c) => c.name.name == "from");
}
return new StaticInvocation(
_listFrom,
new Arguments([new ListLiteral(list)],
named: [new NamedExpression("growable", new BoolLiteral(false))],
types: [const DynamicType()]));
}
/// Check that a call to the targetFunction is legal given the arguments.
///
/// I.e. check that the number of positional parameters and the names of the
/// given named parameters represents a valid call to the function.
bool _callIsLegal(FunctionNode targetFunction, Arguments arguments) {
if ((targetFunction.requiredParameterCount > arguments.positional.length) ||
(targetFunction.positionalParameters.length <
arguments.positional.length)) {
// Given too few or too many positional arguments
return false;
}
// Do we give named that we don't take?
Set<String> givenNamed = arguments.named.map((v) => v.name).toSet();
Set<String> takenNamed =
targetFunction.namedParameters.map((v) => v.name).toSet();
givenNamed.removeAll(takenNamed);
return givenNamed.isEmpty;
}
}
class SuperInitializerResolutionTransformer extends InitializerVisitor {
final Class lookupClass;
SuperInitializerResolutionTransformer(this.lookupClass);
transformInitializers(List<Initializer> initializers) {
for (var initializer in initializers) {
initializer.accept(this);
}
}
visitSuperInitializer(SuperInitializer node) {
Constructor constructor = node.target;
if (constructor.enclosingClass != lookupClass) {
// If [node] refers to a constructor target which is not directly the
// superclass but some indirect base class then this is because classes in
// the middle are mixin applications. These mixin applications will have
// received a forwarding constructor which we are required to use instead.
for (var replacement in lookupClass.constructors) {
if (constructor.name == replacement.name) {
node.target = replacement;
return null;
}
}
throw new Exception(
'Could not find a generative constructor named "${constructor.name}" '
'in lookup class "${lookupClass.name}"!');
}
}
}