| // Copyright (c) 2015, 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. | 
 |  | 
 | part of dart2js.js_emitter.startup_emitter.model_emitter; | 
 |  | 
 | /// The fast startup emitter's goal is to minimize the amount of work that the | 
 | /// JavaScript engine has to do before it can start running user code. | 
 | /// | 
 | /// Whenever possible, the emitter uses object literals instead of updating | 
 | /// objects. | 
 | /// | 
 | /// Example: | 
 | /// | 
 | ///     // Holders are initialized directly with the classes and static | 
 | ///     // functions. | 
 | ///     var A = { Point: function Point(x, y) { this.x = x; this.y = y }, | 
 | ///               someStaticFunction: function someStaticFunction() { ... } }; | 
 | /// | 
 | ///     // Class-behavior is emitted in a prototype object that is directly | 
 | ///     // assigned: | 
 | ///     A.Point.prototype = { distanceTo: function(other) { ... } }; | 
 | /// | 
 | ///     // Inheritance is achieved by updating the prototype objects (hidden in | 
 | ///     // a helper function): | 
 | ///     A.Point.prototype.__proto__ = H.Object.prototype; | 
 | /// | 
 | /// The emitter doesn't try to be clever and emits everything beforehand. This | 
 | /// increases the output size, but improves performance. | 
 | /// | 
 | // The code relies on the fact that all Dart code is inside holders. As such | 
 | // we can use "global" names however we want. As long as we don't shadow | 
 | // JavaScript variables (like `Array`) we are free to chose whatever variable | 
 | // names we want. Furthermore, the pretty-printer minifies local variables, thus | 
 | // reducing their size. | 
 | const String _mainBoilerplate = ''' | 
 | (function dartProgram() { | 
 |  | 
 | if (#startupMetrics) { | 
 |   // Stash the metrics on the main unit IIFE. | 
 |   // TODO(sra): When the JavaScript local renamer is more intelligent, we can | 
 |   // simply use a local variable. | 
 |   (dartProgram.$STARTUP_METRICS = ${ModelEmitter.startupMetricsGlobal}) | 
 |       .add("dartProgramMs"); | 
 | } | 
 |  | 
 | // Copies the own properties from [from] to [to]. | 
 | function copyProperties(from, to) { | 
 |   var keys = Object.keys(from); | 
 |   for (var i = 0; i < keys.length; i++) { | 
 |     var key = keys[i]; | 
 |     to[key] = from[key]; | 
 |   } | 
 | } | 
 |  | 
 | // Copies the own properties from [from] to [to] if not already present in [to]. | 
 | function mixinPropertiesHard(from, to) { | 
 |   var keys = Object.keys(from); | 
 |   for (var i = 0; i < keys.length; i++) { | 
 |     var key = keys[i]; | 
 |     if (!to.hasOwnProperty(key)) { | 
 |       to[key] = from[key]; | 
 |     } | 
 |   } | 
 | } | 
 | // Copies the own properties from [from] to [to] (specialized version of | 
 | // `mixinPropertiesHard` when it is known the properties are disjoint). | 
 | function mixinPropertiesEasy(from, to) { | 
 |   Object.assign(to, from); | 
 | } | 
 |  | 
 | // Only use direct proto access to construct the prototype chain (instead of | 
 | // copying properties) on platforms where we know it works well (Chrome / d8). | 
 | var supportsDirectProtoAccess = #directAccessTestExpression; | 
 |  | 
 | // Makes [cls] inherit from [sup]. | 
 | // On Chrome, Firefox and recent IEs this happens by updating the internal | 
 | // proto-property of the classes 'prototype' field. | 
 | // Older IEs use `Object.create` and copy over the properties. | 
 | function inherit(cls, sup) { | 
 |   // cls.prototype.constructor carries the cached RTI. We could avoid this by | 
 |   // using ES6 classes, but the side effects of this need to be tested. | 
 |   cls.prototype.constructor = cls; | 
 |   cls.prototype[#operatorIsPrefix + cls.name] = cls; | 
 |  | 
 |   // The superclass is only null for the Dart Object. | 
 |   if (sup != null) { | 
 |     if (supportsDirectProtoAccess) { | 
 |       // Firefox doesn't like to update the prototypes, but when setting up | 
 |       // the hierarchy chain it's ok. | 
 |       cls.prototype.__proto__ = sup.prototype; | 
 |       return; | 
 |     } | 
 |     var clsPrototype = Object.create(sup.prototype); | 
 |     copyProperties(cls.prototype, clsPrototype); | 
 |     cls.prototype = clsPrototype; | 
 |   } | 
 | } | 
 |  | 
 | // Batched version of [inherit] for multiple classes from one superclass. | 
 | function inheritMany(sup, classes) { | 
 |   for (var i = 0; i < classes.length; i++) { | 
 |     inherit(classes[i], sup); | 
 |   } | 
 | } | 
 |  | 
 | // Mixes in the properties of [mixin] into [cls]. | 
 | function mixinEasy(cls, mixin) { | 
 |   mixinPropertiesEasy(mixin.prototype, cls.prototype); | 
 |   cls.prototype.constructor = cls; | 
 | } | 
 | function mixinHard(cls, mixin) { | 
 |   mixinPropertiesHard(mixin.prototype, cls.prototype); | 
 |   cls.prototype.constructor = cls; | 
 | } | 
 |  | 
 | // Creates a lazy field. | 
 | // | 
 | // A lazy field has a storage entry, [name], which holds the value, and a | 
 | // getter ([getterName]) to access the field. If the field wasn't set before | 
 | // the first access, it is initialized with the [initializer]. | 
 | function lazyOld(holder, name, getterName, initializer) { | 
 |   var uninitializedSentinel = holder; | 
 |   holder[name] = uninitializedSentinel; | 
 |   holder[getterName] = function() { | 
 |     holder[getterName] = function() { #cyclicThrow(name) }; | 
 |     var result; | 
 |     var sentinelInProgress = initializer; | 
 |     try { | 
 |       if (holder[name] === uninitializedSentinel) { | 
 |         result = holder[name] = sentinelInProgress; | 
 |         result = holder[name] = initializer(); | 
 |       } else { | 
 |         result = holder[name]; | 
 |       } | 
 |     } finally { | 
 |       // Use try-finally, not try-catch/throw as it destroys the stack | 
 |       // trace. | 
 |       if (result === sentinelInProgress) { | 
 |         // The lazy static (holder[name]) might have been set to a different | 
 |         // value. According to spec we still have to reset it to null, if | 
 |         // the initialization failed. | 
 |         holder[name] = null; | 
 |       } | 
 |       // TODO(floitsch): for performance reasons the function should probably | 
 |       // be unique for each static. | 
 |       holder[getterName] = function() { return this[name]; }; | 
 |     } | 
 |     return result; | 
 |   }; | 
 | } | 
 |  | 
 | // Creates a lazy field that uses non-nullable initialization semantics. | 
 | // | 
 | // A lazy field has a storage entry, [name], which holds the value, and a | 
 | // getter ([getterName]) to access the field. If the field wasn't set before | 
 | // the first access, it is initialized with the [initializer]. | 
 | function lazy(holder, name, getterName, initializer) { | 
 |   var uninitializedSentinel = holder; | 
 |   holder[name] = uninitializedSentinel; | 
 |   holder[getterName] = function() { | 
 |     if (holder[name] === uninitializedSentinel) { | 
 |       holder[name] = initializer(); | 
 |     } | 
 |     holder[getterName] = function() { return this[name]; }; | 
 |     return holder[name]; | 
 |   }; | 
 | } | 
 |  | 
 | // Creates a lazy final static field that uses non-nullable initialization | 
 | // semantics. | 
 | // | 
 | // A lazy final field has a storage entry, [name], which holds the value, and a | 
 | // getter ([getterName]) to access the field. The field is initialized on first | 
 | // access with the [initializer]. | 
 | function lazyFinal(holder, name, getterName, initializer) { | 
 |   var uninitializedSentinel = holder; | 
 |   holder[name] = uninitializedSentinel; | 
 |   holder[getterName] = function() { | 
 |     if (holder[name] === uninitializedSentinel) { | 
 |       var value = initializer(); | 
 |       if (holder[name] !== uninitializedSentinel) { | 
 |         // Since there is no setter, the only way to get here is via bounded | 
 |         // recursion, where `initializer` calls the lazy final getter. | 
 |         #throwLateFieldADI(name); | 
 |       } | 
 |       holder[name] = value; | 
 |     } | 
 |     // TODO(sra): Does the value need to be stored in the holder at all? | 
 |     // Potentially a call to the getter could be replaced with an access to | 
 |     // holder slot if dominated by a previous call to the getter. Does the | 
 |     // optimizer do this? If so, within a single function, the dominance | 
 |     // relations should allow a local copy via GVN, so it is not that valuable | 
 |     // an optimization for a `final` static variable. | 
 |     var finalValue = holder[name]; | 
 |     holder[getterName] = function() { return finalValue; }; | 
 |     return finalValue; | 
 |   }; | 
 | } | 
 |  | 
 | // Given a list, marks it as constant. | 
 | // | 
 | // The runtime ensures that const-lists cannot be modified. | 
 | function makeConstList(list) { | 
 |   // By assigning a function to the properties they become part of the | 
 |   // hidden class. The actual values of the fields don't matter, since we | 
 |   // only check if they exist. | 
 |   list.immutable\$list = Array; | 
 |   list.fixed\$length = Array; | 
 |   return list; | 
 | } | 
 |  | 
 | function convertToFastObject(properties) { | 
 |   // Create an instance that uses 'properties' as prototype. This should | 
 |   // make 'properties' a fast object. | 
 |   function t() {} | 
 |   t.prototype = properties; | 
 |   new t(); | 
 |   return properties; | 
 | } | 
 |  | 
 | function convertAllToFastObject(arrayOfObjects) { | 
 |   for (var i = 0; i < arrayOfObjects.length; ++i) { | 
 |     convertToFastObject(arrayOfObjects[i]); | 
 |   } | 
 | } | 
 |  | 
 | // This variable is used by the tearOffCode to guarantee unique functions per | 
 | // tear-offs. | 
 | var functionCounter = 0; | 
 | #tearOffCode; | 
 |  | 
 | // Each deferred hunk comes with its own types which are added to the end | 
 | // of the types-array. | 
 | // The `funType` passed to the `installTearOff` function below is relative to | 
 | // the hunk the function comes from. The `typesOffset` variable encodes the | 
 | // offset at which the new types will be added. | 
 | var typesOffset = 0; | 
 |  | 
 | /// Collect and canonicalize tear-off parameters. | 
 | /// | 
 | /// [container] is either the `prototype` of a class constructor, or the holder | 
 | /// for static functions. | 
 | /// | 
 | /// [funsOrNames] is an array of strings or functions. If it is a | 
 | /// name, then the function should be fetched from the container. The first | 
 | /// entry in that array *must* be a string. | 
 | // TODO(sra): It might be more readable to manually inline and simplify at the | 
 | // two calls. It would need to be assessed as it would likely make the object | 
 | // references polymorphic. | 
 | function tearOffParameters( | 
 |     container, isStatic, isIntercepted, | 
 |     requiredParameterCount, optionalParameterDefaultValues, | 
 |     callNames, funsOrNames, funType, applyIndex, needsDirectAccess) { | 
 |   if (typeof funType == "number") { | 
 |     // The [funType] can be a string type recipe or an index into the types | 
 |     // table.  If it points into the types-table we need to update the index, in | 
 |     // case the tear-off is part of a deferred hunk. | 
 |     funType += typesOffset; | 
 |   } | 
 |   return { | 
 |     #tpContainer: container, | 
 |     #tpIsStatic: isStatic, | 
 |     #tpIsIntercepted: isIntercepted, | 
 |     #tpRequiredParameterCount: requiredParameterCount, | 
 |     #tpOptionalParamaterDefaultValues: optionalParameterDefaultValues, | 
 |     #tpCallNames: callNames, | 
 |     #tpFunctionsOrNames: funsOrNames, | 
 |     #tpFunctionType: funType, | 
 |     #tpApplyIndex: applyIndex || 0, | 
 |     #tpNeedsDirectAccess: needsDirectAccess, | 
 |   } | 
 | } | 
 |  | 
 | /// Stores the static tear-off getter-function in the [holder]'s [getterName] | 
 | /// property. | 
 | function installStaticTearOff( | 
 |     holder, getterName, | 
 |     requiredParameterCount, optionalParameterDefaultValues, | 
 |     callNames, funsOrNames, funType, applyIndex) { | 
 |   // TODO(sra): Specialize for very common simple cases. | 
 |   var parameters = tearOffParameters( | 
 |       holder, true, false, | 
 |       requiredParameterCount, optionalParameterDefaultValues, | 
 |       callNames, funsOrNames, funType, applyIndex, false); | 
 |   var getterFunction = staticTearOffGetter(parameters); | 
 |   // TODO(sra): Returning [getterFunction] would be more versatile. We might | 
 |   // want to store the static tearoff getter in a different holder, or in no | 
 |   // holder if it is immediately called from the constant pool and otherwise | 
 |   // unreferenced. | 
 |   holder[getterName] = getterFunction; | 
 | } | 
 |  | 
 | /// Stores the instance tear-off getter-function in the [prototype]'s | 
 | /// [getterName] property. | 
 | function installInstanceTearOff( | 
 |     prototype, getterName, isIntercepted, | 
 |     requiredParameterCount, optionalParameterDefaultValues, | 
 |     callNames, funsOrNames, funType, applyIndex, needsDirectAccess) { | 
 |   isIntercepted = !!isIntercepted; // force to Boolean. | 
 |   var parameters = tearOffParameters( | 
 |       prototype, false, isIntercepted, | 
 |       requiredParameterCount, optionalParameterDefaultValues, | 
 |       callNames, funsOrNames, funType, applyIndex, !!needsDirectAccess); | 
 |   var getterFunction = instanceTearOffGetter(isIntercepted, parameters); | 
 |   prototype[getterName] = getterFunction; | 
 | } | 
 |  | 
 | // Instead of setting the interceptor tags directly we use this update | 
 | // function. This makes it easier for deferred fragments to contribute to the | 
 | // embedded global. | 
 | function setOrUpdateInterceptorsByTag(newTags) { | 
 |   var tags = #embeddedInterceptorTags; | 
 |   if (!tags) { | 
 |     #embeddedInterceptorTags = newTags; | 
 |     return; | 
 |   } | 
 |   copyProperties(newTags, tags); | 
 | } | 
 |  | 
 | // Instead of setting the leaf tags directly we use this update | 
 | // function. This makes it easier for deferred fragments to contribute to the | 
 | // embedded global. | 
 | function setOrUpdateLeafTags(newTags) { | 
 |   var tags = #embeddedLeafTags; | 
 |   if (!tags) { | 
 |     #embeddedLeafTags = newTags; | 
 |     return; | 
 |   } | 
 |   copyProperties(newTags, tags); | 
 | } | 
 |  | 
 | // Updates the types embedded global. | 
 | function updateTypes(newTypes) { | 
 |   var types = #embeddedTypes; | 
 |   var length = types.length; | 
 |   // The tear-off function uses another 'typesOffset' value cached in | 
 |   // [initializeDeferredHunk] so [updateTypes] can be called either before or | 
 |   // after the tearoffs have been installed. | 
 |   types.push.apply(types, newTypes); | 
 |   return length; | 
 | } | 
 |  | 
 | // Updates the given holder with the properties of the [newHolder]. | 
 | // This function is used when a deferred fragment is initialized. | 
 | function updateHolder(holder, newHolder) { | 
 |   copyProperties(newHolder, holder); | 
 |   return holder; | 
 | } | 
 |  | 
 | var #hunkHelpers = (function(){ | 
 |   var mkInstance = function( | 
 |       isIntercepted, requiredParameterCount, optionalParameterDefaultValues, | 
 |       callNames, applyIndex) { | 
 |     return function(container, getterName, name, funType) { | 
 |       return installInstanceTearOff( | 
 |           container, getterName, isIntercepted, | 
 |           requiredParameterCount, optionalParameterDefaultValues, | 
 |           callNames, [name], funType, applyIndex, | 
 |           /*needsDirectAccess:*/ false); | 
 |     } | 
 |   }, | 
 |  | 
 |   mkStatic = function( | 
 |       requiredParameterCount, optionalParameterDefaultValues, | 
 |       callNames, applyIndex) { | 
 |     return function(container, getterName, name, funType) { | 
 |       return installStaticTearOff( | 
 |           container, getterName, | 
 |           requiredParameterCount, optionalParameterDefaultValues, | 
 |           callNames, [name], funType, applyIndex); | 
 |     } | 
 |   }; | 
 |  | 
 |   // TODO(sra): Minify properties of 'hunkHelpers'. | 
 |   return { | 
 |     inherit: inherit, | 
 |     inheritMany: inheritMany, | 
 |     mixin: mixinEasy, | 
 |     mixinHard: mixinHard, | 
 |     installStaticTearOff: installStaticTearOff, | 
 |     installInstanceTearOff: installInstanceTearOff, | 
 |  | 
 |         // Unintercepted methods. | 
 |     _instance_0u: mkInstance(0, 0, null, [#call0selector], 0), | 
 |     _instance_1u: mkInstance(0, 1, null, [#call1selector], 0), | 
 |     _instance_2u: mkInstance(0, 2, null, [#call2selector], 0), | 
 |  | 
 |         // Intercepted methods. | 
 |     _instance_0i: mkInstance(1, 0, null, [#call0selector], 0), | 
 |     _instance_1i: mkInstance(1, 1, null, [#call1selector], 0), | 
 |     _instance_2i: mkInstance(1, 2, null, [#call2selector], 0), | 
 |  | 
 |         // Static methods. | 
 |     _static_0: mkStatic(0, null, [#call0selector], 0), | 
 |     _static_1: mkStatic(1, null, [#call1selector], 0), | 
 |     _static_2: mkStatic(2, null, [#call2selector], 0), | 
 |  | 
 |     makeConstList: makeConstList, | 
 |     lazy: lazy, | 
 |     lazyFinal: lazyFinal, | 
 |     lazyOld: lazyOld, | 
 |     updateHolder: updateHolder, | 
 |     convertToFastObject: convertToFastObject, | 
 |     updateTypes: updateTypes, | 
 |     setOrUpdateInterceptorsByTag: setOrUpdateInterceptorsByTag, | 
 |     setOrUpdateLeafTags: setOrUpdateLeafTags, | 
 |   }; | 
 | })(); | 
 |  | 
 | // Every deferred hunk (i.e. fragment) is a function that we can invoke to | 
 | // initialize it. At this moment it contributes its data to the main hunk. | 
 | function initializeDeferredHunk(hunk) { | 
 |   // Update the typesOffset for the next deferred library. | 
 |   typesOffset = #embeddedTypes.length; | 
 |  | 
 |   // TODO(floitsch): extend natives. | 
 |   hunk(hunkHelpers, #embeddedGlobalsObject, holders, #staticState); | 
 | } | 
 |  | 
 | if (#isTrackingAllocations) { | 
 |   var allocations = #deferredGlobal['allocations'] = {}; | 
 | } | 
 |  | 
 | // Creates the holders. | 
 | #holders; | 
 |  | 
 | // TODO(floitsch): we should build this object as a literal. | 
 | var #staticStateDeclaration = {}; | 
 |  | 
 | // Sets the prototypes of classes. | 
 | #prototypes; | 
 | // Sets aliases of methods (on the prototypes of classes). | 
 | #aliases; | 
 | // Installs the tear-offs of functions. | 
 | #tearOffs; | 
 | // Builds the inheritance structure. | 
 | #inheritance; | 
 |  | 
 | // Emits the embedded globals. This needs to be before constants so the embedded | 
 | // global type resources are available for generating constants. | 
 | #embeddedGlobalsPart1; | 
 |  | 
 | // Adds the subtype rules for the new RTI. | 
 | #typeRules; | 
 |  | 
 | // Adds the variance table for the new RTI. | 
 | #variances; | 
 |  | 
 | // Shared strings need to be initialized before constants. | 
 | #sharedStrings; | 
 |  | 
 | // Shared types need to be initialized before constants. | 
 | #sharedTypeRtis; | 
 |  | 
 | // Instantiates all constants. | 
 | #constants; | 
 |  | 
 | // Adds to the embedded globals. A few globals refer to constants. | 
 | #embeddedGlobalsPart2; | 
 |  | 
 | // Initializes the static non-final fields (with their constant values). | 
 | #staticNonFinalFields; | 
 | // Creates lazy getters for statics that must run initializers on first access. | 
 | #lazyStatics; | 
 |  | 
 | // Sets up the native support. | 
 | // Native-support uses setOrUpdateInterceptorsByTag and setOrUpdateLeafTags. | 
 | #nativeSupport; | 
 |  | 
 | // Sets up the js-interop support. | 
 | #jsInteropSupport; | 
 |  | 
 | // Ensure holders are in fast mode, now we have finished adding things. | 
 | convertAllToFastObject(holders); | 
 | convertToFastObject(#staticState); | 
 |  | 
 | // Invokes main (making sure that it records the 'current-script' value). | 
 | #invokeMain; | 
 | })(); | 
 | '''; | 
 |  | 
 | /// An expression that returns `true` if `__proto__` can be assigned to stitch | 
 | /// together a prototype chain, and the performance is good. | 
 | const String _directAccessTestExpression = r''' | 
 |   (function () { | 
 |     var cls = function () {}; | 
 |     cls.prototype = {'p': {}}; | 
 |     var object = new cls(); | 
 |     if (!(object.__proto__ && object.__proto__.p === cls.prototype.p)) | 
 |       return false; | 
 |  | 
 |     try { | 
 |       // Are we running on a platform where the performance is good? | 
 |       // (i.e. Chrome or d8). | 
 |  | 
 |       // Chrome userAgent? | 
 |       if (typeof navigator != "undefined" && | 
 |           typeof navigator.userAgent == "string" && | 
 |           navigator.userAgent.indexOf("Chrome/") >= 0) return true; | 
 |       // d8 version() looks like "N.N.N.N", jsshell version() like "N". | 
 |       if (typeof version == "function" && | 
 |           version.length == 0) { | 
 |         var v = version(); | 
 |         if (/^\d+\.\d+\.\d+\.\d+$/.test(v)) return true; | 
 |       } | 
 |     } catch(_) {} | 
 |  | 
 |     return false; | 
 |   })() | 
 | '''; | 
 |  | 
 | /// Soft-deferred fragments are built similarly to the main fragment. | 
 |  | 
 | /// Deferred fragments (aka 'hunks') are built similarly to the main fragment. | 
 | /// | 
 | /// However, at specific moments they need to contribute their data. | 
 | /// For example, once the holders have been created, they are included into | 
 | /// the main holders. | 
 | /// | 
 | /// This template is used for Dart 2. | 
 | const String _deferredBoilerplate = ''' | 
 | function(hunkHelpers, #embeddedGlobalsObject, holdersList, #staticState) { | 
 |  | 
 | // Builds the holders. They only contain the data for new holders. | 
 | // If names are not set on functions, we do it now. Finally, updates the | 
 | // holders of the main-fragment. Uses the provided holdersList to access the | 
 | // main holders. | 
 | // The local holders are replaced by the combined holders. This is necessary | 
 | // for the inheritance setup below. | 
 | #updateHolders; | 
 | // Sets the prototypes of the new classes. | 
 | #prototypes; | 
 | // Add signature function types and compute the types offset in `init.types`. | 
 | // These can only refer to regular classes and in Dart 2 only closures have | 
 | // function types so the `typesOffset` has been safely computed before it's | 
 | // referred in the signatures of the `closures` below. | 
 | var #typesOffset = hunkHelpers.updateTypes(#types); | 
 | #closures; | 
 | // Sets aliases of methods (on the prototypes of classes). | 
 | #aliases; | 
 | // Installs the tear-offs of functions. | 
 | #tearOffs; | 
 | // Builds the inheritance structure. | 
 | #inheritance; | 
 |  | 
 | // Adds the subtype rules for the new RTI. | 
 | #typeRules; | 
 |  | 
 | // Adds the variance table for the new RTI. | 
 | #variances; | 
 |  | 
 | #sharedStrings; | 
 |  | 
 | #sharedTypeRtis; | 
 | // Instantiates all constants of this deferred fragment. | 
 | // Note that the constant-holder has been updated earlier and storing the | 
 | // constant values in the constant-holder makes them available globally. | 
 | #constants; | 
 | // Initializes the static non-final fields (with their constant values). | 
 | #staticNonFinalFields; | 
 | // Creates lazy getters for statics that must run initializers on first access. | 
 | #lazyStatics; | 
 |  | 
 | // Native-support uses setOrUpdateInterceptorsByTag and setOrUpdateLeafTags. | 
 | #nativeSupport; | 
 | }'''; | 
 |  | 
 | /// This class builds a JavaScript tree for a given fragment. | 
 | /// | 
 | /// A fragment is generally written into a separate file so that it can be | 
 | /// loaded dynamically when a deferred library is loaded. | 
 | /// | 
 | /// This class is stateless and can be reused for different fragments. | 
 | class FragmentEmitter { | 
 |   final CompilerOptions _options; | 
 |   final DumpInfoTask _dumpInfoTask; | 
 |   final Namer _namer; | 
 |   final Emitter _emitter; | 
 |   final ConstantEmitter _constantEmitter; | 
 |   final ModelEmitter _modelEmitter; | 
 |   final NativeEmitter _nativeEmitter; | 
 |   final JClosedWorld _closedWorld; | 
 |   final CodegenWorld _codegenWorld; | 
 |   RecipeEncoder _recipeEncoder; | 
 |   RulesetEncoder _rulesetEncoder; | 
 |   final DeferredHolderExpressionFinalizer _holderFinalizer; | 
 |  | 
 |   ClassHierarchy get _classHierarchy => _closedWorld.classHierarchy; | 
 |   CommonElements get _commonElements => _closedWorld.commonElements; | 
 |   DartTypes get _dartTypes => _closedWorld.dartTypes; | 
 |   JElementEnvironment get _elementEnvironment => | 
 |       _closedWorld.elementEnvironment; | 
 |   RuntimeTypesNeed get _rtiNeed => _closedWorld.rtiNeed; | 
 |  | 
 |   js.Name _call0Name, _call1Name, _call2Name; | 
 |   js.Name get call0Name => | 
 |       _call0Name ??= _namer.getNameForJsGetName(null, JsGetName.CALL_PREFIX0); | 
 |   js.Name get call1Name => | 
 |       _call1Name ??= _namer.getNameForJsGetName(null, JsGetName.CALL_PREFIX1); | 
 |   js.Name get call2Name => | 
 |       _call2Name ??= _namer.getNameForJsGetName(null, JsGetName.CALL_PREFIX2); | 
 |   List<js.Name> _callNamesByArity; | 
 |   List<js.Name> get callNamesByArity => | 
 |       _callNamesByArity ??= [call0Name, call1Name, call2Name]; | 
 |  | 
 |   FragmentEmitter( | 
 |       this._options, | 
 |       this._dumpInfoTask, | 
 |       this._namer, | 
 |       this._emitter, | 
 |       this._constantEmitter, | 
 |       this._modelEmitter, | 
 |       this._nativeEmitter, | 
 |       this._closedWorld, | 
 |       this._codegenWorld) | 
 |       : _holderFinalizer = | 
 |             DeferredHolderExpressionFinalizerImpl(_closedWorld.commonElements) { | 
 |     _recipeEncoder = RecipeEncoderImpl( | 
 |         _closedWorld, | 
 |         _options.disableRtiOptimization | 
 |             ? TrivialRuntimeTypesSubstitutions(_closedWorld) | 
 |             : RuntimeTypesImpl(_closedWorld), | 
 |         _closedWorld.nativeData, | 
 |         _closedWorld.commonElements); | 
 |     _rulesetEncoder = | 
 |         RulesetEncoder(_closedWorld.dartTypes, _emitter, _recipeEncoder); | 
 |   } | 
 |  | 
 |   js.Expression generateEmbeddedGlobalAccess(String global) => | 
 |       _emitter.generateEmbeddedGlobalAccess(global); | 
 |  | 
 |   js.Expression generateConstantReference(ConstantValue value) => | 
 |       _modelEmitter.generateConstantReference(value); | 
 |  | 
 |   js.Expression classReference(Class cls) { | 
 |     // TODO(joshualitt): This should be generated by | 
 |     // [DeferredHolderExpressionFinalizer]. | 
 |     return js | 
 |         .js('#.#', [_namer.readGlobalObjectForClass(cls.element), cls.name]); | 
 |   } | 
 |  | 
 |   void registerEntityAst(Entity entity, js.Node code, {LibraryEntity library}) { | 
 |     _dumpInfoTask.registerEntityAst(entity, code); | 
 |     // TODO(sigmund): stop recoding associations twice, dump-info already | 
 |     // has library to element dependencies to recover this data. | 
 |     if (library != null) _dumpInfoTask.registerEntityAst(library, code); | 
 |   } | 
 |  | 
 |   PreFragment emitPreFragment(DeferredFragment fragment, bool estimateSize) { | 
 |     var classPrototypes = emitPrototypes(fragment, includeClosures: false); | 
 |     var closurePrototypes = emitPrototypes(fragment, includeClosures: true); | 
 |     var inheritance = emitInheritance(fragment); | 
 |     var methodAliases = emitInstanceMethodAliases(fragment); | 
 |     var tearOffs = emitInstallTearOffs(fragment); | 
 |     var constants = emitConstants(fragment); | 
 |     var typeRules = emitTypeRules(fragment); | 
 |     var variances = emitVariances(fragment); | 
 |     var staticNonFinalFields = emitStaticNonFinalFields(fragment); | 
 |     var lazyInitializers = emitLazilyInitializedStatics(fragment); | 
 |     // TODO(floitsch): only call emitNativeSupport if we need native. | 
 |     var nativeSupport = emitNativeSupport(fragment); | 
 |     int size = 0; | 
 |     if (estimateSize) { | 
 |       var estimator = SizeEstimator(); | 
 |       estimator.visit(classPrototypes); | 
 |       estimator.visit(closurePrototypes); | 
 |       estimator.visit(inheritance); | 
 |       estimator.visit(methodAliases); | 
 |       estimator.visit(tearOffs); | 
 |       estimator.visit(constants); | 
 |       estimator.visit(typeRules); | 
 |       estimator.visit(variances); | 
 |       estimator.visit(staticNonFinalFields); | 
 |       estimator.visit(lazyInitializers); | 
 |       estimator.visit(nativeSupport); | 
 |       size = estimator.charCount; | 
 |     } | 
 |     var emittedOutputUnit = EmittedOutputUnit( | 
 |         fragment, | 
 |         fragment.outputUnit, | 
 |         fragment.libraries, | 
 |         classPrototypes, | 
 |         closurePrototypes, | 
 |         inheritance, | 
 |         methodAliases, | 
 |         tearOffs, | 
 |         constants, | 
 |         typeRules, | 
 |         variances, | 
 |         staticNonFinalFields, | 
 |         lazyInitializers, | 
 |         nativeSupport); | 
 |     return PreFragment(fragment.outputFileName, emittedOutputUnit, size); | 
 |   } | 
 |  | 
 |   js.Statement emitMainFragment( | 
 |       Program program, | 
 |       Map<String, List<FinalizedFragment>> fragmentsToLoad, | 
 |       DeferredLoadingState deferredLoadingState) { | 
 |     MainFragment fragment = program.fragments.first; | 
 |  | 
 |     // Emit holder code. | 
 |     var holderCode = emitHolderCode(fragment.libraries); | 
 |     var holderDeclaration = DeferredHolderResource( | 
 |         DeferredHolderResourceKind.mainFragment, | 
 |         mainResourceName, | 
 |         [fragment], | 
 |         holderCode); | 
 |     js.Statement mainCode = js.js.statement(_mainBoilerplate, { | 
 |       // TODO(29455): 'hunkHelpers' displaces other names, so don't minify it. | 
 |       'hunkHelpers': js.VariableDeclaration('hunkHelpers', allowRename: false), | 
 |       'directAccessTestExpression': js.js(_directAccessTestExpression), | 
 |       'cyclicThrow': _emitter | 
 |           .staticFunctionAccess(_closedWorld.commonElements.cyclicThrowHelper), | 
 |       'throwLateFieldADI': _emitter | 
 |           .staticFunctionAccess(_closedWorld.commonElements.throwLateFieldADI), | 
 |       'operatorIsPrefix': js.string(_namer.fixedNames.operatorIsPrefix), | 
 |       'tearOffCode': js.Block( | 
 |           buildTearOffCode(_options, _emitter, _closedWorld.commonElements)), | 
 |       'embeddedTypes': generateEmbeddedGlobalAccess(TYPES), | 
 |       'embeddedInterceptorTags': | 
 |           generateEmbeddedGlobalAccess(INTERCEPTORS_BY_TAG), | 
 |       'embeddedLeafTags': generateEmbeddedGlobalAccess(LEAF_TAGS), | 
 |       'embeddedGlobalsObject': js.js("init"), | 
 |       'staticStateDeclaration': DeferredHolderParameter(), | 
 |       'staticState': DeferredHolderParameter(), | 
 |       'holders': holderDeclaration, | 
 |       'startupMetrics': _closedWorld.backendUsage.requiresStartupMetrics, | 
 |  | 
 |       // Tearoff parameters: | 
 |       'tpContainer': js.string(TearOffParametersPropertyNames.container), | 
 |       'tpIsStatic': js.string(TearOffParametersPropertyNames.isStatic), | 
 |       'tpIsIntercepted': | 
 |           js.string(TearOffParametersPropertyNames.isIntercepted), | 
 |       'tpRequiredParameterCount': | 
 |           js.string(TearOffParametersPropertyNames.requiredParameterCount), | 
 |       'tpOptionalParamaterDefaultValues': js.string( | 
 |           TearOffParametersPropertyNames.optionalParameterDefaultValues), | 
 |       'tpCallNames': js.string(TearOffParametersPropertyNames.callNames), | 
 |       'tpFunctionsOrNames': | 
 |           js.string(TearOffParametersPropertyNames.funsOrNames), | 
 |       'tpFunctionType': js.string(TearOffParametersPropertyNames.funType), | 
 |       'tpApplyIndex': js.string(TearOffParametersPropertyNames.applyIndex), | 
 |       'tpNeedsDirectAccess': | 
 |           js.string(TearOffParametersPropertyNames.needsDirectAccess), | 
 |  | 
 |       //'callName': js.string(_namer.fixedNames.callNameField), | 
 |       //'stubName': js.string(_namer.stubNameField), | 
 |       //'argumentCount': js.string(_namer.fixedNames.requiredParameterField), | 
 |       //'defaultArgumentValues': js.string(_namer.fixedNames.defaultValuesField), | 
 |       'deferredGlobal': ModelEmitter.deferredInitializersGlobal, | 
 |       'isTrackingAllocations': _options.experimentalTrackAllocations, | 
 |       'prototypes': emitPrototypes(fragment), | 
 |       'inheritance': emitInheritance(fragment), | 
 |       'aliases': emitInstanceMethodAliases(fragment), | 
 |       'tearOffs': emitInstallTearOffs(fragment), | 
 |       'constants': emitConstants(fragment), | 
 |       'staticNonFinalFields': emitStaticNonFinalFields(fragment), | 
 |       'lazyStatics': emitLazilyInitializedStatics(fragment), | 
 |       'embeddedGlobalsPart1': emitEmbeddedGlobalsPart1( | 
 |           program, fragmentsToLoad, deferredLoadingState), | 
 |       'embeddedGlobalsPart2': | 
 |           emitEmbeddedGlobalsPart2(program, deferredLoadingState), | 
 |       'typeRules': emitTypeRules(fragment), | 
 |       'sharedStrings': StringReferenceResource(), | 
 |       'variances': emitVariances(fragment), | 
 |       'sharedTypeRtis': TypeReferenceResource(), | 
 |       'nativeSupport': emitNativeSupport(fragment), | 
 |       'jsInteropSupport': jsInteropAnalysis.buildJsInteropBootstrap( | 
 |               _codegenWorld, _closedWorld.nativeData, _namer) ?? | 
 |           js.EmptyStatement(), | 
 |       'invokeMain': fragment.invokeMain, | 
 |  | 
 |       'call0selector': js.quoteName(call0Name), | 
 |       'call1selector': js.quoteName(call1Name), | 
 |       'call2selector': js.quoteName(call2Name), | 
 |     }); | 
 |     // We assume emitMainFragment will be the last piece of code we emit. | 
 |     finalizeCode(mainResourceName, mainCode, holderCode, finalizeHolders: true); | 
 |     return mainCode; | 
 |   } | 
 |  | 
 |   js.Expression emitCodeFragment(CodeFragment fragment) { | 
 |     var holderCode = emitHolderCode(fragment.libraries); | 
 |  | 
 |     // TODO(sra): How do we tell if [deferredTypes] is empty? It is filled-in | 
 |     // later via the program finalizers. So we should defer the decision on the | 
 |     // emptiness of the fragment until the finalizers have run.  For now we seem | 
 |     // to get away with the fact that type indexes are either (1) main unit or | 
 |     // (2) local to the emitted unit, so there is no such thing as a type in a | 
 |     // deferred unit that is referenced from another deferred unit.  If we did | 
 |     // not emit any functions, then we probably did not use the signature types | 
 |     // in the OutputUnit's types, leaving them unused and tree-shaken. | 
 |  | 
 |     if (holderCode.isEmpty && fragment.isEmpty) { | 
 |       return null; | 
 |     } | 
 |  | 
 |     var resourceName = fragment.canonicalOutputUnit.name; | 
 |     var updateHolders = DeferredHolderResource( | 
 |         DeferredHolderResourceKind.deferredFragment, | 
 |         resourceName, | 
 |         fragment.fragments, | 
 |         holderCode); | 
 |     js.Expression code = js.js(_deferredBoilerplate, { | 
 |       // TODO(floitsch): don't just reference 'init'. | 
 |       'embeddedGlobalsObject': js.Parameter('init'), | 
 |       'staticState': DeferredHolderParameter(), | 
 |       'updateHolders': updateHolders, | 
 |       'prototypes': fragment.classPrototypes, | 
 |       'closures': fragment.closurePrototypes, | 
 |       'inheritance': fragment.inheritance, | 
 |       'aliases': fragment.methodAliases, | 
 |       'tearOffs': fragment.tearOffs, | 
 |       'typeRules': fragment.typeRules, | 
 |       'variances': fragment.variances, | 
 |       'constants': fragment.constants, | 
 |       'staticNonFinalFields': fragment.staticNonFinalFields, | 
 |       'lazyStatics': fragment.lazyInitializers, | 
 |       'types': fragment.deferredTypes, | 
 |       'nativeSupport': fragment.nativeSupport, | 
 |       'typesOffset': _namer.typesOffsetName, | 
 |       'sharedStrings': StringReferenceResource(), | 
 |       'sharedTypeRtis': TypeReferenceResource(), | 
 |     }); | 
 |  | 
 |     if (_options.experimentStartupFunctions) { | 
 |       code = js.Parentheses(code); | 
 |     } | 
 |     finalizeCode(resourceName, code, holderCode); | 
 |     return code; | 
 |   } | 
 |  | 
 |   /// Adds code to a finalizer. | 
 |   void addCodeToFinalizer(void Function(js.Node) addCode, js.Node fragmentCode, | 
 |       Map<Entity, List<js.Property>> holderCode) { | 
 |     addCode(fragmentCode); | 
 |     for (var properties in holderCode.values) { | 
 |       for (var property in properties) { | 
 |         addCode(property); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// Finalizes the code for a fragment, and optionally finalizes holders. | 
 |   /// Finalizing holders must be the last step of the emitter. | 
 |   void finalizeCode(String resourceName, js.Node code, | 
 |       Map<Entity, List<js.Property>> holderCode, | 
 |       {bool finalizeHolders = false}) { | 
 |     StringReferenceFinalizer stringFinalizer = | 
 |         StringReferenceFinalizerImpl(_options.enableMinification); | 
 |     addCodeToFinalizer(stringFinalizer.addCode, code, holderCode); | 
 |     stringFinalizer.finalize(); | 
 |     TypeReferenceFinalizer typeFinalizer = TypeReferenceFinalizerImpl( | 
 |         _emitter, _commonElements, _recipeEncoder, _options.enableMinification); | 
 |     addCodeToFinalizer(typeFinalizer.addCode, code, holderCode); | 
 |     typeFinalizer.finalize(); | 
 |  | 
 |     // DeferredHolders need to be finalized last. In addition, finalizing | 
 |     // holders needs to be the very last thing the [FragmentEmitter] does before | 
 |     // we actually emit code. This is to ensure all holders are registered. | 
 |     // Note: Unlike the above finalizers, which are created and finalized | 
 |     // per output unit, the holderFinalizer is a whole-program finalizer, | 
 |     // which collects deferred [Node]s from each call to `finalizeCode` | 
 |     // before begin finalized once for the last (main) unit. | 
 |     void _addCode(js.Node code) { | 
 |       _holderFinalizer.addCode(resourceName, code); | 
 |     } | 
 |  | 
 |     addCodeToFinalizer(_addCode, code, holderCode); | 
 |     if (finalizeHolders) { | 
 |       _holderFinalizer.finalize(); | 
 |     } | 
 |   } | 
 |  | 
 |   /// Emits holder code for a list of libraries. We emit [Property]s directly | 
 |   /// into a map keyed by [Entity] because we don't yet know anything about the | 
 |   /// structure of the underlying holders and thus we cannot emit this code | 
 |   /// directly into the ast. | 
 |   Map<Entity, List<js.Property>> emitHolderCode(List<Library> libraries) { | 
 |     Map<Entity, List<js.Property>> holderCode = {}; | 
 |     for (Library library in libraries) { | 
 |       for (StaticMethod method in library.statics) { | 
 |         Map<js.Name, js.Expression> propertyMap = emitStaticMethod(method); | 
 |         propertyMap.forEach((js.Name key, js.Expression value) { | 
 |           var property = js.MethodDefinition(js.quoteName(key), value); | 
 |           Entity holderKey; | 
 |           if (method is StaticStubMethod) { | 
 |             // [StaticStubMethod]s should only be created for interceptors. | 
 |             assert(method.library == _commonElements.interceptorsLibrary); | 
 |             holderKey = method.library; | 
 |           } else { | 
 |             holderKey = method.element; | 
 |           } | 
 |           (holderCode[holderKey] ??= []).add(property); | 
 |           registerEntityAst(method.element, property, library: library.element); | 
 |         }); | 
 |       } | 
 |       for (Class cls in library.classes) { | 
 |         js.Expression constructor = emitConstructor(cls); | 
 |         var property = js.Property(js.quoteName(cls.name), constructor); | 
 |         (holderCode[cls.element] ??= []).add(property); | 
 |         registerEntityAst(cls.element, property, library: library.element); | 
 |       } | 
 |     } | 
 |     return holderCode; | 
 |   } | 
 |  | 
 |   /// Emits the given [method]. | 
 |   /// | 
 |   /// A Dart method might result in several JavaScript functions, if it | 
 |   /// requires stubs. The returned map contains the original method and all | 
 |   /// the stubs it needs. | 
 |   Map<js.Name, js.Expression> emitStaticMethod(StaticMethod method) { | 
 |     Map<js.Name, js.Expression> jsMethods = {}; | 
 |  | 
 |     // We don't need to install stub-methods. They can only be used when there | 
 |     // are tear-offs, in which case they are emitted there. | 
 |     assert(() { | 
 |       if (method is StaticDartMethod) { | 
 |         return method.needsTearOff || method.parameterStubs.isEmpty; | 
 |       } | 
 |       return true; | 
 |     }()); | 
 |     jsMethods[method.name] = method.code; | 
 |  | 
 |     return jsMethods; | 
 |   } | 
 |  | 
 |   /// Emits a constructor for the given class [cls]. | 
 |   /// | 
 |   /// The constructor is statically built. | 
 |   js.Expression emitConstructor(Class cls) { | 
 |     js.Name name = cls.name; | 
 |     // If the class is not directly instantiated we only need it for inheritance | 
 |     // or RTI. In either case we don't need its fields. | 
 |     if (cls.isNative || !cls.isDirectlyInstantiated) { | 
 |       return js.js('function #() { }', name); | 
 |     } | 
 |  | 
 |     var statements = <js.Statement>[]; | 
 |     var parameters = <js.Parameter>[]; | 
 |     var thisRef; | 
 |  | 
 |     if (_options.experimentalTrackAllocations) { | 
 |       String qualifiedName = | 
 |           "${cls.element.library.canonicalUri}:${cls.element.name}"; | 
 |       statements.add(js.js.statement('allocations["$qualifiedName"] = true')); | 
 |     } | 
 |  | 
 |     List<Field> emittedFields = cls.fields.where((f) => !f.isElided).toList(); | 
 |  | 
 |     // If there are many references to `this`, cache it in a local. | 
 |     if (emittedFields.length + (cls.hasRtiField ? 1 : 0) >= 4) { | 
 |       // Parameters are named t0, t1, etc, so '_' will not conflict. Forcing '_' | 
 |       // in minified mode works because no parameter or local also minifies to | 
 |       // '_' (the minifier doesn't know '_' is available). | 
 |       js.Name underscore = StringBackedName('_'); | 
 |       statements.add(js.js.statement('var # = this;', underscore)); | 
 |       thisRef = underscore; | 
 |     } else { | 
 |       thisRef = js.js('this'); | 
 |     } | 
 |  | 
 |     // Chain assignments of the same value, e.g. `this.b = this.a = null`. | 
 |     // Limit chain length so that the JavaScript parser has bounded recursion. | 
 |     const int maxChainLength = 30; | 
 |     js.Expression assignment = null; | 
 |     int chainLength = 0; | 
 |     ConstantValue previousConstant = null; | 
 |     void flushAssignment() { | 
 |       if (assignment != null) { | 
 |         statements.add(js.js.statement('#;', assignment)); | 
 |         assignment = null; | 
 |         chainLength = 0; | 
 |         previousConstant = null; | 
 |       } | 
 |     } | 
 |  | 
 |     for (Field field in emittedFields) { | 
 |       ConstantValue constant = field.initializerInAllocator; | 
 |       if (constant != null) { | 
 |         if (constant == previousConstant && chainLength < maxChainLength) { | 
 |           assignment = js.js('#.# = #', [thisRef, field.name, assignment]); | 
 |         } else { | 
 |           flushAssignment(); | 
 |           assignment = js.js('#.# = #', [ | 
 |             thisRef, | 
 |             field.name, | 
 |             _constantEmitter.generate(constant), | 
 |           ]); | 
 |         } | 
 |         ++chainLength; | 
 |         previousConstant = constant; | 
 |       } else { | 
 |         flushAssignment(); | 
 |         js.Parameter parameter = js.Parameter('t${parameters.length}'); | 
 |         parameters.add(parameter); | 
 |         statements.add( | 
 |             js.js.statement('#.# = #', [thisRef, field.name, parameter.name])); | 
 |       } | 
 |     } | 
 |     flushAssignment(); | 
 |  | 
 |     if (cls.hasRtiField) { | 
 |       js.Parameter parameter = js.Parameter('t${parameters.length}'); | 
 |       parameters.add(parameter); | 
 |       statements.add(js.js.statement( | 
 |           '#.# = #', [thisRef, _namer.rtiFieldJsName, parameter.name])); | 
 |     } | 
 |  | 
 |     return js.js('function #(#) { # }', [name, parameters, statements]); | 
 |   } | 
 |  | 
 |   /// Emits the prototype-section of the fragment. | 
 |   /// | 
 |   /// This section updates the prototype-property of all constructors in the | 
 |   /// global holders. | 
 |   /// | 
 |   /// If [includeClosures] is `true` only prototypes for closure classes are | 
 |   /// generated, if [includeClosures] is `false` only prototypes for non-closure | 
 |   /// classes are generated. Otherwise prototypes for all classes are generated. | 
 |   js.Statement emitPrototypes(Fragment fragment, {bool includeClosures}) { | 
 |     List<js.Statement> assignments = fragment.libraries | 
 |         .expand((Library library) => library.classes) | 
 |         .where((Class cls) { | 
 |       if (includeClosures != null) { | 
 |         if (cls.element.isClosure != includeClosures) { | 
 |           return false; | 
 |         } | 
 |       } | 
 |       return true; | 
 |     }).map((Class cls) { | 
 |       var proto = js.js.statement( | 
 |           '#.prototype = #;', [classReference(cls), emitPrototype(cls)]); | 
 |       ClassEntity element = cls.element; | 
 |       registerEntityAst(element, proto, library: element.library); | 
 |       return proto; | 
 |     }).toList(growable: false); | 
 |  | 
 |     return js.Block(assignments); | 
 |   } | 
 |  | 
 |   /// Emits the prototype of the given class [cls]. | 
 |   /// | 
 |   /// The prototype is generated as object literal. Inheritance is ignored. | 
 |   /// | 
 |   /// The prototype also includes the `is-property` that every class must have. | 
 |   // TODO(floitsch): we could avoid that property if we knew that it wasn't | 
 |   //    needed. | 
 |   js.Expression emitPrototype(Class cls) { | 
 |     Iterable<Method> methods = cls.methods; | 
 |     Iterable<Method> checkedSetters = cls.checkedSetters; | 
 |     Iterable<Method> isChecks = cls.isChecks; | 
 |     Iterable<Method> callStubs = cls.callStubs; | 
 |     Iterable<Method> noSuchMethodStubs = cls.noSuchMethodStubs; | 
 |     Iterable<Method> gettersSetters = cls.gettersSetters; | 
 |     Iterable<Method> allMethods = [ | 
 |       ...methods, | 
 |       ...checkedSetters, | 
 |       ...isChecks, | 
 |       ...callStubs, | 
 |       ...noSuchMethodStubs, | 
 |       ...gettersSetters | 
 |     ]; | 
 |  | 
 |     List<js.Property> properties = []; | 
 |  | 
 |     if (cls.superclass == null) { | 
 |       // This is Dart `Object`. Add properties that are usually added by | 
 |       // `inherit`. | 
 |  | 
 |       // TODO(sra): Adding properties here appears to be redundant with the call | 
 |       // to `inherit(P.Object, null)` in the generated code. See if we can | 
 |       // remove that. | 
 |  | 
 |       properties.add(js.Property(_namer.operatorIs(cls.element), js.number(1))); | 
 |     } | 
 |  | 
 |     allMethods.forEach((Method method) { | 
 |       emitInstanceMethod(method) | 
 |           .forEach((js.Expression name, js.Expression code) { | 
 |         js.Property property = code is js.Fun | 
 |             ? js.MethodDefinition(name, code) | 
 |             : js.Property(name, code); | 
 |         registerEntityAst(method.element, property); | 
 |         properties.add(property); | 
 |       }); | 
 |     }); | 
 |  | 
 |     // Closures have metadata that is often the same. We avoid repeated metadata | 
 |     // by putting it on a shared superclass. It is overridden in the subclass if | 
 |     // necessary. | 
 |  | 
 |     int arity = cls.sharedClosureApplyMetadata; | 
 |     if (arity != null) { | 
 |       // This is a closure base class that has the specialized `Function.apply` | 
 |       // metadata for functions taking exactly [arity] arguments. | 
 |       properties.add(js.Property(js.string(_namer.fixedNames.callCatchAllName), | 
 |           js.quoteName(callNamesByArity[arity]))); | 
 |       properties.add(js.Property( | 
 |           js.string(_namer.fixedNames.requiredParameterField), | 
 |           js.number(arity))); | 
 |     } | 
 |  | 
 |     if (cls.isClosureBaseClass) { | 
 |       // Most closures have no optional arguments. | 
 |       properties.add(js.Property( | 
 |           js.string(_namer.fixedNames.defaultValuesField), js.LiteralNull())); | 
 |     } | 
 |  | 
 |     return js.ObjectInitializer(properties); | 
 |   } | 
 |  | 
 |   /// Emits the given instance [method]. | 
 |   /// | 
 |   /// The given method may be a stub-method (for example for is-checks). | 
 |   /// | 
 |   /// If it is a Dart-method, all necessary stub-methods are emitted, too. In | 
 |   /// that case the returned map contains more than just one entry. | 
 |   /// | 
 |   /// If the method is a closure call-method, also returns the necessary | 
 |   /// properties in case the closure can be applied. | 
 |   Map<js.Expression, js.Expression> emitInstanceMethod(Method method) { | 
 |     var properties = <js.Expression, js.Expression>{}; | 
 |  | 
 |     properties[method.name] = method.code; | 
 |     if (method is InstanceMethod) { | 
 |       for (ParameterStubMethod stubMethod in method.parameterStubs) { | 
 |         properties[stubMethod.name] = stubMethod.code; | 
 |       } | 
 |  | 
 |       if (method.isClosureCallMethod && method.canBeApplied) { | 
 |         // The `call` method might flow to `Function.apply`, so the metadata for | 
 |         // `Function.apply` is needed. | 
 |  | 
 |         // Avoid adding the metadata if a superclass has the same metadata. | 
 |         if (!method.inheritsApplyMetadata) { | 
 |           js.Name applyName = method.applyIndex == 0 | 
 |               ? method.name | 
 |               : method.parameterStubs[method.applyIndex - 1].name; | 
 |           properties[js.string(_namer.fixedNames.callCatchAllName)] = | 
 |               js.quoteName(applyName); | 
 |           properties[js.string(_namer.fixedNames.requiredParameterField)] = | 
 |               js.number(method.requiredParameterCount); | 
 |  | 
 |           js.Expression defaultValues = | 
 |               _encodeOptionalParameterDefaultValues(method); | 
 |           // Default values property of `null` is stored on the common JS | 
 |           // superclass. | 
 |           if (defaultValues is! js.LiteralNull) { | 
 |             properties[js.string(_namer.fixedNames.defaultValuesField)] = | 
 |                 defaultValues; | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     return properties; | 
 |   } | 
 |  | 
 |   /// Emits the inheritance block of the fragment. | 
 |   /// | 
 |   /// In this section prototype chains are updated and mixin functions are | 
 |   /// copied. | 
 |   js.Statement emitInheritance(Fragment fragment) { | 
 |     List<js.Statement> inheritCalls = []; | 
 |     List<js.Statement> mixinCalls = []; | 
 |     // local caches of functions to allow minifaction of function name in call. | 
 |     LocalAliases locals = LocalAliases(); | 
 |  | 
 |     Set<Class> classesInFragment = Set(); | 
 |     for (Library library in fragment.libraries) { | 
 |       classesInFragment.addAll(library.classes); | 
 |     } | 
 |  | 
 |     Map<Class, List<Class>> subclasses = {}; | 
 |     Set<Class> seen = Set(); | 
 |  | 
 |     void collect(cls) { | 
 |       if (cls == null || seen.contains(cls)) return; | 
 |  | 
 |       Class superclass = cls.superclass; | 
 |       if (classesInFragment.contains(superclass)) { | 
 |         collect(superclass); | 
 |       } | 
 |  | 
 |       subclasses.putIfAbsent(superclass, () => []).add(cls); | 
 |  | 
 |       seen.add(cls); | 
 |     } | 
 |  | 
 |     for (Library library in fragment.libraries) { | 
 |       for (Class cls in library.classes) { | 
 |         collect(cls); | 
 |         if (cls.mixinClass != null) { | 
 |           js.Statement statement = js.js.statement('#(#, #)', [ | 
 |             _hasSimpleMixin(cls) | 
 |                 ? locals.find('_mixin', 'hunkHelpers.mixin') | 
 |                 : locals.find('_mixinHard', 'hunkHelpers.mixinHard'), | 
 |             classReference(cls), | 
 |             classReference(cls.mixinClass), | 
 |           ]); | 
 |           registerEntityAst(cls.element, statement, library: library.element); | 
 |           mixinCalls.add(statement); | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     for (Class superclass in subclasses.keys) { | 
 |       List<Class> list = subclasses[superclass]; | 
 |       js.Expression superclassReference = | 
 |           (superclass == null) ? js.LiteralNull() : classReference(superclass); | 
 |       if (list.length == 1) { | 
 |         Class cls = list.single; | 
 |         var statement = js.js.statement('#(#, #)', [ | 
 |           locals.find('_inherit', 'hunkHelpers.inherit'), | 
 |           classReference(cls), | 
 |           superclassReference | 
 |         ]); | 
 |         registerEntityAst(cls.element, statement, library: cls.element.library); | 
 |         inheritCalls.add(statement); | 
 |       } else { | 
 |         List<js.Expression> listElements = []; | 
 |         // Since inheritMany shares the superclass reference, we attribute it | 
 |         // only to the first subclass. | 
 |         ClassEntity firstClass = list.first.element; | 
 |         registerEntityAst(firstClass, superclassReference, | 
 |             library: firstClass.library); | 
 |         for (Class cls in list) { | 
 |           js.Expression reference = classReference(cls); | 
 |           registerEntityAst(cls.element, reference, | 
 |               library: cls.element.library); | 
 |           listElements.add(reference); | 
 |         } | 
 |         inheritCalls.add(js.js.statement('#(#, #)', [ | 
 |           locals.find('_inheritMany', 'hunkHelpers.inheritMany'), | 
 |           superclassReference, | 
 |           js.ArrayInitializer(listElements) | 
 |         ])); | 
 |       } | 
 |     } | 
 |  | 
 |     List<js.Statement> statements = []; | 
 |     if (locals.isNotEmpty) { | 
 |       statements.add(locals.toStatement()); | 
 |     } | 
 |     statements.addAll(inheritCalls); | 
 |     statements.addAll(mixinCalls); | 
 |     return wrapPhase('inheritance', statements); | 
 |   } | 
 |  | 
 |   /// Determines if the mixin methods can be applied to a mixin application | 
 |   /// class by a simple copy, or whether the class defines properties that would | 
 |   /// be clobbered by block-copying the mixin's properties, so a slower checking | 
 |   /// copy is needed. | 
 |   bool _hasSimpleMixin(Class cls) { | 
 |     List<Method> allMethods(Class cls) { | 
 |       return [ | 
 |         ...cls.methods, | 
 |         ...cls.checkedSetters, | 
 |         ...cls.isChecks, | 
 |         ...cls.callStubs, | 
 |         ...cls.noSuchMethodStubs, | 
 |         ...cls.gettersSetters | 
 |       ]; | 
 |     } | 
 |  | 
 |     final clsMethods = allMethods(cls); | 
 |     if (clsMethods.isEmpty) return true; | 
 |     // TODO(sra): Compare methods with those of `cls.mixinClass` to see if the | 
 |     // methods (and hence properties) will actually clash. If they are | 
 |     // non-overlapping, a simple copy might still be possible. | 
 |     return false; | 
 |   } | 
 |  | 
 |   /// Emits the setup of method aliases. | 
 |   /// | 
 |   /// This step consists of simply copying JavaScript functions to their | 
 |   /// aliased names so they point to the same function. | 
 |   js.Statement emitInstanceMethodAliases(Fragment fragment) { | 
 |     List<js.Statement> assignments = []; | 
 |  | 
 |     for (Library library in fragment.libraries) { | 
 |       for (Class cls in library.classes) { | 
 |         bool firstAlias = true; | 
 |         for (InstanceMethod method in cls.methods) { | 
 |           if (method.aliasName != null) { | 
 |             if (firstAlias) { | 
 |               firstAlias = false; | 
 |               js.Statement statement = js.js.statement( | 
 |                   assignments.isEmpty | 
 |                       ? 'var _ = #.prototype;' | 
 |                       : '_ = #.prototype', | 
 |                   classReference(cls)); | 
 |               registerEntityAst(method.element, statement); | 
 |               assignments.add(statement); | 
 |             } | 
 |             js.Statement statement = js.js.statement('_.# = _.#', | 
 |                 [js.quoteName(method.aliasName), js.quoteName(method.name)]); | 
 |             registerEntityAst(method.element, statement); | 
 |             assignments.add(statement); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |     return wrapPhase('aliases', assignments); | 
 |   } | 
 |  | 
 |   /// Encodes the optional default values so that the runtime Function.apply | 
 |   /// can use them. | 
 |   js.Expression _encodeOptionalParameterDefaultValues(DartMethod method) { | 
 |     // TODO(herhut): Replace [js.LiteralNull] with [js.ArrayHole]. | 
 |     if (method.optionalParameterDefaultValues is List) { | 
 |       List<ConstantValue> defaultValues = method.optionalParameterDefaultValues; | 
 |       if (defaultValues.isEmpty) { | 
 |         return js.LiteralNull(); | 
 |       } | 
 |       Iterable<js.Expression> elements = | 
 |           defaultValues.map(generateConstantReference); | 
 |       return js.js( | 
 |           'function() { return #; }', js.ArrayInitializer(elements.toList())); | 
 |     } else { | 
 |       Map<String, ConstantValue> defaultValues = | 
 |           method.optionalParameterDefaultValues; | 
 |       List<js.Property> properties = []; | 
 |       List<String> names = defaultValues.keys.toList(growable: false); | 
 |       // Sort the names the same way we sort them for the named-argument calling | 
 |       // convention. | 
 |       names.sort(); | 
 |  | 
 |       for (String name in names) { | 
 |         ConstantValue value = defaultValues[name]; | 
 |         properties.add( | 
 |             js.Property(js.string(name), generateConstantReference(value))); | 
 |       } | 
 |       return js.js( | 
 |           'function() { return #; }', js.ObjectInitializer(properties)); | 
 |     } | 
 |   } | 
 |  | 
 |   /// Wraps the statement in a named function to that it shows up as a unit in | 
 |   /// profiles. | 
 |   // TODO(sra): Should this be conditional? | 
 |   js.Statement wrapPhase(String name, List<js.Statement> statements) { | 
 |     js.Block block = js.Block(statements); | 
 |     if (statements.isEmpty) return block; | 
 |     return js.js.statement('(function #(){#})();', [name, block]); | 
 |   } | 
 |  | 
 |   /// Emits the section that installs tear-off getters. | 
 |   js.Statement emitInstallTearOffs(Fragment fragment) { | 
 |     LocalAliases locals = LocalAliases(); | 
 |  | 
 |     /// Emits the statement that installs a tear off for a method. | 
 |     /// | 
 |     /// Tear-offs might be passed to `Function.apply` which means that all | 
 |     /// calling-conventions (with or without optional positional/named | 
 |     /// arguments) are possible. As such, the tear-off needs enough information | 
 |     /// to fill in missing parameters. | 
 |     js.Statement emitInstallTearOff( | 
 |         js.Expression container, DartMethod method) { | 
 |       List<js.Name> callNames = []; | 
 |       List<js.Expression> funsOrNames = []; | 
 |  | 
 |       /// Adds the stub-method's code or name to the [funsOrNames] array. | 
 |       /// | 
 |       /// Static methods don't need stub-methods except for tear-offs. As such, | 
 |       /// they are not emitted in the prototype, but directly passed here. | 
 |       /// | 
 |       /// Instance-methods install the stub-methods in their prototype, and we | 
 |       /// use string-based redirections to find them there. | 
 |       void addFunOrName(StubMethod stubMethod) { | 
 |         if (method.isStatic) { | 
 |           funsOrNames.add(stubMethod.code); | 
 |         } else { | 
 |           funsOrNames.add(js.quoteName(stubMethod.name)); | 
 |         } | 
 |       } | 
 |  | 
 |       callNames.add(method.callName); | 
 |       // The first entry in the funsOrNames-array must be a string. | 
 |       funsOrNames.add(js.quoteName(method.name)); | 
 |       for (ParameterStubMethod stubMethod in method.parameterStubs) { | 
 |         js.Name callName = stubMethod.callName; | 
 |         // `callName` might be `null` if the method is called directly with some | 
 |         // CallStructure but it can be proven that the tearoff not called with | 
 |         // with that CallStructure, e.g. the closure does no need the defaulting | 
 |         // of arguments but some direct call does. | 
 |         if (callName != null) { | 
 |           callNames.add(callName); | 
 |           addFunOrName(stubMethod); | 
 |         } | 
 |       } | 
 |  | 
 |       final callNameArray = | 
 |           js.ArrayInitializer([...callNames.map(js.quoteName)]); | 
 |       final funsOrNamesArray = js.ArrayInitializer(funsOrNames); | 
 |  | 
 |       bool isIntercepted = false; | 
 |       if (method is InstanceMethod) { | 
 |         isIntercepted = method.isIntercepted; | 
 |       } | 
 |  | 
 |       int requiredParameterCount = method.requiredParameterCount; | 
 |       js.Expression optionalParameterDefaultValues = js.LiteralNull(); | 
 |       if (method.canBeApplied) { | 
 |         optionalParameterDefaultValues = | 
 |             _encodeOptionalParameterDefaultValues(method); | 
 |       } | 
 |  | 
 |       var applyIndex = js.number(method.applyIndex); | 
 |  | 
 |       if (method.isStatic) { | 
 |         if (requiredParameterCount <= 2 && | 
 |             callNames.length == 1 && | 
 |             optionalParameterDefaultValues is js.LiteralNull && | 
 |             method.applyIndex == 0) { | 
 |           js.Statement finish(int arity) { | 
 |             // Short form for exactly 0/1/2 arguments. | 
 |             var install = | 
 |                 locals.find('_static_${arity}', 'hunkHelpers._static_${arity}'); | 
 |             return js.js.statement(''' | 
 |                 #install(#container, #getterName, #name, #funType)''', { | 
 |               "install": install, | 
 |               "container": container, | 
 |               "getterName": js.quoteName(method.tearOffName), | 
 |               "name": funsOrNames.single, | 
 |               "funType": method.functionType, | 
 |             }); | 
 |           } | 
 |  | 
 |           var installedName = callNames.single; | 
 |           if (installedName == call0Name) return finish(0); | 
 |           if (installedName == call1Name) return finish(1); | 
 |           if (installedName == call2Name) return finish(2); | 
 |         } | 
 |  | 
 |         var install = | 
 |             locals.find('_static', 'hunkHelpers.installStaticTearOff'); | 
 |         return js.js.statement(''' | 
 |             #install(#container, #getterName, | 
 |                      #requiredParameterCount, #optionalParameterDefaultValues, | 
 |                       #callNames, #funsOrNames, #funType, #applyIndex)''', { | 
 |           "install": install, | 
 |           "container": container, | 
 |           "getterName": js.quoteName(method.tearOffName), | 
 |           "requiredParameterCount": js.number(requiredParameterCount), | 
 |           "optionalParameterDefaultValues": optionalParameterDefaultValues, | 
 |           "callNames": callNameArray, | 
 |           "funsOrNames": funsOrNamesArray, | 
 |           "funType": method.functionType, | 
 |           "applyIndex": applyIndex, | 
 |         }); | 
 |       } else { | 
 |         bool tearOffNeedsDirectAccess = | 
 |             (method as InstanceMethod).tearOffNeedsDirectAccess; | 
 |         if (requiredParameterCount <= 2 && | 
 |             callNames.length == 1 && | 
 |             optionalParameterDefaultValues is js.LiteralNull && | 
 |             method.applyIndex == 0 && | 
 |             !tearOffNeedsDirectAccess) { | 
 |           js.Statement finish(int arity) { | 
 |             // Short form for exactly 0/1/2 arguments. | 
 |             String isInterceptedTag = isIntercepted ? 'i' : 'u'; | 
 |             var install = locals.find('_instance_${arity}_${isInterceptedTag}', | 
 |                 'hunkHelpers._instance_${arity}${isInterceptedTag}'); | 
 |             return js.js.statement(''' | 
 |                 #install(#container, #getterName, #name, #funType)''', { | 
 |               "install": install, | 
 |               "container": container, | 
 |               "getterName": js.quoteName(method.tearOffName), | 
 |               "name": funsOrNames.single, | 
 |               "funType": method.functionType, | 
 |             }); | 
 |           } | 
 |  | 
 |           var installedName = callNames.single; | 
 |           if (installedName == call0Name) return finish(0); | 
 |           if (installedName == call1Name) return finish(1); | 
 |           if (installedName == call2Name) return finish(2); | 
 |         } | 
 |  | 
 |         var install = | 
 |             locals.find('_instance', 'hunkHelpers.installInstanceTearOff'); | 
 |         return js.js.statement(''' | 
 |             #install(#container, #getterName, #isIntercepted, | 
 |                      #requiredParameterCount, #optionalParameterDefaultValues, | 
 |                      #callNames, #funsOrNames, #funType, #applyIndex, | 
 |                      #tearOffNeedsDirectAccess)''', { | 
 |           "install": install, | 
 |           "container": container, | 
 |           "getterName": js.quoteName(method.tearOffName), | 
 |           // 'Truthy' values are ok for `isIntercepted`. | 
 |           "isIntercepted": js.number(isIntercepted ? 1 : 0), | 
 |           "requiredParameterCount": js.number(requiredParameterCount), | 
 |           "optionalParameterDefaultValues": optionalParameterDefaultValues, | 
 |           "callNames": callNameArray, | 
 |           "funsOrNames": funsOrNamesArray, | 
 |           "funType": method.functionType, | 
 |           "applyIndex": applyIndex, | 
 |           // 'Truthy' values are ok for `tearOffNeedsDirectAccess`. | 
 |           "tearOffNeedsDirectAccess": | 
 |               js.number(tearOffNeedsDirectAccess ? 1 : 0), | 
 |         }); | 
 |       } | 
 |     } | 
 |  | 
 |     List<js.Statement> inits = []; | 
 |     js.Expression temp; | 
 |  | 
 |     for (Library library in fragment.libraries) { | 
 |       for (StaticMethod method in library.statics) { | 
 |         // TODO(floitsch): can there be anything else than a StaticDartMethod? | 
 |         if (method is StaticDartMethod) { | 
 |           if (method.needsTearOff) { | 
 |             js.Statement statement = emitInstallTearOff( | 
 |                 _namer.readGlobalObjectForMember(method.element), method); | 
 |             registerEntityAst(method.element, statement, | 
 |                 library: library.element); | 
 |             inits.add(statement); | 
 |           } | 
 |         } | 
 |       } | 
 |       for (Class cls in library.classes) { | 
 |         var methods = cls.methods.where((dynamic m) => m.needsTearOff).toList(); | 
 |         js.Expression container = js.js("#.prototype", classReference(cls)); | 
 |         js.Expression reference = container; | 
 |         if (methods.length > 1) { | 
 |           if (temp == null) { | 
 |             inits.add(js.js.statement('var _;')); | 
 |             temp = js.js('_'); | 
 |           } | 
 |           // First call uses assignment to temp to cache the container. | 
 |           reference = js.js('# = #', [temp, container]); | 
 |         } | 
 |         for (InstanceMethod method in methods) { | 
 |           js.Statement statement = emitInstallTearOff(reference, method); | 
 |           registerEntityAst(method.element, statement); | 
 |           inits.add(statement); | 
 |           reference = temp; // Second and subsequent calls use temp. | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     if (locals.isNotEmpty) { | 
 |       inits.insert(0, locals.toStatement()); | 
 |     } | 
 |  | 
 |     return wrapPhase('installTearOffs', inits); | 
 |   } | 
 |  | 
 |   /// Emits the constants section. | 
 |   js.Statement emitConstants(Fragment fragment) { | 
 |     List<js.Statement> assignments = []; | 
 |     bool hasList = false; | 
 |     for (Constant constant in fragment.constants) { | 
 |       // TODO(25230): We only need to name constants that are used from function | 
 |       // bodies or from other constants in a different part. | 
 |       var assignment = js.js.statement('#.# = #', [ | 
 |         _namer.globalObjectForConstant(constant.value), | 
 |         constant.name, | 
 |         _constantEmitter.generate(constant.value) | 
 |       ]); | 
 |       _dumpInfoTask.registerConstantAst(constant.value, assignment); | 
 |       assignments.add(assignment); | 
 |       if (constant.value.isList) hasList = true; | 
 |     } | 
 |     if (hasList) { | 
 |       assignments.insert( | 
 |           0, js.js.statement('var makeConstList = hunkHelpers.makeConstList;')); | 
 |     } | 
 |     return wrapPhase('constants', assignments); | 
 |   } | 
 |  | 
 |   /// Emits the static non-final fields section. | 
 |   /// | 
 |   /// This section initializes all static non-final fields that don't require | 
 |   /// an initializer. | 
 |   js.Statement emitStaticNonFinalFields(Fragment fragment) { | 
 |     List<StaticField> fields = fragment.staticNonFinalFields; | 
 |     // TODO(sra): Chain assignments that have the same value, i.e. | 
 |     // | 
 |     //    $.x = null; $.y = null; $.z = null; | 
 |     // --> | 
 |     //    $.z = $.y = $.x = null; | 
 |     // | 
 |     Iterable<js.Statement> statements = fields.map((StaticField field) { | 
 |       // TODO(joshualitt): Distribute fields into per-unit holders and use a | 
 |       // deferred holder expression for the field assignment left-hand-side. | 
 |       js.Expression location = | 
 |           js.js('#.#', [_namer.globalObjectForStaticState(), field.name]); | 
 |       js.Statement statement; | 
 |       if (field.isInitializedByConstant) { | 
 |         statement = js.js.statement("# = #;", [location, field.code]); | 
 |       } else { | 
 |         // This is a bit of a hack. Field initializers are generated as a | 
 |         // function ending with a return statement. We replace the function | 
 |         // with the body block and replace the return statement with an | 
 |         // assignment to the field. | 
 |         // | 
 |         // Since unneeded blocks are not generated in the output, | 
 |         // the statement(s) of the initializes are inlined in the emitted code. | 
 |         // | 
 |         // This is a cheap way of supporting eager fields (as opposed to | 
 |         // generating one SSA graph for all eager fields) though it does not | 
 |         // avoid redundant declaration of local variable, for instance for | 
 |         // type arguments. | 
 |         js.Fun code = field.code; | 
 |         assert(code != null, "No code for $field"); | 
 |         if (code.params.isEmpty && | 
 |             code.body.statements.length == 1 && | 
 |             code.body.statements.last is js.Return) { | 
 |           // For now we only support initializers of the form | 
 |           // | 
 |           //   function() { return e; } | 
 |           // | 
 |           // To avoid unforeseen consequences of having parameters and locals | 
 |           // in the initializer code. | 
 |           js.Return last = code.body.statements.last; | 
 |           statement = js.js.statement("# = #;", [location, last.value]); | 
 |         } else { | 
 |           // Safe fallback in the event of a field initializer with no return | 
 |           // statement as the last statement. | 
 |           statement = js.js.statement("# = #();", [location, code]); | 
 |         } | 
 |       } | 
 |       registerEntityAst(field.element, statement, | 
 |           library: field.element.library); | 
 |       return statement; | 
 |     }); | 
 |     return wrapPhase('staticFields', statements.toList()); | 
 |   } | 
 |  | 
 |   /// Emits lazy fields. | 
 |   /// | 
 |   /// This section initializes all static (final and non-final) fields that | 
 |   /// require an initializer. | 
 |   js.Statement emitLazilyInitializedStatics(Fragment fragment) { | 
 |     List<StaticField> fields = fragment.staticLazilyInitializedFields; | 
 |     List<js.Statement> statements = []; | 
 |     LocalAliases locals = LocalAliases(); | 
 |     for (StaticField field in fields) { | 
 |       String helper = field.usesNonNullableInitialization | 
 |           ? field.isFinal | 
 |               ? locals.find('_lazyFinal', 'hunkHelpers.lazyFinal') | 
 |               : locals.find('_lazy', 'hunkHelpers.lazy') | 
 |           : locals.find('_lazyOld', 'hunkHelpers.lazyOld'); | 
 |       js.Expression staticFieldCode = field.code; | 
 |       if (staticFieldCode is js.Fun) { | 
 |         js.Fun fun = staticFieldCode; | 
 |         staticFieldCode = js.ArrowFunction(fun.params, fun.body, | 
 |                 asyncModifier: fun.asyncModifier) | 
 |             .withSourceInformation(fun.sourceInformation); | 
 |       } | 
 |       js.Statement statement = js.js.statement("#(#, #, #, #);", [ | 
 |         helper, | 
 |         _namer.globalObjectForStaticState(), | 
 |         js.quoteName(field.name), | 
 |         js.quoteName(field.getterName), | 
 |         staticFieldCode, | 
 |       ]); | 
 |  | 
 |       registerEntityAst(field.element, statement, | 
 |           library: field.element.library); | 
 |       statements.add(statement); | 
 |     } | 
 |  | 
 |     if (locals.isNotEmpty) { | 
 |       statements.insert(0, locals.toStatement()); | 
 |     } | 
 |  | 
 |     return wrapPhase('lazyInitializers', statements); | 
 |   } | 
 |  | 
 |   /// Emits the embedded globals that are needed for deferred loading. | 
 |   /// | 
 |   /// This function is only invoked for the main fragment. | 
 |   /// | 
 |   /// The [loadMap] contains a map from load-ids (for each deferred library) | 
 |   /// to the list of generated fragments that must be installed when the | 
 |   /// deferred library is loaded. | 
 |   Iterable<js.Property> emitEmbeddedGlobalsForDeferredLoading( | 
 |       DeferredLoadingState deferredLoadingState) { | 
 |     List<js.Property> globals = []; | 
 |  | 
 |     globals.add(js.Property( | 
 |         js.string(DEFERRED_INITIALIZED), js.js("Object.create(null)"))); | 
 |  | 
 |     String deferredGlobal = ModelEmitter.deferredInitializersGlobal; | 
 |     js.Expression isHunkLoadedFunction = | 
 |         js.js("function(hash) { return !!$deferredGlobal[hash]; }"); | 
 |     globals.add(js.Property(js.string(IS_HUNK_LOADED), isHunkLoadedFunction)); | 
 |  | 
 |     js.Expression isHunkInitializedFunction = js.js( | 
 |         "function(hash) { return !!#deferredInitialized[hash]; }", { | 
 |       'deferredInitialized': generateEmbeddedGlobalAccess(DEFERRED_INITIALIZED) | 
 |     }); | 
 |     globals.add( | 
 |         js.Property(js.string(IS_HUNK_INITIALIZED), isHunkInitializedFunction)); | 
 |  | 
 |     /// See [finalizeDeferredLoadingData] for the format of the deferred hunk. | 
 |     js.Expression initializeLoadedHunkFunction = js.js(""" | 
 |             function(hash) { | 
 |               var hunk = $deferredGlobal[hash]; | 
 |               if (hunk == null) { | 
 |                 throw "DeferredLoading state error: code with hash '" + | 
 |                     hash + "' was not loaded"; | 
 |               } | 
 |               initializeDeferredHunk(hunk); | 
 |               #deferredInitialized[hash] = true; | 
 |             }""", { | 
 |       'deferredInitialized': generateEmbeddedGlobalAccess(DEFERRED_INITIALIZED) | 
 |     }); | 
 |  | 
 |     globals.add(js.Property( | 
 |         js.string(INITIALIZE_LOADED_HUNK), initializeLoadedHunkFunction)); | 
 |  | 
 |     globals.add(js.Property(js.string(DEFERRED_LIBRARY_PARTS), | 
 |         deferredLoadingState.deferredLibraryParts)); | 
 |     globals.add(js.Property( | 
 |         js.string(DEFERRED_PART_URIS), deferredLoadingState.deferredPartUris)); | 
 |     globals.add(js.Property(js.string(DEFERRED_PART_HASHES), | 
 |         deferredLoadingState.deferredPartHashes)); | 
 |  | 
 |     return globals; | 
 |   } | 
 |  | 
 |   // Create data used for loading and initializing the hunks for a deferred | 
 |   // import. There are three parts: a map from loadId to list of parts, where | 
 |   // parts are represented as an index; an array of uris indexed by part; and an | 
 |   // array of hashes indexed by part. | 
 |   // [deferredLoadHashes] may have missing entries to indicate empty parts. | 
 |   void finalizeDeferredLoadingData( | 
 |       Map<String, List<CodeFragment>> codeFragmentsToLoad, | 
 |       Map<CodeFragment, FinalizedFragment> codeFragmentMap, | 
 |       Map<CodeFragment, String> deferredLoadHashes, | 
 |       DeferredLoadingState deferredLoadingState) { | 
 |     if (codeFragmentsToLoad.isEmpty) return; | 
 |  | 
 |     // We store a map of indices to uris and hashes. Because multiple | 
 |     // [CodeFragments] can map to a single file, a uri may appear multiple times | 
 |     // in [fragmentUris] once per [CodeFragment] reference in that file. | 
 |     // TODO(joshualitt): Use a string table to avoid duplicating part file | 
 |     // names. | 
 |     Map<CodeFragment, int> fragmentIndexes = {}; | 
 |     List<String> fragmentUris = []; | 
 |     List<String> fragmentHashes = []; | 
 |  | 
 |     List<js.Property> libraryPartsMapEntries = []; | 
 |  | 
 |     codeFragmentsToLoad | 
 |         .forEach((String loadId, List<CodeFragment> codeFragments) { | 
 |       List<js.Expression> indexes = []; | 
 |       for (var codeFragment in codeFragments) { | 
 |         var fragment = codeFragmentMap[codeFragment]; | 
 |         String codeFragmentHash = deferredLoadHashes[codeFragment]; | 
 |         if (codeFragmentHash == null) continue; | 
 |         int index = fragmentIndexes[codeFragment]; | 
 |         if (index == null) { | 
 |           index = fragmentIndexes[codeFragment] = fragmentIndexes.length; | 
 |           fragmentUris.add( | 
 |               "${fragment.outputFileName}.${ModelEmitter.deferredExtension}"); | 
 |           fragmentHashes.add(codeFragmentHash); | 
 |         } | 
 |         indexes.add(js.number(index)); | 
 |       } | 
 |       libraryPartsMapEntries | 
 |           .add(js.Property(js.string(loadId), js.ArrayInitializer(indexes))); | 
 |     }); | 
 |  | 
 |     deferredLoadingState.deferredLibraryParts.setValue( | 
 |         js.ObjectInitializer(libraryPartsMapEntries, isOneLiner: false)); | 
 |     deferredLoadingState.deferredPartUris | 
 |         .setValue(js.stringArray(fragmentUris)); | 
 |     deferredLoadingState.deferredPartHashes | 
 |         .setValue(js.stringArray(fragmentHashes)); | 
 |   } | 
 |  | 
 |   /// Emits the [MANGLED_GLOBAL_NAMES] embedded global. | 
 |   /// | 
 |   /// This global maps minified names for selected classes (some important | 
 |   /// core classes, and some native classes) to their unminified names. | 
 |   js.Property emitMangledGlobalNames() { | 
 |     List<js.Property> names = []; | 
 |  | 
 |     CommonElements commonElements = _closedWorld.commonElements; | 
 |     // We want to keep the original names for the most common core classes when | 
 |     // calling toString on them. | 
 |     List<ClassEntity> nativeClassesNeedingUnmangledName = [ | 
 |       commonElements.intClass, | 
 |       commonElements.doubleClass, | 
 |       commonElements.numClass, | 
 |       commonElements.stringClass, | 
 |       commonElements.boolClass, | 
 |       commonElements.nullClass, | 
 |       commonElements.listClass | 
 |     ]; | 
 |     // TODO(floitsch): this should probably be on a per-fragment basis. | 
 |     nativeClassesNeedingUnmangledName.forEach((element) { | 
 |       names.add(js.Property( | 
 |           js.quoteName(_namer.className(element)), js.string(element.name))); | 
 |     }); | 
 |  | 
 |     return js.Property( | 
 |         js.string(MANGLED_GLOBAL_NAMES), js.ObjectInitializer(names)); | 
 |   } | 
 |  | 
 |   /// Emits the [METADATA] embedded global. | 
 |   /// | 
 |   /// The metadata itself has already been computed earlier and is stored in | 
 |   /// the [program]. | 
 |   List<js.Property> emitMetadata(Program program) { | 
 |     List<js.Property> metadataGlobals = []; | 
 |  | 
 |     js.Property createGlobal(js.Expression metadata, String global) { | 
 |       return js.Property(js.string(global), metadata); | 
 |     } | 
 |  | 
 |     var mainUnit = program.mainFragment.outputUnit; | 
 |     js.Expression types = program.metadataTypesForOutputUnit(mainUnit); | 
 |     metadataGlobals.add(createGlobal(types, TYPES)); | 
 |  | 
 |     return metadataGlobals; | 
 |   } | 
 |  | 
 |   /// Emits all embedded globals. | 
 |   js.Statement emitEmbeddedGlobalsPart1( | 
 |       Program program, | 
 |       Map<String, List<FinalizedFragment>> fragmentsToLoad, | 
 |       DeferredLoadingState deferredLoadingState) { | 
 |     List<js.Property> globals = []; | 
 |  | 
 |     if (fragmentsToLoad.isNotEmpty) { | 
 |       globals | 
 |           .addAll(emitEmbeddedGlobalsForDeferredLoading(deferredLoadingState)); | 
 |     } | 
 |  | 
 |     if (program.typeToInterceptorMap != null) { | 
 |       // This property is assigned later. | 
 |       // Initialize property to avoid map transitions. | 
 |       globals.add( | 
 |           js.Property(js.string(TYPE_TO_INTERCEPTOR_MAP), js.LiteralNull())); | 
 |     } | 
 |  | 
 |     globals.add(js.Property(js.string(RTI_UNIVERSE), createRtiUniverse())); | 
 |  | 
 |     globals.add(emitMangledGlobalNames()); | 
 |  | 
 |     // The [MANGLED_NAMES] table must contain the mapping for const symbols. | 
 |     // Without const symbols, the table is only relevant for reflection and | 
 |     // therefore unused in this emitter. | 
 |     // TODO(johnniwinther): Remove the need for adding an empty list of | 
 |     // mangled names. | 
 |     globals | 
 |         .add(js.Property(js.string(MANGLED_NAMES), js.ObjectInitializer([]))); | 
 |  | 
 |     globals.addAll(emitMetadata(program)); | 
 |  | 
 |     if (program.needsNativeSupport) { | 
 |       globals | 
 |           .add(js.Property(js.string(INTERCEPTORS_BY_TAG), js.LiteralNull())); | 
 |       globals.add(js.Property(js.string(LEAF_TAGS), js.LiteralNull())); | 
 |     } | 
 |  | 
 |     globals.add( | 
 |         js.Property(js.string(ARRAY_RTI_PROPERTY), js.js(r'Symbol("$ti")'))); | 
 |  | 
 |     if (_closedWorld.backendUsage.requiresStartupMetrics) { | 
 |       // Copy the metrics object that was stored on the main unit IIFE. | 
 |       globals.add(js.Property( | 
 |           js.string(STARTUP_METRICS), js.js('dartProgram.$STARTUP_METRICS'))); | 
 |     } | 
 |  | 
 |     js.ObjectInitializer globalsObject = | 
 |         js.ObjectInitializer(globals, isOneLiner: false); | 
 |  | 
 |     return js.js.statement('var init = #;', globalsObject); | 
 |   } | 
 |  | 
 |   /// Finish setting up embedded globals. | 
 |   js.Statement emitEmbeddedGlobalsPart2( | 
 |       Program program, DeferredLoadingState deferredLoadingState) { | 
 |     List<js.Statement> statements = []; | 
 |     if (program.typeToInterceptorMap != null) { | 
 |       statements.add(js.js.statement('init.# = #;', | 
 |           [js.string(TYPE_TO_INTERCEPTOR_MAP), program.typeToInterceptorMap])); | 
 |     } | 
 |     return js.Block(statements); | 
 |   } | 
 |  | 
 |   js.Block emitTypeRules(Fragment fragment) { | 
 |     List<js.Statement> statements = []; | 
 |  | 
 |     ClassEntity legacyJsObjectClass = | 
 |         _commonElements.jsLegacyJavaScriptObjectClass; | 
 |  | 
 |     Map<ClassTypeData, List<ClassTypeData>> nativeRedirections = | 
 |         _nativeEmitter.typeRedirections; | 
 |  | 
 |     Ruleset ruleset = Ruleset.empty(); | 
 |     Map<ClassEntity, int> erasedTypes = {}; | 
 |     Iterable<ClassTypeData> classTypeData = | 
 |         fragment.libraries.expand((Library library) => library.classTypeData); | 
 |     classTypeData.forEach((ClassTypeData typeData) { | 
 |       ClassEntity element = typeData.element; | 
 |       InterfaceType targetType = _elementEnvironment.getThisType(element); | 
 |  | 
 |       // TODO(fishythefish): Prune uninstantiated classes. | 
 |       if (_rtiNeed.classHasErasedTypeArguments(element)) { | 
 |         erasedTypes[element] = targetType.typeArguments.length; | 
 |       } | 
 |  | 
 |       bool isInterop = | 
 |           _classHierarchy.isSubclassOf(element, legacyJsObjectClass); | 
 |  | 
 |       if (isInterop && element != legacyJsObjectClass) { | 
 |         ruleset.addRedirection(element, legacyJsObjectClass); | 
 |       } else { | 
 |         Iterable<TypeCheck> checks = typeData.classChecks?.checks ?? const []; | 
 |         Iterable<InterfaceType> supertypes = isInterop | 
 |             ? checks | 
 |                 .map((check) => _elementEnvironment.getJsInteropType(check.cls)) | 
 |             : checks | 
 |                 .map((check) => _dartTypes.asInstanceOf(targetType, check.cls)); | 
 |  | 
 |         Map<TypeVariableType, DartType> typeVariables = {}; | 
 |         Set<TypeVariableType> namedTypeVariables = typeData.namedTypeVariables; | 
 |         nativeRedirections[typeData]?.forEach((ClassTypeData redirectee) { | 
 |           namedTypeVariables.addAll(redirectee.namedTypeVariables); | 
 |         }); | 
 |         for (TypeVariableType typeVariable in typeData.namedTypeVariables) { | 
 |           TypeVariableEntity element = typeVariable.element; | 
 |           InterfaceType supertype = isInterop | 
 |               ? _elementEnvironment.getJsInteropType(element.typeDeclaration) | 
 |               : _dartTypes.asInstanceOf(targetType, element.typeDeclaration); | 
 |           List<DartType> supertypeArguments = supertype.typeArguments; | 
 |           typeVariables[typeVariable] = supertypeArguments[element.index]; | 
 |         } | 
 |         ruleset.addEntry(targetType, supertypes, typeVariables); | 
 |       } | 
 |     }); | 
 |  | 
 |     // We add native redirections only to the main fragment in order to avoid | 
 |     // duplicating them in multiple deferred units. | 
 |     if (fragment.outputUnit.isMainOutput) { | 
 |       nativeRedirections | 
 |           .forEach((ClassTypeData target, List<ClassTypeData> redirectees) { | 
 |         for (ClassTypeData redirectee in redirectees) { | 
 |           ruleset.addRedirection(redirectee.element, target.element); | 
 |         } | 
 |       }); | 
 |     } | 
 |  | 
 |     if (ruleset.isNotEmpty) { | 
 |       FunctionEntity addRules = _closedWorld.commonElements.rtiAddRulesMethod; | 
 |       statements.add(js.js.statement('#(init.#,JSON.parse(#));', [ | 
 |         _emitter.staticFunctionAccess(addRules), | 
 |         RTI_UNIVERSE, | 
 |         _rulesetEncoder.encodeRuleset(ruleset), | 
 |       ])); | 
 |     } | 
 |  | 
 |     if (erasedTypes.isNotEmpty) { | 
 |       FunctionEntity addErasedTypes = | 
 |           _closedWorld.commonElements.rtiAddErasedTypesMethod; | 
 |       statements.add(js.js.statement('#(init.#,JSON.parse(#));', [ | 
 |         _emitter.staticFunctionAccess(addErasedTypes), | 
 |         RTI_UNIVERSE, | 
 |         _rulesetEncoder.encodeErasedTypes(erasedTypes), | 
 |       ])); | 
 |     } | 
 |  | 
 |     return js.Block(statements); | 
 |   } | 
 |  | 
 |   js.Statement emitVariances(Fragment fragment) { | 
 |     if (!_options.enableVariance) { | 
 |       return js.EmptyStatement(); | 
 |     } | 
 |  | 
 |     Map<ClassEntity, List<Variance>> typeParameterVariances = {}; | 
 |     Iterable<Class> classes = | 
 |         fragment.libraries.expand((Library library) => library.classes); | 
 |     classes.forEach((Class cls) { | 
 |       ClassEntity element = cls.element; | 
 |       List<Variance> classVariances = | 
 |           _elementEnvironment.getTypeVariableVariances(element); | 
 |  | 
 |       // Emit variances for a class only if there is at least one explicit | 
 |       // variance defined. | 
 |       bool hasOnlyLegacyVariance = classVariances | 
 |           .every((variance) => variance == Variance.legacyCovariant); | 
 |       if (!hasOnlyLegacyVariance) { | 
 |         typeParameterVariances[element] = classVariances; | 
 |       } | 
 |     }); | 
 |  | 
 |     if (typeParameterVariances.isNotEmpty) { | 
 |       FunctionEntity addVariances = | 
 |           _closedWorld.commonElements.rtiAddTypeParameterVariancesMethod; | 
 |       return js.js.statement('#(init.#,JSON.parse(#));', [ | 
 |         _emitter.staticFunctionAccess(addVariances), | 
 |         RTI_UNIVERSE, | 
 |         _rulesetEncoder.encodeTypeParameterVariances(typeParameterVariances), | 
 |       ]); | 
 |     } | 
 |  | 
 |     return js.EmptyStatement(); | 
 |   } | 
 |  | 
 |   /// Returns an expression that creates the initial Rti Universe. | 
 |   /// | 
 |   /// This needs to be kept in sync with `_Universe.create` in `dart:_rti`. | 
 |   js.Expression createRtiUniverse() { | 
 |     List<js.Property> universeFields = []; | 
 |     void initField(String name, String value) { | 
 |       universeFields.add(js.Property(js.string(name), js.js(value))); | 
 |     } | 
 |  | 
 |     initField(RtiUniverseFieldNames.evalCache, 'new Map()'); | 
 |     initField(RtiUniverseFieldNames.typeRules, '{}'); | 
 |     initField(RtiUniverseFieldNames.erasedTypes, '{}'); | 
 |     initField(RtiUniverseFieldNames.typeParameterVariances, '{}'); | 
 |     initField(RtiUniverseFieldNames.sharedEmptyArray, '[]'); | 
 |  | 
 |     return js.ObjectInitializer(universeFields); | 
 |   } | 
 |  | 
 |   /// Emits data needed for native classes. | 
 |   js.Statement emitNativeSupport(Fragment fragment) { | 
 |     List<js.Statement> statements = []; | 
 |  | 
 |     // The isolate-affinity tag must only be initialized once per program. | 
 |     if (fragment.isMainFragment && | 
 |         NativeGenerator.needsIsolateAffinityTagInitialization( | 
 |             _closedWorld.backendUsage)) { | 
 |       statements.add(NativeGenerator.generateIsolateAffinityTagInitialization( | 
 |           _closedWorld.backendUsage, generateEmbeddedGlobalAccess, js.js(""" | 
 |         // On V8, the 'intern' function converts a string to a symbol, which | 
 |         // makes property access much faster. | 
 |         // TODO(sra): Use Symbol on non-IE11 browsers. | 
 |         function (s) { | 
 |           var o = {}; | 
 |           o[s] = 1; | 
 |           return Object.keys(hunkHelpers.convertToFastObject(o))[0]; | 
 |         }""", []))); | 
 |     } | 
 |  | 
 |     Map<String, js.Expression> interceptorsByTag = {}; | 
 |     Map<String, js.Expression> leafTags = {}; | 
 |     List<js.Statement> subclassAssignments = []; | 
 |  | 
 |     for (Library library in fragment.libraries) { | 
 |       for (Class cls in library.classes) { | 
 |         if (cls.nativeLeafTags != null) { | 
 |           for (String tag in cls.nativeLeafTags) { | 
 |             interceptorsByTag[tag] = classReference(cls); | 
 |             leafTags[tag] = js.LiteralBool(true); | 
 |           } | 
 |         } | 
 |         if (cls.nativeNonLeafTags != null) { | 
 |           for (String tag in cls.nativeNonLeafTags) { | 
 |             interceptorsByTag[tag] = classReference(cls); | 
 |             leafTags[tag] = js.LiteralBool(false); | 
 |           } | 
 |           if (cls.nativeExtensions != null) { | 
 |             List<Class> subclasses = cls.nativeExtensions; | 
 |             js.Expression base = js.string(cls.nativeNonLeafTags[0]); | 
 |  | 
 |             for (Class subclass in subclasses) { | 
 |               subclassAssignments.add(js.js.statement('#.# = #;', [ | 
 |                 classReference(subclass), | 
 |                 NATIVE_SUPERCLASS_TAG_NAME, | 
 |                 base | 
 |               ])); | 
 |             } | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     // Emit the empty objects for main fragment in case we emit | 
 |     // getNativeInterceptor. | 
 |     // TODO(sra): Refine the impacts to accuratley predict whether we need this | 
 |     // at all, and delete 'setOrUpdateInterceptorsByTag' if it is not called. | 
 |     if (fragment.isMainFragment || interceptorsByTag.isNotEmpty) { | 
 |       statements.add(js.js.statement( | 
 |           "hunkHelpers.setOrUpdateInterceptorsByTag(#);", | 
 |           js.objectLiteral(interceptorsByTag))); | 
 |     } | 
 |     if (fragment.isMainFragment || leafTags.isNotEmpty) { | 
 |       statements.add(js.js.statement( | 
 |           "hunkHelpers.setOrUpdateLeafTags(#);", js.objectLiteral(leafTags))); | 
 |     } | 
 |     statements.addAll(subclassAssignments); | 
 |  | 
 |     return wrapPhase('nativeSupport', statements); | 
 |   } | 
 | } | 
 |  | 
 | class LocalAliases { | 
 |   final Map<String, js.Expression> _locals = {}; | 
 |  | 
 |   bool get isEmpty => _locals.isEmpty; | 
 |   bool get isNotEmpty => !isEmpty; | 
 |  | 
 |   String find(String alias, String expression) { | 
 |     _locals[alias] ??= js.js(expression); | 
 |     return alias; | 
 |   } | 
 |  | 
 |   js.Statement toStatement() { | 
 |     List<js.VariableInitialization> initializations = []; | 
 |     _locals.forEach((local, value) { | 
 |       initializations | 
 |           .add(js.VariableInitialization(js.VariableDeclaration(local), value)); | 
 |     }); | 
 |     return js.ExpressionStatement(js.VariableDeclarationList(initializations)); | 
 |   } | 
 | } | 
 |  | 
 | class DeferredLoadingState { | 
 |   final deferredLibraryParts = DeferredPrimaryExpression(); | 
 |   final deferredPartUris = DeferredPrimaryExpression(); | 
 |   final deferredPartHashes = DeferredPrimaryExpression(); | 
 | } | 
 |  | 
 | class DeferredPrimaryExpression extends js.DeferredExpression { | 
 |   js.Expression _value; | 
 |  | 
 |   void setValue(js.Expression value) { | 
 |     assert(_value == null); | 
 |     assert(value.precedenceLevel == this.precedenceLevel); | 
 |     _value = value; | 
 |   } | 
 |  | 
 |   @override | 
 |   js.Expression get value { | 
 |     assert(_value != null); | 
 |     return _value; | 
 |   } | 
 |  | 
 |   @override | 
 |   int get precedenceLevel => js_precedence.PRIMARY; | 
 | } |