blob: 68ade75da4f03a3f9a3d8ff085d883da8f7aaea3 [file] [log] [blame]
// Copyright (c) 2018, 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 vm.transformations.mixin_deduplication;
import 'package:kernel/ast.dart';
/// De-duplication of identical mixin applications.
void transformComponent(Component component) {
final deduplicateMixins = new DeduplicateMixinsTransformer();
component.libraries.forEach(deduplicateMixins.visitLibrary);
}
class _DeduplicateMixinKey {
final Class _class;
_DeduplicateMixinKey(this._class);
@override
bool operator ==(Object other) {
if (other is _DeduplicateMixinKey) {
final thisClass = _class;
final otherClass = other._class;
if (identical(thisClass, otherClass)) {
return true;
}
// Do not deduplicate parameterized mixin applications.
if (thisClass.typeParameters.isNotEmpty ||
otherClass.typeParameters.isNotEmpty) {
return false;
}
// Deduplicate mixin applications with matching supertype, mixed-in type
// and implemented interfaces.
return thisClass.supertype == otherClass.supertype &&
thisClass.mixedInType == otherClass.mixedInType &&
listEquals(thisClass.implementedTypes, otherClass.implementedTypes);
}
return false;
}
@override
int get hashCode {
if (_class.typeParameters.isNotEmpty) {
return _class.hashCode;
}
int hash = 31;
hash = 0x3fffffff & (hash * 31 + _class.supertype.hashCode);
hash = 0x3fffffff & (hash * 31 + _class.mixedInType.hashCode);
for (var i in _class.implementedTypes) {
hash = 0x3fffffff & (hash * 31 + i.hashCode);
}
return hash;
}
}
class DeduplicateMixinsTransformer extends Transformer {
final _canonicalMixins = new Map<_DeduplicateMixinKey, Class>();
final _duplicatedMixins = new Map<Class, Class>();
@override
TreeNode visitLibrary(Library node) {
if (!node.isExternal) {
transformList(node.classes, this, node);
}
return node;
}
@override
TreeNode visitClass(Class c) {
if (c.enclosingLibrary.isExternal) {
return c;
}
if (_duplicatedMixins.containsKey(c)) {
return null; // Class was de-duplicated already, just remove it.
}
if (c.supertype != null) {
_transformSupertype(c);
}
if (!c.isAnonymousMixin) {
return c;
}
Class canonical =
_canonicalMixins.putIfAbsent(new _DeduplicateMixinKey(c), () => c);
assert(canonical != null);
if (canonical != c) {
c.canonicalName?.unbind();
_duplicatedMixins[c] = canonical;
// print('Replacing $c with $canonical');
return null; // Remove class.
}
return c;
}
void _transformSupertype(Class c) {
Class oldSuper = c.superclass;
if (oldSuper == null) {
return;
}
Class newSuper = visitClass(oldSuper);
if (newSuper == null) {
Class canonicalSuper = _duplicatedMixins[oldSuper];
assert(canonicalSuper != null);
c.supertype = new Supertype(canonicalSuper, c.supertype.typeArguments);
_correctForwardingConstructors(c, oldSuper, canonicalSuper);
}
}
@override
TreeNode defaultTreeNode(TreeNode node) =>
throw 'Unexpected node ${node.runtimeType}: $node';
}
/// Corrects synthetic forwarding constructors inserted by mixin resolution
/// after replacing superclass.
void _correctForwardingConstructors(Class c, Class oldSuper, Class newSuper) {
for (var constructor in c.constructors) {
if (constructor.isSynthetic) {
for (var initializer in constructor.initializers) {
if ((initializer is SuperInitializer) &&
initializer.target.enclosingClass == oldSuper) {
Constructor replacement = null;
for (var c in newSuper.constructors) {
if (c.name == initializer.target.name) {
replacement = c;
break;
}
}
if (replacement == null) {
throw 'Unable to find a replacement for $c in $newSuper';
}
initializer.target = replacement;
}
}
}
}
}