| // 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_, |
| referenceFromIndex); |
| } |
| } |
| |
| // 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) { |
| 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_, |
| 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. |
| 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, referenceFromIndex); |
| } |
| |
| // 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(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 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.name] = procedure; |
| } else { |
| nonSetters[procedure.name] = procedure; |
| } |
| } |
| |
| IndexedLibrary indexedLibrary = |
| referenceFromIndex?.lookupLibrary(enclosingLibrary); |
| IndexedClass indexedClass = indexedLibrary?.lookupIndexedClass(class_.name); |
| |
| for (var field in class_.mixin.fields) { |
| Field clone = |
| cloner.cloneField(field, indexedClass?.lookupField(field.name.name)); |
| 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 referenceFrom; |
| if (procedure.isSetter) { |
| referenceFrom = |
| indexedClass?.lookupProcedureSetter(procedure.name.name); |
| } else { |
| referenceFrom = |
| indexedClass?.lookupProcedureNotSetter(procedure.name.name); |
| } |
| |
| // 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.name == procedure.name && |
| 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; |
| break; |
| } |
| } |
| 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 { |
| 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}"!'); |
| } |
| } |
| } |