OLD | NEW |
---|---|
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * This library contains the infrastructure to parse and integrate patch files. | 6 * This library contains the infrastructure to parse and integrate patch files. |
7 * | 7 * |
8 * Three types of elements can be patched: [LibraryElement], [ClassElement], | 8 * Three types of elements can be patched: [LibraryElement], [ClassElement], |
9 * [FunctionElement]. Patches are introduced in patch libraries which are loaded | 9 * [FunctionElement]. Patches are introduced in patch libraries which are loaded |
10 * together with the corresponding origin library. Which libraries that are | 10 * together with the corresponding origin library. Which libraries that are |
(...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
111 * - Work on function parameters is performed on the declaration of the function | 111 * - Work on function parameters is performed on the declaration of the function |
112 * element. | 112 * element. |
113 */ | 113 */ |
114 | 114 |
115 library patchparser; | 115 library patchparser; |
116 | 116 |
117 import "dart:uri"; | 117 import "dart:uri"; |
118 import "tree/tree.dart" as tree; | 118 import "tree/tree.dart" as tree; |
119 import "dart2jslib.dart" as leg; // CompilerTask, Compiler. | 119 import "dart2jslib.dart" as leg; // CompilerTask, Compiler. |
120 import "apiimpl.dart"; | 120 import "apiimpl.dart"; |
121 import "../compiler.dart" as api; | |
121 import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners | 122 import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners |
122 import "elements/elements.dart"; | 123 import "elements/elements.dart"; |
123 import "elements/modelx.dart" show LibraryElementX, MetadataAnnotationX; | 124 import "elements/modelx.dart" show LibraryElementX, MetadataAnnotationX; |
124 import 'util/util.dart'; | 125 import 'util/util.dart'; |
125 | 126 |
126 class PatchParserTask extends leg.CompilerTask { | 127 class PatchParserTask extends leg.CompilerTask { |
127 PatchParserTask(leg.Compiler compiler): super(compiler); | 128 PatchParserTask(leg.Compiler compiler): super(compiler); |
128 final String name = "Patching Parser"; | 129 final String name = "Patching Parser"; |
129 | 130 |
130 /** | 131 /** |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
182 Token token = parser.parseTopLevelDeclaration(element.beginToken); | 183 Token token = parser.parseTopLevelDeclaration(element.beginToken); |
183 assert(identical(token, element.endToken.next)); | 184 assert(identical(token, element.endToken.next)); |
184 element.cachedNode = listener.popNode(); | 185 element.cachedNode = listener.popNode(); |
185 assert(listener.nodes.isEmpty); | 186 assert(listener.nodes.isEmpty); |
186 | 187 |
187 Link<Element> patches = element.localMembers; | 188 Link<Element> patches = element.localMembers; |
188 applyContainerPatch(element.origin, patches); | 189 applyContainerPatch(element.origin, patches); |
189 })); | 190 })); |
190 } | 191 } |
191 | 192 |
192 void applyContainerPatch(ScopeContainerElement original, | 193 void applyContainerPatch(ClassElement originClass, |
193 Link<Element> patches) { | 194 Link<Element> patches) { |
194 while (!patches.isEmpty) { | 195 for (Element patch in patches) { |
195 Element patchElement = patches.head; | 196 if (!_isPatchElement(patch)) continue; |
ngeoffray
2013/01/11 11:46:12
How can that be?
ahe
2013/01/21 10:53:35
This would be an injected member, right? I assume
| |
196 Element originalElement = original.localLookup(patchElement.name); | 197 |
197 if (patchElement.isAccessor() && originalElement != null) { | 198 Element origin = originClass.localLookup(patch.name); |
198 if (!identical(originalElement.kind, ElementKind.ABSTRACT_FIELD)) { | 199 _patchElement(compiler, origin, patch); |
199 compiler.internalError( | |
200 "Cannot patch non-getter/setter with getter/setter", | |
201 element: originalElement); | |
202 } | |
203 AbstractFieldElement originalField = originalElement; | |
204 if (patchElement.isGetter()) { | |
205 originalElement = originalField.getter; | |
206 } else { | |
207 originalElement = originalField.setter; | |
208 } | |
209 } | |
210 if (originalElement == null) { | |
211 if (isPatchElement(patchElement)) { | |
212 compiler.internalError("Cannot patch non-existing member '" | |
213 "${patchElement.name.slowToString()}'."); | |
214 } | |
215 } else { | |
216 patchMember(originalElement, patchElement); | |
217 } | |
218 patches = patches.tail; | |
219 } | 200 } |
220 } | 201 } |
221 | |
222 bool isPatchElement(Element element) { | |
223 // TODO(lrn): More checks needed if we introduce metadata for real. | |
224 // In that case, it must have the identifier "native" as metadata. | |
225 for (Link link = element.metadata; !link.isEmpty; link = link.tail) { | |
226 if (link.head is PatchMetadataAnnotation) return true; | |
227 } | |
228 return false; | |
229 } | |
230 | |
231 void patchMember(Element originalElement, Element patchElement) { | |
232 // The original library has an element with the same name as the patch | |
233 // library element. | |
234 // In this case, the patch library element must be a function marked as | |
235 // "patch" and it must have the same signature as the function it patches. | |
236 if (!isPatchElement(patchElement)) { | |
237 compiler.internalError("Cannot overwrite existing '" | |
238 "${originalElement.name.slowToString()}' with non-patch."); | |
239 } | |
240 if (originalElement is! FunctionElement) { | |
241 // TODO(lrn): Handle class declarations too. | |
242 compiler.internalError("Can only patch functions", element: originalElemen t); | |
243 } | |
244 FunctionElement original = originalElement; | |
245 if (!original.modifiers.isExternal()) { | |
246 compiler.internalError("Can only patch external functions.", element: orig inal); | |
247 } | |
248 if (patchElement is! FunctionElement || | |
249 !patchSignatureMatches(original, patchElement)) { | |
250 compiler.internalError("Can only patch functions with matching signatures" , | |
251 element: original); | |
252 } | |
253 applyFunctionPatch(original, patchElement); | |
254 } | |
255 | |
256 bool patchSignatureMatches(FunctionElement original, FunctionElement patch) { | |
257 // TODO(lrn): Check that patches actually match the signature of | |
258 // the function it's patching. | |
259 return true; | |
260 } | |
261 | |
262 void applyFunctionPatch(FunctionElement element, | |
263 FunctionElement patchElement) { | |
264 if (element.isPatched) { | |
265 compiler.internalError("Trying to patch a function more than once.", | |
266 element: element); | |
267 } | |
268 if (element.cachedNode != null) { | |
269 compiler.internalError("Trying to patch an already compiled function.", | |
270 element: element); | |
271 } | |
272 // Don't just assign the patch field. This also updates the cachedNode. | |
273 element.setPatch(patchElement); | |
274 patchElement.origin = element; | |
275 } | |
276 } | 202 } |
277 | 203 |
278 /** | 204 /** |
279 * Extension of the [Listener] interface to handle the extra "patch" pseudo- | 205 * Extension of the [Listener] interface to handle the extra "patch" pseudo- |
280 * keyword in patch files. | 206 * keyword in patch files. |
281 * Patch files shouldn't have a type named "patch". | 207 * Patch files shouldn't have a type named "patch". |
282 */ | 208 */ |
283 abstract class PatchListener extends Listener { | 209 abstract class PatchListener extends Listener { |
284 void beginPatch(Token patch); | 210 void beginPatch(Token patch); |
285 void endPatch(Token patch); | 211 void endPatch(Token patch); |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
394 * Allow script tags (import only, the parser rejects the rest for now) in | 320 * Allow script tags (import only, the parser rejects the rest for now) in |
395 * patch files. The import tags will be added to the library. | 321 * patch files. The import tags will be added to the library. |
396 */ | 322 */ |
397 bool allowLibraryTags() => true; | 323 bool allowLibraryTags() => true; |
398 | 324 |
399 void addLibraryTag(tree.LibraryTag tag) { | 325 void addLibraryTag(tree.LibraryTag tag) { |
400 super.addLibraryTag(tag); | 326 super.addLibraryTag(tag); |
401 imports.addLast(tag); | 327 imports.addLast(tag); |
402 } | 328 } |
403 | 329 |
404 void pushElement(Element element) { | 330 void pushElement(Element patch) { |
405 if (isMemberPatch || (isClassPatch && element is ClassElement)) { | 331 if (isMemberPatch || (isClassPatch && patch is ClassElement)) { |
406 // Apply patch. | 332 // Apply patch. |
407 element.addMetadata(popMetadata()); | 333 patch.addMetadata(popMetadata()); |
408 LibraryElement originLibrary = compilationUnitElement.getLibrary(); | 334 LibraryElement originLibrary = compilationUnitElement.getLibrary(); |
409 assert(originLibrary.isPatched); | 335 assert(originLibrary.isPatched); |
410 Element existing = originLibrary.localLookup(element.name); | 336 Element origin = originLibrary.localLookup(patch.name); |
411 if (isMemberPatch) { | 337 _patchElement(listener, origin, patch); |
412 if (element is! FunctionElement) { | |
413 listener.internalErrorOnElement(element, | |
414 "Member patch is not a function."); | |
415 } | |
416 FunctionElement functionElement = element; | |
417 if (identical(existing.kind, ElementKind.ABSTRACT_FIELD)) { | |
418 if (!element.isAccessor()) { | |
419 listener.internalErrorOnElement( | |
420 functionElement, "Patching non-accessor with accessor"); | |
421 } | |
422 AbstractFieldElement field = existing; | |
423 if (functionElement.isGetter()) { | |
424 existing = field.getter; | |
425 } else { | |
426 existing = field.setter; | |
427 } | |
428 } | |
429 if (existing is! FunctionElement) { | |
430 listener.internalErrorOnElement(functionElement, | |
431 "No corresponding method for patch."); | |
432 } | |
433 FunctionElement existingFunction = existing; | |
434 if (existingFunction.isPatched) { | |
435 listener.internalErrorOnElement( | |
436 functionElement, "Patching the same function more than once."); | |
437 } | |
438 existingFunction.patch = functionElement; | |
439 functionElement.origin = existingFunction; | |
440 } else { | |
441 assert(leg.invariant(element, element is ClassElement)); | |
442 ClassElement classElement = element; | |
443 if (existing is! ClassElement) { | |
444 listener.internalErrorOnElement( | |
445 classElement, "Patching a non-class with a class patch."); | |
446 } | |
447 ClassElement existingClass = existing; | |
448 if (existingClass.isPatched) { | |
449 listener.internalErrorOnElement( | |
450 classElement, "Patching the same class more than once."); | |
451 } | |
452 existingClass.patch = classElement; | |
453 classElement.origin = existingClass; | |
454 } | |
455 } | 338 } |
456 super.pushElement(element); | 339 super.pushElement(patch); |
457 } | 340 } |
458 } | 341 } |
459 | 342 |
460 /** | 343 /** |
461 * Extension of [MemberListener] for parsing patch class bodies. | 344 * Extension of [MemberListener] for parsing patch class bodies. |
462 */ | 345 */ |
463 class PatchMemberListener extends MemberListener implements PatchListener { | 346 class PatchMemberListener extends MemberListener implements PatchListener { |
464 bool isMemberPatch = false; | 347 bool isMemberPatch = false; |
465 bool isClassPatch = false; | 348 bool isClassPatch = false; |
466 PatchMemberListener(leg.DiagnosticListener listener, | 349 PatchMemberListener(leg.DiagnosticListener listener, |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
499 } | 382 } |
500 | 383 |
501 // TODO(ahe): Get rid of this class. | 384 // TODO(ahe): Get rid of this class. |
502 class PatchMetadataAnnotation extends MetadataAnnotationX { | 385 class PatchMetadataAnnotation extends MetadataAnnotationX { |
503 final leg.Constant value = null; | 386 final leg.Constant value = null; |
504 | 387 |
505 PatchMetadataAnnotation() : super(STATE_DONE); | 388 PatchMetadataAnnotation() : super(STATE_DONE); |
506 | 389 |
507 Token get beginToken => null; | 390 Token get beginToken => null; |
508 } | 391 } |
392 | |
393 void _patchElement(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by all methods in patch_test.
| |
394 Element origin, | |
395 Element patch) { | |
396 if (origin == null) { | |
397 listener.reportMessage( | |
398 listener.spanFromSpannable(patch), | |
399 leg.MessageKind.PATCH_NON_EXISTING.error([patch.name]), | |
400 api.Diagnostic.ERROR); | |
401 return; | |
402 } | |
ngeoffray
2013/01/11 11:46:12
indentation
Johnni Winther
2013/01/29 09:30:56
Done.
| |
403 if (!(patch.isClass() || | |
ahe
2013/01/21 10:53:35
Let me suggest a shorter alternative version of th
Johnni Winther
2013/01/29 09:30:56
Almost done. Test for patchable origin must remain
| |
404 patch.isConstructor() || | |
405 patch.isFunction() || | |
406 patch.isAccessor())) { | |
407 listener.reportMessage( | |
408 listener.spanFromSpannable(patch), | |
409 leg.MessageKind.PATCH_NONPATCHABLE.error(), | |
410 api.Diagnostic.ERROR); | |
411 return; | |
412 } | |
413 if (!(origin.isClass() || | |
414 origin.isConstructor() || | |
415 origin.isFunction() || | |
416 origin.isAbstractField())) { | |
417 listener.reportMessage( | |
418 listener.spanFromSpannable(origin), | |
419 leg.MessageKind.PATCH_NONPATCHABLE.error(), | |
420 api.Diagnostic.ERROR); | |
421 return; | |
422 } | |
423 if (patch.isClass()) { | |
424 _tryPatchClass(listener, origin, patch); | |
425 } else if (patch.isGetter()) { | |
426 _tryPatchGetter(listener, origin, patch); | |
427 } else if (patch.isSetter()) { | |
428 _tryPatchSetter(listener, origin, patch); | |
429 } else if (patch.isConstructor()) { | |
430 _tryPatchConstructor(listener, origin, patch); | |
431 } else { | |
432 assert(patch.isFunction()); | |
433 _tryPatchFunction(listener, origin, patch); | |
434 } | |
435 } | |
436 | |
437 void _tryPatchClass(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by several methods in patch_test.
| |
438 Element origin, | |
439 ClassElement patch) { | |
440 if (!origin.isClass()) { | |
441 listener.reportMessage( | |
442 listener.spanFromSpannable(origin), | |
443 leg.MessageKind.PATCH_NON_CLASS.error([patch.name]), | |
444 api.Diagnostic.ERROR); | |
445 listener.reportMessage( | |
446 listener.spanFromSpannable(patch), | |
447 leg.MessageKind.PATCH_POINT_TO_CLASS.error([patch.name]), | |
448 api.Diagnostic.INFO); | |
449 return; | |
450 } | |
451 _patchClass(listener, origin, patch); | |
452 } | |
453 | |
454 void _patchClass(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by several methods in patch_test.
| |
455 ClassElement origin, | |
456 ClassElement patch) { | |
457 if (origin.isPatched) { | |
458 listener.internalErrorOnElement( | |
459 origin, "Patching the same class more than once."); | |
460 } | |
461 origin.patch = patch; | |
ngeoffray
2013/01/11 11:46:12
Use setPatch to hide implementation details?
Johnni Winther
2013/01/29 09:30:56
Added a TODO.
| |
462 patch.origin = origin; | |
463 } | |
464 | |
465 void _tryPatchGetter(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by some methods in patch_test.
| |
466 Element origin, | |
467 FunctionElement patch) { | |
468 if (!origin.isAbstractField()) { | |
ahe
2013/01/21 10:53:35
I'd share this check between tryPatchGetter and tr
Johnni Winther
2013/01/29 09:30:56
The granularity has already been made and it takes
| |
469 listener.reportMessage( | |
470 listener.spanFromSpannable(origin), | |
471 leg.MessageKind.PATCH_NON_GETTER.error([patch.name]), | |
472 api.Diagnostic.ERROR); | |
473 listener.reportMessage( | |
474 listener.spanFromSpannable(patch), | |
475 leg.MessageKind.PATCH_POINT_TO_GETTER.error([patch.name]), | |
476 api.Diagnostic.INFO); | |
477 return; | |
478 } | |
479 AbstractFieldElement originField = origin; | |
480 if (originField.getter == null) { | |
481 listener.reportMessage( | |
482 listener.spanFromSpannable(origin), | |
483 leg.MessageKind.PATCH_NO_GETTER.error([patch.name]), | |
484 api.Diagnostic.ERROR); | |
485 listener.reportMessage( | |
486 listener.spanFromSpannable(patch), | |
487 leg.MessageKind.PATCH_POINT_TO_GETTER.error([patch.name]), | |
488 api.Diagnostic.INFO); | |
489 return; | |
490 } | |
491 _patchFunction(listener, originField.getter, patch); | |
492 } | |
493 | |
494 void _tryPatchSetter(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by a method in patch_test.
| |
495 Element origin, | |
496 FunctionElement patch) { | |
497 if (!origin.isAbstractField()) { | |
498 listener.reportMessage( | |
499 listener.spanFromSpannable(origin), | |
500 leg.MessageKind.PATCH_NON_SETTER.error([patch.name]), | |
501 api.Diagnostic.ERROR); | |
502 listener.reportMessage( | |
503 listener.spanFromSpannable(patch), | |
504 leg.MessageKind.PATCH_POINT_TO_SETTER.error([patch.name]), | |
505 api.Diagnostic.INFO); | |
506 return; | |
507 } | |
508 AbstractFieldElement originField = origin; | |
509 if (originField.setter == null) { | |
510 listener.reportMessage( | |
511 listener.spanFromSpannable(origin), | |
512 leg.MessageKind.PATCH_NO_SETTER.error([patch.name]), | |
513 api.Diagnostic.ERROR); | |
514 listener.reportMessage( | |
515 listener.spanFromSpannable(patch), | |
516 leg.MessageKind.PATCH_POINT_TO_SETTER.error([patch.name]), | |
517 api.Diagnostic.INFO); | |
518 return; | |
519 } | |
520 _patchFunction(listener, originField.setter, patch); | |
521 } | |
522 | |
523 void _tryPatchConstructor(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by a method in patch_test.
| |
524 Element origin, | |
525 FunctionElement patch) { | |
526 if (!origin.isConstructor()) { | |
527 listener.reportMessage( | |
528 listener.spanFromSpannable(origin), | |
529 leg.MessageKind.PATCH_NON_CONSTRUCTOR.error([patch.name]), | |
530 api.Diagnostic.ERROR); | |
531 listener.reportMessage( | |
532 listener.spanFromSpannable(patch), | |
533 leg.MessageKind.PATCH_POINT_TO_CONSTRUCTOR.error([patch.name]), | |
534 api.Diagnostic.INFO); | |
535 return; | |
536 } | |
537 _patchFunction(listener, origin, patch); | |
538 } | |
539 | |
540 void _tryPatchFunction(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by some methods in patch_test.
| |
541 Element origin, | |
542 FunctionElement patch) { | |
543 if (!origin.isFunction()) { | |
544 listener.reportMessage( | |
545 listener.spanFromSpannable(origin), | |
546 leg.MessageKind.PATCH_NON_FUNCTION.error([patch.name]), | |
547 api.Diagnostic.ERROR); | |
548 listener.reportMessage( | |
549 listener.spanFromSpannable(patch), | |
550 leg.MessageKind.PATCH_POINT_TO_FUNCTION.error([patch.name]), | |
551 api.Diagnostic.INFO); | |
552 return; | |
553 } | |
554 _patchFunction(listener, origin, patch); | |
555 } | |
556 | |
557 void _patchFunction(leg.DiagnosticListener listener, | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
It's tested by some methods in patch_test.
| |
558 FunctionElement origin, | |
559 FunctionElement patch) { | |
560 if (!origin.modifiers.isExternal()) { | |
561 listener.reportMessage( | |
562 listener.spanFromSpannable(origin), | |
563 leg.MessageKind.PATCH_NON_EXTERNAL.error([patch.name]), | |
564 api.Diagnostic.ERROR); | |
565 listener.reportMessage( | |
566 listener.spanFromSpannable(patch), | |
567 leg.MessageKind.PATCH_POINT_TO_FUNCTION.error([patch.name]), | |
568 api.Diagnostic.INFO); | |
569 return; | |
570 } | |
571 if (origin.isPatched) { | |
572 listener.internalErrorOnElement(origin, | |
573 "Trying to patch a function more than once."); | |
574 } | |
575 if (origin.cachedNode != null) { | |
576 listener.internalErrorOnElement(origin, | |
577 "Trying to patch an already compiled function."); | |
578 } | |
579 // Don't just assign the patch field. This also updates the cachedNode. | |
580 origin.setPatch(patch); | |
581 patch.origin = origin; | |
ngeoffray
2013/01/11 11:46:12
Add a setOrigin?
Johnni Winther
2013/01/29 09:30:56
Added a TODO.
| |
582 } | |
583 | |
584 bool _isPatchElement(Element element) { | |
ahe
2013/01/21 10:53:35
I'd like to see a unit test of this method.
Johnni Winther
2013/01/29 09:30:56
Added a TODO.
| |
585 // TODO(lrn): More checks needed if we introduce metadata for real. | |
586 // In that case, it must have the identifier "native" as metadata. | |
587 for (Link link = element.metadata; !link.isEmpty; link = link.tail) { | |
588 if (link.head is PatchMetadataAnnotation) return true; | |
589 } | |
590 return false; | |
591 } | |
OLD | NEW |