// 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 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 libraries, ReferenceFromIndex? referenceFromIndex) { if (libraries.isEmpty) return; var transformedClasses = new Set(); // Desugar all mixin application classes by copying in fields/methods from // the mixin and constructors from the base class. var processedClasses = new Set(); 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); } transformClass( List librariesToBeTransformed, Set processedClasses, Set 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 = {}; var setters = {}; 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? 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, getterReference, setterReference); Procedure? setter = setters[field.name]; if (setter != null) { setters.remove(field.name); VariableDeclaration parameter = setter.function.positionalParameters.first; clone.isCovariant = parameter.isCovariant; clone.isGenericCovariantImpl = parameter.isGenericCovariantImpl; } 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; } 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].isCovariant = src.namedParameters[j].isCovariant; dst.namedParameters[j].isGenericCovariantImpl = src.namedParameters[j].isGenericCovariantImpl; } 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; } }