09f6d09d88
Review URL: https://codereview.chromium.org//11238035 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@14022 260f80e4-7a28-3924-810f-c04153c831b5
506 lines
19 KiB
Dart
506 lines
19 KiB
Dart
// Copyright (c) 2012, 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.
|
|
|
|
/**
|
|
* This library contains the infrastructure to parse and integrate patch files.
|
|
*
|
|
* Three types of elements can be patched: [LibraryElement], [ClassElement],
|
|
* [FunctionElement]. Patches are introduced in patch libraries which are loaded
|
|
* together with the corresponding origin library. Which libraries that are
|
|
* patched is determined by the [dart2jsPatchPath] field of [LibraryInfo] found
|
|
* in [:lib/_internal/libraries.dart:].
|
|
*
|
|
* Patch libraries are parsed like regular library and thus provided with their
|
|
* own elements. These elements which are distinct from the elements from the
|
|
* patched library and the relation between patched and patch elements is
|
|
* established through the [:patch:] and [:origin:] fields found on
|
|
* [LibraryElement], [ClassElement] and [FunctionElement]. The [:patch:] fields
|
|
* are set on the patched elements to point to their corresponding patch
|
|
* element, and the [:origin:] elements are set on the patch elements to point
|
|
* their corresponding patched elements.
|
|
*
|
|
* The fields [Element.isPatched] and [Element.isPatch] can be used to determine
|
|
* whether the [:patch:] or [:origin:] field, respectively, has been set on an
|
|
* element, regardless of whether the element is one of the three patchable
|
|
* element types or not.
|
|
*
|
|
* ## Variants of Classes and Functions ##
|
|
*
|
|
* With patches there are four variants of classes and function:
|
|
*
|
|
* Regular: A class or function which is not declared in a patch library and
|
|
* which has no corresponding patch.
|
|
* Origin: A class or function which is not declared in a patch library and
|
|
* which has a corresponding patch. Origin functions must use the [:external:]
|
|
* modifier and can have no body. Origin classes and functions are also
|
|
* called 'patched'.
|
|
* Patch: A class or function which is declared in a patch library and which
|
|
* has a corresponding origin. Both patch classes and patch functions must use
|
|
* the [:patch:] modifier.
|
|
* Injected: A class or function (or even field) which is declared in a
|
|
* patch library and which has no corresponding origin. An injected element
|
|
* cannot use the [:patch:] modifier. Injected elements are never visible from
|
|
* outside the patch library in which they have been declared. For this
|
|
* reason, injected elements are often declared private and therefore called
|
|
* also called 'patch private'.
|
|
*
|
|
* Examples of the variants is shown in the code below:
|
|
*
|
|
* // In the origin library:
|
|
* class RegularClass { // A regular class.
|
|
* void regularMethod() {} // A regular method.
|
|
* }
|
|
* class PatchedClass { // An origin class.
|
|
* int regularField; // A regular field.
|
|
* void regularMethod() {} // A regular method.
|
|
* external void patchedMethod(); // An origin method.
|
|
* }
|
|
*
|
|
* // In the patch library:
|
|
* class _InjectedClass { // An injected class.
|
|
* void _injectedMethod() {} // An injected method.
|
|
* }
|
|
* patch class PatchedClass { // A patch class.
|
|
* int _injectedField; { // An injected field.
|
|
* patch void patchedMethod() {} // A patch method.
|
|
* }
|
|
*
|
|
*
|
|
* ## Declaration and Implementation ##
|
|
*
|
|
* With patches we have two views on elements: as the 'declaration' which
|
|
* introduces the entity and defines its interface, and as the 'implementation'
|
|
* which defines the actual implementation of the entity.
|
|
*
|
|
* Every element has a 'declaration' and an 'implementation' element. For
|
|
* regular and injected elements these are the same. For origin elements the
|
|
* declaration is the element itself and the implementation is the patch element
|
|
* found through its [:patch:] field. For patch elements the implementation is
|
|
* the element itself and the declaration is the origin element found through
|
|
* its [:origin:] field. The declaration and implementation of any element is
|
|
* conveniently available through the [Element.declaration] and
|
|
* [Element.implementation] getters.
|
|
*
|
|
* Most patch-related invariants enforced through-out the compiler are defined
|
|
* in terms of 'declaration' and 'implementation', and tested through the
|
|
* predicate getters [Element.isDeclaration] and [Element.isImplementation].
|
|
* Patch invariants are stated both in comments and as assertions.
|
|
*
|
|
*
|
|
* ## General invariant guidelines ##
|
|
*
|
|
* For [LibraryElement] we always use declarations. This means the
|
|
* [Element.getLibrary] method will only return library declarations. Patch
|
|
* library implementations are only accessed through calls to
|
|
* [Element.getImplementationLibrary] which is used to setup the correct
|
|
* [Element.enclosingElement] relation between patch/injected elements and the
|
|
* patch library.
|
|
*
|
|
* For [ClassElement] and [FunctionElement] we use declarations for determining
|
|
* identity and implementations for work based on the AST nodes, such as
|
|
* resolution, type-checking, type inference, building SSA graphs, etc.
|
|
* - Worklist only contain declaration elements.
|
|
* - Most maps and sets use declarations exclusively, and their individual
|
|
* invariants are stated in the field comments.
|
|
* - [TreeElements] only map to patch elements from inside a patch library.
|
|
* TODO(johnniwinther): Simplify this invariant to use only declarations in
|
|
* [TreeElements].
|
|
* - Builders shift between declaration and implementation depending on usages.
|
|
* - Compile-time constants use constructor implementation exclusively.
|
|
* - Work on function parameters is performed on the declaration of the function
|
|
* element.
|
|
*/
|
|
|
|
library patchparser;
|
|
|
|
import "dart:uri";
|
|
import "tree/tree.dart" as tree;
|
|
import "dart2jslib.dart" as leg; // CompilerTask, Compiler.
|
|
import "apiimpl.dart";
|
|
import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners
|
|
import "elements/elements.dart";
|
|
import 'util/util.dart';
|
|
|
|
class PatchParserTask extends leg.CompilerTask {
|
|
PatchParserTask(leg.Compiler compiler): super(compiler);
|
|
final String name = "Patching Parser";
|
|
|
|
/**
|
|
* Scans a library patch file, applies the method patches and
|
|
* injections to the library, and returns a list of class
|
|
* patches.
|
|
*/
|
|
void patchLibrary(leg.LibraryDependencyHandler handler,
|
|
Uri patchUri, LibraryElement originLibrary) {
|
|
|
|
leg.Script script = compiler.readScript(patchUri, null);
|
|
var patchLibrary = new LibraryElement(script, patchUri, originLibrary);
|
|
handler.registerNewLibrary(patchLibrary);
|
|
LinkBuilder<tree.LibraryTag> imports = new LinkBuilder<tree.LibraryTag>();
|
|
compiler.withCurrentElement(patchLibrary.entryCompilationUnit, () {
|
|
// This patches the elements of the patch library into [library].
|
|
// Injected elements are added directly under the compilation unit.
|
|
// Patch elements are stored on the patched functions or classes.
|
|
scanLibraryElements(patchLibrary.entryCompilationUnit, imports);
|
|
});
|
|
// After scanning declarations, we handle the import tags in the patch.
|
|
// TODO(lrn): These imports end up in the original library and are in
|
|
// scope for the original methods too. This should be fixed.
|
|
compiler.importHelperLibrary(originLibrary);
|
|
for (tree.LibraryTag tag in imports.toLink()) {
|
|
compiler.libraryLoader.registerLibraryFromTag(handler, patchLibrary, tag);
|
|
}
|
|
}
|
|
|
|
void scanLibraryElements(
|
|
CompilationUnitElement compilationUnit,
|
|
LinkBuilder<tree.LibraryTag> imports) {
|
|
measure(() {
|
|
// TODO(lrn): Possibly recursively handle #source directives in patch.
|
|
leg.Script script = compilationUnit.script;
|
|
Token tokens = new StringScanner(script.text).tokenize();
|
|
Function idGenerator = compiler.getNextFreeClassId;
|
|
PatchListener patchListener =
|
|
new PatchElementListener(compiler,
|
|
compilationUnit,
|
|
idGenerator,
|
|
imports);
|
|
new PatchParser(patchListener).parseUnit(tokens);
|
|
});
|
|
}
|
|
|
|
void parsePatchClassNode(PartialClassElement element) {
|
|
// Parse [PartialClassElement] using a "patch"-aware parser instead
|
|
// of calling its [parseNode] method.
|
|
if (element.cachedNode != null) return;
|
|
|
|
return measure(() => compiler.withCurrentElement(element, () {
|
|
PatchMemberListener listener = new PatchMemberListener(compiler, element);
|
|
Parser parser = new PatchClassElementParser(listener);
|
|
Token token = parser.parseTopLevelDeclaration(element.beginToken);
|
|
assert(identical(token, element.endToken.next));
|
|
element.cachedNode = listener.popNode();
|
|
assert(listener.nodes.isEmpty);
|
|
|
|
Link<Element> patches = element.localMembers;
|
|
applyContainerPatch(element.origin, patches);
|
|
}));
|
|
}
|
|
|
|
void applyContainerPatch(ScopeContainerElement original,
|
|
Link<Element> patches) {
|
|
while (!patches.isEmpty) {
|
|
Element patchElement = patches.head;
|
|
Element originalElement = original.localLookup(patchElement.name);
|
|
if (patchElement.isAccessor() && originalElement != null) {
|
|
if (!identical(originalElement.kind, ElementKind.ABSTRACT_FIELD)) {
|
|
compiler.internalError(
|
|
"Cannot patch non-getter/setter with getter/setter",
|
|
element: originalElement);
|
|
}
|
|
AbstractFieldElement originalField = originalElement;
|
|
if (patchElement.isGetter()) {
|
|
originalElement = originalField.getter;
|
|
} else {
|
|
originalElement = originalField.setter;
|
|
}
|
|
}
|
|
if (originalElement == null) {
|
|
if (isPatchElement(patchElement)) {
|
|
compiler.internalError("Cannot patch non-existing member '"
|
|
"${patchElement.name.slowToString()}'.");
|
|
}
|
|
} else {
|
|
patchMember(originalElement, patchElement);
|
|
}
|
|
patches = patches.tail;
|
|
}
|
|
}
|
|
|
|
bool isPatchElement(Element element) {
|
|
// TODO(lrn): More checks needed if we introduce metadata for real.
|
|
// In that case, it must have the identifier "native" as metadata.
|
|
for (Link link = element.metadata; !link.isEmpty; link = link.tail) {
|
|
if (link.head is PatchMetadataAnnotation) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void patchMember(Element originalElement, Element patchElement) {
|
|
// The original library has an element with the same name as the patch
|
|
// library element.
|
|
// In this case, the patch library element must be a function marked as
|
|
// "patch" and it must have the same signature as the function it patches.
|
|
if (!isPatchElement(patchElement)) {
|
|
compiler.internalError("Cannot overwrite existing '"
|
|
"${originalElement.name.slowToString()}' with non-patch.");
|
|
}
|
|
if (originalElement is! FunctionElement) {
|
|
// TODO(lrn): Handle class declarations too.
|
|
compiler.internalError("Can only patch functions", element: originalElement);
|
|
}
|
|
FunctionElement original = originalElement;
|
|
if (!original.modifiers.isExternal()) {
|
|
compiler.internalError("Can only patch external functions.", element: original);
|
|
}
|
|
if (patchElement is! FunctionElement ||
|
|
!patchSignatureMatches(original, patchElement)) {
|
|
compiler.internalError("Can only patch functions with matching signatures",
|
|
element: original);
|
|
}
|
|
applyFunctionPatch(original, patchElement);
|
|
}
|
|
|
|
bool patchSignatureMatches(FunctionElement original, FunctionElement patch) {
|
|
// TODO(lrn): Check that patches actually match the signature of
|
|
// the function it's patching.
|
|
return true;
|
|
}
|
|
|
|
void applyFunctionPatch(FunctionElement element,
|
|
FunctionElement patchElement) {
|
|
if (element.isPatched) {
|
|
compiler.internalError("Trying to patch a function more than once.",
|
|
element: element);
|
|
}
|
|
if (element.cachedNode != null) {
|
|
compiler.internalError("Trying to patch an already compiled function.",
|
|
element: element);
|
|
}
|
|
// Don't just assign the patch field. This also updates the cachedNode.
|
|
element.setPatch(patchElement);
|
|
patchElement.origin = element;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extension of the [Listener] interface to handle the extra "patch" pseudo-
|
|
* keyword in patch files.
|
|
* Patch files shouldn't have a type named "patch".
|
|
*/
|
|
abstract class PatchListener extends Listener {
|
|
void beginPatch(Token patch);
|
|
void endPatch(Token patch);
|
|
}
|
|
|
|
/**
|
|
* Partial parser that extends the top-level and class grammars to allow the
|
|
* word "patch" in front of some declarations.
|
|
*/
|
|
class PatchParser extends PartialParser {
|
|
PatchParser(PatchListener listener) : super(listener);
|
|
|
|
PatchListener get patchListener => listener;
|
|
|
|
bool isPatch(Token token) {
|
|
return token.stringValue == null &&
|
|
token.slowToString() == "patch";
|
|
}
|
|
|
|
/**
|
|
* Parse top-level declarations, and allow "patch" in front of functions
|
|
* and classes.
|
|
*/
|
|
Token parseTopLevelDeclaration(Token token) {
|
|
if (!isPatch(token)) {
|
|
return super.parseTopLevelDeclaration(token);
|
|
}
|
|
Token patch = token;
|
|
token = token.next;
|
|
String value = token.stringValue;
|
|
if (identical(value, 'interface')
|
|
|| identical(value, 'typedef')
|
|
|| identical(value, '#')
|
|
|| identical(value, 'abstract')) {
|
|
// At the top level, you can only patch functions and classes.
|
|
// Patch classes and functions can't be marked abstract.
|
|
return listener.unexpected(patch);
|
|
}
|
|
patchListener.beginPatch(patch);
|
|
token = super.parseTopLevelDeclaration(token);
|
|
patchListener.endPatch(patch);
|
|
return token;
|
|
}
|
|
|
|
/**
|
|
* Parse a class member.
|
|
* If the member starts with "patch", it's a member override.
|
|
* Only methods can be overridden, including constructors, getters and
|
|
* setters, but not fields. If "patch" occurs in front of a field, the error
|
|
* is caught elsewhere.
|
|
*/
|
|
Token parseMember(Token token) {
|
|
if (!isPatch(token)) {
|
|
return super.parseMember(token);
|
|
}
|
|
Token patch = token;
|
|
patchListener.beginPatch(patch);
|
|
token = super.parseMember(token.next);
|
|
patchListener.endPatch(patch);
|
|
return token;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Partial parser for patch files that also handles the members of class
|
|
* declarations.
|
|
*/
|
|
class PatchClassElementParser extends PatchParser {
|
|
PatchClassElementParser(PatchListener listener) : super(listener);
|
|
|
|
Token parseClassBody(Token token) => fullParseClassBody(token);
|
|
}
|
|
|
|
/**
|
|
* Extension of [ElementListener] for parsing patch files.
|
|
*/
|
|
class PatchElementListener extends ElementListener implements PatchListener {
|
|
final LinkBuilder<tree.LibraryTag> imports;
|
|
bool isMemberPatch = false;
|
|
bool isClassPatch = false;
|
|
|
|
PatchElementListener(leg.DiagnosticListener listener,
|
|
CompilationUnitElement patchElement,
|
|
int idGenerator(),
|
|
this.imports)
|
|
: super(listener, patchElement, idGenerator);
|
|
|
|
MetadataAnnotation popMetadata() {
|
|
// TODO(ahe): Remove this method.
|
|
popNode(); // Discard null.
|
|
return new PatchMetadataAnnotation();
|
|
}
|
|
|
|
void beginPatch(Token token) {
|
|
if (identical(token.next.stringValue, "class")) {
|
|
isClassPatch = true;
|
|
} else {
|
|
isMemberPatch = true;
|
|
}
|
|
handleIdentifier(token);
|
|
}
|
|
|
|
void endPatch(Token token) {
|
|
if (identical(token.next.stringValue, "class")) {
|
|
isClassPatch = false;
|
|
} else {
|
|
isMemberPatch = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allow script tags (import only, the parser rejects the rest for now) in
|
|
* patch files. The import tags will be added to the library.
|
|
*/
|
|
bool allowLibraryTags() => true;
|
|
|
|
void addLibraryTag(tree.LibraryTag tag) {
|
|
super.addLibraryTag(tag);
|
|
imports.addLast(tag);
|
|
}
|
|
|
|
void pushElement(Element element) {
|
|
if (isMemberPatch || (isClassPatch && element is ClassElement)) {
|
|
// Apply patch.
|
|
element.addMetadata(popMetadata());
|
|
LibraryElement originLibrary = compilationUnitElement.getLibrary();
|
|
assert(originLibrary.isPatched);
|
|
Element existing = originLibrary.localLookup(element.name);
|
|
if (isMemberPatch) {
|
|
if (element is! FunctionElement) {
|
|
listener.internalErrorOnElement(element,
|
|
"Member patch is not a function.");
|
|
}
|
|
FunctionElement functionElement = element;
|
|
if (identical(existing.kind, ElementKind.ABSTRACT_FIELD)) {
|
|
if (!element.isAccessor()) {
|
|
listener.internalErrorOnElement(
|
|
functionElement, "Patching non-accessor with accessor");
|
|
}
|
|
AbstractFieldElement field = existing;
|
|
if (functionElement.isGetter()) {
|
|
existing = field.getter;
|
|
} else {
|
|
existing = field.setter;
|
|
}
|
|
}
|
|
if (existing is! FunctionElement) {
|
|
listener.internalErrorOnElement(functionElement,
|
|
"No corresponding method for patch.");
|
|
}
|
|
FunctionElement existingFunction = existing;
|
|
if (existingFunction.isPatched) {
|
|
listener.internalErrorOnElement(
|
|
functionElement, "Patching the same function more than once.");
|
|
}
|
|
existingFunction.patch = functionElement;
|
|
functionElement.origin = existingFunction;
|
|
} else {
|
|
assert(leg.invariant(element, element is ClassElement));
|
|
ClassElement classElement = element;
|
|
if (existing is! ClassElement) {
|
|
listener.internalErrorOnElement(
|
|
classElement, "Patching a non-class with a class patch.");
|
|
}
|
|
ClassElement existingClass = existing;
|
|
if (existingClass.isPatched) {
|
|
listener.internalErrorOnElement(
|
|
classElement, "Patching the same class more than once.");
|
|
}
|
|
existingClass.patch = classElement;
|
|
classElement.origin = existingClass;
|
|
}
|
|
}
|
|
super.pushElement(element);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extension of [MemberListener] for parsing patch class bodies.
|
|
*/
|
|
class PatchMemberListener extends MemberListener implements PatchListener {
|
|
bool isMemberPatch = false;
|
|
bool isClassPatch = false;
|
|
PatchMemberListener(leg.DiagnosticListener listener,
|
|
Element enclosingElement)
|
|
: super(listener, enclosingElement);
|
|
|
|
MetadataAnnotation popMetadata() {
|
|
// TODO(ahe): Remove this method.
|
|
popNode(); // Discard null.
|
|
return new PatchMetadataAnnotation();
|
|
}
|
|
|
|
void beginPatch(Token token) {
|
|
if (identical(token.next.stringValue, "class")) {
|
|
isClassPatch = true;
|
|
} else {
|
|
isMemberPatch = true;
|
|
}
|
|
handleIdentifier(token);
|
|
}
|
|
|
|
void endPatch(Token token) {
|
|
if (identical(token.next.stringValue, "class")) {
|
|
isClassPatch = false;
|
|
} else {
|
|
isMemberPatch = false;
|
|
}
|
|
}
|
|
|
|
void addMember(Element element) {
|
|
if (isMemberPatch || (isClassPatch && element is ClassElement)) {
|
|
element.addMetadata(popMetadata());
|
|
}
|
|
super.addMember(element);
|
|
}
|
|
}
|
|
|
|
// TODO(ahe): Get rid of this class.
|
|
class PatchMetadataAnnotation extends MetadataAnnotation {
|
|
final leg.Constant value = null;
|
|
|
|
PatchMetadataAnnotation() : super(STATE_DONE);
|
|
}
|