| // 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. |
| * - [tree.TreeElements] only map to patch elements from inside a patch library. |
| * TODO(johnniwinther): Simplify this invariant to use only declarations in |
| * [tree.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:async'; |
| |
| import "tree/tree.dart" as tree; |
| import "dart2jslib.dart" as leg; // CompilerTask, Compiler. |
| import "../compiler.dart" as api; |
| import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners |
| import "elements/elements.dart"; |
| import "elements/modelx.dart" show LibraryElementX, MetadataAnnotationX; |
| 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. |
| */ |
| Future patchLibrary(leg.LibraryDependencyHandler handler, |
| Uri patchUri, LibraryElement originLibrary) { |
| return compiler.readScript(patchUri, null).then((leg.Script script) { |
| var patchLibrary = new LibraryElementX(script, null, originLibrary); |
| return compiler.withCurrentElement(patchLibrary, () { |
| handler.registerNewLibrary(patchLibrary); |
| var 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); |
| // TODO(rnystrom): Remove .toList() here if #11523 is fixed. |
| return Future.forEach(imports.toLink().toList(), (tag) { |
| return compiler.withCurrentElement(patchLibrary, () { |
| return compiler.libraryLoader.registerLibraryFromTag( |
| handler, patchLibrary, tag); |
| }); |
| }); |
| }); |
| }); |
| } |
| |
| void scanLibraryElements( |
| CompilationUnitElement compilationUnit, |
| LinkBuilder<tree.LibraryTag> imports) { |
| measure(() { |
| // TODO(lrn): Possibly recursively handle 'part' 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(ClassElement originClass, |
| Link<Element> patches) { |
| for (Element patch in patches) { |
| if (!isPatchElement(patch)) continue; |
| |
| Element origin = originClass.localLookup(patch.name); |
| patchElement(compiler, origin, patch); |
| } |
| } |
| } |
| |
| /** |
| * 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) => token.value == const SourceString('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 popMetadataHack() { |
| // 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 patch) { |
| if (isMemberPatch || (isClassPatch && patch is ClassElement)) { |
| // Apply patch. |
| patch.addMetadata(popMetadataHack()); |
| LibraryElement originLibrary = compilationUnitElement.getLibrary(); |
| assert(originLibrary.isPatched); |
| Element origin = originLibrary.localLookup(patch.name); |
| patchElement(listener, origin, patch); |
| } |
| super.pushElement(patch); |
| } |
| } |
| |
| /** |
| * 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 popMetadataHack() { |
| // 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(popMetadataHack()); |
| } |
| super.addMember(element); |
| } |
| } |
| |
| // TODO(ahe): Get rid of this class. |
| class PatchMetadataAnnotation extends MetadataAnnotationX { |
| final leg.Constant value = null; |
| |
| PatchMetadataAnnotation() : super(STATE_DONE); |
| |
| Token get beginToken => null; |
| Token get endToken => null; |
| } |
| |
| void patchElement(leg.DiagnosticListener listener, |
| Element origin, |
| Element patch) { |
| if (origin == null) { |
| listener.reportError( |
| patch, leg.MessageKind.PATCH_NON_EXISTING, {'name': patch.name}); |
| return; |
| } |
| if (!(origin.isClass() || |
| origin.isConstructor() || |
| origin.isFunction() || |
| origin.isAbstractField())) { |
| // TODO(ahe): Remove this error when the parser rejects all bad modifiers. |
| listener.reportError(origin, leg.MessageKind.PATCH_NONPATCHABLE); |
| return; |
| } |
| if (patch.isClass()) { |
| tryPatchClass(listener, origin, patch); |
| } else if (patch.isGetter()) { |
| tryPatchGetter(listener, origin, patch); |
| } else if (patch.isSetter()) { |
| tryPatchSetter(listener, origin, patch); |
| } else if (patch.isConstructor()) { |
| tryPatchConstructor(listener, origin, patch); |
| } else if(patch.isFunction()) { |
| tryPatchFunction(listener, origin, patch); |
| } else { |
| // TODO(ahe): Remove this error when the parser rejects all bad modifiers. |
| listener.reportError(patch, leg.MessageKind.PATCH_NONPATCHABLE); |
| } |
| } |
| |
| void tryPatchClass(leg.DiagnosticListener listener, |
| Element origin, |
| ClassElement patch) { |
| if (!origin.isClass()) { |
| listener.reportError( |
| origin, leg.MessageKind.PATCH_NON_CLASS, {'className': patch.name}); |
| listener.reportInfo( |
| patch, leg.MessageKind.PATCH_POINT_TO_CLASS, {'className': patch.name}); |
| return; |
| } |
| patchClass(listener, origin, patch); |
| } |
| |
| void patchClass(leg.DiagnosticListener listener, |
| ClassElement origin, |
| ClassElement patch) { |
| if (origin.isPatched) { |
| listener.internalErrorOnElement( |
| origin, "Patching the same class more than once."); |
| } |
| // TODO(johnniwinther): Change to functions on the ElementX class. |
| origin.patch = patch; |
| patch.origin = origin; |
| } |
| |
| void tryPatchGetter(leg.DiagnosticListener listener, |
| Element origin, |
| FunctionElement patch) { |
| if (!origin.isAbstractField()) { |
| listener.reportError( |
| origin, leg.MessageKind.PATCH_NON_GETTER, {'name': origin.name}); |
| listener.reportInfo( |
| patch, |
| leg.MessageKind.PATCH_POINT_TO_GETTER, {'getterName': patch.name}); |
| return; |
| } |
| AbstractFieldElement originField = origin; |
| if (originField.getter == null) { |
| listener.reportError( |
| origin, leg.MessageKind.PATCH_NO_GETTER, {'getterName': patch.name}); |
| listener.reportInfo( |
| patch, |
| leg.MessageKind.PATCH_POINT_TO_GETTER, {'getterName': patch.name}); |
| return; |
| } |
| patchFunction(listener, originField.getter, patch); |
| } |
| |
| void tryPatchSetter(leg.DiagnosticListener listener, |
| Element origin, |
| FunctionElement patch) { |
| if (!origin.isAbstractField()) { |
| listener.reportError( |
| origin, leg.MessageKind.PATCH_NON_SETTER, {'name': origin.name}); |
| listener.reportInfo( |
| patch, |
| leg.MessageKind.PATCH_POINT_TO_SETTER, {'setterName': patch.name}); |
| return; |
| } |
| AbstractFieldElement originField = origin; |
| if (originField.setter == null) { |
| listener.reportError( |
| origin, leg.MessageKind.PATCH_NO_SETTER, {'setterName': patch.name}); |
| listener.reportInfo( |
| patch, |
| leg.MessageKind.PATCH_POINT_TO_SETTER, {'setterName': patch.name}); |
| return; |
| } |
| patchFunction(listener, originField.setter, patch); |
| } |
| |
| void tryPatchConstructor(leg.DiagnosticListener listener, |
| Element origin, |
| FunctionElement patch) { |
| if (!origin.isConstructor()) { |
| listener.reportError( |
| origin, |
| leg.MessageKind.PATCH_NON_CONSTRUCTOR, {'constructorName': patch.name}); |
| listener.reportInfo( |
| patch, |
| leg.MessageKind.PATCH_POINT_TO_CONSTRUCTOR, |
| {'constructorName': patch.name}); |
| return; |
| } |
| patchFunction(listener, origin, patch); |
| } |
| |
| void tryPatchFunction(leg.DiagnosticListener listener, |
| Element origin, |
| FunctionElement patch) { |
| if (!origin.isFunction()) { |
| listener.reportError( |
| origin, |
| leg.MessageKind.PATCH_NON_FUNCTION, {'functionName': patch.name}); |
| listener.reportInfo( |
| patch, |
| leg.MessageKind.PATCH_POINT_TO_FUNCTION, {'functionName': patch.name}); |
| return; |
| } |
| patchFunction(listener, origin, patch); |
| } |
| |
| void patchFunction(leg.DiagnosticListener listener, |
| FunctionElement origin, |
| FunctionElement patch) { |
| if (!origin.modifiers.isExternal()) { |
| listener.reportError(origin, leg.MessageKind.PATCH_NON_EXTERNAL); |
| listener.reportInfo( |
| patch, |
| leg.MessageKind.PATCH_POINT_TO_FUNCTION, {'functionName': patch.name}); |
| return; |
| } |
| if (origin.isPatched) { |
| listener.internalErrorOnElement(origin, |
| "Trying to patch a function more than once."); |
| } |
| if (origin.cachedNode != null) { |
| listener.internalErrorOnElement(origin, |
| "Trying to patch an already compiled function."); |
| } |
| // Don't just assign the patch field. This also updates the cachedNode. |
| // TODO(johnniwinther): Change to functions on the ElementX class. |
| origin.setPatch(patch); |
| patch.origin = origin; |
| } |
| |
| // TODO(johnniwinther): Add unittest when patch is (real) metadata. |
| 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; |
| } |