| // 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 '../command/options.dart' show Options; |
| 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 '../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'; |
| |
| /// Name used as a prefix for extension symbols and the identifier of the object |
| /// used to store them. |
| final _extensionSymbolHolderName = 'dartx'; |
| |
| /// 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 Options _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) { |
| assert(_options.emitLibraryBundle); |
| _ticker?.logMs('Emitting library bundle'); |
| var compiledLibraries = <js_ast.Program>[]; |
| for (var library in component.libraries) { |
| var libraryCompiler = LibraryCompiler( |
| component, |
| _hierarchy, |
| _options, |
| _importToSummary, |
| _summaryToModule, |
| coreTypes: _coreTypes, |
| ticker: _ticker, |
| symbolData: _symbolData, |
| ); |
| _libraryCompilers[library] = libraryCompiler; |
| compiledLibraries.add(libraryCompiler.emitLibrary(library)); |
| } |
| // TODO(nshahan): Nothing about these symbols requires them to be |
| // represented in a library. These could be moved to a construct outside |
| // of the language that is provided to libraries that need it. |
| if (component.libraries.contains(_coreTypes.coreLibrary)) { |
| // Collect all extension symbols from all SDK libraries. |
| var allSymbols = { |
| for (var compiler in _libraryCompilers.values) |
| ...compiler._extensionSymbols |
| }; |
| // Create dartx library |
| var id = js_ast.Identifier(_extensionSymbolHolderName); |
| var statements = [ |
| for (var entry in allSymbols.entries) |
| js.statement('# = Symbol(#);', [ |
| js_ast.PropertyAccess(id, js.string(entry.key)), |
| js.string('$_extensionSymbolHolderName.${entry.key}') |
| ]), |
| js.statement('# = #', [ |
| js_ast.PropertyAccess.field(id, 'link'), |
| js_ast.NamedFunction( |
| js_ast.TemporaryId('link__$_extensionSymbolHolderName'), |
| js_ast.Fun(const [], js_ast.Block(const []))) |
| ]), |
| ]; |
| |
| compiledLibraries.insert( |
| 0, |
| js_ast.Program(statements, |
| name: _extensionSymbolHolderName, librarySelfVar: id)); |
| } |
| 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 Options _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; |
| |
| /// 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; |
| |
| /// Library link method statements that perform class hierarchy connections |
| /// like `class C extends E`. |
| final List<js_ast.Statement> _classExtendsLinks = []; |
| |
| /// Library link method statements that define extension members on mixin |
| /// classes. |
| final List<js_ast.Statement> _mixinClassDefineExtensionMemberLinks = []; |
| |
| /// Library link method statements that define extension members on classes. |
| final List<js_ast.Statement> _defineExtensionMemberLinks = []; |
| |
| /// Library link method statements that apply mixins. |
| final List<js_ast.Statement> _mixinApplicationLinks = []; |
| |
| /// Library link method statements that apply extensions on native types. |
| final List<js_ast.Statement> _nativeExtensionLinks = []; |
| |
| /// Library link method statements that create type rules. |
| final List<js_ast.Statement> _typeRuleLinks = []; |
| |
| /// 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; |
| |
| /// 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 [_extensionSymbolsLibraryId]; 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.Identifier>{}; |
| |
| /// 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 _runtimeLibraryId; |
| |
| /// The library referred to by [_runtimeLibraryId]. |
| final Library _runtimeLibrary; |
| |
| /// 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 _extensionSymbolsLibraryId; |
| |
| /// 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. |
| late final bool _isBuildingSdk; |
| |
| /// Whether or not to move top level symbols into top-level containers. |
| /// |
| /// This is set in both [emitModule] and [_emitLibrary]. |
| /// Depends on [_isBuildingSdk]. |
| 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>[]; |
| |
| /// The entrypoint method of a dynamic module, if any. |
| Procedure? _dynamicEntrypoint; |
| |
| 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, |
| Options 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), |
| _runtimeLibrary = sdk.getLibrary('dart:_runtime'), |
| _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!; |
| _isBuildingSdk = library.importUri.scheme == 'dart'; |
| |
| // 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; |
| } |
| var items = _startLibrary(library); |
| _nullableInference.allowNotNullDeclarations = _isBuildingSdk; |
| _typeTable = TypeTable('T', _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. |
| _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(#, { # })', [_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(); |
| _typeRuleLinks.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), "'") |
| ]); |
| _typeRuleLinks.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]); |
| _typeRuleLinks.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(); |
| _typeRuleLinks.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 |
| if (_isSdkInternalRuntime(_currentLibrary!)) { |
| var prerequisiteRtiTypes = [ |
| _coreTypes.objectLegacyRawType, |
| _coreTypes.objectNullableRawType, |
| NeverType.legacy() |
| ]; |
| prerequisiteRtiTypes.forEach((type) { |
| var recipe = _typeRecipeGenerator |
| .recipeInEnvironment(type, EmptyTypeEnvironment()) |
| .recipe; |
| _typeRuleLinks.add(js.call('#.findType("$recipe")', |
| [_emitLibraryName(_rtiLibrary)]).toStatement()); |
| }); |
| } |
| |
| // 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])); |
| } |
| |
| // Additional method used by the module system to link class hierarchies. |
| _moduleItems.add(_emitLibraryLinkMethod(_currentLibrary!)); |
| _ticker?.logMs('Emitted library link method'); |
| |
| // 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'); |
| |
| // Emit the hoisted type table cache variables |
| items.addAll(_typeTable.dischargeBoundTypes()); |
| _ticker?.logMs('Emitted type 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([ |
| ..._classExtendsLinks, |
| // The ordering of extensions member definition and mixin applications |
| // is fragile but important for the correct functionality of the html and |
| // friends libraries. All mixins should have extension members defined |
| // before being applied. Mixin classes are handled here, regular mixins |
| // are handled inside the mixin application closure. |
| ..._mixinClassDefineExtensionMemberLinks, |
| ..._mixinApplicationLinks, |
| // Extension members defined and mixed in above will be discovered during |
| // the prototype walk during these extension member definitions. |
| ..._defineExtensionMemberLinks, |
| ..._nativeExtensionLinks, |
| ..._typeRuleLinks, |
| ]); |
| 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) { |
| _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); |
| } |
| _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. |
| /// |
| /// Class hierarchy links are collected but not emitted as part of the |
| /// declaration. Those operations will be contained in the link method for the |
| /// library. |
| 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; |
| } |
| |
| 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 ... { ... }`. |
| |
| /// 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.isMixinClass || c.isLegacyMixinEligible(_coreTypes)) { |
| _defineExtensionMembers(className, _mixinClassDefineExtensionMemberLinks); |
| } else if (!c.isMixinDeclaration) { |
| _defineExtensionMembers(className, _defineExtensionMemberLinks); |
| } |
| |
| 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. |
| _nativeExtensionLinks.add(_runtimeStatement('_installIdentityEquals()')); |
| } else { |
| for (var peer in _extensionTypes.getNativePeers(c)) { |
| _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 (heritage != null) { |
| _classExtendsLinks |
| .add(_runtimeStatement('classExtends(#, #)', [className, heritage])); |
| } |
| var classExpr = js_ast.ClassExpression(classIdentifier, null, methods); |
| return js.statement('# = #;', [className, classExpr]); |
| } |
| |
| /// 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>[]; |
| // The extension members need to be defined here when the class is created |
| // inside the `mixinOn` closure. The prototype chain is connected in this |
| // closure as well so it is safe to perform this operation here instead of |
| // the link method. |
| _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 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); |
| } |
| |
| // 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 = 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; |
| 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), null, forwardingMethodStubs) |
| ])); |
| _classExtendsLinks |
| .add(_runtimeStatement('classExtends(#, #)', [mixinId, baseClass])); |
| emitMixinConstructors(mixinId, superclass, mixinClass, mixinType); |
| hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(mixinClass); |
| _mixinApplicationLinks.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.of(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.#); }', [ |
| _emitLibraryName(_runtimeLibrary), |
| _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])); |
| } |
| _nativeExtensionLinks.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 { |
| throw UnsupportedError( |
| 'Unsupported lazy member type ${member.runtimeType}: $member'); |
| } |
| _staticTypeContext.leaveMember(member); |
| } |
| _currentUri = savedUri; |
| |
| return _runtimeStatement('defineLazy(#, { # })', [objExpr, accessors]); |
| } |
| |
| js_ast.Fun _emitStaticFieldInitializer(Field field) { |
| return js_ast.Fun([], js_ast.Block(_withLetScope(() { |
| return [ |
| js_ast.Return(_visitInitializer(field.initializer, field.annotations)) |
| ]; |
| }))); |
| } |
| |
| List<js_ast.Statement> _withLetScope( |
| List<js_ast.Statement> Function() visitBody) { |
| var savedLetVariables = _letVariables; |
| _letVariables = []; |
| |
| var body = visitBody(); |
| var letVars = _initLetVariables(); |
| if (letVars != null) body.insert(0, letVars); |
| |
| _letVariables = savedLetVariables; |
| return body; |
| } |
| |
| js_ast.PropertyAccess _emitTopLevelName(NamedNode n, {String suffix = ''}) { |
| return _emitJSInterop(n) ?? |
| _emitTopLevelNameNoExternalInterop(n, suffix: suffix); |
| } |
| |
| /// Like [_emitMemberName], but for declaration sites. |
| /// |
| /// Unlike call sites, we always have an element available, so we can use it |
| /// directly rather than computing the relevant options for [_emitMemberName]. |
| js_ast.Expression _declareMemberName(Member m, {bool? useExtension}) { |
| var c = m.enclosingClass; |
| return _emitMemberName(m.name.text, |
| isStatic: m is Field ? m. |