blob: 0899be3419411bc66fd745de6ce41499c0ae0651 [file] [log] [blame]
// 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 'constants/values.dart' show ConstantValue;
import 'dart2jslib.dart'
show Compiler,
CompilerTask,
DiagnosticListener,
MessageKind,
Script;
import 'elements/elements.dart';
import 'elements/modelx.dart'
show LibraryElementX,
MetadataAnnotationX,
ClassElementX,
FunctionElementX;
import 'helpers/helpers.dart'; // Included for debug helpers.
import 'library_loader.dart' show LibraryLoader;
import 'scanner/scannerlib.dart'; // Scanner, Parsers, Listeners
import 'util/util.dart';
class PatchParserTask extends CompilerTask {
PatchParserTask(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(LibraryLoader loader,
Uri patchUri, LibraryElement originLibrary) {
return compiler.readScript(originLibrary, patchUri)
.then((Script script) {
var patchLibrary = new LibraryElementX(script, null, originLibrary);
return compiler.withCurrentElement(patchLibrary, () {
loader.registerNewLibrary(patchLibrary);
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);
});
return loader.processLibraryTags(patchLibrary);
});
});
}
void scanLibraryElements(CompilationUnitElement compilationUnit) {
measure(() {
// TODO(johnniwinther): Test that parts and exports are handled correctly.
Script script = compilationUnit.script;
Token tokens = new Scanner(script.file).tokenize();
Function idGenerator = compiler.getNextFreeClassId;
Listener patchListener = new PatchElementListener(compiler,
compilationUnit,
idGenerator);
new PartialParser(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;
measure(() => compiler.withCurrentElement(element, () {
MemberListener listener = new MemberListener(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(compiler, patch)) continue;
Element origin = originClass.localLookup(patch.name);
patchElement(compiler, origin, patch);
}
}
}
/**
* Partial parser for patch files that also handles the members of class
* declarations.
*/
class PatchClassElementParser extends PartialParser {
PatchClassElementParser(Listener listener) : super(listener);
Token parseClassBody(Token token) => fullParseClassBody(token);
}
/**
* Extension of [ElementListener] for parsing patch files.
*/
class PatchElementListener extends ElementListener implements Listener {
final Compiler compiler;
PatchElementListener(Compiler compiler,
CompilationUnitElement patchElement,
int idGenerator())
: this.compiler = compiler,
super(compiler, patchElement, idGenerator);
void pushElement(Element patch) {
super.pushElement(patch);
if (isPatchElement(compiler, patch)) {
LibraryElement originLibrary = compilationUnitElement.library;
assert(originLibrary.isPatched);
Element origin = originLibrary.localLookup(patch.name);
patchElement(listener, origin, patch);
}
}
}
void patchElement(Compiler compiler,
Element origin,
Element patch) {
if (origin == null) {
compiler.reportError(
patch, 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.
compiler.reportError(origin, MessageKind.PATCH_NONPATCHABLE);
return;
}
if (patch.isClass) {
tryPatchClass(compiler, origin, patch);
} else if (patch.isGetter) {
tryPatchGetter(compiler, origin, patch);
} else if (patch.isSetter) {
tryPatchSetter(compiler, origin, patch);
} else if (patch.isConstructor) {
tryPatchConstructor(compiler, origin, patch);
} else if(patch.isFunction) {
tryPatchFunction(compiler, origin, patch);
} else {
// TODO(ahe): Remove this error when the parser rejects all bad modifiers.
compiler.reportError(patch, MessageKind.PATCH_NONPATCHABLE);
}
}
void tryPatchClass(Compiler compiler,
Element origin,
ClassElement patch) {
if (!origin.isClass) {
compiler.reportError(
origin, MessageKind.PATCH_NON_CLASS, {'className': patch.name});
compiler.reportInfo(
patch, MessageKind.PATCH_POINT_TO_CLASS, {'className': patch.name});
return;
}
patchClass(compiler, origin, patch);
}
void patchClass(Compiler compiler,
ClassElementX origin,
ClassElementX patch) {
if (origin.isPatched) {
compiler.internalError(origin,
"Patching the same class more than once.");
}
origin.applyPatch(patch);
checkNativeAnnotation(compiler, patch);
}
/// Check whether [cls] has a `@Native(...)` annotation, and if so, set its
/// native name from the annotation.
checkNativeAnnotation(Compiler compiler, ClassElement cls) {
EagerAnnotationHandler.checkAnnotation(compiler, cls,
const NativeAnnotationHandler());
}
/// Abstract interface for pre-resolution detection of metadata.
///
/// The detection is handled in two steps:
/// - match the annotation syntactically and assume that the annotation is valid
/// if it looks correct,
/// - setup a deferred action to check that the annotation has a valid constant
/// value and report an internal error if not.
abstract class EagerAnnotationHandler {
/// Checks that [annotation] looks like a matching annotation and optionally
/// applies actions on [element]. Returns `true` if the annotation matched.
bool apply(Compiler compiler,
Element element,
MetadataAnnotation annotation);
/// Checks that the annotation value is valid.
void validate(Compiler compiler,
Element element,
MetadataAnnotation annotation,
ConstantValue constant);
/// Checks [element] for metadata matching the [handler]. Return `true` if
/// matching metadata was found.
static bool checkAnnotation(Compiler compiler,
Element element,
EagerAnnotationHandler handler) {
for (Link<MetadataAnnotation> link = element.metadata;
!link.isEmpty;
link = link.tail) {
MetadataAnnotation annotation = link.head;
if (handler.apply(compiler, element, annotation)) {
// TODO(johnniwinther): Perform this check in
// [Compiler.onLibrariesLoaded].
compiler.enqueuer.resolution.addDeferredAction(element, () {
annotation.ensureResolved(compiler);
handler.validate(
compiler, element, annotation, annotation.constant.value);
});
return true;
}
}
return false;
}
}
/// Annotation handler for pre-resolution detection of `@Native(...)`
/// annotations.
class NativeAnnotationHandler implements EagerAnnotationHandler {
const NativeAnnotationHandler();
String getNativeAnnotation(MetadataAnnotation annotation) {
if (annotation.beginToken != null &&
annotation.beginToken.next.value == 'Native') {
// Skipping '@', 'Native', and '('.
Token argument = annotation.beginToken.next.next.next;
if (argument is StringToken) {
return argument.value;
}
}
return null;
}
bool apply(Compiler compiler,
Element element,
MetadataAnnotation annotation) {
if (element.isClass) {
String native = getNativeAnnotation(annotation);
if (native != null) {
ClassElementX declaration = element.declaration;
declaration.setNative(native);
return true;
}
}
return false;
}
void validate(Compiler compiler,
Element element,
MetadataAnnotation annotation,
ConstantValue constant) {
if (constant.computeType(compiler).element !=
compiler.nativeAnnotationClass) {
compiler.internalError(annotation, 'Invalid @Native(...) annotation.');
}
}
}
/// Annotation handler for pre-resolution detection of `@patch` annotations.
class PatchAnnotationHandler implements EagerAnnotationHandler {
const PatchAnnotationHandler();
bool isPatchAnnotation(MetadataAnnotation annotation) {
return annotation.beginToken != null &&
annotation.beginToken.next.value == 'patch';
}
bool apply(Compiler compiler,
Element element,
MetadataAnnotation annotation) {
return isPatchAnnotation(annotation);
}
void validate(Compiler compiler,
Element element,
MetadataAnnotation annotation,
ConstantValue constant) {
if (constant != compiler.patchConstant) {
compiler.internalError(annotation, 'Invalid patch annotation.');
}
}
}
void tryPatchGetter(DiagnosticListener listener,
Element origin,
FunctionElement patch) {
if (!origin.isAbstractField) {
listener.reportError(
origin, MessageKind.PATCH_NON_GETTER, {'name': origin.name});
listener.reportInfo(
patch,
MessageKind.PATCH_POINT_TO_GETTER, {'getterName': patch.name});
return;
}
AbstractFieldElement originField = origin;
if (originField.getter == null) {
listener.reportError(
origin, MessageKind.PATCH_NO_GETTER, {'getterName': patch.name});
listener.reportInfo(
patch,
MessageKind.PATCH_POINT_TO_GETTER, {'getterName': patch.name});
return;
}
patchFunction(listener, originField.getter, patch);
}
void tryPatchSetter(DiagnosticListener listener,
Element origin,
FunctionElement patch) {
if (!origin.isAbstractField) {
listener.reportError(
origin, MessageKind.PATCH_NON_SETTER, {'name': origin.name});
listener.reportInfo(
patch,
MessageKind.PATCH_POINT_TO_SETTER, {'setterName': patch.name});
return;
}
AbstractFieldElement originField = origin;
if (originField.setter == null) {
listener.reportError(
origin, MessageKind.PATCH_NO_SETTER, {'setterName': patch.name});
listener.reportInfo(
patch,
MessageKind.PATCH_POINT_TO_SETTER, {'setterName': patch.name});
return;
}
patchFunction(listener, originField.setter, patch);
}
void tryPatchConstructor(DiagnosticListener listener,
Element origin,
FunctionElement patch) {
if (!origin.isConstructor) {
listener.reportError(
origin,
MessageKind.PATCH_NON_CONSTRUCTOR, {'constructorName': patch.name});
listener.reportInfo(
patch,
MessageKind.PATCH_POINT_TO_CONSTRUCTOR,
{'constructorName': patch.name});
return;
}
patchFunction(listener, origin, patch);
}
void tryPatchFunction(DiagnosticListener listener,
Element origin,
FunctionElement patch) {
if (!origin.isFunction) {
listener.reportError(
origin,
MessageKind.PATCH_NON_FUNCTION, {'functionName': patch.name});
listener.reportInfo(
patch,
MessageKind.PATCH_POINT_TO_FUNCTION, {'functionName': patch.name});
return;
}
patchFunction(listener, origin, patch);
}
void patchFunction(DiagnosticListener listener,
FunctionElementX origin,
FunctionElementX patch) {
if (!origin.modifiers.isExternal) {
listener.reportError(origin, MessageKind.PATCH_NON_EXTERNAL);
listener.reportInfo(
patch,
MessageKind.PATCH_POINT_TO_FUNCTION, {'functionName': patch.name});
return;
}
if (origin.isPatched) {
listener.internalError(origin,
"Trying to patch a function more than once.");
}
origin.applyPatch(patch);
}
// TODO(johnniwinther): Add unittest when patch is (real) metadata.
bool isPatchElement(Compiler compiler, Element element) {
return EagerAnnotationHandler.checkAnnotation(compiler, element,
const PatchAnnotationHandler());
}