OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * This library contains the infrastructure to parse and integrate patch files. | |
7 * | |
8 * Three types of elements can be patched: [LibraryElement], [ClassElement], | |
9 * [FunctionElement]. Patches are introduced in patch libraries which are loaded | |
10 * together with the corresponding origin library. Which libraries that are | |
11 * patched is determined by the dart2jsPatchPath field of LibraryInfo found | |
12 * in [:lib/_internal/libraries.dart:]. | |
13 * | |
14 * Patch libraries are parsed like regular library and thus provided with their | |
15 * own elements. These elements which are distinct from the elements from the | |
16 * patched library and the relation between patched and patch elements is | |
17 * established through the [:patch:] and [:origin:] fields found on | |
18 * [LibraryElement], [ClassElement] and [FunctionElement]. The [:patch:] fields | |
19 * are set on the patched elements to point to their corresponding patch | |
20 * element, and the [:origin:] elements are set on the patch elements to point | |
21 * their corresponding patched elements. | |
22 * | |
23 * The fields [Element.isPatched] and [Element.isPatch] can be used to determine | |
24 * whether the [:patch:] or [:origin:] field, respectively, has been set on an | |
25 * element, regardless of whether the element is one of the three patchable | |
26 * element types or not. | |
27 * | |
28 * ## Variants of classes and functions ## | |
29 * | |
30 * With patches there are four variants of classes and function: | |
31 * | |
32 * Regular: A class or function which is not declared in a patch library and | |
33 * which has no corresponding patch. | |
34 * Origin: A class or function which is not declared in a patch library and | |
35 * which has a corresponding patch. Origin functions must use the [:external:] | |
36 * modifier and can have no body. Origin classes and functions are also | |
37 * called 'patched'. | |
38 * Patch: A class or function which is declared in a patch library and which | |
39 * has a corresponding origin. Both patch classes and patch functions must use | |
40 * the [:patch:] modifier. | |
41 * Injected: A class or function (or even field) which is declared in a | |
42 * patch library and which has no corresponding origin. An injected element | |
43 * cannot use the [:patch:] modifier. Injected elements are never visible from | |
44 * outside the patch library in which they have been declared. For this | |
45 * reason, injected elements are often declared private and therefore called | |
46 * also called 'patch private'. | |
47 * | |
48 * Examples of the variants is shown in the code below: | |
49 * | |
50 * // In the origin library: | |
51 * class RegularClass { // A regular class. | |
52 * void regularMethod() {} // A regular method. | |
53 * } | |
54 * class PatchedClass { // An origin class. | |
55 * int regularField; // A regular field. | |
56 * void regularMethod() {} // A regular method. | |
57 * external void patchedMethod(); // An origin method. | |
58 * } | |
59 * | |
60 * // In the patch library: | |
61 * class _InjectedClass { // An injected class. | |
62 * void _injectedMethod() {} // An injected method. | |
63 * } | |
64 * @patch class PatchedClass { // A patch class. | |
65 * int _injectedField; { // An injected field. | |
66 * @patch void patchedMethod() {} // A patch method. | |
67 * } | |
68 * | |
69 * | |
70 * ## Declaration and implementation ## | |
71 * | |
72 * With patches we have two views on elements: as the 'declaration' which | |
73 * introduces the entity and defines its interface, and as the 'implementation' | |
74 * which defines the actual implementation of the entity. | |
75 * | |
76 * Every element has a 'declaration' and an 'implementation' element. For | |
77 * regular and injected elements these are the same. For origin elements the | |
78 * declaration is the element itself and the implementation is the patch element | |
79 * found through its [:patch:] field. For patch elements the implementation is | |
80 * the element itself and the declaration is the origin element found through | |
81 * its [:origin:] field. The declaration and implementation of any element is | |
82 * conveniently available through the [Element.declaration] and | |
83 * [Element.implementation] getters. | |
84 * | |
85 * Most patch-related invariants enforced through-out the compiler are defined | |
86 * in terms of 'declaration' and 'implementation', and tested through the | |
87 * predicate getters [Element.isDeclaration] and [Element.isImplementation]. | |
88 * Patch invariants are stated both in comments and as assertions. | |
89 * | |
90 * | |
91 * ## General invariant guidelines ## | |
92 * | |
93 * For [LibraryElement] we always use declarations. This means the | |
94 * [Element.getLibrary] method will only return library declarations. Patch | |
95 * library implementations are only accessed through calls to | |
96 * [Element.getImplementationLibrary] which is used to setup the correct | |
97 * [Element.enclosingElement] relation between patch/injected elements and the | |
98 * patch library. | |
99 * | |
100 * For [ClassElement] and [FunctionElement] we use declarations for determining | |
101 * identity and implementations for work based on the AST nodes, such as | |
102 * resolution, type-checking, type inference, building SSA graphs, etc. | |
103 * - Worklist only contain declaration elements. | |
104 * - Most maps and sets use declarations exclusively, and their individual | |
105 * invariants are stated in the field comments. | |
106 * - [tree.TreeElements] only map to patch elements from inside a patch library. | |
107 * TODO(johnniwinther): Simplify this invariant to use only declarations in | |
108 * [tree.TreeElements]. | |
109 * - Builders shift between declaration and implementation depending on usages. | |
110 * - Compile-time constants use constructor implementation exclusively. | |
111 * - Work on function parameters is performed on the declaration of the function | |
112 * element. | |
113 */ | |
114 | |
115 library patchparser; | |
116 | |
117 import 'dart:async'; | |
118 | |
119 import 'constants/values.dart' show ConstantValue; | |
120 import 'dart2jslib.dart' | |
121 show Compiler, | |
122 CompilerTask, | |
123 DiagnosticListener, | |
124 MessageKind, | |
125 Script; | |
126 import 'elements/elements.dart'; | |
127 import 'elements/modelx.dart' | |
128 show LibraryElementX, | |
129 MetadataAnnotationX, | |
130 ClassElementX, | |
131 FunctionElementX; | |
132 import 'helpers/helpers.dart'; // Included for debug helpers. | |
133 import 'library_loader.dart' show LibraryLoader; | |
134 import 'scanner/scannerlib.dart'; // Scanner, Parsers, Listeners | |
135 import 'util/util.dart'; | |
136 | |
137 class PatchParserTask extends CompilerTask { | |
138 PatchParserTask(Compiler compiler): super(compiler); | |
139 final String name = "Patching Parser"; | |
140 | |
141 /** | |
142 * Scans a library patch file, applies the method patches and | |
143 * injections to the library, and returns a list of class | |
144 * patches. | |
145 */ | |
146 Future patchLibrary(LibraryLoader loader, | |
147 Uri patchUri, LibraryElement originLibrary) { | |
148 return compiler.readScript(originLibrary, patchUri) | |
149 .then((Script script) { | |
150 var patchLibrary = new LibraryElementX(script, null, originLibrary); | |
151 return compiler.withCurrentElement(patchLibrary, () { | |
152 loader.registerNewLibrary(patchLibrary); | |
153 compiler.withCurrentElement(patchLibrary.entryCompilationUnit, () { | |
154 // This patches the elements of the patch library into [library]. | |
155 // Injected elements are added directly under the compilation unit. | |
156 // Patch elements are stored on the patched functions or classes. | |
157 scanLibraryElements(patchLibrary.entryCompilationUnit); | |
158 }); | |
159 return loader.processLibraryTags(patchLibrary); | |
160 }); | |
161 }); | |
162 } | |
163 | |
164 void scanLibraryElements(CompilationUnitElement compilationUnit) { | |
165 measure(() { | |
166 // TODO(johnniwinther): Test that parts and exports are handled correctly. | |
167 Script script = compilationUnit.script; | |
168 Token tokens = new Scanner(script.file).tokenize(); | |
169 Function idGenerator = compiler.getNextFreeClassId; | |
170 Listener patchListener = new PatchElementListener(compiler, | |
171 compilationUnit, | |
172 idGenerator); | |
173 new PartialParser(patchListener).parseUnit(tokens); | |
174 }); | |
175 } | |
176 | |
177 void parsePatchClassNode(PartialClassElement element) { | |
178 // Parse [PartialClassElement] using a "patch"-aware parser instead | |
179 // of calling its [parseNode] method. | |
180 if (element.cachedNode != null) return; | |
181 | |
182 measure(() => compiler.withCurrentElement(element, () { | |
183 MemberListener listener = new MemberListener(compiler, element); | |
184 Parser parser = new PatchClassElementParser(listener); | |
185 Token token = parser.parseTopLevelDeclaration(element.beginToken); | |
186 assert(identical(token, element.endToken.next)); | |
187 element.cachedNode = listener.popNode(); | |
188 assert(listener.nodes.isEmpty); | |
189 | |
190 Link<Element> patches = element.localMembers; | |
191 applyContainerPatch(element.origin, patches); | |
192 })); | |
193 } | |
194 | |
195 void applyContainerPatch(ClassElement originClass, | |
196 Link<Element> patches) { | |
197 for (Element patch in patches) { | |
198 if (!isPatchElement(compiler, patch)) continue; | |
199 | |
200 Element origin = originClass.localLookup(patch.name); | |
201 patchElement(compiler, origin, patch); | |
202 } | |
203 } | |
204 } | |
205 | |
206 /** | |
207 * Partial parser for patch files that also handles the members of class | |
208 * declarations. | |
209 */ | |
210 class PatchClassElementParser extends PartialParser { | |
211 PatchClassElementParser(Listener listener) : super(listener); | |
212 | |
213 Token parseClassBody(Token token) => fullParseClassBody(token); | |
214 } | |
215 | |
216 /** | |
217 * Extension of [ElementListener] for parsing patch files. | |
218 */ | |
219 class PatchElementListener extends ElementListener implements Listener { | |
220 final Compiler compiler; | |
221 | |
222 PatchElementListener(Compiler compiler, | |
223 CompilationUnitElement patchElement, | |
224 int idGenerator()) | |
225 : this.compiler = compiler, | |
226 super(compiler, patchElement, idGenerator); | |
227 | |
228 void pushElement(Element patch) { | |
229 super.pushElement(patch); | |
230 if (isPatchElement(compiler, patch)) { | |
231 LibraryElement originLibrary = compilationUnitElement.library; | |
232 assert(originLibrary.isPatched); | |
233 Element origin = originLibrary.localLookup(patch.name); | |
234 patchElement(listener, origin, patch); | |
235 } | |
236 } | |
237 } | |
238 | |
239 void patchElement(Compiler compiler, | |
240 Element origin, | |
241 Element patch) { | |
242 if (origin == null) { | |
243 compiler.reportError( | |
244 patch, MessageKind.PATCH_NON_EXISTING, {'name': patch.name}); | |
245 return; | |
246 } | |
247 if (!(origin.isClass || | |
248 origin.isConstructor || | |
249 origin.isFunction || | |
250 origin.isAbstractField)) { | |
251 // TODO(ahe): Remove this error when the parser rejects all bad modifiers. | |
252 compiler.reportError(origin, MessageKind.PATCH_NONPATCHABLE); | |
253 return; | |
254 } | |
255 if (patch.isClass) { | |
256 tryPatchClass(compiler, origin, patch); | |
257 } else if (patch.isGetter) { | |
258 tryPatchGetter(compiler, origin, patch); | |
259 } else if (patch.isSetter) { | |
260 tryPatchSetter(compiler, origin, patch); | |
261 } else if (patch.isConstructor) { | |
262 tryPatchConstructor(compiler, origin, patch); | |
263 } else if(patch.isFunction) { | |
264 tryPatchFunction(compiler, origin, patch); | |
265 } else { | |
266 // TODO(ahe): Remove this error when the parser rejects all bad modifiers. | |
267 compiler.reportError(patch, MessageKind.PATCH_NONPATCHABLE); | |
268 } | |
269 } | |
270 | |
271 void tryPatchClass(Compiler compiler, | |
272 Element origin, | |
273 ClassElement patch) { | |
274 if (!origin.isClass) { | |
275 compiler.reportError( | |
276 origin, MessageKind.PATCH_NON_CLASS, {'className': patch.name}); | |
277 compiler.reportInfo( | |
278 patch, MessageKind.PATCH_POINT_TO_CLASS, {'className': patch.name}); | |
279 return; | |
280 } | |
281 patchClass(compiler, origin, patch); | |
282 } | |
283 | |
284 void patchClass(Compiler compiler, | |
285 ClassElementX origin, | |
286 ClassElementX patch) { | |
287 if (origin.isPatched) { | |
288 compiler.internalError(origin, | |
289 "Patching the same class more than once."); | |
290 } | |
291 origin.applyPatch(patch); | |
292 checkNativeAnnotation(compiler, patch); | |
293 } | |
294 | |
295 /// Check whether [cls] has a `@Native(...)` annotation, and if so, set its | |
296 /// native name from the annotation. | |
297 checkNativeAnnotation(Compiler compiler, ClassElement cls) { | |
298 EagerAnnotationHandler.checkAnnotation(compiler, cls, | |
299 const NativeAnnotationHandler()); | |
300 } | |
301 | |
302 /// Abstract interface for pre-resolution detection of metadata. | |
303 /// | |
304 /// The detection is handled in two steps: | |
305 /// - match the annotation syntactically and assume that the annotation is valid | |
306 /// if it looks correct, | |
307 /// - setup a deferred action to check that the annotation has a valid constant | |
308 /// value and report an internal error if not. | |
309 abstract class EagerAnnotationHandler { | |
310 /// Checks that [annotation] looks like a matching annotation and optionally | |
311 /// applies actions on [element]. Returns `true` if the annotation matched. | |
312 bool apply(Compiler compiler, | |
313 Element element, | |
314 MetadataAnnotation annotation); | |
315 | |
316 /// Checks that the annotation value is valid. | |
317 void validate(Compiler compiler, | |
318 Element element, | |
319 MetadataAnnotation annotation, | |
320 ConstantValue constant); | |
321 | |
322 | |
323 /// Checks [element] for metadata matching the [handler]. Return `true` if | |
324 /// matching metadata was found. | |
325 static bool checkAnnotation(Compiler compiler, | |
326 Element element, | |
327 EagerAnnotationHandler handler) { | |
328 for (Link<MetadataAnnotation> link = element.metadata; | |
329 !link.isEmpty; | |
330 link = link.tail) { | |
331 MetadataAnnotation annotation = link.head; | |
332 if (handler.apply(compiler, element, annotation)) { | |
333 // TODO(johnniwinther): Perform this check in | |
334 // [Compiler.onLibrariesLoaded]. | |
335 compiler.enqueuer.resolution.addDeferredAction(element, () { | |
336 annotation.ensureResolved(compiler); | |
337 handler.validate( | |
338 compiler, element, annotation, annotation.constant.value); | |
339 }); | |
340 return true; | |
341 } | |
342 } | |
343 return false; | |
344 } | |
345 } | |
346 | |
347 /// Annotation handler for pre-resolution detection of `@Native(...)` | |
348 /// annotations. | |
349 class NativeAnnotationHandler implements EagerAnnotationHandler { | |
350 const NativeAnnotationHandler(); | |
351 | |
352 String getNativeAnnotation(MetadataAnnotation annotation) { | |
353 if (annotation.beginToken != null && | |
354 annotation.beginToken.next.value == 'Native') { | |
355 // Skipping '@', 'Native', and '('. | |
356 Token argument = annotation.beginToken.next.next.next; | |
357 if (argument is StringToken) { | |
358 return argument.value; | |
359 } | |
360 } | |
361 return null; | |
362 } | |
363 | |
364 bool apply(Compiler compiler, | |
365 Element element, | |
366 MetadataAnnotation annotation) { | |
367 if (element.isClass) { | |
368 String native = getNativeAnnotation(annotation); | |
369 if (native != null) { | |
370 ClassElementX declaration = element.declaration; | |
371 declaration.setNative(native); | |
372 return true; | |
373 } | |
374 } | |
375 return false; | |
376 } | |
377 | |
378 void validate(Compiler compiler, | |
379 Element element, | |
380 MetadataAnnotation annotation, | |
381 ConstantValue constant) { | |
382 if (constant.computeType(compiler).element != | |
383 compiler.nativeAnnotationClass) { | |
384 compiler.internalError(annotation, 'Invalid @Native(...) annotation.'); | |
385 } | |
386 } | |
387 } | |
388 | |
389 /// Annotation handler for pre-resolution detection of `@patch` annotations. | |
390 class PatchAnnotationHandler implements EagerAnnotationHandler { | |
391 const PatchAnnotationHandler(); | |
392 | |
393 bool isPatchAnnotation(MetadataAnnotation annotation) { | |
394 return annotation.beginToken != null && | |
395 annotation.beginToken.next.value == 'patch'; | |
396 } | |
397 | |
398 bool apply(Compiler compiler, | |
399 Element element, | |
400 MetadataAnnotation annotation) { | |
401 return isPatchAnnotation(annotation); | |
402 } | |
403 | |
404 void validate(Compiler compiler, | |
405 Element element, | |
406 MetadataAnnotation annotation, | |
407 ConstantValue constant) { | |
408 if (constant != compiler.patchConstant) { | |
409 compiler.internalError(annotation, 'Invalid patch annotation.'); | |
410 } | |
411 } | |
412 } | |
413 | |
414 | |
415 void tryPatchGetter(DiagnosticListener listener, | |
416 Element origin, | |
417 FunctionElement patch) { | |
418 if (!origin.isAbstractField) { | |
419 listener.reportError( | |
420 origin, MessageKind.PATCH_NON_GETTER, {'name': origin.name}); | |
421 listener.reportInfo( | |
422 patch, | |
423 MessageKind.PATCH_POINT_TO_GETTER, {'getterName': patch.name}); | |
424 return; | |
425 } | |
426 AbstractFieldElement originField = origin; | |
427 if (originField.getter == null) { | |
428 listener.reportError( | |
429 origin, MessageKind.PATCH_NO_GETTER, {'getterName': patch.name}); | |
430 listener.reportInfo( | |
431 patch, | |
432 MessageKind.PATCH_POINT_TO_GETTER, {'getterName': patch.name}); | |
433 return; | |
434 } | |
435 patchFunction(listener, originField.getter, patch); | |
436 } | |
437 | |
438 void tryPatchSetter(DiagnosticListener listener, | |
439 Element origin, | |
440 FunctionElement patch) { | |
441 if (!origin.isAbstractField) { | |
442 listener.reportError( | |
443 origin, MessageKind.PATCH_NON_SETTER, {'name': origin.name}); | |
444 listener.reportInfo( | |
445 patch, | |
446 MessageKind.PATCH_POINT_TO_SETTER, {'setterName': patch.name}); | |
447 return; | |
448 } | |
449 AbstractFieldElement originField = origin; | |
450 if (originField.setter == null) { | |
451 listener.reportError( | |
452 origin, MessageKind.PATCH_NO_SETTER, {'setterName': patch.name}); | |
453 listener.reportInfo( | |
454 patch, | |
455 MessageKind.PATCH_POINT_TO_SETTER, {'setterName': patch.name}); | |
456 return; | |
457 } | |
458 patchFunction(listener, originField.setter, patch); | |
459 } | |
460 | |
461 void tryPatchConstructor(DiagnosticListener listener, | |
462 Element origin, | |
463 FunctionElement patch) { | |
464 if (!origin.isConstructor) { | |
465 listener.reportError( | |
466 origin, | |
467 MessageKind.PATCH_NON_CONSTRUCTOR, {'constructorName': patch.name}); | |
468 listener.reportInfo( | |
469 patch, | |
470 MessageKind.PATCH_POINT_TO_CONSTRUCTOR, | |
471 {'constructorName': patch.name}); | |
472 return; | |
473 } | |
474 patchFunction(listener, origin, patch); | |
475 } | |
476 | |
477 void tryPatchFunction(DiagnosticListener listener, | |
478 Element origin, | |
479 FunctionElement patch) { | |
480 if (!origin.isFunction) { | |
481 listener.reportError( | |
482 origin, | |
483 MessageKind.PATCH_NON_FUNCTION, {'functionName': patch.name}); | |
484 listener.reportInfo( | |
485 patch, | |
486 MessageKind.PATCH_POINT_TO_FUNCTION, {'functionName': patch.name}); | |
487 return; | |
488 } | |
489 patchFunction(listener, origin, patch); | |
490 } | |
491 | |
492 void patchFunction(DiagnosticListener listener, | |
493 FunctionElementX origin, | |
494 FunctionElementX patch) { | |
495 if (!origin.modifiers.isExternal) { | |
496 listener.reportError(origin, MessageKind.PATCH_NON_EXTERNAL); | |
497 listener.reportInfo( | |
498 patch, | |
499 MessageKind.PATCH_POINT_TO_FUNCTION, {'functionName': patch.name}); | |
500 return; | |
501 } | |
502 if (origin.isPatched) { | |
503 listener.internalError(origin, | |
504 "Trying to patch a function more than once."); | |
505 } | |
506 origin.applyPatch(patch); | |
507 } | |
508 | |
509 // TODO(johnniwinther): Add unittest when patch is (real) metadata. | |
510 bool isPatchElement(Compiler compiler, Element element) { | |
511 return EagerAnnotationHandler.checkAnnotation(compiler, element, | |
512 const PatchAnnotationHandler()); | |
513 } | |
OLD | NEW |