blob: 314803a6fb88abfb21f471db1a6931ec9ff2c4e2 [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 '../reference_from_index.dart';
import '../target/targets.dart' show Target;
import '../type_algebra.dart';
/// Transforms the libraries in [libraries].
/// Note that [referenceFromIndex] can be null, and is generally only needed
/// when (ultimately) called from the incremental compiler.
void transformLibraries(
Target targetInfo,
CoreTypes coreTypes,
ClassHierarchy hierarchy,
List<Library> libraries,
ReferenceFromIndex referenceFromIndex,
{bool doSuperResolution: true}) {
new MixinFullResolution(targetInfo, coreTypes, hierarchy,
doSuperResolution: doSuperResolution)
.transform(libraries, referenceFromIndex);
/// Replaces all mixin applications with regular classes, cloning all fields
/// and procedures from the mixed-in class, cloning all constructors from the
/// base class.
/// When [doSuperResolution] constructor parameter is [true], super calls
/// (as well as super initializer invocations) are also resolved to their
/// targets in this pass.
class MixinFullResolution {
final Target targetInfo;
final CoreTypes coreTypes;
/// The [ClassHierarchy] that should be used after applying this transformer.
/// If any class was updated, in general we need to create a new
/// [ClassHierarchy] instance, with new dispatch targets; or at least let
/// the existing instance know that some of its dispatch tables are not
/// valid anymore.
ClassHierarchy hierarchy;
// This enables `super` resolution transformation, which is not compatible
// with Dart VM's requirements around incremental compilation and has been
// moved to Dart VM itself.
final bool doSuperResolution;
MixinFullResolution(this.targetInfo, this.coreTypes, this.hierarchy,
{this.doSuperResolution: true});
/// Transform the given new [libraries]. It is expected that all other
/// libraries have already been transformed.
void transform(
List<Library> libraries, ReferenceFromIndex referenceFromIndex) {
if (libraries.isEmpty) return;
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 libraries) {
for (var class_ in library.classes) {
transformClass(libraries, processedClasses, transformedClasses, class_,
// We might need to update the class hierarchy.
hierarchy =
hierarchy.applyMemberChanges(transformedClasses, findDescendants: true);
if (!doSuperResolution) {
// Resolve all super call expressions and super initializers.
for (var library in libraries) {
for (var class_ in library.classes) {
for (var procedure in class_.procedures) {
if (procedure.containsSuperCalls) {
new SuperCallResolutionTransformer(
hierarchy, coreTypes, class_.superclass, targetInfo)
List<Library> librariesToBeTransformed,
Set<Class> processedClasses,
Set<Class> transformedClasses,
Class class_,
ReferenceFromIndex referenceFromIndex) {
// If this class was already handled then so were all classes up to the
// [Object] class.
if (!processedClasses.add(class_)) return;
Library enclosingLibrary = class_.enclosingLibrary;
if (!librariesToBeTransformed.contains(enclosingLibrary) &&
enclosingLibrary.importUri?.scheme == "dart") {
// If we're not asked to transform the platform libraries then we expect
// that they will be already transformed.
// Ensure super classes have been transformed before this class.
if (class_.superclass != null &&
class_.superclass.level.index >= ClassLevel.Mixin.index) {
transformClass(librariesToBeTransformed, processedClasses,
transformedClasses, class_.superclass, referenceFromIndex);
// If this is not a mixin application we don't need to make forwarding
// constructors in this class.
if (!class_.isMixinApplication) return;
if (class_.mixedInClass.level.index < ClassLevel.Mixin.index) {
throw new Exception(
'Class "${}" mixes in "${}" from'
' an external library. Did you forget --link?');
// Clone fields and methods from the mixin class.
var substitution = getSubstitutionMap(class_.mixedInType);
var cloner = new CloneVisitorWithMembers(typeSubstitution: substitution);
// When we copy a field from the mixed in class, we remove any
// forwarding-stub getters/setters from the superclass, but copy their
// covariance-bits onto the new field.
var nonSetters = <Name, Procedure>{};
var setters = <Name, Procedure>{};
for (var procedure in class_.procedures) {
if (procedure.isSetter) {
setters[] = procedure;
} else {
nonSetters[] = procedure;
IndexedLibrary indexedLibrary =
IndexedClass indexedClass = indexedLibrary?.lookupIndexedClass(;
for (var field in class_.mixin.fields) {
Field clone =
cloner.cloneField(field, indexedClass?.lookupField(;
Procedure setter = setters[];
if (setter != null) {
VariableDeclaration parameter =
clone.isGenericCovariantImpl = parameter.isGenericCovariantImpl;
// Existing procedures in the class should only be forwarding stubs.
// Replace them with methods from the mixin class if they have the same
// name, but keep their parameter flags.
int originalLength = class_.procedures.length;
for (var procedure in class_.mixin.procedures) {
// Forwarding stubs in the mixin class are used when calling through the
// mixin class's interface, not when calling through the mixin
// application. They should not be copied.
if (procedure.isForwardingStub) continue;
// Factory constructors are not cloned.
if (procedure.isFactory) continue;
// NoSuchMethod forwarders aren't cloned.
if (procedure.isNoSuchMethodForwarder) continue;
Procedure referenceFrom;
if (procedure.isSetter) {
referenceFrom =
} else {
referenceFrom =
// Linear search for a forwarding stub with the same name.
int originalIndex;
for (int i = 0; i < originalLength; ++i) {
var originalProcedure = class_.procedures[i];
if ( == &&
originalProcedure.kind == procedure.kind) {
FunctionNode src = originalProcedure.function;
FunctionNode dst = procedure.function;
if (src.positionalParameters.length !=
dst.positionalParameters.length ||
src.namedParameters.length != dst.namedParameters.length) {
// A compile time error has already occurred, but don't crash below,
// and don't add several procedures with the same name to the class.
continue outer;
originalIndex = i;
if (originalIndex != null) {
referenceFrom ??= class_.procedures[originalIndex];
Procedure clone = cloner.cloneProcedure(procedure, referenceFrom);
if (originalIndex != null) {
Procedure originalProcedure = class_.procedures[originalIndex];
FunctionNode src = originalProcedure.function;
FunctionNode dst = clone.function;
assert(src.typeParameters.length == dst.typeParameters.length);
for (int j = 0; j < src.typeParameters.length; ++j) {
dst.typeParameters[j].flags = src.typeParameters[j].flags;
for (int j = 0; j < src.positionalParameters.length; ++j) {
dst.positionalParameters[j].flags = src.positionalParameters[j].flags;
// TODO(kernel team): The named parameters are not sorted,
// this might not be correct.
for (int j = 0; j < src.namedParameters.length; ++j) {
dst.namedParameters[j].flags = src.namedParameters[j].flags;
class_.procedures[originalIndex] = clone;
} else {
// This class implements the mixin type. Also, backends rely on the fact
// that eliminated mixin is appended into the end of interfaces list.
// This class is now a normal class.
class_.mixedInType = null;
// Leave breadcrumbs for backends (e.g. for dart:mirrors implementation).
class_.isEliminatedMixin = true;
class SuperCallResolutionTransformer extends Transformer {
final ClassHierarchy hierarchy;
final CoreTypes coreTypes;
final Class lookupClass;
final Target targetInfo;
this.hierarchy, this.coreTypes, this.lookupClass, this.targetInfo);
TreeNode visit(TreeNode node) => node.accept(this);
visitSuperPropertyGet(SuperPropertyGet node) {
Member target = hierarchy.getDispatchTarget(lookupClass,;
if (target != null) {
return new DirectPropertyGet(new ThisExpression(), target)
..fileOffset = node.fileOffset;
} else {
return _callNoSuchMethod(, new Arguments.empty(), node,
isGetter: true, isSuper: true);
visitSuperPropertySet(SuperPropertySet node) {
Member target =
hierarchy.getDispatchTarget(lookupClass,, setter: true);
if (target != null) {
return new DirectPropertySet(
new ThisExpression(), target, visit(node.value))
..fileOffset = node.fileOffset;
} else {
// Call has to return right-hand-side.
VariableDeclaration rightHandSide =
new VariableDeclaration.forValue(visit(node.value));
Expression result = _callNoSuchMethod(, new Arguments([new VariableGet(rightHandSide)]), node,
isSetter: true, isSuper: 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,;
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(, visitedArguments, node,
isSuper: true);
} else {
return new MethodInvocation(
new DirectPropertyGet(new ThisExpression(), target),
new Name('call'),
..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(
coreTypes.objectClass, 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) {
return targetInfo.instantiateInvocation(
coreTypes, receiver, methodName, callArguments, -1, isSuperInvocation);
/// 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 = =>;
Set<String> takenNamed = =>;
return givenNamed.isEmpty;
class SuperInitializerResolutionTransformer extends InitializerVisitor {
final Class lookupClass;
transformInitializers(List<Initializer> initializers) {
for (var initializer in initializers) {
visitSuperInitializer(SuperInitializer node) {
Constructor constructor =;
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 ( == { = replacement;
return null;
throw new Exception(
'Could not find a generative constructor named "${}" '
'in lookup class "${}"!');