blob: a8240adf14acfe59a65b23f5cefc1b2b689f2720 [file] [log] [blame]
// 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);
});
library.forEachExport((Element export) {
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(
listener.createMessage(
cls,
MessageKind.GENERIC,
{'text': "Duplicate enum names are not supported "
"in dart2dart."}),
<DiagnosticMessage>[
listener.createMessage(
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;
if (outputLibrary.hasLibraryName) {
unparser.unparseLibraryName(outputLibrary.libraryName);
}
for (ImportElement import in outputLibrary.imports) {
LibraryElement libraryElement = import.importedLibrary;
String uri = outputPaths.containsKey(libraryElement)
? "${outputPaths[libraryElement]}.dart"
: libraryElement.canonicalUri.toString();
unparser.unparseImportTag(uri);
}
for (ExportElement export in outputLibrary.exports) {
LibraryElement libraryElement = export.exportedLibrary;
String uri = outputPaths.containsKey(libraryElement)
? "${outputPaths[libraryElement]}.dart"
: libraryElement.canonicalUri.toString();
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;
}
}