|  | // Copyright (c) 2018, 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. | 
|  |  | 
|  | // @dart = 2.9 | 
|  |  | 
|  | import 'dart:collection'; | 
|  | import 'package:meta/meta.dart'; | 
|  |  | 
|  | import '../compiler/js_names.dart' as js_ast; | 
|  | import '../compiler/module_containers.dart' show ModuleItemContainer; | 
|  | import '../js_ast/js_ast.dart' as js_ast; | 
|  | import '../js_ast/js_ast.dart' show js; | 
|  |  | 
|  | /// Shared code between Analyzer and Kernel backends. | 
|  | /// | 
|  | /// This class should only implement functionality that depends purely on JS | 
|  | /// classes, rather than on Analyzer/Kernel types. | 
|  | abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> { | 
|  | /// When inside a `[]=` operator, this will be a non-null value that should be | 
|  | /// returned by any `return;` statement. | 
|  | /// | 
|  | /// This lets DDC use the setter method's return value directly. | 
|  | final List<js_ast.Identifier> _operatorSetResultStack = []; | 
|  |  | 
|  | /// Private member names in this module, organized by their library. | 
|  | final _privateNames = HashMap<Library, HashMap<String, js_ast.TemporaryId>>(); | 
|  |  | 
|  | /// Holds all top-level JS symbols (used for caching or indexing fields). | 
|  | final _symbolContainer = ModuleItemContainer<js_ast.Identifier>.asObject('S', | 
|  | keyToString: (js_ast.Identifier i) => i.name); | 
|  |  | 
|  | ModuleItemContainer<js_ast.Identifier> get symbolContainer => | 
|  | _symbolContainer; | 
|  |  | 
|  | /// Extension member symbols for adding Dart members to JS types. | 
|  | /// | 
|  | /// These are added to the [extensionSymbolsModule]; see that field for more | 
|  | /// information. | 
|  | final _extensionSymbols = <String, js_ast.TemporaryId>{}; | 
|  |  | 
|  | /// The set of libraries we are currently compiling, and the temporaries used | 
|  | /// to refer to them. | 
|  | final _libraries = <Library, js_ast.Identifier>{}; | 
|  |  | 
|  | /// Imported libraries, and the temporaries used to refer to them. | 
|  | final _imports = <Library, js_ast.TemporaryId>{}; | 
|  |  | 
|  | /// Incremental mode for expression compilation. | 
|  | /// | 
|  | /// If set to true, triggers emitting tall used ypes, symbols, libraries, | 
|  | /// constants, urs inside the generated function. | 
|  | bool _incrementalMode = false; | 
|  |  | 
|  | @protected | 
|  | bool get incrementalMode => _incrementalMode; | 
|  |  | 
|  | /// Set incremental mode for one expression compilation. | 
|  | /// | 
|  | /// Sets all tables and internal structures to incremental mode so | 
|  | /// only referenced items will be emitted in a generated function. | 
|  | /// | 
|  | /// Note: the compiler cannot revert to non-incremental mode. | 
|  | @protected | 
|  | void setIncrementalMode() { | 
|  | incrementalModules.clear(); | 
|  | _privateNames.clear(); | 
|  | symbolContainer.setIncrementalMode(); | 
|  | _incrementalMode = true; | 
|  | } | 
|  |  | 
|  | /// Modules and libraries accessed during compilation in incremental mode. | 
|  | @protected | 
|  | final Map<String, Set<String>> incrementalModules = {}; | 
|  |  | 
|  | @protected | 
|  | void setEmitIfIncrementalLibrary(Library library) { | 
|  | if (incrementalMode && library != null) { | 
|  | setEmitIfIncremental(libraryToModule(library), jsLibraryName(library)); | 
|  | } | 
|  | } | 
|  |  | 
|  | @protected | 
|  | void setEmitIfIncremental(String module, String library) { | 
|  | if (incrementalMode && library != null) { | 
|  | incrementalModules.putIfAbsent(module, () => {}).add(library); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// The identifier used to reference DDC's core "dart:_runtime" library from | 
|  | /// generated JS code, typically called "dart" e.g. `dart.dcall`. | 
|  | js_ast.Identifier runtimeModule; | 
|  |  | 
|  | /// The identifier used to reference DDC's "extension method" symbols, used to | 
|  | /// safely add Dart-specific member names to JavaScript classes, such as | 
|  | /// primitive types (e.g. String) or DOM types in "dart:html". | 
|  | @protected | 
|  | js_ast.Identifier extensionSymbolsModule; | 
|  |  | 
|  | /// Whether we're currently building the SDK, which may require special | 
|  | /// bootstrapping logic. | 
|  | /// | 
|  | /// This is initialized by [emitModule], which must be called before | 
|  | /// accessing this field. | 
|  | @protected | 
|  | bool isBuildingSdk; | 
|  |  | 
|  | /// Whether or not to move top level symbols into top-level containers. | 
|  | /// | 
|  | /// This is set in both [emitModule] and [emitLibrary]. | 
|  | /// Depends on [isBuildingSdk]. | 
|  | @protected | 
|  | bool containerizeSymbols; | 
|  |  | 
|  | /// The temporary variable that stores named arguments (these are passed via a | 
|  | /// JS object literal, to match JS conventions). | 
|  | @protected | 
|  | final namedArgumentTemp = js_ast.TemporaryId('opts'); | 
|  |  | 
|  | /// The list of output module items, in the order they need to be emitted in. | 
|  | @protected | 
|  | final moduleItems = <js_ast.ModuleItem>[]; | 
|  |  | 
|  | /// Like [moduleItems] but for items that should be emitted after classes. | 
|  | /// | 
|  | /// This is used for deferred supertypes of mutually recursive non-generic | 
|  | /// classes. | 
|  | @protected | 
|  | final afterClassDefItems = <js_ast.ModuleItem>[]; | 
|  |  | 
|  | /// The type used for private Dart [Symbol]s. | 
|  | @protected | 
|  | InterfaceType get privateSymbolType; | 
|  |  | 
|  | /// The type used for public Dart [Symbol]s. | 
|  | @protected | 
|  | InterfaceType get internalSymbolType; | 
|  |  | 
|  | /// The current library being compiled. | 
|  | @protected | 
|  | Library get currentLibrary; | 
|  |  | 
|  | /// The library for dart:core in the SDK. | 
|  | @protected | 
|  | Library get coreLibrary; | 
|  |  | 
|  | /// The import URI of current library. | 
|  | @protected | 
|  | Uri get currentLibraryUri; | 
|  |  | 
|  | /// The current function being compiled, if any. | 
|  | @protected | 
|  | FunctionNode get currentFunction; | 
|  |  | 
|  | /// Choose a canonical name from the [library] element. | 
|  | @protected | 
|  | String jsLibraryName(Library library); | 
|  |  | 
|  | /// Choose a module-unique name from the [library] element. | 
|  | /// | 
|  | /// Returns null if no alias exists or there are multiple output paths | 
|  | /// (e.g., when compiling the Dart SDK). | 
|  | /// | 
|  | /// This never uses the library's name (the identifier in the `library` | 
|  | /// declaration) as it doesn't have any meaningful rules enforced. | 
|  | @protected | 
|  | String jsLibraryAlias(Library library); | 
|  |  | 
|  | /// Debugger friendly name for a Dart [library]. | 
|  | @protected | 
|  | String jsLibraryDebuggerName(Library library); | 
|  |  | 
|  | /// Debugger friendly names for all parts in a Dart [library]. | 
|  | @protected | 
|  | Iterable<String> jsPartDebuggerNames(Library library); | 
|  |  | 
|  | /// Gets the module import URI that contains [library]. | 
|  | @protected | 
|  | String libraryToModule(Library library); | 
|  |  | 
|  | /// Returns true if the library [l] is "dart:_runtime". | 
|  | @protected | 
|  | bool isSdkInternalRuntime(Library l); | 
|  |  | 
|  | /// Whether any superclass of [c] defines a static [name]. | 
|  | @protected | 
|  | bool superclassHasStatic(Class c, String name); | 
|  |  | 
|  | /// Emits the expression necessary to access a constructor of [type]; | 
|  | @protected | 
|  | js_ast.Expression emitConstructorAccess(InterfaceType type); | 
|  |  | 
|  | /// When compiling the body of a `operator []=` method, this will be non-null | 
|  | /// and will indicate the value that should be returned from any `return;` | 
|  | /// statements. | 
|  | js_ast.Identifier get _operatorSetResult { | 
|  | var stack = _operatorSetResultStack; | 
|  | return stack.isEmpty ? null : stack.last; | 
|  | } | 
|  |  | 
|  | /// Called when starting to emit methods/functions, in particular so we can | 
|  | /// implement special handling of the user-defined `[]=` and `==` methods. | 
|  | /// | 
|  | /// See also [exitFunction] and [emitReturnStatement]. | 
|  | @protected | 
|  | void enterFunction(String name, List<js_ast.Parameter> formals, | 
|  | bool Function() isLastParamMutated) { | 
|  | if (name == '[]=') { | 
|  | _operatorSetResultStack.add(isLastParamMutated() | 
|  | ? js_ast.TemporaryId((formals.last as js_ast.Identifier).name) | 
|  | : formals.last as js_ast.Identifier); | 
|  | } else { | 
|  | _operatorSetResultStack.add(null); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Called when finished emitting methods/functions, and must correspond to a | 
|  | /// previous [enterFunction] call. | 
|  | @protected | 
|  | js_ast.Block exitFunction( | 
|  | String name, List<js_ast.Parameter> formals, js_ast.Block code) { | 
|  | var setOperatorResult = _operatorSetResultStack.removeLast(); | 
|  | if (setOperatorResult != null) { | 
|  | // []= methods need to return the value. We could also address this at | 
|  | // call sites, but it's less code size to handle inside the operator. | 
|  | var valueParam = formals.last; | 
|  | var statements = code.statements; | 
|  | if (statements.isEmpty || !statements.last.alwaysReturns) { | 
|  | statements.add(js_ast.Return(setOperatorResult)); | 
|  | } | 
|  | if (!identical(setOperatorResult, valueParam)) { | 
|  | // If the value parameter was mutated, then we use a temporary | 
|  | // variable to track the initial value | 
|  | formals.last = setOperatorResult; | 
|  | code = js | 
|  | .block('{ let # = #; #; }', [valueParam, setOperatorResult, code]); | 
|  | } | 
|  | } | 
|  | return code; | 
|  | } | 
|  |  | 
|  | /// Emits a return statement `return <value>;`, handling special rules for | 
|  | /// the `operator []=` method. | 
|  | @protected | 
|  | js_ast.Statement emitReturnStatement(js_ast.Expression value) { | 
|  | if (_operatorSetResult != null) { | 
|  | var result = js_ast.Return(_operatorSetResult); | 
|  | return value != null | 
|  | ? js_ast.Block([value.toStatement(), result]) | 
|  | : result; | 
|  | } | 
|  | return value != null ? value.toReturn() : js_ast.Return(); | 
|  | } | 
|  |  | 
|  | /// Prepends the `dart.` and then uses [js.call] to parse the specified JS | 
|  | /// [code] template, passing [args]. | 
|  | /// | 
|  | /// For example: | 
|  | /// | 
|  | ///     runtimeCall('asInt(#)', [<expr>]) | 
|  | /// | 
|  | /// Generates a JS AST representing: | 
|  | /// | 
|  | ///     dart.asInt(<expr>) | 
|  | /// | 
|  | @protected | 
|  | js_ast.Expression runtimeCall(String code, [List<Object> args]) { | 
|  | setEmitIfIncremental(libraryToModule(coreLibrary), runtimeModule.name); | 
|  | return js.call('#.$code', <Object>[runtimeModule, ...?args]); | 
|  | } | 
|  |  | 
|  | /// Calls [runtimeCall] and uses `toStatement()` to convert the resulting | 
|  | /// expression into a statement. | 
|  | @protected | 
|  | js_ast.Statement runtimeStatement(String code, [List<Object> args]) => | 
|  | runtimeCall(code, args).toStatement(); | 
|  |  | 
|  | /// Emits a private name JS Symbol for [name] scoped to the Dart [library]. | 
|  | /// | 
|  | /// If the same name is used in multiple libraries in the same module, | 
|  | /// distinct symbols will be used, so each library will have distinct private | 
|  | /// member names, that won't collide at runtime, as required by the Dart | 
|  | /// language spec. | 
|  | /// | 
|  | /// If an [id] is provided, try to use that. | 
|  | /// | 
|  | /// TODO(vsm): Clean up id generation logic.  This method is used to both | 
|  | /// define new symbols and to reference existing ones.  If it's called | 
|  | /// multiple times with same [library] and [name], we'll allocate redundant | 
|  | /// top-level variables (see callers to this method). | 
|  | @protected | 
|  | js_ast.TemporaryId emitPrivateNameSymbol(Library library, String name, | 
|  | [js_ast.TemporaryId id]) { | 
|  | /// Initializes the JS `Symbol` for the private member [name] in [library]. | 
|  | /// | 
|  | /// If the library is in the current JS module ([_libraries] contains it), | 
|  | /// the private name will be created and exported. The exported symbol is | 
|  | /// used for a few things: | 
|  | /// | 
|  | /// - private fields of constant objects | 
|  | /// - stateful hot reload (not yet implemented) | 
|  | /// - correct library scope in REPL (not yet implemented) | 
|  | /// | 
|  | /// If the library is imported, then the existing private name will be | 
|  | /// retrieved from it. In both cases, we use the same `dart.privateName` | 
|  | /// runtime call. | 
|  | js_ast.TemporaryId initPrivateNameSymbol() { | 
|  | var idName = name.endsWith('=') ? name.replaceAll('=', '_') : name; | 
|  | idName = idName.replaceAll(js_ast.invalidCharInIdentifier, '_'); | 
|  | id ??= js_ast.TemporaryId(idName); | 
|  | addSymbol( | 
|  | id, | 
|  | runtimeCall('privateName(#, #)', | 
|  | [emitLibraryName(library), js.string(name)])); | 
|  | if (!containerizeSymbols) { | 
|  | // TODO(vsm): Change back to `const`. | 
|  | // See https://github.com/dart-lang/sdk/issues/40380. | 
|  | moduleItems.add(js.statement('var # = #', [ | 
|  | id, | 
|  | runtimeCall( | 
|  | 'privateName(#, #)', [emitLibraryName(library), js.string(name)]) | 
|  | ])); | 
|  | } | 
|  | return id; | 
|  | } | 
|  |  | 
|  | var privateNames = _privateNames.putIfAbsent(library, () => HashMap()); | 
|  | var symbolId = privateNames.putIfAbsent(name, initPrivateNameSymbol); | 
|  |  | 
|  | setEmitIfIncrementalLibrary(library); | 
|  | setEmitIfIncremental(libraryToModule(coreLibrary), runtimeModule.name); | 
|  | _symbolContainer.setEmitIfIncremental(symbolId); | 
|  |  | 
|  | return symbolId; | 
|  | } | 
|  |  | 
|  | /// Emits an expression to set the property [nameExpr] on the class [className], | 
|  | /// with [value]. | 
|  | /// | 
|  | /// This will use `className.name = value` if possible, otherwise it will use | 
|  | /// `dart.defineValue(className, name, value)`. This is required when | 
|  | /// `FunctionNode.prototype` already defins a getters with the same name. | 
|  | @protected | 
|  | js_ast.Expression defineValueOnClass(Class c, js_ast.Expression className, | 
|  | js_ast.Expression nameExpr, js_ast.Expression value) { | 
|  | var args = [className, nameExpr, value]; | 
|  | if (nameExpr is js_ast.LiteralString) { | 
|  | var name = nameExpr.valueWithoutQuotes; | 
|  | if (js_ast.isFunctionPrototypeGetter(name) || | 
|  | superclassHasStatic(c, name)) { | 
|  | return runtimeCall('defineValue(#, #, #)', args); | 
|  | } | 
|  | } | 
|  | return js.call('#.# = #', args); | 
|  | } | 
|  |  | 
|  | /// Caches a constant (list/set/map or class instance) in a variable, so it's | 
|  | /// only canonicalized once at this location in the code, which improves | 
|  | /// performance. | 
|  | /// | 
|  | /// This method ensures the constant is not initialized until use. | 
|  | /// | 
|  | /// The expression [jsExpr] should contain the already-canonicalized constant. | 
|  | /// If the constant is not canonicalized yet, it should be wrapped in the | 
|  | /// appropriate call, such as: | 
|  | /// | 
|  | /// - dart.constList (for Lists), | 
|  | /// - dart.constMap (for Maps), | 
|  | /// - dart.constSet (for Sets), | 
|  | /// - dart.const (for other instances of classes) | 
|  | /// | 
|  | /// [canonicalizeConstObject] can be used for class instances; it will wrap | 
|  | /// the expression in `dart.const` and then call this method. | 
|  | /// | 
|  | /// If the same consant is used elsewhere (in this module, or another module), | 
|  | /// that will require a second canonicalization. In general it is uncommon | 
|  | /// to define the same large constant (such as lists, maps) in different | 
|  | /// locations, because that requires copy+paste, so in practice this | 
|  | /// optimization is rather effective (we should consider caching once | 
|  | /// per-module, though, as that would be relatively easy for the compiler to | 
|  | /// implement once we have a single Kernel backend). | 
|  | @protected | 
|  | js_ast.Expression cacheConst(js_ast.Expression jsExpr) { | 
|  | if (currentFunction == null) return jsExpr; | 
|  |  | 
|  | var temp = js_ast.TemporaryId('const'); | 
|  | moduleItems.add(js.statement('let #;', [temp])); | 
|  | return js.call('# || (# = #)', [temp, temp, jsExpr]); | 
|  | } | 
|  |  | 
|  | /// Emits a Dart Symbol with the given member [symbolName]. | 
|  | /// | 
|  | /// If the symbol refers to a private name, its library will be set to the | 
|  | /// [currentLibrary], so the Symbol is scoped properly. | 
|  | @protected | 
|  | js_ast.Expression emitDartSymbol(String symbolName) { | 
|  | // TODO(vsm): Handle qualified symbols correctly. | 
|  | var last = symbolName.split('.').last; | 
|  | var name = js.escapedString(symbolName, "'"); | 
|  | js_ast.Expression result; | 
|  | if (last.startsWith('_')) { | 
|  | var nativeSymbolAccessor = | 
|  | getSymbol(emitPrivateNameSymbol(currentLibrary, last)); | 
|  | result = js.call('new #.new(#, #)', [ | 
|  | emitConstructorAccess(privateSymbolType), | 
|  | name, | 
|  | nativeSymbolAccessor | 
|  | ]); | 
|  | } else { | 
|  | result = js.call( | 
|  | 'new #.new(#)', [emitConstructorAccess(internalSymbolType), name]); | 
|  | } | 
|  | return canonicalizeConstObject(result); | 
|  | } | 
|  |  | 
|  | /// Calls the `dart.const` function in "dart:_runtime" to canonicalize a | 
|  | /// constant instance of a user-defined class stored in [expr]. | 
|  | @protected | 
|  | js_ast.Expression canonicalizeConstObject(js_ast.Expression expr) => | 
|  | cacheConst(runtimeCall('const(#)', [expr])); | 
|  |  | 
|  | /// Emits preamble for the module containing [libraries], and returns the | 
|  | /// list of module items for further items to be added. | 
|  | /// | 
|  | /// The preamble consists of initializing the identifiers for each library, | 
|  | /// that will be used to store their members. It also generates the | 
|  | /// appropriate ES6 `export` declaration to export them from this module. | 
|  | /// | 
|  | /// After the code for all of the library members is emitted, | 
|  | /// [emitImportsAndExtensionSymbols] should be used to emit imports/extension | 
|  | /// symbols into the list returned by this method. Finally, [finishModule] | 
|  | /// can be called to complete the module and return the resulting JS AST. | 
|  | /// | 
|  | /// This also initializes several fields: [runtimeModule], | 
|  | /// [extensionSymbolsModule], and the [_libraries] map needed by | 
|  | /// [emitLibraryName]. | 
|  | @protected | 
|  | List<js_ast.ModuleItem> startModule(Iterable<Library> libraries) { | 
|  | if (isBuildingSdk) { | 
|  | // Don't allow these to be renamed when we're building the SDK. | 
|  | // There is JS code in dart:* that depends on their names. | 
|  | runtimeModule = js_ast.Identifier('dart'); | 
|  | extensionSymbolsModule = js_ast.Identifier('dartx'); | 
|  | } else { | 
|  | // Otherwise allow these to be renamed so users can write them. | 
|  | runtimeModule = js_ast.TemporaryId('dart'); | 
|  | extensionSymbolsModule = js_ast.TemporaryId('dartx'); | 
|  | } | 
|  |  | 
|  | // Initialize our library variables. | 
|  | var items = <js_ast.ModuleItem>[]; | 
|  | var exports = <js_ast.NameSpecifier>[]; | 
|  |  | 
|  | if (isBuildingSdk) { | 
|  | // Bootstrap the ability to create Dart library objects. | 
|  | var libraryProto = js_ast.TemporaryId('_library'); | 
|  | items.add(js.statement('const # = Object.create(null)', libraryProto)); | 
|  | items.add(js.statement( | 
|  | 'const # = Object.create(#)', [runtimeModule, libraryProto])); | 
|  | items.add(js.statement('#.library = #', [runtimeModule, libraryProto])); | 
|  | exports.add(js_ast.NameSpecifier(runtimeModule)); | 
|  | } | 
|  |  | 
|  | for (var library in libraries) { | 
|  | if (isBuildingSdk && isSdkInternalRuntime(library)) { | 
|  | _libraries[library] = runtimeModule; | 
|  | continue; | 
|  | } | 
|  | var libraryId = js_ast.TemporaryId(jsLibraryName(library)); | 
|  | _libraries[library] = libraryId; | 
|  | var alias = jsLibraryAlias(library); | 
|  | var aliasId = alias == null ? null : js_ast.TemporaryId(alias); | 
|  |  | 
|  | // TODO(vsm): Change back to `const`. | 
|  | // See https://github.com/dart-lang/sdk/issues/40380. | 
|  | items.add(js.statement( | 
|  | 'var # = Object.create(#.library)', [libraryId, runtimeModule])); | 
|  | exports.add(js_ast.NameSpecifier(libraryId, asName: aliasId)); | 
|  | } | 
|  |  | 
|  | // dart:_runtime has a magic module that holds extension method symbols. | 
|  | // TODO(jmesserly): find a cleaner design for this. | 
|  | if (isBuildingSdk) { | 
|  | var id = extensionSymbolsModule; | 
|  | // TODO(vsm): Change back to `const`. | 
|  | // See https://github.com/dart-lang/sdk/issues/40380. | 
|  | items.add(js | 
|  | .statement('var # = Object.create(#.library)', [id, runtimeModule])); | 
|  | exports.add(js_ast.NameSpecifier(id)); | 
|  | } | 
|  | items.add(js_ast.ExportDeclaration(js_ast.ExportClause(exports))); | 
|  |  | 
|  | if (isBuildingSdk) { | 
|  | // Initialize the private name function. | 
|  | // To bootstrap the SDK, this needs to be emitted before other code. | 
|  | var symbol = js_ast.TemporaryId('_privateNames'); | 
|  | items.add(js.statement('const # = Symbol("_privateNames")', symbol)); | 
|  | items.add(runtimeStatement(r''' | 
|  | privateName = function(library, name) { | 
|  | let names = library[#]; | 
|  | if (names == null) names = library[#] = new Map(); | 
|  | let symbol = names.get(name); | 
|  | if (symbol == null) names.set(name, symbol = Symbol(name)); | 
|  | return symbol; | 
|  | } | 
|  | ''', [symbol, symbol])); | 
|  | } | 
|  |  | 
|  | return items; | 
|  | } | 
|  |  | 
|  | /// Returns the canonical name to refer to the Dart library. | 
|  | js_ast.Identifier emitLibraryName(Library library) { | 
|  | setEmitIfIncrementalLibrary(library); | 
|  |  | 
|  | // Avoid adding the dart:_runtime to _imports when our runtime unit tests | 
|  | // import it explicitly. It will always be implicitly imported. | 
|  | if (isSdkInternalRuntime(library)) return runtimeModule; | 
|  |  | 
|  | // It's either one of the libraries in this module, or it's an import. | 
|  | return _libraries[library] ?? | 
|  | _imports.putIfAbsent( | 
|  | library, () => js_ast.TemporaryId(jsLibraryName(library))); | 
|  | } | 
|  |  | 
|  | /// Emits imports into [items]. | 
|  | @protected | 
|  | void emitImports(List<js_ast.ModuleItem> items) { | 
|  | var modules = <String, List<Library>>{}; | 
|  | for (var import in _imports.keys) { | 
|  | modules.putIfAbsent(libraryToModule(import), () => []).add(import); | 
|  | } | 
|  |  | 
|  | String coreModuleName; | 
|  | if (!_libraries.containsKey(coreLibrary)) { | 
|  | coreModuleName = libraryToModule(coreLibrary); | 
|  | } | 
|  |  | 
|  | modules.forEach((module, libraries) { | 
|  | if (!incrementalMode || incrementalModules.containsKey(module)) { | 
|  | var usedLibraries = incrementalModules[module]; | 
|  |  | 
|  | // Generate import directives. | 
|  | // | 
|  | // Our import variables are temps and can get renamed. Since our renaming | 
|  | // is integrated into js_ast, it is aware of this possibility and will | 
|  | // generate an "as" if needed. For example: | 
|  | // | 
|  | //     import {foo} from 'foo';         // if no rename needed | 
|  | //     import {foo as foo$} from 'foo'; // if rename was needed | 
|  | // | 
|  | var imports = <js_ast.NameSpecifier>[]; | 
|  | for (var library in libraries) { | 
|  | if (!incrementalMode || | 
|  | usedLibraries.contains(jsLibraryName(library))) { | 
|  | var alias = jsLibraryAlias(library); | 
|  | if (alias != null) { | 
|  | var aliasId = js_ast.TemporaryId(alias); | 
|  | imports.add( | 
|  | js_ast.NameSpecifier(aliasId, asName: _imports[library])); | 
|  | } else { | 
|  | imports.add(js_ast.NameSpecifier(_imports[library])); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (module == coreModuleName) { | 
|  | if (!incrementalMode || usedLibraries.contains(runtimeModule.name)) { | 
|  | imports.add(js_ast.NameSpecifier(runtimeModule)); | 
|  | } | 
|  | if (!incrementalMode || | 
|  | usedLibraries.contains(extensionSymbolsModule.name)) { | 
|  | imports.add(js_ast.NameSpecifier(extensionSymbolsModule)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!incrementalMode || imports.isNotEmpty) { | 
|  | items.add(js_ast.ImportDeclaration( | 
|  | namedImports: imports, from: js.string(module, "'"))); | 
|  | } | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Emits extension methods into [items]. | 
|  | @protected | 
|  | void emitExtensionSymbols(List<js_ast.ModuleItem> items, | 
|  | {bool forceExtensionSymbols = false}) { | 
|  | // Initialize extension symbols | 
|  | _extensionSymbols.forEach((name, id) { | 
|  | js_ast.Expression value = | 
|  | js_ast.PropertyAccess(extensionSymbolsModule, propertyName(name)); | 
|  | if (isBuildingSdk) { | 
|  | value = js.call('# = Symbol(#)', [value, js.string('dartx.$name')]); | 
|  | } else if (forceExtensionSymbols) { | 
|  | value = js.call( | 
|  | '# || (# = Symbol(#))', [value, value, js.string('dartx.$name')]); | 
|  | } | 
|  | // Emit hoisted extension symbols that are marked as noEmit in regular as | 
|  | // well as incremental mode (if needed) since they are going to be | 
|  | // referenced as such in the generated expression. | 
|  | if (!incrementalMode || | 
|  | _symbolContainer.incrementalModuleItems.contains(id)) { | 
|  | if (!_symbolContainer.canEmit(id)) { | 
|  | // Extension symbols marked with noEmit are managed manually. | 
|  | // TODO(vsm): Change back to `const`. | 
|  | // See https://github.com/dart-lang/sdk/issues/40380. | 
|  | items.add(js.statement('var # = #;', [id, value])); | 
|  | } | 
|  | } | 
|  | if (_symbolContainer.incrementalModuleItems.contains(id)) { | 
|  | setEmitIfIncremental( | 
|  | libraryToModule(coreLibrary), extensionSymbolsModule.name); | 
|  | } | 
|  | _symbolContainer[id] = value; | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Emits exports as imports into [items]. | 
|  | /// | 
|  | /// Use information from exports to re-define library variables referenced | 
|  | /// inside compiled expressions in incremental mode. That matches importing | 
|  | /// a current module into the symbol used to represent the library during | 
|  | /// original compilation in [ProgramCompiler.emitModule]. | 
|  | /// | 
|  | /// Example of exports emitted to JavaScript during emitModule: | 
|  | /// | 
|  | /// ``` | 
|  | /// dart.trackLibraries("web/main", { ... }); | 
|  | /// // Exports: | 
|  | /// return { | 
|  | ///  web__main: main | 
|  | /// }; | 
|  | /// ``` | 
|  | /// | 
|  | /// The transformation to imports during expression compilation converts the | 
|  | /// exports above to: | 
|  | /// | 
|  | /// ``` | 
|  | /// const web__main = require('web/main'); | 
|  | /// const main = web__main.web__main; | 
|  | /// ``` | 
|  | /// | 
|  | /// Where the compiled expression references `main`. | 
|  | @protected | 
|  | void emitExportsAsImports(List<js_ast.ModuleItem> items, Library current) { | 
|  | var exports = <js_ast.NameSpecifier>[]; | 
|  | assert(incrementalMode); | 
|  | assert(!isBuildingSdk); | 
|  |  | 
|  | var module = libraryToModule(current); | 
|  | var usedLibraries = incrementalModules[module] ?? {}; | 
|  |  | 
|  | if (usedLibraries.isNotEmpty) { | 
|  | _libraries.forEach((library, libraryId) { | 
|  | if (usedLibraries.contains(jsLibraryName(library))) { | 
|  | var alias = jsLibraryAlias(library); | 
|  | var aliasId = alias == null ? libraryId : js_ast.TemporaryId(alias); | 
|  | var asName = alias == null ? null : libraryId; | 
|  | exports.add(js_ast.NameSpecifier(aliasId, asName: asName)); | 
|  | } | 
|  | }); | 
|  |  | 
|  | items.add(js_ast.ImportDeclaration( | 
|  | namedImports: exports, from: js.string(module, "'"))); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Emits imports and extension methods into [items]. | 
|  | @protected | 
|  | void emitImportsAndExtensionSymbols(List<js_ast.ModuleItem> items, | 
|  | {bool forceExtensionSymbols = false}) { | 
|  | emitImports(items); | 
|  | emitExtensionSymbols(items, forceExtensionSymbols: forceExtensionSymbols); | 
|  | } | 
|  |  | 
|  | void _emitDebuggerExtensionInfo(String name) { | 
|  | var properties = <js_ast.Property>[]; | 
|  | var parts = <js_ast.Property>[]; | 
|  | _libraries.forEach((library, value) { | 
|  | // TODO(jacobr): we could specify a short library name instead of the | 
|  | // full library uri if we wanted to save space. | 
|  | var libraryName = js.escapedString(jsLibraryDebuggerName(library)); | 
|  | properties.add(js_ast.Property(libraryName, value)); | 
|  | var partNames = jsPartDebuggerNames(library); | 
|  | if (partNames.isNotEmpty) { | 
|  | parts.add(js_ast.Property(libraryName, js.stringArray(partNames))); | 
|  | } | 
|  | }); | 
|  | var module = js_ast.ObjectInitializer(properties, multiline: true); | 
|  | var partMap = js_ast.ObjectInitializer(parts, multiline: true); | 
|  |  | 
|  | // Track the module name for each library in the module. | 
|  | // This data is only required for debugging. | 
|  | moduleItems.add(runtimeStatement( | 
|  | 'trackLibraries(#, #, #, $sourceMapLocationID)', | 
|  | [js.string(name), module, partMap])); | 
|  | } | 
|  |  | 
|  | /// Returns an accessor for [id] via the symbol container. | 
|  | /// E.g., transforms $sym to S$5.$sym. | 
|  | /// | 
|  | /// A symbol lookup on an id marked no emit omits the symbol accessor. | 
|  | js_ast.Expression getSymbol(js_ast.Identifier id) { | 
|  | _symbolContainer.setEmitIfIncremental(id); | 
|  | return _symbolContainer.canEmit(id) ? _symbolContainer.access(id) : id; | 
|  | } | 
|  |  | 
|  | /// Returns the raw JS value associated with [id]. | 
|  | js_ast.Expression getSymbolValue(js_ast.Identifier id) { | 
|  | _symbolContainer.setEmitIfIncremental(id); | 
|  | return _symbolContainer[id]; | 
|  | } | 
|  |  | 
|  | /// Inserts a symbol into the symbol table. | 
|  | js_ast.Expression addSymbol(js_ast.Identifier id, js_ast.Expression symbol) { | 
|  | _symbolContainer[id] = symbol; | 
|  | _symbolContainer.setEmitIfIncremental(id); | 
|  | if (!containerizeSymbols) { | 
|  | _symbolContainer.setNoEmit(id); | 
|  | } | 
|  | return _symbolContainer[id]; | 
|  | } | 
|  |  | 
|  | /// Finishes the module created by [startModule], by combining the preable | 
|  | /// [items] with the [moduleItems] that have been emitted. | 
|  | /// | 
|  | /// The [moduleName] should specify the module's name, and the items should | 
|  | /// be the list resulting from startModule, with additional items added, | 
|  | /// but not including the contents of moduleItems (which will be handled by | 
|  | /// this method itself). | 
|  | /// | 
|  | /// Note, this function mutates the items list and returns it as the `body` | 
|  | /// field of the result. | 
|  | @protected | 
|  | js_ast.Program finishModule( | 
|  | List<js_ast.ModuleItem> items, String moduleName) { | 
|  | // TODO(jmesserly): there's probably further consolidation we can do | 
|  | // between DDC's two backends, by moving more code into this method, as the | 
|  | // code between `startModule` and `finishModule` is very similar in both. | 
|  | _emitDebuggerExtensionInfo(moduleName); | 
|  |  | 
|  | // Emit all top-level JS symbol containers. | 
|  | items.addAll(_symbolContainer.emit()); | 
|  |  | 
|  | // Add the module's code (produced by visiting compilation units, above) | 
|  | _copyAndFlattenBlocks(items, moduleItems); | 
|  | moduleItems.clear(); | 
|  |  | 
|  | // Build the module. | 
|  | return js_ast.Program(items, name: moduleName); | 
|  | } | 
|  |  | 
|  | /// Flattens blocks in [items] to a single list. | 
|  | /// | 
|  | /// This will not flatten blocks that are marked as being scopes. | 
|  | void _copyAndFlattenBlocks( | 
|  | List<js_ast.ModuleItem> result, Iterable<js_ast.ModuleItem> items) { | 
|  | for (var item in items) { | 
|  | if (item is js_ast.Block && !item.isScope) { | 
|  | _copyAndFlattenBlocks(result, item.statements); | 
|  | } else if (item != null) { | 
|  | result.add(item); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// This is an internal method used by [_emitMemberName] and the | 
|  | /// optimized `dart:_runtime extensionSymbol` builtin to get the symbol | 
|  | /// for `dartx.<name>`. | 
|  | /// | 
|  | /// Do not call this directly; you want [_emitMemberName], which knows how to | 
|  | /// handle the many details involved in naming. | 
|  | @protected | 
|  | js_ast.TemporaryId getExtensionSymbolInternal(String name) { | 
|  | if (!_extensionSymbols.containsKey(name)) { | 
|  | var id = js_ast.TemporaryId( | 
|  | '\$${js_ast.friendlyNameForDartOperator[name] ?? name}'); | 
|  | _extensionSymbols[name] = id; | 
|  | addSymbol(id, id); | 
|  | } | 
|  | var symbolId = _extensionSymbols[name]; | 
|  | _symbolContainer.setEmitIfIncremental(symbolId); | 
|  | return symbolId; | 
|  | } | 
|  |  | 
|  | /// Shorthand for identifier-like property names. | 
|  | /// For now, we emit them as strings and the printer restores them to | 
|  | /// identifiers if it can. | 
|  | // TODO(jmesserly): avoid the round tripping through quoted form. | 
|  | @protected | 
|  | js_ast.LiteralString propertyName(String name) => js.string(name, "'"); | 
|  |  | 
|  | /// Unique identifiers indicating the locations to inline the corresponding | 
|  | /// information. | 
|  | /// | 
|  | /// We cannot generate the source map before the script it is for is | 
|  | /// generated so we have generate the script including this identifier in the | 
|  | /// JS AST, and then replace it once the source map is generated.  Similarly, | 
|  | /// metrics include the size of the source map. | 
|  | static const String sourceMapLocationID = | 
|  | 'SourceMap3G5a8h6JVhHfdGuDxZr1EF9GQC8y0e6u'; | 
|  | static const String metricsLocationID = | 
|  | 'MetricsJ7xFWBfSv6ZjrW9yLb21GNzisZr3anSf5h'; | 
|  | } | 
|  |  | 
|  | /// Whether a variable with [name] is referenced in the [node]. | 
|  | bool variableIsReferenced(String name, js_ast.Node node) { | 
|  | var finder = _IdentifierFinder.instance; | 
|  | finder.nameToFind = name; | 
|  | finder.found = false; | 
|  | node.accept(finder); | 
|  | return finder.found; | 
|  | } | 
|  |  | 
|  | class _IdentifierFinder extends js_ast.BaseVisitorVoid { | 
|  | String nameToFind; | 
|  | bool found = false; | 
|  |  | 
|  | static final instance = _IdentifierFinder(); | 
|  |  | 
|  | @override | 
|  | void visitIdentifier(node) { | 
|  | if (node.name == nameToFind) found = true; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitNode(node) { | 
|  | if (!found) super.visitNode(node); | 
|  | } | 
|  | } | 
|  |  | 
|  | class YieldFinder extends js_ast.BaseVisitorVoid { | 
|  | bool hasYield = false; | 
|  | bool hasThis = false; | 
|  | bool _nestedFunction = false; | 
|  |  | 
|  | @override | 
|  | void visitThis(js_ast.This node) { | 
|  | hasThis = true; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitFunctionExpression(js_ast.FunctionExpression node) { | 
|  | var savedNested = _nestedFunction; | 
|  | _nestedFunction = true; | 
|  | super.visitFunctionExpression(node); | 
|  | _nestedFunction = savedNested; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitYield(js_ast.Yield node) { | 
|  | if (!_nestedFunction) hasYield = true; | 
|  | super.visitYield(node); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitNode(js_ast.Node node) { | 
|  | if (hasYield && hasThis) return; // found both, nothing more to do. | 
|  | super.visitNode(node); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Given the function [fn], returns a function declaration statement, binding | 
|  | /// `this` and `super` if necessary (using an arrow function). | 
|  | js_ast.Statement toBoundFunctionStatement( | 
|  | js_ast.Fun fn, js_ast.Identifier name) { | 
|  | if (usesThisOrSuper(fn)) { | 
|  | return js.statement('const # = (#) => {#}', [name, fn.params, fn.body]); | 
|  | } else { | 
|  | return js_ast.FunctionDeclaration(name, fn); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns whether [node] uses `this` or `super`. | 
|  | bool usesThisOrSuper(js_ast.Expression node) { | 
|  | var finder = _ThisOrSuperFinder.instance; | 
|  | finder.found = false; | 
|  | node.accept(finder); | 
|  | return finder.found; | 
|  | } | 
|  |  | 
|  | class _ThisOrSuperFinder extends js_ast.BaseVisitorVoid { | 
|  | bool found = false; | 
|  |  | 
|  | static final instance = _ThisOrSuperFinder(); | 
|  |  | 
|  | @override | 
|  | void visitThis(js_ast.This node) { | 
|  | found = true; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitSuper(js_ast.Super node) { | 
|  | found = true; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void visitNode(js_ast.Node node) { | 
|  | if (!found) super.visitNode(node); | 
|  | } | 
|  | } |