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 import 'dart:collection' show HashMap, HashSet, Queue; | 5 import 'dart:collection' show HashMap, HashSet, Queue; |
6 | 6 |
7 import 'package:analyzer/dart/ast/ast.dart' show Identifier; | |
8 import 'package:analyzer/dart/element/element.dart'; | 7 import 'package:analyzer/dart/element/element.dart'; |
| 8 import 'package:analyzer/dart/element/type.dart' show InterfaceType; |
9 import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl; | 9 import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl; |
10 | 10 |
11 import '../js_ast/js_ast.dart' as JS; | 11 import '../js_ast/js_ast.dart' as JS; |
12 import 'element_helpers.dart'; | 12 import 'element_helpers.dart'; |
| 13 import 'extension_types.dart'; |
13 import 'js_names.dart' as JS; | 14 import 'js_names.dart' as JS; |
14 | 15 |
15 /// Dart allows all fields to be overridden. | 16 /// Dart allows all fields to be overridden. |
16 /// | 17 /// |
17 /// To prevent a performance/code size penalty for allowing this, we analyze | 18 /// To prevent a performance/code size penalty for allowing this, we analyze |
18 /// private classes within each library that is being compiled to determine | 19 /// private classes within each library that is being compiled to determine |
19 /// if those fields should be virtual or not. In effect, we devirtualize fields | 20 /// if those fields should be virtual or not. In effect, we devirtualize fields |
20 /// when possible by analyzing the class hierarchy and using knowledge of | 21 /// when possible by analyzing the class hierarchy and using knowledge of |
21 /// which members are private and thus, could not be overridden outside of the | 22 /// which members are private and thus, could not be overridden outside of the |
22 /// current library. | 23 /// current library. |
(...skipping 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
177 /// The set of inherited getters, used because JS getters/setters are paired, | 178 /// The set of inherited getters, used because JS getters/setters are paired, |
178 /// so if we're generating a setter we may need to emit a getter that calls | 179 /// so if we're generating a setter we may need to emit a getter that calls |
179 /// super. | 180 /// super. |
180 final inheritedGetters = new HashSet<String>(); | 181 final inheritedGetters = new HashSet<String>(); |
181 | 182 |
182 /// The set of inherited setters, used because JS getters/setters are paired, | 183 /// The set of inherited setters, used because JS getters/setters are paired, |
183 /// so if we're generating a getter we may need to emit a setter that calls | 184 /// so if we're generating a getter we may need to emit a setter that calls |
184 /// super. | 185 /// super. |
185 final inheritedSetters = new HashSet<String>(); | 186 final inheritedSetters = new HashSet<String>(); |
186 | 187 |
187 ClassPropertyModel.build(VirtualFieldModel fieldModel, ClassElement classElem, | 188 final mockMembers = <String, ExecutableElement>{}; |
188 Iterable<ExecutableElement> extensionMembers) { | 189 |
| 190 final extensionMembers = new Set<ExecutableElement>(); |
| 191 final mixinExtensionMembers = new Set<ExecutableElement>(); |
| 192 |
| 193 ClassPropertyModel.build(ExtensionTypeSet extensionTypes, |
| 194 VirtualFieldModel fieldModel, ClassElement classElem) { |
189 // Visit superclasses to collect information about their fields/accessors. | 195 // Visit superclasses to collect information about their fields/accessors. |
190 // This is expensive so we try to collect everything in one pass. | 196 // This is expensive so we try to collect everything in one pass. |
191 for (var base in getSuperclasses(classElem)) { | 197 for (var base in getSuperclasses(classElem)) { |
192 for (var accessor in base.accessors) { | 198 for (var accessor in base.accessors) { |
193 // For getter/setter pairs only process them once. | 199 // For getter/setter pairs only process them once. |
194 if (accessor.correspondingGetter != null) continue; | 200 if (accessor.correspondingGetter != null) continue; |
195 | 201 |
196 var field = accessor.variable; | 202 var field = accessor.variable; |
197 var name = field.name; | |
198 // Ignore private names from other libraries. | 203 // Ignore private names from other libraries. |
199 if (Identifier.isPrivateName(name) && | 204 if (field.isPrivate && accessor.library != classElem.library) { |
200 accessor.library != classElem.library) { | |
201 continue; | 205 continue; |
202 } | 206 } |
203 | 207 |
204 if (field.getter?.isAbstract == false) inheritedGetters.add(name); | 208 if (field.getter?.isAbstract == false) inheritedGetters.add(field.name); |
205 if (field.setter?.isAbstract == false) inheritedSetters.add(name); | 209 if (field.setter?.isAbstract == false) inheritedSetters.add(field.name); |
206 } | 210 } |
207 } | 211 } |
208 | 212 |
209 var extensionNames = | 213 _collectMockMembers(classElem.type); |
210 new HashSet<String>.from(extensionMembers.map((e) => e.name)); | 214 _collectExtensionMembers(extensionTypes, classElem); |
| 215 |
| 216 var virtualAccessorNames = new HashSet<String>() |
| 217 ..addAll(inheritedGetters) |
| 218 ..addAll(inheritedSetters) |
| 219 ..addAll(extensionMembers |
| 220 .map((m) => m is PropertyAccessorElement ? m.variable.name : m.name)) |
| 221 ..addAll(mockMembers.values |
| 222 .map((m) => m is PropertyAccessorElement ? m.variable.name : m.name)); |
211 | 223 |
212 // Visit accessors in the current class, and see if they need to be | 224 // Visit accessors in the current class, and see if they need to be |
213 // generated differently based on the inherited fields/accessors. | 225 // generated differently based on the inherited fields/accessors. |
214 for (var accessor in classElem.accessors) { | 226 for (var accessor in classElem.accessors) { |
215 // For getter/setter pairs only process them once. | 227 // For getter/setter pairs only process them once. |
216 if (accessor.correspondingGetter != null) continue; | 228 if (accessor.correspondingGetter != null) continue; |
217 // Also ignore abstract fields. | 229 // Also ignore abstract fields. |
218 if (accessor.isAbstract) continue; | 230 if (accessor.isAbstract) continue; |
219 | 231 |
220 var field = accessor.variable; | 232 var field = accessor.variable; |
221 var name = field.name; | 233 var name = field.name; |
222 // Is it a field? | 234 // Is it a field? |
223 if (!field.isSynthetic && field is FieldElementImpl) { | 235 if (!field.isSynthetic && field is FieldElementImpl) { |
224 if (inheritedGetters.contains(name) || | 236 if (virtualAccessorNames.contains(name) || |
225 inheritedSetters.contains(name) || | |
226 extensionNames.contains(name) || | |
227 fieldModel.isVirtual(field)) { | 237 fieldModel.isVirtual(field)) { |
228 if (field.isStatic) { | 238 if (field.isStatic) { |
229 staticFieldOverrides.add(field); | 239 staticFieldOverrides.add(field); |
230 } else { | 240 } else { |
231 virtualFields[field] = new JS.TemporaryId(name); | 241 virtualFields[field] = new JS.TemporaryId(name); |
232 } | 242 } |
233 } | 243 } |
234 } | 244 } |
235 } | 245 } |
236 } | 246 } |
| 247 |
| 248 void _collectMockMembers(InterfaceType type) { |
| 249 // TODO(jmesserly): every type with nSM will generate new stubs for all |
| 250 // abstract members. For example: |
| 251 // |
| 252 // class C { m(); noSuchMethod(...) { ... } } |
| 253 // class D extends C { m(); noSuchMethod(...) { ... } } |
| 254 // |
| 255 // We'll generate D.m even though it is not necessary. |
| 256 // |
| 257 // Doing better is a bit tricky, as our current codegen strategy for the |
| 258 // mock methods encodes information about the number of arguments (and type |
| 259 // arguments) that D expects. |
| 260 var element = type.element; |
| 261 if (!hasNoSuchMethod(element)) return; |
| 262 |
| 263 // Collect all unimplemented members. |
| 264 // |
| 265 // Initially, we track abstract and concrete members separately, then |
| 266 // remove concrete from the abstract set. This is done because abstract |
| 267 // members are allowed to "override" concrete ones in Dart. |
| 268 // (In that case, it will still be treated as a concrete member and can be |
| 269 // called at runtime.) |
| 270 var concreteMembers = new HashSet<String>(); |
| 271 |
| 272 void visit(InterfaceType type, bool isAbstract) { |
| 273 if (type == null) return; |
| 274 visit(type.superclass, isAbstract); |
| 275 for (var m in type.mixins) visit(m, isAbstract); |
| 276 for (var i in type.interfaces) visit(i, true); |
| 277 |
| 278 for (var m in [type.methods, type.accessors].expand((m) => m)) { |
| 279 if (isAbstract || m.isAbstract) { |
| 280 mockMembers[m.name] = m; |
| 281 } else if (!m.isStatic) { |
| 282 concreteMembers.add(m.name); |
| 283 } |
| 284 } |
| 285 } |
| 286 |
| 287 visit(type, false); |
| 288 |
| 289 concreteMembers.forEach(mockMembers.remove); |
| 290 } |
| 291 |
| 292 void _collectExtensionMembers( |
| 293 ExtensionTypeSet extensionTypes, ClassElement element) { |
| 294 if (extensionTypes.isNativeClass(element)) return; |
| 295 |
| 296 // Collect all extension types we implement. |
| 297 var type = element.type; |
| 298 var types = extensionTypes.collectNativeInterfaces(element); |
| 299 if (types.isEmpty) return; |
| 300 |
| 301 // Collect all possible extension method names. |
| 302 var possibleExtensions = new HashSet<String>(); |
| 303 for (var t in types) { |
| 304 for (var m in [t.methods, t.accessors].expand((m) => m)) { |
| 305 if (!m.isStatic && m.isPublic) possibleExtensions.add(m.name); |
| 306 } |
| 307 } |
| 308 |
| 309 // Collect all of extension methods this type and its mixins implement. |
| 310 |
| 311 void visitType(InterfaceType type, bool isMixin) { |
| 312 for (var mixin in type.mixins) { |
| 313 // If the mixin isn't native, make sure to visit it too, because those |
| 314 // methods haven't been accounted for yet. |
| 315 if (!extensionTypes.hasNativeSubtype(mixin)) visitType(mixin, true); |
| 316 } |
| 317 for (var m in [type.methods, type.accessors].expand((m) => m)) { |
| 318 if (!m.isAbstract && possibleExtensions.contains(m.name)) { |
| 319 (isMixin ? mixinExtensionMembers : extensionMembers).add(m); |
| 320 } |
| 321 } |
| 322 } |
| 323 |
| 324 visitType(type, false); |
| 325 |
| 326 for (var m in mockMembers.values) { |
| 327 if (possibleExtensions.contains(m.name)) extensionMembers.add(m); |
| 328 } |
| 329 } |
237 } | 330 } |
OLD | NEW |