b990c503c8
Change-Id: Ie351a9419a1957f777f229c6caf8515a90c0e6d7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96649 Reviewed-by: Aske Simon Christensen <askesc@google.com>
379 lines
15 KiB
Dart
379 lines
15 KiB
Dart
// 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 '../target/targets.dart' show Target;
|
|
import '../type_algebra.dart';
|
|
|
|
void transformLibraries(Target targetInfo, CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy, List<Library> libraries,
|
|
{bool doSuperResolution: true}) {
|
|
new MixinFullResolution(targetInfo, coreTypes, hierarchy,
|
|
doSuperResolution: doSuperResolution)
|
|
.transform(libraries);
|
|
}
|
|
|
|
/// 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) {
|
|
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) {
|
|
if (library.isExternal) continue;
|
|
|
|
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) {
|
|
return;
|
|
}
|
|
// Resolve all super call expressions and super initializers.
|
|
for (var library in libraries) {
|
|
if (library.isExternal) continue;
|
|
|
|
for (var class_ in library.classes) {
|
|
for (var procedure in class_.procedures) {
|
|
if (procedure.containsSuperCalls) {
|
|
new SuperCallResolutionTransformer(
|
|
hierarchy, coreTypes, class_.superclass, targetInfo)
|
|
.visit(procedure);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
transformClass(
|
|
List<Library> librariesToBeTransformed,
|
|
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;
|
|
|
|
if (!librariesToBeTransformed.contains(class_.enclosingLibrary) &&
|
|
class_.enclosingLibrary.importUri?.scheme == "dart") {
|
|
// If we're not asked to transform the platform libraries then we expect
|
|
// that they will be already transformed.
|
|
return;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// If this is not a mixin application we don't need to make forwarding
|
|
// constructors in this class.
|
|
if (!class_.isMixinApplication) return;
|
|
assert(librariesToBeTransformed.contains(class_.enclosingLibrary));
|
|
|
|
if (class_.mixedInClass.level.index < ClassLevel.Mixin.index) {
|
|
throw new Exception(
|
|
'Class "${class_.name}" mixes in "${class_.mixedInClass.name}" from'
|
|
' an external library. Did you forget --link?');
|
|
}
|
|
|
|
transformedClasses.add(class_);
|
|
|
|
// Clone fields and methods from the mixin class.
|
|
var substitution = getSubstitutionMap(class_.mixedInType);
|
|
var cloner = new CloneVisitor(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.name] = procedure;
|
|
} else {
|
|
nonSetters[procedure.name] = procedure;
|
|
}
|
|
}
|
|
for (var field in class_.mixin.fields) {
|
|
Field clone = cloner.clone(field);
|
|
Procedure setter = setters[field.name];
|
|
if (setter != null) {
|
|
setters.remove(field.name);
|
|
VariableDeclaration parameter =
|
|
setter.function.positionalParameters.first;
|
|
clone.isGenericCovariantImpl = parameter.isGenericCovariantImpl;
|
|
}
|
|
nonSetters.remove(field.name);
|
|
class_.addMember(clone);
|
|
}
|
|
class_.procedures.clear();
|
|
class_.procedures..addAll(nonSetters.values)..addAll(setters.values);
|
|
|
|
// 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;
|
|
outer:
|
|
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 clone = cloner.clone(procedure);
|
|
// Linear search for a forwarding stub with the same name.
|
|
for (int i = 0; i < originalLength; ++i) {
|
|
var originalProcedure = class_.procedures[i];
|
|
if (originalProcedure.name == clone.name &&
|
|
originalProcedure.kind == clone.kind) {
|
|
FunctionNode src = originalProcedure.function;
|
|
FunctionNode dst = clone.function;
|
|
|
|
if (src.positionalParameters.length !=
|
|
dst.positionalParameters.length ||
|
|
src.namedParameters.length != dst.namedParameters.length) {
|
|
// A compile time error has already occured, but don't crash below,
|
|
// and don't add several procedures with the same name to the class.
|
|
continue outer;
|
|
}
|
|
|
|
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[i] = clone;
|
|
continue outer;
|
|
}
|
|
}
|
|
class_.addMember(clone);
|
|
}
|
|
assert(class_.constructors.isNotEmpty);
|
|
|
|
// This class implements the mixin type. Also, backends rely on the fact
|
|
// that eliminated mixin is appended into the end of interfaces list.
|
|
class_.implementedTypes.add(class_.mixedInType);
|
|
|
|
// 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;
|
|
|
|
SuperCallResolutionTransformer(
|
|
this.hierarchy, this.coreTypes, this.lookupClass, this.targetInfo);
|
|
|
|
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)
|
|
..fileOffset = node.fileOffset;
|
|
} else {
|
|
return _callNoSuchMethod(node.name.name, new Arguments.empty(), node,
|
|
isGetter: true, isSuper: 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))
|
|
..fileOffset = node.fileOffset;
|
|
} 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, 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, 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 {
|
|
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(
|
|
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 = 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}"!');
|
|
}
|
|
}
|
|
}
|