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 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
185 Token token = parser.parseTopLevelDeclaration(element.beginToken); | 186 Token token = parser.parseTopLevelDeclaration(element.beginToken); |
186 assert(identical(token, element.endToken.next)); | 187 assert(identical(token, element.endToken.next)); |
187 element.cachedNode = listener.popNode(); | 188 element.cachedNode = listener.popNode(); |
188 assert(listener.nodes.isEmpty); | 189 assert(listener.nodes.isEmpty); |
189 | 190 |
190 Link<Element> patches = element.localMembers; | 191 Link<Element> patches = element.localMembers; |
191 applyContainerPatch(element.origin, patches); | 192 applyContainerPatch(element.origin, patches); |
192 })); | 193 })); |
193 } | 194 } |
194 | 195 |
195 void applyContainerPatch(ScopeContainerElement original, | 196 void applyContainerPatch(ClassElement originClass, |
196 Link<Element> patches) { | 197 Link<Element> patches) { |
197 while (!patches.isEmpty) { | 198 for (Element patch in patches) { |
198 Element patchElement = patches.head; | 199 if (!isPatchElement(patch)) continue; |
199 Element originalElement = original.localLookup(patchElement.name); | 200 |
200 if (patchElement.isAccessor() && originalElement != null) { | 201 Element origin = originClass.localLookup(patch.name); |
201 if (!identical(originalElement.kind, ElementKind.ABSTRACT_FIELD)) { | 202 patchElement(compiler, origin, patch); |
202 compiler.internalError( | |
203 "Cannot patch non-getter/setter with getter/setter", | |
204 element: originalElement); | |
205 } | |
206 AbstractFieldElement originalField = originalElement; | |
207 if (patchElement.isGetter()) { | |
208 originalElement = originalField.getter; | |
209 } else { | |
210 originalElement = originalField.setter; | |
211 } | |
212 } | |
213 if (originalElement == null) { | |
214 if (isPatchElement(patchElement)) { | |
215 compiler.internalError("Cannot patch non-existing member '" | |
216 "${patchElement.name.slowToString()}'."); | |
217 } | |
218 } else { | |
219 patchMember(originalElement, patchElement); | |
220 } | |
221 patches = patches.tail; | |
222 } | 203 } |
223 } | 204 } |
224 | |
225 bool isPatchElement(Element element) { | |
226 // TODO(lrn): More checks needed if we introduce metadata for real. | |
227 // In that case, it must have the identifier "native" as metadata. | |
228 for (Link link = element.metadata; !link.isEmpty; link = link.tail) { | |
229 if (link.head is PatchMetadataAnnotation) return true; | |
230 } | |
231 return false; | |
232 } | |
233 | |
234 void patchMember(Element originalElement, Element patchElement) { | |
235 // The original library has an element with the same name as the patch | |
236 // library element. | |
237 // In this case, the patch library element must be a function marked as | |
238 // "patch" and it must have the same signature as the function it patches. | |
239 if (!isPatchElement(patchElement)) { | |
240 compiler.internalError("Cannot overwrite existing '" | |
241 "${originalElement.name.slowToString()}' with non-patch."); | |
242 } | |
243 if (originalElement is! FunctionElement) { | |
244 // TODO(lrn): Handle class declarations too. | |
245 compiler.internalError("Can only patch functions", element: originalElemen
t); | |
246 } | |
247 FunctionElement original = originalElement; | |
248 if (!original.modifiers.isExternal()) { | |
249 compiler.internalError("Can only patch external functions.", element: orig
inal); | |
250 } | |
251 if (patchElement is! FunctionElement || | |
252 !patchSignatureMatches(original, patchElement)) { | |
253 compiler.internalError("Can only patch functions with matching signatures"
, | |
254 element: original); | |
255 } | |
256 applyFunctionPatch(original, patchElement); | |
257 } | |
258 | |
259 bool patchSignatureMatches(FunctionElement original, FunctionElement patch) { | |
260 // TODO(lrn): Check that patches actually match the signature of | |
261 // the function it's patching. | |
262 return true; | |
263 } | |
264 | |
265 void applyFunctionPatch(FunctionElement element, | |
266 FunctionElement patchElement) { | |
267 if (element.isPatched) { | |
268 compiler.internalError("Trying to patch a function more than once.", | |
269 element: element); | |
270 } | |
271 if (element.cachedNode != null) { | |
272 compiler.internalError("Trying to patch an already compiled function.", | |
273 element: element); | |
274 } | |
275 // Don't just assign the patch field. This also updates the cachedNode. | |
276 element.setPatch(patchElement); | |
277 patchElement.origin = element; | |
278 } | |
279 } | 205 } |
280 | 206 |
281 /** | 207 /** |
282 * Extension of the [Listener] interface to handle the extra "patch" pseudo- | 208 * Extension of the [Listener] interface to handle the extra "patch" pseudo- |
283 * keyword in patch files. | 209 * keyword in patch files. |
284 * Patch files shouldn't have a type named "patch". | 210 * Patch files shouldn't have a type named "patch". |
285 */ | 211 */ |
286 abstract class PatchListener extends Listener { | 212 abstract class PatchListener extends Listener { |
287 void beginPatch(Token patch); | 213 void beginPatch(Token patch); |
288 void endPatch(Token patch); | 214 void endPatch(Token patch); |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
397 * Allow script tags (import only, the parser rejects the rest for now) in | 323 * Allow script tags (import only, the parser rejects the rest for now) in |
398 * patch files. The import tags will be added to the library. | 324 * patch files. The import tags will be added to the library. |
399 */ | 325 */ |
400 bool allowLibraryTags() => true; | 326 bool allowLibraryTags() => true; |
401 | 327 |
402 void addLibraryTag(tree.LibraryTag tag) { | 328 void addLibraryTag(tree.LibraryTag tag) { |
403 super.addLibraryTag(tag); | 329 super.addLibraryTag(tag); |
404 imports.addLast(tag); | 330 imports.addLast(tag); |
405 } | 331 } |
406 | 332 |
407 void pushElement(Element element) { | 333 void pushElement(Element patch) { |
408 if (isMemberPatch || (isClassPatch && element is ClassElement)) { | 334 if (isMemberPatch || (isClassPatch && patch is ClassElement)) { |
409 // Apply patch. | 335 // Apply patch. |
410 element.addMetadata(popMetadataHack()); | 336 patch.addMetadata(popMetadataHack()); |
411 LibraryElement originLibrary = compilationUnitElement.getLibrary(); | 337 LibraryElement originLibrary = compilationUnitElement.getLibrary(); |
412 assert(originLibrary.isPatched); | 338 assert(originLibrary.isPatched); |
413 Element existing = originLibrary.localLookup(element.name); | 339 Element origin = originLibrary.localLookup(patch.name); |
414 if (isMemberPatch) { | 340 patchElement(listener, origin, patch); |
415 if (element is! FunctionElement) { | |
416 listener.internalErrorOnElement(element, | |
417 "Member patch is not a function."); | |
418 } | |
419 FunctionElement functionElement = element; | |
420 if (identical(existing.kind, ElementKind.ABSTRACT_FIELD)) { | |
421 if (!element.isAccessor()) { | |
422 listener.internalErrorOnElement( | |
423 functionElement, "Patching non-accessor with accessor"); | |
424 } | |
425 AbstractFieldElement field = existing; | |
426 if (functionElement.isGetter()) { | |
427 existing = field.getter; | |
428 } else { | |
429 existing = field.setter; | |
430 } | |
431 } | |
432 if (existing is! FunctionElement) { | |
433 listener.internalErrorOnElement(functionElement, | |
434 "No corresponding method for patch."); | |
435 } | |
436 FunctionElement existingFunction = existing; | |
437 if (existingFunction.isPatched) { | |
438 listener.internalErrorOnElement( | |
439 functionElement, "Patching the same function more than once."); | |
440 } | |
441 existingFunction.patch = functionElement; | |
442 functionElement.origin = existingFunction; | |
443 } else { | |
444 assert(leg.invariant(element, element is ClassElement)); | |
445 ClassElement classElement = element; | |
446 if (existing is! ClassElement) { | |
447 listener.internalErrorOnElement( | |
448 classElement, "Patching a non-class with a class patch."); | |
449 } | |
450 ClassElement existingClass = existing; | |
451 if (existingClass.isPatched) { | |
452 listener.internalErrorOnElement( | |
453 classElement, "Patching the same class more than once."); | |
454 } | |
455 existingClass.patch = classElement; | |
456 classElement.origin = existingClass; | |
457 } | |
458 } | 341 } |
459 super.pushElement(element); | 342 super.pushElement(patch); |
460 } | 343 } |
461 } | 344 } |
462 | 345 |
463 /** | 346 /** |
464 * Extension of [MemberListener] for parsing patch class bodies. | 347 * Extension of [MemberListener] for parsing patch class bodies. |
465 */ | 348 */ |
466 class PatchMemberListener extends MemberListener implements PatchListener { | 349 class PatchMemberListener extends MemberListener implements PatchListener { |
467 bool isMemberPatch = false; | 350 bool isMemberPatch = false; |
468 bool isClassPatch = false; | 351 bool isClassPatch = false; |
469 PatchMemberListener(leg.DiagnosticListener listener, | 352 PatchMemberListener(leg.DiagnosticListener listener, |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
503 | 386 |
504 // TODO(ahe): Get rid of this class. | 387 // TODO(ahe): Get rid of this class. |
505 class PatchMetadataAnnotation extends MetadataAnnotationX { | 388 class PatchMetadataAnnotation extends MetadataAnnotationX { |
506 final leg.Constant value = null; | 389 final leg.Constant value = null; |
507 | 390 |
508 PatchMetadataAnnotation() : super(STATE_DONE); | 391 PatchMetadataAnnotation() : super(STATE_DONE); |
509 | 392 |
510 Token get beginToken => null; | 393 Token get beginToken => null; |
511 Token get endToken => null; | 394 Token get endToken => null; |
512 } | 395 } |
| 396 |
| 397 void patchElement(leg.DiagnosticListener listener, |
| 398 Element origin, |
| 399 Element patch) { |
| 400 if (origin == null) { |
| 401 listener.reportMessage( |
| 402 listener.spanFromSpannable(patch), |
| 403 leg.MessageKind.PATCH_NON_EXISTING.error({'name': patch.name}), |
| 404 api.Diagnostic.ERROR); |
| 405 return; |
| 406 } |
| 407 if (!(origin.isClass() || |
| 408 origin.isConstructor() || |
| 409 origin.isFunction() || |
| 410 origin.isAbstractField())) { |
| 411 listener.reportMessage( |
| 412 listener.spanFromSpannable(origin), |
| 413 leg.MessageKind.PATCH_NONPATCHABLE.error(), |
| 414 api.Diagnostic.ERROR); |
| 415 return; |
| 416 } |
| 417 if (patch.isClass()) { |
| 418 tryPatchClass(listener, origin, patch); |
| 419 } else if (patch.isGetter()) { |
| 420 tryPatchGetter(listener, origin, patch); |
| 421 } else if (patch.isSetter()) { |
| 422 tryPatchSetter(listener, origin, patch); |
| 423 } else if (patch.isConstructor()) { |
| 424 tryPatchConstructor(listener, origin, patch); |
| 425 } else if(patch.isFunction()) { |
| 426 tryPatchFunction(listener, origin, patch); |
| 427 } else { |
| 428 listener.reportMessage( |
| 429 listener.spanFromSpannable(patch), |
| 430 leg.MessageKind.PATCH_NONPATCHABLE.error(), |
| 431 api.Diagnostic.ERROR); |
| 432 } |
| 433 } |
| 434 |
| 435 void tryPatchClass(leg.DiagnosticListener listener, |
| 436 Element origin, |
| 437 ClassElement patch) { |
| 438 if (!origin.isClass()) { |
| 439 listener.reportMessage( |
| 440 listener.spanFromSpannable(origin), |
| 441 leg.MessageKind.PATCH_NON_CLASS.error({'className': patch.name}), |
| 442 api.Diagnostic.ERROR); |
| 443 listener.reportMessage( |
| 444 listener.spanFromSpannable(patch), |
| 445 leg.MessageKind.PATCH_POINT_TO_CLASS.error({'className': patch.name}), |
| 446 api.Diagnostic.INFO); |
| 447 return; |
| 448 } |
| 449 patchClass(listener, origin, patch); |
| 450 } |
| 451 |
| 452 void patchClass(leg.DiagnosticListener listener, |
| 453 ClassElement origin, |
| 454 ClassElement patch) { |
| 455 if (origin.isPatched) { |
| 456 listener.internalErrorOnElement( |
| 457 origin, "Patching the same class more than once."); |
| 458 } |
| 459 // TODO(johnniwinther): Change to functions on the ElementX class. |
| 460 origin.patch = patch; |
| 461 patch.origin = origin; |
| 462 } |
| 463 |
| 464 void tryPatchGetter(leg.DiagnosticListener listener, |
| 465 Element origin, |
| 466 FunctionElement patch) { |
| 467 if (!origin.isAbstractField()) { |
| 468 listener.reportMessage( |
| 469 listener.spanFromSpannable(origin), |
| 470 leg.MessageKind.PATCH_NON_GETTER.error({'name': origin.name}), |
| 471 api.Diagnostic.ERROR); |
| 472 listener.reportMessage( |
| 473 listener.spanFromSpannable(patch), |
| 474 leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}), |
| 475 api.Diagnostic.INFO); |
| 476 return; |
| 477 } |
| 478 AbstractFieldElement originField = origin; |
| 479 if (originField.getter == null) { |
| 480 listener.reportMessage( |
| 481 listener.spanFromSpannable(origin), |
| 482 leg.MessageKind.PATCH_NO_GETTER.error({'getterName': patch.name}), |
| 483 api.Diagnostic.ERROR); |
| 484 listener.reportMessage( |
| 485 listener.spanFromSpannable(patch), |
| 486 leg.MessageKind.PATCH_POINT_TO_GETTER.error({'getterName': patch.name}), |
| 487 api.Diagnostic.INFO); |
| 488 return; |
| 489 } |
| 490 patchFunction(listener, originField.getter, patch); |
| 491 } |
| 492 |
| 493 void tryPatchSetter(leg.DiagnosticListener listener, |
| 494 Element origin, |
| 495 FunctionElement patch) { |
| 496 if (!origin.isAbstractField()) { |
| 497 listener.reportMessage( |
| 498 listener.spanFromSpannable(origin), |
| 499 leg.MessageKind.PATCH_NON_SETTER.error({'name': origin.name}), |
| 500 api.Diagnostic.ERROR); |
| 501 listener.reportMessage( |
| 502 listener.spanFromSpannable(patch), |
| 503 leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}), |
| 504 api.Diagnostic.INFO); |
| 505 return; |
| 506 } |
| 507 AbstractFieldElement originField = origin; |
| 508 if (originField.setter == null) { |
| 509 listener.reportMessage( |
| 510 listener.spanFromSpannable(origin), |
| 511 leg.MessageKind.PATCH_NO_SETTER.error({'setterName': patch.name}), |
| 512 api.Diagnostic.ERROR); |
| 513 listener.reportMessage( |
| 514 listener.spanFromSpannable(patch), |
| 515 leg.MessageKind.PATCH_POINT_TO_SETTER.error({'setterName': patch.name}), |
| 516 api.Diagnostic.INFO); |
| 517 return; |
| 518 } |
| 519 patchFunction(listener, originField.setter, patch); |
| 520 } |
| 521 |
| 522 void tryPatchConstructor(leg.DiagnosticListener listener, |
| 523 Element origin, |
| 524 FunctionElement patch) { |
| 525 if (!origin.isConstructor()) { |
| 526 listener.reportMessage( |
| 527 listener.spanFromSpannable(origin), |
| 528 leg.MessageKind.PATCH_NON_CONSTRUCTOR.error( |
| 529 {'constructorName': patch.name}), |
| 530 api.Diagnostic.ERROR); |
| 531 listener.reportMessage( |
| 532 listener.spanFromSpannable(patch), |
| 533 leg.MessageKind.PATCH_POINT_TO_CONSTRUCTOR.error( |
| 534 {'constructorName': patch.name}), |
| 535 api.Diagnostic.INFO); |
| 536 return; |
| 537 } |
| 538 patchFunction(listener, origin, patch); |
| 539 } |
| 540 |
| 541 void tryPatchFunction(leg.DiagnosticListener listener, |
| 542 Element origin, |
| 543 FunctionElement patch) { |
| 544 if (!origin.isFunction()) { |
| 545 listener.reportMessage( |
| 546 listener.spanFromSpannable(origin), |
| 547 leg.MessageKind.PATCH_NON_FUNCTION.error({'functionName': patch.name}), |
| 548 api.Diagnostic.ERROR); |
| 549 listener.reportMessage( |
| 550 listener.spanFromSpannable(patch), |
| 551 leg.MessageKind.PATCH_POINT_TO_FUNCTION.error( |
| 552 {'functionName': patch.name}), |
| 553 api.Diagnostic.INFO); |
| 554 return; |
| 555 } |
| 556 patchFunction(listener, origin, patch); |
| 557 } |
| 558 |
| 559 void patchFunction(leg.DiagnosticListener listener, |
| 560 FunctionElement origin, |
| 561 FunctionElement patch) { |
| 562 if (!origin.modifiers.isExternal()) { |
| 563 listener.reportMessage( |
| 564 listener.spanFromSpannable(origin), |
| 565 leg.MessageKind.PATCH_NON_EXTERNAL.error(), |
| 566 api.Diagnostic.ERROR); |
| 567 listener.reportMessage( |
| 568 listener.spanFromSpannable(patch), |
| 569 leg.MessageKind.PATCH_POINT_TO_FUNCTION.error( |
| 570 {'functionName': patch.name}), |
| 571 api.Diagnostic.INFO); |
| 572 return; |
| 573 } |
| 574 if (origin.isPatched) { |
| 575 listener.internalErrorOnElement(origin, |
| 576 "Trying to patch a function more than once."); |
| 577 } |
| 578 if (origin.cachedNode != null) { |
| 579 listener.internalErrorOnElement(origin, |
| 580 "Trying to patch an already compiled function."); |
| 581 } |
| 582 // Don't just assign the patch field. This also updates the cachedNode. |
| 583 // TODO(johnniwinther): Change to functions on the ElementX class. |
| 584 origin.setPatch(patch); |
| 585 patch.origin = origin; |
| 586 } |
| 587 |
| 588 // TODO(johnniwinther): Add unittest when patch is (real) metadata. |
| 589 bool isPatchElement(Element element) { |
| 590 // TODO(lrn): More checks needed if we introduce metadata for real. |
| 591 // In that case, it must have the identifier "native" as metadata. |
| 592 for (Link link = element.metadata; !link.isEmpty; link = link.tail) { |
| 593 if (link.head is PatchMetadataAnnotation) return true; |
| 594 } |
| 595 return false; |
| 596 } |
OLD | NEW |