blob: 323b6b132986c670306e9e27972fbc494c7efc65 [file] [log] [blame]
// Copyright (c) 2024, 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.
import 'dart:collection';
import 'dart:convert';
import 'dart:io' as io;
import 'dart:math' show max, min;
import 'package:_js_interop_checks/src/transformations/js_util_optimizer.dart'
show ExtensionIndex;
import 'package:front_end/src/api_unstable/ddc.dart';
import 'package:js_shared/synced/embedded_names.dart' show JsGetName, JsBuiltin;
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/clone.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/library_index.dart';
import 'package:kernel/src/dart_type_equivalence.dart';
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import 'package:path/path.dart' as p;
import 'package:source_span/source_span.dart' show SourceLocation;
import '../compiler/js_names.dart' as js_ast;
import '../compiler/js_utils.dart' as js_ast;
import '../compiler/module_builder.dart'
show isSdkInternalRuntimeUri, libraryUriToJsIdentifier;
import '../compiler/module_containers.dart' show ModuleItemContainer;
import '../compiler/rewrite_async.dart';
import '../compiler/shared_command.dart' show SharedCompilerOptions;
import '../js_ast/js_ast.dart' as js_ast;
import '../js_ast/js_ast.dart' show ModuleItem, js;
import '../js_ast/source_map_printer.dart'
show NodeEnd, NodeSpan, HoverComment, continueSourceMap;
import 'compiler.dart' as old;
import 'constants.dart';
import 'future_or_normalizer.dart';
import 'js_interop.dart';
import 'js_typerep.dart';
import 'kernel_helpers.dart';
import 'native_types.dart';
import 'nullable_inference.dart';
import 'property_model.dart';
import 'target.dart' show allowedNativeTest;
import 'type_environment.dart';
import 'type_recipe_generator.dart';
import 'type_table.dart';
/// Symbol data used to map library members kernel nodes to identifiers used
/// in the compiled JavaScript.
///
/// This data is intended to be serialized and consumed by the debugger.
class SymbolData {
/// Maps each `Class` node compiled in the module to the `Identifier`s used to
/// name the class in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
final classIdentifiers = <Class, js_ast.Identifier>{};
/// Maps each class `Member` node compiled in the module to the name used for
/// the member in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
final memberNames = <Member, String>{};
/// Maps each `Procedure` node compiled in the module to the `Identifier`s
/// used to name the class in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
final procedureIdentifiers = <Procedure, js_ast.Identifier>{};
/// Maps each `VariableDeclaration` node compiled in the module to the name
/// used for the variable in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
final variableIdentifiers = <VariableDeclaration, js_ast.Identifier>{};
}
/// Compiles a kernel [Component] to a bundle of individual libraries.
class LibraryBundleCompiler implements old.Compiler {
final ClassHierarchy _hierarchy;
final SharedCompilerOptions _options;
final Map<Library, Component> _importToSummary;
final Map<Component, String> _summaryToModule;
final CoreTypes _coreTypes;
final Ticker? _ticker;
final _symbolData = SymbolData();
final _libraryCompilers = <Library, LibraryCompiler>{};
LibraryBundleCompiler(
Component component,
this._hierarchy,
this._options,
this._importToSummary,
this._summaryToModule, {
CoreTypes? coreTypes,
Ticker? ticker,
}) : _coreTypes = coreTypes ?? CoreTypes(component),
_ticker = ticker;
@override
Map<Class, js_ast.Identifier> get classIdentifiers =>
_symbolData.classIdentifiers;
@override
Map<Member, String> get memberNames => _symbolData.memberNames;
@override
Map<Procedure, js_ast.Identifier> get procedureIdentifiers =>
_symbolData.procedureIdentifiers;
@override
Map<VariableDeclaration, js_ast.Identifier> get variableIdentifiers =>
_symbolData.variableIdentifiers;
@override
js_ast.Program emitModule(Component component) {
_ticker?.logMs('Emitting library bundle');
var compiledLibraries = <js_ast.Program>[];
for (var library in component.libraries) {
var compiler = LibraryCompiler(
component,
_hierarchy,
_options,
_importToSummary,
_summaryToModule,
coreTypes: _coreTypes,
ticker: _ticker,
symbolData: _symbolData,
);
_libraryCompilers[library] = compiler;
compiledLibraries.add(compiler.emitLibrary(library));
}
return js_ast.LibraryBundle(compiledLibraries,
header: _generateCompilationHeader());
}
@override
js_ast.Fun emitFunctionIncremental(List<js_ast.ModuleItem> items,
Library library, Class? cls, FunctionNode functionNode, String name) {
return _libraryCompilers[library]!
._emitFunctionIncremental(items, library, cls, functionNode, name);
}
/// Creates header comments with helpful compilation information.
List<js_ast.Comment> _generateCompilationHeader() {
var headerOptions = [
if (_options.canaryFeatures) 'canary',
if (_options.emitLibraryBundle) 'emitLibraryBundle',
'soundNullSafety(${_options.soundNullSafety})',
'enableAsserts(${_options.enableAsserts})',
];
var enabledExperiments = <String>[];
_options.experiments.forEach((key, value) {
if (value) enabledExperiments.add(key);
});
var header = [
js_ast.Comment(
'Generated by DDC, the Dart Development Compiler (to JavaScript).'),
js_ast.Comment('Version: ${io.Platform.version}'),
js_ast.Comment('Module: ${_options.moduleName}'),
js_ast.Comment('Flags: ${headerOptions.join(', ')}'),
if (enabledExperiments.isNotEmpty)
js_ast.Comment('Experiments: ${enabledExperiments.join(', ')}')
];
return header;
}
}
class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
with OnceConstantVisitorDefaultMixin<js_ast.Expression>
implements
StatementVisitor<js_ast.Statement>,
ExpressionVisitor<js_ast.Expression> {
final SharedCompilerOptions _options;
final SymbolData _symbolData;
/// Maps each `Class` node compiled in the module to the `Identifier`s used to
/// name the class in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
Map<Class, js_ast.Identifier> get classIdentifiers =>
_symbolData.classIdentifiers;
/// Maps each class `Member` node compiled in the module to the name used for
/// the member in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
Map<Member, String> get memberNames => _symbolData.memberNames;
/// Maps each `Procedure` node compiled in the module to the `Identifier`s
/// used to name the class in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
Map<Procedure, js_ast.Identifier> get procedureIdentifiers =>
_symbolData.procedureIdentifiers;
/// Maps each `VariableDeclaration` node compiled in the module to the name
/// used for the variable in JavaScript.
///
/// This mapping is used when generating the symbol information for the
/// module.
Map<VariableDeclaration, js_ast.Identifier> get variableIdentifiers =>
_symbolData.variableIdentifiers;
/// Maps a library URI import, that is not in [_libraries], to the
/// corresponding Kernel summary module we imported it with.
///
/// An entry must exist for every reachable component.
final Map<Library, Component> _importToSummary;
/// Maps a Kernel summary to the JS import name for the module.
///
/// An entry must exist for every reachable component.
final Map<Component, String> _summaryToModule;
/// The variable for the current catch clause
VariableDeclaration? _rethrowParameter;
/// Temporary variables mapped to their corresponding JavaScript variable.
final _tempVariables = <VariableDeclaration, js_ast.TemporaryId>{};
/// Let variables collected for the given function.
List<js_ast.TemporaryId>? _letVariables;
final _constTable = js_ast.Identifier('CT');
/// Constant getters used to populate the constant table.
final _constLazyAccessors = <js_ast.Method>[];
/// Container for holding the results of lazily-evaluated constants.
var _constTableCache = ModuleItemContainer<String>.asArray('C');
/// Tracks the index in [_moduleItems] where the const table must be inserted.
/// Required for SDK builds due to internal circular dependencies.
/// E.g., dart.constList depends on JSArray.
int _constTableInsertionIndex = 0;
/// The class that is emitting its base class or mixin references, otherwise
/// null.
///
/// This is not used when inside the class method bodies, or for other type
/// information such as `implements`.
Class? _classEmittingExtends;
/// The class that is emitting its signature information, otherwise null.
Class? _classEmittingSignatures;
/// True when a class is emitting a deferred class hierarchy.
bool _emittingDeferredType = false;
/// The current type environment of type parameters introduced to the scope
/// via generic classes and functions.
DDCTypeEnvironment _currentTypeEnvironment = const EmptyTypeEnvironment();
final TypeRecipeGenerator _typeRecipeGenerator;
/// Visitor used for testing static invocations in the dart:_rti library to
/// determine if they are suitable for inlining at call sites.
final BasicInlineTester _inlineTester;
/// The current element being loaded.
/// We can use this to determine if we're loading top-level code or not:
///
/// _currentClass == _classEmittingTopLevel
///
Class? _currentClass;
/// The current source file URI for emitting in the source map.
Uri? _currentUri;
late Component _component;
/// The current library being compiled.
Library? _currentLibrary;
/// The current function being compiled, if any.
FunctionNode? _currentFunction;
/// Statements in order they should be inserted into the body of the link
/// method for the current library.
final List<js_ast.Statement> _currentLibraryLinkFunctionBody = [];
/// Whether the current function needs to insert parameter checks.
///
/// Used to avoid adding checks for formal parameters inside a synthetic
/// function that is generated during expression compilation in the
/// incremental compiler, since those checks would already be done in
/// the original code.
bool _checkParameters = true;
/// Whether we are currently generating code for the body of a `JS()` call.
bool _isInForeignJS = false;
/// Table of named and possibly hoisted types.
late TypeTable _typeTable;
/// Table of instantiated generic class references.
///
/// Provides a cache for the instantiated generic types local to a module.
late TypeTable _genericClassTable;
/// The global extension type table.
// TODO(jmesserly): rename to `_nativeTypes`
final NativeTypeSet _extensionTypes;
final CoreTypes _coreTypes;
final TypeEnvironment _types;
final StatefulStaticTypeContext _staticTypeContext;
final ClassHierarchy _hierarchy;
/// Information about virtual and overridden fields/getters/setters in the
/// class we're currently compiling, or `null` if we aren't compiling a class.
ClassPropertyModel? _classProperties;
/// Information about virtual fields for all libraries in the current build
/// unit.
final _virtualFields = VirtualFieldModel();
final JSTypeRep _typeRep;
bool _superAllowed = true;
bool _optimizeNonVirtualFieldAccess = true;
final _superHelpers = <String, js_ast.Method>{};
/// Cache for the results of calling [_requiresRtiForInstantiation].
final _requiresRtiForInstantiationCache = <Class, bool>{};
/// Reserved parameter used to reference RTI objects passed to generic
/// constructors/factories and generic method signatures.
final _rtiParam = js_ast.TemporaryId('_ti');
// Compilation of Kernel's [BreakStatement].
//
// Kernel represents Dart's `break` and `continue` uniformly as
// [BreakStatement], by representing a loop continue as a break from the
// loop's body. [BreakStatement] always targets an enclosing
// [LabeledStatement] statement directly without naming it. (Continue to
// a labeled switch case is not represented by a [BreakStatement].)
//
// We prefer to compile to `continue` where possible and to avoid labeling
// statements where it is not necessary. We maintain some state to track
// which statements can be targets of break or continue without a label, which
// statements must be labeled to be targets, and the labels that have been
// assigned.
/// A list of statements that can be the target of break without a label.
///
/// A [BreakStatement] targeting any [LabeledStatement] in this list can be
/// compiled to a break without a label. All the statements in the list have
/// the same effective target which must compile to something that can be
/// targeted by break in JS. This list and [_currentContinueTargets] are
/// disjoint.
List<LabeledStatement> _currentBreakTargets = [];
/// A list of statements that can be the target of a continue without a label.
///
/// A [BreakStatement] targeting any [LabeledStatement] in this list can be
/// compiled to a continue without a label. All the statements in this list
/// have the same effective target which must compile to something that can be
/// targeted by continue in JS. This list and [_currentBreakTargets] are
/// disjoint.
List<LabeledStatement> _currentContinueTargets = [];
/// A map from labeled statements to their 'effective targets'.
///
/// The effective target of a labeled loop body is the enclosing loop. A
/// [BreakStatement] targeting this statement can be compiled to `continue`
/// either with or without a label. The effective target of a labeled
/// statement that is not a loop body is the outermost non-labeled statement
/// that it encloses. A [BreakStatement] targeting this statement can be
/// compiled to `break` either with or without a label.
final _effectiveTargets = HashMap<LabeledStatement, Statement>.identity();
/// A map from effective targets to their label names.
///
/// If the target needs to be labeled when compiled to JS, because it was
/// targeted by a break or continue with a label, then this map contains the
/// label name that was assigned to it.
final _labelNames = HashMap<Statement, String>.identity();
/// Indicates that the current context exists within a switch statement that
/// uses at least one continue statement with a target label.
///
/// JS forbids labels at case statement boundaries, so these switch
/// statements must be generated less directly.
/// Updated from the method 'visitSwitchStatement'.
bool _inLabeledContinueSwitch = false;
/// A map from switch statements to their state information.
/// State information includes the names of the switch statement's implicit
/// label name and implicit state variable name.
///
/// Entries are only created for switch statements that contain labeled
/// continue statements and are used to simulate "jumping" to case statements.
/// State variables hold the next constant case expression, while labels act
/// as targets for continue and break.
final _switchLabelStates = HashMap<Statement, _SwitchLabelState>();
/// Maps Kernel constants to their JS aliases.
final _constAliasCache = HashMap<Constant, js_ast.Expression>();
/// Maps uri strings in asserts and elsewhere to hoisted identifiers.
var _uriContainer = ModuleItemContainer<String>.asArray('I');
/// Index of extension and extension type members in order to filter static
/// interop members.
// TODO(srujzs): Is there some way to share this from the js_util_optimizer to
// avoid having to recompute?
final ExtensionIndex _extensionIndex;
/// When inside a `[]=` operator, this will be a non-null value that should be
/// returned by any `return;` statement.
///
/// This lets DDC use the setter method's return value directly.
final _operatorSetResultStack = <js_ast.Identifier?>[];
/// Private member names in this module, organized by their library.
final _privateNames = HashMap<Library, HashMap<String, js_ast.TemporaryId>>();
/// Holds all top-level JS symbols (used for caching or indexing fields).
final _symbolContainer = ModuleItemContainer<js_ast.Identifier>.asObject('S',
keyToString: (js_ast.Identifier i) => i.name);
/// Extension member symbols for adding Dart members to JS types.
///
/// These are added to the [_extensionSymbolsModule]; see that field for more
/// information.
final _extensionSymbols = <String, js_ast.TemporaryId>{};
/// The set of libraries we are currently compiling, and the temporaries used
/// to refer to them.
final _libraries = <Library, js_ast.Identifier>{};
/// Imported libraries, and the temporaries used to refer to them.
final _imports = <Library, js_ast.TemporaryId>{};
/// Incremental mode for expression compilation.
///
/// If set to true, triggers emitting all used types, symbols, libraries,
/// constants, urs inside the generated function.
bool _incrementalMode = false;
/// Modules and libraries accessed during compilation in incremental mode.
final _incrementalModules = <String, Set<String>>{};
/// The identifier used to reference DDC's core "dart:_runtime" library from
/// generated JS code, typically called "dart" e.g. `dart.dcall`.
late final js_ast.Identifier _runtimeModule;
/// The identifier used to reference DDC's "extension method" symbols, used to
/// safely add Dart-specific member names to JavaScript classes, such as
/// primitive types (e.g. String) or DOM types in "dart:html".
late final js_ast.Identifier _extensionSymbolsModule;
/// The identifier used to reference DDC's core "dart:_rti" library from
/// generated JS code.
///
/// Must manually name the dart:_rti library because there are local variables
/// within the library that inadvertently shadow the default name.
final _rtiLibraryId = js_ast.TemporaryId('dart_rti');
/// The library referred to by [_rtiLibraryId].
final Library _rtiLibrary;
/// The `Rti` class defined in [_rtiLibrary].
final Class _rtiClass;
/// Whether we're currently building the SDK, which may require special
/// bootstrapping logic.
///
/// This is initialized by [emitModule], which must be called before
/// accessing this field.
// TODO(nshahan): Set to true if needed when the SDK can be compiled with this
// compiler.
final bool _isBuildingSdk = false;
/// Whether or not to move top level symbols into top-level containers.
///
/// This is set in both [emitModule] and [_emitLibrary].
/// Depends on [_isBuildingSdk].
bool _containerizeSymbols = false;
/// The temporary variable that stores named arguments (these are passed via a
/// JS object literal, to match JS conventions).
final _namedArgumentTemp = js_ast.TemporaryId('opts');
/// The list of output module items, in the order they need to be emitted in.
final _moduleItems = <js_ast.ModuleItem>[];
/// Like [_moduleItems] but for items that should be emitted after classes.
///
/// This is used for deferred supertypes of mutually recursive non-generic
/// classes.
final _afterClassDefItems = <js_ast.ModuleItem>[];
final Class _jsArrayClass;
final Class _privateSymbolClass;
final Class _linkedHashMapImplClass;
final Class _identityHashMapImplClass;
final Class _linkedHashSetClass;
final Class _linkedHashSetImplClass;
final Class _identityHashSetImplClass;
// Helpers for async function lowering
final Member _asyncStartMember;
final Member _asyncAwaitMember;
final Member _asyncReturnMember;
final Member _asyncRethrowMember;
final Member _asyncMakeCompleterMember;
final Member _asyncWrapJsFunctionMember;
// Helpers for sync* function lowering
final Member _syncStarMakeIterableMember;
final Member _syncStarIteratorCurrentMember;
final Member _syncStarIteratorDatumMember;
final Member _syncStarIteratorYieldStarMember;
// Helpers for async* function lowering
final Member _asyncStarHelperMember;
final Member _asyncStreamOfControllerMember;
final Member _asyncMakeAsyncStarStreamControllerMember;
final Member _asyncIterationMarkerYieldSingleMember;
final Member _asyncIterationMarkerYieldStarMember;
final Class _asyncStreamIteratorClass;
final Procedure _assertInteropMethod;
final DevCompilerConstants _constants;
final NullableInference _nullableInference;
bool _moduleEmitted = false;
/// Supports verbose logging with a timer.
Ticker? _ticker;
factory LibraryCompiler(
Component component,
ClassHierarchy hierarchy,
SharedCompilerOptions options,
Map<Library, Component> importToSummary,
Map<Component, String> summaryToModule, {
CoreTypes? coreTypes,
Ticker? ticker,
required SymbolData symbolData,
}) {
coreTypes ??= CoreTypes(component);
var types = TypeEnvironment(coreTypes, hierarchy);
var constants = DevCompilerConstants();
var nativeTypes = NativeTypeSet(coreTypes, constants, component);
var jsTypeRep = JSTypeRep(types, hierarchy);
var staticTypeContext = StatefulStaticTypeContext.stacked(types);
return LibraryCompiler._(
ticker,
coreTypes,
coreTypes.index,
nativeTypes,
constants,
types,
hierarchy,
jsTypeRep,
NullableInference(jsTypeRep, staticTypeContext, options: options),
staticTypeContext,
options,
importToSummary,
summaryToModule,
symbolData,
);
}
LibraryCompiler._(
this._ticker,
this._coreTypes,
LibraryIndex sdk,
this._extensionTypes,
this._constants,
this._types,
this._hierarchy,
this._typeRep,
this._nullableInference,
this._staticTypeContext,
this._options,
this._importToSummary,
this._summaryToModule,
this._symbolData)
: _jsArrayClass = sdk.getClass('dart:_interceptors', 'JSArray'),
_privateSymbolClass = sdk.getClass('dart:_js_helper', 'PrivateSymbol'),
_linkedHashMapImplClass = sdk.getClass('dart:_js_helper', 'LinkedMap'),
_identityHashMapImplClass =
sdk.getClass('dart:_js_helper', 'IdentityMap'),
_linkedHashSetClass = sdk.getClass('dart:collection', 'LinkedHashSet'),
_linkedHashSetImplClass = sdk.getClass('dart:_js_helper', 'LinkedSet'),
_identityHashSetImplClass =
sdk.getClass('dart:_js_helper', 'IdentitySet'),
_assertInteropMethod =
sdk.getTopLevelProcedure('dart:_runtime', 'assertInterop'),
_asyncStartMember =
sdk.getTopLevelMember('dart:async', '_asyncStartSync'),
_asyncAwaitMember = sdk.getTopLevelMember('dart:async', '_asyncAwait'),
_asyncReturnMember =
sdk.getTopLevelMember('dart:async', '_asyncReturn'),
_asyncRethrowMember =
sdk.getTopLevelMember('dart:async', '_asyncRethrow'),
_asyncMakeCompleterMember =
sdk.getTopLevelMember('dart:async', '_makeAsyncAwaitCompleter'),
_asyncWrapJsFunctionMember =
sdk.getTopLevelMember('dart:async', '_wrapJsFunctionForAsync'),
_syncStarMakeIterableMember =
sdk.getTopLevelMember('dart:async', '_makeSyncStarIterable'),
_syncStarIteratorCurrentMember =
sdk.getMember('dart:async', '_SyncStarIterator', '_current'),
_syncStarIteratorDatumMember =
sdk.getMember('dart:async', '_SyncStarIterator', '_datum'),
_syncStarIteratorYieldStarMember =
sdk.getMember('dart:async', '_SyncStarIterator', '_yieldStar'),
_asyncStarHelperMember =
sdk.getTopLevelMember('dart:async', '_asyncStarHelper'),
_asyncStreamOfControllerMember =
sdk.getTopLevelMember('dart:async', '_streamOfController'),
_asyncMakeAsyncStarStreamControllerMember = sdk.getTopLevelMember(
'dart:async', '_makeAsyncStarStreamController'),
_asyncIterationMarkerYieldSingleMember =
sdk.getMember('dart:async', '_IterationMarker', 'yieldSingle'),
_asyncIterationMarkerYieldStarMember =
sdk.getMember('dart:async', '_IterationMarker', 'yieldStar'),
_asyncStreamIteratorClass =
sdk.getClass('dart:async', 'StreamIterator'),
_futureOrNormalizer = FutureOrNormalizer(_coreTypes),
_typeRecipeGenerator = TypeRecipeGenerator(_coreTypes, _hierarchy),
_extensionIndex =
ExtensionIndex(_coreTypes, _staticTypeContext.typeEnvironment),
_inlineTester = BasicInlineTester(_constants),
_rtiLibrary = sdk.getLibrary('dart:_rti'),
_rtiClass = sdk.getClass('dart:_rti', 'Rti');
/// The library for dart:core in the SDK.
Library get _coreLibrary => _coreTypes.coreLibrary;
/// The type used for private Dart [Symbol]s.
InterfaceType get _privateSymbolType =>
_coreTypes.nonNullableRawType(_privateSymbolClass);
/// The type used for public Dart [Symbol]s.
InterfaceType get _internalSymbolType =>
_coreTypes.nonNullableRawType(_coreTypes.internalSymbolClass);
final FutureOrNormalizer _futureOrNormalizer;
/// Module can be emitted only once, and the compiler can be reused after
/// only in incremental mode, for expression compilation only.
js_ast.Program emitLibrary(Library library) {
if (_moduleEmitted) {
throw StateError('Can only call emitLibrary once.');
}
_ticker?.logMs('Emitting library');
_currentLibrary = library;
_component = library.enclosingComponent!;
// For runtime performance reasons, we only containerize SDK symbols in web
// libraries. Otherwise, we use a 600-member cutoff before a module is
// containerized. This is somewhat arbitrary but works promisingly for the
// SDK and Flutter Web.
if (!_isBuildingSdk) {
// The number of DDC top-level symbols scales with the number of
// non-static class members across an entire module.
var uniqueNames = HashSet<String>();
library.classes.forEach((Class c) {
c.members.forEach((m) {
var isStatic =
m is Field ? m.isStatic : (m is Procedure ? m.isStatic : false);
if (isStatic) return;
var name = js_ast.toJSIdentifier(
m.name.text.replaceAll(js_ast.invalidCharInIdentifier, '_'));
uniqueNames.add(name);
});
});
_containerizeSymbols = uniqueNames.length > 600;
}
// TODO(nshahan): Refactor to use a single library.
var items = _startLibrary([library]);
_nullableInference.allowNotNullDeclarations = _isBuildingSdk;
_typeTable = TypeTable('T', _runtimeCall);
_genericClassTable = TypeTable('G', _runtimeCall);
// Insert a circular reference so neither the constant table or its cache
// are optimized away by V8. Required for expression evaluation.
var constTableDeclaration =
js.statement('const # = Object.create({# : () => (#, #)});', [
_constTable,
js_ast.LiteralString('_'),
_constTableCache.containerId,
_constTable
]);
_moduleItems.add(constTableDeclaration);
// Record a safe index after the declaration of type generators and
// top-level symbols but before the declaration of any functions.
// Various preliminary data structures must be inserted here prior before
// referenced by the rest of the module.
var safeDeclarationIndex = _moduleItems.length;
_constTableInsertionIndex = safeDeclarationIndex;
// Add implicit dart:core dependency so it is first.
_emitLibraryName(_coreTypes.coreLibrary);
_ticker?.logMs('Added table declarations');
// Visit the library and emit its code.
//
// NOTE: classes are not necessarily emitted in this order.
// Order will be changed as needed so the resulting code can execute.
// This is done by forward declaring items.
if (!_isBuildingSdk) {
_forceLibraryImport(_rtiLibrary, _rtiLibraryId);
}
_emitLibrary(library);
_ticker?.logMs('Emitted library: ${library.importUri}');
// Emit hoisted assert strings
_moduleItems.insertAll(safeDeclarationIndex, _uriContainer.emit());
_moduleItems.insertAll(safeDeclarationIndex, _constTableCache.emit());
if (_constLazyAccessors.isNotEmpty) {
var constTableBody = _runtimeStatement(
'defineLazy(#, { # }, false)', [_constTable, _constLazyAccessors]);
_moduleItems.insert(_constTableInsertionIndex, constTableBody);
_constLazyAccessors.clear();
}
_moduleItems.addAll(_afterClassDefItems);
_afterClassDefItems.clear();
// Register the local const cache for this module so it can be cleared on a
// hot restart.
if (_constTableCache.isNotEmpty) {
_moduleItems.add(_runtimeCall('moduleConstCaches.set(#, #)', [
js_ast.string(_options.moduleName),
_constTableCache.containerId
]).toStatement());
}
_ticker?.logMs('Added table caches');
// Add all type hierarchy rules for the interface types used in this module.
// TODO(nshahan) This is likely more information than the application
// really uses. It could be reduced to only the types of values that are
// potentially "live" in the module which includes the types of all the
// constructor invocations and the types of the constructors torn off
// (potentially constructed) within the module. The current constructor
// tearoff lowering does make this harder to know since all constructors
// appeared to be invoked in the body of the method created by the
// lowering. For now we over estimate and simply use all the interface
// types introduced by all the classes defined in the module.
for (var cls in library.classes) {
var type = cls.getThisType(_coreTypes, Nullability.nonNullable);
_typeRecipeGenerator.addLiveTypeAncestries(type);
}
var universeClass =
_rtiLibrary.classes.firstWhere((cls) => cls.name == '_Universe');
var typeRules = _typeRecipeGenerator.liveInterfaceTypeRules;
if (typeRules.isNotEmpty) {
var template = '#._Universe.#(#, JSON.parse(#))';
var addRulesStatement = js.call(template, [
_emitLibraryName(_rtiLibrary),
_emitMemberName('addRules', memberClass: universeClass),
_runtimeCall('typeUniverse'),
js.string(jsonEncode(typeRules), "'")
]).toStatement();
_moduleItems.add(addRulesStatement);
}
// Update type rules for `LegacyJavaScriptObject` to add all interop
// types in this module as a supertype.
var updateRules = _typeRecipeGenerator.updateLegacyJavaScriptObjectRules;
if (updateRules.isNotEmpty) {
// All JavaScript interop classes should be mutual subtypes with
// `LegacyJavaScriptObject`. To achieve this the rules are manually
// added here. There is special redirecting rule logic in the dart:_rti
// library for interop types because otherwise they would duplicate
// a lot of supertype information.
var updateRulesStatement =
js.statement('#._Universe.#(#, JSON.parse(#))', [
_emitLibraryName(_rtiLibrary),
_emitMemberName('addOrUpdateRules', memberClass: universeClass),
_runtimeCall('typeUniverse'),
js.string(jsonEncode(updateRules), "'")
]);
_moduleItems.add(updateRulesStatement);
}
var jsInteropTypeRecipes = _typeRecipeGenerator.visitedJsInteropTypeRecipes;
if (jsInteropTypeRecipes.isNotEmpty) {
// Update the `LegacyJavaScriptObject` class with the type tags for all
// interop types in this module. This is the quick path for simple type
// tests that matches the rules encoded above.
var legacyJavaScriptObjectClass = _coreTypes.index
.getClass('dart:_interceptors', 'LegacyJavaScriptObject');
var legacyJavaScriptObjectClassRef = _emitClassRef(
legacyJavaScriptObjectClass.getThisType(
_coreTypes, Nullability.nonNullable));
var interopRecipesArray = js_ast.stringArray([
_typeRecipeGenerator.interfaceTypeRecipe(legacyJavaScriptObjectClass),
...jsInteropTypeRecipes
]);
var jsInteropRules = _runtimeStatement('addRtiResources(#, #)',
[legacyJavaScriptObjectClassRef, interopRecipesArray]);
_moduleItems.add(jsInteropRules);
}
// Annotates the type parameter variances for each interface.
var typeVariances = _typeRecipeGenerator.variances;
if (typeVariances.isNotEmpty) {
var addTypeParameterVariancesTemplate = '#._Universe.#(#, JSON.parse(#))';
var addTypeParameterVariancesStatement =
js.call(addTypeParameterVariancesTemplate, [
_emitLibraryName(_rtiLibrary),
_emitMemberName('addTypeParameterVariances',
memberClass: universeClass),
_runtimeCall('typeUniverse'),
js.string(jsonEncode(typeVariances), "'")
]).toStatement();
_moduleItems.add(addTypeParameterVariancesStatement);
}
// Certain RTIs must be emitted during RTI normalization. We cache these
// eagerly with 'findType' (without normalization) to avoid infinite loops.
// See normalization functions in: sdk/lib/_internal/js_shared/lib/rti.dart
var prerequisiteRtiTypes = [
_coreTypes.objectLegacyRawType,
_coreTypes.objectNullableRawType,
NeverType.legacy()
];
prerequisiteRtiTypes.forEach((type) {
var recipe = _typeRecipeGenerator
.recipeInEnvironment(type, EmptyTypeEnvironment())
.recipe;
_moduleItems.add(js.call('#.findType("$recipe")',
[_emitLibraryName(_rtiLibrary)]).toStatement());
});
// Visit directives (for exports)
_emitExports(library);
_ticker?.logMs('Emitted exports');
// Declare imports and extension symbols
_emitImportsAndExtensionSymbols(items,
forceExtensionSymbols: allowedNativeTest(library.importUri));
_ticker?.logMs('Emitted imports and extension symbols');
// Insert a check that runs when loading this module to verify that the null
// safety mode it was compiled in matches the mode used when compiling the
// dart sdk module.
//
// This serves as a sanity check at runtime that we don't have an
// infrastructure issue that loaded js files compiled with different modes
// into the same application.
js_ast.LiteralBool soundNullSafety;
switch (_component.mode) {
case NonNullableByDefaultCompiledMode.Strong:
soundNullSafety = js_ast.LiteralBool(true);
break;
case NonNullableByDefaultCompiledMode.Weak:
soundNullSafety = js_ast.LiteralBool(false);
break;
default:
throw StateError('Unsupported Null Safety mode ${_component.mode}, '
'in ${_component.location?.file}.');
}
if (!_isBuildingSdk) {
items.add(_runtimeStatement(
'_checkModuleNullSafetyMode(#)', [soundNullSafety]));
}
// Emit the hoisted type table cache variables
items.addAll(_typeTable.dischargeBoundTypes());
_ticker?.logMs('Emitted type table');
// Emit the hoisted instantiated generic class table cache variables
items.addAll(_genericClassTable.dischargeBoundTypes());
_ticker?.logMs('Emitted instantiated generic class table');
var compiledLibrary = _finishLibrary(
items, '${library.importUri}', _emitLibraryName(library));
_ticker?.logMs('Finished emitting module');
// Mark as finished for incremental mode, so it is safe to
// switch to the incremental mode for expression compilation.
_moduleEmitted = true;
return compiledLibrary;
}
/// Returns a method that will perform all class hierarchy operations for the
/// classes defined in this module.
///
/// At a high level this method performs the prototype stitching for all
/// `class A extends B` relationships but in practice will also include the
/// operations that implicitly depend on those relationships to be established
/// so they can walk the prototype chain.
js_ast.Statement _emitLibraryLinkMethod(Library library) {
var libraryName = _emitLibraryName(library);
var nameExpr = js_ast.PropertyAccess.field(libraryName, 'link');
var functionName = _emitTemporaryId('link__${_jsLibraryName(library)}');
var parameters = const <js_ast.Parameter>[];
var body = js_ast.Block(_currentLibraryLinkFunctionBody);
var function =
js_ast.NamedFunction(functionName, js_ast.Fun(parameters, body));
return js.statement('# = #', [nameExpr, function]);
}
/// Choose a canonical name from the [library] element.
String _jsLibraryName(Library library) {
return libraryUriToJsIdentifier(library.importUri);
}
/// Choose a module-unique name from the [library] element.
///
/// Returns null if no alias exists or there are multiple output paths
/// (e.g., when compiling the Dart SDK).
///
/// This never uses the library's name (the identifier in the `library`
/// declaration) as it doesn't have any meaningful rules enforced.
String? _jsLibraryAlias(Library library) {
var uri = library.importUri.normalizePath();
if (uri.isScheme('dart')) return null;
Iterable<String> segments;
if (uri.isScheme('package')) {
// Strip the package name.
segments = uri.pathSegments.skip(1);
} else {
segments = uri.pathSegments;
}
var qualifiedPath =
js_ast.pathToJSIdentifier(p.withoutExtension(segments.join('/')));
return qualifiedPath == _jsLibraryName(library) ? null : qualifiedPath;
}
/// Debugger friendly name for a Dart [library].
String _jsLibraryDebuggerName(Library library) => '${library.importUri}';
/// Debugger friendly names for all parts in a Dart [library].
Iterable<String> _jsPartDebuggerNames(Library library) =>
library.parts.map((part) => part.partUri);
/// True when [library] is the sdk internal library 'dart:_internal'.
bool _isDartInternal(Library library) => _isDartLibrary(library, '_internal');
/// True when [library] is the sdk internal library 'dart:_js_helper'.
bool _isDartJsHelper(Library library) =>
_isDartLibrary(library, '_js_helper');
/// True when [library] is the sdk internal library 'dart:_internal'.
bool _isDartForeignHelper(Library library) =>
_isDartLibrary(library, '_foreign_helper');
/// True when [library] is the sdk library 'dart:js_util'.
bool _isDartJsUtil(Library library) => _isDartLibrary(library, 'js_util');
/// Returns true if [library] is identified by [name].
bool _isDartLibrary(Library library, String name) {
var importUri = library.importUri;
return importUri.isScheme('dart') && importUri.path == name;
}
/// Returns true if the library [l] is "dart:_runtime".
bool _isSdkInternalRuntime(Library l) {
return isSdkInternalRuntimeUri(l.importUri);
}
/// Gets the module import URI that contains [library].
String _libraryToModule(Library library, {bool throwIfNotFound = true}) {
if (library.importUri.isScheme('dart')) {
// TODO(jmesserly): we need to split out HTML.
return js_ast.dartSdkModule;
}
var summary = _importToSummary[library];
if (summary == null) {
if (throwIfNotFound) {
throw StateError('Could not find summary for library "$library".');
}
return '';
}
var moduleName = _summaryToModule[summary];
if (moduleName == null) {
if (throwIfNotFound) {
throw StateError('Could not find module name for library "$library" '
'from component "$summary".');
}
return '';
}
return moduleName;
}
void _emitLibrary(Library library) {
// NOTE: this method isn't the right place to initialize per-library state.
// Classes can be visited out of order, so this is only to catch things that
// haven't been emitted yet.
//
// See _emitClass.
_staticTypeContext.enterLibrary(_currentLibrary!);
if (_isBuildingSdk) {
_containerizeSymbols = _isWebLibrary(library.importUri);
}
if (_isSdkInternalRuntime(library)) {
// Add embedded globals.
_moduleItems.add(
_runtimeCall('typeUniverse = #', [js_ast.createRtiUniverse()])
.toStatement());
// `dart:_runtime` uses a different order for bootstrapping.
//
// Functions are first because we use them to associate type info
// (such as `dart.fn`), then classes/typedefs, then fields
// (which instantiate classes).
//
// For other libraries, we start with classes/types, because functions
// often use classes/types from the library in their signature.
//
// TODO(jmesserly): we can merge these once we change signatures to be
// lazily associated at the tear-off point for top-level functions.
_emitLibraryProcedures(library);
_emitTopLevelFields(library.fields);
library.classes.forEach(_emitClass);
} else {
library.classes.forEach(_emitClass);
_emitLibraryProcedures(library);
_emitTopLevelFields(library.fields);
}
// Additional method used by the module system to link class hierarchies.
_moduleItems.add(_emitLibraryLinkMethod(library));
_staticTypeContext.leaveLibrary(_currentLibrary!);
}
void _emitExports(Library library) {
library.additionalExports.forEach(_emitExport);
}
void _emitExport(Reference export) {
var library = _currentLibrary!;
// We only need to export main as it is the only method part of the
// publicly exposed JS API for a library.
var node = export.node;
if (node is Procedure && node.name.text == 'main') {
// Don't allow redefining names from this library.
var name = _emitTopLevelName(node);
_moduleItems.add(js.statement(
'#.# = #;', [_emitLibraryName(library), name.selector, name]));
}
}
/// Called to emit class declarations.
///
/// During the course of emitting one item, we may emit another. For example
///
/// class D extends B { C m() { ... } }
///
/// Because D depends on B, we'll emit B first if needed. However C is not
/// used by top-level JavaScript code, so we can ignore that dependency.
void _emitClass(Class c) {
// Avoid attempting to compile classes we reach through emitting class
// extends supertypes when they are not members of the library being
// compiled.
// TODO(nshahan): Once `_declareBeforeUse` is removed this escape hatch will
// no longer be necessary.
if (c.enclosingLibrary != _currentLibrary) return;
var savedClass = _currentClass;
var savedLibrary = _currentLibrary;
var savedUri = _currentUri;
_currentClass = c;
_currentLibrary = c.enclosingLibrary;
_currentUri = c.fileUri;
var savedTypeEnvironment = _currentTypeEnvironment;
// When compiling the type heritage of the class we can't reference an rti
// object attached to an instance. Instead we construct a type environment
// manually when needed. Later we use the rti attached to an instance for
// a simpler representation within instance members of the class.
_currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters);
// Mixins are unrolled in _defineClass.
if (!c.isAnonymousMixin) {
// If this class is annotated with `@JS`, then we only need to emit the
// non-external factories and static members.
if (!hasJSInteropAnnotation(c)) {
_moduleItems.add(_emitClassDeclaration(c));
} else {
var interopClassDef = _emitJSInteropClassNonExternalMembers(c);
if (interopClassDef != null) _moduleItems.add(interopClassDef);
}
}
// The const table depends on dart.defineLazy, so emit it after the SDK.
if (_isSdkInternalRuntime(_currentLibrary!)) {
_constTableInsertionIndex = _moduleItems.length;
}
_currentClass = savedClass;
_currentLibrary = savedLibrary;
_currentUri = savedUri;
_currentTypeEnvironment = savedTypeEnvironment;
}
/// To emit top-level classes, we sometimes need to reorder them.
///
/// This function takes care of that, and also detects cases where reordering
/// failed, and we need to resort to lazy loading, by marking the element as
/// lazy. All elements need to be aware of this possibility and generate code
/// accordingly.
///
/// If we are not emitting top-level code, this does nothing, because all
/// declarations are assumed to be available before we start execution.
/// See [startTopLevel].
void _declareBeforeUse(Class? c) {
if (c != null && _emittingClassExtends) {
_emitClass(c);
}
}
static js_ast.Identifier _emitIdentifier(String name) =>
js_ast.Identifier(js_ast.toJSIdentifier(name));
static js_ast.TemporaryId _emitTemporaryId(String name) =>
js_ast.TemporaryId(js_ast.toJSIdentifier(name));
js_ast.Statement _emitClassDeclaration(Class c) {
var className = _emitTopLevelNameNoExternalInterop(c);
var savedClassProperties = _classProperties;
_classProperties =
ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, c);
var body = <js_ast.Statement>[];
// ClassPropertyModel.build introduces symbols for virtual field accessors.
_classProperties!.virtualFields.forEach((field, virtualField) {
// TODO(vsm): Clean up this logic.
//
// Typically, [emitClassPrivateNameSymbol] creates a new symbol. If it
// is called multiple times, that symbol is cached. If the former,
// assign directly to [virtualField]. If the latter, copy the old
// variable to [virtualField].
var symbol = _emitClassPrivateNameSymbol(
c.enclosingLibrary, getLocalClassName(c), field, virtualField);
if (symbol != virtualField) {
_addSymbol(virtualField, _getSymbolValue(symbol));
if (!_containerizeSymbols) {
body.add(js.statement('const # = #;', [virtualField, symbol]));
}
}
});
var jsCtors = _defineConstructors(c, className);
var jsMethods = _emitClassMethods(c);
_emitSuperHelperSymbols(body);
// Deferred supertypes must be evaluated lazily while emitting classes to
// prevent evaluating a JS expression for a deferred type from influencing
// class declaration order (such as when calling 'emitDeferredType').
var deferredSupertypes = <js_ast.Statement Function()>[];
// Emit the class, e.g. `core.Object = class Object { ... }`
_defineClass(c, className, jsMethods, body, deferredSupertypes);
body.addAll(jsCtors);
// Emit things that come after the ES6 `class ... { ... }`.
var jsPeerNames = _extensionTypes.getNativePeers(c);
if (jsPeerNames.length == 1 && c.typeParameters.isNotEmpty) {
// Special handling for JSArray<E>
body.add(_runtimeStatement('setExtensionBaseClass(#, #)', [
className,
_runtimeCall('global.#', [jsPeerNames[0]])
]));
}
/// Collects all implemented types in the ancestry of [cls].
Iterable<Supertype> transitiveImplementedTypes(Class cls) {
var allImplementedTypes = <Supertype>{};
var toVisit = ListQueue<Supertype>()..addAll(cls.implementedTypes);
if (cls.isMixinApplication) {
// Implemented types can come through the immediate mixin so we seed
// the search with it as well.
var mixedInType = cls.mixedInType;
if (mixedInType != null) toVisit.add(mixedInType);
}
while (toVisit.isNotEmpty) {
var supertype = toVisit.removeFirst();
var superclass = supertype.classNode;
if (allImplementedTypes.contains(supertype) ||
superclass == _coreTypes.objectClass) {
continue;
}
toVisit.addAll(superclass.supers);
// Skip encoding the synthetic classes in the type rules because they
// will never be instantiated or appear in type tests.
if (superclass.isAnonymousMixin) continue;
allImplementedTypes.add(supertype);
}
return allImplementedTypes;
}
// Tag all classes with the resources needed by the dart:_rti library.
var name = _typeRecipeGenerator.interfaceTypeRecipe(c);
var implementedRecipes = [
name,
for (var type in transitiveImplementedTypes(c))
_typeRecipeGenerator.interfaceTypeRecipe(type.classNode)
];
body.add(_runtimeStatement('addRtiResources(#, #)',
[className, js_ast.stringArray(implementedRecipes)]));
_emitClassSignature(c, className, body);
_initExtensionSymbols(c);
if (!c.isMixinDeclaration) {
// TODO(55547): Move "Extension member" manipulations to the library link
// method.
_defineExtensionMembers(className, body);
}
var typeFormals = c.typeParameters;
var evaluatedDeferredSupertypes =
deferredSupertypes.map<js_ast.Statement>((f) => f()).toList();
if (typeFormals.isNotEmpty) {
var genericClassStmts = _defineGenericClass(typeFormals,
js_ast.Statement.from(body), evaluatedDeferredSupertypes);
body = [...genericClassStmts];
} else {
_afterClassDefItems.addAll(evaluatedDeferredSupertypes);
}
_emitStaticFieldsAndAccessors(c, body);
if (c == _coreTypes.objectClass) {
// Avoid polluting the native JavaScript Object prototype with the members
// of the Dart Core Object class.
// Instead, just assign the identity equals method.
body.add(_runtimeStatement('_installIdentityEquals()'));
} else {
for (var peer in jsPeerNames) {
_registerExtensionType(c, peer, body);
}
}
_classProperties = savedClassProperties;
return js_ast.Statement.from(body);
}
/// Emits a class declaration for the JS interop class [c] for any
/// non-external factories or static members.
///
/// If [c] is not an interop class or does not contain non-external factories
/// or static members, returns null.
js_ast.Statement? _emitJSInteropClassNonExternalMembers(Class c) {
if (!hasJSInteropAnnotation(c)) return null;
var className = _emitTopLevelNameNoExternalInterop(c);
var nonExternalMethods = <js_ast.Method>[];
for (var procedure in c.procedures) {
if (procedure.isExternal) continue;
// Don't emit tear-offs for @staticInterop members as they're disallowed.
if (_isStaticInteropTearOff(procedure)) continue;
if (procedure.isFactory && !procedure.isRedirectingFactory) {
// Skip redirecting factories (they've already been resolved).
var factory = _emitFactoryConstructor(procedure);
if (factory != null) nonExternalMethods.add(factory);
} else if (procedure.isStatic) {
var staticMethod = _emitMethodDeclaration(procedure);
if (staticMethod != null) nonExternalMethods.add(staticMethod);
}
}
// Emit static fields, if there are any.
var fieldInitialization = <js_ast.Statement>[];
_emitStaticFieldsAndAccessors(c, fieldInitialization);
// Avoid unnecessary code emission if there are no members we care about.
if (nonExternalMethods.isNotEmpty || fieldInitialization.isNotEmpty) {
// Note that this class has no heritage. This class should never be used
// as a type. It's merely a placeholder for static members.
var body = <js_ast.Statement>[
_emitClassStatement(c, className, null, nonExternalMethods)
.toStatement()
];
var typeFormals = c.typeParameters;
if (typeFormals.isNotEmpty) {
var genericClassStmts =
_defineGenericClass(typeFormals, js_ast.Statement.from(body), []);
body = [...genericClassStmts, ...fieldInitialization];
} else {
body = [...body, ...fieldInitialization];
}
return js_ast.Statement.from(body);
}
return null;
}
/// Emits a generic class with additional initialization logic.
List<js_ast.Statement> _defineGenericClass(List<TypeParameter> formals,
js_ast.Statement body, List<js_ast.Statement> deferredBaseClass) {
assert(formals.isNotEmpty);
return [
..._typeTable.dischargeFreeTypes(formals),
body,
...deferredBaseClass,
];
}
js_ast.Statement _emitClassStatement(Class c, js_ast.Expression className,
js_ast.Expression? heritage, List<js_ast.Method> methods) {
var classIdentifier = _emitTemporaryId(getLocalClassName(c));
if (_options.emitDebugSymbols) classIdentifiers[c] = classIdentifier;
if (!requiresJsExtends(c)) {
if (heritage != null) {
_currentLibraryLinkFunctionBody.add(
_runtimeStatement('classExtends(#, #)', [className, heritage]));
}
heritage = null;
}
var classExpr = js_ast.ClassExpression(classIdentifier, heritage, methods);
return js.statement('# = #;', [className, classExpr]);
}
/// Returns `true` when the compiled representation of [cls] still needs
/// an `extends` clause in it.
///
/// Eventually there should be no classes that require the use of the
/// JavaScript `extends` keyword and this test will be unnecessary.
bool requiresJsExtends(Class cls) {
// TODO(55545): Stop emitting `extends baseClass` when mixin applications
// classes can be linked in the library link method.
if (cls.isAnonymousMixin ||
cls.isMixinApplication ||
cls.isMixinDeclaration ||
cls.isMixinClass) {
return true;
}
if (_classProperties != null &&
(_classProperties!.extensionAccessors.isNotEmpty ||
_classProperties!.extensionMethods.isNotEmpty)) {
// TODO(55547): Stop emitting `extends baseClass` when classes with
// overrides of extension members added to native peers can be linked in
// the library link method.
return true;
}
// TODO(55547): Stop emitting `extends baseClass` when classes with native
// peers can be linked in the library link method.
return _extensionTypes.getNativePeers(cls).isNotEmpty ||
// The entire class hierarchy of classes with native peers must be
// available too.
_isDartLibrary(_currentLibrary!, '_interceptors') ||
_isDartLibrary(_currentLibrary!, 'typed_data') ||
_isDartLibrary(_currentLibrary!, '_native_typed_data');
}
/// Like [_emitClassStatement] but emits a Dart 2.1 mixin represented by
/// [c].
///
/// Mixins work similar to normal classes, but their instance methods close
/// over the actual superclass. Given a Dart class like:
///
/// mixin M on C {
/// foo() => super.foo() + 42;
/// }
///
/// We generate a JS class like this:
///
/// lib.M = class M extends core.Object {}
/// lib.M[dart.mixinOn] = (C) => class M extends C {
/// foo() {
/// return super.foo() + 42;
/// }
/// };
///
/// The special `dart.mixinOn` symbolized property is used by the runtime
/// helper `dart.applyMixin`. The helper calls the function with the actual
/// base class, and then copies the resulting members to the destination
/// class.
///
/// In the long run we may be able to improve this so we do not have the
/// unnecessary class, but for now, this lets us get the right semantics with
/// minimal compiler and runtime changes.
void _emitMixinStatement(
Class c,
js_ast.Expression className,
js_ast.Expression heritage,
List<js_ast.Method> methods,
List<js_ast.Statement> body) {
var staticMethods = methods.where((m) => m.isStatic).toList();
var instanceMethods = methods.where((m) => !m.isStatic).toList();
body.add(_emitClassStatement(c, className, heritage, staticMethods));
var superclassId = _emitTemporaryId(getLocalClassName(c.superclass!));
var classId = className is js_ast.Identifier
? className
: _emitTemporaryId(getLocalClassName(c));
var mixinMemberClass =
js_ast.ClassExpression(classId, superclassId, instanceMethods);
js_ast.Node arrowFnBody = mixinMemberClass;
var extensionInit = <js_ast.Statement>[];
_defineExtensionMembers(classId, extensionInit);
if (extensionInit.isNotEmpty) {
extensionInit.insert(0, mixinMemberClass.toStatement());
extensionInit.add(classId.toReturn());
arrowFnBody = js_ast.Block(extensionInit);
}
body.add(js.statement('#[#] = #', [
className,
_runtimeCall('mixinOn'),
js_ast.ArrowFun([superclassId], arrowFnBody)
]));
}
void _defineClass(
Class c,
js_ast.Expression className,
List<js_ast.Method> methods,
List<js_ast.Statement> body,
List<js_ast.Statement Function()> deferredSupertypes) {
if (c == _coreTypes.objectClass) {
body.add(_emitClassStatement(c, className, null, methods));
return;
}
js_ast.Expression emitDeferredClassRef(InterfaceType type) {
var savedEmittingDeferredType = _emittingDeferredType;
_emittingDeferredType = true;
_declareBeforeUse(type.classNode);
var deferredClassRef = _emitClassRef(type);
_emittingDeferredType = savedEmittingDeferredType;
return deferredClassRef;
}
bool shouldDefer(InterfaceType type) {
var visited = <DartType>{};
bool defer(InterfaceType t) {
var tc = t.classNode;
if (c == tc) return true;
if (tc == _coreTypes.objectClass || !visited.add(t)) return false;
var mixin = tc.mixedInType;
return mixin != null && defer(mixin.asInterfaceType) ||
defer(tc.supertype!.asInterfaceType);
}
return defer(type);
}
js_ast.Expression emitClassRef(InterfaceType t) {
// TODO(jmesserly): investigate this. It seems like `lazyJSType` is
// invalid for use in an `extends` clause, hence this workaround.
return _emitJSInterop(t.classNode) ?? _emitClassRef(t);
}
js_ast.Expression getBaseClass(int count) {
var base = emitDeferredClassRef(
c.getThisType(_coreTypes, c.enclosingLibrary.nonNullable));
while (--count >= 0) {
base = _emitJSObjectGetPrototypeOf(base, fullyQualifiedName: true);
}
return base;
}
// Find the real (user declared) superclass and the list of mixins.
// We'll use this to unroll the intermediate classes.
//
// TODO(jmesserly): consider using Kernel's mixin unrolling.
var superclass = _superClassAsWritten(c);
var supertype = identical(c.superclass, superclass)
? c.supertype!.asInterfaceType
: _hierarchy.getClassAsInstanceOf(c, superclass)!.asInterfaceType;
// All mixins (real and anonymous) classes applied to c.
var mixinApplications = [
if (c.mixedInClass != null) c.mixedInClass,
for (var sc = c.superclass!;
sc.isAnonymousMixin && sc.mixedInClass != null;
sc = sc.superclass!)
sc,
].reversed.toList();
var hasUnnamedSuper = _hasUnnamedInheritedConstructor(superclass);
void emitMixinConstructors(js_ast.Expression className,
Class mixinSuperclass, Class mixinClass, InterfaceType mixin) {
for (var ctor in mixinSuperclass.constructors) {
var savedUri = _currentUri;
_currentUri = ctor.enclosingClass.fileUri;
var sharedParams = _emitParameters(ctor.function, isForwarding: true);
var mixinConstructorParams = [
if (_requiresRtiForInstantiation(mixinSuperclass)) _rtiParam,
...sharedParams
];
var superConstructorArgs = [
if (_requiresRtiForInstantiation(ctor.enclosingClass))
js_ast.LiteralNull(),
...sharedParams
];
js_ast.Statement? mixinCtor;
if (_hasUnnamedConstructor(mixin.classNode)) {
var mixinRti = _requiresRtiForInstantiation(mixin.classNode)
? js_ast.LiteralNull()
: null;
mixinCtor = js.statement('#.#.call(this, #);', [
emitClassRef(mixin),
_usesMixinNew(mixin.classNode)
? _runtimeCall('mixinNew')
: _constructorName(''),
[if (mixinRti != null) mixinRti],
]);
}
var name = ctor.name.text;
var ctorBody = [
if (mixinCtor != null) mixinCtor,
if (name != '' || hasUnnamedSuper)
_emitSuperConstructorCall(
ctor, className, name, superConstructorArgs),
];
// TODO(nshahan) Record the name for this constructor in memberNames.
body.add(_addConstructorToClass(c, className, _constructorName(name),
js_ast.Fun(mixinConstructorParams, js_ast.Block(ctorBody))));
_currentUri = savedUri;
}
}
var savedTopLevelClass = _classEmittingExtends;
_classEmittingExtends = c;
// Unroll mixins.
var baseClass = shouldDefer(supertype)
? emitDeferredClassRef(supertype)
: emitClassRef(supertype);
// TODO(jmesserly): we need to unroll kernel mixins because the synthetic
// classes lack required synthetic members, such as constructors.
//
// Also, we need to generate one extra level of nesting for alias classes.
for (var i = 0; i < mixinApplications.length; i++) {
var m = mixinApplications[i]!;
var mixinClass = m.isAnonymousMixin ? m.mixedInClass! : m;
_declareBeforeUse(mixinClass);
var mixinType =
_hierarchy.getClassAsInstanceOf(c, mixinClass)!.asInterfaceType;
var mixinName =
'${getLocalClassName(superclass)}_${getLocalClassName(mixinClass)}';
var mixinId = _emitTemporaryId('$mixinName\$');
// Collect all forwarding stub members from anonymous mixins classes.
// These can contain covariant parameter checks that need to be applied.
var savedClassProperties = _classProperties;
_classProperties =
ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, m);
var forwardingMembers = {
for (var procedure in m.procedures)
if (procedure.isForwardingStub && !procedure.isAbstract)
procedure.name.text: procedure
};
// Mixin applications can introduce their own reference to the type
// parameters from the class being mixed in and their use can appear in
// the forwarding stubs.
var savedTypeEnvironment = _currentTypeEnvironment;
if (m.typeParameters.isNotEmpty) {
assert(_currentTypeEnvironment is ClassTypeEnvironment);
_currentTypeEnvironment = ClassTypeEnvironment(m.typeParameters);
}
var forwardingMethodStubs = <js_ast.Method>[];
for (var s in forwardingMembers.values) {
// Members are marked as "forwarding stubs" when they require a type
// check of the arguments before calling super. It is assumed here that
// no getters will be marked as a "forwarding stub".
assert(!s.isGetter);
var stub = _emitMethodDeclaration(s);
if (stub != null) forwardingMethodStubs.add(stub);
// If there are getters matching the setters somewhere above in the
// class hierarchy we must also generate a forwarding getter due to the
// representation used in the compiled JavaScript.
if (s.isSetter) {
var getterWrapper = _emitSuperAccessorWrapper(s, const {}, const {});
if (getterWrapper != null) forwardingMethodStubs.add(getterWrapper);
}
}
_currentTypeEnvironment = savedTypeEnvironment;
_classProperties = savedClassProperties;
// Bind the mixin class to a name to workaround a V8 bug with es6 classes
// and anonymous function names.
// TODO(leafp:) Eliminate this once the bug is fixed:
// https://bugs.chromium.org/p/v8/issues/detail?id=7069
body.add(js.statement('const # = #', [
mixinId,
js_ast.ClassExpression(
_emitTemporaryId(mixinName), baseClass, forwardingMethodStubs)
]));
emitMixinConstructors(mixinId, superclass, mixinClass, mixinType);
hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass);
// TODO(55545): Move these operations to the library link method.
if (shouldDefer(mixinType)) {
deferredSupertypes.add(() => _runtimeStatement('applyMixin(#, #)', [
getBaseClass(mixinApplications.length - i),
emitDeferredClassRef(mixinType)
]));
} else {
body.add(_runtimeStatement(
'applyMixin(#, #)', [mixinId, emitClassRef(mixinType)]));
}
baseClass = mixinId;
}
if (c.isMixinDeclaration && !c.isMixinClass) {
_emitMixinStatement(c, className, baseClass, methods, body);
} else {
body.add(_emitClassStatement(c, className, baseClass, methods));
}
_classEmittingExtends = savedTopLevelClass;
}
/// Defines all constructors for this class as ES5 constructors.
List<js_ast.Statement> _defineConstructors(
Class c, js_ast.Expression className) {
var body = <js_ast.Statement>[];
if (c.isAnonymousMixin) {
// We already handled this when we defined the class.
return body;
}
void addConstructor(js_ast.LiteralString name, js_ast.Expression jsCtor) {
body.add(_addConstructorToClass(c, className, name, jsCtor));
}
var fields = c.fields;
for (var ctor in c.constructors) {
if (ctor.isExternal) continue;
var constructorName = _constructorName(ctor.name.text);
memberNames[ctor] = constructorName.valueWithoutQuotes;
addConstructor(
constructorName, _emitConstructor(ctor, fields, className));
}
// If classElement has only factory constructors, and it can be mixed in,
// then we need to emit a special hidden default constructor for use by
// mixins.
if (_usesMixinNew(c)) {
body.add(
js.statement('(#[#] = function() { # }).prototype = #.prototype;', [
className,
_runtimeCall('mixinNew'),
[_initializeFields(fields)],
className
]));
}
return body;
}
void _emitDartSymbols(
Iterable<js_ast.TemporaryId> vars, List<js_ast.ModuleItem> body) {
for (var id in vars) {
body.add(js.statement('const # = Symbol(#)', [id, js.string(id.name)]));
}
}
void _emitSuperHelperSymbols(List<js_ast.Statement> body) {
_emitDartSymbols(
_superHelpers.values.map((m) => m.name as js_ast.TemporaryId), body);
_superHelpers.clear();
}
/// Emits non-external static fields for a class, and initialize them eagerly
/// if possible, otherwise define them as lazy properties.
void _emitStaticFieldsAndAccessors(Class c, List<js_ast.Statement> body) {
var fields = c.fields.where((f) => f.isStatic && !f.isExternal).toList();
var fieldNames = Set.from(fields.map((f) => f.name));
var staticSetters = c.procedures.where(
(p) => p.isStatic && p.isAccessor && fieldNames.contains(p.name));
var members = [...fields, ...staticSetters];
if (fields.isNotEmpty) {
body.add(_emitLazyMembers(_emitTopLevelNameNoExternalInterop(c), members,
(n) => _emitStaticMemberName(n.name.text)));
}
}
/// Ensure `dartx.` symbols we will use are present.
void _initExtensionSymbols(Class c) {
if (_extensionTypes.hasNativeSubtype(c) || c == _coreTypes.objectClass) {
for (var m in c.procedures) {
if (!m.isAbstract && !m.isStatic && !m.name.isPrivate) {
_declareMemberName(m, useExtension: true);
}
}
}
}
/// If a concrete class implements one of our extensions, we might need to
/// add forwarders.
void _defineExtensionMembers(
js_ast.Expression className, List<js_ast.Statement> body) {
void emitExtensions(String helperName, Iterable<String> extensions) {
if (extensions.isEmpty) return;
var names = extensions
.map((e) => _propertyName(js_ast.memberNameForDartMember(e)))
.toList();
body.add(_runtimeStatement('#(#, #)', [
helperName,
className,
js_ast.ArrayInitializer(names, multiline: names.length > 4)
]));
}
var props = _classProperties!;
emitExtensions('defineExtensionMethods', props.extensionMethods);
emitExtensions('defineExtensionAccessors', props.extensionAccessors);
}
/// Emit the signature on the class recording the runtime type information
void _emitClassSignature(
Class c, js_ast.Expression className, List<js_ast.Statement> body) {
var savedTypeEnvironment = _currentTypeEnvironment;
_currentTypeEnvironment =
RtiTypeEnvironment(_currentTypeEnvironment.classTypeParameters);
var savedClass = _classEmittingSignatures;
_classEmittingSignatures = c;
void emitSignature(String name, List<js_ast.Property> elements) {
if (elements.isEmpty) return;
js_ast.Statement setSignature;
if (!name.startsWith('Static')) {
var proto = c == _coreTypes.objectClass
? js.call('Object.create(null)')
: _runtimeCall('get${name}s(#)', [
_emitJSObjectGetPrototypeOf(className, fullyQualifiedName: true)
]);
setSignature = _runtimeStatement('set${name}Signature(#, () => #)', [
className,
_emitJSObjectSetPrototypeOf(
js_ast.ObjectInitializer(elements,
multiline: elements.length > 1),
proto,
fullyQualifiedName: true)
]);
} else {
// TODO(40273) Only tagging with the names of static members until the
// debugger consumes signature information from symbol files.
setSignature = _runtimeStatement('set${name}Signature(#, () => #)', [
className,
js_ast.ArrayInitializer(elements.map((e) => e.name).toList())
]);
}
body.add(setSignature);
}
js_ast.Expression emitClassFieldSignature(Field field, Class fromClass) {
var fieldType =
_typeFromClass(field.type, field.enclosingClass!, fromClass)
.extensionTypeErasure;
var uri = fieldType is InterfaceType
? _cacheUri(
_jsLibraryDebuggerName(fieldType.classNode.enclosingLibrary))
: null;
var isConst = js.boolean(field.isConst);
var isFinal = js.boolean(field.isFinal);
var type = _emitType(fieldType);
var typeResolver = js_ast.ArrowFun([_rtiParam], type);
return uri == null
? js('{type: #, isConst: #, isFinal: #}',
[typeResolver, isConst, isFinal])
: js('{type: #, isConst: #, isFinal: #, libraryUri: #}',
[typeResolver, isConst, isFinal, uri]);
}
var extMethods = _classProperties!.extensionMethods;
var extAccessors = _classProperties!.extensionAccessors;
var staticMethods = <js_ast.Property>[];
var instanceMethods = <js_ast.Property>[];
var instanceMethodsDefaultTypeArgs = <js_ast.Property>[];
var staticGetters = <js_ast.Property>[];
var instanceGetters = <js_ast.Property>[];
var staticSetters = <js_ast.Property>[];
var instanceSetters = <js_ast.Property>[];
List<js_ast.Property> getSignatureList(Procedure p) {
// TODO(40273) Skip for all statics when the debugger consumes signature
// information from symbol files.
if (p.isStatic) {
if (p.isGetter) {
return staticGetters;
} else if (p.isSetter) {
return staticSetters;
} else {
return staticMethods;
}
} else {
if (p.isGetter) {
return instanceGetters;
} else if (p.isSetter) {
return instanceSetters;
} else {
return instanceMethods;
}
}
}
var classProcedures = c.procedures.where((p) => !p.isAbstract).toList();
for (var member in classProcedures) {
// Static getters/setters cannot be called with dynamic dispatch or torn
// off. Static methods can't be called with dynamic dispatch and are
// tagged with a type when torn off. Most are implicitly const and
// canonicalized. Static signatures are only used by the debugger and are
// not needed for runtime correctness.
// TODO(40273) Skip for all statics when the debugger consumes signature
// information from symbol files.
if (isTearOffLowering(member)) continue;
var name = member.name.text;
var reifiedType = _memberRuntimeType(member, c) as FunctionType;
// Don't add redundant signatures for inherited methods whose signature
// did not change. If we are not overriding, or if the thing we are
// overriding has a different reified type from ourselves, we must
// emit a signature on this class. Otherwise we will inherit the
// signature from the superclass.
var memberOverride = c.superclass != null
? _hierarchy.getDispatchTarget(c.superclass!, member.name,
setter: member.isSetter)
: null;
var needsSignature = memberOverride == null ||
reifiedType != _memberRuntimeType(memberOverride, c);
if (needsSignature) {
js_ast.Expression type;
var memberName = _declareMemberName(member);
if (member.isAccessor) {
// These signatures are used for dynamic access and to inform the
// debugger. The `arrayRti` accessor is only used by the dart:_rti
// library internals and should not be included in the accessible
// signatures.
if (c == _jsArrayClass && name == 'arrayRti') continue;
type = _emitType(member.isGetter
? reifiedType.returnType
: reifiedType.positionalParameters[0]);
} else {
type = _emitType(reifiedType);
if (!member.isStatic && reifiedType.typeParameters.isNotEmpty) {
// Instance methods with generic type parameters require extra
// information to support dynamic calls. The default values for the
// type parameters are encoded into a separate storage object for
// use at runtime.
var defaultTypeArgs = js_ast.ArrayInitializer([
for (var parameter in reifiedType.typeParameters)
_emitType(parameter.defaultType)
]);
var typeResolver = js_ast.ArrowFun([_rtiParam], defaultTypeArgs);
var property = js_ast.Property(memberName, typeResolver);
instanceMethodsDefaultTypeArgs.add(property);
// As seen below, sometimes the member signatures are added again
// using the extension symbol as the name. That logic is duplicated
// here to ensure there are always default type arguments accessible
// via the same name as the signature.
// TODO(52867): Cleanup default type argument duplication.
if (extMethods.contains(name) || extAccessors.contains(name)) {
var property = js_ast.Property(
_declareMemberName(member, useExtension: true), typeResolver);
instanceMethodsDefaultTypeArgs.add(property);
}
}
}
var typeResolver = js_ast.ArrowFun([_rtiParam], type);
var property = js_ast.Property(memberName, typeResolver);
var signatures = getSignatureList(member);
signatures.add(property);
if (!member.isStatic &&
(extMethods.contains(name) || extAccessors.contains(name))) {
// TODO(52867): Cleanup signature duplication.
var typeResolver = js_ast.ArrowFun([_rtiParam], type);
var property = js_ast.Property(
_declareMemberName(member, useExtension: true), typeResolver);
signatures.add(property);
}
}
}
emitSignature('Method', instanceMethods);
emitSignature('MethodsDefaultTypeArg', instanceMethodsDefaultTypeArgs);
// TODO(40273) Skip for all statics when the debugger consumes signature
// information from symbol files.
emitSignature('StaticMethod', staticMethods);
emitSignature('Getter', instanceGetters);
emitSignature('Setter', instanceSetters);
emitSignature('StaticGetter', staticGetters);
emitSignature('StaticSetter', staticSetters);
body.add(_runtimeStatement('setLibraryUri(#, #)',
[className, _cacheUri(_jsLibraryDebuggerName(c.enclosingLibrary))]));
var instanceFields = <js_ast.Property>[];
var staticFields = <js_ast.Property>[];
var classFields = c.fields.toList();
for (var field in classFields) {
// Static fields cannot be called with dynamic dispatch or torn off. The
// signatures are only used by the debugger and are not needed for runtime
// correctness.
var memberName = _declareMemberName(field);
var fieldSig = emitClassFieldSignature(field, c);
var property = js_ast.Property(memberName, fieldSig);
// TODO(40273) Skip static fields when the debugger consumes signature
// information from symbol files.
(field.isStatic ? staticFields : instanceFields).add(property);
}
emitSignature('Field', instanceFields);
// TODO(40273) Skip for all statics when the debugger consumes signature
// information from symbol files.
emitSignature('StaticField', staticFields);
_classEmittingSignatures = savedClass;
_currentTypeEnvironment = savedTypeEnvironment;
}
DartType _memberRuntimeType(Member member, Class fromClass) {
var f = member.function;
if (f == null) {
return (member as Field).type;
}
FunctionType result;
if (!f.positionalParameters.any(isCovariantParameter) &&
!f.namedParameters.any(isCovariantParameter)) {
// Avoid tagging a member as Function? or Function*
result = f.computeThisFunctionType(Nullability.nonNullable);
} else {
var fComputed =
f.computeThisFunctionType(member.enclosingLibrary.nonNullable);
var fComputedNamedByName = {
for (NamedType namedParameter in fComputed.namedParameters)
namedParameter.name: namedParameter
};
DartType reifyParameter(
VariableDeclaration parameter, DartType fComputedParameter) =>
isCovariantParameter(parameter)
? _coreTypes.objectRawType(member.enclosingLibrary.nullable)
: fComputedParameter;
NamedType reifyNamedParameter(
VariableDeclaration parameter, NamedType fComputedNamedParameter) {
assert(parameter.name == fComputedNamedParameter.name);
return NamedType(parameter.name!,
reifyParameter(parameter, fComputedNamedParameter.type));
}
// TODO(jmesserly): do covariant type parameter bounds also need to be
// reified as `Object`?
result = FunctionType(
List<DartType>.generate(
f.positionalParameters.length,
(index) => reifyParameter(f.positionalParameters[index],
fComputed.positionalParameters[index])),
f.returnType,
Nullability.nonNullable,
namedParameters: List<NamedType>.generate(
f.namedParameters.length,
(index) => reifyNamedParameter(f.namedParameters[index],
fComputedNamedByName[f.namedParameters[index].name]!))
..sort(),
typeParameters: fComputed.typeParameters,
requiredParameterCount: f.requiredParameterCount);
}
return _typeFromClass(result, member.enclosingClass!, fromClass)
as FunctionType;
}
DartType _typeFromClass(DartType type, Class superclass, Class subclass) {
if (identical(superclass, subclass)) return type;
return Substitution.fromSupertype(
_hierarchy.getClassAsInstanceOf(subclass, superclass)!)
.substituteType(type);
}
js_ast.Expression _emitConstructor(
Constructor node, List<Field> fields, js_ast.Expression className) {
var savedUri = _currentUri;
_currentUri = node.fileUri;
_staticTypeContext.enterMember(node);
var savedTypeEnvironment = _currentTypeEnvironment;
_currentTypeEnvironment =
ClassTypeEnvironment(node.enclosingClass.typeParameters);
var params = <js_ast.Parameter>[];
// Generic class constructors accept their RTI as their first argument.
params.addAll(_emitParameters(node.function));
var body = _withCurrentFunction(
node.function,
() => _superDisallowed(
() => _emitConstructorBody(node, fields, className)));
var end = _nodeEnd(node.fileEndOffset);
_currentUri = savedUri;
_staticTypeContext.leaveMember(node);
end ??= _nodeEnd(node.enclosingClass.fileEndOffset);
var constructor = js_ast.Fun([
if (_requiresRtiForInstantiation(node.enclosingClass)) _rtiParam,
...params
], js_ast.Block(body))
..sourceInformation = end;
_currentTypeEnvironment = savedTypeEnvironment;
return constructor;
}
List<js_ast.Statement> _emitConstructorBody(
Constructor node,
List<Field> fields,
js_ast.Expression className,
) {
var cls = node.enclosingClass;
// Generate optional/named argument value assignment. These can not have
// side effects, and may be used by the constructor's initializers, so it's
// nice to do them first.
// Also for const constructors we need to ensure default values are
// available for use by top-level constant initializers.
var fn = node.function;
var body = _emitArgumentInitializers(fn, node.name.text);
// Class instances with type arguments are bound to their RTI on creation.
// This must be bound early, as instantiated fields may reference this RTI.
if (_requiresRtiForInstantiation(cls)) {
// Only set the rti if there isn't one already. This avoids superclasses
// overwriting the value already set by a subclass.
var rtiProperty = _propertyName(js_ast.FixedNames.rtiName);
body.add(js.statement('this.# = this.# || # || #', [
rtiProperty,
rtiProperty,
_rtiParam,
_runtimeCall('getReifiedType(this)')
]));
}
// Redirecting constructors are not allowed to have conventional
// initializers but can have variable declarations in the form of
// initializers to support named arguments appearing anywhere in the
// arguments list.
if (node.initializers.any((i) => i is RedirectingInitializer)) {
body.add(_emitRedirectingConstructor(node.initializers, className));
return body;
}
// Generate field initializers.
// These are expanded into each non-redirecting constructor.
// In the future we may want to create an initializer function if we have
// multiple constructors, but it needs to be balanced against readability.
body.add(_initializeFields(fields, node));
// If no superinitializer is provided, an implicit superinitializer of the
// form `super()` is added at the end of the initializer list, unless the
// enclosing class is class Object.
var superCall = node.initializers.whereType<SuperInitializer>().firstOrNull;
var jsSuper = _emitSuperConstructorCallIfNeeded(cls, className, superCall);
if (jsSuper != null) {
// TODO(50465) Fix incorrect assumption there should always be a super
// initializer here.
if (superCall != null) jsSuper.sourceInformation = _nodeStart(superCall);
body.add(jsSuper);
}
body.add(_emitFunctionScopedBody(fn));
return body;
}
/// Returns the "actual" superclass of [c].
///
/// Walks up the superclass chain looking for the first actual class
/// skipping any synthetic classes inserted by the CFE.
Class _superClassAsWritten(Class c) {
var superclass = c.superclass!;
while (superclass.isAnonymousMixin) {
superclass = superclass.superclass!;
}
return superclass;
}
/// Returns `true` if [cls] requires/accepts an RTI during instantiation.
///
/// We check [cls]'s transitive super classes for generic type parameters,
/// but we do not consider anonymous mixins, implemented types or mixin on
/// clauses - as their constructors are never invoked via super calls.
/// Synthetic mixins are also skipped (despite sometimes having type
/// parameters) since they can't be referenced during instantiation.
///
/// Context: type arguments must be provided to a generic class during its
/// instantiation. To avoid extraneous RTI evals, we pass the entire class's
/// RTI instead of each type parameter's RTI individually. RTIs are attached
/// to the instance on the hidden '$ti' field (see: FixedNames.rtiName). We
/// attach RTIs eagerly (i.e., closer to the 'leaf' than the 'root') for
/// simplicity. Setters on 'this' propagate up super calls since Dart super
/// calls are synthetic. Ordinary JS super calls would require us to
/// propagate the RTI all the way to the 'uppermost' generic class.
bool _requiresRtiForInstantiation(Class? cls) {
if (cls == null) return false;
var cachedResult = _requiresRtiForInstantiationCache[cls];
if (cachedResult != null) return cachedResult;
// Skip synthetic mixins since their RTIs are never needed during
// instantiation.
if (cls.isAnonymousMixin) {
cls = _superClassAsWritten(cls);
}
var hasTypeParameters = cls.typeParameters.isNotEmpty ||
_requiresRtiForInstantiation(cls.superclass);
_requiresRtiForInstantiationCache[cls] = hasTypeParameters;
return hasTypeParameters;
}
js_ast.LiteralString _constructorName(String name) {
if (name == '') {
// Default constructors (factory or not) use `new` as their name.
return _propertyName('new');
}
return _emitStaticMemberName(name);
}
js_ast.Statement _emitRedirectingConstructor(
List<Initializer> initializers, js_ast.Expression className) {
var jsInitializers = <js_ast.Statement>[];
for (var init in initializers) {
if (init is LocalInitializer) {
// Temporary locals are created when named arguments don't appear at
// the end of the arguments list.
jsInitializers.add(visitVariableDeclaration(init.variable));
} else if (init is RedirectingInitializer) {
var rtiParam = _requiresRtiForInstantiation(init.target.enclosingClass)
? _rtiParam
: null;
// We can't dispatch to the constructor with `this.new` as that might
// hit a derived class constructor with the same name.
var initializer = js.statement('#.#.call(this, #);', [
className,
_constructorName(init.target.name.text),
[
if (rtiParam != null) rtiParam,
..._emitArgumentList(init.arguments, types: false)
]
]);
jsInitializers.add(initializer);
}
}
return js_ast.Block(jsInitializers);
}
js_ast.Statement? _emitSuperConstructorCallIfNeeded(
Class c, js_ast.Expression className, SuperInitializer? superInit) {
if (c == _coreTypes.objectClass) return null;
Constructor ctor;
List<js_ast.Expression> args;
if (superInit == null) {
ctor = unnamedConstructor(c.superclass!)!;
args = [];
} else {
ctor = superInit.target;
var savedTypeEnvironment = _currentTypeEnvironment;
_currentTypeEnvironment = ClassTypeEnvironment(c.typeParameters);
// An RTI will already have been set at the constructor call site, so
// pass nothing if the superclass is expecting an RTI.
var rti = _requiresRtiForInstantiation(ctor.enclosingClass)
? js_ast.LiteralNull()
: null;
args = [
if (rti != null) rti,
..._emitArgumentList(superInit.arguments, types: true)
];
_currentTypeEnvironment = savedTypeEnvironment;
}
// We can skip the super call if it's empty. Most commonly this happens for
// things that extend Object, and don't have any field initializers or their
// own default constructor.
if (ctor.name.text == '' && !_hasUnnamedSuperConstructor(c)) {
return null;
}
return _emitSuperConstructorCall(ctor, className, ctor.name.text, args);
}
js_ast.Statement _emitSuperConstructorCall(Constructor constructor,
js_ast.Expression className, String name, List<js_ast.Expression> args) {
return js.statement('#.#.call(this, #);', [
_emitJSObjectGetPrototypeOf(className, fullyQualifiedName: true),
_constructorName(name),
args
]);
}
bool _hasUnnamedInheritedConstructor(Class? c) {
if (c == null) return false;
return _hasUnnamedConstructor(c) || _hasUnnamedSuperConstructor(c);
}
bool _hasUnnamedSuperConstructor(Class c) {
return _hasUnnamedConstructor(c.mixedInClass) ||
_hasUnnamedInheritedConstructor(c.superclass);
}
bool _hasUnnamedConstructor(Class? c) {
if (c == null || c == _coreTypes.objectClass) return false;
var ctor = unnamedConstructor(c);
if (ctor != null && !ctor.isSynthetic) return true;
return c.fields.any((f) => !f.isStatic);
}
/// Initialize fields. They follow the sequence:
///
/// 1. field declaration initializer if non-const,
/// 2. field initializing parameters,
/// 3. constructor field initializers,
/// 4. initialize fields not covered in 1-3
js_ast.Statement _initializeFields(List<Field> fields, [Constructor? ctor]) {
// Run field initializers if they can have side-effects.
var ctorFields = ctor?.initializers
.whereType<FieldInitializer>()
.map((c) => c.field)
.toSet();
var body = <js_ast.Statement>[];
void emitFieldInit(Field f, Expression? initializer, TreeNode hoverInfo) {
var virtualField = _classProperties!.virtualFields[f];
// Avoid calling getSymbol on _declareMemberName since _declareMemberName
// calls _emitMemberName downstream, which already invokes getSymbol.
var access = virtualField == null
? _declareMemberName(f)
: _getSymbol(virtualField);
var jsInit = _visitInitializer(initializer, f.annotations);
body.add(jsInit
.toAssignExpression(js.call('this.#', [access])
..sourceInformation = _nodeStart(hoverInfo))
.toStatement());
}
for (var f in fields) {
if (f.isStatic) continue;
var init = f.initializer;
if (ctorFields != null &&
ctorFields.contains(f) &&
(init == null || _constants.isConstant(init))) {
continue;
}
_staticTypeContext.enterMember(f);
emitFieldInit(f, init, f);
_staticTypeContext.leaveMember(f);
}
// Run constructor field initializers such as `: foo = bar.baz`
if (ctor != null) {
for (var init in ctor.initializers) {
if (init is FieldInitializer) {
emitFieldInit(init.field, init.value, init);
} else if (init is LocalInitializer) {
body.add(visitVariableDeclaration(init.variable));
} else if (init is AssertInitializer) {
body.add(visitAssertStatement(init.statement));
}
}
}
return js_ast.Statement.from(body);
}
js_ast.Expression _visitInitializer(
Expression? init, List<Expression> annotations) {
// explicitly initialize to null, to avoid getting `undefined`.
// TODO(jmesserly): do this only for vars that aren't definitely assigned.
if (init == null) return js_ast.LiteralNull();
return _annotatedNullCheck(annotations)
? _notNull(init)
: _visitExpression(init);
}
js_ast.Expression _notNull(Expression expr) {
var jsExpr = _visitExpression(expr);
if (!_isNullable(expr)) return jsExpr;
return _runtimeCall('notNull(#)', [jsExpr]);
}
/// If the class has only factory constructors, and it can be mixed in,
/// then we need to emit a special hidden default constructor for use by
/// mixins.
bool _usesMixinNew(Class mixin) {
// TODO(jmesserly): mixin declarations don't get implicit constructor nodes,
// even if they have fields, so we need to ensure they're getting generated.
return mixin.isMixinDeclaration && _hasUnnamedConstructor(mixin) ||
mixin.superclass?.superclass == null &&
mixin.constructors.every((c) => c.isExternal);
}
js_ast.Statement _addConstructorToClass(Class c, js_ast.Expression className,
js_ast.LiteralString name, js_ast.Expression jsCtor) {
jsCtor = _defineValueOnClass(c, className, name, jsCtor);
return js.statement('#.prototype = #.prototype;', [jsCtor, className]);
}
/// Whether any superclass of [c] defines a static [name].
bool _superclassHasStatic(Class c, String memberName) {
// Note: because we're only considering statics, we can ignore mixins.
// We're only trying to find conflicts due to JS inheriting statics.
var superclass = c.superclass;
var name = Name(memberName, c.enclosingLibrary);
while (true) {
if (superclass == null) return false;
for (var m in superclass.members) {
if (m.name == name &&
(m is Procedure && m.isStatic || m is Field && m.isStatic)) {
return true;
}
}
superclass = superclass.superclass;
}
}
List<js_ast.Method> _emitClassMethods(Class c) {
var virtualFields = _classProperties!.virtualFields;
var jsMethods = <js_ast.Method?>[];
var hasJsPeer = _extensionTypes.isNativeClass(c);
var hasIterator = false;
if (c == _coreTypes.objectClass) {
// Dart does not use ES6 constructors.
// Add an error to catch any invalid usage.
jsMethods.add(js_ast.Method(
_propertyName('constructor'),
js.fun(r'''function() {
throw Error("use `new " + # +
".new(...)` to create a Dart object");
}''', [
_runtimeCall('typeName(#)', [_runtimeCall('getReifiedType(this)')])
])));
} else if (c == _jsArrayClass) {
// Provide access to the Array constructor property, so it works like
// other native types (rather than calling the Dart Object "constructor"
// above, which throws).
//
// This will become obsolete when
// https://github.com/dart-lang/sdk/issues/31003 is addressed.
jsMethods.add(js_ast.Method(
_propertyName('constructor'), js.fun(r'function() { return []; }')));
}
var staticFieldNames = <Name>{};
for (var m in c.fields) {
if (m.isStatic) {
staticFieldNames.add(m.name);
} else if (_extensionTypes.isNativeClass(c)) {
jsMethods.addAll(_emitNativeFieldAccessors(m));
} else if (virtualFields.containsKey(m)) {
jsMethods.addAll(_emitVirtualFieldAccessor(m));
}
}
var getters = <String, Procedure>{};
var setters = <String, Procedure>{};
for (var m in c.procedures) {
if (m.isAbstract) continue;
if (m.isGetter) {
getters[m.name.text] = m;
} else if (m.isSetter) {
setters[m.name.text] = m;
}
}
var savedUri = _currentUri;
for (var m in c.procedures) {
// Static accessors on static/lazy fields are emitted earlier in
// `_emitStaticFieldsAndAccessors`.
if (m.isStatic && m.isAccessor && staticFieldNames.contains(m.name)) {
continue;
}
_staticTypeContext.enterMember(m);
// For the Dart SDK, we use the member URI because it may be different
// from the class (because of patch files). User code does not need this.
//
// TODO(jmesserly): CFE has a bug(?) where nSM forwarders sometimes have a
// bogus file URI, that is mismatched compared to the offsets. This causes
// a crash when we look up the location. So for those forwarders, we just
// suppress source spans.
_currentUri = m.isNoSuchMethodForwarder ? null : m.fileUri;
if (_isForwardingStub(m)) {
// TODO(jmesserly): is there any other kind of forwarding stub?
jsMethods.addAll(_emitCovarianceCheckStub(m));
} else if (m.isFactory) {
if (m.isRedirectingFactory) {
// Skip redirecting factories (they've already been resolved).
} else {
jsMethods.add(_emitFactoryConstructor(m));
}
} else if (m.isAccessor) {
jsMethods.add(_emitMethodDeclaration(m));
jsMethods.add(_emitSuperAccessorWrapper(m, getters, setters));
if (!hasJsPeer && m.isGetter && m.name.text == 'iterator') {
hasIterator = true;
jsMethods.add(_emitIterable(c));
}
} else {
jsMethods.add(_emitMethodDeclaration(m));
}
_staticTypeContext.leaveMember(m);
}
_currentUri = savedUri;
// If the type doesn't have an `iterator`, but claims to implement Iterable,
// we inject the adaptor method here, as it's less code size to put the
// helper on a parent class. This pattern is common in the core libraries
// (e.g. IterableMixin<E> and IterableBase<E>).
//
// (We could do this same optimization for any interface with an `iterator`
// method, but that's more expensive to check for, so it doesn't seem worth
// it. The above case for an explicit `iterator` method will catch those.)
if (!hasJsPeer && !hasIterator) {
jsMethods.add(_emitIterable(c));
}
// Add all of the super helper methods
jsMethods.addAll(_superHelpers.values);
return jsMethods.nonNulls.toList();
}
bool _isForwardingStub(Procedure member) {
if (member.isForwardingStub || member.isForwardingSemiStub) {
if (!_currentLibrary!.importUri.isScheme('dart')) return true;
// TODO(jmesserly): external methods in the SDK seem to get incorrectly
// tagged as forwarding stubs even if they are patched. Perhaps there is
// an ordering issue in CFE. So for now we pattern match to see if it
// looks like an actual forwarding stub.
//
// We may be able to work around this in a cleaner way by simply emitting
// the code, and letting the normal covariance check logic handle things.
// But currently we use _emitCovarianceCheckStub to work around some
// issues in the stubs.
var body = member.function.body;
if (body is ReturnStatement) {
var expr = body.expression;
return expr is SuperMethodInvocation || expr is SuperPropertySet;
}
}
return false;
}
/// Emits a method, getter, or setter.
js_ast.Method? _emitMethodDeclaration(Procedure member) {
if (member.isAbstract) {
return null;
}
js_ast.Fun fn;
if (member.isExternal && !member.isNoSuchMethodForwarder) {
if (member.isStatic) {
// TODO(vsm): Do we need to handle this case?
return null;
}
fn = _emitNativeFunctionBody(member);
} else {
fn = _withMethodDeclarationContext(
member,
() => _emitFunction(member.function, member.name.text,
functionBody: _toSourceLocation(member.fileOffset),
functionEnd: _toSourceLocation(member.fileEndOffset)));
}
var method = js_ast.Method(_declareMemberName(member), fn,
isGetter: member.isGetter,
isSetter: member.isSetter,
isStatic: member.isStatic);
if (isTearOffLowering(member)) {
// Remove all source information from static methods introduced by the
// constructor tearoff CFE lowering.
method.accept(js_ast.SourceInformationClearer());
} else {
method.sourceInformation = _nodeEnd(member.fileEndOffset);
}
return method;
}
js_ast.Fun _emitNativeFunctionBody(Procedure node) {
var name = _annotationName(node, isJSAnnotation) ?? node.name.text;
if (node.isGetter) {
var returnValue = js('this.#', [name]);
if (_isNullCheckableNative(node)) {
// Add a potential null-check on native getter if type is non-nullable.
returnValue = _runtimeCall('checkNativeNonNull(#)', [returnValue]);
}
return js_ast.Fun([], js.block('{ return #; }', [returnValue]));
} else if (node.isSetter) {
var params = _emitParameters(node.function);
return js_ast.Fun(
params, js.block('{ this.# = #; }', [name, params.last]));
} else {
var returnValue = js('this.#.apply(this, args)', [name]);
if (_isNullCheckableNative(node)) {
// Add a potential null-check on return value if type is non-nullable.
returnValue = _runtimeCall('checkNativeNonNull(#)', [returnValue]);
}
return js.fun('function (...args) { return #; }', [returnValue]);
}
}
List<js_ast.Method> _emitCovarianceCheckStub(Procedure member) {
// TODO(jmesserly): kernel stubs have a few problems:
// - they're generated even when there is no concrete super member
// - the stub parameter types don't match the types we need to check to
// ensure soundness of the super member, so we must lookup the super
// member and determine checks ourselves.
// - it generates getter stubs, but these are not used
if (member.isGetter) return const [];
var enclosingClass = member.enclosingClass;
var superMember = member.concreteForwardingStubTarget ??
member.abstractForwardingStubTarget;
if (superMember == null) return const [];
DartType substituteType(DartType t) {
return _typeFromClass(t, superMember.enclosingClass!, enclosingClass!);
}
var superMemberFunction = superMember.function;
var name = _declareMemberName(member);
if (member.isSetter) {
if (superMember is Field && isCovariantField(superMember) ||
superMember is Procedure &&
isCovariantParameter(
superMemberFunction!.positionalParameters[0])) {
return const [];
}
var setterType =
substituteType(superMember.superSetterType).extensionTypeErasure;
if (_types.isTop(setterType)) return const [];
return [
js_ast.Method(
name,
js.fun('function(x) { return super.# = #; }',
[name, _emitCast(_emitIdentifier('x'), setterType)]),
isSetter: true),
js_ast.Method(name, js.fun('function() { return super.#; }', [name]),
isGetter: true)
];
}
assert(!member.isAccessor);
var superMethodType = substituteType(superMemberFunction!
.computeThisFunctionType(superMember.enclosingLibrary.nonNullable))
as FunctionType;
var function = member.function;
var body = <js_ast.Statement>[];
var typeParameters = superMethodType.typeParameters;
_emitCovarianceBoundsCheck(typeParameters, body);
var typeFormals = _emitTypeFormals(typeParameters);
var jsParams = List<js_ast.Parameter>.from(typeFormals);
var positionalParameters = function.positionalParameters;
for (var i = 0, n = positionalParameters.length; i < n; i++) {
var param = positionalParameters[i];
var jsParam = _emitIdentifier(param.name!);
jsParams.add(jsParam);
if (isCovariantParameter(param) &&
!isCovariantParameter(superMemberFunction.positionalParameters[i])) {
var check = _emitCast(jsParam, superMethodType.positionalParameters[i]);
if (i >= function.requiredParameterCount) {
body.add(js.statement('if (# !== void 0) #;', [jsParam, check]));
} else {
body.add(check.toStatement());
}
}
}
var namedParameters = function.namedParameters;
for (var param in namedParameters) {
if (isCovariantParameter(param) &&
!isCovariantParameter(superMemberFunction.namedParameters
.firstWhere((n) => n.name == param.name))) {
var name = _propertyName(param.name!);
var paramType = superMethodType.namedParameters
.firstWhere((n) => n.name == param.name);
body.add(js.statement('if (#) #;', [
_namedArgumentProbe(name),
_emitCast(
js_ast.PropertyAccess(_namedArgumentTemp, name), paramType.type)
]));
}
}
if (body.isEmpty) return const []; // No checks were needed.
if (namedParameters.isNotEmpty) jsParams.add(_namedArgumentTemp);
body.add(js.statement('return super.#(#);', [name, jsParams]));
return [js_ast.Method(name, js_ast.Fun(jsParams, js_ast.Block(body)))];
}
/// Emits a Dart factory constructor to a JS static method.
js_ast.Method? _emitFactoryConstructor(Procedure node) {
if (node.isExternal || isUnsupportedFactoryConstructor(node)) return null;
var function = node.function;
/// Note: factory constructors can't use `sync*`/`async*`/`async` bodies
/// because it would return the wrong type, so we can assume `sync` here.
///
/// We can also skip the logic in [_emitFunction] related to operator
/// methods like ==, as well as generic method parameters.
///
/// If a future Dart version allows factory constructors to take their
/// own type parameters, this will need to be changed to call
/// [_emitFunction] instead.
var name = node.name.text;
var savedTypeEnvironment = _currentTypeEnvironment;
_currentTypeEnvironment = RtiTypeEnvironment([
...function.typeParameters,
..._currentTypeEnvironment.classTypeParameters
]);
var jsBody = js_ast.Block(_withCurrentFunction(function, () {
var block = _emitArgumentInitializers(function, name);
block.add(_emitFunctionScopedBody(function));
return block;
}));
var jsName = _constructorName(name);
memberNames[node] = jsName.valueWithoutQuotes;
// Generic class constructors accept their RTI as their first argument.
var method = js_ast.Method(
jsName,
js_ast.Fun(
[
if (_requiresRtiForInstantiation(node.enclosingClass)) _rtiParam,
..._emitParameters(function)
],
jsBody,
),
isStatic: true,
)..sourceInformation = _nodeEnd(node.fileEndOffset);
_currentTypeEnvironment = savedTypeEnvironment;
return method;
}
/// Emits the expression necessary to access a constructor of [type];
js_ast.Expression _emitConstructorAccess(InterfaceType type) =>
_emitJSInterop(type.classNode) ?? _emitClassRef(type);
/// This is called whenever a derived class needs to introduce a new field,
/// shadowing a field or getter/setter pair on its parent.
///
/// This is important because otherwise, trying to read or write the field
/// would end up calling the getter or setter, and one of those might not even
/// exist, resulting in a runtime error. Even if they did exist, that's the
/// wrong behavior if a new field was declared.
List<js_ast.Method> _emitVirtualFieldAccessor(Field field) {
var virtualField = _classProperties!.virtualFields[field]!;
var virtualFieldSymbol = _getSymbol(virtualField);
var name = _declareMemberName(field);
var getter = js.fun('function() { return this[#]; }', [virtualFieldSymbol]);
var jsGetter = js_ast.Method(name, getter, isGetter: true)
..sourceInformation = _nodeStart(field);
var body = <js_ast.Statement>[];
var value = _emitIdentifier('value');
// Avoid adding a null checks on forwarding field setters.
if (field.hasSetter &&
_requiresExtraNullCheck(field.setterType, field.annotations)) {
body.add(
_nullSafetyParameterCheck(value, field.location, field.name.text));
}
var args = field.isFinal
? [js_ast.Super(), name, value]
: [
js_ast.This(),
virtualFieldSymbol,
if (isCovariantField(field)) _emitCast(value, field.type) else value
];
body.add(js.call('#[#] = #', args).toStatement());
var jsSetter = js_ast.Method(name, js_ast.Fun([value], js_ast.Block(body)),
isSetter: true)
..sourceInformation = _nodeStart(field);
return [jsGetter, jsSetter];
}
/// Provide Dart getters and setters that forward to the underlying native
/// field. Note that the Dart names are always symbolized to avoid
/// conflicts. They will be installed as extension methods on the underlying
/// native type.
List<js_ast.Method> _emitNativeFieldAccessors(Field field) {
// TODO(vsm): Can this by meta-programmed?
// E.g., dart.nativeField(symbol, jsName)
// Alternatively, perhaps it could be meta-programmed directly in
// dart.registerExtensions?
var jsMethods = <js_ast.Method>[];
assert(!field.isStatic);
var name = _annotationName(field, isJSName) ?? field.name.text;
// Generate getter
var fn = js_ast.Fun([], js.block('{ return this.#; }', [name]));
var method = js_ast.Method(_declareMemberName(field), fn, isGetter: true);
jsMethods.add(method);
// Generate setter
if (!field.isFinal) {
var value = _emitTemporaryId('value');
fn = js_ast.Fun([value], js.block('{ this.# = #; }', [name, value]));
method = js_ast.Method(_declareMemberName(field), fn, isSetter: true);
jsMethods.add(method);
}
return jsMethods;
}
/// Emit a getter (or setter) that simply forwards to the superclass getter
/// (or setter).
///
/// This is needed because in ES6, if you only override a getter
/// (alternatively, a setter), then there is an implicit override of the
/// setter (alternatively, the getter) that does nothing.
js_ast.Method? _emitSuperAccessorWrapper(Procedure member,
Map<String, Procedure> getters, Map<String, Procedure> setters) {
if (member.isAbstract) return null;
var name = member.name.text;
var memberName = _declareMemberName(member);
if (member.isGetter) {
if (!setters.containsKey(name) &&
_classProperties!.inheritedSetters.contains(name)) {
// Generate a setter that forwards to super.
var fn = js.fun('function(value) { super[#] = value; }', [memberName]);
return js_ast.Method(memberName, fn, isSetter: true);
}
} else {
assert(member.isSetter);
if (!getters.containsKey(name) &&
_classProperties!.inheritedGetters.contains(name)) {
// Generate a getter that forwards to super.
var fn = js.fun('function() { return super[#]; }', [memberName]);
return js_ast.Method(memberName, fn, isGetter: true);
}
}
return null;
}
/// Support for adapting dart:core Iterable to ES6 versions.
///
/// This lets them use for-of loops transparently:
/// <https://github.com/lukehoban/es6features#iterators--forof>
///
/// This will return `null` if the adapter was already added on a super type,
/// otherwise it returns the adapter code.
// TODO(jmesserly): should we adapt `Iterator` too?
js_ast.Method? _emitIterable(Class c) {
var iterable = _hierarchy.getClassAsInstanceOf(c, _coreTypes.iterableClass);
if (iterable == null) return null;
var superclass = c.superclass!;
// If a parent had an `iterator` (concrete or abstract) or implements
// Iterable, we know the adapter is already there, so we can skip it as a
// simple code size optimization.
var parent = _hierarchy.getDispatchTarget(superclass, Name('iterator'));
if (parent != null) return null;
var parentIterable =
_hierarchy.getClassAsInstanceOf(superclass, _coreTypes.iterableClass);
if (parentIterable != null) return null;
if (c.enclosingLibrary.importUri.isScheme('dart') &&
c.procedures.any((m) => _jsExportName(m) == 'Symbol.iterator')) {
return null;
}
// Otherwise, emit the adapter method, which wraps the Dart iterator in
// an ES6 iterator.
return js_ast.Method(
js.call('Symbol.iterator'),
// TODO(nshahan) Don't access values in `runtimeModule` outside of
// `runtimeCall`.
js.call('function() { return new #.JsIterator(this.#); }', [
_runtimeModule,
_emitMemberName('iterator', memberClass: _coreTypes.iterableClass)
]) as js_ast.Fun);
}
void _registerExtensionType(
Class c, String jsPeerName, List<js_ast.Statement> body) {
var className = _emitTopLevelName(c);
// TODO(55547): Move these operations to the library link method.
if (_typeRep.isPrimitive(_coreTypes.nonNullableRawType(c))) {
body.add(_runtimeStatement(
'definePrimitiveHashCode(#.prototype)', [className]));
}
body.add(_runtimeStatement(
'registerExtension(#, #)', [js.string(jsPeerName), className]));
}
void _emitTopLevelFields(List<Field> fields) {
if (_isSdkInternalRuntime(_currentLibrary!)) {
/// Treat dart:_runtime fields as safe to eagerly evaluate.
// TODO(jmesserly): it'd be nice to avoid this special case.
var lazyFields = <Field>[];
var savedUri = _currentUri;
// Helper functions to test if a constructor invocation is internal and
// should be eagerly evaluated.
bool isInternalConstructor(ConstructorInvocation node) {
var type = node.getStaticType(_staticTypeContext) as InterfaceType;
var library = type.classNode.enclosingLibrary;
return _isSdkInternalRuntime(library);
}
for (var field in fields) {
_staticTypeContext.enterMember(field);
var init = field.initializer;
if (init == null ||
init is BasicLiteral ||
init is ConstructorInvocation && isInternalConstructor(init) ||
init is StaticInvocation && isInlineJS(init.target)) {
if (init is ConstructorInvocation) {
// This is an eagerly executed constructor invocation. We need to
// ensure the class is emitted before this statement.
var type = init.getStaticType(_staticTypeContext) as InterfaceType;
_emitClass(type.classNode);
}
_currentUri = field.fileUri;
_moduleItems.add(js.statement('# = #;', [
_emitTopLevelName(field),
_visitInitializer(init, field.annotations)
]));
} else {
lazyFields.add(field);
}
_staticTypeContext.leaveMember(field);
}
_currentUri = savedUri;
fields = lazyFields;
}
if (fields.isEmpty) return;
_moduleItems.add(_emitLazyMembers(
_emitLibraryName(_currentLibrary!), fields, _emitTopLevelMemberName));
}
js_ast.Statement _emitLazyMembers(
js_ast.Expression objExpr,
Iterable<Member> members,
js_ast.LiteralString Function(Member) emitMemberName,
) {
var accessors = <js_ast.Method>[];
var savedUri = _currentUri;
for (var member in members) {
_currentUri = member.fileUri;
_staticTypeContext.enterMember(member);
var access = emitMemberName(member);
memberNames[member] = access.valueWithoutQuotes;
if (member is Field) {
accessors.add(js_ast.Method(access, _emitStaticFieldInitializer(member),
isGetter: true)
..sourceInformation = _hoverComment(
js_ast.PropertyAccess(objExpr, access),
member.fileOffset,
member.name.text.length));
if (!member.isFinal && !member.isConst) {
var body = <js_ast.Statement>[];
var value = _emitIdentifier('value');
if (_requiresExtraNullCheck(member.setterType, member.annotations)) {
body.add(_nullSafetyParameterCheck(
value, member.location, member.name.text));
}
// Even when no null check is present a dummy setter is still required
// to indicate writeable.
accessors.add(js_ast.Method(
access, js_ast.Fun([value], js_ast.Block(body)),
isSetter: true));
}
} else if (member is Procedure) {
accessors.add(js_ast.Method(
access, _emitFunction(member.function, member.name.text),
isGetter: member.isGetter, isSetter: member.isSetter)
..sourceInformation = _hoverComment(
js_ast.PropertyAccess(objExpr, access),
member.fileOffset,
member.name.text.length));
} else {</