blob: c9b5c39f8761aa462aa7837e2f0833136c84fc72 [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) {
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);
}
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? 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;
}
}