blob: f19320a47e5d1af20a0c0d3645f45e88c8aff8ea [file] [log] [blame]
// Copyright (c) 2017, 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.
import '../common.dart';
import '../common_elements.dart';
import '../elements/entities.dart';
import '../elements/types.dart' show InterfaceType;
import 'class_set.dart';
// TODO(johnniwinther): Move more methods from `JClosedWorld` to
// `ClassHierarchy`.
abstract class ClassHierarchy {
/// Returns [ClassHierarchyNode] for [cls] used to model the class hierarchies
/// of known classes.
///
/// This method is only provided for testing. For queries on classes, use the
/// methods defined in [JClosedWorld].
ClassHierarchyNode getClassHierarchyNode(ClassEntity cls);
/// Returns [ClassSet] for [cls] used to model the extends and implements
/// relations of known classes.
///
/// This method is only provided for testing. For queries on classes, use the
/// methods defined in [JClosedWorld].
ClassSet getClassSet(ClassEntity cls);
/// Returns `true` if [x] is a subtype of [y], that is, if [x] implements an
/// instance of [y].
bool isSubtypeOf(ClassEntity x, ClassEntity y);
/// Returns a [SubclassResult] for the subclasses that are contained in
/// the subclass/subtype sets of both [cls1] and [cls2].
///
/// Classes that are implied by included superclasses/supertypes are not
/// returned.
///
/// For instance for this hierarchy
///
/// class A {}
/// class B {}
/// class C implements A, B {}
/// class D extends C {}
///
/// the query
///
/// commonSubclasses(A, ClassQuery.SUBTYPE, B, ClassQuery.SUBTYPE)
///
/// return the set {C} because [D] is implied by [C].
SubclassResult commonSubclasses(
ClassEntity cls1, ClassQuery query1, ClassEntity cls2, ClassQuery query2);
/// Returns an iterable over the directly instantiated that implement [cls]
/// possibly including [cls] itself, if it is live.
Iterable<ClassEntity> subtypesOf(ClassEntity cls);
/// Returns an iterable over the live classes that extend [cls] including
/// [cls] itself.
Iterable<ClassEntity> subclassesOf(ClassEntity cls);
/// Applies [f] to each live class that implements [cls] _not_ including [cls]
/// itself.
void forEachStrictSubtypeOf(
ClassEntity cls, IterationStep f(ClassEntity cls));
}
class ClassHierarchyImpl implements ClassHierarchy {
final CommonElements _commonElements;
final Map<ClassEntity, ClassHierarchyNode> _classHierarchyNodes;
final Map<ClassEntity, ClassSet> _classSets;
ClassHierarchyImpl(
this._commonElements, this._classHierarchyNodes, this._classSets);
/// Returns [ClassHierarchyNode] for [cls] used to model the class hierarchies
/// of known classes.
///
/// This method is only provided for testing. For queries on classes, use the
/// methods defined in [JClosedWorld].
ClassHierarchyNode getClassHierarchyNode(ClassEntity cls) {
return _classHierarchyNodes[cls];
}
/// Returns [ClassSet] for [cls] used to model the extends and implements
/// relations of known classes.
///
/// This method is only provided for testing. For queries on classes, use the
/// methods defined in [JClosedWorld].
ClassSet getClassSet(ClassEntity cls) {
return _classSets[cls];
}
/// Returns `true` if [x] is a subtype of [y], that is, if [x] implements an
/// instance of [y].
bool isSubtypeOf(ClassEntity x, ClassEntity y) {
ClassSet classSet = _classSets[y];
assert(classSet != null,
failedAt(y, "No ClassSet for $y (${y.runtimeType}): ${_classSets}"));
ClassHierarchyNode classHierarchyNode = _classHierarchyNodes[x];
assert(classHierarchyNode != null,
failedAt(x, "No ClassHierarchyNode for $x"));
return classSet.hasSubtype(classHierarchyNode);
}
/// Return `true` if [x] is a (non-strict) subclass of [y].
bool isSubclassOf(ClassEntity x, ClassEntity y) {
return _classHierarchyNodes[y].hasSubclass(_classHierarchyNodes[x]);
}
/// Returns an iterable over the directly instantiated that implement [cls]
/// possibly including [cls] itself, if it is live.
Iterable<ClassEntity> subtypesOf(ClassEntity cls) {
ClassSet classSet = _classSets[cls];
if (classSet == null) {
return const <ClassEntity>[];
} else {
return classSet
.subtypesByMask(ClassHierarchyNode.EXPLICITLY_INSTANTIATED);
}
}
/// Returns an iterable over the directly instantiated classes that extend
/// [cls] possibly including [cls] itself, if it is live.
Iterable<ClassEntity> subclassesOf(ClassEntity cls) {
ClassHierarchyNode hierarchy = _classHierarchyNodes[cls];
if (hierarchy == null) return const <ClassEntity>[];
return hierarchy
.subclassesByMask(ClassHierarchyNode.EXPLICITLY_INSTANTIATED);
}
/// Returns an iterable over the directly instantiated that implement [cls]
/// _not_ including [cls].
Iterable<ClassEntity> strictSubtypesOf(ClassEntity cls) {
ClassSet classSet = _classSets[cls];
if (classSet == null) {
return const <ClassEntity>[];
} else {
return classSet.subtypesByMask(ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
}
/// Returns an iterable over the directly instantiated classes that extend
/// [cls] _not_ including [cls] itself.
Iterable<ClassEntity> strictSubclassesOf(ClassEntity cls) {
ClassHierarchyNode subclasses = _classHierarchyNodes[cls];
if (subclasses == null) return const <ClassEntity>[];
return subclasses.subclassesByMask(
ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
/// Applies [f] to each live class that extend [cls] _not_ including [cls]
/// itself.
void forEachStrictSubclassOf(
ClassEntity cls, IterationStep f(ClassEntity cls)) {
ClassHierarchyNode subclasses = _classHierarchyNodes[cls];
if (subclasses == null) return;
subclasses.forEachSubclass(f, ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
/// Applies [f] to each live class that implements [cls] _not_ including [cls]
/// itself.
void forEachStrictSubtypeOf(
ClassEntity cls, IterationStep f(ClassEntity cls)) {
ClassSet classSet = _classSets[cls];
if (classSet == null) return;
classSet.forEachSubtype(f, ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
SubclassResult commonSubclasses(ClassEntity cls1, ClassQuery query1,
ClassEntity cls2, ClassQuery query2) {
if (query1 == ClassQuery.EXACT && query2 == ClassQuery.EXACT) {
// Exact classes [cls1] and [cls2] must be identical to have any classes
// in common.
if (cls1 != cls2) {
return const SubclassResult.empty();
}
return new SubclassResult.exact(cls1);
} else if (query1 == ClassQuery.EXACT) {
if (query2 == ClassQuery.SUBCLASS) {
// Exact [cls1] must be a subclass of [cls2] to have any classes in
// common.
if (isSubclassOf(cls1, cls2)) {
return new SubclassResult.exact(cls1);
}
} else if (query2 == ClassQuery.SUBTYPE) {
// Exact [cls1] must be a subtype of [cls2] to have any classes in
// common.
if (isSubtypeOf(cls1, cls2)) {
return new SubclassResult.exact(cls1);
}
}
return const SubclassResult.empty();
} else if (query2 == ClassQuery.EXACT) {
if (query1 == ClassQuery.SUBCLASS) {
// Exact [cls2] must be a subclass of [cls1] to have any classes in
// common.
if (isSubclassOf(cls2, cls1)) {
return new SubclassResult.exact(cls2);
}
} else if (query1 == ClassQuery.SUBTYPE) {
// Exact [cls2] must be a subtype of [cls1] to have any classes in
// common.
if (isSubtypeOf(cls2, cls1)) {
return new SubclassResult.exact(cls2);
}
}
return const SubclassResult.empty();
} else if (query1 == ClassQuery.SUBCLASS && query2 == ClassQuery.SUBCLASS) {
// [cls1] must be a subclass of [cls2] or vice versa to have any classes
// in common.
if (cls1 == cls2 || isSubclassOf(cls1, cls2)) {
// The subclasses of [cls1] are contained within the subclasses of
// [cls2].
return new SubclassResult.subclass(cls1);
} else if (isSubclassOf(cls2, cls1)) {
// The subclasses of [cls2] are contained within the subclasses of
// [cls1].
return new SubclassResult.subclass(cls2);
}
return const SubclassResult.empty();
} else if (query1 == ClassQuery.SUBCLASS) {
if (isSubtypeOf(cls1, cls2)) {
// The subclasses of [cls1] are all subtypes of [cls2].
return new SubclassResult.subclass(cls1);
}
if (cls1 == _commonElements.objectClass) {
// Since [cls1] is `Object` all subtypes of [cls2] are contained within
// the subclasses of [cls1].
return new SubclassResult.subtype(cls2);
}
// Find all the root subclasses of [cls1] of that implement [cls2].
//
// For this hierarchy:
//
// class I {}
// class A {}
// class B extends A implements I {}
// class C extends B {}
// class D extends A implements I {}
//
// the common subclasses of "subclass of A" and "subtype of I" returns
// "subclasses of {B, D}". The inclusion of class `C` is implied because
// it is a subclass of `B`.
List<ClassEntity> classes = <ClassEntity>[];
forEachStrictSubclassOf(cls1, (ClassEntity subclass) {
if (isSubtypeOf(subclass, cls2)) {
classes.add(subclass);
// Skip subclasses of [subclass]; they all implement [cls2] by
// inheritance and are included in the subclasses of [subclass].
return IterationStep.SKIP_SUBCLASSES;
}
return IterationStep.CONTINUE;
});
return new SubclassResult.subclasses(classes);
} else if (query2 == ClassQuery.SUBCLASS) {
if (isSubtypeOf(cls2, cls1)) {
// The subclasses of [cls2] are all subtypes of [cls1].
return new SubclassResult.subclass(cls2);
}
if (cls2 == _commonElements.objectClass) {
// Since [cls2] is `Object` all subtypes of [cls1] are contained within
// the subclasses of [cls2].
return new SubclassResult.subtype(cls1);
}
// Find all the root subclasses of [cls2] of that implement [cls1].
List<ClassEntity> classes = <ClassEntity>[];
forEachStrictSubclassOf(cls2, (ClassEntity subclass) {
if (isSubtypeOf(subclass, cls1)) {
classes.add(subclass);
// Skip subclasses of [subclass]; they all implement [cls1] by
// inheritance and are included in the subclasses of [subclass].
return IterationStep.SKIP_SUBCLASSES;
}
return IterationStep.CONTINUE;
});
return new SubclassResult.subclasses(classes);
} else {
if (cls1 == cls2 || isSubtypeOf(cls1, cls2)) {
// The subtypes of [cls1] are contained within the subtypes of [cls2].
return new SubclassResult.subtype(cls1);
} else if (isSubtypeOf(cls2, cls1)) {
// The subtypes of [cls2] are contained within the subtypes of [cls1].
return new SubclassResult.subtype(cls2);
}
// Find all the root subclasses of [cls1] of that implement [cls2].
//
// For this hierarchy:
//
// class I {}
// class A {}
// class B extends A implements I {}
// class C extends B {}
// class D extends A implements I {}
// class E implements B {}
// class F extends E {}
//
// the common subclasses of "subtype of A" and "subtype of I" returns
// "subclasses of {B, D, E}". The inclusion of classes `C` and `F` is
// implied because they are subclasses of `B` and `E`, respectively.
List<ClassEntity> classes = <ClassEntity>[];
forEachStrictSubtypeOf(cls1, (ClassEntity subclass) {
if (isSubtypeOf(subclass, cls2)) {
classes.add(subclass);
// Skip subclasses of [subclass]; they all implement [cls2] by
// inheritance and are included in the subclasses of [subclass].
return IterationStep.SKIP_SUBCLASSES;
}
return IterationStep.CONTINUE;
});
return new SubclassResult.subclasses(classes);
}
}
}
class ClassHierarchyBuilder {
// We keep track of subtype and subclass relationships in four
// distinct sets to make class hierarchy analysis faster.
final Map<ClassEntity, ClassHierarchyNode> classHierarchyNodes =
<ClassEntity, ClassHierarchyNode>{};
final Map<ClassEntity, ClassSet> classSets = <ClassEntity, ClassSet>{};
final Map<ClassEntity, Set<ClassEntity>> mixinUses =
new Map<ClassEntity, Set<ClassEntity>>();
final CommonElements _commonElements;
final ClassQueries _classQueries;
ClassHierarchyBuilder(this._commonElements, this._classQueries);
void registerClass(ClassEntity cls) {
_ensureClassSet(_classQueries.getDeclaration(cls));
}
ClassHierarchyNode _ensureClassHierarchyNode(ClassEntity cls) {
assert(_classQueries.checkClass(cls));
return classHierarchyNodes.putIfAbsent(cls, () {
ClassHierarchyNode parentNode;
ClassEntity superclass = _classQueries.getSuperClass(cls);
if (superclass != null) {
parentNode = _ensureClassHierarchyNode(superclass);
}
return new ClassHierarchyNode(
parentNode, cls, _classQueries.getHierarchyDepth(cls));
});
}
ClassSet _ensureClassSet(ClassEntity cls) {
assert(_classQueries.checkClass(cls));
return classSets.putIfAbsent(cls, () {
ClassHierarchyNode node = _ensureClassHierarchyNode(cls);
ClassSet classSet = new ClassSet(node);
for (InterfaceType type in _classQueries.getSupertypes(cls)) {
// TODO(johnniwinther): Optimization: Avoid adding [cls] to
// superclasses.
ClassSet subtypeSet = _ensureClassSet(type.element);
subtypeSet.addSubtype(node);
}
ClassEntity appliedMixin = _classQueries.getAppliedMixin(cls);
while (appliedMixin != null) {
// TODO(johnniwinther): Use the data stored in [ClassSet].
registerMixinUse(cls, appliedMixin);
ClassSet mixinSet = _ensureClassSet(appliedMixin);
mixinSet.addMixinApplication(node);
// In case of
//
// class A {}
// class B = Object with A;
// class C = Object with B;
//
// we need to register that C not only mixes in B but also A.
appliedMixin = _classQueries.getAppliedMixin(appliedMixin);
}
return classSet;
});
}
void _updateSuperClassHierarchyNodeForClass(ClassHierarchyNode node) {
// Ensure that classes implicitly implementing `Function` are in its
// subtype set.
ClassEntity cls = node.cls;
if (cls != _commonElements.functionClass &&
_classQueries.implementsFunction(cls)) {
ClassSet subtypeSet = _ensureClassSet(_commonElements.functionClass);
subtypeSet.addSubtype(node);
}
if (!node.isInstantiated && node.parentNode != null) {
_updateSuperClassHierarchyNodeForClass(node.parentNode);
}
}
void updateClassHierarchyNodeForClass(ClassEntity cls,
{bool directlyInstantiated: false, bool abstractlyInstantiated: false}) {
ClassHierarchyNode node = _ensureClassSet(cls).node;
_updateSuperClassHierarchyNodeForClass(node);
if (directlyInstantiated) {
node.isDirectlyInstantiated = true;
}
if (abstractlyInstantiated) {
node.isAbstractlyInstantiated = true;
}
}
void registerMixinUse(ClassEntity mixinApplication, ClassEntity mixin) {
// TODO(johnniwinther): Add map restricted to live classes.
// We don't support patch classes as mixin.
Set<ClassEntity> users =
mixinUses.putIfAbsent(mixin, () => new Set<ClassEntity>());
users.add(mixinApplication);
}
bool _isSubtypeOf(ClassEntity x, ClassEntity y) {
assert(
classSets.containsKey(x), "ClassSet for $x has not been computed yet.");
ClassSet classSet = classSets[y];
assert(classSet != null,
failedAt(y, "No ClassSet for $y (${y.runtimeType}): ${classSets}"));
ClassHierarchyNode classHierarchyNode = classHierarchyNodes[x];
assert(classHierarchyNode != null,
failedAt(x, "No ClassHierarchyNode for $x"));
return classSet.hasSubtype(classHierarchyNode);
}
bool isInheritedInSubtypeOf(ClassEntity x, ClassEntity y) {
ClassSet classSet = classSets[x];
assert(classSet != null,
failedAt(x, "No ClassSet for $x (${x.runtimeType}): ${classSets}"));
if (_isSubtypeOf(x, y)) {
// [x] implements [y] itself, possible through supertypes.
return true;
}
/// Returns `true` if any live subclass of [node] implements [y].
bool subclassImplements(ClassHierarchyNode node, {bool strict}) {
return node.anySubclass((ClassEntity z) => _isSubtypeOf(z, y),
ClassHierarchyNode.INSTANTIATED,
strict: strict);
}
if (subclassImplements(classSet.node, strict: true)) {
// A subclass of [x] implements [y].
return true;
}
for (ClassHierarchyNode mixinApplication
in classSet.mixinApplicationNodes) {
if (subclassImplements(mixinApplication, strict: false)) {
// A subclass of [mixinApplication] implements [y].
return true;
}
}
return false;
}
}
abstract class ClassQueries {
bool checkClass(covariant ClassEntity cls);
bool validateClass(covariant ClassEntity cls);
/// Returns the declaration of [cls].
ClassEntity getDeclaration(covariant ClassEntity cls);
/// Returns the class mixed into [cls] if any.
// TODO(johnniwinther): Replace this by a `getAppliedMixins` function that
// return transitively mixed in classes like in:
// class A {}
// class B = Object with A;
// class C = Object with B;
ClassEntity getAppliedMixin(covariant ClassEntity cls);
/// Returns the hierarchy depth of [cls].
int getHierarchyDepth(covariant ClassEntity cls);
/// Returns `true` if [cls] implements `Function` either explicitly or through
/// a `call` method.
bool implementsFunction(covariant ClassEntity cls);
/// Returns the superclass of [cls] if any.
ClassEntity getSuperClass(covariant ClassEntity cls);
/// Returns all supertypes of [cls].
Iterable<InterfaceType> getSupertypes(covariant ClassEntity cls);
}
/// Enum values defining subset of classes included in queries.
enum ClassQuery {
/// Only the class itself is included.
EXACT,
/// The class and all subclasses (transitively) are included.
SUBCLASS,
/// The class and all classes that implement or subclass it (transitively)
/// are included.
SUBTYPE,
}
/// Result computed in [ClassHierarchy.commonSubclasses].
class SubclassResult {
/// The classes in the result set. The classes are always disjoint wrt. the
/// interpretation of [query].
final List<ClassEntity> classes;
/// How [classes] should be interpreted: If `ClassQuery.EXACT`, only the
/// classes in [classes] are on the result set. If `ClassQuery.SUBCLASS`,
/// non-strict subclasses of the classes in [classes] are in the result set.
/// If `ClassQuery.SUBTYPE`, non-strict subtypes of the classes in [classes]
/// are in the result set.
final ClassQuery query;
/// Creates the empty result set.
const SubclassResult.empty()
: query = ClassQuery.EXACT,
classes = const <ClassEntity>[];
/// Creates the single set of [cls].
SubclassResult.exact(ClassEntity cls)
: query = ClassQuery.EXACT,
classes = <ClassEntity>[cls];
/// Creates the set of subclasses of [cls].
SubclassResult.subclass(ClassEntity cls)
: query = ClassQuery.SUBCLASS,
classes = <ClassEntity>[cls];
/// Creates the set of subtypes of [cls].
SubclassResult.subtype(ClassEntity cls)
: query = ClassQuery.SUBTYPE,
classes = <ClassEntity>[cls];
/// Creates the set of classes that are subclasses of a class in [classes].
SubclassResult.subclasses(this.classes) : query = ClassQuery.SUBCLASS;
SubclassResult.internal(this.query, this.classes);
String toString() => 'SubclassResult($query,$classes)';
}