Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(258)

Side by Side Diff: pkg/fletchc/lib/incremental/reuser.dart

Issue 1659163007: Rename fletch -> dartino (Closed) Base URL: https://github.com/dartino/sdk.git@master
Patch Set: address comments Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2014, 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 library fletchc_incremental.reuser;
6
7 import 'dart:async' show
8 Future;
9
10 import 'package:compiler/compiler.dart' as api;
11
12 import 'package:compiler/src/compiler.dart' show
13 Compiler;
14
15 import 'package:compiler/src/diagnostics/messages.dart' show
16 MessageKind;
17
18 import 'package:compiler/src/script.dart' show
19 Script;
20
21 import 'package:compiler/src/elements/elements.dart' show
22 ClassElement,
23 CompilationUnitElement,
24 Element,
25 LibraryElement,
26 STATE_NOT_STARTED,
27 ScopeContainerElement,
28 TypeDeclarationElement;
29
30 import 'package:compiler/src/tokens/token_constants.dart' show
31 EOF_TOKEN;
32
33 import 'package:compiler/src/tokens/token.dart' show
34 Token;
35
36 import 'package:compiler/src/parser/partial_elements.dart' show
37 PartialClassElement,
38 PartialElement,
39 PartialFieldList,
40 PartialFunctionElement;
41
42 import 'package:compiler/src/scanner/scanner.dart' show
43 Scanner;
44
45 import 'package:compiler/src/parser/parser.dart' show
46 Parser;
47
48 import 'package:compiler/src/parser/listener.dart' show
49 Listener;
50
51 import 'package:compiler/src/parser/node_listener.dart' show
52 NodeListener;
53
54 import 'package:compiler/src/io/source_file.dart' show
55 CachingUtf8BytesSourceFile,
56 SourceFile,
57 StringSourceFile;
58
59 import 'package:compiler/src/tree/tree.dart' show
60 ClassNode,
61 FunctionExpression,
62 LibraryTag,
63 NodeList,
64 unparse;
65
66 import 'package:compiler/src/util/util.dart' show
67 Link;
68
69 import 'package:compiler/src/elements/modelx.dart' show
70 ClassElementX,
71 CompilationUnitElementX,
72 DeclarationSite,
73 ElementX,
74 FieldElementX,
75 LibraryElementX;
76
77 import 'package:compiler/src/constants/values.dart' show
78 ConstantValue;
79
80 import 'package:compiler/src/library_loader.dart' show
81 TagState;
82
83 import '../incremental_backend.dart' show
84 IncrementalBackend;
85
86 import 'diff.dart' show
87 Difference,
88 computeDifference;
89
90 import 'fletchc_incremental.dart' show
91 IncrementalCompilationFailed;
92
93 typedef void Logger(message);
94
95 typedef bool ReuseFunction(
96 Token diffToken,
97 PartialElement before,
98 PartialElement after);
99
100 class FailedUpdate {
101 /// Either an [Element] or a [Difference].
102 final context;
103 final String message;
104
105 FailedUpdate(this.context, this.message);
106
107 String toString() {
108 if (context == null) return '$message';
109 return 'In $context:\n $message';
110 }
111 }
112
113 abstract class Reuser {
114 final Compiler compiler;
115
116 final api.CompilerInputProvider inputProvider;
117
118 final Logger logTime;
119
120 final Logger logVerbose;
121
122 final List<Update> updates = <Update>[];
123
124 final List<FailedUpdate> _failedUpdates = <FailedUpdate>[];
125
126 final Set<ElementX> _elementsToInvalidate = new Set<ElementX>();
127
128 final Set<ElementX> _removedElements = new Set<ElementX>();
129
130 final Map<Uri, Future> _sources = <Uri, Future>{};
131
132 /// Cached tokens of entry compilation units.
133 final Map<LibraryElementX, Token> _entryUnitTokens =
134 <LibraryElementX, Token>{};
135
136 /// Cached source files for entry compilation units.
137 final Map<LibraryElementX, SourceFile> _entrySourceFiles =
138 <LibraryElementX, SourceFile>{};
139
140 Reuser(
141 this.compiler,
142 this.inputProvider,
143 this.logTime,
144 this.logVerbose);
145
146 IncrementalBackend get backend;
147
148 /// When [true], updates must be applied (using [applyUpdates]) before the
149 /// [compiler]'s state correctly reflects the updated program.
150 bool get hasPendingUpdates => updates.isNotEmpty;
151
152 bool get failed => _failedUpdates.isNotEmpty;
153
154 /// Used as tear-off passed to [LibraryLoaderTask.resetLibraries].
155 Future<Iterable<LibraryElement>> reuseLibraries(
156 Iterable<LibraryElement> libraries) async {
157 List<LibraryElement> reusedLibraries = <LibraryElement>[];
158 for (LibraryElement library in libraries) {
159 if (await _reuseLibrary(library)) {
160 reusedLibraries.add(library);
161 }
162 }
163 return reusedLibraries;
164 }
165
166 Future<bool> _reuseLibrary(LibraryElement library) async {
167 assert(compiler != null);
168 if (library.isPlatformLibrary) {
169 logTime('Reusing $library (assumed read-only).');
170 return true;
171 }
172 try {
173 if (await _haveTagsChanged(library)) {
174 cannotReuse(
175 library,
176 "Changes to library, import, export, or part declarations not"
177 " supported.");
178 // We return true to here to avoid that the library loader tries to
179 // load a different version of this library.
180 return true;
181 }
182
183 bool isChanged = false;
184 List<Script> scripts = <Script>[];
185
186 for (CompilationUnitElementX unit in library.compilationUnits) {
187 Uri uri = unit.script.resourceUri;
188 if (uriHasUpdate(uri)) {
189 isChanged = true;
190 scripts.add(await _updatedScript(unit.script, library));
191 } else {
192 scripts.add(unit.script);
193 }
194 }
195
196 if (!isChanged) {
197 logTime("Reusing $library, source didn't change.");
198 return true;
199 }
200
201 return canReuseLibrary(library, scripts);
202 } finally {
203 _cleanUp(library);
204 }
205 }
206
207 void _cleanUp(LibraryElementX library) {
208 _entryUnitTokens.remove(library);
209 _entrySourceFiles.remove(library);
210 }
211
212 Future<Script> _updatedScript(Script before, LibraryElementX library) {
213 if (before == library.entryCompilationUnit.script &&
214 _entrySourceFiles.containsKey(library)) {
215 return new Future.value(before.copyWithFile(_entrySourceFiles[library]));
216 }
217
218 return _readUri(before.resourceUri).then((bytes) {
219 Uri uri = before.file.uri;
220 String filename = before.file.filename;
221 SourceFile sourceFile = bytes is String
222 ? new StringSourceFile(uri, filename, bytes)
223 : new CachingUtf8BytesSourceFile(uri, filename, bytes);
224 return before.copyWithFile(sourceFile);
225 });
226 }
227
228 Future<bool> _haveTagsChanged(LibraryElementX library) {
229 Script before = library.entryCompilationUnit.script;
230 if (!uriHasUpdate(before.resourceUri)) {
231 // The entry compilation unit hasn't been updated. So the tags aren't
232 // changed.
233 return new Future<bool>.value(false);
234 }
235
236 return _updatedScript(before, library).then((Script script) {
237 _entrySourceFiles[library] = script.file;
238 Token token = new Scanner(_entrySourceFiles[library]).tokenize();
239 _entryUnitTokens[library] = token;
240 // Using two parsers to only create the nodes we want ([LibraryTag]).
241 Parser parser = new Parser(new Listener());
242 Element entryCompilationUnit = library.entryCompilationUnit;
243 NodeListener listener = new NodeListener(
244 compiler.resolution.parsing
245 .getScannerOptionsFor(entryCompilationUnit),
246 compiler.reporter, entryCompilationUnit);
247 Parser nodeParser = new Parser(listener);
248 Iterator<LibraryTag> tags = library.tags.iterator;
249 while (token.kind != EOF_TOKEN) {
250 token = parser.parseMetadataStar(token);
251 if (parser.optional('library', token) ||
252 parser.optional('import', token) ||
253 parser.optional('export', token) ||
254 parser.optional('part', token)) {
255 if (!tags.moveNext()) return true;
256 token = nodeParser.parseTopLevelDeclaration(token);
257 LibraryTag tag = listener.popNode();
258 assert(listener.nodes.isEmpty);
259 if (unparse(tags.current) != unparse(tag)) {
260 return true;
261 }
262 } else {
263 break;
264 }
265 }
266 return tags.moveNext();
267 });
268 }
269
270 Future _readUri(Uri uri) {
271 return _sources.putIfAbsent(uri, () => inputProvider(uri));
272 }
273
274 /// Returns true if [library] can be reused.
275 ///
276 /// This methods also computes the [updates] (patches) needed to have
277 /// [library] reflect the modifications in [scripts].
278 bool canReuseLibrary(LibraryElement library, List<Script> scripts) {
279 logTime('Attempting to reuse ${library}.');
280
281 Uri entryUri = library.entryCompilationUnit.script.resourceUri;
282 Script entryScript =
283 scripts.singleWhere((Script script) => script.resourceUri == entryUri);
284 LibraryElementX newLibrary =
285 new LibraryElementX(entryScript, library.canonicalUri);
286 if (_entryUnitTokens.containsKey(library)) {
287 compiler.dietParser.dietParse(
288 newLibrary.entryCompilationUnit, _entryUnitTokens[library]);
289 } else {
290 compiler.scanner.scanLibrary(newLibrary);
291 }
292
293 TagState tagState = new TagState();
294 for (LibraryTag tag in newLibrary.tags) {
295 if (tag.isImport) {
296 tagState.checkTag(TagState.IMPORT_OR_EXPORT, tag, compiler.reporter);
297 } else if (tag.isExport) {
298 tagState.checkTag(TagState.IMPORT_OR_EXPORT, tag, compiler.reporter);
299 } else if (tag.isLibraryName) {
300 tagState.checkTag(TagState.LIBRARY, tag, compiler.reporter);
301 if (newLibrary.libraryTag == null) {
302 // Use the first if there are multiple (which is reported as an
303 // error in [TagState.checkTag]).
304 newLibrary.libraryTag = tag;
305 }
306 } else if (tag.isPart) {
307 tagState.checkTag(TagState.PART, tag, compiler.reporter);
308 }
309 }
310
311 // TODO(ahe): Process tags using TagState, not
312 // LibraryLoaderTask.processLibraryTags.
313 Link<CompilationUnitElement> units = library.compilationUnits;
314 for (Script script in scripts) {
315 CompilationUnitElementX unit = units.head;
316 units = units.tail;
317 if (script != entryScript) {
318 // TODO(ahe): Copied from library_loader.
319 CompilationUnitElement newUnit =
320 new CompilationUnitElementX(script, newLibrary);
321 compiler.reporter.withCurrentElement(newUnit, () {
322 compiler.scanner.scan(newUnit);
323 if (unit.partTag == null) {
324 compiler.reporter.reportErrorMessage(
325 unit, MessageKind.MISSING_PART_OF_TAG);
326 }
327 });
328 }
329 }
330
331 logTime('New library synthesized.');
332 return canReuseScopeContainerElement(library, newLibrary);
333 }
334
335 bool cannotReuse(context, String message) {
336 _failedUpdates.add(new FailedUpdate(context, message));
337 logVerbose(message);
338 return false;
339 }
340
341 bool canReuseScopeContainerElement(
342 ScopeContainerElement element,
343 ScopeContainerElement newElement) {
344 if (checkForGenericTypes(element)) return false;
345 if (checkForGenericTypes(newElement)) return false;
346 List<Difference> differences = computeDifference(element, newElement);
347 logTime('Differences computed.');
348 for (Difference difference in differences) {
349 logTime('Looking at difference: $difference');
350
351 if (difference.before == null && difference.after is PartialElement) {
352 canReuseAddedElement(difference.after, element, newElement);
353 continue;
354 }
355 if (difference.after == null && difference.before is PartialElement) {
356 canReuseRemovedElement(difference.before, element);
357 continue;
358 }
359 Token diffToken = difference.token;
360 if (diffToken == null) {
361 cannotReuse(difference, "No difference token.");
362 continue;
363 }
364 if (difference.after is! PartialElement &&
365 difference.before is! PartialElement) {
366 cannotReuse(difference, "Don't know how to recompile.");
367 continue;
368 }
369 PartialElement before = difference.before;
370 PartialElement after = difference.after;
371
372 ReuseFunction reuser;
373
374 if (before is PartialFunctionElement && after is PartialFunctionElement) {
375 reuser = canReuseFunction;
376 } else if (before is PartialClassElement &&
377 after is PartialClassElement) {
378 reuser = canReuseClass;
379 } else {
380 reuser = unableToReuse;
381 }
382 if (!reuser(diffToken, before, after)) {
383 assert(_failedUpdates.isNotEmpty);
384 continue;
385 }
386 }
387
388 return _failedUpdates.isEmpty;
389 }
390
391 bool canReuseAddedElement(
392 PartialElement element,
393 ScopeContainerElement container,
394 ScopeContainerElement syntheticContainer) {
395 if (!allowAddedElement(element)) return false;
396 if (element is PartialFunctionElement) {
397 addFunction(element, container);
398 return true;
399 } else if (element is PartialClassElement) {
400 addClass(element, container);
401 return true;
402 } else if (element is PartialFieldList) {
403 addFields(element, container, syntheticContainer);
404 return true;
405 }
406 return cannotReuse(element, "Adding ${element.runtimeType} not supported.");
407 }
408
409 void addFunction(
410 PartialFunctionElement element,
411 /* ScopeContainerElement */ container) {
412 invalidateScopesAffectedBy(element, container);
413
414 addAddedFunctionUpdate(compiler, element, container);
415 }
416
417 void addClass(
418 PartialClassElement element,
419 LibraryElementX library) {
420 invalidateScopesAffectedBy(element, library);
421
422 addAddedClassUpdate(compiler, element, library);
423 }
424
425 /// Called when a field in [definition] has changed.
426 ///
427 /// There's no direct link from a [PartialFieldList] to its implied
428 /// [FieldElementX], so instead we use [syntheticContainer], the (synthetic)
429 /// container created by [canReuseLibrary], or [canReuseClass] (through
430 /// [PartialClassElement.parseNode]). This container is scanned looking for
431 /// fields whose declaration site is [definition].
432 // TODO(ahe): It would be nice if [computeDifference] returned this
433 // information directly.
434 void addFields(
435 PartialFieldList definition,
436 ScopeContainerElement container,
437 ScopeContainerElement syntheticContainer) {
438 List<FieldElementX> fields = <FieldElementX>[];
439 syntheticContainer.forEachLocalMember((ElementX member) {
440 if (member.declarationSite == definition) {
441 fields.add(member);
442 }
443 });
444 for (FieldElementX field in fields) {
445 // TODO(ahe): This only works when there's one field per
446 // PartialFieldList.
447 addField(field, container);
448 }
449 }
450
451 void addField(FieldElementX element, ScopeContainerElement container) {
452 logVerbose("Add field $element to $container.");
453 invalidateScopesAffectedBy(element, container);
454 addAddedFieldUpdate(compiler, element, container);
455 }
456
457 bool canReuseRemovedElement(
458 PartialElement element,
459 ScopeContainerElement container) {
460 if (!allowRemovedElement(element)) return false;
461 if (element is PartialFunctionElement) {
462 removeFunction(element);
463 return true;
464 } else if (element is PartialClassElement) {
465 removeClass(element);
466 return true;
467 } else if (element is PartialFieldList) {
468 removeFields(element, container);
469 return true;
470 }
471 return cannotReuse(
472 element, "Removing ${element.runtimeType} not supported.");
473 }
474
475 void removeFunction(PartialFunctionElement element) {
476 logVerbose("Removed method $element.");
477
478 invalidateScopesAffectedBy(element, element.enclosingElement);
479
480 _removedElements.add(element);
481
482 addRemovedFunctionUpdate(compiler, element);
483 }
484
485 void removeClass(PartialClassElement element) {
486 logVerbose("Removed class $element.");
487
488 invalidateScopesAffectedBy(element, element.library);
489
490 _removedElements.add(element);
491 element.forEachLocalMember((ElementX member) {
492 _removedElements.add(member);
493 });
494
495 addRemovedClassUpdate(compiler, element);
496 }
497
498 void removeFields(
499 PartialFieldList definition,
500 ScopeContainerElement container) {
501 List<FieldElementX> fields = <FieldElementX>[];
502 container.forEachLocalMember((ElementX member) {
503 if (member.declarationSite == definition) {
504 fields.add(member);
505 }
506 });
507 for (FieldElementX field in fields) {
508 // TODO(ahe): This only works when there's one field per
509 // PartialFieldList.
510 removeField(field);
511 }
512 }
513
514 void removeField(FieldElementX element) {
515 logVerbose("Removed field $element.");
516 if (!element.isInstanceMember) {
517 cannotReuse(element, "Not an instance field.");
518 } else {
519 removeInstanceField(element);
520 }
521 }
522
523 void removeInstanceField(FieldElementX element) {
524 PartialClassElement cls = element.enclosingClass;
525
526 invalidateScopesAffectedBy(element, cls);
527
528 _removedElements.add(element);
529
530 addRemovedFieldUpdate(compiler, element);
531 }
532
533 /// Returns true if [element] has generic types (or if we cannot rule out
534 /// that it has generic types).
535 bool checkForGenericTypes(Element element) {
536 if (element is TypeDeclarationElement) {
537 if (!element.isResolved) {
538 if (element is PartialClassElement) {
539 ClassNode node = element.parseNode(compiler.parsing).asClassNode();
540 if (node == null) {
541 cannotReuse(
542 element, "Class body isn't a ClassNode on $element");
543 return true;
544 }
545 bool isGeneric =
546 node.typeParameters != null && !node.typeParameters.isEmpty;
547 if (isGeneric) {
548 // TODO(ahe): Support generic types.
549 cannotReuse(
550 element,
551 "Type variables not supported: '${node.typeParameters}'");
552 return true;
553 }
554 } else {
555 cannotReuse(
556 element, "Can't check for generic types on $element");
557 return true;
558 }
559 } else if (!element.thisType.isRaw) {
560 cannotReuse(
561 element, "Generic types not supported: '${element.thisType}'");
562 return true;
563 }
564 }
565 return false;
566 }
567
568 void invalidateScopesAffectedBy(
569 ElementX element,
570 /* ScopeContainerElement */ container) {
571 if (checkForGenericTypes(element)) return;
572 for (ScopeContainerElement scope in scopesAffectedBy(element, container)) {
573 scanSites(scope, (Element member, DeclarationSite site) {
574 // TODO(ahe): Cache qualifiedNamesIn to avoid quadratic behavior.
575 Set<String> names = qualifiedNamesIn(site);
576 if (canNamesResolveStaticallyTo(names, element, container)) {
577 if (checkForGenericTypes(member)) return;
578 if (member is TypeDeclarationElement) {
579 if (!member.isResolved) {
580 // TODO(ahe): This is a bug in dart2js' forgetElement which
581 // attempts to check if member is a generic type.
582 cannotReuse(member, "Not resolved");
583 return;
584 }
585 }
586 _elementsToInvalidate.add(member);
587 }
588 });
589 }
590 }
591
592 void replaceFunctionInBackend(
593 ElementX element,
594 /* ScopeContainerElement */ container) {
595 List<Element> elements = <Element>[];
596 if (checkForGenericTypes(element)) return;
597 for (ScopeContainerElement scope in scopesAffectedBy(element, container)) {
598 scanSites(scope, (Element member, DeclarationSite site) {
599 // TODO(ahe): Cache qualifiedNamesIn to avoid quadratic behavior.
600 Set<String> names = qualifiedNamesIn(site);
601 if (canNamesResolveStaticallyTo(names, element, container)) {
602 if (checkForGenericTypes(member)) return;
603 if (member is TypeDeclarationElement) {
604 if (!member.isResolved) {
605 // TODO(ahe): This is a bug in dart2js' forgetElement which
606 // attempts to check if member is a generic type.
607 cannotReuse(member, "Not resolved");
608 return;
609 }
610 }
611 elements.add(member);
612 }
613 });
614 }
615 backend.replaceFunctionUsageElement(element, elements);
616 }
617
618 /// Invoke [f] on each [DeclarationSite] in [element]. If [element] is a
619 /// [ScopeContainerElement], invoke f on all local members as well.
620 void scanSites(
621 Element element,
622 void f(ElementX element, DeclarationSite site)) {
623 DeclarationSite site = declarationSite(element);
624 if (site != null) {
625 f(element, site);
626 }
627 if (element is ScopeContainerElement) {
628 element.forEachLocalMember((member) { scanSites(member, f); });
629 }
630 }
631
632 /// Assume [element] is either removed from or added to [container], and
633 /// return all [ScopeContainerElement] that can see this change.
634 List<ScopeContainerElement> scopesAffectedBy(
635 Element element,
636 /* ScopeContainerElement */ container) {
637 // TODO(ahe): Use library export graph to compute this.
638 // TODO(ahe): Should return all user-defined libraries and packages.
639 LibraryElement library = container.library;
640 List<ScopeContainerElement> result = <ScopeContainerElement>[library];
641
642 if (!container.isClass) return result;
643
644 ClassElement cls = container;
645
646 if (!cls.declaration.isResolved) {
647 // TODO(ahe): This test fails otherwise: experimental/add_static_field.
648 throw new IncrementalCompilationFailed(
649 "Unresolved class ${cls.declaration}");
650 }
651 var externalSubtypes =
652 compiler.world.subclassesOf(cls).where((e) => e.library != library);
653
654 return result..addAll(externalSubtypes);
655 }
656
657 /// Returns true if function [before] can be reused to reflect the changes in
658 /// [after].
659 ///
660 /// If [before] can be reused, an update (patch) is added to [updates].
661 bool canReuseFunction(
662 Token diffToken,
663 PartialFunctionElement before,
664 PartialFunctionElement after) {
665 FunctionExpression node =
666 after.parseNode(compiler.parsing).asFunctionExpression();
667 if (node == null) {
668 return cannotReuse(after, "Not a function expression: '$node'");
669 }
670 Token last = after.endToken;
671 if (node.body != null) {
672 last = node.body.getBeginToken();
673 }
674 if (before.isMalformed ||
675 compiler.elementHasCompileTimeError(before) ||
676 isTokenBetween(diffToken, after.beginToken, last)) {
677 removeFunction(before);
678 addFunction(after, before.enclosingElement);
679 if (compiler.mainFunction == before) {
680 return cannotReuse(
681 after,
682 "Unable to handle when signature of '${after.name}' changes");
683 }
684 return allowSignatureChanged(before, after);
685 }
686 logVerbose('Simple modification of ${after} detected');
687 if (!before.isInstanceMember) {
688 if (!allowNonInstanceMemberModified(after)) return false;
689 }
690 updates.add(new FunctionUpdate(compiler, before, after));
691 return true;
692 }
693
694 bool canReuseClass(
695 Token diffToken,
696 PartialClassElement before,
697 PartialClassElement after) {
698 ClassNode node = after.parseNode(compiler.parsing).asClassNode();
699 if (node == null) {
700 return cannotReuse(after, "Not a ClassNode: '$node'");
701 }
702 NodeList body = node.body;
703 if (body == null) {
704 return cannotReuse(after, "Class has no body.");
705 }
706 if (isTokenBetween(diffToken, node.beginToken, body.beginToken.next)) {
707 if (!allowClassHeaderModified(after)) return false;
708 logVerbose('Class header modified in ${after}');
709 addClassUpdate(compiler, before, after);
710 before.forEachLocalMember((ElementX member) {
711 // TODO(ahe): Quadratic.
712 invalidateScopesAffectedBy(member, before);
713 });
714 } else {
715 logVerbose('Simple modification of ${after} detected');
716 }
717 return canReuseScopeContainerElement(before, after);
718 }
719
720 /// Returns true if [token] is found between [first] (included) and [last]
721 /// (excluded).
722 bool isTokenBetween(Token token, Token first, Token last) {
723 Token current = first;
724 while (current != last && current.kind != EOF_TOKEN) {
725 if (current == token) {
726 return true;
727 }
728 current = current.next;
729 }
730 return false;
731 }
732
733 bool unableToReuse(
734 Token diffToken,
735 PartialElement before,
736 PartialElement after) {
737 return cannotReuse(
738 after,
739 'Unhandled change:'
740 ' ${before} (${before.runtimeType} -> ${after.runtimeType}).');
741 }
742
743 /// Apply the collected [updates]. Return a list of elements that needs to be
744 /// recompiled after applying the updates.
745 List<Element> applyUpdates() {
746 for (Update update in updates) {
747 update.captureState();
748 }
749 if (_failedUpdates.isNotEmpty) {
750 throw new IncrementalCompilationFailed(_failedUpdates.join('\n\n'));
751 }
752 for (ElementX element in _elementsToInvalidate) {
753 compiler.forgetElement(element);
754 element.reuseElement();
755 if (element.isFunction) {
756 replaceFunctionInBackend(element, element.enclosingElement);
757 }
758 }
759 List<Element> elementsToInvalidate = <Element>[];
760 for (ElementX element in _elementsToInvalidate) {
761 if (!_removedElements.contains(element)) {
762 elementsToInvalidate.add(element);
763 }
764 }
765 for (Update update in updates) {
766 Element element = update.apply(backend);
767 if (!update.isRemoval) {
768 elementsToInvalidate.add(element);
769 }
770 if (update is FunctionUpdate) {
771 replaceFunctionInBackend(element, element.enclosingElement);
772 }
773 }
774 return elementsToInvalidate;
775 }
776
777 void addClassUpdate(
778 Compiler compiler,
779 PartialClassElement before,
780 PartialClassElement after);
781
782 void addAddedFunctionUpdate(
783 Compiler compiler,
784 PartialFunctionElement element,
785 /* ScopeContainerElement */ container);
786
787 void addRemovedFunctionUpdate(
788 Compiler compiler,
789 PartialFunctionElement element);
790
791 void addRemovedFieldUpdate(
792 Compiler compiler,
793 FieldElementX element);
794
795 void addRemovedClassUpdate(
796 Compiler compiler,
797 PartialClassElement element);
798
799 void addAddedFieldUpdate(
800 Compiler compiler,
801 FieldElementX element,
802 /* ScopeContainerElement */ container);
803
804 void addAddedClassUpdate(
805 Compiler compiler,
806 PartialClassElement element,
807 LibraryElementX library);
808
809 bool uriHasUpdate(Uri uri);
810
811 bool allowClassHeaderModified(PartialClassElement after);
812
813 bool allowSignatureChanged(
814 PartialFunctionElement before,
815 PartialFunctionElement after);
816
817 bool allowNonInstanceMemberModified(PartialFunctionElement after);
818
819 bool allowRemovedElement(PartialElement element);
820
821 bool allowAddedElement(PartialElement element);
822 }
823
824 /// Represents an update (aka patch) of [before] to [after]. We use the word
825 /// "update" to avoid confusion with the compiler feature of "patch" methods.
826 abstract class Update {
827 final Compiler compiler;
828
829 PartialElement get before;
830
831 PartialElement get after;
832
833 Update(this.compiler);
834
835 /// Applies the update to [before] and returns that element.
836 Element apply(IncrementalBackend backend);
837
838 bool get isRemoval => false;
839
840 /// Called before any patches are applied to capture any state that is needed
841 /// later.
842 void captureState() {
843 }
844 }
845
846 /// Represents an update of a function element.
847 class FunctionUpdate extends Update with ReuseFunctionElement {
848 final PartialFunctionElement before;
849
850 final PartialFunctionElement after;
851
852 FunctionUpdate(Compiler compiler, this.before, this.after)
853 : super(compiler);
854
855 PartialFunctionElement apply(IncrementalBackend backend) {
856 patchElement();
857 reuseElement();
858 return before;
859 }
860
861 /// Destructively change the tokens in [before] to match those of [after].
862 void patchElement() {
863 before.beginToken = after.beginToken;
864 before.endToken = after.endToken;
865 before.getOrSet = after.getOrSet;
866 }
867 }
868
869 abstract class ReuseFunctionElement {
870 Compiler get compiler;
871
872 PartialFunctionElement get before;
873
874 /// Reset various caches and remove this element from the compiler's internal
875 /// state.
876 void reuseElement() {
877 compiler.forgetElement(before);
878 before.reuseElement();
879 }
880 }
881
882 abstract class RemovalUpdate extends Update {
883 ElementX get element;
884
885 RemovalUpdate(Compiler compiler)
886 : super(compiler);
887
888 bool get isRemoval => true;
889
890 void removeFromEnclosing() {
891 // TODO(ahe): Need to recompute duplicated elements logic again. Simplest
892 // solution is probably to remove all elements from enclosing scope and add
893 // them back.
894 if (element.isTopLevel) {
895 removeFromLibrary(element.library);
896 } else {
897 removeFromEnclosingClass(element.enclosingClass);
898 }
899 }
900
901 void removeFromEnclosingClass(PartialClassElement cls) {
902 cls.localMembersCache = null;
903 cls.localMembersReversed = cls.localMembersReversed.copyWithout(element);
904 cls.localScope.contents.remove(element.name);
905 }
906
907 void removeFromLibrary(LibraryElementX library) {
908 library.localMembers = library.localMembers.copyWithout(element);
909 library.localScope.contents.remove(element.name);
910 }
911 }
912
913 abstract class RemovedFunctionUpdate extends RemovalUpdate
914 with ReuseFunctionElement {
915 final PartialFunctionElement element;
916
917 bool wasStateCaptured = false;
918
919 RemovedFunctionUpdate(Compiler compiler, this.element)
920 : super(compiler);
921
922 PartialFunctionElement get before => element;
923
924 PartialFunctionElement get after => null;
925
926 void captureState() {
927 if (wasStateCaptured) throw "captureState was called twice.";
928 wasStateCaptured = true;
929 }
930
931 PartialFunctionElement apply(IncrementalBackend backend) {
932 if (!wasStateCaptured) throw "captureState must be called before apply.";
933 removeFromEnclosing();
934 backend.removeFunction(element);
935 reuseElement();
936 return null;
937 }
938 }
939
940 abstract class RemovedClassUpdate extends RemovalUpdate {
941 final PartialClassElement element;
942
943 bool wasStateCaptured = false;
944
945 RemovedClassUpdate(Compiler compiler, this.element)
946 : super(compiler);
947
948 PartialClassElement get before => element;
949
950 PartialClassElement get after => null;
951
952 void captureState() {
953 if (wasStateCaptured) throw "captureState was called twice.";
954 wasStateCaptured = true;
955 }
956
957 PartialClassElement apply(IncrementalBackend backend) {
958 if (!wasStateCaptured) {
959 throw new StateError("captureState must be called before apply.");
960 }
961
962 removeFromEnclosing();
963
964 element.forEachLocalMember((ElementX member) {
965 compiler.forgetElement(member);
966 member.reuseElement();
967 });
968
969 compiler.forgetElement(element);
970 element.reuseElement();
971
972 return null;
973 }
974 }
975
976 abstract class RemovedFieldUpdate extends RemovalUpdate {
977 final FieldElementX element;
978
979 bool wasStateCaptured = false;
980
981 RemovedFieldUpdate(Compiler compiler, this.element)
982 : super(compiler);
983
984 PartialFieldList get before => element.declarationSite;
985
986 PartialFieldList get after => null;
987
988 void captureState() {
989 if (wasStateCaptured) throw "captureState was called twice.";
990 wasStateCaptured = true;
991 }
992
993 FieldElementX apply(IncrementalBackend backend) {
994 if (!wasStateCaptured) {
995 throw new StateError("captureState must be called before apply.");
996 }
997
998 removeFromEnclosing();
999 backend.removeField(element);
1000
1001 return element;
1002 }
1003 }
1004
1005 abstract class AddedFunctionUpdate extends Update {
1006 final PartialFunctionElement element;
1007
1008 final /* ScopeContainerElement */ container;
1009
1010 AddedFunctionUpdate(Compiler compiler, this.element, this.container)
1011 : super(compiler) {
1012 if (container == null) {
1013 throw "container is null";
1014 }
1015 }
1016
1017 PartialFunctionElement get before => null;
1018
1019 PartialFunctionElement get after => element;
1020
1021 PartialFunctionElement apply(IncrementalBackend backend) {
1022 Element enclosing = container;
1023 if (enclosing.isLibrary) {
1024 // TODO(ahe): Reuse compilation unit of element instead?
1025 enclosing = enclosing.compilationUnit;
1026 }
1027 PartialFunctionElement copy = element.copyWithEnclosing(enclosing);
1028 container.addMember(copy, compiler.reporter);
1029 return copy;
1030 }
1031 }
1032
1033 abstract class AddedClassUpdate extends Update {
1034 final PartialClassElement element;
1035
1036 final LibraryElementX library;
1037
1038 AddedClassUpdate(Compiler compiler, this.element, this.library)
1039 : super(compiler);
1040
1041 PartialClassElement get before => null;
1042
1043 PartialClassElement get after => element;
1044
1045 PartialClassElement apply(IncrementalBackend backend) {
1046 // TODO(ahe): Reuse compilation unit of element instead?
1047 CompilationUnitElementX compilationUnit = library.compilationUnit;
1048 PartialClassElement copy = element.copyWithEnclosing(compilationUnit);
1049 compilationUnit.addMember(copy, compiler.reporter);
1050 return copy;
1051 }
1052 }
1053
1054 abstract class AddedFieldUpdate extends Update {
1055 final FieldElementX element;
1056
1057 final /* ScopeContainerElement */ container;
1058
1059 AddedFieldUpdate(Compiler compiler, this.element, this.container)
1060 : super(compiler);
1061
1062 PartialFieldList get before => null;
1063
1064 PartialFieldList get after => element.declarationSite;
1065
1066 FieldElementX apply(IncrementalBackend backend) {
1067 Element enclosing = container;
1068 if (enclosing.isLibrary) {
1069 // TODO(ahe): Reuse compilation unit of element instead?
1070 enclosing = enclosing.compilationUnit;
1071 }
1072 FieldElementX copy = element.copyWithEnclosing(enclosing);
1073 container.addMember(copy, compiler.reporter);
1074 return copy;
1075 }
1076 }
1077
1078 abstract class ClassUpdate extends Update {
1079 final PartialClassElement before;
1080
1081 final PartialClassElement after;
1082
1083 ClassUpdate(Compiler compiler, this.before, this.after)
1084 : super(compiler);
1085
1086 PartialClassElement apply(IncrementalBackend backend) {
1087 patchElement();
1088 reuseElement();
1089 return before;
1090 }
1091
1092 /// Destructively change the tokens in [before] to match those of [after].
1093 void patchElement() {
1094 before.cachedNode = after.cachedNode;
1095 before.beginToken = after.beginToken;
1096 before.endToken = after.endToken;
1097 }
1098
1099 void reuseElement() {
1100 before.supertype = null;
1101 before.interfaces = null;
1102 before.supertypeLoadState = STATE_NOT_STARTED;
1103 before.resolutionState = STATE_NOT_STARTED;
1104 before.isProxy = false;
1105 before.hasIncompleteHierarchy = false;
1106 before.backendMembers = const Link<Element>();
1107 before.allSupertypesAndSelf = null;
1108 }
1109 }
1110
1111 /// Returns all qualified names in [element] with less than four identifiers. A
1112 /// qualified name is an identifier followed by a sequence of dots and
1113 /// identifiers, for example, "x", and "x.y.z". But not "x.y.z.w" ("w" is the
1114 /// fourth identifier).
1115 ///
1116 /// The longest possible name that can be resolved is three identifiers, for
1117 /// example, "prefix.MyClass.staticMethod". Since four or more identifiers
1118 /// cannot resolve to anything statically, they're not included in the returned
1119 /// value of this method.
1120 Set<String> qualifiedNamesIn(PartialElement element) {
1121 Token beginToken = element.beginToken;
1122 Token endToken = element.endToken;
1123 Token token = beginToken;
1124 if (element is PartialClassElement) {
1125 ClassNode node = element.cachedNode;
1126 if (node != null) {
1127 NodeList body = node.body;
1128 if (body != null) {
1129 endToken = body.beginToken;
1130 }
1131 }
1132 }
1133 Set<String> names = new Set<String>();
1134 do {
1135 if (token.isIdentifier()) {
1136 String name = token.value;
1137 // [name] is a single "identifier".
1138 names.add(name);
1139 if (identical('.', token.next.stringValue) &&
1140 token.next.next.isIdentifier()) {
1141 token = token.next.next;
1142 name += '.${token.value}';
1143 // [name] is "idenfifier.idenfifier".
1144 names.add(name);
1145
1146 if (identical('.', token.next.stringValue) &&
1147 token.next.next.isIdentifier()) {
1148 token = token.next.next;
1149 name += '.${token.value}';
1150 // [name] is "idenfifier.idenfifier.idenfifier".
1151 names.add(name);
1152
1153 while (identical('.', token.next.stringValue) &&
1154 token.next.next.isIdentifier()) {
1155 // Skip remaining identifiers, they cannot statically resolve to
1156 // anything, and must be dynamic sends.
1157 token = token.next.next;
1158 }
1159 }
1160 }
1161 }
1162 token = token.next;
1163 } while (token.kind != EOF_TOKEN && token != endToken);
1164 return names;
1165 }
1166
1167 /// Returns true if one of the qualified names in names (as computed by
1168 /// [qualifiedNamesIn]) could be a static reference to [element].
1169 bool canNamesResolveStaticallyTo(
1170 Set<String> names,
1171 Element element,
1172 /* ScopeContainerElement */ container) {
1173 if (names.contains(element.name)) return true;
1174 if (container != null && container.isClass) {
1175 // [names] contains C.m, where C is the name of [container], and m is the
1176 // name of [element].
1177 if (names.contains("${container.name}.${element.name}")) return true;
1178 }
1179 // TODO(ahe): Check for prefixes as well.
1180 return false;
1181 }
1182
1183 DeclarationSite declarationSite(Element element) {
1184 return element is ElementX ? element.declarationSite : null;
1185 }
OLDNEW
« no previous file with comments | « pkg/fletchc/lib/incremental/fletchc_incremental.dart ('k') | pkg/fletchc/lib/incremental/scope_information_visitor.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698