OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, 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 part of dart_backend; | |
6 | |
7 typedef bool IsSafeToRemoveTypeDeclarations( | |
8 Map<ClassElement, Iterable<Element>> classMembers); | |
9 typedef void ElementCallback<E>(E element); | |
10 typedef void ElementPostProcessFunction( | |
11 AstElement element, ElementAst elementAst, | |
12 ElementCallback<TypedefElement> typedefCallback, | |
13 ElementCallback<ClassElement> classCallback); | |
14 typedef ElementAst ComputeElementAstFunction(AstElement element); | |
15 typedef bool ElementFilter(Element element); | |
16 typedef List<Element> ElementSorter(Iterable<Element> elements); | |
17 | |
18 /// Output engine for dart2dart that is shared between the dart2js and the | |
19 /// analyzer implementations of dart2dart. | |
20 class DartOutputter { | |
21 final DiagnosticListener listener; | |
22 final CompilerOutputProvider outputProvider; | |
23 final bool forceStripTypes; | |
24 | |
25 // TODO(antonm): make available from command-line options. | |
26 final bool outputAst = false; | |
27 final bool enableMinification; | |
28 | |
29 /// If `true`, libraries are generated into separate files. | |
30 final bool multiFile; | |
31 | |
32 /// Internal structures accessible for tests and logging. | |
33 // TODO(johnniwinther): Clean this up. | |
34 PlaceholderRenamer renamer; | |
35 MainOutputGenerator output; | |
36 LibraryInfo libraryInfo; | |
37 ElementInfo elementInfo; | |
38 | |
39 // TODO(johnniwinther): Support recompilation. | |
40 DartOutputter(this.listener, this.outputProvider, | |
41 {bool this.forceStripTypes: false, | |
42 bool this.enableMinification: false, | |
43 bool this.multiFile: false}); | |
44 | |
45 /// Generate Dart code for the program starting at [mainFunction]. | |
46 /// | |
47 /// [libraries] is the set of all libraries (user/package/sdk) that are | |
48 /// referenced in the program. | |
49 /// | |
50 /// [instantiatedClasses] is the set of classes that are potentially | |
51 /// instantiated in the program. | |
52 /// | |
53 /// [resolvedElements] is the set of methods, constructors, and fields that | |
54 /// are potentially accessed/called in the program. | |
55 /// | |
56 /// The [sortElements] function is used to sort [instantiatedClasses] and | |
57 /// [resolvedElements] in the generated output. | |
58 String assembleProgram({ | |
59 MirrorRenamer mirrorRenamer: const MirrorRenamer(), | |
60 Iterable<LibraryElement> libraries, | |
61 Iterable<Element> instantiatedClasses, | |
62 Iterable<Element> resolvedElements, | |
63 Iterable<ClassElement> usedTypeLiterals: const <ClassElement>[], | |
64 FunctionElement mainFunction, | |
65 Uri outputUri, | |
66 ElementPostProcessFunction postProcessElementAst, | |
67 ComputeElementAstFunction computeElementAst, | |
68 ElementFilter shouldOutput, | |
69 IsSafeToRemoveTypeDeclarations isSafeToRemoveTypeDeclarations, | |
70 ElementSorter sortElements}) { | |
71 | |
72 assert(invariant(NO_LOCATION_SPANNABLE, libraries != null, | |
73 message: "'libraries' must be non-null.")); | |
74 assert(invariant(NO_LOCATION_SPANNABLE, instantiatedClasses != null, | |
75 message: "'instantiatedClasses' must be non-null.")); | |
76 assert(invariant(NO_LOCATION_SPANNABLE, resolvedElements != null, | |
77 message: "'resolvedElements' must be non-null.")); | |
78 assert(invariant(NO_LOCATION_SPANNABLE, mainFunction != null, | |
79 message: "'mainFunction' must be non-null.")); | |
80 assert(invariant(NO_LOCATION_SPANNABLE, computeElementAst != null, | |
81 message: "'computeElementAst' must be non-null.")); | |
82 assert(invariant(NO_LOCATION_SPANNABLE, shouldOutput != null, | |
83 message: "'shouldOutput' must be non-null.")); | |
84 assert(invariant(NO_LOCATION_SPANNABLE, | |
85 isSafeToRemoveTypeDeclarations != null, | |
86 message: "'isSafeToRemoveTypeDeclarations' must be non-null.")); | |
87 | |
88 if (sortElements == null) { | |
89 // Ensure deterministic output order. | |
90 sortElements = (Iterable<Element> elements) { | |
91 List<Element> list = elements.toList(); | |
92 list.sort((Element a, Element b) => a.name.compareTo(b.name)); | |
93 return list; | |
94 }; | |
95 } | |
96 | |
97 libraryInfo = LibraryInfo.processLibraries(libraries, resolvedElements); | |
98 | |
99 elementInfo = ElementInfoProcessor.createElementInfo( | |
100 instantiatedClasses, | |
101 resolvedElements, | |
102 usedTypeLiterals, | |
103 postProcessElementAst: postProcessElementAst, | |
104 parseElementAst: computeElementAst, | |
105 shouldOutput: shouldOutput, | |
106 sortElements: sortElements); | |
107 | |
108 PlaceholderCollector collector = collectPlaceholders( | |
109 listener, | |
110 mirrorRenamer, | |
111 mainFunction, | |
112 libraryInfo, | |
113 elementInfo); | |
114 | |
115 renamer = createRenamer( | |
116 collector, | |
117 libraryInfo, | |
118 elementInfo, | |
119 enableMinification: enableMinification, | |
120 forceStripTypes: forceStripTypes, | |
121 isSafeToRemoveTypeDeclarations: isSafeToRemoveTypeDeclarations); | |
122 | |
123 String assembledCode; | |
124 if (outputAst) { | |
125 assembledCode = astOutput(listener, elementInfo); | |
126 } else { | |
127 output = new MainOutputGenerator(); | |
128 assembledCode = output.generateCode( | |
129 libraryInfo, | |
130 elementInfo, | |
131 collector, | |
132 renamer, | |
133 mainFunction, | |
134 outputUri, | |
135 outputProvider, | |
136 mirrorRenamer, | |
137 multiFile: multiFile, | |
138 forceStripTypes: forceStripTypes, | |
139 enableMinification: enableMinification); | |
140 } | |
141 return assembledCode; | |
142 } | |
143 | |
144 static PlaceholderCollector collectPlaceholders( | |
145 DiagnosticListener listener, | |
146 MirrorRenamer mirrorRenamer, | |
147 FunctionElement mainFunction, | |
148 LibraryInfo libraryInfo, | |
149 ElementInfo elementInfo) { | |
150 // Create all necessary placeholders. | |
151 PlaceholderCollector collector = new PlaceholderCollector( | |
152 listener, | |
153 mirrorRenamer, | |
154 libraryInfo.fixedMemberNames, | |
155 elementInfo.elementAsts, | |
156 mainFunction); | |
157 | |
158 makePlaceholders(element) { | |
159 collector.collect(element); | |
160 | |
161 if (element.isClass) { | |
162 elementInfo.classMembers[element].forEach(makePlaceholders); | |
163 } | |
164 } | |
165 elementInfo.topLevelElements.forEach(makePlaceholders); | |
166 return collector; | |
167 } | |
168 | |
169 static PlaceholderRenamer createRenamer( | |
170 PlaceholderCollector collector, | |
171 LibraryInfo libraryInfo, | |
172 ElementInfo elementInfo, | |
173 {bool enableMinification: false, | |
174 bool forceStripTypes: false, | |
175 isSafeToRemoveTypeDeclarations}) { | |
176 // Create renames. | |
177 bool shouldCutDeclarationTypes = forceStripTypes | |
178 || (enableMinification | |
179 && isSafeToRemoveTypeDeclarations(elementInfo.classMembers)); | |
180 | |
181 PlaceholderRenamer placeholderRenamer = new PlaceholderRenamer( | |
182 libraryInfo.fixedMemberNames, libraryInfo.reexportingLibraries, | |
183 cutDeclarationTypes: shouldCutDeclarationTypes, | |
184 enableMinification: enableMinification); | |
185 | |
186 placeholderRenamer.computeRenames(collector); | |
187 return placeholderRenamer; | |
188 } | |
189 | |
190 static String astOutput(DiagnosticListener listener, | |
191 ElementInfo elementInfo) { | |
192 // TODO(antonm): Ideally XML should be a separate backend. | |
193 // TODO(antonm): obey renames and minification, at least as an option. | |
194 StringBuffer sb = new StringBuffer(); | |
195 outputElement(element) { | |
196 sb.write(element.parseNode(listener).toDebugString()); | |
197 } | |
198 | |
199 // Emit XML for AST instead of the program. | |
200 for (Element topLevel in elementInfo.topLevelElements) { | |
201 if (topLevel.isClass && | |
202 !elementInfo.emitNoMembersFor.contains(topLevel)) { | |
203 // TODO(antonm): add some class info. | |
204 elementInfo.classMembers[topLevel].forEach(outputElement); | |
205 } else { | |
206 outputElement(topLevel); | |
207 } | |
208 } | |
209 return '<Program>\n$sb</Program>\n'; | |
210 } | |
211 } | |
212 | |
213 class LibraryInfo { | |
214 final Set<String> fixedMemberNames; | |
215 final Map<Element, LibraryElement> reexportingLibraries; | |
216 final List<LibraryElement> userLibraries; | |
217 | |
218 LibraryInfo(this.fixedMemberNames, | |
219 this.reexportingLibraries, | |
220 this.userLibraries); | |
221 | |
222 static LibraryInfo processLibraries( | |
223 Iterable<LibraryElement> libraries, | |
224 Iterable<AstElement> resolvedElements) { | |
225 Set<String> fixedMemberNames = new Set<String>(); | |
226 Map<Element, LibraryElement> reexportingLibraries = | |
227 <Element, LibraryElement>{}; | |
228 List<LibraryElement> userLibraries = <LibraryElement>[]; | |
229 // Conservatively traverse all platform libraries and collect member names. | |
230 // TODO(antonm): ideally we should only collect names of used members, | |
231 // however as of today there are problems with names of some core library | |
232 // interfaces, most probably for interfaces of literals. | |
233 | |
234 for (LibraryElement library in libraries) { | |
235 if (!library.isPlatformLibrary) { | |
236 userLibraries.add(library); | |
237 continue; | |
238 } | |
239 library.forEachLocalMember((Element element) { | |
240 if (element.isClass) { | |
241 ClassElement classElement = element; | |
242 assert(invariant(classElement, classElement.isResolved, | |
243 message: "Unresolved platform class.")); | |
244 classElement.forEachLocalMember((member) { | |
245 String name = member.name; | |
246 // Skip operator names. | |
247 if (!name.startsWith(r'operator$')) { | |
248 // Fetch name of named constructors and factories if any, | |
249 // otherwise store regular name. | |
250 // TODO(antonm): better way to analyze the name. | |
251 fixedMemberNames.add(name.split(r'$').last); | |
252 } | |
253 }); | |
254 } | |
255 // Even class names are added due to a delicate problem we have: | |
256 // if one imports dart:core with a prefix, we cannot tell prefix.name | |
257 // from dynamic invocation (alas!). So we'd better err on preserving | |
258 // those names. | |
259 fixedMemberNames.add(element.name); | |
260 }); | |
261 | |
262 for (Element export in library.exports) { | |
263 if (!library.isInternalLibrary && | |
264 export.library.isInternalLibrary) { | |
265 // If an element of an internal library is reexported by a platform | |
266 // library, we have to import the reexporting library instead of the | |
267 // internal library, because the internal library is an | |
268 // implementation detail of dart2js. | |
269 reexportingLibraries[export] = library; | |
270 } | |
271 } | |
272 } | |
273 // As of now names of named optionals are not renamed. Therefore add all | |
274 // field names used as named optionals into [fixedMemberNames]. | |
275 for (final element in resolvedElements) { | |
276 if (!element.isConstructor) continue; | |
277 Link<Element> optionalParameters = | |
278 element.functionSignature.optionalParameters; | |
279 for (final optional in optionalParameters) { | |
280 if (!optional.isInitializingFormal) continue; | |
281 fixedMemberNames.add(optional.name); | |
282 } | |
283 } | |
284 // The VM will automatically invoke the call method of objects | |
285 // that are invoked as functions. Make sure to not rename that. | |
286 fixedMemberNames.add('call'); | |
287 // TODO(antonm): TypeError.srcType and TypeError.dstType are defined in | |
288 // runtime/lib/error.dart. Overall, all DartVM specific libs should be | |
289 // accounted for. | |
290 fixedMemberNames.add('srcType'); | |
291 fixedMemberNames.add('dstType'); | |
292 | |
293 return new LibraryInfo( | |
294 fixedMemberNames, reexportingLibraries, userLibraries); | |
295 } | |
296 } | |
297 | |
298 class ElementInfo { | |
299 final Map<Element, ElementAst> elementAsts; | |
300 final Iterable<Element> topLevelElements; | |
301 final Map<ClassElement, Iterable<Element>> classMembers; | |
302 final Iterable<ClassElement> emitNoMembersFor; | |
303 | |
304 ElementInfo(this.elementAsts, | |
305 this.topLevelElements, | |
306 this.classMembers, | |
307 this.emitNoMembersFor); | |
308 } | |
309 | |
310 class ElementInfoProcessor implements ElementInfo { | |
311 final Map<Element, ElementAst> elementAsts = new Map<Element, ElementAst>(); | |
312 final Set<Element> topLevelElements = new Set<Element>(); | |
313 final Map<ClassElement, Set<Element>> classMembers = | |
314 new Map<ClassElement, Set<Element>>(); | |
315 final Set<ClassElement> emitNoMembersFor = new Set<ClassElement>(); | |
316 final ElementPostProcessFunction postProcessElementAst; | |
317 final ComputeElementAstFunction parseElementAst; | |
318 final ElementFilter shouldOutput; | |
319 | |
320 ElementInfoProcessor( | |
321 {this.postProcessElementAst, | |
322 this.parseElementAst, | |
323 this.shouldOutput}); | |
324 | |
325 static ElementInfo createElementInfo( | |
326 Iterable<ClassElement> instantiatedClasses, | |
327 Iterable<AstElement> resolvedElements, | |
328 Iterable<ClassElement> usedTypeLiterals, | |
329 {ElementPostProcessFunction postProcessElementAst, | |
330 ComputeElementAstFunction parseElementAst, | |
331 ElementFilter shouldOutput, | |
332 ElementSorter sortElements}) { | |
333 ElementInfoProcessor processor = new ElementInfoProcessor( | |
334 postProcessElementAst: postProcessElementAst, | |
335 parseElementAst: parseElementAst, | |
336 shouldOutput: shouldOutput); | |
337 return processor.process( | |
338 instantiatedClasses, resolvedElements, usedTypeLiterals, | |
339 sortElements: sortElements); | |
340 } | |
341 | |
342 ElementInfo process(Iterable<ClassElement> instantiatedClasses, | |
343 Iterable<AstElement> resolvedElements, | |
344 Iterable<ClassElement> usedTypeLiterals, | |
345 {ElementSorter sortElements}) { | |
346 // Build all top level elements to emit and necessary class members. | |
347 instantiatedClasses.where(shouldOutput).forEach(addClass); | |
348 resolvedElements.where(shouldOutput).forEach(addMember); | |
349 usedTypeLiterals.forEach((ClassElement element) { | |
350 if (shouldOutput(element)) { | |
351 if (!topLevelElements.contains(element)) { | |
352 // The class is only referenced by type literals. | |
353 emitNoMembersFor.add(element); | |
354 } | |
355 addClass(element); | |
356 } | |
357 }); | |
358 | |
359 // Sort elements. | |
360 List<Element> sortedTopLevels = sortElements(topLevelElements); | |
361 Map<ClassElement, List<Element>> sortedClassMembers = | |
362 new Map<ClassElement, List<Element>>(); | |
363 classMembers.forEach((classElement, members) { | |
364 sortedClassMembers[classElement] = sortElements(members); | |
365 }); | |
366 | |
367 return new ElementInfo( | |
368 elementAsts, sortedTopLevels, sortedClassMembers, emitNoMembersFor); | |
369 } | |
370 | |
371 void processElement(Element element, ElementAst elementAst) { | |
372 if (postProcessElementAst != null) { | |
373 postProcessElementAst(element, elementAst, | |
374 newTypedefElementCallback, | |
375 newClassElementCallback); | |
376 } | |
377 elementAsts[element] = elementAst; | |
378 } | |
379 | |
380 void addTopLevel(AstElement element, ElementAst elementAst) { | |
381 if (topLevelElements.contains(element)) return; | |
382 topLevelElements.add(element); | |
383 processElement(element, elementAst); | |
384 } | |
385 | |
386 void addClass(ClassElement classElement) { | |
387 TreeElements treeElements = new TreeElementMapping(classElement); | |
388 backend2frontend.TreePrinter treePrinter = | |
389 new backend2frontend.TreePrinter(treeElements); | |
390 Node node = treePrinter.makeNodeForClassElement(classElement); | |
391 addTopLevel(classElement, new ElementAst.internal(node, treeElements)); | |
392 classMembers.putIfAbsent(classElement, () => new Set()); | |
393 } | |
394 | |
395 void newTypedefElementCallback(TypedefElement element) { | |
396 if (!shouldOutput(element)) return; | |
397 addTopLevel(element, new ElementAst(element)); | |
398 } | |
399 | |
400 void newClassElementCallback(ClassElement classElement) { | |
401 if (!shouldOutput(classElement)) return; | |
402 addClass(classElement); | |
403 } | |
404 | |
405 void addMember(element) { | |
406 ElementAst elementAst = parseElementAst(element); | |
407 if (element.isClassMember) { | |
408 ClassElement enclosingClass = element.enclosingClass; | |
409 assert(enclosingClass.isClass); | |
410 assert(enclosingClass.isTopLevel); | |
411 assert(shouldOutput(enclosingClass)); | |
412 addClass(enclosingClass); | |
413 classMembers[enclosingClass].add(element); | |
414 processElement(element, elementAst); | |
415 } else { | |
416 if (element.isTopLevel) { | |
417 addTopLevel(element, elementAst); | |
418 } | |
419 } | |
420 } | |
421 } | |
422 | |
423 /// Main output generator for [DartOutputter] that emits dart code through a | |
424 /// [CompilerOutputProvider]. | |
425 class MainOutputGenerator { | |
426 final Map<ClassNode, List<Node>> memberNodes = | |
427 new Map<ClassNode, List<Node>>(); | |
428 final List<Node> topLevelNodes = <Node>[]; | |
429 | |
430 String generateCode( | |
431 LibraryInfo libraryInfo, | |
432 ElementInfo elementInfo, | |
433 PlaceholderCollector collector, | |
434 PlaceholderRenamer placeholderRenamer, | |
435 FunctionElement mainFunction, | |
436 Uri outputUri, | |
437 CompilerOutputProvider outputProvider, | |
438 MirrorRenamer mirrorRenamer, | |
439 {bool multiFile: false, | |
440 bool forceStripTypes: false, | |
441 bool enableMinification: false}) { | |
442 for (Element element in elementInfo.topLevelElements) { | |
443 topLevelNodes.add(elementInfo.elementAsts[element].ast); | |
444 if (element.isClass && !element.isMixinApplication) { | |
445 final members = <Node>[]; | |
446 for (Element member in elementInfo.classMembers[element]) { | |
447 members.add(elementInfo.elementAsts[member].ast); | |
448 } | |
449 memberNodes[elementInfo.elementAsts[element].ast] = members; | |
450 } | |
451 } | |
452 | |
453 mirrorRenamer.addRenames(placeholderRenamer.renames, | |
454 topLevelNodes, collector); | |
455 | |
456 Map<LibraryElement, String> outputPaths = new Map<LibraryElement, String>(); | |
457 Map<LibraryElement, EmitterUnparser> unparsers = | |
458 new Map<LibraryElement, EmitterUnparser>(); | |
459 | |
460 // The single unparser used if we collect all the output in one file. | |
461 EmitterUnparser mainUnparser = multiFile | |
462 ? null | |
463 : new EmitterUnparser(placeholderRenamer.renames, | |
464 stripTypes: forceStripTypes, | |
465 minify: enableMinification); | |
466 | |
467 if (multiFile) { | |
468 // TODO(sigurdm): Factor handling of library-paths out from emitting. | |
469 String mainName = outputUri.pathSegments.last; | |
470 String mainBaseName = mainName.endsWith(".dart") | |
471 ? mainName.substring(0, mainName.length - 5) | |
472 : mainName; | |
473 // Map each library to a path based on the uri of the original | |
474 // library and [compiler.outputUri]. | |
475 Set<String> usedLibraryPaths = new Set<String>(); | |
476 for (LibraryElement library in libraryInfo.userLibraries) { | |
477 if (library == mainFunction.library) { | |
478 outputPaths[library] = mainBaseName; | |
479 } else { | |
480 List<String> names = | |
481 library.canonicalUri.pathSegments.last.split("."); | |
482 if (names.last == "dart") { | |
483 names = names.sublist(0, names.length - 1); | |
484 } | |
485 outputPaths[library] = | |
486 "$mainBaseName.${makeUnique(names.join("."), usedLibraryPaths)}"; | |
487 } | |
488 } | |
489 | |
490 /// Rewrites imports/exports to refer to the paths given in [outputPaths]. | |
491 for(LibraryElement outputLibrary in libraryInfo.userLibraries) { | |
492 EmitterUnparser unparser = new EmitterUnparser( | |
493 placeholderRenamer.renames, | |
494 stripTypes: forceStripTypes, | |
495 minify: enableMinification); | |
496 unparsers[outputLibrary] = unparser; | |
497 LibraryName libraryName = outputLibrary.libraryTag; | |
498 if (libraryName != null) { | |
499 unparser.visitLibraryName(libraryName); | |
500 } | |
501 for (LibraryTag tag in outputLibrary.tags) { | |
502 if (tag is! LibraryDependency) continue; | |
503 LibraryDependency dependency = tag; | |
504 LibraryElement libraryElement = | |
505 outputLibrary.getLibraryFromTag(dependency); | |
506 String uri = outputPaths.containsKey(libraryElement) | |
507 ? "${outputPaths[libraryElement]}.dart" | |
508 : libraryElement.canonicalUri.toString(); | |
509 if (dependency is Import) { | |
510 unparser.unparseImportTag(uri); | |
511 } else { | |
512 unparser.unparseExportTag(uri); | |
513 } | |
514 } | |
515 } | |
516 } else { | |
517 for(LibraryElement library in placeholderRenamer.platformImports) { | |
518 if (library.isPlatformLibrary && !library.isInternalLibrary) { | |
519 mainUnparser.unparseImportTag(library.canonicalUri.toString()); | |
520 } | |
521 } | |
522 } | |
523 | |
524 for (int i = 0; i < elementInfo.topLevelElements.length; i++) { | |
525 Element element = elementInfo.topLevelElements.elementAt(i); | |
526 Node node = topLevelNodes[i]; | |
527 Unparser unparser = multiFile ? unparsers[element.library] : mainUnparser; | |
528 if (node is ClassNode) { | |
529 // TODO(smok): Filter out default constructors here. | |
530 unparser.unparseClassWithBody(node, memberNodes[node]); | |
531 } else { | |
532 unparser.unparse(node); | |
533 } | |
534 unparser.newline(); | |
535 } | |
536 | |
537 int totalSize = 0; | |
538 String assembledCode; | |
539 if (multiFile) { | |
540 for(LibraryElement outputLibrary in libraryInfo.userLibraries) { | |
541 // TODO(sigurdm): Make the unparser output directly into the buffer | |
542 // instead of caching in `.result`. | |
543 String code = unparsers[outputLibrary].result; | |
544 totalSize += code.length; | |
545 outputProvider(outputPaths[outputLibrary], "dart") | |
546 ..add(code) | |
547 ..close(); | |
548 } | |
549 // TODO(sigurdm): We should get rid of compiler.assembledCode. | |
550 assembledCode = unparsers[mainFunction.library].result; | |
551 } else { | |
552 assembledCode = mainUnparser.result; | |
553 outputProvider("", "dart") | |
554 ..add(assembledCode) | |
555 ..close(); | |
556 | |
557 totalSize = assembledCode.length; | |
558 } | |
559 | |
560 return assembledCode; | |
561 } | |
562 } | |
OLD | NEW |