| OLD | NEW |
| (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 dart2js_incremental.library_updater; | |
| 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/elements/elements.dart' show | |
| 19 ClassElement, | |
| 20 CompilationUnitElement, | |
| 21 Element, | |
| 22 FunctionElement, | |
| 23 LibraryElement, | |
| 24 STATE_NOT_STARTED, | |
| 25 ScopeContainerElement; | |
| 26 | |
| 27 import 'package:compiler/src/enqueue.dart' show | |
| 28 EnqueueTask; | |
| 29 | |
| 30 import 'package:compiler/src/parser/listener.dart' show | |
| 31 Listener; | |
| 32 | |
| 33 import 'package:compiler/src/parser/node_listener.dart' show | |
| 34 NodeListener; | |
| 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/parser/parser.dart' show | |
| 43 Parser; | |
| 44 | |
| 45 import 'package:compiler/src/scanner/scanner.dart' show | |
| 46 Scanner; | |
| 47 | |
| 48 import 'package:compiler/src/tokens/token.dart' show | |
| 49 Token; | |
| 50 | |
| 51 import 'package:compiler/src/tokens/token_constants.dart' show | |
| 52 EOF_TOKEN; | |
| 53 | |
| 54 import 'package:compiler/src/script.dart' show | |
| 55 Script; | |
| 56 | |
| 57 import 'package:compiler/src/io/source_file.dart' show | |
| 58 CachingUtf8BytesSourceFile, | |
| 59 SourceFile, | |
| 60 StringSourceFile; | |
| 61 | |
| 62 import 'package:compiler/src/tree/tree.dart' show | |
| 63 ClassNode, | |
| 64 FunctionExpression, | |
| 65 LibraryTag, | |
| 66 NodeList, | |
| 67 Part, | |
| 68 StringNode, | |
| 69 unparse; | |
| 70 | |
| 71 import 'package:compiler/src/js/js.dart' show | |
| 72 js; | |
| 73 | |
| 74 import 'package:compiler/src/js/js.dart' as jsAst; | |
| 75 | |
| 76 import 'package:compiler/src/js_emitter/js_emitter.dart' show | |
| 77 CodeEmitterTask, | |
| 78 computeMixinClass; | |
| 79 | |
| 80 import 'package:compiler/src/js_emitter/full_emitter/emitter.dart' | |
| 81 as full show Emitter; | |
| 82 | |
| 83 import 'package:compiler/src/js_emitter/model.dart' show | |
| 84 Class, | |
| 85 Method; | |
| 86 | |
| 87 import 'package:compiler/src/js_emitter/program_builder/program_builder.dart' | |
| 88 show ProgramBuilder; | |
| 89 | |
| 90 import 'package:js_runtime/shared/embedded_names.dart' | |
| 91 as embeddedNames; | |
| 92 | |
| 93 import 'package:compiler/src/js_backend/js_backend.dart' show | |
| 94 JavaScriptBackend, | |
| 95 Namer; | |
| 96 | |
| 97 import 'package:compiler/src/util/util.dart' show | |
| 98 Link, | |
| 99 LinkBuilder; | |
| 100 | |
| 101 import 'package:compiler/src/elements/modelx.dart' show | |
| 102 ClassElementX, | |
| 103 CompilationUnitElementX, | |
| 104 DeclarationSite, | |
| 105 ElementX, | |
| 106 FieldElementX, | |
| 107 LibraryElementX; | |
| 108 | |
| 109 import 'package:compiler/src/universe/selector.dart' show | |
| 110 Selector; | |
| 111 | |
| 112 import 'package:compiler/src/constants/values.dart' show | |
| 113 ConstantValue; | |
| 114 | |
| 115 import 'package:compiler/src/library_loader.dart' show | |
| 116 TagState; | |
| 117 | |
| 118 import 'diff.dart' show | |
| 119 Difference, | |
| 120 computeDifference; | |
| 121 | |
| 122 import 'dart2js_incremental.dart' show | |
| 123 IncrementalCompilationFailed, | |
| 124 IncrementalCompiler; | |
| 125 | |
| 126 typedef void Logger(message); | |
| 127 | |
| 128 typedef bool Reuser( | |
| 129 Token diffToken, | |
| 130 PartialElement before, | |
| 131 PartialElement after); | |
| 132 | |
| 133 class FailedUpdate { | |
| 134 /// Either an [Element] or a [Difference]. | |
| 135 final context; | |
| 136 final String message; | |
| 137 | |
| 138 FailedUpdate(this.context, this.message); | |
| 139 | |
| 140 String toString() { | |
| 141 if (context == null) return '$message'; | |
| 142 return 'In $context:\n $message'; | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 abstract class _IncrementalCompilerContext { | |
| 147 IncrementalCompiler incrementalCompiler; | |
| 148 | |
| 149 Set<ClassElementX> _emittedClasses; | |
| 150 | |
| 151 Set<ClassElementX> _directlyInstantiatedClasses; | |
| 152 | |
| 153 Set<ConstantValue> _compiledConstants; | |
| 154 } | |
| 155 | |
| 156 class IncrementalCompilerContext extends _IncrementalCompilerContext { | |
| 157 final Set<Uri> _uriWithUpdates = new Set<Uri>(); | |
| 158 | |
| 159 void set incrementalCompiler(IncrementalCompiler value) { | |
| 160 if (super.incrementalCompiler != null) { | |
| 161 throw new StateError("Can't set [incrementalCompiler] more than once."); | |
| 162 } | |
| 163 super.incrementalCompiler = value; | |
| 164 } | |
| 165 | |
| 166 void registerUriWithUpdates(Iterable<Uri> uris) { | |
| 167 _uriWithUpdates.addAll(uris); | |
| 168 } | |
| 169 | |
| 170 void _captureState(Compiler compiler) { | |
| 171 JavaScriptBackend backend = compiler.backend; | |
| 172 Set neededClasses = backend.emitter.neededClasses; | |
| 173 if (neededClasses == null) { | |
| 174 neededClasses = new Set(); | |
| 175 } | |
| 176 _emittedClasses = new Set.from(neededClasses); | |
| 177 | |
| 178 _directlyInstantiatedClasses = | |
| 179 new Set.from(compiler.codegenWorld.directlyInstantiatedClasses); | |
| 180 | |
| 181 // This breaks constant tracking of the incremental compiler. It would need | |
| 182 // to capture the emitted constants. | |
| 183 List<ConstantValue> constants = null; | |
| 184 if (constants == null) constants = <ConstantValue>[]; | |
| 185 _compiledConstants = new Set<ConstantValue>.identity()..addAll(constants); | |
| 186 } | |
| 187 | |
| 188 bool _uriHasUpdate(Uri uri) => _uriWithUpdates.contains(uri); | |
| 189 } | |
| 190 | |
| 191 class LibraryUpdater extends JsFeatures { | |
| 192 final Compiler compiler; | |
| 193 | |
| 194 final api.CompilerInputProvider inputProvider; | |
| 195 | |
| 196 final Logger logTime; | |
| 197 | |
| 198 final Logger logVerbose; | |
| 199 | |
| 200 final List<Update> updates = <Update>[]; | |
| 201 | |
| 202 final List<FailedUpdate> _failedUpdates = <FailedUpdate>[]; | |
| 203 | |
| 204 final Set<ElementX> _elementsToInvalidate = new Set<ElementX>(); | |
| 205 | |
| 206 final Set<ElementX> _removedElements = new Set<ElementX>(); | |
| 207 | |
| 208 final Set<ClassElementX> _classesWithSchemaChanges = | |
| 209 new Set<ClassElementX>(); | |
| 210 | |
| 211 final IncrementalCompilerContext _context; | |
| 212 | |
| 213 final Map<Uri, Future> _sources = <Uri, Future>{}; | |
| 214 | |
| 215 /// Cached tokens of entry compilation units. | |
| 216 final Map<LibraryElementX, Token> _entryUnitTokens = | |
| 217 <LibraryElementX, Token>{}; | |
| 218 | |
| 219 /// Cached source files for entry compilation units. | |
| 220 final Map<LibraryElementX, SourceFile> _entrySourceFiles = | |
| 221 <LibraryElementX, SourceFile>{}; | |
| 222 | |
| 223 bool _hasComputedNeeds = false; | |
| 224 | |
| 225 bool _hasCapturedCompilerState = false; | |
| 226 | |
| 227 LibraryUpdater( | |
| 228 this.compiler, | |
| 229 this.inputProvider, | |
| 230 this.logTime, | |
| 231 this.logVerbose, | |
| 232 this._context) { | |
| 233 // TODO(ahe): Would like to remove this from the constructor. However, the | |
| 234 // state must be captured before calling [reuseCompiler]. | |
| 235 // Proper solution might be: [reuseCompiler] should not clear the sets that | |
| 236 // are captured in [IncrementalCompilerContext._captureState]. | |
| 237 _ensureCompilerStateCaptured(); | |
| 238 } | |
| 239 | |
| 240 /// Returns the classes emitted by [compiler]. | |
| 241 Set<ClassElementX> get _emittedClasses => _context._emittedClasses; | |
| 242 | |
| 243 /// Returns the directly instantantiated classes seen by [compiler] (this | |
| 244 /// includes interfaces and may be different from [_emittedClasses] that only | |
| 245 /// includes interfaces used in type tests). | |
| 246 Set<ClassElementX> get _directlyInstantiatedClasses { | |
| 247 return _context._directlyInstantiatedClasses; | |
| 248 } | |
| 249 | |
| 250 /// Returns the constants emitted by [compiler]. | |
| 251 Set<ConstantValue> get _compiledConstants => _context._compiledConstants; | |
| 252 | |
| 253 /// When [true], updates must be applied (using [applyUpdates]) before the | |
| 254 /// [compiler]'s state correctly reflects the updated program. | |
| 255 bool get hasPendingUpdates => !updates.isEmpty; | |
| 256 | |
| 257 bool get failed => !_failedUpdates.isEmpty; | |
| 258 | |
| 259 /// Used as tear-off passed to [LibraryLoaderTask.resetAsync]. | |
| 260 Future<bool> reuseLibrary(LibraryElement library) { | |
| 261 _ensureCompilerStateCaptured(); | |
| 262 assert(compiler != null); | |
| 263 if (library.isPlatformLibrary) { | |
| 264 logTime('Reusing $library (assumed read-only).'); | |
| 265 return new Future.value(true); | |
| 266 } | |
| 267 return _haveTagsChanged(library).then((bool haveTagsChanged) { | |
| 268 if (haveTagsChanged) { | |
| 269 cannotReuse( | |
| 270 library, | |
| 271 "Changes to library, import, export, or part declarations not" | |
| 272 " supported."); | |
| 273 return true; | |
| 274 } | |
| 275 | |
| 276 bool isChanged = false; | |
| 277 List<Future<Script>> futureScripts = <Future<Script>>[]; | |
| 278 | |
| 279 for (CompilationUnitElementX unit in library.compilationUnits) { | |
| 280 Uri uri = unit.script.resourceUri; | |
| 281 if (_context._uriHasUpdate(uri)) { | |
| 282 isChanged = true; | |
| 283 futureScripts.add(_updatedScript(unit.script, library)); | |
| 284 } else { | |
| 285 futureScripts.add(new Future.value(unit.script)); | |
| 286 } | |
| 287 } | |
| 288 | |
| 289 if (!isChanged) { | |
| 290 logTime("Reusing $library, source didn't change."); | |
| 291 return true; | |
| 292 } | |
| 293 | |
| 294 return Future.wait(futureScripts).then( | |
| 295 (List<Script> scripts) => canReuseLibrary(library, scripts)); | |
| 296 }).whenComplete(() => _cleanUp(library)); | |
| 297 } | |
| 298 | |
| 299 void _cleanUp(LibraryElementX library) { | |
| 300 _entryUnitTokens.remove(library); | |
| 301 _entrySourceFiles.remove(library); | |
| 302 } | |
| 303 | |
| 304 Future<Script> _updatedScript(Script before, LibraryElementX library) { | |
| 305 if (before == library.entryCompilationUnit.script && | |
| 306 _entrySourceFiles.containsKey(library)) { | |
| 307 return new Future.value(before.copyWithFile(_entrySourceFiles[library])); | |
| 308 } | |
| 309 | |
| 310 return _readUri(before.resourceUri).then((bytes) { | |
| 311 Uri uri = before.file.uri; | |
| 312 String filename = before.file.filename; | |
| 313 SourceFile sourceFile = bytes is String | |
| 314 ? new StringSourceFile(uri, filename, bytes) | |
| 315 : new CachingUtf8BytesSourceFile(uri, filename, bytes); | |
| 316 return before.copyWithFile(sourceFile); | |
| 317 }); | |
| 318 } | |
| 319 | |
| 320 Future<bool> _haveTagsChanged(LibraryElement library) { | |
| 321 Script before = library.entryCompilationUnit.script; | |
| 322 if (!_context._uriHasUpdate(before.resourceUri)) { | |
| 323 // The entry compilation unit hasn't been updated. So the tags aren't | |
| 324 // changed. | |
| 325 return new Future<bool>.value(false); | |
| 326 } | |
| 327 | |
| 328 return _updatedScript(before, library).then((Script script) { | |
| 329 _entrySourceFiles[library] = script.file; | |
| 330 Token token = new Scanner(_entrySourceFiles[library]).tokenize(); | |
| 331 _entryUnitTokens[library] = token; | |
| 332 // Using two parsers to only create the nodes we want ([LibraryTag]). | |
| 333 Parser parser = new Parser(new Listener(), compiler.options); | |
| 334 NodeListener listener = new NodeListener( | |
| 335 compiler, library.entryCompilationUnit); | |
| 336 Parser nodeParser = new Parser(listener, compiler.options); | |
| 337 Iterator<LibraryTag> tags = library.tags.iterator; | |
| 338 while (token.kind != EOF_TOKEN) { | |
| 339 token = parser.parseMetadataStar(token); | |
| 340 if (parser.optional('library', token) || | |
| 341 parser.optional('import', token) || | |
| 342 parser.optional('export', token) || | |
| 343 parser.optional('part', token)) { | |
| 344 if (!tags.moveNext()) return true; | |
| 345 token = nodeParser.parseTopLevelDeclaration(token); | |
| 346 LibraryTag tag = listener.popNode(); | |
| 347 assert(listener.nodes.isEmpty); | |
| 348 if (unparse(tags.current) != unparse(tag)) { | |
| 349 return true; | |
| 350 } | |
| 351 } else { | |
| 352 break; | |
| 353 } | |
| 354 } | |
| 355 return tags.moveNext(); | |
| 356 }); | |
| 357 } | |
| 358 | |
| 359 Future _readUri(Uri uri) { | |
| 360 return _sources.putIfAbsent(uri, () => inputProvider(uri)); | |
| 361 } | |
| 362 | |
| 363 void _ensureCompilerStateCaptured() { | |
| 364 // TODO(ahe): [compiler] shouldn't be null, remove the following line. | |
| 365 if (compiler == null) return; | |
| 366 | |
| 367 if (_hasCapturedCompilerState) return; | |
| 368 _context._captureState(compiler); | |
| 369 _hasCapturedCompilerState = true; | |
| 370 } | |
| 371 | |
| 372 /// Returns true if [library] can be reused. | |
| 373 /// | |
| 374 /// This methods also computes the [updates] (patches) needed to have | |
| 375 /// [library] reflect the modifications in [scripts]. | |
| 376 bool canReuseLibrary(LibraryElement library, List<Script> scripts) { | |
| 377 logTime('Attempting to reuse ${library}.'); | |
| 378 | |
| 379 Uri entryUri = library.entryCompilationUnit.script.resourceUri; | |
| 380 Script entryScript = | |
| 381 scripts.singleWhere((Script script) => script.resourceUri == entryUri); | |
| 382 LibraryElement newLibrary = | |
| 383 new LibraryElementX(entryScript, library.canonicalUri); | |
| 384 if (_entryUnitTokens.containsKey(library)) { | |
| 385 compiler.dietParser.dietParse( | |
| 386 newLibrary.entryCompilationUnit, _entryUnitTokens[library]); | |
| 387 } else { | |
| 388 compiler.scanner.scanLibrary(newLibrary); | |
| 389 } | |
| 390 | |
| 391 TagState tagState = new TagState(); | |
| 392 for (LibraryTag tag in newLibrary.tags) { | |
| 393 if (tag.isImport) { | |
| 394 tagState.checkTag(TagState.IMPORT_OR_EXPORT, tag, compiler); | |
| 395 } else if (tag.isExport) { | |
| 396 tagState.checkTag(TagState.IMPORT_OR_EXPORT, tag, compiler); | |
| 397 } else if (tag.isLibraryName) { | |
| 398 tagState.checkTag(TagState.LIBRARY, tag, compiler); | |
| 399 if (newLibrary.libraryTag == null) { | |
| 400 // Use the first if there are multiple (which is reported as an | |
| 401 // error in [TagState.checkTag]). | |
| 402 newLibrary.libraryTag = tag; | |
| 403 } | |
| 404 } else if (tag.isPart) { | |
| 405 tagState.checkTag(TagState.PART, tag, compiler); | |
| 406 } | |
| 407 } | |
| 408 | |
| 409 // TODO(ahe): Process tags using TagState, not | |
| 410 // LibraryLoaderTask.processLibraryTags. | |
| 411 Link<CompilationUnitElement> units = library.compilationUnits; | |
| 412 for (Script script in scripts) { | |
| 413 CompilationUnitElementX unit = units.head; | |
| 414 units = units.tail; | |
| 415 if (script != entryScript) { | |
| 416 // TODO(ahe): Copied from library_loader. | |
| 417 CompilationUnitElement newUnit = | |
| 418 new CompilationUnitElementX(script, newLibrary); | |
| 419 compiler.withCurrentElement(newUnit, () { | |
| 420 compiler.scanner.scan(newUnit); | |
| 421 if (unit.partTag == null) { | |
| 422 compiler.reportError(unit, MessageKind.MISSING_PART_OF_TAG); | |
| 423 } | |
| 424 }); | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 logTime('New library synthesized.'); | |
| 429 return canReuseScopeContainerElement(library, newLibrary); | |
| 430 } | |
| 431 | |
| 432 bool cannotReuse(context, String message) { | |
| 433 _failedUpdates.add(new FailedUpdate(context, message)); | |
| 434 logVerbose(message); | |
| 435 return false; | |
| 436 } | |
| 437 | |
| 438 bool canReuseScopeContainerElement( | |
| 439 ScopeContainerElement element, | |
| 440 ScopeContainerElement newElement) { | |
| 441 List<Difference> differences = computeDifference(element, newElement); | |
| 442 logTime('Differences computed.'); | |
| 443 for (Difference difference in differences) { | |
| 444 logTime('Looking at difference: $difference'); | |
| 445 | |
| 446 if (difference.before == null && difference.after is PartialElement) { | |
| 447 canReuseAddedElement(difference.after, element, newElement); | |
| 448 continue; | |
| 449 } | |
| 450 if (difference.after == null && difference.before is PartialElement) { | |
| 451 canReuseRemovedElement(difference.before, element); | |
| 452 continue; | |
| 453 } | |
| 454 Token diffToken = difference.token; | |
| 455 if (diffToken == null) { | |
| 456 cannotReuse(difference, "No difference token."); | |
| 457 continue; | |
| 458 } | |
| 459 if (difference.after is! PartialElement && | |
| 460 difference.before is! PartialElement) { | |
| 461 cannotReuse(difference, "Don't know how to recompile."); | |
| 462 continue; | |
| 463 } | |
| 464 PartialElement before = difference.before; | |
| 465 PartialElement after = difference.after; | |
| 466 | |
| 467 Reuser reuser; | |
| 468 | |
| 469 if (before is PartialFunctionElement && after is PartialFunctionElement) { | |
| 470 reuser = canReuseFunction; | |
| 471 } else if (before is PartialClassElement && | |
| 472 after is PartialClassElement) { | |
| 473 reuser = canReuseClass; | |
| 474 } else { | |
| 475 reuser = unableToReuse; | |
| 476 } | |
| 477 if (!reuser(diffToken, before, after)) { | |
| 478 assert(!_failedUpdates.isEmpty); | |
| 479 continue; | |
| 480 } | |
| 481 } | |
| 482 | |
| 483 return _failedUpdates.isEmpty; | |
| 484 } | |
| 485 | |
| 486 bool canReuseAddedElement( | |
| 487 PartialElement element, | |
| 488 ScopeContainerElement container, | |
| 489 ScopeContainerElement syntheticContainer) { | |
| 490 if (element is PartialFunctionElement) { | |
| 491 addFunction(element, container); | |
| 492 return true; | |
| 493 } else if (element is PartialClassElement) { | |
| 494 addClass(element, container); | |
| 495 return true; | |
| 496 } else if (element is PartialFieldList) { | |
| 497 addFields(element, container, syntheticContainer); | |
| 498 return true; | |
| 499 } | |
| 500 return cannotReuse(element, "Adding ${element.runtimeType} not supported."); | |
| 501 } | |
| 502 | |
| 503 void addFunction( | |
| 504 PartialFunctionElement element, | |
| 505 /* ScopeContainerElement */ container) { | |
| 506 invalidateScopesAffectedBy(element, container); | |
| 507 | |
| 508 updates.add(new AddedFunctionUpdate(compiler, element, container)); | |
| 509 } | |
| 510 | |
| 511 void addClass( | |
| 512 PartialClassElement element, | |
| 513 LibraryElementX library) { | |
| 514 invalidateScopesAffectedBy(element, library); | |
| 515 | |
| 516 updates.add(new AddedClassUpdate(compiler, element, library)); | |
| 517 } | |
| 518 | |
| 519 /// Called when a field in [definition] has changed. | |
| 520 /// | |
| 521 /// There's no direct link from a [PartialFieldList] to its implied | |
| 522 /// [FieldElementX], so instead we use [syntheticContainer], the (synthetic) | |
| 523 /// container created by [canReuseLibrary], or [canReuseClass] (through | |
| 524 /// [PartialClassElement.parseNode]). This container is scanned looking for | |
| 525 /// fields whose declaration site is [definition]. | |
| 526 // TODO(ahe): It would be nice if [computeDifference] returned this | |
| 527 // information directly. | |
| 528 void addFields( | |
| 529 PartialFieldList definition, | |
| 530 ScopeContainerElement container, | |
| 531 ScopeContainerElement syntheticContainer) { | |
| 532 List<FieldElementX> fields = <FieldElementX>[]; | |
| 533 syntheticContainer.forEachLocalMember((ElementX member) { | |
| 534 if (member.declarationSite == definition) { | |
| 535 fields.add(member); | |
| 536 } | |
| 537 }); | |
| 538 for (FieldElementX field in fields) { | |
| 539 // TODO(ahe): This only works when there's one field per | |
| 540 // PartialFieldList. | |
| 541 addField(field, container); | |
| 542 } | |
| 543 } | |
| 544 | |
| 545 void addField(FieldElementX element, ScopeContainerElement container) { | |
| 546 invalidateScopesAffectedBy(element, container); | |
| 547 if (element.isInstanceMember) { | |
| 548 _classesWithSchemaChanges.add(container); | |
| 549 } | |
| 550 updates.add(new AddedFieldUpdate(compiler, element, container)); | |
| 551 } | |
| 552 | |
| 553 bool canReuseRemovedElement( | |
| 554 PartialElement element, | |
| 555 ScopeContainerElement container) { | |
| 556 if (element is PartialFunctionElement) { | |
| 557 removeFunction(element); | |
| 558 return true; | |
| 559 } else if (element is PartialClassElement) { | |
| 560 removeClass(element); | |
| 561 return true; | |
| 562 } else if (element is PartialFieldList) { | |
| 563 removeFields(element, container); | |
| 564 return true; | |
| 565 } | |
| 566 return cannotReuse( | |
| 567 element, "Removing ${element.runtimeType} not supported."); | |
| 568 } | |
| 569 | |
| 570 void removeFunction(PartialFunctionElement element) { | |
| 571 logVerbose("Removed method $element."); | |
| 572 | |
| 573 invalidateScopesAffectedBy(element, element.enclosingElement); | |
| 574 | |
| 575 _removedElements.add(element); | |
| 576 | |
| 577 updates.add(new RemovedFunctionUpdate(compiler, element)); | |
| 578 } | |
| 579 | |
| 580 void removeClass(PartialClassElement element) { | |
| 581 logVerbose("Removed class $element."); | |
| 582 | |
| 583 invalidateScopesAffectedBy(element, element.library); | |
| 584 | |
| 585 _removedElements.add(element); | |
| 586 element.forEachLocalMember((ElementX member) { | |
| 587 _removedElements.add(member); | |
| 588 }); | |
| 589 | |
| 590 updates.add(new RemovedClassUpdate(compiler, element)); | |
| 591 } | |
| 592 | |
| 593 void removeFields( | |
| 594 PartialFieldList definition, | |
| 595 ScopeContainerElement container) { | |
| 596 List<FieldElementX> fields = <FieldElementX>[]; | |
| 597 container.forEachLocalMember((ElementX member) { | |
| 598 if (member.declarationSite == definition) { | |
| 599 fields.add(member); | |
| 600 } | |
| 601 }); | |
| 602 for (FieldElementX field in fields) { | |
| 603 // TODO(ahe): This only works when there's one field per | |
| 604 // PartialFieldList. | |
| 605 removeField(field); | |
| 606 } | |
| 607 } | |
| 608 | |
| 609 void removeField(FieldElementX element) { | |
| 610 logVerbose("Removed field $element."); | |
| 611 if (!element.isInstanceMember) { | |
| 612 cannotReuse(element, "Not an instance field."); | |
| 613 } else { | |
| 614 removeInstanceField(element); | |
| 615 } | |
| 616 } | |
| 617 | |
| 618 void removeInstanceField(FieldElementX element) { | |
| 619 PartialClassElement cls = element.enclosingClass; | |
| 620 | |
| 621 _classesWithSchemaChanges.add(cls); | |
| 622 invalidateScopesAffectedBy(element, cls); | |
| 623 | |
| 624 _removedElements.add(element); | |
| 625 | |
| 626 updates.add(new RemovedFieldUpdate(compiler, element)); | |
| 627 } | |
| 628 | |
| 629 void invalidateScopesAffectedBy( | |
| 630 ElementX element, | |
| 631 /* ScopeContainerElement */ container) { | |
| 632 for (ScopeContainerElement scope in scopesAffectedBy(element, container)) { | |
| 633 scanSites(scope, (Element member, DeclarationSite site) { | |
| 634 // TODO(ahe): Cache qualifiedNamesIn to avoid quadratic behavior. | |
| 635 Set<String> names = qualifiedNamesIn(site); | |
| 636 if (canNamesResolveStaticallyTo(names, element, container)) { | |
| 637 _elementsToInvalidate.add(member); | |
| 638 } | |
| 639 }); | |
| 640 } | |
| 641 } | |
| 642 | |
| 643 /// Invoke [f] on each [DeclarationSite] in [element]. If [element] is a | |
| 644 /// [ScopeContainerElement], invoke f on all local members as well. | |
| 645 void scanSites( | |
| 646 Element element, | |
| 647 void f(ElementX element, DeclarationSite site)) { | |
| 648 DeclarationSite site = declarationSite(element); | |
| 649 if (site != null) { | |
| 650 f(element, site); | |
| 651 } | |
| 652 if (element is ScopeContainerElement) { | |
| 653 element.forEachLocalMember((member) { scanSites(member, f); }); | |
| 654 } | |
| 655 } | |
| 656 | |
| 657 /// Assume [element] is either removed from or added to [container], and | |
| 658 /// return all [ScopeContainerElement] that can see this change. | |
| 659 List<ScopeContainerElement> scopesAffectedBy( | |
| 660 Element element, | |
| 661 /* ScopeContainerElement */ container) { | |
| 662 // TODO(ahe): Use library export graph to compute this. | |
| 663 // TODO(ahe): Should return all user-defined libraries and packages. | |
| 664 LibraryElement library = container.library; | |
| 665 List<ScopeContainerElement> result = <ScopeContainerElement>[library]; | |
| 666 | |
| 667 if (!container.isClass) return result; | |
| 668 | |
| 669 ClassElement cls = container; | |
| 670 | |
| 671 var externalSubtypes = | |
| 672 compiler.world.subtypesOf(cls).where((e) => e.library != library); | |
| 673 | |
| 674 return result..addAll(externalSubtypes); | |
| 675 } | |
| 676 | |
| 677 /// Returns true if function [before] can be reused to reflect the changes in | |
| 678 /// [after]. | |
| 679 /// | |
| 680 /// If [before] can be reused, an update (patch) is added to [updates]. | |
| 681 bool canReuseFunction( | |
| 682 Token diffToken, | |
| 683 PartialFunctionElement before, | |
| 684 PartialFunctionElement after) { | |
| 685 FunctionExpression node = | |
| 686 after.parseNode(compiler.parsingContext).asFunctionExpression(); | |
| 687 if (node == null) { | |
| 688 return cannotReuse(after, "Not a function expression: '$node'"); | |
| 689 } | |
| 690 Token last = after.endToken; | |
| 691 if (node.body != null) { | |
| 692 last = node.body.getBeginToken(); | |
| 693 } | |
| 694 if (isTokenBetween(diffToken, after.beginToken, last)) { | |
| 695 removeFunction(before); | |
| 696 addFunction(after, before.enclosingElement); | |
| 697 return true; | |
| 698 } | |
| 699 logVerbose('Simple modification of ${after} detected'); | |
| 700 updates.add(new FunctionUpdate(compiler, before, after)); | |
| 701 return true; | |
| 702 } | |
| 703 | |
| 704 bool canReuseClass( | |
| 705 Token diffToken, | |
| 706 PartialClassElement before, | |
| 707 PartialClassElement after) { | |
| 708 ClassNode node = after.parseNode(compiler.parsingContext).asClassNode(); | |
| 709 if (node == null) { | |
| 710 return cannotReuse(after, "Not a ClassNode: '$node'"); | |
| 711 } | |
| 712 NodeList body = node.body; | |
| 713 if (body == null) { | |
| 714 return cannotReuse(after, "Class has no body."); | |
| 715 } | |
| 716 if (isTokenBetween(diffToken, node.beginToken, body.beginToken)) { | |
| 717 logVerbose('Class header modified in ${after}'); | |
| 718 updates.add(new ClassUpdate(compiler, before, after)); | |
| 719 before.forEachLocalMember((ElementX member) { | |
| 720 // TODO(ahe): Quadratic. | |
| 721 invalidateScopesAffectedBy(member, before); | |
| 722 }); | |
| 723 } | |
| 724 return canReuseScopeContainerElement(before, after); | |
| 725 } | |
| 726 | |
| 727 bool isTokenBetween(Token token, Token first, Token last) { | |
| 728 Token current = first; | |
| 729 while (current != last && current.kind != EOF_TOKEN) { | |
| 730 if (current == token) { | |
| 731 return true; | |
| 732 } | |
| 733 current = current.next; | |
| 734 } | |
| 735 return false; | |
| 736 } | |
| 737 | |
| 738 bool unableToReuse( | |
| 739 Token diffToken, | |
| 740 PartialElement before, | |
| 741 PartialElement after) { | |
| 742 return cannotReuse( | |
| 743 after, | |
| 744 'Unhandled change:' | |
| 745 ' ${before} (${before.runtimeType} -> ${after.runtimeType}).'); | |
| 746 } | |
| 747 | |
| 748 /// Apply the collected [updates]. Return a list of elements that needs to be | |
| 749 /// recompiled after applying the updates. Any elements removed as a | |
| 750 /// consequence of applying the patches are added to [removals] if provided. | |
| 751 List<Element> applyUpdates([List<Update> removals]) { | |
| 752 for (Update update in updates) { | |
| 753 update.captureState(); | |
| 754 } | |
| 755 if (!_failedUpdates.isEmpty) { | |
| 756 throw new IncrementalCompilationFailed(_failedUpdates.join('\n\n')); | |
| 757 } | |
| 758 for (ElementX element in _elementsToInvalidate) { | |
| 759 compiler.forgetElement(element); | |
| 760 element.reuseElement(); | |
| 761 } | |
| 762 List<Element> elementsToInvalidate = <Element>[]; | |
| 763 for (ElementX element in _elementsToInvalidate) { | |
| 764 if (!_removedElements.contains(element)) { | |
| 765 elementsToInvalidate.add(element); | |
| 766 } | |
| 767 } | |
| 768 for (Update update in updates) { | |
| 769 Element element = update.apply(); | |
| 770 if (update.isRemoval) { | |
| 771 if (removals != null) { | |
| 772 removals.add(update); | |
| 773 } | |
| 774 } else { | |
| 775 elementsToInvalidate.add(element); | |
| 776 } | |
| 777 } | |
| 778 return elementsToInvalidate; | |
| 779 } | |
| 780 | |
| 781 String computeUpdateJs() { | |
| 782 List<Update> removals = <Update>[]; | |
| 783 List<Element> updatedElements = applyUpdates(removals); | |
| 784 if (compiler.progress != null) { | |
| 785 compiler.progress.reset(); | |
| 786 } | |
| 787 for (Element element in updatedElements) { | |
| 788 if (!element.isClass) { | |
| 789 enqueuer.resolution.addToWorkList(element); | |
| 790 } else { | |
| 791 NO_WARN(element).ensureResolved(compiler); | |
| 792 } | |
| 793 } | |
| 794 compiler.processQueue(enqueuer.resolution, null); | |
| 795 | |
| 796 compiler.phase = Compiler.PHASE_DONE_RESOLVING; | |
| 797 | |
| 798 // TODO(ahe): Clean this up. Don't call this method in analyze-only mode. | |
| 799 if (compiler.options.analyzeOnly) return "/* analyze only */"; | |
| 800 | |
| 801 Set<ClassElementX> changedClasses = | |
| 802 new Set<ClassElementX>.from(_classesWithSchemaChanges); | |
| 803 for (Element element in updatedElements) { | |
| 804 if (!element.isClass) { | |
| 805 enqueuer.codegen.addToWorkList(element); | |
| 806 } else { | |
| 807 changedClasses.add(element); | |
| 808 } | |
| 809 } | |
| 810 compiler.processQueue(enqueuer.codegen, null); | |
| 811 | |
| 812 // Run through all compiled methods and see if they may apply to | |
| 813 // newlySeenSelectors. | |
| 814 for (Element e in enqueuer.codegen.generatedCode.keys) { | |
| 815 if (e.isFunction && !e.isConstructor && | |
| 816 e.functionSignature.hasOptionalParameters) { | |
| 817 for (Selector selector in enqueuer.codegen.newlySeenSelectors) { | |
| 818 // TODO(ahe): Group selectors by name at this point for improved | |
| 819 // performance. | |
| 820 if (e.isInstanceMember && selector.applies(e, compiler.world)) { | |
| 821 // TODO(ahe): Don't use | |
| 822 // enqueuer.codegen.newlyEnqueuedElements directly like | |
| 823 // this, make a copy. | |
| 824 enqueuer.codegen.newlyEnqueuedElements.add(e); | |
| 825 } | |
| 826 if (selector.name == namer.closureInvocationSelectorName) { | |
| 827 selector = new Selector.call( | |
| 828 e.name, e.library, | |
| 829 selector.argumentCount, selector.namedArguments); | |
| 830 if (selector.appliesUnnamed(e, compiler.world)) { | |
| 831 // TODO(ahe): Also make a copy here. | |
| 832 enqueuer.codegen.newlyEnqueuedElements.add(e); | |
| 833 } | |
| 834 } | |
| 835 } | |
| 836 } | |
| 837 } | |
| 838 | |
| 839 List<jsAst.Statement> updates = <jsAst.Statement>[]; | |
| 840 | |
| 841 Set<ClassElementX> newClasses = new Set.from( | |
| 842 compiler.codegenWorld.directlyInstantiatedClasses); | |
| 843 newClasses.removeAll(_directlyInstantiatedClasses); | |
| 844 | |
| 845 if (!newClasses.isEmpty) { | |
| 846 // Ask the emitter to compute "needs" (only) if new classes were | |
| 847 // instantiated. | |
| 848 _ensureAllNeededEntitiesComputed(); | |
| 849 newClasses = new Set.from(emitter.neededClasses); | |
| 850 newClasses.removeAll(_emittedClasses); | |
| 851 } else { | |
| 852 // Make sure that the set of emitted classes is preserved for subsequent | |
| 853 // updates. | |
| 854 // TODO(ahe): This is a bit convoluted, find a better approach. | |
| 855 emitter.neededClasses | |
| 856 ..clear() | |
| 857 ..addAll(_emittedClasses); | |
| 858 } | |
| 859 | |
| 860 List<jsAst.Statement> inherits = <jsAst.Statement>[]; | |
| 861 | |
| 862 for (ClassElementX cls in newClasses) { | |
| 863 jsAst.Node classAccess = emitter.constructorAccess(cls); | |
| 864 String name = namer.className(cls); | |
| 865 | |
| 866 updates.add( | |
| 867 js.statement( | |
| 868 r'# = #', [classAccess, invokeDefineClass(cls)])); | |
| 869 | |
| 870 ClassElement superclass = cls.superclass; | |
| 871 if (superclass != null) { | |
| 872 jsAst.Node superAccess = emitter.constructorAccess(superclass); | |
| 873 inherits.add( | |
| 874 js.statement( | |
| 875 r'this.inheritFrom(#, #)', [classAccess, superAccess])); | |
| 876 } | |
| 877 } | |
| 878 | |
| 879 // Call inheritFrom after all classes have been created. This way we don't | |
| 880 // need to sort the classes by having superclasses defined before their | |
| 881 // subclasses. | |
| 882 updates.addAll(inherits); | |
| 883 | |
| 884 for (ClassElementX cls in changedClasses) { | |
| 885 ClassElement superclass = cls.superclass; | |
| 886 jsAst.Node superAccess = | |
| 887 superclass == null ? js('null') | |
| 888 : emitter.constructorAccess(superclass); | |
| 889 jsAst.Node classAccess = emitter.constructorAccess(cls); | |
| 890 updates.add( | |
| 891 js.statement( | |
| 892 r'# = this.schemaChange(#, #, #)', | |
| 893 [classAccess, invokeDefineClass(cls), classAccess, superAccess])); | |
| 894 } | |
| 895 | |
| 896 for (RemovalUpdate update in removals) { | |
| 897 update.writeUpdateJsOn(updates); | |
| 898 } | |
| 899 for (Element element in enqueuer.codegen.newlyEnqueuedElements) { | |
| 900 if (element.isField) { | |
| 901 updates.addAll(computeFieldUpdateJs(element)); | |
| 902 } else { | |
| 903 updates.add(computeMethodUpdateJs(element)); | |
| 904 } | |
| 905 } | |
| 906 | |
| 907 Set<ConstantValue> newConstants = new Set<ConstantValue>.identity()..addAll( | |
| 908 compiler.backend.constants.compiledConstants); | |
| 909 newConstants.removeAll(_compiledConstants); | |
| 910 | |
| 911 if (!newConstants.isEmpty) { | |
| 912 _ensureAllNeededEntitiesComputed(); | |
| 913 List<ConstantValue> constants = | |
| 914 emitter.outputConstantLists[compiler.deferredLoadTask.mainOutputUnit]; | |
| 915 if (constants != null) { | |
| 916 for (ConstantValue constant in constants) { | |
| 917 if (!_compiledConstants.contains(constant)) { | |
| 918 full.Emitter fullEmitter = emitter.emitter; | |
| 919 jsAst.Statement constantInitializer = | |
| 920 fullEmitter.buildConstantInitializer(constant).toStatement(); | |
| 921 updates.add(constantInitializer); | |
| 922 } | |
| 923 } | |
| 924 } | |
| 925 } | |
| 926 | |
| 927 updates.add(js.statement(r''' | |
| 928 if (this.pendingStubs) { | |
| 929 this.pendingStubs.map(function(e) { return e(); }); | |
| 930 this.pendingStubs = void 0; | |
| 931 } | |
| 932 ''')); | |
| 933 | |
| 934 if (updates.length == 1) { | |
| 935 return prettyPrintJs(updates.single); | |
| 936 } else { | |
| 937 return prettyPrintJs(js.statement('{#}', [updates])); | |
| 938 } | |
| 939 } | |
| 940 | |
| 941 jsAst.Expression invokeDefineClass(ClassElementX cls) { | |
| 942 String name = namer.className(cls); | |
| 943 var descriptor = js('Object.create(null)'); | |
| 944 return js( | |
| 945 r''' | |
| 946 (new Function( | |
| 947 "$collectedClasses", "$desc", | |
| 948 this.defineClass(#name, #computeFields) +"\n;return " + #name))( | |
| 949 {#name: [,#descriptor]})''', | |
| 950 {'name': js.string(name), | |
| 951 'computeFields': js.stringArray(computeFields(cls)), | |
| 952 'descriptor': descriptor}); | |
| 953 } | |
| 954 | |
| 955 jsAst.Node computeMethodUpdateJs(Element element) { | |
| 956 Method member = new ProgramBuilder(compiler, namer, emitter) | |
| 957 .buildMethodHackForIncrementalCompilation(element); | |
| 958 if (member == null) { | |
| 959 compiler.internalError(element, '${element.runtimeType}'); | |
| 960 } | |
| 961 ClassBuilder builder = new ClassBuilder(element, namer); | |
| 962 containerBuilder.addMemberMethod(member, builder); | |
| 963 jsAst.Node partialDescriptor = | |
| 964 builder.toObjectInitializer(emitClassDescriptor: false); | |
| 965 | |
| 966 String name = member.name; | |
| 967 jsAst.Node function = member.code; | |
| 968 bool isStatic = !element.isInstanceMember; | |
| 969 | |
| 970 /// Either a global object (non-instance members) or a prototype (instance | |
| 971 /// members). | |
| 972 jsAst.Node holder; | |
| 973 | |
| 974 if (element.isInstanceMember) { | |
| 975 holder = emitter.prototypeAccess(element.enclosingClass); | |
| 976 } else { | |
| 977 holder = js('#', namer.globalObjectFor(element)); | |
| 978 } | |
| 979 | |
| 980 jsAst.Expression globalFunctionsAccess = | |
| 981 emitter.generateEmbeddedGlobalAccess(embeddedNames.GLOBAL_FUNCTIONS); | |
| 982 | |
| 983 return js.statement( | |
| 984 r'this.addMethod(#, #, #, #, #)', | |
| 985 [partialDescriptor, js.string(name), holder, | |
| 986 new jsAst.LiteralBool(isStatic), globalFunctionsAccess]); | |
| 987 } | |
| 988 | |
| 989 List<jsAst.Statement> computeFieldUpdateJs(FieldElementX element) { | |
| 990 if (element.isInstanceMember) { | |
| 991 // Any initializers are inlined in factory methods, and the field is | |
| 992 // declared by adding its class to [_classesWithSchemaChanges]. | |
| 993 return const <jsAst.Statement>[]; | |
| 994 } | |
| 995 // A static (or top-level) field. | |
| 996 if (backend.constants.lazyStatics.contains(element)) { | |
| 997 full.Emitter fullEmitter = emitter.emitter; | |
| 998 jsAst.Expression init = | |
| 999 fullEmitter.buildLazilyInitializedStaticField( | |
| 1000 element, isolateProperties: namer.staticStateHolder); | |
| 1001 if (init == null) { | |
| 1002 throw new StateError("Initializer optimized away for $element"); | |
| 1003 } | |
| 1004 return <jsAst.Statement>[init.toStatement()]; | |
| 1005 } else { | |
| 1006 // TODO(ahe): When a field is referenced it is enqueued. If the field has | |
| 1007 // no initializer, it will not have any associated code, so it will | |
| 1008 // appear as if it was newly enqueued. | |
| 1009 if (element.initializer == null) { | |
| 1010 return const <jsAst.Statement>[]; | |
| 1011 } else { | |
| 1012 throw new StateError("Don't know how to compile $element"); | |
| 1013 } | |
| 1014 } | |
| 1015 } | |
| 1016 | |
| 1017 String prettyPrintJs(jsAst.Node node) { | |
| 1018 jsAst.JavaScriptPrintingOptions options = | |
| 1019 new jsAst.JavaScriptPrintingOptions(); | |
| 1020 jsAst.JavaScriptPrintingContext context = | |
| 1021 new jsAst.Dart2JSJavaScriptPrintingContext(compiler, null); | |
| 1022 jsAst.Printer printer = new jsAst.Printer(options, context); | |
| 1023 printer.blockOutWithoutBraces(node); | |
| 1024 return context.outBuffer.getText(); | |
| 1025 } | |
| 1026 | |
| 1027 String callNameFor(FunctionElement element) { | |
| 1028 // TODO(ahe): Call a method in the compiler to obtain this name. | |
| 1029 String callPrefix = namer.callPrefix; | |
| 1030 int parameterCount = element.functionSignature.parameterCount; | |
| 1031 return '$callPrefix\$$parameterCount'; | |
| 1032 } | |
| 1033 | |
| 1034 List<String> computeFields(ClassElement cls) { | |
| 1035 return new EmitterHelper(compiler).computeFields(cls); | |
| 1036 } | |
| 1037 | |
| 1038 void _ensureAllNeededEntitiesComputed() { | |
| 1039 if (_hasComputedNeeds) return; | |
| 1040 emitter.computeAllNeededEntities(); | |
| 1041 _hasComputedNeeds = true; | |
| 1042 } | |
| 1043 } | |
| 1044 | |
| 1045 /// Represents an update (aka patch) of [before] to [after]. We use the word | |
| 1046 /// "update" to avoid confusion with the compiler feature of "patch" methods. | |
| 1047 abstract class Update { | |
| 1048 final Compiler compiler; | |
| 1049 | |
| 1050 PartialElement get before; | |
| 1051 | |
| 1052 PartialElement get after; | |
| 1053 | |
| 1054 Update(this.compiler); | |
| 1055 | |
| 1056 /// Applies the update to [before] and returns that element. | |
| 1057 Element apply(); | |
| 1058 | |
| 1059 bool get isRemoval => false; | |
| 1060 | |
| 1061 /// Called before any patches are applied to capture any state that is needed | |
| 1062 /// later. | |
| 1063 void captureState() { | |
| 1064 } | |
| 1065 } | |
| 1066 | |
| 1067 /// Represents an update of a function element. | |
| 1068 class FunctionUpdate extends Update with ReuseFunction { | |
| 1069 final PartialFunctionElement before; | |
| 1070 | |
| 1071 final PartialFunctionElement after; | |
| 1072 | |
| 1073 FunctionUpdate(Compiler compiler, this.before, this.after) | |
| 1074 : super(compiler); | |
| 1075 | |
| 1076 PartialFunctionElement apply() { | |
| 1077 patchElement(); | |
| 1078 reuseElement(); | |
| 1079 return before; | |
| 1080 } | |
| 1081 | |
| 1082 /// Destructively change the tokens in [before] to match those of [after]. | |
| 1083 void patchElement() { | |
| 1084 before.beginToken = after.beginToken; | |
| 1085 before.endToken = after.endToken; | |
| 1086 before.getOrSet = after.getOrSet; | |
| 1087 } | |
| 1088 } | |
| 1089 | |
| 1090 abstract class ReuseFunction { | |
| 1091 Compiler get compiler; | |
| 1092 | |
| 1093 PartialFunctionElement get before; | |
| 1094 | |
| 1095 /// Reset various caches and remove this element from the compiler's internal | |
| 1096 /// state. | |
| 1097 void reuseElement() { | |
| 1098 compiler.forgetElement(before); | |
| 1099 before.reuseElement(); | |
| 1100 } | |
| 1101 } | |
| 1102 | |
| 1103 abstract class RemovalUpdate extends Update { | |
| 1104 ElementX get element; | |
| 1105 | |
| 1106 RemovalUpdate(Compiler compiler) | |
| 1107 : super(compiler); | |
| 1108 | |
| 1109 bool get isRemoval => true; | |
| 1110 | |
| 1111 void writeUpdateJsOn(List<jsAst.Statement> updates); | |
| 1112 | |
| 1113 void removeFromEnclosing() { | |
| 1114 // TODO(ahe): Need to recompute duplicated elements logic again. Simplest | |
| 1115 // solution is probably to remove all elements from enclosing scope and add | |
| 1116 // them back. | |
| 1117 if (element.isTopLevel) { | |
| 1118 removeFromLibrary(element.library); | |
| 1119 } else { | |
| 1120 removeFromEnclosingClass(element.enclosingClass); | |
| 1121 } | |
| 1122 } | |
| 1123 | |
| 1124 void removeFromEnclosingClass(PartialClassElement cls) { | |
| 1125 cls.localMembersCache = null; | |
| 1126 cls.localMembersReversed = cls.localMembersReversed.copyWithout(element); | |
| 1127 cls.localScope.contents.remove(element.name); | |
| 1128 } | |
| 1129 | |
| 1130 void removeFromLibrary(LibraryElementX library) { | |
| 1131 library.localMembers = library.localMembers.copyWithout(element); | |
| 1132 library.localScope.contents.remove(element.name); | |
| 1133 } | |
| 1134 } | |
| 1135 | |
| 1136 class RemovedFunctionUpdate extends RemovalUpdate | |
| 1137 with JsFeatures, ReuseFunction { | |
| 1138 final PartialFunctionElement element; | |
| 1139 | |
| 1140 /// Name of property to remove using JavaScript "delete". Null for | |
| 1141 /// non-instance methods. | |
| 1142 String name; | |
| 1143 | |
| 1144 /// For instance methods, access to class object. Otherwise, access to the | |
| 1145 /// method itself. | |
| 1146 jsAst.Node elementAccess; | |
| 1147 | |
| 1148 bool wasStateCaptured = false; | |
| 1149 | |
| 1150 RemovedFunctionUpdate(Compiler compiler, this.element) | |
| 1151 : super(compiler); | |
| 1152 | |
| 1153 PartialFunctionElement get before => element; | |
| 1154 | |
| 1155 PartialFunctionElement get after => null; | |
| 1156 | |
| 1157 void captureState() { | |
| 1158 if (wasStateCaptured) throw "captureState was called twice."; | |
| 1159 wasStateCaptured = true; | |
| 1160 | |
| 1161 if (element.isInstanceMember) { | |
| 1162 elementAccess = emitter.constructorAccess(element.enclosingClass); | |
| 1163 name = namer.instanceMethodName(element); | |
| 1164 } else { | |
| 1165 elementAccess = emitter.staticFunctionAccess(element); | |
| 1166 } | |
| 1167 } | |
| 1168 | |
| 1169 PartialFunctionElement apply() { | |
| 1170 if (!wasStateCaptured) throw "captureState must be called before apply."; | |
| 1171 removeFromEnclosing(); | |
| 1172 reuseElement(); | |
| 1173 return null; | |
| 1174 } | |
| 1175 | |
| 1176 void writeUpdateJsOn(List<jsAst.Statement> updates) { | |
| 1177 if (elementAccess == null) { | |
| 1178 compiler.internalError( | |
| 1179 element, 'No elementAccess for ${element.runtimeType}'); | |
| 1180 } | |
| 1181 if (element.isInstanceMember) { | |
| 1182 if (name == null) { | |
| 1183 compiler.internalError(element, 'No name for ${element.runtimeType}'); | |
| 1184 } | |
| 1185 updates.add( | |
| 1186 js.statement('delete #.prototype.#', [elementAccess, name])); | |
| 1187 } else { | |
| 1188 updates.add(js.statement('delete #', [elementAccess])); | |
| 1189 } | |
| 1190 } | |
| 1191 } | |
| 1192 | |
| 1193 class RemovedClassUpdate extends RemovalUpdate with JsFeatures { | |
| 1194 final PartialClassElement element; | |
| 1195 | |
| 1196 bool wasStateCaptured = false; | |
| 1197 | |
| 1198 final List<jsAst.Node> accessToStatics = <jsAst.Node>[]; | |
| 1199 | |
| 1200 RemovedClassUpdate(Compiler compiler, this.element) | |
| 1201 : super(compiler); | |
| 1202 | |
| 1203 PartialClassElement get before => element; | |
| 1204 | |
| 1205 PartialClassElement get after => null; | |
| 1206 | |
| 1207 void captureState() { | |
| 1208 if (wasStateCaptured) throw "captureState was called twice."; | |
| 1209 wasStateCaptured = true; | |
| 1210 accessToStatics.add(emitter.constructorAccess(element)); | |
| 1211 | |
| 1212 element.forEachLocalMember((ElementX member) { | |
| 1213 if (!member.isInstanceMember) { | |
| 1214 accessToStatics.add(emitter.staticFunctionAccess(member)); | |
| 1215 } | |
| 1216 }); | |
| 1217 } | |
| 1218 | |
| 1219 PartialClassElement apply() { | |
| 1220 if (!wasStateCaptured) { | |
| 1221 throw new StateError("captureState must be called before apply."); | |
| 1222 } | |
| 1223 | |
| 1224 removeFromEnclosing(); | |
| 1225 | |
| 1226 element.forEachLocalMember((ElementX member) { | |
| 1227 compiler.forgetElement(member); | |
| 1228 member.reuseElement(); | |
| 1229 }); | |
| 1230 | |
| 1231 compiler.forgetElement(element); | |
| 1232 element.reuseElement(); | |
| 1233 | |
| 1234 return null; | |
| 1235 } | |
| 1236 | |
| 1237 void writeUpdateJsOn(List<jsAst.Statement> updates) { | |
| 1238 if (accessToStatics.isEmpty) { | |
| 1239 throw | |
| 1240 new StateError("captureState must be called before writeUpdateJsOn."); | |
| 1241 } | |
| 1242 | |
| 1243 for (jsAst.Node access in accessToStatics) { | |
| 1244 updates.add(js.statement('delete #', [access])); | |
| 1245 } | |
| 1246 } | |
| 1247 } | |
| 1248 | |
| 1249 class RemovedFieldUpdate extends RemovalUpdate with JsFeatures { | |
| 1250 final FieldElementX element; | |
| 1251 | |
| 1252 bool wasStateCaptured = false; | |
| 1253 | |
| 1254 jsAst.Node prototypeAccess; | |
| 1255 | |
| 1256 String getterName; | |
| 1257 | |
| 1258 String setterName; | |
| 1259 | |
| 1260 RemovedFieldUpdate(Compiler compiler, this.element) | |
| 1261 : super(compiler); | |
| 1262 | |
| 1263 PartialFieldList get before => element.declarationSite; | |
| 1264 | |
| 1265 PartialFieldList get after => null; | |
| 1266 | |
| 1267 void captureState() { | |
| 1268 if (wasStateCaptured) throw "captureState was called twice."; | |
| 1269 wasStateCaptured = true; | |
| 1270 | |
| 1271 prototypeAccess = emitter.prototypeAccess(element.enclosingClass); | |
| 1272 getterName = namer.getterForElement(element); | |
| 1273 setterName = namer.setterForElement(element); | |
| 1274 } | |
| 1275 | |
| 1276 FieldElementX apply() { | |
| 1277 if (!wasStateCaptured) { | |
| 1278 throw new StateError("captureState must be called before apply."); | |
| 1279 } | |
| 1280 | |
| 1281 removeFromEnclosing(); | |
| 1282 | |
| 1283 return element; | |
| 1284 } | |
| 1285 | |
| 1286 void writeUpdateJsOn(List<jsAst.Statement> updates) { | |
| 1287 if (!wasStateCaptured) { | |
| 1288 throw new StateError( | |
| 1289 "captureState must be called before writeUpdateJsOn."); | |
| 1290 } | |
| 1291 | |
| 1292 updates.add( | |
| 1293 js.statement('delete #.#', [prototypeAccess, getterName])); | |
| 1294 updates.add( | |
| 1295 js.statement('delete #.#', [prototypeAccess, setterName])); | |
| 1296 } | |
| 1297 } | |
| 1298 | |
| 1299 class AddedFunctionUpdate extends Update with JsFeatures { | |
| 1300 final PartialFunctionElement element; | |
| 1301 | |
| 1302 final /* ScopeContainerElement */ container; | |
| 1303 | |
| 1304 AddedFunctionUpdate(Compiler compiler, this.element, this.container) | |
| 1305 : super(compiler) { | |
| 1306 if (container == null) { | |
| 1307 throw "container is null"; | |
| 1308 } | |
| 1309 } | |
| 1310 | |
| 1311 PartialFunctionElement get before => null; | |
| 1312 | |
| 1313 PartialFunctionElement get after => element; | |
| 1314 | |
| 1315 PartialFunctionElement apply() { | |
| 1316 Element enclosing = container; | |
| 1317 if (enclosing.isLibrary) { | |
| 1318 // TODO(ahe): Reuse compilation unit of element instead? | |
| 1319 enclosing = enclosing.compilationUnit; | |
| 1320 } | |
| 1321 PartialFunctionElement copy = element.copyWithEnclosing(enclosing); | |
| 1322 NO_WARN(container).addMember(copy, compiler); | |
| 1323 return copy; | |
| 1324 } | |
| 1325 } | |
| 1326 | |
| 1327 class AddedClassUpdate extends Update with JsFeatures { | |
| 1328 final PartialClassElement element; | |
| 1329 | |
| 1330 final LibraryElementX library; | |
| 1331 | |
| 1332 AddedClassUpdate(Compiler compiler, this.element, this.library) | |
| 1333 : super(compiler); | |
| 1334 | |
| 1335 PartialClassElement get before => null; | |
| 1336 | |
| 1337 PartialClassElement get after => element; | |
| 1338 | |
| 1339 PartialClassElement apply() { | |
| 1340 // TODO(ahe): Reuse compilation unit of element instead? | |
| 1341 CompilationUnitElementX compilationUnit = library.compilationUnit; | |
| 1342 PartialClassElement copy = element.copyWithEnclosing(compilationUnit); | |
| 1343 compilationUnit.addMember(copy, compiler); | |
| 1344 return copy; | |
| 1345 } | |
| 1346 } | |
| 1347 | |
| 1348 class AddedFieldUpdate extends Update with JsFeatures { | |
| 1349 final FieldElementX element; | |
| 1350 | |
| 1351 final ScopeContainerElement container; | |
| 1352 | |
| 1353 AddedFieldUpdate(Compiler compiler, this.element, this.container) | |
| 1354 : super(compiler); | |
| 1355 | |
| 1356 PartialFieldList get before => null; | |
| 1357 | |
| 1358 PartialFieldList get after => element.declarationSite; | |
| 1359 | |
| 1360 FieldElementX apply() { | |
| 1361 Element enclosing = container; | |
| 1362 if (enclosing.isLibrary) { | |
| 1363 // TODO(ahe): Reuse compilation unit of element instead? | |
| 1364 enclosing = enclosing.compilationUnit; | |
| 1365 } | |
| 1366 FieldElementX copy = element.copyWithEnclosing(enclosing); | |
| 1367 NO_WARN(container).addMember(copy, compiler); | |
| 1368 return copy; | |
| 1369 } | |
| 1370 } | |
| 1371 | |
| 1372 | |
| 1373 class ClassUpdate extends Update with JsFeatures { | |
| 1374 final PartialClassElement before; | |
| 1375 | |
| 1376 final PartialClassElement after; | |
| 1377 | |
| 1378 ClassUpdate(Compiler compiler, this.before, this.after) | |
| 1379 : super(compiler); | |
| 1380 | |
| 1381 PartialClassElement apply() { | |
| 1382 patchElement(); | |
| 1383 reuseElement(); | |
| 1384 return before; | |
| 1385 } | |
| 1386 | |
| 1387 /// Destructively change the tokens in [before] to match those of [after]. | |
| 1388 void patchElement() { | |
| 1389 before.cachedNode = after.cachedNode; | |
| 1390 before.beginToken = after.beginToken; | |
| 1391 before.endToken = after.endToken; | |
| 1392 } | |
| 1393 | |
| 1394 void reuseElement() { | |
| 1395 before.supertype = null; | |
| 1396 before.interfaces = null; | |
| 1397 before.nativeTagInfo = null; | |
| 1398 before.supertypeLoadState = STATE_NOT_STARTED; | |
| 1399 before.resolutionState = STATE_NOT_STARTED; | |
| 1400 before.isProxy = false; | |
| 1401 before.hasIncompleteHierarchy = false; | |
| 1402 before.backendMembers = const Link<Element>(); | |
| 1403 before.allSupertypesAndSelf = null; | |
| 1404 } | |
| 1405 } | |
| 1406 | |
| 1407 /// Returns all qualified names in [element] with less than four identifiers. A | |
| 1408 /// qualified name is an identifier followed by a sequence of dots and | |
| 1409 /// identifiers, for example, "x", and "x.y.z". But not "x.y.z.w" ("w" is the | |
| 1410 /// fourth identifier). | |
| 1411 /// | |
| 1412 /// The longest possible name that can be resolved is three identifiers, for | |
| 1413 /// example, "prefix.MyClass.staticMethod". Since four or more identifiers | |
| 1414 /// cannot resolve to anything statically, they're not included in the returned | |
| 1415 /// value of this method. | |
| 1416 Set<String> qualifiedNamesIn(PartialElement element) { | |
| 1417 Token beginToken = element.beginToken; | |
| 1418 Token endToken = element.endToken; | |
| 1419 Token token = beginToken; | |
| 1420 if (element is PartialClassElement) { | |
| 1421 ClassNode node = element.cachedNode; | |
| 1422 if (node != null) { | |
| 1423 NodeList body = node.body; | |
| 1424 if (body != null) { | |
| 1425 endToken = body.beginToken; | |
| 1426 } | |
| 1427 } | |
| 1428 } | |
| 1429 Set<String> names = new Set<String>(); | |
| 1430 do { | |
| 1431 if (token.isIdentifier()) { | |
| 1432 String name = token.value; | |
| 1433 // [name] is a single "identifier". | |
| 1434 names.add(name); | |
| 1435 if (identical('.', token.next.stringValue) && | |
| 1436 token.next.next.isIdentifier()) { | |
| 1437 token = token.next.next; | |
| 1438 name += '.${token.value}'; | |
| 1439 // [name] is "idenfifier.idenfifier". | |
| 1440 names.add(name); | |
| 1441 | |
| 1442 if (identical('.', token.next.stringValue) && | |
| 1443 token.next.next.isIdentifier()) { | |
| 1444 token = token.next.next; | |
| 1445 name += '.${token.value}'; | |
| 1446 // [name] is "idenfifier.idenfifier.idenfifier". | |
| 1447 names.add(name); | |
| 1448 | |
| 1449 while (identical('.', token.next.stringValue) && | |
| 1450 token.next.next.isIdentifier()) { | |
| 1451 // Skip remaining identifiers, they cannot statically resolve to | |
| 1452 // anything, and must be dynamic sends. | |
| 1453 token = token.next.next; | |
| 1454 } | |
| 1455 } | |
| 1456 } | |
| 1457 } | |
| 1458 token = token.next; | |
| 1459 } while (token.kind != EOF_TOKEN && token != endToken); | |
| 1460 return names; | |
| 1461 } | |
| 1462 | |
| 1463 /// Returns true if one of the qualified names in names (as computed by | |
| 1464 /// [qualifiedNamesIn]) could be a static reference to [element]. | |
| 1465 bool canNamesResolveStaticallyTo( | |
| 1466 Set<String> names, | |
| 1467 Element element, | |
| 1468 /* ScopeContainerElement */ container) { | |
| 1469 if (names.contains(element.name)) return true; | |
| 1470 if (container != null && container.isClass) { | |
| 1471 // [names] contains C.m, where C is the name of [container], and m is the | |
| 1472 // name of [element]. | |
| 1473 if (names.contains("${container.name}.${element.name}")) return true; | |
| 1474 } | |
| 1475 // TODO(ahe): Check for prefixes as well. | |
| 1476 return false; | |
| 1477 } | |
| 1478 | |
| 1479 DeclarationSite declarationSite(Element element) { | |
| 1480 return element is ElementX ? element.declarationSite : null; | |
| 1481 } | |
| 1482 | |
| 1483 abstract class JsFeatures { | |
| 1484 Compiler get compiler; | |
| 1485 | |
| 1486 JavaScriptBackend get backend => compiler.backend; | |
| 1487 | |
| 1488 Namer get namer => backend.namer; | |
| 1489 | |
| 1490 CodeEmitterTask get emitter => backend.emitter; | |
| 1491 | |
| 1492 ContainerBuilder get containerBuilder { | |
| 1493 full.Emitter fullEmitter = emitter.emitter; | |
| 1494 return fullEmitter.containerBuilder; | |
| 1495 } | |
| 1496 | |
| 1497 EnqueueTask get enqueuer => compiler.enqueuer; | |
| 1498 } | |
| 1499 | |
| 1500 class EmitterHelper extends JsFeatures { | |
| 1501 final Compiler compiler; | |
| 1502 | |
| 1503 EmitterHelper(this.compiler); | |
| 1504 | |
| 1505 ClassEmitter get classEmitter { | |
| 1506 full.Emitter fullEmitter = emitter.emitter; | |
| 1507 return fullEmitter.classEmitter; | |
| 1508 } | |
| 1509 | |
| 1510 List<String> computeFields(ClassElement classElement) { | |
| 1511 Class cls = new ProgramBuilder(compiler, namer, emitter) | |
| 1512 .buildFieldsHackForIncrementalCompilation(classElement); | |
| 1513 // TODO(ahe): Rewrite for new emitter. | |
| 1514 ClassBuilder builder = new ClassBuilder(classElement, namer); | |
| 1515 classEmitter.emitFields(cls, builder); | |
| 1516 return builder.fields; | |
| 1517 } | |
| 1518 } | |
| 1519 | |
| 1520 // TODO(ahe): Remove this method. | |
| 1521 NO_WARN(x) => x; | |
| OLD | NEW |