| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of dart_backend; |
| |
| typedef bool IsSafeToRemoveTypeDeclarations( |
| Map<ClassElement, Iterable<Element>> classMembers); |
| typedef void ElementCallback<E>(E element); |
| typedef void ElementPostProcessFunction( |
| AstElement element, ElementAst elementAst, |
| ElementCallback<TypedefElement> typedefCallback, |
| ElementCallback<ClassElement> classCallback); |
| typedef ElementAst ComputeElementAstFunction(AstElement element); |
| typedef bool ElementFilter(Element element); |
| typedef List<Element> ElementSorter(Iterable<Element> elements); |
| |
| /// Output engine for dart2dart that is shared between the dart2js and the |
| /// analyzer implementations of dart2dart. |
| class DartOutputter { |
| final DiagnosticListener listener; |
| final CompilerOutputProvider outputProvider; |
| final bool forceStripTypes; |
| |
| // TODO(antonm): make available from command-line options. |
| final bool outputAst = false; |
| final bool enableMinification; |
| |
| /// If `true`, libraries are generated into separate files. |
| final bool multiFile; |
| |
| /// Internal structures accessible for tests and logging. |
| // TODO(johnniwinther): Clean this up. |
| PlaceholderRenamer renamer; |
| MainOutputGenerator output; |
| LibraryInfo libraryInfo; |
| ElementInfo elementInfo; |
| |
| // TODO(johnniwinther): Support recompilation. |
| DartOutputter(this.listener, this.outputProvider, |
| {bool this.forceStripTypes: false, |
| bool this.enableMinification: false, |
| bool this.multiFile: false}); |
| |
| /// Generate Dart code for the program starting at [mainFunction]. |
| /// |
| /// [libraries] is the set of all libraries (user/package/sdk) that are |
| /// referenced in the program. |
| /// |
| /// [instantiatedClasses] is the set of classes that are potentially |
| /// instantiated in the program. |
| /// |
| /// [resolvedElements] is the set of methods, constructors, and fields that |
| /// are potentially accessed/called in the program. |
| /// |
| /// The [sortElements] function is used to sort [instantiatedClasses] and |
| /// [resolvedElements] in the generated output. |
| /// |
| /// Returns the total size of the generated code. |
| int assembleProgram({ |
| MirrorRenamer mirrorRenamer: const MirrorRenamer(), |
| Iterable<LibraryElement> libraries, |
| Iterable<Element> instantiatedClasses, |
| Iterable<Element> resolvedElements, |
| Iterable<ClassElement> usedTypeLiterals: const <ClassElement>[], |
| FunctionElement mainFunction, |
| Uri outputUri, |
| ElementPostProcessFunction postProcessElementAst, |
| ComputeElementAstFunction computeElementAst, |
| ElementFilter shouldOutput, |
| IsSafeToRemoveTypeDeclarations isSafeToRemoveTypeDeclarations, |
| ElementSorter sortElements}) { |
| |
| assert(invariant(NO_LOCATION_SPANNABLE, libraries != null, |
| message: "'libraries' must be non-null.")); |
| assert(invariant(NO_LOCATION_SPANNABLE, instantiatedClasses != null, |
| message: "'instantiatedClasses' must be non-null.")); |
| assert(invariant(NO_LOCATION_SPANNABLE, resolvedElements != null, |
| message: "'resolvedElements' must be non-null.")); |
| assert(invariant(NO_LOCATION_SPANNABLE, mainFunction != null, |
| message: "'mainFunction' must be non-null.")); |
| assert(invariant(NO_LOCATION_SPANNABLE, computeElementAst != null, |
| message: "'computeElementAst' must be non-null.")); |
| assert(invariant(NO_LOCATION_SPANNABLE, shouldOutput != null, |
| message: "'shouldOutput' must be non-null.")); |
| assert(invariant(NO_LOCATION_SPANNABLE, |
| isSafeToRemoveTypeDeclarations != null, |
| message: "'isSafeToRemoveTypeDeclarations' must be non-null.")); |
| |
| if (sortElements == null) { |
| // Ensure deterministic output order. |
| sortElements = (Iterable<Element> elements) { |
| List<Element> list = elements.toList(); |
| list.sort((Element a, Element b) => a.name.compareTo(b.name)); |
| return list; |
| }; |
| } |
| |
| libraryInfo = LibraryInfo.processLibraries( |
| listener, libraries, resolvedElements); |
| |
| elementInfo = ElementInfoProcessor.createElementInfo( |
| instantiatedClasses, |
| resolvedElements, |
| usedTypeLiterals, |
| postProcessElementAst: postProcessElementAst, |
| parseElementAst: computeElementAst, |
| shouldOutput: shouldOutput, |
| sortElements: sortElements); |
| |
| PlaceholderCollector collector = collectPlaceholders( |
| listener, |
| mirrorRenamer, |
| mainFunction, |
| libraryInfo, |
| elementInfo); |
| |
| renamer = createRenamer( |
| collector, |
| libraryInfo, |
| elementInfo, |
| enableMinification: enableMinification, |
| forceStripTypes: forceStripTypes, |
| isSafeToRemoveTypeDeclarations: isSafeToRemoveTypeDeclarations); |
| |
| if (outputAst) { |
| String code = astOutput(listener, elementInfo); |
| outputProvider("", "dart") |
| ..add(code) |
| ..close(); |
| return code.length; |
| } else { |
| output = new MainOutputGenerator(); |
| return output.generateCode( |
| libraryInfo, |
| elementInfo, |
| collector, |
| renamer, |
| mainFunction, |
| outputUri, |
| outputProvider, |
| mirrorRenamer, |
| multiFile: multiFile, |
| forceStripTypes: forceStripTypes, |
| enableMinification: enableMinification); |
| } |
| } |
| |
| static PlaceholderCollector collectPlaceholders( |
| DiagnosticListener listener, |
| MirrorRenamer mirrorRenamer, |
| FunctionElement mainFunction, |
| LibraryInfo libraryInfo, |
| ElementInfo elementInfo) { |
| // Create all necessary placeholders. |
| PlaceholderCollector collector = new PlaceholderCollector( |
| listener, |
| mirrorRenamer, |
| libraryInfo.fixedDynamicNames, |
| elementInfo.elementAsts, |
| mainFunction); |
| |
| makePlaceholders(element) { |
| collector.collect(element); |
| |
| if (element.isClass && !element.isEnumClass) { |
| elementInfo.classMembers[element].forEach(makePlaceholders); |
| } |
| } |
| elementInfo.topLevelElements.forEach(makePlaceholders); |
| return collector; |
| } |
| |
| static PlaceholderRenamer createRenamer( |
| PlaceholderCollector collector, |
| LibraryInfo libraryInfo, |
| ElementInfo elementInfo, |
| {bool enableMinification: false, |
| bool forceStripTypes: false, |
| isSafeToRemoveTypeDeclarations}) { |
| // Create renames. |
| bool shouldCutDeclarationTypes = forceStripTypes |
| || (enableMinification |
| && isSafeToRemoveTypeDeclarations(elementInfo.classMembers)); |
| |
| PlaceholderRenamer placeholderRenamer = new PlaceholderRenamer( |
| libraryInfo.fixedDynamicNames, |
| libraryInfo.fixedStaticNames, |
| libraryInfo.reexportingLibraries, |
| cutDeclarationTypes: shouldCutDeclarationTypes, |
| enableMinification: enableMinification); |
| |
| placeholderRenamer.computeRenames(collector); |
| return placeholderRenamer; |
| } |
| |
| static String astOutput(DiagnosticListener listener, |
| ElementInfo elementInfo) { |
| // TODO(antonm): Ideally XML should be a separate backend. |
| // TODO(antonm): obey renames and minification, at least as an option. |
| StringBuffer sb = new StringBuffer(); |
| outputElement(element) { |
| sb.write(element.parseNode(listener).toDebugString()); |
| } |
| |
| // Emit XML for AST instead of the program. |
| for (Element topLevel in elementInfo.topLevelElements) { |
| if (topLevel.isClass && |
| !elementInfo.emitNoMembersFor.contains(topLevel)) { |
| // TODO(antonm): add some class info. |
| elementInfo.classMembers[topLevel].forEach(outputElement); |
| } else { |
| outputElement(topLevel); |
| } |
| } |
| return '<Program>\n$sb</Program>\n'; |
| } |
| } |
| |
| class LibraryInfo { |
| final Set<String> fixedStaticNames; |
| final Set<String> fixedDynamicNames; |
| final Map<Element, LibraryElement> reexportingLibraries; |
| final List<LibraryElement> userLibraries; |
| |
| LibraryInfo(this.fixedStaticNames, |
| this.fixedDynamicNames, |
| this.reexportingLibraries, |
| this.userLibraries); |
| |
| static LibraryInfo processLibraries( |
| DiagnosticListener listener, |
| Iterable<LibraryElement> libraries, |
| Iterable<AstElement> resolvedElements) { |
| Set<String> fixedStaticNames = new Set<String>(); |
| Set<String> fixedDynamicNames = new Set<String>(); |
| Map<Element, LibraryElement> reexportingLibraries = |
| <Element, LibraryElement>{}; |
| List<LibraryElement> userLibraries = <LibraryElement>[]; |
| // Conservatively traverse all platform libraries and collect member names. |
| // TODO(antonm): ideally we should only collect names of used members, |
| // however as of today there are problems with names of some core library |
| // interfaces, most probably for interfaces of literals. |
| |
| for (LibraryElement library in libraries) { |
| if (!library.isPlatformLibrary) { |
| userLibraries.add(library); |
| continue; |
| } |
| library.forEachLocalMember((Element element) { |
| if (element.isClass) { |
| ClassElement classElement = element; |
| assert(invariant(classElement, classElement.isResolved, |
| message: "Unresolved platform class.")); |
| classElement.forEachLocalMember((member) { |
| if (member.isInstanceMember) { |
| fixedDynamicNames.add(member.name); |
| } else { |
| fixedStaticNames.add(member.name); |
| } |
| }); |
| } |
| // Even class names are added due to a delicate problem we have: |
| // if one imports dart:core with a prefix, we cannot tell prefix.name |
| // from dynamic invocation (alas!). So we'd better err on preserving |
| // those names. |
| fixedStaticNames.add(element.name); |
| }); |
| |
| for (Element export in library.exports) { |
| if (!library.isInternalLibrary && |
| export.library.isInternalLibrary) { |
| // If an element of an internal library is reexported by a platform |
| // library, we have to import the reexporting library instead of the |
| // internal library, because the internal library is an |
| // implementation detail of dart2js. |
| reexportingLibraries[export] = library; |
| } |
| } |
| } |
| |
| // Map to keep track of names of enum classes. Since these cannot be renamed |
| // we ensure that they are unique. |
| Map<String, ClassElement> enumClassMap = <String, ClassElement>{}; |
| |
| // As of now names of named optionals are not renamed. Therefore add all |
| // field names used as named optionals into [fixedMemberNames]. |
| for (final element in resolvedElements) { |
| if (!element.isConstructor) continue; |
| for (ParameterElement parameter in element.parameters) { |
| if (parameter.isInitializingFormal && parameter.isNamed) { |
| fixedDynamicNames.add(parameter.name); |
| } |
| } |
| ClassElement cls = element.enclosingClass; |
| if (cls != null && cls.isEnumClass) { |
| fixedDynamicNames.add('index'); |
| |
| ClassElement existingEnumClass = |
| enumClassMap.putIfAbsent(cls.name, () => cls); |
| if (existingEnumClass != cls) { |
| listener.reportError(cls, MessageKind.GENERIC, |
| {'text': "Duplicate enum names are not supported in dart2dart."}); |
| listener.reportInfo(existingEnumClass, MessageKind.GENERIC, |
| {'text': "This is the other declaration of '${cls.name}'."}); |
| } |
| } |
| } |
| |
| fixedStaticNames.addAll(enumClassMap.keys); |
| |
| // The VM will automatically invoke the call method of objects |
| // that are invoked as functions. Make sure to not rename that. |
| fixedDynamicNames.add('call'); |
| |
| return new LibraryInfo( |
| fixedStaticNames, fixedDynamicNames, |
| reexportingLibraries, userLibraries); |
| } |
| } |
| |
| class ElementInfo { |
| final Map<Element, ElementAst> elementAsts; |
| final Iterable<Element> topLevelElements; |
| final Map<ClassElement, Iterable<Element>> classMembers; |
| final Iterable<ClassElement> emitNoMembersFor; |
| |
| ElementInfo(this.elementAsts, |
| this.topLevelElements, |
| this.classMembers, |
| this.emitNoMembersFor); |
| } |
| |
| class ElementInfoProcessor implements ElementInfo { |
| final Map<Element, ElementAst> elementAsts = new Map<Element, ElementAst>(); |
| final Set<Element> topLevelElements = new Set<Element>(); |
| final Map<ClassElement, Set<Element>> classMembers = |
| new Map<ClassElement, Set<Element>>(); |
| final Set<ClassElement> emitNoMembersFor = new Set<ClassElement>(); |
| final ElementPostProcessFunction postProcessElementAst; |
| final ComputeElementAstFunction parseElementAst; |
| final ElementFilter shouldOutput; |
| |
| ElementInfoProcessor( |
| {this.postProcessElementAst, |
| this.parseElementAst, |
| this.shouldOutput}); |
| |
| static ElementInfo createElementInfo( |
| Iterable<ClassElement> instantiatedClasses, |
| Iterable<AstElement> resolvedElements, |
| Iterable<ClassElement> usedTypeLiterals, |
| {ElementPostProcessFunction postProcessElementAst, |
| ComputeElementAstFunction parseElementAst, |
| ElementFilter shouldOutput, |
| ElementSorter sortElements}) { |
| ElementInfoProcessor processor = new ElementInfoProcessor( |
| postProcessElementAst: postProcessElementAst, |
| parseElementAst: parseElementAst, |
| shouldOutput: shouldOutput); |
| return processor.process( |
| instantiatedClasses, resolvedElements, usedTypeLiterals, |
| sortElements: sortElements); |
| } |
| |
| ElementInfo process(Iterable<ClassElement> instantiatedClasses, |
| Iterable<AstElement> resolvedElements, |
| Iterable<ClassElement> usedTypeLiterals, |
| {ElementSorter sortElements}) { |
| // Build all top level elements to emit and necessary class members. |
| instantiatedClasses.where(shouldOutput).forEach(addClass); |
| resolvedElements.where(shouldOutput).forEach(addMember); |
| usedTypeLiterals.forEach((ClassElement element) { |
| if (shouldOutput(element)) { |
| if (!topLevelElements.contains(element)) { |
| // The class is only referenced by type literals. |
| emitNoMembersFor.add(element); |
| } |
| addClass(element); |
| } |
| }); |
| |
| // Sort elements. |
| List<Element> sortedTopLevels = sortElements(topLevelElements); |
| Map<ClassElement, List<Element>> sortedClassMembers = |
| new Map<ClassElement, List<Element>>(); |
| classMembers.forEach((classElement, members) { |
| sortedClassMembers[classElement] = sortElements(members); |
| }); |
| |
| return new ElementInfo( |
| elementAsts, sortedTopLevels, sortedClassMembers, emitNoMembersFor); |
| } |
| |
| void processElement(Element element, ElementAst elementAst) { |
| if (postProcessElementAst != null) { |
| postProcessElementAst(element, elementAst, |
| newTypedefElementCallback, |
| newClassElementCallback); |
| } |
| elementAsts[element] = elementAst; |
| } |
| |
| void addTopLevel(AstElement element, ElementAst elementAst) { |
| if (topLevelElements.contains(element)) return; |
| topLevelElements.add(element); |
| processElement(element, elementAst); |
| } |
| |
| void addClass(ClassElement classElement) { |
| TreeElements treeElements = new TreeElementMapping(classElement); |
| backend2frontend.TreePrinter treePrinter = |
| new backend2frontend.TreePrinter(treeElements); |
| Node node = treePrinter.makeNodeForClassElement(classElement); |
| addTopLevel(classElement, new ElementAst(node, treeElements)); |
| classMembers.putIfAbsent(classElement, () => new Set()); |
| } |
| |
| void newTypedefElementCallback(TypedefElement element) { |
| if (!shouldOutput(element)) return; |
| TreeElements treeElements = new TreeElementMapping(element); |
| backend2frontend.TreePrinter treePrinter = |
| new backend2frontend.TreePrinter(treeElements); |
| Node node = treePrinter.makeTypedef(element); |
| addTopLevel(element, new ElementAst(node, treeElements)); |
| } |
| |
| void newClassElementCallback(ClassElement classElement) { |
| if (!shouldOutput(classElement)) return; |
| addClass(classElement); |
| } |
| |
| void addMember(element) { |
| if (element.isClassMember) { |
| ClassElement enclosingClass = element.enclosingClass; |
| assert(enclosingClass.isClass); |
| assert(shouldOutput(enclosingClass)); |
| addClass(enclosingClass); |
| classMembers[enclosingClass].add(element); |
| if (enclosingClass.isEnumClass) return; |
| processElement(element, parseElementAst(element)); |
| } else { |
| if (element.isTopLevel) { |
| addTopLevel(element, parseElementAst(element)); |
| } |
| } |
| } |
| } |
| |
| /// Main output generator for [DartOutputter] that emits dart code through a |
| /// [CompilerOutputProvider]. |
| class MainOutputGenerator { |
| final Map<ClassNode, List<Node>> memberNodes = |
| new Map<ClassNode, List<Node>>(); |
| final List<Node> topLevelNodes = <Node>[]; |
| |
| /// Generates the code and returns the total size. |
| int generateCode( |
| LibraryInfo libraryInfo, |
| ElementInfo elementInfo, |
| PlaceholderCollector collector, |
| PlaceholderRenamer placeholderRenamer, |
| FunctionElement mainFunction, |
| Uri outputUri, |
| CompilerOutputProvider outputProvider, |
| MirrorRenamer mirrorRenamer, |
| {bool multiFile: false, |
| bool forceStripTypes: false, |
| bool enableMinification: false}) { |
| for (Element element in elementInfo.topLevelElements) { |
| topLevelNodes.add(elementInfo.elementAsts[element].ast); |
| if (element.isClass) { |
| ClassElement cls = element; |
| if (cls.isMixinApplication || cls.isEnumClass) { |
| continue; |
| } |
| final members = <Node>[]; |
| for (Element member in elementInfo.classMembers[cls]) { |
| members.add(elementInfo.elementAsts[member].ast); |
| } |
| memberNodes[elementInfo.elementAsts[cls].ast] = members; |
| } |
| } |
| |
| mirrorRenamer.addRenames(placeholderRenamer.renames, |
| topLevelNodes, collector); |
| |
| Map<LibraryElement, String> outputPaths = new Map<LibraryElement, String>(); |
| Map<LibraryElement, EmitterUnparser> unparsers = |
| new Map<LibraryElement, EmitterUnparser>(); |
| |
| // The single unparser used if we collect all the output in one file. |
| EmitterUnparser mainUnparser = multiFile |
| ? null |
| : new EmitterUnparser(placeholderRenamer.renames, |
| stripTypes: forceStripTypes, |
| minify: enableMinification); |
| |
| if (multiFile) { |
| // TODO(sigurdm): Factor handling of library-paths out from emitting. |
| String mainName = outputUri.pathSegments.last; |
| String mainBaseName = mainName.endsWith(".dart") |
| ? mainName.substring(0, mainName.length - 5) |
| : mainName; |
| // Map each library to a path based on the uri of the original |
| // library and [compiler.outputUri]. |
| Set<String> usedLibraryPaths = new Set<String>(); |
| for (LibraryElement library in libraryInfo.userLibraries) { |
| if (library == mainFunction.library) { |
| outputPaths[library] = mainBaseName; |
| } else { |
| List<String> names = |
| library.canonicalUri.pathSegments.last.split("."); |
| if (names.last == "dart") { |
| names = names.sublist(0, names.length - 1); |
| } |
| outputPaths[library] = |
| "$mainBaseName.${makeUnique(names.join("."), usedLibraryPaths)}"; |
| } |
| } |
| |
| /// Rewrites imports/exports to refer to the paths given in [outputPaths]. |
| for(LibraryElement outputLibrary in libraryInfo.userLibraries) { |
| EmitterUnparser unparser = new EmitterUnparser( |
| placeholderRenamer.renames, |
| stripTypes: forceStripTypes, |
| minify: enableMinification); |
| unparsers[outputLibrary] = unparser; |
| LibraryName libraryName = outputLibrary.libraryTag; |
| if (libraryName != null) { |
| unparser.visitLibraryName(libraryName); |
| } |
| for (LibraryTag tag in outputLibrary.tags) { |
| if (tag is! LibraryDependency) continue; |
| LibraryDependency dependency = tag; |
| LibraryElement libraryElement = |
| outputLibrary.getLibraryFromTag(dependency); |
| String uri = outputPaths.containsKey(libraryElement) |
| ? "${outputPaths[libraryElement]}.dart" |
| : libraryElement.canonicalUri.toString(); |
| if (dependency is Import) { |
| unparser.unparseImportTag(uri); |
| } else { |
| unparser.unparseExportTag(uri); |
| } |
| } |
| } |
| } else { |
| placeholderRenamer.platformImports.forEach( |
| (LibraryElement library, String prefix) { |
| assert(library.isPlatformLibrary && !library.isInternalLibrary); |
| mainUnparser.unparseImportTag(library.canonicalUri.toString()); |
| if (prefix != null) { |
| // Adding a prefixed import because (some) top-level access need |
| // it to avoid shadowing. |
| // TODO(johnniwinther): Avoid prefix-less import if not needed. |
| mainUnparser.unparseImportTag(library.canonicalUri.toString(), |
| prefix: prefix); |
| } |
| }); |
| } |
| |
| for (int i = 0; i < elementInfo.topLevelElements.length; i++) { |
| Element element = elementInfo.topLevelElements.elementAt(i); |
| Node node = topLevelNodes[i]; |
| Unparser unparser = multiFile ? unparsers[element.library] : mainUnparser; |
| if (node is ClassNode) { |
| // TODO(smok): Filter out default constructors here. |
| unparser.unparseClassWithBody(node, memberNodes[node]); |
| } else { |
| unparser.unparse(node); |
| } |
| unparser.newline(); |
| } |
| |
| int totalSize = 0; |
| if (multiFile) { |
| for(LibraryElement outputLibrary in libraryInfo.userLibraries) { |
| // TODO(sigurdm): Make the unparser output directly into the buffer |
| // instead of caching in `.result`. |
| String code = unparsers[outputLibrary].result; |
| totalSize += code.length; |
| outputProvider(outputPaths[outputLibrary], "dart") |
| ..add(code) |
| ..close(); |
| } |
| } else { |
| String code = mainUnparser.result; |
| outputProvider("", "dart") |
| ..add(code) |
| ..close(); |
| |
| totalSize = code.length; |
| } |
| |
| return totalSize; |
| } |
| } |