Index: pkg/fletchc/lib/incremental/reuser.dart |
diff --git a/pkg/fletchc/lib/incremental/reuser.dart b/pkg/fletchc/lib/incremental/reuser.dart |
deleted file mode 100644 |
index 776c6fca1847725e57757a9d36e48fce4d357c1d..0000000000000000000000000000000000000000 |
--- a/pkg/fletchc/lib/incremental/reuser.dart |
+++ /dev/null |
@@ -1,1185 +0,0 @@ |
-// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-library fletchc_incremental.reuser; |
- |
-import 'dart:async' show |
- Future; |
- |
-import 'package:compiler/compiler.dart' as api; |
- |
-import 'package:compiler/src/compiler.dart' show |
- Compiler; |
- |
-import 'package:compiler/src/diagnostics/messages.dart' show |
- MessageKind; |
- |
-import 'package:compiler/src/script.dart' show |
- Script; |
- |
-import 'package:compiler/src/elements/elements.dart' show |
- ClassElement, |
- CompilationUnitElement, |
- Element, |
- LibraryElement, |
- STATE_NOT_STARTED, |
- ScopeContainerElement, |
- TypeDeclarationElement; |
- |
-import 'package:compiler/src/tokens/token_constants.dart' show |
- EOF_TOKEN; |
- |
-import 'package:compiler/src/tokens/token.dart' show |
- Token; |
- |
-import 'package:compiler/src/parser/partial_elements.dart' show |
- PartialClassElement, |
- PartialElement, |
- PartialFieldList, |
- PartialFunctionElement; |
- |
-import 'package:compiler/src/scanner/scanner.dart' show |
- Scanner; |
- |
-import 'package:compiler/src/parser/parser.dart' show |
- Parser; |
- |
-import 'package:compiler/src/parser/listener.dart' show |
- Listener; |
- |
-import 'package:compiler/src/parser/node_listener.dart' show |
- NodeListener; |
- |
-import 'package:compiler/src/io/source_file.dart' show |
- CachingUtf8BytesSourceFile, |
- SourceFile, |
- StringSourceFile; |
- |
-import 'package:compiler/src/tree/tree.dart' show |
- ClassNode, |
- FunctionExpression, |
- LibraryTag, |
- NodeList, |
- unparse; |
- |
-import 'package:compiler/src/util/util.dart' show |
- Link; |
- |
-import 'package:compiler/src/elements/modelx.dart' show |
- ClassElementX, |
- CompilationUnitElementX, |
- DeclarationSite, |
- ElementX, |
- FieldElementX, |
- LibraryElementX; |
- |
-import 'package:compiler/src/constants/values.dart' show |
- ConstantValue; |
- |
-import 'package:compiler/src/library_loader.dart' show |
- TagState; |
- |
-import '../incremental_backend.dart' show |
- IncrementalBackend; |
- |
-import 'diff.dart' show |
- Difference, |
- computeDifference; |
- |
-import 'fletchc_incremental.dart' show |
- IncrementalCompilationFailed; |
- |
-typedef void Logger(message); |
- |
-typedef bool ReuseFunction( |
- Token diffToken, |
- PartialElement before, |
- PartialElement after); |
- |
-class FailedUpdate { |
- /// Either an [Element] or a [Difference]. |
- final context; |
- final String message; |
- |
- FailedUpdate(this.context, this.message); |
- |
- String toString() { |
- if (context == null) return '$message'; |
- return 'In $context:\n $message'; |
- } |
-} |
- |
-abstract class Reuser { |
- final Compiler compiler; |
- |
- final api.CompilerInputProvider inputProvider; |
- |
- final Logger logTime; |
- |
- final Logger logVerbose; |
- |
- final List<Update> updates = <Update>[]; |
- |
- final List<FailedUpdate> _failedUpdates = <FailedUpdate>[]; |
- |
- final Set<ElementX> _elementsToInvalidate = new Set<ElementX>(); |
- |
- final Set<ElementX> _removedElements = new Set<ElementX>(); |
- |
- final Map<Uri, Future> _sources = <Uri, Future>{}; |
- |
- /// Cached tokens of entry compilation units. |
- final Map<LibraryElementX, Token> _entryUnitTokens = |
- <LibraryElementX, Token>{}; |
- |
- /// Cached source files for entry compilation units. |
- final Map<LibraryElementX, SourceFile> _entrySourceFiles = |
- <LibraryElementX, SourceFile>{}; |
- |
- Reuser( |
- this.compiler, |
- this.inputProvider, |
- this.logTime, |
- this.logVerbose); |
- |
- IncrementalBackend get backend; |
- |
- /// When [true], updates must be applied (using [applyUpdates]) before the |
- /// [compiler]'s state correctly reflects the updated program. |
- bool get hasPendingUpdates => updates.isNotEmpty; |
- |
- bool get failed => _failedUpdates.isNotEmpty; |
- |
- /// Used as tear-off passed to [LibraryLoaderTask.resetLibraries]. |
- Future<Iterable<LibraryElement>> reuseLibraries( |
- Iterable<LibraryElement> libraries) async { |
- List<LibraryElement> reusedLibraries = <LibraryElement>[]; |
- for (LibraryElement library in libraries) { |
- if (await _reuseLibrary(library)) { |
- reusedLibraries.add(library); |
- } |
- } |
- return reusedLibraries; |
- } |
- |
- Future<bool> _reuseLibrary(LibraryElement library) async { |
- assert(compiler != null); |
- if (library.isPlatformLibrary) { |
- logTime('Reusing $library (assumed read-only).'); |
- return true; |
- } |
- try { |
- if (await _haveTagsChanged(library)) { |
- cannotReuse( |
- library, |
- "Changes to library, import, export, or part declarations not" |
- " supported."); |
- // We return true to here to avoid that the library loader tries to |
- // load a different version of this library. |
- return true; |
- } |
- |
- bool isChanged = false; |
- List<Script> scripts = <Script>[]; |
- |
- for (CompilationUnitElementX unit in library.compilationUnits) { |
- Uri uri = unit.script.resourceUri; |
- if (uriHasUpdate(uri)) { |
- isChanged = true; |
- scripts.add(await _updatedScript(unit.script, library)); |
- } else { |
- scripts.add(unit.script); |
- } |
- } |
- |
- if (!isChanged) { |
- logTime("Reusing $library, source didn't change."); |
- return true; |
- } |
- |
- return canReuseLibrary(library, scripts); |
- } finally { |
- _cleanUp(library); |
- } |
- } |
- |
- void _cleanUp(LibraryElementX library) { |
- _entryUnitTokens.remove(library); |
- _entrySourceFiles.remove(library); |
- } |
- |
- Future<Script> _updatedScript(Script before, LibraryElementX library) { |
- if (before == library.entryCompilationUnit.script && |
- _entrySourceFiles.containsKey(library)) { |
- return new Future.value(before.copyWithFile(_entrySourceFiles[library])); |
- } |
- |
- return _readUri(before.resourceUri).then((bytes) { |
- Uri uri = before.file.uri; |
- String filename = before.file.filename; |
- SourceFile sourceFile = bytes is String |
- ? new StringSourceFile(uri, filename, bytes) |
- : new CachingUtf8BytesSourceFile(uri, filename, bytes); |
- return before.copyWithFile(sourceFile); |
- }); |
- } |
- |
- Future<bool> _haveTagsChanged(LibraryElementX library) { |
- Script before = library.entryCompilationUnit.script; |
- if (!uriHasUpdate(before.resourceUri)) { |
- // The entry compilation unit hasn't been updated. So the tags aren't |
- // changed. |
- return new Future<bool>.value(false); |
- } |
- |
- return _updatedScript(before, library).then((Script script) { |
- _entrySourceFiles[library] = script.file; |
- Token token = new Scanner(_entrySourceFiles[library]).tokenize(); |
- _entryUnitTokens[library] = token; |
- // Using two parsers to only create the nodes we want ([LibraryTag]). |
- Parser parser = new Parser(new Listener()); |
- Element entryCompilationUnit = library.entryCompilationUnit; |
- NodeListener listener = new NodeListener( |
- compiler.resolution.parsing |
- .getScannerOptionsFor(entryCompilationUnit), |
- compiler.reporter, entryCompilationUnit); |
- Parser nodeParser = new Parser(listener); |
- Iterator<LibraryTag> tags = library.tags.iterator; |
- while (token.kind != EOF_TOKEN) { |
- token = parser.parseMetadataStar(token); |
- if (parser.optional('library', token) || |
- parser.optional('import', token) || |
- parser.optional('export', token) || |
- parser.optional('part', token)) { |
- if (!tags.moveNext()) return true; |
- token = nodeParser.parseTopLevelDeclaration(token); |
- LibraryTag tag = listener.popNode(); |
- assert(listener.nodes.isEmpty); |
- if (unparse(tags.current) != unparse(tag)) { |
- return true; |
- } |
- } else { |
- break; |
- } |
- } |
- return tags.moveNext(); |
- }); |
- } |
- |
- Future _readUri(Uri uri) { |
- return _sources.putIfAbsent(uri, () => inputProvider(uri)); |
- } |
- |
- /// Returns true if [library] can be reused. |
- /// |
- /// This methods also computes the [updates] (patches) needed to have |
- /// [library] reflect the modifications in [scripts]. |
- bool canReuseLibrary(LibraryElement library, List<Script> scripts) { |
- logTime('Attempting to reuse ${library}.'); |
- |
- Uri entryUri = library.entryCompilationUnit.script.resourceUri; |
- Script entryScript = |
- scripts.singleWhere((Script script) => script.resourceUri == entryUri); |
- LibraryElementX newLibrary = |
- new LibraryElementX(entryScript, library.canonicalUri); |
- if (_entryUnitTokens.containsKey(library)) { |
- compiler.dietParser.dietParse( |
- newLibrary.entryCompilationUnit, _entryUnitTokens[library]); |
- } else { |
- compiler.scanner.scanLibrary(newLibrary); |
- } |
- |
- TagState tagState = new TagState(); |
- for (LibraryTag tag in newLibrary.tags) { |
- if (tag.isImport) { |
- tagState.checkTag(TagState.IMPORT_OR_EXPORT, tag, compiler.reporter); |
- } else if (tag.isExport) { |
- tagState.checkTag(TagState.IMPORT_OR_EXPORT, tag, compiler.reporter); |
- } else if (tag.isLibraryName) { |
- tagState.checkTag(TagState.LIBRARY, tag, compiler.reporter); |
- if (newLibrary.libraryTag == null) { |
- // Use the first if there are multiple (which is reported as an |
- // error in [TagState.checkTag]). |
- newLibrary.libraryTag = tag; |
- } |
- } else if (tag.isPart) { |
- tagState.checkTag(TagState.PART, tag, compiler.reporter); |
- } |
- } |
- |
- // TODO(ahe): Process tags using TagState, not |
- // LibraryLoaderTask.processLibraryTags. |
- Link<CompilationUnitElement> units = library.compilationUnits; |
- for (Script script in scripts) { |
- CompilationUnitElementX unit = units.head; |
- units = units.tail; |
- if (script != entryScript) { |
- // TODO(ahe): Copied from library_loader. |
- CompilationUnitElement newUnit = |
- new CompilationUnitElementX(script, newLibrary); |
- compiler.reporter.withCurrentElement(newUnit, () { |
- compiler.scanner.scan(newUnit); |
- if (unit.partTag == null) { |
- compiler.reporter.reportErrorMessage( |
- unit, MessageKind.MISSING_PART_OF_TAG); |
- } |
- }); |
- } |
- } |
- |
- logTime('New library synthesized.'); |
- return canReuseScopeContainerElement(library, newLibrary); |
- } |
- |
- bool cannotReuse(context, String message) { |
- _failedUpdates.add(new FailedUpdate(context, message)); |
- logVerbose(message); |
- return false; |
- } |
- |
- bool canReuseScopeContainerElement( |
- ScopeContainerElement element, |
- ScopeContainerElement newElement) { |
- if (checkForGenericTypes(element)) return false; |
- if (checkForGenericTypes(newElement)) return false; |
- List<Difference> differences = computeDifference(element, newElement); |
- logTime('Differences computed.'); |
- for (Difference difference in differences) { |
- logTime('Looking at difference: $difference'); |
- |
- if (difference.before == null && difference.after is PartialElement) { |
- canReuseAddedElement(difference.after, element, newElement); |
- continue; |
- } |
- if (difference.after == null && difference.before is PartialElement) { |
- canReuseRemovedElement(difference.before, element); |
- continue; |
- } |
- Token diffToken = difference.token; |
- if (diffToken == null) { |
- cannotReuse(difference, "No difference token."); |
- continue; |
- } |
- if (difference.after is! PartialElement && |
- difference.before is! PartialElement) { |
- cannotReuse(difference, "Don't know how to recompile."); |
- continue; |
- } |
- PartialElement before = difference.before; |
- PartialElement after = difference.after; |
- |
- ReuseFunction reuser; |
- |
- if (before is PartialFunctionElement && after is PartialFunctionElement) { |
- reuser = canReuseFunction; |
- } else if (before is PartialClassElement && |
- after is PartialClassElement) { |
- reuser = canReuseClass; |
- } else { |
- reuser = unableToReuse; |
- } |
- if (!reuser(diffToken, before, after)) { |
- assert(_failedUpdates.isNotEmpty); |
- continue; |
- } |
- } |
- |
- return _failedUpdates.isEmpty; |
- } |
- |
- bool canReuseAddedElement( |
- PartialElement element, |
- ScopeContainerElement container, |
- ScopeContainerElement syntheticContainer) { |
- if (!allowAddedElement(element)) return false; |
- if (element is PartialFunctionElement) { |
- addFunction(element, container); |
- return true; |
- } else if (element is PartialClassElement) { |
- addClass(element, container); |
- return true; |
- } else if (element is PartialFieldList) { |
- addFields(element, container, syntheticContainer); |
- return true; |
- } |
- return cannotReuse(element, "Adding ${element.runtimeType} not supported."); |
- } |
- |
- void addFunction( |
- PartialFunctionElement element, |
- /* ScopeContainerElement */ container) { |
- invalidateScopesAffectedBy(element, container); |
- |
- addAddedFunctionUpdate(compiler, element, container); |
- } |
- |
- void addClass( |
- PartialClassElement element, |
- LibraryElementX library) { |
- invalidateScopesAffectedBy(element, library); |
- |
- addAddedClassUpdate(compiler, element, library); |
- } |
- |
- /// Called when a field in [definition] has changed. |
- /// |
- /// There's no direct link from a [PartialFieldList] to its implied |
- /// [FieldElementX], so instead we use [syntheticContainer], the (synthetic) |
- /// container created by [canReuseLibrary], or [canReuseClass] (through |
- /// [PartialClassElement.parseNode]). This container is scanned looking for |
- /// fields whose declaration site is [definition]. |
- // TODO(ahe): It would be nice if [computeDifference] returned this |
- // information directly. |
- void addFields( |
- PartialFieldList definition, |
- ScopeContainerElement container, |
- ScopeContainerElement syntheticContainer) { |
- List<FieldElementX> fields = <FieldElementX>[]; |
- syntheticContainer.forEachLocalMember((ElementX member) { |
- if (member.declarationSite == definition) { |
- fields.add(member); |
- } |
- }); |
- for (FieldElementX field in fields) { |
- // TODO(ahe): This only works when there's one field per |
- // PartialFieldList. |
- addField(field, container); |
- } |
- } |
- |
- void addField(FieldElementX element, ScopeContainerElement container) { |
- logVerbose("Add field $element to $container."); |
- invalidateScopesAffectedBy(element, container); |
- addAddedFieldUpdate(compiler, element, container); |
- } |
- |
- bool canReuseRemovedElement( |
- PartialElement element, |
- ScopeContainerElement container) { |
- if (!allowRemovedElement(element)) return false; |
- if (element is PartialFunctionElement) { |
- removeFunction(element); |
- return true; |
- } else if (element is PartialClassElement) { |
- removeClass(element); |
- return true; |
- } else if (element is PartialFieldList) { |
- removeFields(element, container); |
- return true; |
- } |
- return cannotReuse( |
- element, "Removing ${element.runtimeType} not supported."); |
- } |
- |
- void removeFunction(PartialFunctionElement element) { |
- logVerbose("Removed method $element."); |
- |
- invalidateScopesAffectedBy(element, element.enclosingElement); |
- |
- _removedElements.add(element); |
- |
- addRemovedFunctionUpdate(compiler, element); |
- } |
- |
- void removeClass(PartialClassElement element) { |
- logVerbose("Removed class $element."); |
- |
- invalidateScopesAffectedBy(element, element.library); |
- |
- _removedElements.add(element); |
- element.forEachLocalMember((ElementX member) { |
- _removedElements.add(member); |
- }); |
- |
- addRemovedClassUpdate(compiler, element); |
- } |
- |
- void removeFields( |
- PartialFieldList definition, |
- ScopeContainerElement container) { |
- List<FieldElementX> fields = <FieldElementX>[]; |
- container.forEachLocalMember((ElementX member) { |
- if (member.declarationSite == definition) { |
- fields.add(member); |
- } |
- }); |
- for (FieldElementX field in fields) { |
- // TODO(ahe): This only works when there's one field per |
- // PartialFieldList. |
- removeField(field); |
- } |
- } |
- |
- void removeField(FieldElementX element) { |
- logVerbose("Removed field $element."); |
- if (!element.isInstanceMember) { |
- cannotReuse(element, "Not an instance field."); |
- } else { |
- removeInstanceField(element); |
- } |
- } |
- |
- void removeInstanceField(FieldElementX element) { |
- PartialClassElement cls = element.enclosingClass; |
- |
- invalidateScopesAffectedBy(element, cls); |
- |
- _removedElements.add(element); |
- |
- addRemovedFieldUpdate(compiler, element); |
- } |
- |
- /// Returns true if [element] has generic types (or if we cannot rule out |
- /// that it has generic types). |
- bool checkForGenericTypes(Element element) { |
- if (element is TypeDeclarationElement) { |
- if (!element.isResolved) { |
- if (element is PartialClassElement) { |
- ClassNode node = element.parseNode(compiler.parsing).asClassNode(); |
- if (node == null) { |
- cannotReuse( |
- element, "Class body isn't a ClassNode on $element"); |
- return true; |
- } |
- bool isGeneric = |
- node.typeParameters != null && !node.typeParameters.isEmpty; |
- if (isGeneric) { |
- // TODO(ahe): Support generic types. |
- cannotReuse( |
- element, |
- "Type variables not supported: '${node.typeParameters}'"); |
- return true; |
- } |
- } else { |
- cannotReuse( |
- element, "Can't check for generic types on $element"); |
- return true; |
- } |
- } else if (!element.thisType.isRaw) { |
- cannotReuse( |
- element, "Generic types not supported: '${element.thisType}'"); |
- return true; |
- } |
- } |
- return false; |
- } |
- |
- void invalidateScopesAffectedBy( |
- ElementX element, |
- /* ScopeContainerElement */ container) { |
- if (checkForGenericTypes(element)) return; |
- for (ScopeContainerElement scope in scopesAffectedBy(element, container)) { |
- scanSites(scope, (Element member, DeclarationSite site) { |
- // TODO(ahe): Cache qualifiedNamesIn to avoid quadratic behavior. |
- Set<String> names = qualifiedNamesIn(site); |
- if (canNamesResolveStaticallyTo(names, element, container)) { |
- if (checkForGenericTypes(member)) return; |
- if (member is TypeDeclarationElement) { |
- if (!member.isResolved) { |
- // TODO(ahe): This is a bug in dart2js' forgetElement which |
- // attempts to check if member is a generic type. |
- cannotReuse(member, "Not resolved"); |
- return; |
- } |
- } |
- _elementsToInvalidate.add(member); |
- } |
- }); |
- } |
- } |
- |
- void replaceFunctionInBackend( |
- ElementX element, |
- /* ScopeContainerElement */ container) { |
- List<Element> elements = <Element>[]; |
- if (checkForGenericTypes(element)) return; |
- for (ScopeContainerElement scope in scopesAffectedBy(element, container)) { |
- scanSites(scope, (Element member, DeclarationSite site) { |
- // TODO(ahe): Cache qualifiedNamesIn to avoid quadratic behavior. |
- Set<String> names = qualifiedNamesIn(site); |
- if (canNamesResolveStaticallyTo(names, element, container)) { |
- if (checkForGenericTypes(member)) return; |
- if (member is TypeDeclarationElement) { |
- if (!member.isResolved) { |
- // TODO(ahe): This is a bug in dart2js' forgetElement which |
- // attempts to check if member is a generic type. |
- cannotReuse(member, "Not resolved"); |
- return; |
- } |
- } |
- elements.add(member); |
- } |
- }); |
- } |
- backend.replaceFunctionUsageElement(element, elements); |
- } |
- |
- /// Invoke [f] on each [DeclarationSite] in [element]. If [element] is a |
- /// [ScopeContainerElement], invoke f on all local members as well. |
- void scanSites( |
- Element element, |
- void f(ElementX element, DeclarationSite site)) { |
- DeclarationSite site = declarationSite(element); |
- if (site != null) { |
- f(element, site); |
- } |
- if (element is ScopeContainerElement) { |
- element.forEachLocalMember((member) { scanSites(member, f); }); |
- } |
- } |
- |
- /// Assume [element] is either removed from or added to [container], and |
- /// return all [ScopeContainerElement] that can see this change. |
- List<ScopeContainerElement> scopesAffectedBy( |
- Element element, |
- /* ScopeContainerElement */ container) { |
- // TODO(ahe): Use library export graph to compute this. |
- // TODO(ahe): Should return all user-defined libraries and packages. |
- LibraryElement library = container.library; |
- List<ScopeContainerElement> result = <ScopeContainerElement>[library]; |
- |
- if (!container.isClass) return result; |
- |
- ClassElement cls = container; |
- |
- if (!cls.declaration.isResolved) { |
- // TODO(ahe): This test fails otherwise: experimental/add_static_field. |
- throw new IncrementalCompilationFailed( |
- "Unresolved class ${cls.declaration}"); |
- } |
- var externalSubtypes = |
- compiler.world.subclassesOf(cls).where((e) => e.library != library); |
- |
- return result..addAll(externalSubtypes); |
- } |
- |
- /// Returns true if function [before] can be reused to reflect the changes in |
- /// [after]. |
- /// |
- /// If [before] can be reused, an update (patch) is added to [updates]. |
- bool canReuseFunction( |
- Token diffToken, |
- PartialFunctionElement before, |
- PartialFunctionElement after) { |
- FunctionExpression node = |
- after.parseNode(compiler.parsing).asFunctionExpression(); |
- if (node == null) { |
- return cannotReuse(after, "Not a function expression: '$node'"); |
- } |
- Token last = after.endToken; |
- if (node.body != null) { |
- last = node.body.getBeginToken(); |
- } |
- if (before.isMalformed || |
- compiler.elementHasCompileTimeError(before) || |
- isTokenBetween(diffToken, after.beginToken, last)) { |
- removeFunction(before); |
- addFunction(after, before.enclosingElement); |
- if (compiler.mainFunction == before) { |
- return cannotReuse( |
- after, |
- "Unable to handle when signature of '${after.name}' changes"); |
- } |
- return allowSignatureChanged(before, after); |
- } |
- logVerbose('Simple modification of ${after} detected'); |
- if (!before.isInstanceMember) { |
- if (!allowNonInstanceMemberModified(after)) return false; |
- } |
- updates.add(new FunctionUpdate(compiler, before, after)); |
- return true; |
- } |
- |
- bool canReuseClass( |
- Token diffToken, |
- PartialClassElement before, |
- PartialClassElement after) { |
- ClassNode node = after.parseNode(compiler.parsing).asClassNode(); |
- if (node == null) { |
- return cannotReuse(after, "Not a ClassNode: '$node'"); |
- } |
- NodeList body = node.body; |
- if (body == null) { |
- return cannotReuse(after, "Class has no body."); |
- } |
- if (isTokenBetween(diffToken, node.beginToken, body.beginToken.next)) { |
- if (!allowClassHeaderModified(after)) return false; |
- logVerbose('Class header modified in ${after}'); |
- addClassUpdate(compiler, before, after); |
- before.forEachLocalMember((ElementX member) { |
- // TODO(ahe): Quadratic. |
- invalidateScopesAffectedBy(member, before); |
- }); |
- } else { |
- logVerbose('Simple modification of ${after} detected'); |
- } |
- return canReuseScopeContainerElement(before, after); |
- } |
- |
- /// Returns true if [token] is found between [first] (included) and [last] |
- /// (excluded). |
- bool isTokenBetween(Token token, Token first, Token last) { |
- Token current = first; |
- while (current != last && current.kind != EOF_TOKEN) { |
- if (current == token) { |
- return true; |
- } |
- current = current.next; |
- } |
- return false; |
- } |
- |
- bool unableToReuse( |
- Token diffToken, |
- PartialElement before, |
- PartialElement after) { |
- return cannotReuse( |
- after, |
- 'Unhandled change:' |
- ' ${before} (${before.runtimeType} -> ${after.runtimeType}).'); |
- } |
- |
- /// Apply the collected [updates]. Return a list of elements that needs to be |
- /// recompiled after applying the updates. |
- List<Element> applyUpdates() { |
- for (Update update in updates) { |
- update.captureState(); |
- } |
- if (_failedUpdates.isNotEmpty) { |
- throw new IncrementalCompilationFailed(_failedUpdates.join('\n\n')); |
- } |
- for (ElementX element in _elementsToInvalidate) { |
- compiler.forgetElement(element); |
- element.reuseElement(); |
- if (element.isFunction) { |
- replaceFunctionInBackend(element, element.enclosingElement); |
- } |
- } |
- List<Element> elementsToInvalidate = <Element>[]; |
- for (ElementX element in _elementsToInvalidate) { |
- if (!_removedElements.contains(element)) { |
- elementsToInvalidate.add(element); |
- } |
- } |
- for (Update update in updates) { |
- Element element = update.apply(backend); |
- if (!update.isRemoval) { |
- elementsToInvalidate.add(element); |
- } |
- if (update is FunctionUpdate) { |
- replaceFunctionInBackend(element, element.enclosingElement); |
- } |
- } |
- return elementsToInvalidate; |
- } |
- |
- void addClassUpdate( |
- Compiler compiler, |
- PartialClassElement before, |
- PartialClassElement after); |
- |
- void addAddedFunctionUpdate( |
- Compiler compiler, |
- PartialFunctionElement element, |
- /* ScopeContainerElement */ container); |
- |
- void addRemovedFunctionUpdate( |
- Compiler compiler, |
- PartialFunctionElement element); |
- |
- void addRemovedFieldUpdate( |
- Compiler compiler, |
- FieldElementX element); |
- |
- void addRemovedClassUpdate( |
- Compiler compiler, |
- PartialClassElement element); |
- |
- void addAddedFieldUpdate( |
- Compiler compiler, |
- FieldElementX element, |
- /* ScopeContainerElement */ container); |
- |
- void addAddedClassUpdate( |
- Compiler compiler, |
- PartialClassElement element, |
- LibraryElementX library); |
- |
- bool uriHasUpdate(Uri uri); |
- |
- bool allowClassHeaderModified(PartialClassElement after); |
- |
- bool allowSignatureChanged( |
- PartialFunctionElement before, |
- PartialFunctionElement after); |
- |
- bool allowNonInstanceMemberModified(PartialFunctionElement after); |
- |
- bool allowRemovedElement(PartialElement element); |
- |
- bool allowAddedElement(PartialElement element); |
-} |
- |
-/// Represents an update (aka patch) of [before] to [after]. We use the word |
-/// "update" to avoid confusion with the compiler feature of "patch" methods. |
-abstract class Update { |
- final Compiler compiler; |
- |
- PartialElement get before; |
- |
- PartialElement get after; |
- |
- Update(this.compiler); |
- |
- /// Applies the update to [before] and returns that element. |
- Element apply(IncrementalBackend backend); |
- |
- bool get isRemoval => false; |
- |
- /// Called before any patches are applied to capture any state that is needed |
- /// later. |
- void captureState() { |
- } |
-} |
- |
-/// Represents an update of a function element. |
-class FunctionUpdate extends Update with ReuseFunctionElement { |
- final PartialFunctionElement before; |
- |
- final PartialFunctionElement after; |
- |
- FunctionUpdate(Compiler compiler, this.before, this.after) |
- : super(compiler); |
- |
- PartialFunctionElement apply(IncrementalBackend backend) { |
- patchElement(); |
- reuseElement(); |
- return before; |
- } |
- |
- /// Destructively change the tokens in [before] to match those of [after]. |
- void patchElement() { |
- before.beginToken = after.beginToken; |
- before.endToken = after.endToken; |
- before.getOrSet = after.getOrSet; |
- } |
-} |
- |
-abstract class ReuseFunctionElement { |
- Compiler get compiler; |
- |
- PartialFunctionElement get before; |
- |
- /// Reset various caches and remove this element from the compiler's internal |
- /// state. |
- void reuseElement() { |
- compiler.forgetElement(before); |
- before.reuseElement(); |
- } |
-} |
- |
-abstract class RemovalUpdate extends Update { |
- ElementX get element; |
- |
- RemovalUpdate(Compiler compiler) |
- : super(compiler); |
- |
- bool get isRemoval => true; |
- |
- void removeFromEnclosing() { |
- // TODO(ahe): Need to recompute duplicated elements logic again. Simplest |
- // solution is probably to remove all elements from enclosing scope and add |
- // them back. |
- if (element.isTopLevel) { |
- removeFromLibrary(element.library); |
- } else { |
- removeFromEnclosingClass(element.enclosingClass); |
- } |
- } |
- |
- void removeFromEnclosingClass(PartialClassElement cls) { |
- cls.localMembersCache = null; |
- cls.localMembersReversed = cls.localMembersReversed.copyWithout(element); |
- cls.localScope.contents.remove(element.name); |
- } |
- |
- void removeFromLibrary(LibraryElementX library) { |
- library.localMembers = library.localMembers.copyWithout(element); |
- library.localScope.contents.remove(element.name); |
- } |
-} |
- |
-abstract class RemovedFunctionUpdate extends RemovalUpdate |
- with ReuseFunctionElement { |
- final PartialFunctionElement element; |
- |
- bool wasStateCaptured = false; |
- |
- RemovedFunctionUpdate(Compiler compiler, this.element) |
- : super(compiler); |
- |
- PartialFunctionElement get before => element; |
- |
- PartialFunctionElement get after => null; |
- |
- void captureState() { |
- if (wasStateCaptured) throw "captureState was called twice."; |
- wasStateCaptured = true; |
- } |
- |
- PartialFunctionElement apply(IncrementalBackend backend) { |
- if (!wasStateCaptured) throw "captureState must be called before apply."; |
- removeFromEnclosing(); |
- backend.removeFunction(element); |
- reuseElement(); |
- return null; |
- } |
-} |
- |
-abstract class RemovedClassUpdate extends RemovalUpdate { |
- final PartialClassElement element; |
- |
- bool wasStateCaptured = false; |
- |
- RemovedClassUpdate(Compiler compiler, this.element) |
- : super(compiler); |
- |
- PartialClassElement get before => element; |
- |
- PartialClassElement get after => null; |
- |
- void captureState() { |
- if (wasStateCaptured) throw "captureState was called twice."; |
- wasStateCaptured = true; |
- } |
- |
- PartialClassElement apply(IncrementalBackend backend) { |
- if (!wasStateCaptured) { |
- throw new StateError("captureState must be called before apply."); |
- } |
- |
- removeFromEnclosing(); |
- |
- element.forEachLocalMember((ElementX member) { |
- compiler.forgetElement(member); |
- member.reuseElement(); |
- }); |
- |
- compiler.forgetElement(element); |
- element.reuseElement(); |
- |
- return null; |
- } |
-} |
- |
-abstract class RemovedFieldUpdate extends RemovalUpdate { |
- final FieldElementX element; |
- |
- bool wasStateCaptured = false; |
- |
- RemovedFieldUpdate(Compiler compiler, this.element) |
- : super(compiler); |
- |
- PartialFieldList get before => element.declarationSite; |
- |
- PartialFieldList get after => null; |
- |
- void captureState() { |
- if (wasStateCaptured) throw "captureState was called twice."; |
- wasStateCaptured = true; |
- } |
- |
- FieldElementX apply(IncrementalBackend backend) { |
- if (!wasStateCaptured) { |
- throw new StateError("captureState must be called before apply."); |
- } |
- |
- removeFromEnclosing(); |
- backend.removeField(element); |
- |
- return element; |
- } |
-} |
- |
-abstract class AddedFunctionUpdate extends Update { |
- final PartialFunctionElement element; |
- |
- final /* ScopeContainerElement */ container; |
- |
- AddedFunctionUpdate(Compiler compiler, this.element, this.container) |
- : super(compiler) { |
- if (container == null) { |
- throw "container is null"; |
- } |
- } |
- |
- PartialFunctionElement get before => null; |
- |
- PartialFunctionElement get after => element; |
- |
- PartialFunctionElement apply(IncrementalBackend backend) { |
- Element enclosing = container; |
- if (enclosing.isLibrary) { |
- // TODO(ahe): Reuse compilation unit of element instead? |
- enclosing = enclosing.compilationUnit; |
- } |
- PartialFunctionElement copy = element.copyWithEnclosing(enclosing); |
- container.addMember(copy, compiler.reporter); |
- return copy; |
- } |
-} |
- |
-abstract class AddedClassUpdate extends Update { |
- final PartialClassElement element; |
- |
- final LibraryElementX library; |
- |
- AddedClassUpdate(Compiler compiler, this.element, this.library) |
- : super(compiler); |
- |
- PartialClassElement get before => null; |
- |
- PartialClassElement get after => element; |
- |
- PartialClassElement apply(IncrementalBackend backend) { |
- // TODO(ahe): Reuse compilation unit of element instead? |
- CompilationUnitElementX compilationUnit = library.compilationUnit; |
- PartialClassElement copy = element.copyWithEnclosing(compilationUnit); |
- compilationUnit.addMember(copy, compiler.reporter); |
- return copy; |
- } |
-} |
- |
-abstract class AddedFieldUpdate extends Update { |
- final FieldElementX element; |
- |
- final /* ScopeContainerElement */ container; |
- |
- AddedFieldUpdate(Compiler compiler, this.element, this.container) |
- : super(compiler); |
- |
- PartialFieldList get before => null; |
- |
- PartialFieldList get after => element.declarationSite; |
- |
- FieldElementX apply(IncrementalBackend backend) { |
- Element enclosing = container; |
- if (enclosing.isLibrary) { |
- // TODO(ahe): Reuse compilation unit of element instead? |
- enclosing = enclosing.compilationUnit; |
- } |
- FieldElementX copy = element.copyWithEnclosing(enclosing); |
- container.addMember(copy, compiler.reporter); |
- return copy; |
- } |
-} |
- |
-abstract class ClassUpdate extends Update { |
- final PartialClassElement before; |
- |
- final PartialClassElement after; |
- |
- ClassUpdate(Compiler compiler, this.before, this.after) |
- : super(compiler); |
- |
- PartialClassElement apply(IncrementalBackend backend) { |
- patchElement(); |
- reuseElement(); |
- return before; |
- } |
- |
- /// Destructively change the tokens in [before] to match those of [after]. |
- void patchElement() { |
- before.cachedNode = after.cachedNode; |
- before.beginToken = after.beginToken; |
- before.endToken = after.endToken; |
- } |
- |
- void reuseElement() { |
- before.supertype = null; |
- before.interfaces = null; |
- before.supertypeLoadState = STATE_NOT_STARTED; |
- before.resolutionState = STATE_NOT_STARTED; |
- before.isProxy = false; |
- before.hasIncompleteHierarchy = false; |
- before.backendMembers = const Link<Element>(); |
- before.allSupertypesAndSelf = null; |
- } |
-} |
- |
-/// Returns all qualified names in [element] with less than four identifiers. A |
-/// qualified name is an identifier followed by a sequence of dots and |
-/// identifiers, for example, "x", and "x.y.z". But not "x.y.z.w" ("w" is the |
-/// fourth identifier). |
-/// |
-/// The longest possible name that can be resolved is three identifiers, for |
-/// example, "prefix.MyClass.staticMethod". Since four or more identifiers |
-/// cannot resolve to anything statically, they're not included in the returned |
-/// value of this method. |
-Set<String> qualifiedNamesIn(PartialElement element) { |
- Token beginToken = element.beginToken; |
- Token endToken = element.endToken; |
- Token token = beginToken; |
- if (element is PartialClassElement) { |
- ClassNode node = element.cachedNode; |
- if (node != null) { |
- NodeList body = node.body; |
- if (body != null) { |
- endToken = body.beginToken; |
- } |
- } |
- } |
- Set<String> names = new Set<String>(); |
- do { |
- if (token.isIdentifier()) { |
- String name = token.value; |
- // [name] is a single "identifier". |
- names.add(name); |
- if (identical('.', token.next.stringValue) && |
- token.next.next.isIdentifier()) { |
- token = token.next.next; |
- name += '.${token.value}'; |
- // [name] is "idenfifier.idenfifier". |
- names.add(name); |
- |
- if (identical('.', token.next.stringValue) && |
- token.next.next.isIdentifier()) { |
- token = token.next.next; |
- name += '.${token.value}'; |
- // [name] is "idenfifier.idenfifier.idenfifier". |
- names.add(name); |
- |
- while (identical('.', token.next.stringValue) && |
- token.next.next.isIdentifier()) { |
- // Skip remaining identifiers, they cannot statically resolve to |
- // anything, and must be dynamic sends. |
- token = token.next.next; |
- } |
- } |
- } |
- } |
- token = token.next; |
- } while (token.kind != EOF_TOKEN && token != endToken); |
- return names; |
-} |
- |
-/// Returns true if one of the qualified names in names (as computed by |
-/// [qualifiedNamesIn]) could be a static reference to [element]. |
-bool canNamesResolveStaticallyTo( |
- Set<String> names, |
- Element element, |
- /* ScopeContainerElement */ container) { |
- if (names.contains(element.name)) return true; |
- if (container != null && container.isClass) { |
- // [names] contains C.m, where C is the name of [container], and m is the |
- // name of [element]. |
- if (names.contains("${container.name}.${element.name}")) return true; |
- } |
- // TODO(ahe): Check for prefixes as well. |
- return false; |
-} |
- |
-DeclarationSite declarationSite(Element element) { |
- return element is ElementX ? element.declarationSite : null; |
-} |