OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015, 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; | |
6 import 'package:analyzer/src/generated/ast.dart'; | |
7 import 'package:analyzer/src/generated/element.dart'; | |
8 import 'package:dev_compiler/src/dependency_graph.dart' show corelibOrder; | |
9 | |
10 typedef void ModuleItemEmitter(AstNode item); | |
11 | |
12 /// Helper that tracks order of elements visited by the compiler, detecting | |
13 /// if the top level item can be loaded eagerly or not. | |
14 class ModuleItemLoadOrder { | |
15 | |
16 /// The order that elements should be emitted in, with a bit indicating if | |
17 /// the element should be generated lazily. The value will be `false` if | |
18 /// the item could not be loaded, and needs to be made lazy. | |
19 /// | |
20 /// The order will match the original source order, unless something needed to | |
21 /// be moved sooner to satisfy dependencies. | |
22 /// | |
23 /// Sometimes it's impossible to ensure an ordering when confronting library | |
24 /// cycles. In that case, we mark the definition as lazy. | |
25 final _loaded = new Map<Element, bool>.identity(); | |
vsm
2015/05/12 18:00:11
Can you add a comment on why identity? Should all
Jennifer Messerly
2015/05/12 18:47:04
ah, I can remove. This was to workaround the temp-
| |
26 | |
27 final _declarationNodes = new HashMap<Element, AstNode>.identity(); | |
28 | |
29 /// The stack of currently emitting elements, if generating top-level code | |
30 /// for them. This is not used when inside method bodies, because order does | |
31 /// not matter for those. | |
32 final _topLevelElements = new List<Element>(); | |
33 | |
34 /// The current element being loaded. | |
35 /// We can use this to determine if we're loading top-level code or not: | |
36 /// | |
37 /// _currentElements.last == _topLevelElements.last | |
38 final _currentElements = new List<Element>(); | |
39 | |
40 /// Memoized results of [_inLibraryCycle]. | |
41 final _libraryCycleMemo = new HashMap<LibraryElement, bool>(); | |
42 | |
43 final ModuleItemEmitter _emitModuleItem; | |
44 | |
45 LibraryElement _currentLibrary; | |
46 | |
47 ModuleItemLoadOrder(this._emitModuleItem); | |
48 | |
49 bool isLoaded(Element e) => _loaded[e] == true; | |
50 | |
51 /// Collect top-level elements and nodes we need to emit. | |
52 void collectElements( | |
53 LibraryElement library, Iterable<CompilationUnit> partsThenLibrary) { | |
54 assert(_currentLibrary == null); | |
55 _currentLibrary = library; | |
56 | |
57 for (var unit in partsThenLibrary) { | |
58 for (var decl in unit.declarations) { | |
59 _declarationNodes[decl.element] = decl; | |
60 | |
61 if (decl is ClassDeclaration) { | |
62 for (var member in decl.members) { | |
63 if (member is FieldDeclaration && member.isStatic) { | |
64 _collectElementsForVariable(member.fields); | |
65 } | |
66 } | |
67 } else if (decl is TopLevelVariableDeclaration) { | |
68 _collectElementsForVariable(decl.variables); | |
69 } | |
70 } | |
71 } | |
72 } | |
73 | |
74 void _collectElementsForVariable(VariableDeclarationList fields) { | |
75 for (var field in fields.variables) { | |
76 _declarationNodes[field.element] = field; | |
77 } | |
78 } | |
79 | |
80 /// Start generating top-level code for the element [e]. | |
81 /// Subsequent [loadElement] calls will cause those elements to be generated | |
82 /// before this one, until [finishElement] is called. | |
83 void startTopLevel(Element e) { | |
84 assert(isCurrentElement(e)); | |
85 // Assume loading will succeed until proven otherwise. | |
86 _loaded[e] = true; | |
87 _topLevelElements.add(e); | |
88 } | |
89 | |
90 /// Finishes the top-level code for the element [e]. | |
91 void finishTopLevel(Element e) { | |
92 var last = _topLevelElements.removeLast(); | |
93 assert(identical(e, last)); | |
94 } | |
95 | |
96 // Starts generating code for the element [e]. | |
97 // | |
98 // Normally this is called implicitly by [loadDeclaration] and/or [loadElement ]. | |
vsm
2015/05/12 18:00:11
line len?
Jennifer Messerly
2015/05/12 18:47:04
Done.
| |
99 // However, for synthetic elements (like temporary variables) it must be | |
100 // called explicitly, and paired with a call to [finishElement]. | |
101 void startDeclaration(Element e) { | |
102 // Assume load will succeed until proven otherwise. | |
103 _loaded[e] = true; | |
104 _currentElements.add(e); | |
105 } | |
106 | |
107 /// Returns true if all dependencies were loaded successfully. | |
108 bool finishDeclaration(Element e) { | |
109 var last = _currentElements.removeLast(); | |
110 assert(identical(e, last)); | |
111 return _loaded[e]; | |
112 } | |
113 | |
114 /// Loads a top-level declaration. This is similar to [loadElement], but it | |
115 /// ensures we always visit the node if it has not been emitted yet. In other | |
116 /// words, it handles nodes that do not need dependency resolution like | |
117 /// top-level functions. | |
118 bool loadDeclaration(AstNode node, Element e) { | |
119 assert(e.library == _currentLibrary); | |
120 | |
121 // If we already tried to load it, see if we succeeded or not. | |
122 // Otherwise try and load now. | |
123 var loaded = _loaded[e]; | |
124 if (loaded != null) return loaded; | |
125 | |
126 // Otherwise, try to load it. | |
127 startDeclaration(e); | |
128 _emitModuleItem(node); | |
129 return finishDeclaration(e); | |
130 } | |
131 | |
132 bool isCurrentElement(Element e) => | |
133 _currentElements.isNotEmpty && identical(e, _currentElements.last); | |
134 | |
135 /// To emit top-level module items, we sometimes need to reorder them. | |
136 /// | |
137 /// This function takes care of that, and also detects cases where reordering | |
138 /// failed, and we need to resort to lazy loading, by marking the element as | |
139 /// lazy. All elements need to be aware of this possibility and generate code | |
140 /// accordingly. | |
141 /// | |
142 /// If we are not emitting top-level code, this does nothing, because all | |
143 /// declarations are assumed to be available before we start execution. | |
144 /// See [startTopLevel]. | |
145 void declareBeforeUse(Element e) { | |
146 if (e == null || _topLevelElements.isEmpty) return; | |
147 if (!isCurrentElement(_topLevelElements.last)) return; | |
148 | |
149 // If the item is from our library, try to emit it now. | |
150 bool loaded; | |
151 if (e.library == _currentLibrary) { | |
152 // Type parameters are not in scope when generating hoisted fields. | |
153 if (e is TypeParameterElement && | |
154 _currentElements.last is VariableElement) { | |
155 loaded = false; | |
156 } else { | |
157 var node = _declarationNodes[e]; | |
158 loaded = node == null || loadDeclaration(node, e); | |
159 } | |
160 } else { | |
161 // We can't force things from other libraries to be emitted in a different | |
162 // order. Instead, we see if the library itself can be loaded before the | |
163 // current library. Generally that is possible, unless we have cyclic | |
164 // imports. | |
165 loaded = libraryIsLoaded(e.library); | |
166 } | |
167 | |
168 if (loaded) return; | |
169 | |
170 // If we failed to emit it, then we need to make sure all currently emitting | |
171 // elements are generated in a lazy way. | |
172 for (var current in _topLevelElements) { | |
173 // TODO(jmesserly): if we want to log what triggered laziness, this is | |
174 // the place to do so. | |
175 _loaded[current] = false; | |
176 } | |
177 } | |
178 | |
179 bool libraryIsLoaded(LibraryElement library) { | |
180 assert(library != _currentLibrary); | |
181 | |
182 if (library == null) return true; | |
vsm
2015/05/12 18:00:11
When would this be null?
Jennifer Messerly
2015/05/12 18:47:04
good catch. This showed up in the code at one poin
| |
183 | |
184 // The SDK is a special case: we optimize the order to prevent laziness. | |
185 if (library.isInSdk) { | |
186 // SDK is loaded before non-SDK libraries | |
187 if (!_currentLibrary.isInSdk) return true; | |
188 | |
189 // Compute the order of both SDK libraries. If unknown, assume it's after. | |
190 var order = corelibOrder.indexOf(library.name); | |
191 if (order == -1) order = corelibOrder.length; | |
192 | |
193 var currentOrder = corelibOrder.indexOf(_currentLibrary.name); | |
194 if (currentOrder == -1) currentOrder = corelibOrder.length; | |
195 | |
196 // If the dart:* library we are currently compiling is loaded after the | |
197 // class's library, then we know the class is available. | |
198 if (order != currentOrder) return currentOrder > order; | |
199 | |
200 // If we don't know the order of the class's library or the current | |
201 // library, do the normal cycle check. (Not all SDK libs are cycles.) | |
202 } | |
203 | |
204 return !_inLibraryCycle(library); | |
205 } | |
206 | |
207 /// Returns true if [library] depends on the [currentLibrary] via some | |
208 /// transitive import. | |
209 bool _inLibraryCycle(LibraryElement library) { | |
210 // SDK libs don't depend on things outside the SDK. | |
211 // (We can reach this via the recursive call below.) | |
212 if (library.isInSdk && !_currentLibrary.isInSdk) return false; | |
213 | |
214 var result = _libraryCycleMemo[library]; | |
215 if (result != null) return result; | |
216 | |
217 result = library == _currentLibrary; | |
218 _libraryCycleMemo[library] = result; | |
219 for (var e in library.imports) { | |
220 if (result) break; | |
221 result = _inLibraryCycle(e.importedLibrary); | |
222 } | |
223 for (var e in library.exports) { | |
224 if (result) break; | |
225 result = _inLibraryCycle(e.exportedLibrary); | |
226 } | |
227 return _libraryCycleMemo[library] = result; | |
228 } | |
229 } | |
OLD | NEW |