Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, 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 kernel.tree_shaker; | 5 library kernel.tree_shaker; |
| 6 | 6 |
| 7 import '../ast.dart'; | 7 import '../ast.dart'; |
| 8 import '../class_hierarchy.dart'; | 8 import '../class_hierarchy.dart'; |
| 9 import '../core_types.dart'; | 9 import '../core_types.dart'; |
| 10 import '../type_environment.dart'; | |
| 10 | 11 |
| 11 Program transformProgram(Program program) { | 12 Program transformProgram(Program program) { |
| 12 new TreeShaker(program).transform(program); | 13 new TreeShaker(program).transform(program); |
| 13 return program; | 14 return program; |
| 14 } | 15 } |
| 15 | 16 |
| 16 /// Tree shaking based on class hierarchy analysis. | 17 /// Tree shaking based on class hierarchy analysis. |
| 17 /// | 18 /// |
| 18 /// Any dynamic dispatch not on `this` is conservatively assumed to target | 19 /// Any dynamic dispatch not on `this` is conservatively assumed to target |
| 19 /// any instantiated class that implements a member matching the selector. | 20 /// any instantiated class that implements a member matching the selector. |
| 20 /// | 21 /// |
| 21 /// Member bodies are analyzed relative to a given "host class" which is the | 22 /// Member bodies are analyzed relative to a given "host class" which is the |
| 22 /// concrete type of `this` (or null if in static context), so dispatches on | 23 /// concrete type of `this` (or null if in static context), so dispatches on |
| 23 /// `this` can be resolved more precisely. | 24 /// `this` can be resolved more precisely. |
| 24 /// | 25 /// |
| 25 /// The tree shaker computes the following in a fixed-point iteration: | 26 /// The tree shaker computes the following in a fixed-point iteration: |
| 26 /// - a set of instantiated classes | 27 /// - a set of instantiated classes |
| 27 /// - for each member, a set of potential host classes | 28 /// - for each member, a set of potential host classes |
| 28 /// - a set of names used in dynamic dispatch not on `this` | 29 /// - a set of names used in dynamic dispatch not on `this` |
| 29 /// | 30 /// |
| 30 /// The `dart:mirrors` library is not supported. | 31 /// If the `dart:mirrors` library is used then nothing will be tree-shaken. |
| 31 // | 32 // |
| 32 // TODO(asgerf): Shake off parts of the core libraries based on the Target. | 33 // TODO(asgerf): Shake off parts of the core libraries based on the Target. |
| 33 // TODO(asgerf): Tree shake unused instance fields. | 34 // TODO(asgerf): Tree shake unused instance fields. |
| 34 class TreeShaker { | 35 class TreeShaker { |
| 35 final Program program; | 36 final Program program; |
| 36 final ClassHierarchy hierarchy; | 37 final ClassHierarchy hierarchy; |
| 37 final CoreTypes coreTypes; | 38 final CoreTypes coreTypes; |
| 39 final bool strongMode; | |
| 38 | 40 |
| 39 /// Names used in a dynamic dispatch invocation that could not be resolved | 41 /// Map from classes to set of names that have been dispatched with that class |
| 40 /// to a concrete target (i.e. not on `this`). | 42 /// as the static receiver type (meaning any subtype of that class can be |
| 41 final Set<Name> _dispatchedNames = new Set<Name>(); | 43 /// the potential concrete receiver). |
| 44 /// | |
| 45 /// The map is implemented as a list, indexed by | |
| 46 /// [ClassHierarchy.getClassIndex]. | |
| 47 final List<Set<Name>> _dispatchedNames; | |
| 48 | |
| 49 /// Map from names to the set of classes that might be the concrete receiver | |
| 50 /// of a call with the given name. | |
| 51 final Map<Name, ClassSet> _receiversOfName = <Name, ClassSet>{}; | |
| 42 | 52 |
| 43 /// Instance members that are potential targets for dynamic dispatch, but | 53 /// Instance members that are potential targets for dynamic dispatch, but |
| 44 /// whose name has not yet been seen in a dynamic dispatch invocation. | 54 /// whose name has not yet been seen in a dynamic dispatch invocation. |
| 45 /// | 55 /// |
| 46 /// The map is indexed by the name of the member, and value is a list of | 56 /// The map is indexed by the name of the member, and value is a list of |
| 47 /// interleaved (host class, member) pairs. | 57 /// interleaved (host class, member) pairs. |
| 48 final Map<Name, List<TreeNode>> _dispatchTargetCandidates = | 58 final Map<Name, List<TreeNode>> _dispatchTargetCandidates = |
| 49 <Name, List<TreeNode>>{}; | 59 <Name, List<TreeNode>>{}; |
| 50 | 60 |
| 51 /// Map from classes to the set of members that are reachable with that | 61 /// Map from classes to the set of members that are reachable with that |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 70 /// See [ClassRetention]. | 80 /// See [ClassRetention]. |
| 71 final List<ClassRetention> _classRetention; | 81 final List<ClassRetention> _classRetention; |
| 72 | 82 |
| 73 /// Interleaved (host class, member) pairs that are reachable but have not yet | 83 /// Interleaved (host class, member) pairs that are reachable but have not yet |
| 74 /// been analyzed for more uses. | 84 /// been analyzed for more uses. |
| 75 final List<TreeNode> _worklist = new List<TreeNode>(); | 85 final List<TreeNode> _worklist = new List<TreeNode>(); |
| 76 | 86 |
| 77 /// Classes whose interface can be used by external code to invoke user code. | 87 /// Classes whose interface can be used by external code to invoke user code. |
| 78 final Set<Class> _escapedClasses = new Set<Class>(); | 88 final Set<Class> _escapedClasses = new Set<Class>(); |
| 79 | 89 |
| 90 /// Members that have been overridden by a member whose concrete body is | |
| 91 /// needed. These must be preserved in order to maintain interface targets | |
| 92 /// for typed calls. | |
| 93 final Set<Member> _overriddenMembers = new Set<Member>(); | |
| 94 | |
| 95 final List<Expression> _typedCalls = <Expression>[]; | |
| 96 | |
| 80 /// AST visitor for finding static uses and dynamic dispatches in code. | 97 /// AST visitor for finding static uses and dynamic dispatches in code. |
| 81 _TreeShakerVisitor _visitor; | 98 _TreeShakerVisitor _visitor; |
| 82 | 99 |
| 83 /// AST visitor for analyzing type annotations on external members. | 100 /// AST visitor for analyzing type annotations on external members. |
| 84 _ExternalTypeVisitor _covariantVisitor; | 101 _ExternalTypeVisitor _covariantVisitor; |
| 85 _ExternalTypeVisitor _contravariantVisitor; | 102 _ExternalTypeVisitor _contravariantVisitor; |
| 86 _ExternalTypeVisitor _bivariantVisitor; | 103 _ExternalTypeVisitor _invariantVisitor; |
| 87 | 104 |
| 88 TreeShaker(Program program, {ClassHierarchy hierarchy, CoreTypes coreTypes}) | 105 Library _mirrorsLibrary; |
| 106 | |
| 107 /// Set to true if any use of the `dart:mirrors` API is found. | |
| 108 bool isUsingMirrors = false; | |
| 109 | |
| 110 TreeShaker(Program program, | |
| 111 {ClassHierarchy hierarchy, CoreTypes coreTypes, bool strongMode: false}) | |
| 89 : this._internal(program, hierarchy ?? new ClassHierarchy(program), | 112 : this._internal(program, hierarchy ?? new ClassHierarchy(program), |
| 90 coreTypes ?? new CoreTypes(program)); | 113 coreTypes ?? new CoreTypes(program), strongMode); |
| 114 | |
| 115 bool isMemberBodyUsed(Member member) { | |
| 116 return _usedMembers.containsKey(member); | |
| 117 } | |
| 118 | |
| 119 bool isMemberOverridden(Member member) { | |
| 120 return _overriddenMembers.contains(member); | |
| 121 } | |
| 91 | 122 |
| 92 bool isMemberUsed(Member member) { | 123 bool isMemberUsed(Member member) { |
| 93 return _usedMembers.containsKey(member); | 124 return isMemberBodyUsed(member) || isMemberOverridden(member); |
| 94 } | 125 } |
| 95 | 126 |
| 96 bool isInstantiated(Class classNode) { | 127 bool isInstantiated(Class classNode) { |
| 97 return getClassRetention(classNode).index >= ClassRetention.Instance.index; | 128 return getClassRetention(classNode).index >= ClassRetention.Instance.index; |
| 98 } | 129 } |
| 99 | 130 |
| 100 bool isHierarchyUsed(Class classNode) { | 131 bool isHierarchyUsed(Class classNode) { |
| 101 return getClassRetention(classNode).index >= ClassRetention.Hierarchy.index; | 132 return getClassRetention(classNode).index >= ClassRetention.Hierarchy.index; |
| 102 } | 133 } |
| 103 | 134 |
| 104 ClassRetention getClassRetention(Class classNode) { | 135 ClassRetention getClassRetention(Class classNode) { |
| 105 int index = hierarchy.getClassIndex(classNode); | 136 int index = hierarchy.getClassIndex(classNode); |
| 106 return _classRetention[index]; | 137 return _classRetention[index]; |
| 107 } | 138 } |
| 108 | 139 |
| 109 /// Applies the tree shaking results to the program. | 140 /// Applies the tree shaking results to the program. |
| 110 /// | 141 /// |
| 111 /// This removes unused classes, members, and hierarchy data. | 142 /// This removes unused classes, members, and hierarchy data. |
| 112 void transform(Program program) { | 143 void transform(Program program) { |
| 144 if (isUsingMirrors) return; // Give up if using mirrors. | |
| 113 new _TreeShakingTransformer(this).transform(program); | 145 new _TreeShakingTransformer(this).transform(program); |
| 114 } | 146 } |
| 115 | 147 |
| 116 TreeShaker._internal(this.program, ClassHierarchy hierarchy, this.coreTypes) | 148 TreeShaker._internal( |
| 149 this.program, ClassHierarchy hierarchy, this.coreTypes, this.strongMode) | |
| 117 : this.hierarchy = hierarchy, | 150 : this.hierarchy = hierarchy, |
| 151 this._dispatchedNames = new List<Set<Name>>(hierarchy.classes.length), | |
| 118 this._usedMembersWithHost = | 152 this._usedMembersWithHost = |
| 119 new List<Set<Member>>(hierarchy.classes.length), | 153 new List<Set<Member>>(hierarchy.classes.length), |
| 120 this._classRetention = new List<ClassRetention>.filled( | 154 this._classRetention = new List<ClassRetention>.filled( |
| 121 hierarchy.classes.length, ClassRetention.None) { | 155 hierarchy.classes.length, ClassRetention.None) { |
| 122 _visitor = new _TreeShakerVisitor(this); | 156 _visitor = new _TreeShakerVisitor(this); |
| 123 _covariantVisitor = new _ExternalTypeVisitor(this, isCovariant: true); | 157 _covariantVisitor = new _ExternalTypeVisitor(this, isCovariant: true); |
| 124 _contravariantVisitor = | 158 _contravariantVisitor = |
| 125 new _ExternalTypeVisitor(this, isContravariant: true); | 159 new _ExternalTypeVisitor(this, isContravariant: true); |
| 126 _bivariantVisitor = new _ExternalTypeVisitor(this, | 160 _invariantVisitor = new _ExternalTypeVisitor(this, |
| 127 isCovariant: true, isContravariant: true); | 161 isCovariant: true, isContravariant: true); |
| 128 _build(); | 162 _mirrorsLibrary = coreTypes.getCoreLibrary('dart:mirrors'); |
| 163 try { | |
| 164 _build(); | |
| 165 } on _UsingMirrorsException { | |
| 166 isUsingMirrors = true; | |
| 167 } | |
| 129 } | 168 } |
| 130 | 169 |
| 131 void _build() { | 170 void _build() { |
| 132 if (program.mainMethod == null) { | 171 if (program.mainMethod == null) { |
| 133 throw 'Cannot perform tree shaking on a program without a main method'; | 172 throw 'Cannot perform tree shaking on a program without a main method'; |
| 134 } | 173 } |
| 135 if (program.mainMethod.function.positionalParameters.length > 0) { | 174 if (program.mainMethod.function.positionalParameters.length > 0) { |
| 136 // The main method takes a List<String> as argument. | 175 // The main method takes a List<String> as argument. |
| 137 _addInstantiatedExternalSubclass(coreTypes.listClass); | 176 _addInstantiatedExternalSubclass(coreTypes.listClass); |
| 138 _addInstantiatedExternalSubclass(coreTypes.stringClass); | 177 _addInstantiatedExternalSubclass(coreTypes.stringClass); |
| 139 } | 178 } |
| 140 _addDispatchedName(new Name('noSuchMethod')); | 179 _addDispatchedName(hierarchy.rootClass, new Name('noSuchMethod')); |
| 141 _addPervasiveUses(); | 180 _addPervasiveUses(); |
| 142 _addUsedMember(null, program.mainMethod); | 181 _addUsedMember(null, program.mainMethod); |
| 143 _iterateWorklist(); | 182 _iterateWorklist(); |
| 183 | |
| 184 // Mark overridden members in order to preserve abstract members as | |
| 185 // necessary. | |
| 186 if (strongMode) { | |
| 187 for (int i = hierarchy.classes.length - 1; i >= 0; --i) { | |
| 188 Class class_ = hierarchy.classes[i]; | |
| 189 if (isHierarchyUsed(class_)) { | |
| 190 hierarchy.forEachOverridePair(class_, | |
| 191 (Member ownMember, Member superMember, bool isSetter) { | |
| 192 if (isMemberBodyUsed(ownMember) || | |
| 193 _overriddenMembers.contains(ownMember)) { | |
| 194 _overriddenMembers.add(superMember); | |
| 195 // Ensure the types mentioned in the member can be preserved. | |
| 196 _visitor.visitMemberInterface(superMember); | |
| 197 } | |
| 198 }); | |
| 199 } | |
| 200 } | |
| 201 // Marking members as overridden should not cause new code to become | |
| 202 // reachable. | |
| 203 assert(_worklist.isEmpty); | |
| 204 } | |
| 144 } | 205 } |
| 145 | 206 |
| 146 /// Registers some extremely commonly used core classes as instantiated, so | 207 /// Registers some extremely commonly used core classes as instantiated, so |
| 147 /// we don't have to register them for every use we find. | 208 /// we don't have to register them for every use we find. |
| 148 void _addPervasiveUses() { | 209 void _addPervasiveUses() { |
| 149 _addInstantiatedExternalSubclass(coreTypes.stringClass); | 210 _addInstantiatedExternalSubclass(coreTypes.stringClass); |
| 150 _addInstantiatedExternalSubclass(coreTypes.intClass); | 211 _addInstantiatedExternalSubclass(coreTypes.intClass); |
| 151 _addInstantiatedExternalSubclass(coreTypes.boolClass); | 212 _addInstantiatedExternalSubclass(coreTypes.boolClass); |
| 152 _addInstantiatedExternalSubclass(coreTypes.nullClass); | 213 _addInstantiatedExternalSubclass(coreTypes.nullClass); |
| 214 _addInstantiatedExternalSubclass(coreTypes.functionClass); | |
| 215 _addInstantiatedExternalSubclass(coreTypes.invocationClass); | |
| 153 } | 216 } |
| 154 | 217 |
| 155 /// Registers the given name as seen in a dynamic dispatch, and discovers used | 218 /// Registers the given name as seen in a dynamic dispatch, and discovers used |
| 156 /// instance members accordingly. | 219 /// instance members accordingly. |
| 157 void _addDispatchedName(Name name) { | 220 void _addDispatchedName(Class receiver, Name name) { |
| 221 int index = hierarchy.getClassIndex(receiver); | |
| 222 Set<Name> receiverNames = _dispatchedNames[index] ??= new Set<Name>(); | |
| 158 // TODO(asgerf): make use of selector arity and getter/setter kind | 223 // TODO(asgerf): make use of selector arity and getter/setter kind |
| 159 if (_dispatchedNames.add(name)) { | 224 if (receiverNames.add(name)) { |
| 160 List<TreeNode> targets = _dispatchTargetCandidates[name]; | 225 List<TreeNode> candidates = _dispatchTargetCandidates[name]; |
| 161 if (targets != null) { | 226 if (candidates != null) { |
| 162 for (int i = 0; i < targets.length; i += 2) { | 227 for (int i = 0; i < candidates.length; i += 2) { |
| 163 _addUsedMember(targets[i], targets[i + 1]); | 228 Class host = candidates[i]; |
| 229 if (hierarchy.isSubtypeOf(host, receiver)) { | |
| 230 // This (host, member) pair is a potential target of the dispatch. | |
| 231 Member member = candidates[i + 1]; | |
| 232 | |
| 233 // Remove the (host,member) pair from the candidate list. | |
| 234 // Move the last pair into the current index and shrink the list. | |
| 235 int lastPair = candidates.length - 2; | |
| 236 candidates[i] = candidates[lastPair]; | |
| 237 candidates[i + 1] = candidates[lastPair + 1]; | |
| 238 candidates.length -= 2; | |
| 239 i -= 2; // Revisit the same index now that it has been updated. | |
| 240 | |
| 241 // Mark the pair as used. This should be done after removing it | |
| 242 // from the candidate list, since this call may recursively scan | |
| 243 // for more used members. | |
| 244 _addUsedMember(host, member); | |
| 245 } | |
| 164 } | 246 } |
| 165 } | 247 } |
| 248 var subtypes = hierarchy.getSubtypesOf(receiver); | |
| 249 var receiverSet = _receiversOfName[name]; | |
| 250 _receiversOfName[name] = receiverSet == null | |
| 251 ? subtypes | |
| 252 : _receiversOfName[name].union(subtypes); | |
| 166 } | 253 } |
| 167 } | 254 } |
| 168 | 255 |
| 169 /// Registers the given method as a potential target of dynamic dispatch on | 256 /// Registers the given method as a potential target of dynamic dispatch on |
| 170 /// the given class. | 257 /// the given class. |
| 171 void _addDispatchTarget(Class host, Member member) { | 258 void _addDispatchTarget(Class host, Member member) { |
| 172 if (_dispatchedNames.contains(member.name)) { | 259 ClassSet receivers = _receiversOfName[member.name]; |
| 260 if (receivers != null && receivers.contains(host)) { | |
| 173 _addUsedMember(host, member); | 261 _addUsedMember(host, member); |
| 174 } else { | 262 } else { |
| 175 _dispatchTargetCandidates.putIfAbsent(member.name, _makeTreeNodeList) | 263 _dispatchTargetCandidates.putIfAbsent(member.name, _makeTreeNodeList) |
| 176 ..add(host) | 264 ..add(host) |
| 177 ..add(member); | 265 ..add(member); |
| 178 } | 266 } |
| 179 } | 267 } |
| 180 | 268 |
| 181 static List<TreeNode> _makeTreeNodeList() => <TreeNode>[]; | 269 static List<TreeNode> _makeTreeNodeList() => <TreeNode>[]; |
| 182 | 270 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 206 if (oldRetention.index >= ClassRetention.ExternalInstance.index) { | 294 if (oldRetention.index >= ClassRetention.ExternalInstance.index) { |
| 207 return; | 295 return; |
| 208 } | 296 } |
| 209 _propagateClassInstanceLevel(classNode, oldRetention); | 297 _propagateClassInstanceLevel(classNode, oldRetention); |
| 210 for (Member member in hierarchy.getInterfaceMembers(classNode)) { | 298 for (Member member in hierarchy.getInterfaceMembers(classNode)) { |
| 211 if (member is Field) { | 299 if (member is Field) { |
| 212 _covariantVisitor.visit(member.type); | 300 _covariantVisitor.visit(member.type); |
| 213 } else { | 301 } else { |
| 214 _addCallToExternalProcedure(member); | 302 _addCallToExternalProcedure(member); |
| 215 } | 303 } |
| 304 _addDispatchTarget(classNode, member); | |
| 305 } | |
| 306 for (Member member | |
| 307 in hierarchy.getInterfaceMembers(classNode, setters: true)) { | |
| 308 _addDispatchTarget(classNode, member); | |
| 216 } | 309 } |
| 217 } | 310 } |
| 218 | 311 |
| 219 /// Called when the retention level for [classNode] has been raised from | 312 /// Called when the retention level for [classNode] has been raised from |
| 220 /// [oldRetention] to instance level. | 313 /// [oldRetention] to instance level. |
| 221 /// | 314 /// |
| 222 /// Ensures that the relevant members are put in the worklist, and super types | 315 /// Ensures that the relevant members are put in the worklist, and super types |
| 223 /// and raised to hierarchy level. | 316 /// and raised to hierarchy level. |
| 224 void _propagateClassInstanceLevel( | 317 void _propagateClassInstanceLevel( |
| 225 Class classNode, ClassRetention oldRetention) { | 318 Class classNode, ClassRetention oldRetention) { |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 300 } | 393 } |
| 301 } | 394 } |
| 302 | 395 |
| 303 /// Registers the given member as being used, in the following sense: | 396 /// Registers the given member as being used, in the following sense: |
| 304 /// - Fields are used if they can be read or written or their initializer is | 397 /// - Fields are used if they can be read or written or their initializer is |
| 305 /// evaluated. | 398 /// evaluated. |
| 306 /// - Constructors are used if they can be invoked, either directly or through | 399 /// - Constructors are used if they can be invoked, either directly or through |
| 307 /// the initializer list of another constructor. | 400 /// the initializer list of another constructor. |
| 308 /// - Procedures are used if they can be invoked or torn off. | 401 /// - Procedures are used if they can be invoked or torn off. |
| 309 void _addUsedMember(Class host, Member member) { | 402 void _addUsedMember(Class host, Member member) { |
| 403 if (member.enclosingLibrary == _mirrorsLibrary) { | |
| 404 throw new _UsingMirrorsException(); | |
| 405 } | |
| 310 if (host != null) { | 406 if (host != null) { |
| 311 // Check if the member has been seen with this host before. | 407 // Check if the member has been seen with this host before. |
| 312 int index = hierarchy.getClassIndex(host); | 408 int index = hierarchy.getClassIndex(host); |
| 313 Set<Member> members = _usedMembersWithHost[index] ??= new Set<Member>(); | 409 Set<Member> members = _usedMembersWithHost[index] ??= new Set<Member>(); |
| 314 if (!members.add(member)) return; | 410 if (!members.add(member)) return; |
| 315 _usedMembers.putIfAbsent(member, _makeIncompleteSummary); | 411 _usedMembers.putIfAbsent(member, _makeIncompleteSummary); |
| 316 } else { | 412 } else { |
| 317 // Check if the member has been seen before. | 413 // Check if the member has been seen before. |
| 318 if (_usedMembers.containsKey(member)) return; | 414 if (_usedMembers.containsKey(member)) return; |
| 319 _usedMembers[member] = _makeIncompleteSummary(); | 415 _usedMembers[member] = _makeIncompleteSummary(); |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 342 for (int i = 0; i < function.namedParameters.length; ++i) { | 438 for (int i = 0; i < function.namedParameters.length; ++i) { |
| 343 _contravariantVisitor.visit(function.namedParameters[i].type); | 439 _contravariantVisitor.visit(function.namedParameters[i].type); |
| 344 } | 440 } |
| 345 } | 441 } |
| 346 | 442 |
| 347 /// Called when external code may invoke the interface of the given class. | 443 /// Called when external code may invoke the interface of the given class. |
| 348 void _addEscapedClass(Class node) { | 444 void _addEscapedClass(Class node) { |
| 349 if (!_escapedClasses.add(node)) return; | 445 if (!_escapedClasses.add(node)) return; |
| 350 for (Member member in hierarchy.getInterfaceMembers(node)) { | 446 for (Member member in hierarchy.getInterfaceMembers(node)) { |
| 351 if (member is Procedure) { | 447 if (member is Procedure) { |
| 352 _addDispatchedName(member.name); | 448 _addDispatchedName(node, member.name); |
| 353 } | 449 } |
| 354 } | 450 } |
| 355 } | 451 } |
| 356 | 452 |
| 357 /// Creates a incomplete summary object, indicating that a member has not | 453 /// Creates a incomplete summary object, indicating that a member has not |
| 358 /// yet been analyzed. | 454 /// yet been analyzed. |
| 359 static List<Node> _makeIncompleteSummary() => <Node>[null]; | 455 static List<Node> _makeIncompleteSummary() => <Node>[null]; |
| 360 | 456 |
| 361 bool isIncompleteSummary(List<Node> summary) { | 457 bool isIncompleteSummary(List<Node> summary) { |
| 362 return summary.isNotEmpty && summary[0] == null; | 458 return summary.isNotEmpty && summary[0] == null; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 411 } | 507 } |
| 412 | 508 |
| 413 /// Sentinel that occurs in method summaries in front of each name that should | 509 /// Sentinel that occurs in method summaries in front of each name that should |
| 414 /// be interpreted as a setter. | 510 /// be interpreted as a setter. |
| 415 final Node _setterSentinel = const InvalidType(); | 511 final Node _setterSentinel = const InvalidType(); |
| 416 | 512 |
| 417 /// Searches the AST for static references and dynamically dispatched names. | 513 /// Searches the AST for static references and dynamically dispatched names. |
| 418 class _TreeShakerVisitor extends RecursiveVisitor { | 514 class _TreeShakerVisitor extends RecursiveVisitor { |
| 419 final TreeShaker shaker; | 515 final TreeShaker shaker; |
| 420 final CoreTypes coreTypes; | 516 final CoreTypes coreTypes; |
| 517 final TypeEnvironment types; | |
| 518 final bool strongMode; | |
| 421 List<Node> summary; | 519 List<Node> summary; |
| 422 | 520 |
| 423 _TreeShakerVisitor(TreeShaker shaker) | 521 _TreeShakerVisitor(TreeShaker shaker) |
| 424 : this.shaker = shaker, | 522 : this.shaker = shaker, |
| 425 this.coreTypes = shaker.coreTypes; | 523 this.coreTypes = shaker.coreTypes, |
| 524 this.strongMode = shaker.strongMode, | |
| 525 this.types = new TypeEnvironment(shaker.coreTypes, shaker.hierarchy) { | |
| 526 types.errorHandler = handleError; | |
| 527 } | |
| 426 | 528 |
| 427 void analyzeAndBuildSummary(Node node, List<Node> summary) { | 529 void handleError(TreeNode node, String message) { |
| 530 print('[error] $message (${node.location})'); | |
| 531 } | |
| 532 | |
| 533 void analyzeAndBuildSummary(Member member, List<Node> summary) { | |
| 428 this.summary = summary; | 534 this.summary = summary; |
| 429 node.accept(this); | 535 types.thisType = member.enclosingClass?.thisType; |
| 536 member.accept(this); | |
| 537 } | |
| 538 | |
| 539 void visitMemberInterface(Member node) { | |
| 540 if (node is Field) { | |
| 541 node.type.accept(this); | |
| 542 } else if (node is Procedure) { | |
| 543 visitFunctionInterface(node.function); | |
| 544 } | |
| 545 } | |
| 546 | |
| 547 visitFunctionInterface(FunctionNode node) { | |
| 548 for (var parameter in node.typeParameters) { | |
| 549 parameter.bound.accept(this); | |
| 550 } | |
| 551 for (var parameter in node.positionalParameters) { | |
| 552 parameter.type.accept(this); | |
| 553 } | |
| 554 for (var parameter in node.namedParameters) { | |
| 555 parameter.type.accept(this); | |
| 556 } | |
| 557 node.returnType.accept(this); | |
| 430 } | 558 } |
| 431 | 559 |
| 432 @override | 560 @override |
| 433 visitFunctionNode(FunctionNode node) { | 561 visitFunctionNode(FunctionNode node) { |
| 434 switch (node.asyncMarker) { | 562 switch (node.asyncMarker) { |
| 435 case AsyncMarker.Sync: | 563 case AsyncMarker.Sync: |
| 436 break; | 564 break; |
| 437 case AsyncMarker.SyncStar: | 565 case AsyncMarker.SyncStar: |
| 438 shaker._addInstantiatedExternalSubclass(coreTypes.iterableClass); | 566 shaker._addInstantiatedExternalSubclass(coreTypes.iterableClass); |
| 439 break; | 567 break; |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 497 @override | 625 @override |
| 498 visitDirectMethodInvocation(DirectMethodInvocation node) { | 626 visitDirectMethodInvocation(DirectMethodInvocation node) { |
| 499 if (node.receiver is! ThisExpression) { | 627 if (node.receiver is! ThisExpression) { |
| 500 // TODO(asgerf): Support arbitrary direct calls. | 628 // TODO(asgerf): Support arbitrary direct calls. |
| 501 throw 'Direct calls are only supported on "this"'; | 629 throw 'Direct calls are only supported on "this"'; |
| 502 } | 630 } |
| 503 addUseFromCurrentHost(node.target); | 631 addUseFromCurrentHost(node.target); |
| 504 node.visitChildren(this); | 632 node.visitChildren(this); |
| 505 } | 633 } |
| 506 | 634 |
| 635 Class getKnownSupertype(DartType type) { | |
| 636 if (type is InterfaceType) { | |
| 637 return type.classNode; | |
| 638 } else if (type is TypeParameterType) { | |
| 639 return getKnownSupertype(type.parameter.bound); | |
| 640 } else if (type is FunctionType) { | |
| 641 return coreTypes.functionClass; | |
| 642 } else if (type is BottomType) { | |
| 643 return coreTypes.nullClass; | |
| 644 } else { | |
| 645 return coreTypes.objectClass; | |
| 646 } | |
| 647 } | |
| 648 | |
| 649 Class getStaticType(Expression node) { | |
| 650 if (!strongMode) return coreTypes.objectClass; | |
| 651 return getKnownSupertype(node.getStaticType(types)); | |
| 652 } | |
| 653 | |
| 507 @override | 654 @override |
| 508 visitMethodInvocation(MethodInvocation node) { | 655 visitMethodInvocation(MethodInvocation node) { |
| 509 if (node.receiver is ThisExpression) { | 656 if (node.receiver is ThisExpression) { |
| 510 addSelfDispatch(node.name); | 657 addSelfDispatch(node.name); |
| 511 } else { | 658 } else { |
| 512 shaker._addDispatchedName(node.name); | 659 shaker._addDispatchedName(getStaticType(node.receiver), node.name); |
| 660 if (node.interfaceTarget != null) { | |
| 661 shaker._typedCalls.add(node); | |
| 662 } | |
| 513 } | 663 } |
| 514 node.visitChildren(this); | 664 node.visitChildren(this); |
| 515 } | 665 } |
| 516 | 666 |
| 517 @override | 667 @override |
| 518 visitStaticGet(StaticGet node) { | 668 visitStaticGet(StaticGet node) { |
| 519 addStaticUse(node.target); | 669 addStaticUse(node.target); |
| 520 node.visitChildren(this); | 670 node.visitChildren(this); |
| 521 } | 671 } |
| 522 | 672 |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 544 } | 694 } |
| 545 addUseFromCurrentHost(node.target); | 695 addUseFromCurrentHost(node.target); |
| 546 node.visitChildren(this); | 696 node.visitChildren(this); |
| 547 } | 697 } |
| 548 | 698 |
| 549 @override | 699 @override |
| 550 visitPropertyGet(PropertyGet node) { | 700 visitPropertyGet(PropertyGet node) { |
| 551 if (node.receiver is ThisExpression) { | 701 if (node.receiver is ThisExpression) { |
| 552 addSelfDispatch(node.name); | 702 addSelfDispatch(node.name); |
| 553 } else { | 703 } else { |
| 554 shaker._addDispatchedName(node.name); | 704 shaker._addDispatchedName(getStaticType(node.receiver), node.name); |
| 705 if (node.interfaceTarget != null) { | |
| 706 shaker._typedCalls.add(node); | |
| 707 } | |
| 555 } | 708 } |
| 556 node.visitChildren(this); | 709 node.visitChildren(this); |
| 557 } | 710 } |
| 558 | 711 |
| 559 @override | 712 @override |
| 560 visitPropertySet(PropertySet node) { | 713 visitPropertySet(PropertySet node) { |
| 561 if (node.receiver is ThisExpression) { | 714 if (node.receiver is ThisExpression) { |
| 562 addSelfDispatch(node.name, setter: true); | 715 addSelfDispatch(node.name, setter: true); |
| 563 } else { | 716 } else { |
| 564 shaker._addDispatchedName(node.name); | 717 shaker._addDispatchedName(getStaticType(node.receiver), node.name); |
| 718 if (node.interfaceTarget != null) { | |
| 719 shaker._typedCalls.add(node); | |
| 720 } | |
| 565 } | 721 } |
| 566 node.visitChildren(this); | 722 node.visitChildren(this); |
| 567 } | 723 } |
| 568 | 724 |
| 569 @override | 725 @override |
| 570 visitListLiteral(ListLiteral node) { | 726 visitListLiteral(ListLiteral node) { |
| 571 shaker._addInstantiatedExternalSubclass(coreTypes.listClass); | 727 shaker._addInstantiatedExternalSubclass(coreTypes.listClass); |
| 572 node.visitChildren(this); | 728 node.visitChildren(this); |
| 573 } | 729 } |
| 574 | 730 |
| 575 @override | 731 @override |
| 576 visitMapLiteral(MapLiteral node) { | 732 visitMapLiteral(MapLiteral node) { |
| 577 shaker._addInstantiatedExternalSubclass(coreTypes.mapClass); | 733 shaker._addInstantiatedExternalSubclass(coreTypes.mapClass); |
| 578 node.visitChildren(this); | 734 node.visitChildren(this); |
| 579 } | 735 } |
| 580 | 736 |
| 581 static final Name _toStringName = new Name('toString'); | 737 static final Name _toStringName = new Name('toString'); |
| 582 | 738 |
| 583 @override | 739 @override |
| 584 visitStringConcatenation(StringConcatenation node) { | 740 visitStringConcatenation(StringConcatenation node) { |
| 585 shaker._addDispatchedName(_toStringName); | 741 for (var expression in node.expressions) { |
| 742 shaker._addDispatchedName(getStaticType(expression), _toStringName); | |
| 743 } | |
| 586 node.visitChildren(this); | 744 node.visitChildren(this); |
| 587 } | 745 } |
| 588 | 746 |
| 589 @override | 747 @override |
| 590 visitInterfaceType(InterfaceType node) { | 748 visitInterfaceType(InterfaceType node) { |
| 591 shaker._addClassUsedInType(node.classNode); | 749 shaker._addClassUsedInType(node.classNode); |
| 592 node.visitChildren(this); | 750 node.visitChildren(this); |
| 593 } | 751 } |
| 594 | 752 |
| 595 @override | 753 @override |
| 754 visitSupertype(Supertype node) { | |
| 755 shaker._addClassUsedInType(node.classNode); | |
| 756 node.visitChildren(this); | |
| 757 } | |
| 758 | |
| 759 @override | |
| 596 visitDoubleLiteral(DoubleLiteral node) { | 760 visitDoubleLiteral(DoubleLiteral node) { |
| 597 shaker._addInstantiatedExternalSubclass(coreTypes.doubleClass); | 761 shaker._addInstantiatedExternalSubclass(coreTypes.doubleClass); |
| 598 } | 762 } |
| 599 | 763 |
| 600 @override | 764 @override |
| 601 visitSymbolLiteral(SymbolLiteral node) { | 765 visitSymbolLiteral(SymbolLiteral node) { |
| 602 shaker._addInstantiatedExternalSubclass(coreTypes.symbolClass); | 766 shaker._addInstantiatedExternalSubclass(coreTypes.symbolClass); |
| 603 // Note: we do not support 'dart:mirrors' right now, so nothing else needs | |
| 604 // to be done for symbols. | |
| 605 } | 767 } |
| 606 | 768 |
| 607 @override | 769 @override |
| 608 visitTypeLiteral(TypeLiteral node) { | 770 visitTypeLiteral(TypeLiteral node) { |
| 609 shaker._addInstantiatedExternalSubclass(coreTypes.typeClass); | 771 shaker._addInstantiatedExternalSubclass(coreTypes.typeClass); |
| 610 node.visitChildren(this); | 772 node.visitChildren(this); |
| 611 } | 773 } |
| 612 } | 774 } |
| 613 | 775 |
| 614 /// The degree to which a class is needed in a program. | 776 /// The degree to which a class is needed in a program. |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 633 } | 795 } |
| 634 | 796 |
| 635 /// Removes classes and members that are not needed. | 797 /// Removes classes and members that are not needed. |
| 636 /// | 798 /// |
| 637 /// There must not be any dangling references in the program afterwards. | 799 /// There must not be any dangling references in the program afterwards. |
| 638 class _TreeShakingTransformer extends Transformer { | 800 class _TreeShakingTransformer extends Transformer { |
| 639 final TreeShaker shaker; | 801 final TreeShaker shaker; |
| 640 | 802 |
| 641 _TreeShakingTransformer(this.shaker); | 803 _TreeShakingTransformer(this.shaker); |
| 642 | 804 |
| 805 Member _translateInterfaceTarget(Member target) { | |
| 806 return target != null && shaker.isMemberUsed(target) ? target : null; | |
| 807 } | |
| 808 | |
| 643 void transform(Program program) { | 809 void transform(Program program) { |
| 644 for (var library in program.libraries) { | 810 for (var library in program.libraries) { |
| 645 if (library.importUri.scheme == 'dart') { | 811 if (library.importUri.scheme == 'dart') { |
| 646 // As long as patching happens in the backend, we cannot shake off | 812 // The backend expects certain things to be present in the core |
| 647 // anything in the core libraries. | 813 // libraries, so we currently don't shake off anything there. |
| 648 continue; | 814 continue; |
| 649 } | 815 } |
| 650 library.transformChildren(this); | 816 library.transformChildren(this); |
| 651 // Note: we can't shake off empty libraries yet since we don't check if | 817 // Note: we can't shake off empty libraries yet since we don't check if |
| 652 // there are private names that use the library. | 818 // there are private names that use the library. |
| 653 } | 819 } |
| 820 for (Expression node in shaker._typedCalls) { | |
| 821 // We should not leave dangling references, so if the target of a typed | |
| 822 // call has been removed, we must remove the reference. The receiver of | |
| 823 // such a call can only be null. | |
| 824 // TODO(asgerf): Rewrite to a NSM call instead of adding dynamic calls. | |
| 825 if (node is MethodInvocation) { | |
| 826 node.interfaceTarget = _translateInterfaceTarget(node.interfaceTarget); | |
| 827 } else if (node is PropertyGet) { | |
| 828 node.interfaceTarget = _translateInterfaceTarget(node.interfaceTarget); | |
| 829 } else if (node is PropertySet) { | |
| 830 node.interfaceTarget = _translateInterfaceTarget(node.interfaceTarget); | |
| 831 } | |
| 832 } | |
| 654 } | 833 } |
| 655 | 834 |
| 656 Class visitClass(Class node) { | 835 Class visitClass(Class node) { |
| 657 switch (shaker.getClassRetention(node)) { | 836 switch (shaker.getClassRetention(node)) { |
| 658 case ClassRetention.None: | 837 case ClassRetention.None: |
| 659 return null; // Remove the class. | 838 return null; // Remove the class. |
| 660 | 839 |
| 661 case ClassRetention.Namespace: | 840 case ClassRetention.Namespace: |
| 662 // The class is only a namespace for static members. Remove its | 841 // The class is only a namespace for static members. Remove its |
| 663 // hierarchy information. This is mandatory, since these references | 842 // hierarchy information. This is mandatory, since these references |
| 664 // might otherwise become dangling. | 843 // might otherwise become dangling. |
| 665 node.supertype = shaker.coreTypes.objectClass.asRawSupertype; | 844 node.supertype = shaker.coreTypes.objectClass.asRawSupertype; |
| 666 node.implementedTypes.clear(); | 845 node.implementedTypes.clear(); |
| 667 node.typeParameters.clear(); | 846 node.typeParameters.clear(); |
| 847 node.isAbstract = true; | |
| 668 // Mixin applications cannot have static members. | 848 // Mixin applications cannot have static members. |
| 669 assert(node.mixedInType == null); | 849 assert(node.mixedInType == null); |
| 670 // Unused members will be removed below. | 850 // Unused members will be removed below. |
| 671 break; | 851 break; |
| 672 | 852 |
| 673 case ClassRetention.Hierarchy: | 853 case ClassRetention.Hierarchy: |
| 854 node.isAbstract = true; | |
| 855 break; | |
| 856 | |
| 674 case ClassRetention.Instance: | 857 case ClassRetention.Instance: |
| 675 case ClassRetention.ExternalInstance: | 858 case ClassRetention.ExternalInstance: |
| 676 break; | 859 break; |
| 677 } | 860 } |
| 678 node.transformChildren(this); | 861 node.transformChildren(this); |
| 679 if (node.constructors.isEmpty && node.procedures.isEmpty) { | |
| 680 // The VM does not like classes without any members, so ensure there is | |
| 681 // always a constructor left. | |
| 682 node.addMember(new Constructor(new FunctionNode(new EmptyStatement()))); | |
| 683 } | |
| 684 return node; | 862 return node; |
| 685 } | 863 } |
| 686 | 864 |
| 687 Member defaultMember(Member node) { | 865 Member defaultMember(Member node) { |
| 688 if (!shaker.isMemberUsed(node)) { | 866 if (!shaker.isMemberBodyUsed(node)) { |
| 689 return null; // Remove unused member. | 867 if (!shaker.isMemberOverridden(node)) { |
| 868 return null; | |
| 869 } | |
| 870 if (node is Procedure) { | |
| 871 // Remove body of unused member. | |
| 872 if (node.enclosingClass.isAbstract) { | |
| 873 node.isAbstract = true; | |
| 874 node.function.body = null; | |
| 875 } else { | |
| 876 // If the enclosing class is not abstract, the method should still | |
| 877 // have a body even if it can never be called. | |
| 878 if (node.function.body != null) { | |
| 879 node.function.body = new InvalidStatement()..parent = node.function; | |
|
Kevin Millikin (Google)
2017/01/19 11:40:20
Is there something else we could put here than Inv
asgerf
2017/01/19 11:46:30
Changed to a 'throw'. Should make it easier to deb
| |
| 880 } | |
| 881 } | |
| 882 node.function.asyncMarker = AsyncMarker.Sync; | |
| 883 } else if (node is Field) { | |
| 884 node.initializer = null; | |
| 885 } | |
| 690 } | 886 } |
| 691 return node; | 887 return node; |
| 692 } | 888 } |
| 693 | 889 |
| 694 TreeNode defaultTreeNode(TreeNode node) { | 890 TreeNode defaultTreeNode(TreeNode node) { |
| 695 return node; // Do not traverse into other nodes. | 891 return node; // Do not traverse into other nodes. |
| 696 } | 892 } |
| 697 } | 893 } |
| 698 | 894 |
| 699 class _ExternalTypeVisitor extends DartTypeVisitor { | 895 class _ExternalTypeVisitor extends DartTypeVisitor { |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 713 type?.accept(this); | 909 type?.accept(this); |
| 714 } else if (isContravariant) { | 910 } else if (isContravariant) { |
| 715 type?.accept(shaker._covariantVisitor); | 911 type?.accept(shaker._covariantVisitor); |
| 716 } else { | 912 } else { |
| 717 type?.accept(shaker._contravariantVisitor); | 913 type?.accept(shaker._contravariantVisitor); |
| 718 } | 914 } |
| 719 } | 915 } |
| 720 | 916 |
| 721 visitCovariant(DartType type) => type?.accept(this); | 917 visitCovariant(DartType type) => type?.accept(this); |
| 722 | 918 |
| 723 visitBivariant(DartType type) => shaker._bivariantVisitor.visit(type); | 919 visitInvariant(DartType type) => shaker._invariantVisitor.visit(type); |
| 724 | 920 |
| 725 visitInvalidType(InvalidType node) {} | 921 visitInvalidType(InvalidType node) {} |
| 726 | 922 |
| 727 visitDynamicType(DynamicType node) { | 923 visitDynamicType(DynamicType node) { |
| 728 // TODO(asgerf): Find a suitable model for untyped externals, e.g. track | 924 // TODO(asgerf): Find a suitable model for untyped externals, e.g. track |
| 729 // them to the first type boundary. | 925 // them to the first type boundary. |
| 730 } | 926 } |
| 731 | 927 |
| 732 visitVoidType(VoidType node) {} | 928 visitVoidType(VoidType node) {} |
| 733 | 929 |
| 734 visitInterfaceType(InterfaceType node) { | 930 visitInterfaceType(InterfaceType node) { |
| 735 if (isCovariant) { | 931 if (isCovariant) { |
| 736 shaker._addInstantiatedExternalSubclass(node.classNode); | 932 shaker._addInstantiatedExternalSubclass(node.classNode); |
| 737 } | 933 } |
| 738 if (isContravariant) { | 934 if (isContravariant) { |
| 739 shaker._addEscapedClass(node.classNode); | 935 shaker._addEscapedClass(node.classNode); |
| 740 } | 936 } |
| 741 for (int i = 0; i < node.typeArguments.length; ++i) { | 937 for (int i = 0; i < node.typeArguments.length; ++i) { |
| 742 DartType typeArgument = node.typeArguments[i]; | 938 DartType typeArgument = node.typeArguments[i]; |
| 743 // In practice we don't get much out of analyzing variance here, so | 939 // In practice we don't get much out of analyzing variance here, so |
| 744 // just use a whitelist of classes that can be seen as covariant | 940 // just use a whitelist of classes that can be seen as covariant |
| 745 // for external purposes. | 941 // for external purposes. |
| 746 // TODO(asgerf): Variance analysis might pay off for other external APIs. | 942 // TODO(asgerf): Variance analysis might pay off for other external APIs. |
| 747 if (isWhitelistedCovariant(node.classNode)) { | 943 if (isWhitelistedCovariant(node.classNode)) { |
| 748 visitCovariant(typeArgument); | 944 visitCovariant(typeArgument); |
| 749 } else { | 945 } else { |
| 750 visitBivariant(typeArgument); | 946 visitInvariant(typeArgument); |
| 751 } | 947 } |
| 752 } | 948 } |
| 753 } | 949 } |
| 754 | 950 |
| 755 visitFunctionType(FunctionType node) { | 951 visitFunctionType(FunctionType node) { |
| 756 visit(node.returnType); | 952 visit(node.returnType); |
| 757 for (int i = 0; i < node.positionalParameters.length; ++i) { | 953 for (int i = 0; i < node.positionalParameters.length; ++i) { |
| 758 visitContravariant(node.positionalParameters[i]); | 954 visitContravariant(node.positionalParameters[i]); |
| 759 } | 955 } |
| 760 for (int i = 0; i < node.namedParameters.length; ++i) { | 956 for (int i = 0; i < node.namedParameters.length; ++i) { |
| 761 visitContravariant(node.namedParameters[i].type); | 957 visitContravariant(node.namedParameters[i].type); |
| 762 } | 958 } |
| 763 } | 959 } |
| 764 | 960 |
| 765 visitTypeParameterType(TypeParameterType node) {} | 961 visitTypeParameterType(TypeParameterType node) {} |
| 766 | 962 |
| 767 /// Just treat a couple of whitelisted classes as having covariant type | 963 /// Just treat a couple of whitelisted classes as having covariant type |
| 768 /// parameters. | 964 /// parameters. |
| 769 bool isWhitelistedCovariant(Class classNode) { | 965 bool isWhitelistedCovariant(Class classNode) { |
| 770 if (classNode.typeParameters.isEmpty) return false; | 966 if (classNode.typeParameters.isEmpty) return false; |
| 771 CoreTypes coreTypes = shaker.coreTypes; | 967 CoreTypes coreTypes = shaker.coreTypes; |
| 772 return classNode == coreTypes.iteratorClass || | 968 return classNode == coreTypes.iteratorClass || |
| 773 classNode == coreTypes.iterableClass || | 969 classNode == coreTypes.iterableClass || |
| 774 classNode == coreTypes.futureClass || | 970 classNode == coreTypes.futureClass || |
| 775 classNode == coreTypes.streamClass || | 971 classNode == coreTypes.streamClass || |
| 776 classNode == coreTypes.listClass || | 972 classNode == coreTypes.listClass || |
| 777 classNode == coreTypes.mapClass; | 973 classNode == coreTypes.mapClass; |
| 778 } | 974 } |
| 779 } | 975 } |
| 976 | |
| 977 /// Exception that is thrown to stop the tree shaking analysis when a use | |
| 978 /// of `dart:mirrors` is found. | |
| 979 class _UsingMirrorsException {} | |
| OLD | NEW |