Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dart2js.world; | 5 library dart2js.world; |
| 6 | 6 |
| 7 import 'closure.dart' show SynthesizedCallMethodElementX; | 7 import 'closure.dart' show SynthesizedCallMethodElementX; |
| 8 import 'common/backend_api.dart' show BackendClasses; | 8 import 'common/backend_api.dart' show BackendClasses; |
| 9 import 'common.dart'; | 9 import 'common.dart'; |
| 10 import 'compiler.dart' show Compiler; | 10 import 'compiler.dart' show Compiler; |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 48 /// Returns `true` if [cls] is either directly or indirectly instantiated. | 48 /// Returns `true` if [cls] is either directly or indirectly instantiated. |
| 49 bool isInstantiated(ClassElement cls); | 49 bool isInstantiated(ClassElement cls); |
| 50 | 50 |
| 51 /// Returns `true` if [cls] is directly instantiated. | 51 /// Returns `true` if [cls] is directly instantiated. |
| 52 bool isDirectlyInstantiated(ClassElement cls); | 52 bool isDirectlyInstantiated(ClassElement cls); |
| 53 | 53 |
| 54 /// Returns `true` if [cls] is indirectly instantiated, that is through a | 54 /// Returns `true` if [cls] is indirectly instantiated, that is through a |
| 55 /// subclass. | 55 /// subclass. |
| 56 bool isIndirectlyInstantiated(ClassElement cls); | 56 bool isIndirectlyInstantiated(ClassElement cls); |
| 57 | 57 |
| 58 /// Returns `true` if [cls] is abstract and thus can only be instantiated | |
| 59 /// through subclasses. | |
| 60 bool isAbstract(ClassElement cls); | |
| 61 | |
| 58 /// Returns `true` if [cls] is implemented by an instantiated class. | 62 /// Returns `true` if [cls] is implemented by an instantiated class. |
| 59 bool isImplemented(ClassElement cls); | 63 bool isImplemented(ClassElement cls); |
| 60 | 64 |
| 61 /// Return `true` if [x] is a subclass of [y]. | 65 /// Return `true` if [x] is a subclass of [y]. |
| 62 bool isSubclassOf(ClassElement x, ClassElement y); | 66 bool isSubclassOf(ClassElement x, ClassElement y); |
| 63 | 67 |
| 64 /// Returns `true` if [x] is a subtype of [y], that is, if [x] implements an | 68 /// Returns `true` if [x] is a subtype of [y], that is, if [x] implements an |
| 65 /// instance of [y]. | 69 /// instance of [y]. |
| 66 bool isSubtypeOf(ClassElement x, ClassElement y); | 70 bool isSubtypeOf(ClassElement x, ClassElement y); |
| 67 | 71 |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 149 /// Returns `true` if [cls] or any superclass mixes in [mixin]. | 153 /// Returns `true` if [cls] or any superclass mixes in [mixin]. |
| 150 bool isSubclassOfMixinUseOf(ClassElement cls, ClassElement mixin); | 154 bool isSubclassOfMixinUseOf(ClassElement cls, ClassElement mixin); |
| 151 | 155 |
| 152 /// Returns `true` if every subtype of [x] is a subclass of [y] or a subclass | 156 /// Returns `true` if every subtype of [x] is a subclass of [y] or a subclass |
| 153 /// of a mixin application of [y]. | 157 /// of a mixin application of [y]. |
| 154 bool everySubtypeIsSubclassOfOrMixinUseOf(ClassElement x, ClassElement y); | 158 bool everySubtypeIsSubclassOfOrMixinUseOf(ClassElement x, ClassElement y); |
| 155 | 159 |
| 156 /// Returns `true` if any subclass of [superclass] implements [type]. | 160 /// Returns `true` if any subclass of [superclass] implements [type]. |
| 157 bool hasAnySubclassThatImplements(ClassElement superclass, ClassElement type); | 161 bool hasAnySubclassThatImplements(ClassElement superclass, ClassElement type); |
| 158 | 162 |
| 163 /// Returns `true` if a call of [selector] on [cls] and/or subclasses/subtypes | |
| 164 /// need noSuchMethod handling. | |
| 165 /// | |
| 166 /// If the receiver is guaranteed to have a member that matches what we're | |
| 167 /// looking for, there's no need to introduce a noSuchMethod handler. It will | |
| 168 /// never be called. | |
| 169 /// | |
| 170 /// As an example, consider this class hierarchy: | |
| 171 /// | |
| 172 /// A <-- noSuchMethod | |
| 173 /// / \ | |
| 174 /// C B <-- foo | |
| 175 /// | |
| 176 /// If we know we're calling foo on an object of type B we don't have to worry | |
| 177 /// about the noSuchMethod method in A because objects of type B implement | |
| 178 /// foo. On the other hand, if we end up calling foo on something of type C we | |
| 179 /// have to add a handler for it. | |
| 180 /// | |
| 181 /// If the holders of all user-defined noSuchMethod implementations that might | |
| 182 /// be applicable to the receiver type have a matching member for the current | |
| 183 /// name and selector, we avoid introducing a noSuchMethod handler. | |
| 184 /// | |
| 185 /// As an example, consider this class hierarchy: | |
| 186 /// | |
| 187 /// A <-- foo | |
| 188 /// / \ | |
| 189 /// noSuchMethod --> B C <-- bar | |
| 190 /// | | | |
| 191 /// C D <-- noSuchMethod | |
| 192 /// | |
| 193 /// When calling foo on an object of type A, we know that the implementations | |
| 194 /// of noSuchMethod are in the classes B and D that also (indirectly) | |
| 195 /// implement foo, so we do not need a handler for it. | |
| 196 /// | |
| 197 /// If we're calling bar on an object of type D, we don't need the handler | |
| 198 /// either because all objects of type D implement bar through inheritance. | |
| 199 /// | |
| 200 /// If we're calling bar on an object of type A we do need the handler because | |
| 201 /// we may have to call B.noSuchMethod since B does not implement bar. | |
| 202 bool needsNoSuchMethod(ClassElement cls, Selector selector, ClassMask mask); | |
| 203 | |
| 204 /// Returns whether [element] will be the one used at runtime when being | |
| 205 /// invoked on an instance of [cls]. [selector] is used to ensure library | |
| 206 /// privacy is taken into account. | |
| 207 bool hasElementIn(ClassElement cls, Selector selector, Element element); | |
| 208 | |
| 159 /// Returns [ClassHierarchyNode] for [cls] used to model the class hierarchies | 209 /// Returns [ClassHierarchyNode] for [cls] used to model the class hierarchies |
| 160 /// of known classes. | 210 /// of known classes. |
| 161 /// | 211 /// |
| 162 /// This method is only provided for testing. For queries on classes, use the | 212 /// This method is only provided for testing. For queries on classes, use the |
| 163 /// methods defined in [ClosedWorld]. | 213 /// methods defined in [ClosedWorld]. |
| 164 ClassHierarchyNode getClassHierarchyNode(ClassElement cls); | 214 ClassHierarchyNode getClassHierarchyNode(ClassElement cls); |
| 165 | 215 |
| 166 /// Returns [ClassSet] for [cls] used to model the extends and implements | 216 /// Returns [ClassSet] for [cls] used to model the extends and implements |
| 167 /// relations of known classes. | 217 /// relations of known classes. |
| 168 /// | 218 /// |
| 169 /// This method is only provided for testing. For queries on classes, use the | 219 /// This method is only provided for testing. For queries on classes, use the |
| 170 /// methods defined in [ClosedWorld]. | 220 /// methods defined in [ClosedWorld]. |
| 171 ClassSet getClassSet(ClassElement cls); | 221 ClassSet getClassSet(ClassElement cls); |
| 172 | 222 |
| 173 // TODO(johnniwinther): Find a better strategy for caching these. | 223 /// Return the cached mask for [base] with the given flags, or |
| 174 @deprecated | 224 /// calls [createMask] to create the mask and cache it. |
| 175 List<Map<ClassElement, TypeMask>> get canonicalizedTypeMasks; | 225 // TODO(johnniwinther): Find a better strategy for caching these? |
| 226 TypeMask getCachedMask(ClassElement base, int flags, TypeMask createMask()); | |
| 176 | 227 |
| 177 /// Returns the [FunctionSet] containing all live functions in the closed | 228 /// Returns the [FunctionSet] containing all live functions in the closed |
| 178 /// world. | 229 /// world. |
| 179 FunctionSet get allFunctions; | 230 FunctionSet get allFunctions; |
| 180 | 231 |
| 181 /// Returns `true` if the field [element] is known to be effectively final. | 232 /// Returns `true` if the field [element] is known to be effectively final. |
| 182 bool fieldNeverChanges(Element element); | 233 bool fieldNeverChanges(Element element); |
| 183 | 234 |
| 184 /// Extends the receiver type [mask] for calling [selector] to take live | 235 /// Extends the receiver type [mask] for calling [selector] to take live |
| 185 /// `noSuchMethod` handlers into account. | 236 /// `noSuchMethod` handlers into account. |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 267 | 318 |
| 268 ClosedWorld closeWorld(); | 319 ClosedWorld closeWorld(); |
| 269 | 320 |
| 270 /// Returns an iterable over all mixin applications that mixin [cls]. | 321 /// Returns an iterable over all mixin applications that mixin [cls]. |
| 271 Iterable<MixinApplicationElement> allMixinUsesOf(ClassElement cls); | 322 Iterable<MixinApplicationElement> allMixinUsesOf(ClassElement cls); |
| 272 } | 323 } |
| 273 | 324 |
| 274 class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { | 325 class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { |
| 275 bool _closed = false; | 326 bool _closed = false; |
| 276 | 327 |
| 328 TypeMask getCachedMask(ClassElement base, int flags, TypeMask createMask()) { | |
| 329 Map<ClassElement, TypeMask> cachedMasks = _canonicalizedTypeMasks[flags]; | |
| 330 if (cachedMasks == null) { | |
| 331 _canonicalizedTypeMasks[flags] = cachedMasks = <ClassElement, TypeMask>{}; | |
| 332 } | |
|
Siggi Cherem (dart-lang)
2016/10/14 16:49:57
we can also replace the 3 lines above with:
var
Johnni Winther
2016/10/17 07:57:40
Done(-ish).
| |
| 333 return cachedMasks.putIfAbsent( | |
| 334 base, () => new FlatTypeMask.internal(base, flags)); | |
|
Siggi Cherem (dart-lang)
2016/10/14 16:49:57
use createMask here
Johnni Winther
2016/10/17 07:57:39
Done.
| |
| 335 } | |
| 336 | |
| 277 /// Cache of [FlatTypeMask]s grouped by the 8 possible values of the | 337 /// Cache of [FlatTypeMask]s grouped by the 8 possible values of the |
| 278 /// `FlatTypeMask.flags` property. | 338 /// `FlatTypeMask.flags` property. |
| 279 List<Map<ClassElement, TypeMask>> canonicalizedTypeMasks = | 339 final List<Map<ClassElement, TypeMask>> _canonicalizedTypeMasks = |
| 280 new List<Map<ClassElement, TypeMask>>.filled(8, null); | 340 new List<Map<ClassElement, TypeMask>>.filled(8, null); |
| 281 | 341 |
| 282 bool checkInvariants(ClassElement cls, {bool mustBeInstantiated: true}) { | 342 bool checkInvariants(ClassElement cls, {bool mustBeInstantiated: true}) { |
| 283 return invariant(cls, cls.isDeclaration, | 343 return invariant(cls, cls.isDeclaration, |
| 284 message: '$cls must be the declaration.') && | 344 message: '$cls must be the declaration.') && |
| 285 invariant(cls, cls.isResolved, | 345 invariant(cls, cls.isResolved, |
| 286 message: | 346 message: |
| 287 '$cls must be resolved.') /* && | 347 '$cls must be resolved.') /* && |
| 288 // TODO(johnniwinther): Reinsert this or similar invariant. | 348 // TODO(johnniwinther): Reinsert this or similar invariant. |
| 289 (!mustBeInstantiated || | 349 (!mustBeInstantiated || |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 335 return node != null && node.isDirectlyInstantiated; | 395 return node != null && node.isDirectlyInstantiated; |
| 336 } | 396 } |
| 337 | 397 |
| 338 @override | 398 @override |
| 339 bool isIndirectlyInstantiated(ClassElement cls) { | 399 bool isIndirectlyInstantiated(ClassElement cls) { |
| 340 assert(isClosed); | 400 assert(isClosed); |
| 341 ClassHierarchyNode node = _classHierarchyNodes[cls.declaration]; | 401 ClassHierarchyNode node = _classHierarchyNodes[cls.declaration]; |
| 342 return node != null && node.isIndirectlyInstantiated; | 402 return node != null && node.isIndirectlyInstantiated; |
| 343 } | 403 } |
| 344 | 404 |
| 405 @override | |
| 406 bool isAbstract(ClassElement cls) => cls.isAbstract; | |
| 407 | |
| 345 /// Returns `true` if [cls] is implemented by an instantiated class. | 408 /// Returns `true` if [cls] is implemented by an instantiated class. |
| 346 bool isImplemented(ClassElement cls) { | 409 bool isImplemented(ClassElement cls) { |
| 347 assert(isClosed); | 410 assert(isClosed); |
| 348 return _compiler.resolverWorld.isImplemented(cls); | 411 return _compiler.resolverWorld.isImplemented(cls); |
| 349 } | 412 } |
| 350 | 413 |
| 351 /// Returns an iterable over the directly instantiated classes that extend | 414 /// Returns an iterable over the directly instantiated classes that extend |
| 352 /// [cls] possibly including [cls] itself, if it is live. | 415 /// [cls] possibly including [cls] itself, if it is live. |
| 353 Iterable<ClassElement> subclassesOf(ClassElement cls) { | 416 Iterable<ClassElement> subclassesOf(ClassElement cls) { |
| 354 assert(isClosed); | 417 assert(isClosed); |
| (...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 615 /// Returns `true` if any live class that mixes in [mixin] is also a subclass | 678 /// Returns `true` if any live class that mixes in [mixin] is also a subclass |
| 616 /// of [superclass]. | 679 /// of [superclass]. |
| 617 bool hasAnySubclassThatMixes(ClassElement superclass, ClassElement mixin) { | 680 bool hasAnySubclassThatMixes(ClassElement superclass, ClassElement mixin) { |
| 618 assert(isClosed); | 681 assert(isClosed); |
| 619 return mixinUsesOf(mixin).any((each) => each.isSubclassOf(superclass)); | 682 return mixinUsesOf(mixin).any((each) => each.isSubclassOf(superclass)); |
| 620 } | 683 } |
| 621 | 684 |
| 622 /// Returns `true` if [cls] or any superclass mixes in [mixin]. | 685 /// Returns `true` if [cls] or any superclass mixes in [mixin]. |
| 623 bool isSubclassOfMixinUseOf(ClassElement cls, ClassElement mixin) { | 686 bool isSubclassOfMixinUseOf(ClassElement cls, ClassElement mixin) { |
| 624 assert(isClosed); | 687 assert(isClosed); |
| 688 assert(cls.isDeclaration); | |
| 689 assert(mixin.isDeclaration); | |
| 625 if (isUsedAsMixin(mixin)) { | 690 if (isUsedAsMixin(mixin)) { |
| 626 ClassElement current = cls.declaration; | 691 ClassElement current = cls; |
| 627 mixin = mixin.declaration; | |
| 628 while (current != null) { | 692 while (current != null) { |
| 629 current = current.declaration; | |
| 630 if (current.isMixinApplication) { | 693 if (current.isMixinApplication) { |
| 631 MixinApplicationElement application = current; | 694 MixinApplicationElement application = current; |
| 632 if (application.mixin.declaration == mixin) return true; | 695 if (application.mixin == mixin) return true; |
| 633 } | 696 } |
| 634 current = current.superclass; | 697 current = current.superclass; |
|
Siggi Cherem (dart-lang)
2016/10/14 16:49:57
I assume we don't need to assert that superclass i
Johnni Winther
2016/10/17 07:57:40
Correct.
| |
| 635 } | 698 } |
| 636 } | 699 } |
| 637 return false; | 700 return false; |
| 638 } | 701 } |
| 639 | 702 |
| 640 /// Returns `true` if every subtype of [x] is a subclass of [y] or a subclass | 703 /// Returns `true` if every subtype of [x] is a subclass of [y] or a subclass |
| 641 /// of a mixin application of [y]. | 704 /// of a mixin application of [y]. |
| 642 bool everySubtypeIsSubclassOfOrMixinUseOf(ClassElement x, ClassElement y) { | 705 bool everySubtypeIsSubclassOfOrMixinUseOf(ClassElement x, ClassElement y) { |
| 643 assert(isClosed); | 706 assert(isClosed); |
| 644 x = x.declaration; | 707 assert(x.isDeclaration); |
| 645 y = y.declaration; | 708 assert(y.isDeclaration); |
| 646 Map<ClassElement, bool> secondMap = | 709 Map<ClassElement, bool> secondMap = |
| 647 _subtypeCoveredByCache[x] ??= <ClassElement, bool>{}; | 710 _subtypeCoveredByCache[x] ??= <ClassElement, bool>{}; |
| 648 return secondMap[y] ??= subtypesOf(x).every((ClassElement cls) => | 711 return secondMap[y] ??= subtypesOf(x).every((ClassElement cls) => |
| 649 isSubclassOf(cls, y) || isSubclassOfMixinUseOf(cls, y)); | 712 isSubclassOf(cls, y) || isSubclassOfMixinUseOf(cls, y)); |
| 650 } | 713 } |
| 651 | 714 |
| 652 /// Returns `true` if any subclass of [superclass] implements [type]. | 715 /// Returns `true` if any subclass of [superclass] implements [type]. |
| 653 bool hasAnySubclassThatImplements( | 716 bool hasAnySubclassThatImplements( |
| 654 ClassElement superclass, ClassElement type) { | 717 ClassElement superclass, ClassElement type) { |
| 655 assert(isClosed); | 718 assert(isClosed); |
| 656 Set<ClassElement> subclasses = typesImplementedBySubclassesOf(superclass); | 719 Set<ClassElement> subclasses = typesImplementedBySubclassesOf(superclass); |
| 657 if (subclasses == null) return false; | 720 if (subclasses == null) return false; |
| 658 return subclasses.contains(type); | 721 return subclasses.contains(type); |
| 659 } | 722 } |
| 660 | 723 |
| 724 @override | |
| 725 bool hasElementIn(ClassElement cls, Selector selector, Element element) { | |
| 726 // Use [:implementation:] of [element] | |
| 727 // because our function set only stores declarations. | |
| 728 Element result = findMatchIn(cls, selector); | |
| 729 return result == null | |
| 730 ? false | |
| 731 : result.implementation == element.implementation; | |
| 732 } | |
| 733 | |
| 734 Element findMatchIn(ClassElement cls, Selector selector) { | |
| 735 // Use the [:implementation] of [cls] in case the found [element] | |
| 736 // is in the patch class. | |
| 737 var result = cls.implementation.lookupByName(selector.memberName); | |
| 738 return result; | |
| 739 } | |
| 740 | |
| 741 /// Returns whether a [selector] call on an instance of [cls] | |
| 742 /// will hit a method at runtime, and not go through [noSuchMethod]. | |
| 743 bool hasConcreteMatch(ClassElement cls, Selector selector) { | |
| 744 assert(invariant(cls, isInstantiated(cls), | |
| 745 message: '$cls has not been instantiated.')); | |
| 746 Element element = findMatchIn(cls, selector); | |
| 747 if (element == null) return false; | |
| 748 | |
| 749 if (element.isAbstract) { | |
| 750 ClassElement enclosingClass = element.enclosingClass; | |
| 751 return hasConcreteMatch(enclosingClass.superclass, selector); | |
| 752 } | |
| 753 return selector.appliesUntyped(element); | |
| 754 } | |
| 755 | |
| 756 @override | |
| 757 bool needsNoSuchMethod(ClassElement base, Selector selector, ClassMask mask) { | |
| 758 /// Returns `true` if [cls] is an instantiated class that does not have | |
| 759 /// a concrete method matching [selector]. | |
| 760 bool needsNoSuchMethod(ClassElement cls) { | |
| 761 // We can skip uninstantiated subclasses. | |
| 762 if (!isInstantiated(cls)) { | |
| 763 return false; | |
| 764 } | |
| 765 // We can just skip abstract classes because we know no | |
| 766 // instance of them will be created at runtime, and | |
| 767 // therefore there is no instance that will require | |
| 768 // [noSuchMethod] handling. | |
| 769 return !cls.isAbstract && !hasConcreteMatch(cls, selector); | |
| 770 } | |
| 771 | |
| 772 bool baseNeedsNoSuchMethod = needsNoSuchMethod(base); | |
| 773 if (mask == ClassMask.EXACT || baseNeedsNoSuchMethod) { | |
| 774 return baseNeedsNoSuchMethod; | |
| 775 } | |
| 776 | |
| 777 Iterable<ClassElement> subclassesToCheck; | |
| 778 if (mask == ClassMask.SUBTYPE) { | |
| 779 subclassesToCheck = strictSubtypesOf(base); | |
| 780 } else { | |
| 781 assert(mask == ClassMask.SUBCLASS); | |
| 782 subclassesToCheck = strictSubclassesOf(base); | |
| 783 } | |
| 784 | |
| 785 return subclassesToCheck != null && | |
| 786 subclassesToCheck.any(needsNoSuchMethod); | |
| 787 } | |
| 788 | |
| 661 final Compiler _compiler; | 789 final Compiler _compiler; |
| 662 BackendClasses get backendClasses => _backend.backendClasses; | 790 BackendClasses get backendClasses => _backend.backendClasses; |
| 663 JavaScriptBackend get _backend => _compiler.backend; | 791 JavaScriptBackend get _backend => _compiler.backend; |
| 664 CommonMasks get commonMasks => _compiler.commonMasks; | 792 CommonMasks get commonMasks => _compiler.commonMasks; |
| 665 final FunctionSet allFunctions; | 793 final FunctionSet allFunctions; |
| 666 final Set<Element> functionsCalledInLoop = new Set<Element>(); | 794 final Set<Element> functionsCalledInLoop = new Set<Element>(); |
| 667 final Map<Element, SideEffects> sideEffects = new Map<Element, SideEffects>(); | 795 final Map<Element, SideEffects> sideEffects = new Map<Element, SideEffects>(); |
| 668 | 796 |
| 669 final Set<TypedefElement> _allTypedefs = new Set<TypedefElement>(); | 797 final Set<TypedefElement> _allTypedefs = new Set<TypedefElement>(); |
| 670 | 798 |
| (...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1004 return getMightBePassedToApply(element.expression); | 1132 return getMightBePassedToApply(element.expression); |
| 1005 } | 1133 } |
| 1006 return functionsThatMightBePassedToApply.contains(element); | 1134 return functionsThatMightBePassedToApply.contains(element); |
| 1007 } | 1135 } |
| 1008 | 1136 |
| 1009 @override | 1137 @override |
| 1010 bool getCurrentlyKnownMightBePassedToApply(Element element) { | 1138 bool getCurrentlyKnownMightBePassedToApply(Element element) { |
| 1011 return getMightBePassedToApply(element); | 1139 return getMightBePassedToApply(element); |
| 1012 } | 1140 } |
| 1013 } | 1141 } |
| 1142 | |
| 1143 /// Enum values defining subset of classes included in queries. | |
| 1144 enum ClassMask { | |
|
Siggi Cherem (dart-lang)
2016/10/14 16:49:57
I'm not convinced about adding this enum. Since it
Johnni Winther
2016/10/17 07:57:40
Renamed to `ClassQuery`. We'll need it more functi
| |
| 1145 /// Only the class itself is included. | |
| 1146 EXACT, | |
| 1147 | |
| 1148 /// The class and all subclasses (transitively) are included. | |
| 1149 SUBCLASS, | |
| 1150 | |
| 1151 /// The class and all classes that implement or subclass it (transitively) | |
| 1152 /// are included. | |
| 1153 SUBTYPE, | |
| 1154 } | |
| OLD | NEW |