OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library dart2js.library_loader; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart2jslib.dart' | |
9 show Compiler, | |
10 CompilerTask, | |
11 MessageKind, | |
12 Script, | |
13 invariant; | |
14 import 'elements/elements.dart' | |
15 show CompilationUnitElement, | |
16 Element, | |
17 LibraryElement, | |
18 PrefixElement; | |
19 import 'elements/modelx.dart' | |
20 show CompilationUnitElementX, | |
21 DeferredLoaderGetterElementX, | |
22 ErroneousElementX, | |
23 LibraryElementX, | |
24 PrefixElementX; | |
25 import 'helpers/helpers.dart'; // Included for debug helpers. | |
26 import 'native/native.dart' as native; | |
27 import 'tree/tree.dart'; | |
28 import 'util/util.dart' show Link, LinkBuilder; | |
29 | |
30 /** | |
31 * [CompilerTask] for loading libraries and setting up the import/export scopes. | |
32 * | |
33 * The library loader uses four different kinds of URIs in different parts of | |
34 * the loading process. | |
35 * | |
36 * ## User URI ## | |
37 * | |
38 * A 'user URI' is a URI provided by the user in code and as the main entry URI | |
39 * at the command line. These generally come in 3 versions: | |
40 * | |
41 * * A relative URI such as 'foo.dart', '../bar.dart', and 'baz/boz.dart'. | |
42 * | |
43 * * A dart URI such as 'dart:core' and 'dart:_js_helper'. | |
44 * | |
45 * * A package URI such as 'package:foo.dart' and 'package:bar/baz.dart'. | |
46 * | |
47 * A user URI can also be absolute, like 'file:///foo.dart' or | |
48 * 'http://example.com/bar.dart', but such URIs cannot necessarily be used for | |
49 * locating source files, since the scheme must be supported by the input | |
50 * provider. The standard input provider for dart2js only supports the 'file' | |
51 * and 'http' scheme. | |
52 * | |
53 * ## Resolved URI ## | |
54 * | |
55 * A 'resolved URI' is a (user) URI that has been resolved to an absolute URI | |
56 * based on the readable URI (see below) from which it was loaded. A URI with an | |
57 * explicit scheme (such as 'dart:', 'package:' or 'file:') is already resolved. | |
58 * A relative URI like for instance '../foo/bar.dart' is translated into an | |
59 * resolved URI in one of three ways: | |
60 * | |
61 * * If provided as the main entry URI at the command line, the URI is resolved | |
62 * relative to the current working directory, say | |
63 * 'file:///current/working/dir/', and the resolved URI is therefore | |
64 * 'file:///current/working/foo/bar.dart'. | |
65 * | |
66 * * If the relative URI is provided in an import, export or part tag, and the | |
67 * readable URI of the enclosing compilation unit is a file URI, | |
68 * 'file://some/path/baz.dart', then the resolved URI is | |
69 * 'file://some/foo/bar.dart'. | |
70 * | |
71 * * If the relative URI is provided in an import, export or part tag, and the | |
72 * readable URI of the enclosing compilation unit is a package URI, | |
73 * 'package:some/path/baz.dart', then the resolved URI is | |
74 * 'package:some/foo/bar.dart'. | |
75 * | |
76 * The resolved URI thus preserves the scheme through resolution: A readable | |
77 * file URI results in an resolved file URI and a readable package URI results | |
78 * in an resolved package URI. Note that since a dart URI is not a readable URI, | |
79 * import, export or part tags within platform libraries are not interpreted as | |
80 * dart URIs but instead relative to the library source file location. | |
81 * | |
82 * The resolved URI of a library is also used as the canonical URI | |
83 * ([LibraryElement.canonicalUri]) by which we identify which libraries are | |
84 * identical. This means that libraries loaded through the 'package' scheme will | |
85 * resolve to the same library when loaded from within using relative URIs (see | |
86 * for instance the test 'standalone/package/package1_test.dart'). But loading a | |
87 * platform library using a relative URI will _not_ result in the same library | |
88 * as when loaded through the dart URI. | |
89 * | |
90 * ## Readable URI ## | |
91 * | |
92 * A 'readable URI' is an absolute URI whose scheme is either 'package' or | |
93 * something supported by the input provider, normally 'file'. Dart URIs such as | |
94 * 'dart:core' and 'dart:_js_helper' are not readable themselves but are instead | |
95 * resolved into a readable URI using the library root URI provided from the | |
96 * command line and the list of platform libraries found in | |
97 * 'sdk/lib/_internal/libraries.dart'. This is done through the | |
98 * [Compiler.translateResolvedUri] method which checks whether a library by that | |
99 * name exists and in case of internal libraries whether access is granted. | |
100 * | |
101 * ## Resource URI ## | |
102 * | |
103 * A 'resource URI' is an absolute URI with a scheme supported by the input | |
104 * provider. For the standard implementation this means a URI with the 'file' | |
105 * scheme. Readable URIs are converted into resource URIs as part of the | |
106 * [Compiler.readScript] method. In the standard implementation the package URIs | |
107 * are converted to file URIs using the package root URI provided on the | |
108 * command line as base. If the package root URI is | |
109 * 'file:///current/working/dir/' then the package URI 'package:foo/bar.dart' | |
110 * will be resolved to the resource URI | |
111 * 'file:///current/working/dir/foo/bar.dart'. | |
112 * | |
113 * The distinction between readable URI and resource URI is necessary to ensure | |
114 * that these imports | |
115 * | |
116 * import 'package:foo.dart' as a; | |
117 * import 'packages/foo.dart' as b; | |
118 * | |
119 * do _not_ resolve to the same library when the package root URI happens to | |
120 * point to the 'packages' folder. | |
121 * | |
122 */ | |
123 abstract class LibraryLoaderTask implements CompilerTask { | |
124 factory LibraryLoaderTask(Compiler compiler) = _LibraryLoaderTask; | |
125 | |
126 /// Returns all libraries that have been loaded. | |
127 Iterable<LibraryElement> get libraries; | |
128 | |
129 /// Looks up the library with the [canonicalUri]. | |
130 LibraryElement lookupLibrary(Uri canonicalUri); | |
131 | |
132 /// Loads the library specified by the [resolvedUri] and returns its | |
133 /// [LibraryElement]. | |
134 /// | |
135 /// If the library is not already loaded, the method creates the | |
136 /// [LibraryElement] for the library and computes the import/export scope, | |
137 /// loading and computing the import/export scopes of all required libraries | |
138 /// in the process. The method handles cyclic dependency between libraries. | |
139 Future<LibraryElement> loadLibrary(Uri resolvedUri); | |
140 | |
141 /// Reset the library loader task to prepare for compilation. If provided, | |
142 /// libraries matching [reuseLibrary] are reused. | |
143 /// | |
144 /// This method is used for incremental compilation. | |
145 void reset({bool reuseLibrary(LibraryElement library)}); | |
146 | |
147 /// Asynchronous version of [reset]. | |
148 Future resetAsync(Future<bool> reuseLibrary(LibraryElement library)); | |
149 } | |
150 | |
151 /// Handle for creating synthesized/patch libraries during library loading. | |
152 abstract class LibraryLoader { | |
153 /// This method must be called when a new synthesized/patch library has been | |
154 /// created to ensure that [library] will part of library dependency graph | |
155 /// used for computing import/export scopes. | |
156 void registerNewLibrary(LibraryElement library); | |
157 | |
158 /// This method must be called when a new synthesized/patch library has been | |
159 /// scanned in order to process the library tags in [library] and thus handle | |
160 /// imports/exports/parts in the synthesized/patch library. | |
161 Future processLibraryTags(LibraryElement library); | |
162 } | |
163 | |
164 /** | |
165 * [CombinatorFilter] is a succinct representation of a list of combinators from | |
166 * a library dependency tag. | |
167 */ | |
168 class CombinatorFilter { | |
169 const CombinatorFilter(); | |
170 | |
171 /** | |
172 * Returns [:true:] if [element] is excluded by this filter. | |
173 */ | |
174 bool exclude(Element element) => false; | |
175 | |
176 /** | |
177 * Creates a filter based on the combinators of [tag]. | |
178 */ | |
179 factory CombinatorFilter.fromTag(LibraryDependency tag) { | |
180 if (tag == null || tag.combinators == null) { | |
181 return const CombinatorFilter(); | |
182 } | |
183 | |
184 // If the list of combinators contain at least one [:show:] we can create | |
185 // a positive list of elements to include, otherwise we create a negative | |
186 // list of elements to exclude. | |
187 bool show = false; | |
188 Set<String> nameSet; | |
189 for (Combinator combinator in tag.combinators) { | |
190 if (combinator.isShow) { | |
191 show = true; | |
192 var set = new Set<String>(); | |
193 for (Identifier identifier in combinator.identifiers) { | |
194 set.add(identifier.source); | |
195 } | |
196 if (nameSet == null) { | |
197 nameSet = set; | |
198 } else { | |
199 nameSet = nameSet.intersection(set); | |
200 } | |
201 } | |
202 } | |
203 if (nameSet == null) { | |
204 nameSet = new Set<String>(); | |
205 } | |
206 for (Combinator combinator in tag.combinators) { | |
207 if (combinator.isHide) { | |
208 for (Identifier identifier in combinator.identifiers) { | |
209 if (show) { | |
210 // We have a positive list => Remove hidden elements. | |
211 nameSet.remove(identifier.source); | |
212 } else { | |
213 // We have no positive list => Accumulate hidden elements. | |
214 nameSet.add(identifier.source); | |
215 } | |
216 } | |
217 } | |
218 } | |
219 return show ? new ShowFilter(nameSet) : new HideFilter(nameSet); | |
220 } | |
221 } | |
222 | |
223 /** | |
224 * A list of combinators represented as a list of element names to include. | |
225 */ | |
226 class ShowFilter extends CombinatorFilter { | |
227 final Set<String> includedNames; | |
228 | |
229 ShowFilter(this.includedNames); | |
230 | |
231 bool exclude(Element element) => !includedNames.contains(element.name); | |
232 } | |
233 | |
234 /** | |
235 * A list of combinators represented as a list of element names to exclude. | |
236 */ | |
237 class HideFilter extends CombinatorFilter { | |
238 final Set<String> excludedNames; | |
239 | |
240 HideFilter(this.excludedNames); | |
241 | |
242 bool exclude(Element element) => excludedNames.contains(element.name); | |
243 } | |
244 | |
245 /** | |
246 * Implementation class for [LibraryLoader]. The distinction between | |
247 * [LibraryLoader] and [LibraryLoaderTask] is made to hide internal members from | |
248 * the [LibraryLoader] interface. | |
249 */ | |
250 class _LibraryLoaderTask extends CompilerTask implements LibraryLoaderTask { | |
251 _LibraryLoaderTask(Compiler compiler) : super(compiler); | |
252 String get name => 'LibraryLoader'; | |
253 | |
254 final Map<Uri, LibraryElement> libraryCanonicalUriMap = | |
255 new Map<Uri, LibraryElement>(); | |
256 final Map<Uri, LibraryElement> libraryResourceUriMap = | |
257 new Map<Uri, LibraryElement>(); | |
258 final Map<String, LibraryElement> libraryNames = | |
259 new Map<String, LibraryElement>(); | |
260 | |
261 LibraryDependencyHandler currentHandler; | |
262 | |
263 Iterable<LibraryElement> get libraries => libraryCanonicalUriMap.values; | |
264 | |
265 LibraryElement lookupLibrary(Uri canonicalUri) { | |
266 return libraryCanonicalUriMap[canonicalUri]; | |
267 } | |
268 | |
269 void reset({bool reuseLibrary(LibraryElement library)}) { | |
270 measure(() { | |
271 assert(currentHandler == null); | |
272 | |
273 Iterable<LibraryElement> reusedLibraries = null; | |
274 if (reuseLibrary != null) { | |
275 reusedLibraries = compiler.reuseLibraryTask.measure(() { | |
276 // Call [toList] to force eager calls to [reuseLibrary]. | |
277 return libraryCanonicalUriMap.values.where(reuseLibrary).toList(); | |
278 }); | |
279 } | |
280 | |
281 resetImplementation(reusedLibraries); | |
282 }); | |
283 } | |
284 | |
285 void resetImplementation(Iterable<LibraryElement> reusedLibraries) { | |
286 measure(() { | |
287 libraryCanonicalUriMap.clear(); | |
288 libraryResourceUriMap.clear(); | |
289 libraryNames.clear(); | |
290 | |
291 if (reusedLibraries != null) { | |
292 reusedLibraries.forEach(mapLibrary); | |
293 } | |
294 }); | |
295 } | |
296 | |
297 Future resetAsync(Future<bool> reuseLibrary(LibraryElement library)) { | |
298 return measure(() { | |
299 assert(currentHandler == null); | |
300 | |
301 Future<LibraryElement> wrapper(LibraryElement library) { | |
302 try { | |
303 return reuseLibrary(library).then( | |
304 (bool reuse) => reuse ? library : null); | |
305 } catch (exception, trace) { | |
306 compiler.diagnoseCrashInUserCode( | |
307 'Uncaught exception in reuseLibrary', exception, trace); | |
308 rethrow; | |
309 } | |
310 } | |
311 | |
312 List<Future<LibraryElement>> reusedLibrariesFuture = | |
313 compiler.reuseLibraryTask.measure( | |
314 () => libraryCanonicalUriMap.values.map(wrapper).toList()); | |
315 | |
316 return Future.wait(reusedLibrariesFuture).then( | |
317 (List<LibraryElement> reusedLibraries) { | |
318 resetImplementation(reusedLibraries.where((e) => e != null)); | |
319 }); | |
320 }); | |
321 } | |
322 | |
323 /// Insert [library] in the internal maps. Used for compiler reuse. | |
324 void mapLibrary(LibraryElement library) { | |
325 libraryCanonicalUriMap[library.canonicalUri] = library; | |
326 | |
327 Uri resourceUri = library.entryCompilationUnit.script.resourceUri; | |
328 libraryResourceUriMap[resourceUri] = library; | |
329 | |
330 String name = library.getLibraryOrScriptName(); | |
331 libraryNames[name] = library; | |
332 } | |
333 | |
334 Future<LibraryElement> loadLibrary(Uri resolvedUri) { | |
335 return measure(() { | |
336 assert(currentHandler == null); | |
337 // TODO(johnniwinther): Ensure that currentHandler correctly encloses the | |
338 // loading of a library cluster. | |
339 currentHandler = new LibraryDependencyHandler(this); | |
340 return createLibrary(currentHandler, null, resolvedUri) | |
341 .then((LibraryElement library) { | |
342 return compiler.withCurrentElement(library, () { | |
343 return measure(() { | |
344 currentHandler.computeExports(); | |
345 Map<Uri, LibraryElement> loadedLibraries = <Uri, LibraryElement>{}; | |
346 currentHandler.loadedLibraries.forEach( | |
347 (LibraryElement loadedLibrary) { | |
348 loadedLibraries[loadedLibrary.canonicalUri] = loadedLibrary; | |
349 }); | |
350 currentHandler = null; | |
351 return compiler.onLibrariesLoaded(loadedLibraries) | |
352 .then((_) => library); | |
353 }); | |
354 }); | |
355 }); | |
356 }); | |
357 } | |
358 | |
359 /** | |
360 * Processes the library tags in [library]. | |
361 * | |
362 * The imported/exported libraries are loaded and processed recursively but | |
363 * the import/export scopes are not set up. | |
364 */ | |
365 Future processLibraryTags(LibraryDependencyHandler handler, | |
366 LibraryElement library) { | |
367 int tagState = TagState.NO_TAG_SEEN; | |
368 | |
369 /** | |
370 * If [value] is less than [tagState] complain and return | |
371 * [tagState]. Otherwise return the new value for [tagState] | |
372 * (transition function for state machine). | |
373 */ | |
374 int checkTag(int value, LibraryTag tag) { | |
375 if (tagState > value) { | |
376 compiler.reportFatalError( | |
377 tag, | |
378 MessageKind.GENERIC, {'text': 'Error: Out of order.'}); | |
379 return tagState; | |
380 } | |
381 return TagState.NEXT[value]; | |
382 } | |
383 | |
384 bool importsDartCore = false; | |
385 var libraryDependencies = new LinkBuilder<LibraryDependency>(); | |
386 Uri base = library.entryCompilationUnit.script.readableUri; | |
387 | |
388 return Future.forEach(library.tags, (LibraryTag tag) { | |
389 return compiler.withCurrentElement(library, () { | |
390 if (tag.isImport) { | |
391 Import import = tag; | |
392 tagState = checkTag(TagState.IMPORT_OR_EXPORT, import); | |
393 if (import.uri.dartString.slowToString() == 'dart:core') { | |
394 importsDartCore = true; | |
395 } | |
396 libraryDependencies.addLast(import); | |
397 } else if (tag.isExport) { | |
398 tagState = checkTag(TagState.IMPORT_OR_EXPORT, tag); | |
399 libraryDependencies.addLast(tag); | |
400 } else if (tag.isLibraryName) { | |
401 tagState = checkTag(TagState.LIBRARY, tag); | |
402 if (library.libraryTag != null) { | |
403 compiler.internalError(tag, "Duplicated library declaration."); | |
404 } else { | |
405 library.libraryTag = tag; | |
406 } | |
407 } else if (tag.isPart) { | |
408 Part part = tag; | |
409 StringNode uri = part.uri; | |
410 Uri resolvedUri = base.resolve(uri.dartString.slowToString()); | |
411 tagState = checkTag(TagState.SOURCE, part); | |
412 return scanPart(part, resolvedUri, library); | |
413 } else { | |
414 compiler.internalError(tag, "Unhandled library tag."); | |
415 } | |
416 }); | |
417 }).then((_) { | |
418 return compiler.onLibraryScanned(library, handler); | |
419 }).then((_) { | |
420 return compiler.withCurrentElement(library, () { | |
421 checkDuplicatedLibraryName(library); | |
422 | |
423 // Import dart:core if not already imported. | |
424 if (!importsDartCore && library.canonicalUri != Compiler.DART_CORE) { | |
425 return createLibrary(handler, null, Compiler.DART_CORE) | |
426 .then((LibraryElement coreLibrary) { | |
427 handler.registerDependency(library, null, coreLibrary); | |
428 }); | |
429 } | |
430 }); | |
431 }).then((_) { | |
432 return Future.forEach(libraryDependencies.toList(), (tag) { | |
433 return compiler.withCurrentElement(library, () { | |
434 return registerLibraryFromTag(handler, library, tag); | |
435 }); | |
436 }); | |
437 }); | |
438 } | |
439 | |
440 /// True if the uris are pointing to a library that is shared between dart2js | |
441 /// and the core libraries. By construction they must be imported into the | |
442 /// runtime, and, at the same time, into dart2js. This can lead to | |
443 /// duplicated imports, like in the docgen. | |
444 // TODO(johnniwinther): is this necessary, or should we change docgen not | |
445 // to include both libraries (compiler and lib) at the same time? | |
446 bool _isSharedDart2jsLibrary(Uri uri1, Uri uri2) { | |
447 bool inJsLibShared(Uri uri) { | |
448 List<String> segments = uri.pathSegments; | |
449 if (segments.length < 3) return false; | |
450 if (segments[segments.length - 2] != 'shared') return false; | |
451 return (segments[segments.length - 3] == 'js_lib'); | |
452 } | |
453 return inJsLibShared(uri1) && inJsLibShared(uri2); | |
454 } | |
455 | |
456 void checkDuplicatedLibraryName(LibraryElement library) { | |
457 Uri resourceUri = library.entryCompilationUnit.script.resourceUri; | |
458 LibraryName tag = library.libraryTag; | |
459 LibraryElement existing = | |
460 libraryResourceUriMap.putIfAbsent(resourceUri, () => library); | |
461 if (!identical(existing, library)) { | |
462 if (tag != null) { | |
463 compiler.withCurrentElement(library, () { | |
464 compiler.reportWarning(tag.name, | |
465 MessageKind.DUPLICATED_LIBRARY_RESOURCE, | |
466 {'libraryName': tag.name, | |
467 'resourceUri': resourceUri, | |
468 'canonicalUri1': library.canonicalUri, | |
469 'canonicalUri2': existing.canonicalUri}); | |
470 }); | |
471 } else { | |
472 compiler.reportHint(library, | |
473 MessageKind.DUPLICATED_RESOURCE, | |
474 {'resourceUri': resourceUri, | |
475 'canonicalUri1': library.canonicalUri, | |
476 'canonicalUri2': existing.canonicalUri}); | |
477 } | |
478 } else if (tag != null) { | |
479 String name = library.getLibraryOrScriptName(); | |
480 existing = libraryNames.putIfAbsent(name, () => library); | |
481 if (!identical(existing, library) && | |
482 !_isSharedDart2jsLibrary(resourceUri, existing.canonicalUri)) { | |
483 compiler.withCurrentElement(library, () { | |
484 compiler.reportWarning(tag.name, | |
485 MessageKind.DUPLICATED_LIBRARY_NAME, | |
486 {'libraryName': name}); | |
487 }); | |
488 compiler.withCurrentElement(existing, () { | |
489 compiler.reportWarning(existing.libraryTag.name, | |
490 MessageKind.DUPLICATED_LIBRARY_NAME, | |
491 {'libraryName': name}); | |
492 }); | |
493 } | |
494 } | |
495 } | |
496 | |
497 /** | |
498 * Handle a part tag in the scope of [library]. The [resolvedUri] given is | |
499 * used as is, any URI resolution should be done beforehand. | |
500 */ | |
501 Future scanPart(Part part, Uri resolvedUri, LibraryElement library) { | |
502 if (!resolvedUri.isAbsolute) throw new ArgumentError(resolvedUri); | |
503 Uri readableUri = compiler.translateResolvedUri(library, resolvedUri, part); | |
504 if (readableUri == null) return new Future.value(); | |
505 return compiler.withCurrentElement(library, () { | |
506 return compiler.readScript(part, readableUri). | |
507 then((Script sourceScript) { | |
508 if (sourceScript == null) return; | |
509 | |
510 CompilationUnitElement unit = | |
511 new CompilationUnitElementX(sourceScript, library); | |
512 compiler.withCurrentElement(unit, () { | |
513 compiler.scanner.scan(unit); | |
514 if (unit.partTag == null) { | |
515 compiler.reportError(unit, MessageKind.MISSING_PART_OF_TAG); | |
516 } | |
517 }); | |
518 }); | |
519 }); | |
520 } | |
521 | |
522 /** | |
523 * Handle an import/export tag by loading the referenced library and | |
524 * registering its dependency in [handler] for the computation of the import/ | |
525 * export scope. | |
526 */ | |
527 Future registerLibraryFromTag(LibraryDependencyHandler handler, | |
528 LibraryElement library, | |
529 LibraryDependency tag) { | |
530 Uri base = library.entryCompilationUnit.script.readableUri; | |
531 Uri resolvedUri = base.resolve(tag.uri.dartString.slowToString()); | |
532 return createLibrary(handler, library, resolvedUri, tag.uri) | |
533 .then((LibraryElement loadedLibrary) { | |
534 if (loadedLibrary == null) return; | |
535 compiler.withCurrentElement(library, () { | |
536 handler.registerDependency(library, tag, loadedLibrary); | |
537 }); | |
538 }); | |
539 } | |
540 | |
541 /** | |
542 * Create (or reuse) a library element for the library specified by the | |
543 * [resolvedUri]. | |
544 * | |
545 * If a new library is created, the [handler] is notified. | |
546 */ | |
547 Future<LibraryElement> createLibrary(LibraryDependencyHandler handler, | |
548 LibraryElement importingLibrary, | |
549 Uri resolvedUri, | |
550 [Node node]) { | |
551 // TODO(johnniwinther): Create erroneous library elements for missing | |
552 // libraries. | |
553 Uri readableUri = | |
554 compiler.translateResolvedUri(importingLibrary, resolvedUri, node); | |
555 if (readableUri == null) return new Future.value(); | |
556 LibraryElement library = libraryCanonicalUriMap[resolvedUri]; | |
557 if (library != null) { | |
558 return new Future.value(library); | |
559 } | |
560 return compiler.withCurrentElement(importingLibrary, () { | |
561 return compiler.readScript(node, readableUri).then((Script script) { | |
562 if (script == null) return null; | |
563 LibraryElement element = | |
564 createLibrarySync(handler, script, resolvedUri); | |
565 return processLibraryTags(handler, element).then((_) { | |
566 compiler.withCurrentElement(element, () { | |
567 handler.registerLibraryExports(element); | |
568 }); | |
569 return element; | |
570 }); | |
571 }); | |
572 }); | |
573 } | |
574 | |
575 LibraryElement createLibrarySync( | |
576 LibraryDependencyHandler handler, | |
577 Script script, | |
578 Uri resolvedUri) { | |
579 LibraryElement element = new LibraryElementX(script, resolvedUri); | |
580 return compiler.withCurrentElement(element, () { | |
581 if (handler != null) { | |
582 handler.registerNewLibrary(element); | |
583 libraryCanonicalUriMap[resolvedUri] = element; | |
584 } | |
585 native.maybeEnableNative(compiler, element); | |
586 compiler.scanner.scanLibrary(element); | |
587 return element; | |
588 }); | |
589 } | |
590 } | |
591 | |
592 | |
593 /** | |
594 * The fields of this class models a state machine for checking script | |
595 * tags come in the correct order. | |
596 */ | |
597 class TagState { | |
598 static const int NO_TAG_SEEN = 0; | |
599 static const int LIBRARY = 1; | |
600 static const int IMPORT_OR_EXPORT = 2; | |
601 static const int SOURCE = 3; | |
602 static const int RESOURCE = 4; | |
603 | |
604 /** Next state. */ | |
605 static const List<int> NEXT = | |
606 const <int>[NO_TAG_SEEN, | |
607 IMPORT_OR_EXPORT, // Only one library tag is allowed. | |
608 IMPORT_OR_EXPORT, | |
609 SOURCE, | |
610 RESOURCE]; | |
611 } | |
612 | |
613 /** | |
614 * An [import] tag and the [importedLibrary] imported through [import]. | |
615 */ | |
616 class ImportLink { | |
617 final Import import; | |
618 final LibraryElement importedLibrary; | |
619 | |
620 ImportLink(this.import, this.importedLibrary); | |
621 | |
622 /** | |
623 * Imports the library into the [importingLibrary]. | |
624 */ | |
625 void importLibrary(Compiler compiler, LibraryElement importingLibrary) { | |
626 assert(invariant(importingLibrary, | |
627 importedLibrary.exportsHandled, | |
628 message: 'Exports not handled on $importedLibrary')); | |
629 var combinatorFilter = new CombinatorFilter.fromTag(import); | |
630 if (import != null && import.prefix != null) { | |
631 String prefix = import.prefix.source; | |
632 Element existingElement = importingLibrary.find(prefix); | |
633 PrefixElement prefixElement; | |
634 if (existingElement == null || !existingElement.isPrefix) { | |
635 prefixElement = new PrefixElementX(prefix, | |
636 importingLibrary.entryCompilationUnit, import.getBeginToken()); | |
637 } else { | |
638 prefixElement = existingElement; | |
639 } | |
640 importingLibrary.addToScope(prefixElement, compiler); | |
641 importedLibrary.forEachExport((Element element) { | |
642 if (combinatorFilter.exclude(element)) return; | |
643 prefixElement.addImport(element, import, compiler); | |
644 }); | |
645 if (import.isDeferred) { | |
646 prefixElement.addImport( | |
647 new DeferredLoaderGetterElementX(prefixElement), | |
648 import, compiler); | |
649 // TODO(sigurdm): When we remove support for the annotation based | |
650 // syntax the [PrefixElement] constructor should receive this | |
651 // information. | |
652 prefixElement.markAsDeferred(import); | |
653 } | |
654 } else { | |
655 importedLibrary.forEachExport((Element element) { | |
656 compiler.withCurrentElement(importingLibrary, () { | |
657 if (combinatorFilter.exclude(element)) return; | |
658 importingLibrary.addImport(element, import, compiler); | |
659 }); | |
660 }); | |
661 } | |
662 } | |
663 } | |
664 | |
665 /** | |
666 * The combinator filter computed from an export tag and the library dependency | |
667 * node for the library that declared the export tag. This represents an edge in | |
668 * the library dependency graph. | |
669 */ | |
670 class ExportLink { | |
671 final Export export; | |
672 final CombinatorFilter combinatorFilter; | |
673 final LibraryDependencyNode exportNode; | |
674 | |
675 ExportLink(Export export, LibraryDependencyNode this.exportNode) | |
676 : this.export = export, | |
677 this.combinatorFilter = new CombinatorFilter.fromTag(export); | |
678 | |
679 /** | |
680 * Exports [element] to the dependent library unless [element] is filtered by | |
681 * the export combinators. Returns [:true:] if the set pending exports of the | |
682 * dependent library was modified. | |
683 */ | |
684 bool exportElement(Element element) { | |
685 if (combinatorFilter.exclude(element)) return false; | |
686 return exportNode.addElementToPendingExports(element, export); | |
687 } | |
688 } | |
689 | |
690 /** | |
691 * A node in the library dependency graph. | |
692 * | |
693 * This class is used to collect the library dependencies expressed through | |
694 * import and export tags, and as the work-list entry in computations of library | |
695 * exports performed in [LibraryDependencyHandler.computeExports]. | |
696 */ | |
697 class LibraryDependencyNode { | |
698 final LibraryElement library; | |
699 | |
700 // TODO(ahe): Remove [hashCodeCounter] and [hashCode] when | |
701 // VM implementation of Object.hashCode is not slow. | |
702 final int hashCode = ++hashCodeCounter; | |
703 static int hashCodeCounter = 0; | |
704 | |
705 | |
706 /** | |
707 * A linked list of the import tags that import [library] mapped to the | |
708 * corresponding libraries. This is used to propagate exports into imports | |
709 * after the export scopes have been computed. | |
710 */ | |
711 Link<ImportLink> imports = const Link<ImportLink>(); | |
712 | |
713 /** | |
714 * A linked list of the export tags the dependent upon this node library. | |
715 * This is used to propagate exports during the computation of export scopes. | |
716 */ | |
717 Link<ExportLink> dependencies = const Link<ExportLink>(); | |
718 | |
719 /** | |
720 * The export scope for [library] which is gradually computed by the work-list | |
721 * computation in [LibraryDependencyHandler.computeExports]. | |
722 */ | |
723 Map<String, Element> exportScope = | |
724 new Map<String, Element>(); | |
725 | |
726 /// Map from exported elements to the export directives that exported them. | |
727 Map<Element, Link<Export>> exporters = new Map<Element, Link<Export>>(); | |
728 | |
729 /** | |
730 * The set of exported elements that need to be propageted to dependent | |
731 * libraries as part of the work-list computation performed in | |
732 * [LibraryDependencyHandler.computeExports]. Each export element is mapped | |
733 * to a list of exports directives that export it. | |
734 */ | |
735 Map<Element, Link<Export>> pendingExportMap = | |
736 new Map<Element, Link<Export>>(); | |
737 | |
738 LibraryDependencyNode(LibraryElement this.library); | |
739 | |
740 /** | |
741 * Registers that the library of this node imports [importLibrary] through the | |
742 * [import] tag. | |
743 */ | |
744 void registerImportDependency(Import import, | |
745 LibraryElement importedLibrary) { | |
746 imports = imports.prepend(new ImportLink(import, importedLibrary)); | |
747 } | |
748 | |
749 /** | |
750 * Registers that the library of this node is exported by | |
751 * [exportingLibraryNode] through the [export] tag. | |
752 */ | |
753 void registerExportDependency(Export export, | |
754 LibraryDependencyNode exportingLibraryNode) { | |
755 dependencies = | |
756 dependencies.prepend(new ExportLink(export, exportingLibraryNode)); | |
757 } | |
758 | |
759 /** | |
760 * Registers all non-private locally declared members of the library of this | |
761 * node to be exported. This forms the basis for the work-list computation of | |
762 * the export scopes performed in [LibraryDependencyHandler.computeExports]. | |
763 */ | |
764 void registerInitialExports() { | |
765 for (Element element in library.getNonPrivateElementsInScope()) { | |
766 pendingExportMap[element] = const Link<Export>(); | |
767 } | |
768 } | |
769 | |
770 void registerHandledExports(LibraryElement exportedLibraryElement, | |
771 Export export, | |
772 CombinatorFilter filter) { | |
773 assert(invariant(library, exportedLibraryElement.exportsHandled)); | |
774 for (Element exportedElement in exportedLibraryElement.exports) { | |
775 if (!filter.exclude(exportedElement)) { | |
776 Link<Export> exports = | |
777 pendingExportMap.putIfAbsent(exportedElement, | |
778 () => const Link<Export>()); | |
779 pendingExportMap[exportedElement] = exports.prepend(export); | |
780 } | |
781 } | |
782 } | |
783 | |
784 /** | |
785 * Registers the compute export scope with the node library. | |
786 */ | |
787 void registerExports() { | |
788 library.setExports(exportScope.values.toList()); | |
789 } | |
790 | |
791 /** | |
792 * Registers the imports of the node library. | |
793 */ | |
794 void registerImports(Compiler compiler) { | |
795 for (ImportLink link in imports) { | |
796 link.importLibrary(compiler, library); | |
797 } | |
798 } | |
799 | |
800 /** | |
801 * Copies and clears pending export set for this node. | |
802 */ | |
803 Map<Element, Link<Export>> pullPendingExports() { | |
804 Map<Element, Link<Export>> pendingExports = | |
805 new Map<Element, Link<Export>>.from(pendingExportMap); | |
806 pendingExportMap.clear(); | |
807 return pendingExports; | |
808 } | |
809 | |
810 /** | |
811 * Adds [element] to the export scope for this node. If the [element] name | |
812 * is a duplicate, an error element is inserted into the export scope. | |
813 */ | |
814 Element addElementToExportScope(Compiler compiler, Element element, | |
815 Link<Export> exports) { | |
816 String name = element.name; | |
817 | |
818 void reportDuplicateExport(Element duplicate, | |
819 Link<Export> duplicateExports, | |
820 {bool reportError: true}) { | |
821 assert(invariant(library, !duplicateExports.isEmpty, | |
822 message: "No export for $duplicate from ${duplicate.library} " | |
823 "in $library.")); | |
824 compiler.withCurrentElement(library, () { | |
825 for (Export export in duplicateExports) { | |
826 if (reportError) { | |
827 compiler.reportError(export, | |
828 MessageKind.DUPLICATE_EXPORT, {'name': name}); | |
829 reportError = false; | |
830 } else { | |
831 compiler.reportInfo(export, | |
832 MessageKind.DUPLICATE_EXPORT_CONT, {'name': name}); | |
833 } | |
834 } | |
835 }); | |
836 } | |
837 | |
838 void reportDuplicateExportDecl(Element duplicate, | |
839 Link<Export> duplicateExports) { | |
840 assert(invariant(library, !duplicateExports.isEmpty, | |
841 message: "No export for $duplicate from ${duplicate.library} " | |
842 "in $library.")); | |
843 compiler.reportInfo(duplicate, MessageKind.DUPLICATE_EXPORT_DECL, | |
844 {'name': name, 'uriString': duplicateExports.head.uri}); | |
845 } | |
846 | |
847 Element existingElement = exportScope[name]; | |
848 if (existingElement != null && existingElement != element) { | |
849 if (existingElement.isErroneous) { | |
850 reportDuplicateExport(element, exports); | |
851 reportDuplicateExportDecl(element, exports); | |
852 element = existingElement; | |
853 } else if (existingElement.library == library) { | |
854 // Do nothing. [existingElement] hides [element]. | |
855 } else if (element.library == library) { | |
856 // [element] hides [existingElement]. | |
857 exportScope[name] = element; | |
858 exporters[element] = exports; | |
859 } else { | |
860 // Declared elements hide exported elements. | |
861 Link<Export> existingExports = exporters[existingElement]; | |
862 reportDuplicateExport(existingElement, existingExports); | |
863 reportDuplicateExport(element, exports, reportError: false); | |
864 reportDuplicateExportDecl(existingElement, existingExports); | |
865 reportDuplicateExportDecl(element, exports); | |
866 element = exportScope[name] = new ErroneousElementX( | |
867 MessageKind.DUPLICATE_EXPORT, {'name': name}, name, library); | |
868 } | |
869 } else { | |
870 exportScope[name] = element; | |
871 exporters[element] = exports; | |
872 } | |
873 return element; | |
874 } | |
875 | |
876 /** | |
877 * Propagates the exported [element] to all library nodes that depend upon | |
878 * this node. If the propagation updated any pending exports, [:true:] is | |
879 * returned. | |
880 */ | |
881 bool propagateElement(Element element) { | |
882 bool change = false; | |
883 for (ExportLink link in dependencies) { | |
884 if (link.exportElement(element)) { | |
885 change = true; | |
886 } | |
887 } | |
888 return change; | |
889 } | |
890 | |
891 /** | |
892 * Adds [element] to the pending exports of this node and returns [:true:] if | |
893 * the pending export set was modified. The combinators of [export] are used | |
894 * to filter the element. | |
895 */ | |
896 bool addElementToPendingExports(Element element, Export export) { | |
897 bool changed = false; | |
898 if (!identical(exportScope[element.name], element)) { | |
899 Link<Export> exports = pendingExportMap.putIfAbsent(element, () { | |
900 changed = true; | |
901 return const Link<Export>(); | |
902 }); | |
903 pendingExportMap[element] = exports.prepend(export); | |
904 } | |
905 return changed; | |
906 } | |
907 } | |
908 | |
909 /** | |
910 * Helper class used for computing the possibly cyclic import/export scopes of | |
911 * a set of libraries. | |
912 * | |
913 * This class is used by [ScannerTask.scanLibrary] to collect all newly loaded | |
914 * libraries and to compute their import/export scopes through a fixed-point | |
915 * algorithm. | |
916 */ | |
917 class LibraryDependencyHandler implements LibraryLoader { | |
918 final _LibraryLoaderTask task; | |
919 | |
920 /** | |
921 * Newly loaded libraries and their corresponding node in the library | |
922 * dependency graph. Libraries that have already been fully loaded are not | |
923 * part of the dependency graph of this handler since their export scopes have | |
924 * already been computed. | |
925 */ | |
926 Map<LibraryElement, LibraryDependencyNode> nodeMap = | |
927 new Map<LibraryElement, LibraryDependencyNode>(); | |
928 | |
929 LibraryDependencyHandler(this.task); | |
930 | |
931 Compiler get compiler => task.compiler; | |
932 | |
933 /// The libraries loaded with this handler. | |
934 Iterable<LibraryElement> get loadedLibraries => nodeMap.keys; | |
935 | |
936 /** | |
937 * Performs a fixed-point computation on the export scopes of all registered | |
938 * libraries and creates the import/export of the libraries based on the | |
939 * fixed-point. | |
940 */ | |
941 void computeExports() { | |
942 bool changed = true; | |
943 while (changed) { | |
944 changed = false; | |
945 Map<LibraryDependencyNode, Map<Element, Link<Export>>> tasks = | |
946 new Map<LibraryDependencyNode, Map<Element, Link<Export>>>(); | |
947 | |
948 // Locally defined elements take precedence over exported | |
949 // elements. So we must propagate local elements first. We | |
950 // ensure this by pulling the pending exports before | |
951 // propagating. This enforces that we handle exports | |
952 // breadth-first, with locally defined elements being level 0. | |
953 nodeMap.forEach((_, LibraryDependencyNode node) { | |
954 Map<Element, Link<Export>> pendingExports = node.pullPendingExports(); | |
955 tasks[node] = pendingExports; | |
956 }); | |
957 tasks.forEach((LibraryDependencyNode node, | |
958 Map<Element, Link<Export>> pendingExports) { | |
959 pendingExports.forEach((Element element, Link<Export> exports) { | |
960 element = node.addElementToExportScope(compiler, element, exports); | |
961 if (node.propagateElement(element)) { | |
962 changed = true; | |
963 } | |
964 }); | |
965 }); | |
966 } | |
967 | |
968 // Setup export scopes. These have to be set before computing the import | |
969 // scopes to avoid accessing uncomputed export scopes during handling of | |
970 // imports. | |
971 nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { | |
972 node.registerExports(); | |
973 }); | |
974 | |
975 // Setup import scopes. | |
976 nodeMap.forEach((LibraryElement library, LibraryDependencyNode node) { | |
977 node.registerImports(compiler); | |
978 }); | |
979 } | |
980 | |
981 /** | |
982 * Registers that [library] depends on [loadedLibrary] through [tag]. | |
983 */ | |
984 void registerDependency(LibraryElement library, | |
985 LibraryDependency tag, | |
986 LibraryElement loadedLibrary) { | |
987 if (tag != null) { | |
988 library.recordResolvedTag(tag, loadedLibrary); | |
989 } | |
990 if (tag is Export) { | |
991 // [loadedLibrary] is exported by [library]. | |
992 LibraryDependencyNode exportingNode = nodeMap[library]; | |
993 if (loadedLibrary.exportsHandled) { | |
994 // Export scope already computed on [loadedLibrary]. | |
995 var combinatorFilter = new CombinatorFilter.fromTag(tag); | |
996 exportingNode.registerHandledExports( | |
997 loadedLibrary, tag, combinatorFilter); | |
998 return; | |
999 } | |
1000 LibraryDependencyNode exportedNode = nodeMap[loadedLibrary]; | |
1001 assert(invariant(loadedLibrary, exportedNode != null, | |
1002 message: "$loadedLibrary has not been registered")); | |
1003 assert(invariant(library, exportingNode != null, | |
1004 message: "$library has not been registered")); | |
1005 exportedNode.registerExportDependency(tag, exportingNode); | |
1006 } else if (tag == null || tag is Import) { | |
1007 // [loadedLibrary] is imported by [library]. | |
1008 LibraryDependencyNode importingNode = nodeMap[library]; | |
1009 assert(invariant(library, importingNode != null, | |
1010 message: "$library has not been registered")); | |
1011 importingNode.registerImportDependency(tag, loadedLibrary); | |
1012 } | |
1013 } | |
1014 | |
1015 /** | |
1016 * Registers [library] for the processing of its import/export scope. | |
1017 */ | |
1018 void registerNewLibrary(LibraryElement library) { | |
1019 nodeMap[library] = new LibraryDependencyNode(library); | |
1020 compiler.onLibraryCreated(library); | |
1021 } | |
1022 | |
1023 /** | |
1024 * Registers all top-level entities of [library] as starting point for the | |
1025 * fixed-point computation of the import/export scopes. | |
1026 */ | |
1027 void registerLibraryExports(LibraryElement library) { | |
1028 nodeMap[library].registerInitialExports(); | |
1029 } | |
1030 | |
1031 Future processLibraryTags(LibraryElement library) { | |
1032 return task.processLibraryTags(this, library); | |
1033 } | |
1034 } | |
OLD | NEW |