OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import 'dart:collection' show HashMap, HashSet, Queue; | |
6 | |
7 import 'package:analyzer/dart/ast/ast.dart' show Identifier; | |
8 import 'package:analyzer/dart/element/element.dart'; | |
9 import 'package:analyzer/dart/element/type.dart'; | |
10 import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl; | |
11 | |
12 import '../js_ast/js_ast.dart' as JS; | |
13 import 'element_helpers.dart'; | |
14 import 'js_names.dart' as JS; | |
15 | |
16 /// Dart allows all fields to be overridden. | |
17 /// | |
18 /// To prevent a performance/code size penalty for allowing this, we analyze | |
19 /// private classes within each library that is being compiled to determine | |
20 /// if those fields should be virtual or not. In effect, we devirtualize fields | |
21 /// when possible by analyzing the class hierarchy and using knowledge of | |
22 /// which members are private and thus, could not be overridden outside of the | |
23 /// current library. | |
24 class VirtualFieldModel { | |
25 final _modelForLibrary = | |
26 new HashMap<LibraryElement, _LibraryVirtualFieldModel>(); | |
27 | |
28 _LibraryVirtualFieldModel _getModel(LibraryElement library) => | |
29 _modelForLibrary.putIfAbsent( | |
30 library, () => new _LibraryVirtualFieldModel.build(library)); | |
31 | |
32 /// Returns true if a field is virtual. | |
33 bool isVirtual(FieldElement field) => | |
34 _getModel(field.library).isVirtual(field); | |
35 } | |
36 | |
37 /// This is a building block of [VirtualFieldModel], used to track information | |
38 /// about a single library that has been analyzed. | |
39 class _LibraryVirtualFieldModel { | |
40 /// Fields that are private (or public fields of a private class) and | |
41 /// overridden in this library. | |
42 /// | |
43 /// This means we must generate them as virtual fields using a property pair | |
44 /// in JavaScript. | |
45 final _overriddenPrivateFields = new HashSet<FieldElement>(); | |
46 | |
47 /// Private classes that can be extended outside of this library. | |
48 /// | |
49 /// Normally private classes cannot be accessed outside this library, however, | |
50 /// this can happen if they are extended by a public class, for example: | |
51 /// | |
52 /// class _A { int x = 42; } | |
53 /// class _B { int x = 42; } | |
54 /// | |
55 /// // _A is now effectively public for the purpose of overrides. | |
56 /// class C extends _A {} | |
57 /// | |
58 /// The class _A must treat is "x" as virtual, however _B does not. | |
59 final _extensiblePrivateClasses = new HashSet<ClassElement>(); | |
60 | |
61 _LibraryVirtualFieldModel.build(LibraryElement library) { | |
62 var allTypes = library.units.expand((u) => u.types).toList(); | |
63 | |
64 // The set of public types is our initial extensible type set. | |
65 // From there, visit all immediate private types in this library, and so on | |
66 // from those private types, marking them as extensible. | |
67 var typesToVisit = | |
68 new Queue<ClassElement>.from(allTypes.where((t) => t.isPublic)); | |
69 while (typesToVisit.isNotEmpty) { | |
70 var extensibleType = typesToVisit.removeFirst(); | |
71 | |
72 // For each supertype of a public type in this library, | |
73 // if we encounter a private class, we mark it as being extended, and | |
74 // add it to our work set if this is the first time we've visited it. | |
75 for (var type in getImmediateSuperclasses(extensibleType)) { | |
76 if (!type.isPublic && type.library == library) { | |
77 if (_extensiblePrivateClasses.add(type)) typesToVisit.add(type); | |
78 } | |
79 } | |
80 } | |
81 | |
82 // ClassElement can only look up inherited members with an O(N) scan through | |
83 // the class, so we build up a mapping of all fields in the library ahead of | |
84 // time. | |
85 var allFields = | |
86 new HashMap<ClassElement, HashMap<String, FieldElement>>.fromIterable( | |
87 allTypes, | |
88 value: (t) => new HashMap.fromIterable( | |
89 t.fields.where((f) => !f.isStatic), | |
90 key: (f) => f.name)); | |
91 | |
92 for (var type in allTypes) { | |
93 Set<ClassElement> supertypes = null; | |
94 | |
95 // Visit accessors in the current class, and see if they override an | |
96 // otherwise private field. | |
97 for (var accessor in type.accessors) { | |
98 // For getter/setter pairs only process them once. | |
99 if (accessor.correspondingGetter != null) continue; | |
100 // Ignore abstract or static accessors. | |
101 if (accessor.isAbstract || accessor.isStatic) continue; | |
102 // Ignore public accessors in extensible classes. | |
103 if (accessor.isPublic && | |
104 (type.isPublic || _extensiblePrivateClasses.contains(type))) { | |
105 continue; | |
106 } | |
107 | |
108 if (supertypes == null) { | |
109 supertypes = new Set(); | |
110 var library = type.library; | |
111 void collectSupertypes(ClassElement cls) { | |
112 // Invariant: we never need to leave the current library. | |
113 // This works because we are only checking for field overrides for | |
114 // those we know cannot be overridden outside the current library, | |
115 // such as private fields and public fields of non-extensible | |
116 // private classes. | |
117 if (cls.library != library) return; | |
vsm
2017/03/28 13:28:32
I don't think this is quite right. If I patch you
Jennifer Messerly
2017/03/28 16:48:22
I was trying really hard to keep the search cheap
vsm
2017/03/28 17:12:33
You might get most of the benefit from checking if
| |
118 if (!identical(type, cls) && !supertypes.add(cls)) return; | |
119 | |
120 var s = cls.supertype?.element; | |
121 if (s != null) collectSupertypes(s); | |
122 cls.mixins.forEach((m) => collectSupertypes(m.element)); | |
123 } | |
124 | |
125 collectSupertypes(type); | |
126 } | |
127 | |
128 // Look in all super classes to see if we're overriding a field in our | |
129 // library, if so mark that field as overridden. | |
130 var name = accessor.variable.name; | |
131 _overriddenPrivateFields.addAll( | |
132 supertypes.map((c) => allFields[c][name]).where((f) => f != null)); | |
133 } | |
134 } | |
135 } | |
136 | |
137 /// Returns true if a field inside this library is virtual. | |
138 bool isVirtual(FieldElement field) { | |
139 // If the field was marked non-virtual, we know for sure. | |
140 if (!field.isVirtual) return false; | |
141 if (field.isStatic) return false; | |
142 | |
143 var type = field.enclosingElement; | |
144 var library = type.library; | |
145 if (library.isInSdk && library.source.uri.toString().startsWith('dart:_')) { | |
146 // There should be no extensible fields in private SDK libraries. | |
147 return false; | |
148 } | |
149 | |
150 if (field.isPublic) { | |
151 // Public fields in public classes (or extensible private classes) | |
152 // are always virtual. | |
153 // They could be overridden by someone using our library. | |
154 if (type.isPublic) return true; | |
155 if (_extensiblePrivateClasses.contains(type)) return true; | |
156 } | |
157 | |
158 // Otherwise, the field is effectively private and we only need to make it | |
159 // virtual if it's overridden. | |
160 return _overriddenPrivateFields.contains(field); | |
161 } | |
162 } | |
163 | |
164 /// Tracks how fields, getters and setters are represented when emitting JS. | |
165 /// | |
166 /// Dart classes have implicit features that must be made explicit: | |
167 /// | |
168 /// - virtual fields induce a getter and setter pair. | |
169 /// - getters and setters are independent. | |
170 /// - getters and setters can be overridden. | |
171 /// | |
172 class ClassPropertyModel { | |
173 /// Fields that are virtual, that is, they must be generated as a property | |
174 /// pair in JavaScript. | |
175 /// | |
176 /// The value property stores the symbol used for the field's storage slot. | |
177 final virtualFields = <FieldElement, JS.TemporaryId>{}; | |
178 | |
179 /// Static fields that are overridden, this does not matter for Dart but in | |
180 /// JS we need to take care initializing these because JS classes inherit | |
181 /// statics. | |
182 final staticFieldOverrides = new HashSet<FieldElement>(); | |
183 | |
184 /// The set of inherited getters, used because JS getters/setters are paired, | |
185 /// so if we're generating a setter we may need to emit a getter that calls | |
186 /// super. | |
187 final inheritedGetters = new HashSet<String>(); | |
188 | |
189 /// The set of inherited setters, used because JS getters/setters are paired, | |
190 /// so if we're generating a getter we may need to emit a setter that calls | |
191 /// super. | |
192 final inheritedSetters = new HashSet<String>(); | |
193 | |
194 ClassPropertyModel.build(VirtualFieldModel fieldModel, ClassElement classElem, | |
195 Iterable<ExecutableElement> extensionMembers) { | |
196 // Visit superclasses to collect information about their fields/accessors. | |
197 // This is expensive so we try to collect everything in one pass. | |
198 for (var base in getSuperclasses(classElem)) { | |
199 for (var accessor in base.accessors) { | |
200 // For getter/setter pairs only process them once. | |
201 if (accessor.correspondingGetter != null) continue; | |
202 | |
203 var field = accessor.variable; | |
204 var name = field.name; | |
205 // Ignore private names from other libraries. | |
206 if (Identifier.isPrivateName(name) && | |
207 accessor.library != classElem.library) { | |
208 continue; | |
209 } | |
210 | |
211 if (field.getter?.isAbstract == false) inheritedGetters.add(name); | |
212 if (field.setter?.isAbstract == false) inheritedSetters.add(name); | |
213 } | |
214 } | |
215 | |
216 var extensionNames = | |
217 new HashSet<String>.from(extensionMembers.map((e) => e.name)); | |
218 | |
219 // Visit accessors in the current class, and see if they need to be | |
220 // generated differently based on the inherited fields/accessors. | |
221 for (var accessor in classElem.accessors) { | |
222 // For getter/setter pairs only process them once. | |
223 if (accessor.correspondingGetter != null) continue; | |
224 // Also ignore abstract fields. | |
225 if (accessor.isAbstract) continue; | |
226 | |
227 var field = accessor.variable; | |
228 var name = field.name; | |
229 // Is it a field? | |
230 if (!field.isSynthetic && field is FieldElementImpl) { | |
231 if (inheritedGetters.contains(name) || | |
232 inheritedSetters.contains(name) || | |
233 extensionNames.contains(name) || | |
234 fieldModel.isVirtual(field)) { | |
235 if (field.isStatic) { | |
236 staticFieldOverrides.add(field); | |
237 } else { | |
238 virtualFields[field] = new JS.TemporaryId(name); | |
239 } | |
240 } | |
241 } | |
242 } | |
243 } | |
244 } | |
OLD | NEW |