| // 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) { |
| new MixinFullResolution(targetInfo, coreTypes, hierarchy) |
| .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. |
| 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; |
| |
| MixinFullResolution(this.targetInfo, this.coreTypes, this.hierarchy); |
| |
| /// 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); |
| } |
| |
| void 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) { |
| 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)); |
| |
| transformedClasses.add(class_); |
| |
| // Clone fields and methods from the mixin class. |
| var substitution = getSubstitutionMap(class_.mixedInType!); |
| var cloner = new CloneVisitorWithMembers(typeSubstitution: substitution); |
| |
| IndexedLibrary? indexedLibrary = |
| referenceFromIndex?.lookupLibrary(enclosingLibrary); |
| IndexedClass? indexedClass = |
| indexedLibrary?.lookupIndexedClass(class_.name); |
| |
| if (class_.mixin.fields.isNotEmpty) { |
| // 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) { |
| Reference? fieldReference = |
| indexedClass?.lookupFieldReference(field.name); |
| Reference? getterReference = |
| indexedClass?.lookupGetterReference(field.name); |
| Reference? setterReference = |
| indexedClass?.lookupSetterReference(field.name); |
| if (getterReference == null) { |
| getterReference = nonSetters[field.name]?.reference; |
| getterReference?.canonicalName?.unbind(); |
| } |
| if (setterReference == null) { |
| setterReference = setters[field.name]?.reference; |
| setterReference?.canonicalName?.unbind(); |
| } |
| Field clone = cloner.cloneField( |
| field, fieldReference, getterReference, setterReference); |
| Procedure? setter = setters[field.name]; |
| if (setter != null) { |
| setters.remove(field.name); |
| VariableDeclaration parameter = |
| setter.function.positionalParameters.first; |
| clone.isCovariantByDeclaration = parameter.isCovariantByDeclaration; |
| clone.isCovariantByClass = parameter.isCovariantByClass; |
| } |
| nonSetters.remove(field.name); |
| class_.addField(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) { |
| if (procedure.isSynthetic) continue; |
| // 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; |
| |
| Reference? reference; |
| if (procedure.isSetter) { |
| reference = indexedClass?.lookupSetterReference(procedure.name); |
| } else { |
| reference = indexedClass?.lookupGetterReference(procedure.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; |
| } |
| if (procedure.isAbstract && |
| (originalProcedure.stubKind == |
| ProcedureStubKind.ConcreteForwardingStub || |
| originalProcedure.stubKind == |
| ProcedureStubKind.ConcreteMixinStub)) { |
| // Don't replace concrete stubs with abstract methods. |
| originalProcedure.stubKind = ProcedureStubKind.Regular; |
| originalProcedure.stubTarget = null; |
| continue outer; |
| } |
| |
| originalIndex = i; |
| break; |
| } |
| } |
| if (originalIndex != null) { |
| reference ??= class_.procedures[originalIndex].reference; |
| } |
| Procedure clone = cloner.cloneProcedure(procedure, reference); |
| 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].isCovariantByDeclaration = |
| src.namedParameters[j].isCovariantByDeclaration; |
| dst.namedParameters[j].isCovariantByClass = |
| src.namedParameters[j].isCovariantByClass; |
| } |
| |
| class_.procedures[originalIndex] = clone; |
| clone.parent = class_; |
| } else { |
| class_.addProcedure(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; |
| } |
| } |