Include reasons for failing in StateError.

R=johnniwinther@google.com

Review URL: https://codereview.chromium.org//705253002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@41625 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
ahe@google.com 2014-11-10 10:51:56 +00:00
parent 583f408260
commit 1441fe2d76
3 changed files with 73 additions and 51 deletions

View file

@ -64,6 +64,19 @@ typedef bool Reuser(
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';
}
}
// TODO(ahe): Generalize this class. For now only works for Compiler.mainApp,
// and only if that library has exactly one compilation unit.
class LibraryUpdater {
@ -79,14 +92,10 @@ class LibraryUpdater {
// changed.
final Uri uri;
// When [true], updates must be applied (using [applyUpdates]) before the
// [compiler]'s state correctly reflects the updated program.
bool hasPendingUpdates = false;
bool onlySimpleUpdates = true;
final List<Update> updates = <Update>[];
final List<FailedUpdate> _failedUpdates = <FailedUpdate>[];
LibraryUpdater(
this.compiler,
this.inputProvider,
@ -94,6 +103,12 @@ class LibraryUpdater {
this.logTime,
this.logVerbose);
/// When [true], updates must be applied (using [applyUpdates]) before the
/// [compiler]'s state correctly reflects the updated program.
bool get hasPendingUpdates => !updates.isEmpty;
bool get failed => !_failedUpdates.isEmpty;
JavaScriptBackend get backend => compiler.backend;
Namer get namer => backend.namer;
@ -141,6 +156,12 @@ class LibraryUpdater {
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) {
@ -149,23 +170,18 @@ class LibraryUpdater {
for (Difference difference in differences) {
logTime('Looking at difference: $difference');
if (difference.before == null || difference.after == null) {
logVerbose('Scope changed in $difference');
// Scope changed, don't reuse library.
onlySimpleUpdates = false;
return false;
cannotReuse(difference, "Can't reuse; Scope changed.");
continue;
}
Token diffToken = difference.token;
if (diffToken == null) {
logVerbose('No token stored in difference.');
onlySimpleUpdates = false;
return false;
cannotReuse(difference, "No difference token.");
continue;
}
if (difference.after is! PartialElement &&
difference.before is! PartialElement) {
logVerbose('Not a PartialElement: $difference');
// Don't know how to recompile element.
onlySimpleUpdates = false;
return false;
cannotReuse(difference, "Don't know how to recompile.");
continue;
}
PartialElement before = difference.before;
PartialElement after = difference.after;
@ -178,16 +194,15 @@ class LibraryUpdater {
after is PartialClassElement) {
reuser = canReuseClass;
} else {
reuser = cannotReuse;
reuser = unableToReuse;
}
if (!reuser(diffToken, before, after)) {
onlySimpleUpdates = false;
return false;
assert(!_failedUpdates.isEmpty);
continue;
}
}
hasPendingUpdates = true;
return true;
return _failedUpdates.isEmpty;
}
/// Returns true if function [before] can be reused to reflect the changes in
@ -201,16 +216,14 @@ class LibraryUpdater {
FunctionExpression node =
after.parseNode(compiler).asFunctionExpression();
if (node == null) {
logVerbose('Not a function expression.');
return false;
return cannotReuse(after, "Not a function expression: '$node'");
}
Token last = after.endToken;
if (node.body != null) {
last = node.body.getBeginToken();
}
if (isTokenBetween(diffToken, after.beginToken, last)) {
logVerbose('Signature changed.');
return false;
return cannotReuse(after, 'Signature changed.');
}
logVerbose('Simple modification of ${after} detected');
updates.add(new FunctionUpdate(compiler, before, after));
@ -223,17 +236,14 @@ class LibraryUpdater {
PartialClassElement after) {
ClassNode node = after.parseNode(compiler).asClassNode();
if (node == null) {
logVerbose('Not a ClassNode.');
return false;
return cannotReuse(after, "Not a ClassNode: '$node'");
}
NodeList body = node.body;
if (body == null) {
logVerbose('Class has no body.');
return false;
return cannotReuse(after, "Class has no body.");
}
if (isTokenBetween(diffToken, node.beginToken, body.beginToken)) {
logVerbose('Class header changed.');
return false;
return cannotReuse(after, "Class header changed.");
}
logVerbose('Simple modification of ${after} detected');
return canReuseScopeContainerElement(before, after);
@ -250,19 +260,20 @@ class LibraryUpdater {
return false;
}
bool cannotReuse(
bool unableToReuse(
Token diffToken,
PartialElement before,
PartialElement after) {
logVerbose(
return cannotReuse(
after,
'Unhandled change:'
' ${before} (${before.runtimeType} -> ${after.runtimeType}).');
return false;
}
List<Element> applyUpdates() {
if (!onlySimpleUpdates) {
throw new StateError("Can't compute update.");
if (!_failedUpdates.isEmpty) {
throw new StateError(
"Can't compute update.\n\n${_failedUpdates.join('\n\n')}");
}
return updates.map((Update update) => update.apply()).toList();
}

View file

@ -397,22 +397,35 @@ Future<Element> runPoi(
options.add('--minify');
}
LibraryUpdater updater =
new LibraryUpdater(
cachedCompiler, inputProvider, script, printWallClock, printVerbose);
LibraryUpdater updater;
Future<bool> reuseLibrary(LibraryElement library) {
return poiTask.measure(() => updater.reuseLibrary(library));
}
return reuseCompiler(
diagnosticHandler: handler,
inputProvider: inputProvider,
options: options,
cachedCompiler: cachedCompiler,
libraryRoot: libraryRoot,
packageRoot: packageRoot,
packagesAreImmutable: true,
reuseLibrary: reuseLibrary).then((Compiler newCompiler) {
Future<Compiler> invokeReuseCompiler() {
updater = new LibraryUpdater(
cachedCompiler, inputProvider, script, printWallClock, printVerbose);
return reuseCompiler(
diagnosticHandler: handler,
inputProvider: inputProvider,
options: options,
cachedCompiler: cachedCompiler,
libraryRoot: libraryRoot,
packageRoot: packageRoot,
packagesAreImmutable: true,
reuseLibrary: reuseLibrary);
}
return invokeReuseCompiler().then((Compiler newCompiler) {
// TODO(ahe): Move this "then" block to [reuseCompiler].
if (updater.failed) {
cachedCompiler = null;
return invokeReuseCompiler();
} else {
return newCompiler;
}
}).then((Compiler newCompiler) {
if (!isCompiler) {
newCompiler.enqueuerFilter = new ScriptOnlyFilter(script);
}

View file

@ -40,8 +40,6 @@ class LibraryUpdaterTestCase extends CompilerTestCase {
updater.canReuseLibrary(library, UTF8.encode(newSource));
Expect.equals(expectedCanReuse, actualCanReuse);
Expect.equals(expectedCanReuse, updater.hasPendingUpdates);
Expect.setEquals(
expectedUpdates.toSet(),
updater.updates.map((Update update) => update.before.name).toSet());