|  | // 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; | 
|  | } |