| // Copyright (c) 2017, 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:math' show max, min; |
| |
| import 'package:front_end/src/api_unstable/ddc.dart' show TypeSchemaEnvironment; |
| import 'package:kernel/class_hierarchy.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/kernel.dart' hide MapEntry; |
| import 'package:kernel/library_index.dart'; |
| import 'package:kernel/type_algebra.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:source_span/source_span.dart' show SourceLocation; |
| import 'package:path/path.dart' as p; |
| |
| import '../compiler/js_names.dart' as js_ast; |
| import '../compiler/js_utils.dart' as js_ast; |
| import '../compiler/module_builder.dart' show pathToJSIdentifier; |
| import '../compiler/shared_command.dart' show SharedCompilerOptions; |
| import '../compiler/shared_compiler.dart'; |
| import '../js_ast/js_ast.dart' as js_ast; |
| import '../js_ast/js_ast.dart' show js; |
| import '../js_ast/source_map_printer.dart' show NodeEnd, NodeSpan, HoverComment; |
| import 'constants.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 'type_table.dart'; |
| |
| class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression> |
| with SharedCompiler<Library, Class, InterfaceType, FunctionNode> |
| implements |
| StatementVisitor<js_ast.Statement>, |
| ExpressionVisitor<js_ast.Expression>, |
| DartTypeVisitor<js_ast.Expression> { |
| final SharedCompilerOptions _options; |
| |
| /// Maps a library URI import, that is not in [_libraries], to the |
| /// corresponding Kernel summary module we imported it with. |
| final _importToSummary = Map<Library, Component>.identity(); |
| |
| /// Maps a Kernel summary to the JS import name for the module. |
| final _summaryToModule = Map<Component, String>.identity(); |
| |
| /// The variable for the current catch clause |
| VariableDeclaration _rethrowParameter; |
| |
| /// In an async* function, this represents the stream controller parameter. |
| js_ast.TemporaryId _asyncStarController; |
| |
| Set<Class> _pendingClasses; |
| |
| /// 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 = _emitTemporaryId("CT"); |
| |
| // Constant getters used to populate the constant table. |
| final _constLazyAccessors = List<js_ast.Method>(); |
| |
| /// 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 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; |
| |
| Component _component; |
| |
| Library _currentLibrary; |
| |
| FunctionNode _currentFunction; |
| |
| /// Whether we are currently generating code for the body of a `JS()` call. |
| bool _isInForeignJS = false; |
| |
| /// Table of named and possibly hoisted types. |
| TypeTable _typeTable; |
| |
| /// The global extension type table. |
| // TODO(jmesserly): rename to `_nativeTypes` |
| final NativeTypeSet _extensionTypes; |
| |
| final CoreTypes _coreTypes; |
| |
| final TypeEnvironment _types; |
| |
| 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; |
| |
| final _superHelpers = Map<String, js_ast.Method>(); |
| |
| // 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>(); |
| |
| final Class _jsArrayClass; |
| final Class _privateSymbolClass; |
| final Class _linkedHashMapImplClass; |
| final Class _identityHashMapImplClass; |
| final Class _linkedHashSetClass; |
| final Class _linkedHashSetImplClass; |
| final Class _identityHashSetImplClass; |
| final Class _syncIterableClass; |
| final Class _asyncStarImplClass; |
| |
| /// The dart:async `StreamIterator<T>` type. |
| final Class _asyncStreamIteratorClass; |
| |
| final DevCompilerConstants _constants; |
| |
| final NullableInference _nullableInference; |
| |
| factory ProgramCompiler(Component component, ClassHierarchy hierarchy, |
| SharedCompilerOptions options) { |
| var coreTypes = CoreTypes(component); |
| var types = TypeSchemaEnvironment(coreTypes, hierarchy); |
| var constants = DevCompilerConstants(); |
| var nativeTypes = NativeTypeSet(coreTypes, constants); |
| var jsTypeRep = JSTypeRep(types, hierarchy); |
| return ProgramCompiler._(coreTypes, coreTypes.index, nativeTypes, constants, |
| types, hierarchy, jsTypeRep, NullableInference(jsTypeRep), options); |
| } |
| |
| ProgramCompiler._( |
| this._coreTypes, |
| LibraryIndex sdk, |
| this._extensionTypes, |
| this._constants, |
| this._types, |
| this._hierarchy, |
| this._typeRep, |
| this._nullableInference, |
| this._options) |
| : _jsArrayClass = sdk.getClass('dart:_interceptors', 'JSArray'), |
| _asyncStreamIteratorClass = |
| sdk.getClass('dart:async', 'StreamIterator'), |
| _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:collection', '_HashSet'), |
| _identityHashSetImplClass = |
| sdk.getClass('dart:collection', '_IdentityHashSet'), |
| _syncIterableClass = sdk.getClass('dart:_js_helper', 'SyncIterable'), |
| _asyncStarImplClass = sdk.getClass('dart:async', '_AsyncStarImpl'); |
| |
| @override |
| Uri get currentLibraryUri => _currentLibrary.importUri; |
| |
| @override |
| Library get currentLibrary => _currentLibrary; |
| |
| @override |
| Library get coreLibrary => _coreTypes.coreLibrary; |
| |
| @override |
| FunctionNode get currentFunction => _currentFunction; |
| |
| @override |
| InterfaceType get privateSymbolType => |
| _coreTypes.legacyRawType(_privateSymbolClass); |
| |
| @override |
| InterfaceType get internalSymbolType => |
| _coreTypes.legacyRawType(_coreTypes.internalSymbolClass); |
| |
| js_ast.Program emitModule(Component component, List<Component> summaries, |
| List<Uri> summaryUris, Map<Uri, String> moduleImportForSummary) { |
| if (moduleItems.isNotEmpty) { |
| throw StateError('Can only call emitModule once.'); |
| } |
| _component = component; |
| |
| for (var i = 0; i < summaries.length; i++) { |
| var summary = summaries[i]; |
| var moduleImport = moduleImportForSummary[summaryUris[i]]; |
| for (var l in summary.libraries) { |
| assert(!_importToSummary.containsKey(l)); |
| _importToSummary[l] = summary; |
| _summaryToModule[summary] = moduleImport; |
| } |
| } |
| |
| var libraries = component.libraries.where((l) => !l.isExternal); |
| |
| // Initialize our library variables. |
| var items = startModule(libraries); |
| _nullableInference.allowNotNullDeclarations = isBuildingSdk; |
| _typeTable = TypeTable(runtimeModule); |
| |
| // Collect all class/type Element -> Node mappings |
| // in case we need to forward declare any classes. |
| _pendingClasses = HashSet.identity(); |
| for (var l in libraries) { |
| _pendingClasses.addAll(l.classes); |
| } |
| |
| // TODO(markzipan): Don't emit this when compiling the SDK. |
| moduleItems |
| .add(js.statement("const # = Object.create(null);", [_constTable])); |
| _constTableInsertionIndex = moduleItems.length; |
| |
| // Add implicit dart:core dependency so it is first. |
| emitLibraryName(_coreTypes.coreLibrary); |
| |
| // Visit each library and emit its code. |
| // |
| // NOTE: clases 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. |
| libraries.forEach(_emitLibrary); |
| |
| // This can cause problems if it's ever true during the SDK build, as it's |
| // emitted before dart.defineLazy. |
| if (_constLazyAccessors.isNotEmpty) { |
| var constTableBody = runtimeStatement( |
| 'defineLazy(#, { # })', [_constTable, _constLazyAccessors]); |
| moduleItems.insert(_constTableInsertionIndex, constTableBody); |
| _constLazyAccessors.clear(); |
| } |
| |
| moduleItems.addAll(afterClassDefItems); |
| afterClassDefItems.clear(); |
| |
| // Visit directives (for exports) |
| libraries.forEach(_emitExports); |
| |
| // Declare imports and extension symbols |
| emitImportsAndExtensionSymbols(items); |
| |
| // Discharge the type table cache variables and |
| // hoisted definitions. |
| items.addAll(_typeTable.discharge()); |
| |
| return finishModule(items, _options.moduleName); |
| } |
| |
| @override |
| String jsLibraryName(Library library) { |
| var uri = library.importUri; |
| if (uri.scheme == 'dart') { |
| return isSdkInternalRuntime(library) ? 'dart' : uri.path; |
| } |
| return pathToJSIdentifier(p.withoutExtension(uri.pathSegments.last)); |
| } |
| |
| @override |
| String jsLibraryAlias(Library library) { |
| var uri = library.importUri.normalizePath(); |
| if (uri.scheme == 'dart') return null; |
| |
| Iterable<String> segments; |
| if (uri.scheme == 'package') { |
| // Strip the package name. |
| segments = uri.pathSegments.skip(1); |
| } else { |
| segments = uri.pathSegments; |
| } |
| |
| var qualifiedPath = |
| pathToJSIdentifier(p.withoutExtension(segments.join('/'))); |
| return qualifiedPath == jsLibraryName(library) ? null : qualifiedPath; |
| } |
| |
| @override |
| String jsLibraryDebuggerName(Library library) => '${library.importUri}'; |
| |
| @override |
| Iterable<String> jsPartDebuggerNames(Library library) => |
| library.parts.map((part) => part.partUri); |
| |
| @override |
| bool isSdkInternalRuntime(Library l) { |
| var uri = l.importUri; |
| return uri.scheme == 'dart' && uri.path == '_runtime'; |
| } |
| |
| @override |
| String libraryToModule(Library library) { |
| if (library.importUri.scheme == 'dart') { |
| // TODO(jmesserly): we need to split out HTML. |
| return js_ast.dartSdkModule; |
| } |
| var summary = _importToSummary[library]; |
| var moduleName = _summaryToModule[summary]; |
| if (moduleName == null) { |
| throw StateError('Could not find module name for library "$library" ' |
| 'from component "$summary".'); |
| } |
| return moduleName; |
| } |
| |
| void _emitLibrary(Library library) { |
| // NOTE: this method isn't the right place to initialize per-library state. |
| // Classes can be visited out of order, so this is only to catch things that |
| // haven't been emitted yet. |
| // |
| // See _emitClass. |
| assert(_currentLibrary == null); |
| _currentLibrary = library; |
| |
| if (isSdkInternalRuntime(library)) { |
| // `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); |
| } |
| |
| _currentLibrary = null; |
| } |
| |
| void _emitExports(Library library) { |
| assert(_currentLibrary == null); |
| _currentLibrary = library; |
| |
| library.additionalExports.forEach(_emitExport); |
| |
| _currentLibrary = null; |
| } |
| |
| 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. |
| // TODO(jacobr): add a library level annotation indicating that all |
| // contents of a library need to be exposed to JS. |
| // https://github.com/dart-lang/sdk/issues/26368 |
| |
| var node = export.node; |
| if (node is Procedure && node.name.name == 'main') { |
| // Don't allow redefining names from this library. |
| var name = _emitTopLevelName(export.node); |
| moduleItems.add(js.statement( |
| '#.# = #;', [emitLibraryName(library), name.selector, name])); |
| } |
| } |
| |
| /// Called to emit class declarations. |
| /// |
| /// During the course of emitting one item, we may emit another. For example |
| /// |
| /// class D extends B { C m() { ... } } |
| /// |
| /// Because D depends on B, we'll emit B first if needed. However C is not |
| /// used by top-level JavaScript code, so we can ignore that dependency. |
| void _emitClass(Class c) { |
| if (!_pendingClasses.remove(c)) return; |
| |
| var savedClass = _currentClass; |
| var savedLibrary = _currentLibrary; |
| var savedUri = _currentUri; |
| _currentClass = c; |
| _types.thisType = c.thisType; |
| _currentLibrary = c.enclosingLibrary; |
| _currentUri = c.fileUri; |
| |
| moduleItems.add(_emitClassDeclaration(c)); |
| |
| // The const table depends on dart.defineLazy, so emit it after the SDK. |
| if (isSdkInternalRuntime(_currentLibrary)) { |
| _constTableInsertionIndex = moduleItems.length; |
| } |
| |
| _currentClass = savedClass; |
| _types.thisType = savedClass?.thisType; |
| _currentLibrary = savedLibrary; |
| _currentUri = savedUri; |
| } |
| |
| /// To emit top-level classes, we sometimes need to reorder them. |
| /// |
| /// This function takes care of that, and also detects cases where reordering |
| /// failed, and we need to resort to lazy loading, by marking the element as |
| /// lazy. All elements need to be aware of this possibility and generate code |
| /// accordingly. |
| /// |
| /// If we are not emitting top-level code, this does nothing, because all |
| /// declarations are assumed to be available before we start execution. |
| /// See [startTopLevel]. |
| void _declareBeforeUse(Class c) { |
| if (c != null && _emittingClassExtends) { |
| _emitClass(c); |
| } |
| } |
| |
| static js_ast.Identifier _emitIdentifier(String name) => |
| js_ast.Identifier(js_ast.toJSIdentifier(name)); |
| |
| static js_ast.TemporaryId _emitTemporaryId(String name) => |
| js_ast.TemporaryId(js_ast.toJSIdentifier(name)); |
| |
| js_ast.Statement _emitClassDeclaration(Class c) { |
| // Mixins are unrolled in _defineClass. |
| if (c.isAnonymousMixin) return null; |
| |
| // If this class is annotated with `@JS`, then there is nothing to emit. |
| if (findAnnotation(c, isPublicJSAnnotation) != null) return null; |
| |
| // Generic classes will be defined inside a function that closes over the |
| // type parameter. So we can use their local variable name directly. |
| // |
| // TODO(jmesserly): the special case for JSArray is to support its special |
| // type-tagging factory constructors. Those will go away once we fix: |
| // https://github.com/dart-lang/sdk/issues/31003 |
| var className = c.typeParameters.isNotEmpty |
| ? (c == _jsArrayClass |
| ? _emitIdentifier(c.name) |
| : _emitTemporaryId(getLocalClassName(c))) |
| : _emitTopLevelName(c); |
| |
| var savedClassProperties = _classProperties; |
| _classProperties = |
| ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, c); |
| |
| var jsCtors = _defineConstructors(c, className); |
| var jsMethods = _emitClassMethods(c); |
| |
| var body = <js_ast.Statement>[]; |
| _emitSuperHelperSymbols(body); |
| var deferredSupertypes = <js_ast.Statement>[]; |
| |
| // Emit the class, e.g. `core.Object = class Object { ... }` |
| _defineClass(c, className, jsMethods, body, deferredSupertypes); |
| body.addAll(jsCtors); |
| |
| // Emit things that come after the ES6 `class ... { ... }`. |
| var jsPeerNames = _extensionTypes.getNativePeers(c); |
| if (jsPeerNames.length == 1 && c.typeParameters.isNotEmpty) { |
| // Special handling for JSArray<E> |
| body.add(runtimeStatement('setExtensionBaseClass(#, #.global.#)', |
| [className, runtimeModule, jsPeerNames[0]])); |
| } |
| |
| var finishGenericTypeTest = _emitClassTypeTests(c, className, body); |
| |
| _emitVirtualFieldSymbols(c, body); |
| _emitClassSignature(c, className, body); |
| _initExtensionSymbols(c); |
| if (!c.isMixinDeclaration) { |
| _defineExtensionMembers(className, body); |
| } |
| _emitClassMetadata(c.annotations, className, body); |
| |
| var classDef = js_ast.Statement.from(body); |
| var typeFormals = c.typeParameters; |
| if (typeFormals.isNotEmpty) { |
| classDef = _defineClassTypeArguments( |
| c, typeFormals, classDef, className, deferredSupertypes); |
| } else { |
| afterClassDefItems.addAll(deferredSupertypes); |
| } |
| |
| body = [classDef]; |
| _emitStaticFields(c, body); |
| if (finishGenericTypeTest != null) body.add(finishGenericTypeTest); |
| for (var peer in jsPeerNames) { |
| _registerExtensionType(c, peer, body); |
| } |
| |
| _classProperties = savedClassProperties; |
| return js_ast.Statement.from(body); |
| } |
| |
| /// Wraps a possibly generic class in its type arguments. |
| js_ast.Statement _defineClassTypeArguments( |
| NamedNode c, List<TypeParameter> formals, js_ast.Statement body, |
| [js_ast.Expression className, List<js_ast.Statement> deferredBaseClass]) { |
| assert(formals.isNotEmpty); |
| var name = getTopLevelName(c); |
| var jsFormals = _emitTypeFormals(formals); |
| var typeConstructor = js.call('(#) => { #; #; return #; }', [ |
| jsFormals, |
| _typeTable.discharge(formals), |
| body, |
| className ?? _emitIdentifier(name) |
| ]); |
| |
| var genericArgs = [ |
| typeConstructor, |
| if (deferredBaseClass != null && deferredBaseClass.isNotEmpty) |
| js.call('(#) => { #; }', [jsFormals, deferredBaseClass]), |
| ]; |
| |
| var genericCall = runtimeCall('generic(#)', [genericArgs]); |
| |
| var genericName = _emitTopLevelNameNoInterop(c, suffix: '\$'); |
| return js.statement('{ # = #; # = #(); }', |
| [genericName, genericCall, _emitTopLevelName(c), genericName]); |
| } |
| |
| js_ast.Statement _emitClassStatement(Class c, js_ast.Expression className, |
| js_ast.Expression heritage, List<js_ast.Method> methods) { |
| if (c.typeParameters.isNotEmpty) { |
| return js_ast.ClassExpression( |
| className as js_ast.Identifier, heritage, methods) |
| .toStatement(); |
| } |
| var classExpr = js_ast.ClassExpression( |
| _emitTemporaryId(getLocalClassName(c)), heritage, 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>[]; |
| _defineExtensionMembers(classId, extensionInit); |
| if (extensionInit.isNotEmpty) { |
| extensionInit.insert(0, mixinMemberClass.toStatement()); |
| extensionInit.add(classId.toReturn()); |
| arrowFnBody = js_ast.Block(extensionInit); |
| } |
| |
| body.add(js.statement('#[#.mixinOn] = #', [ |
| className, |
| runtimeModule, |
| 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> deferredSupertypes) { |
| if (c == _coreTypes.objectClass) { |
| body.add(_emitClassStatement(c, className, null, methods)); |
| return; |
| } |
| |
| js_ast.Expression emitDeferredType(DartType t) { |
| if (t is InterfaceType && t.typeArguments.isNotEmpty) { |
| _declareBeforeUse(t.classNode); |
| return _emitGenericClassType(t, t.typeArguments.map(emitDeferredType)); |
| } |
| return _emitType(t); |
| } |
| |
| bool shouldDefer(InterfaceType t) { |
| var visited = Set<DartType>(); |
| bool defer(DartType t) { |
| if (t is InterfaceType) { |
| var tc = t.classNode; |
| if (c == tc) return true; |
| if (tc == _coreTypes.objectClass || !visited.add(t)) return false; |
| if (t.typeArguments.any(defer)) return true; |
| var mixin = tc.mixedInType; |
| return mixin != null && defer(mixin.asInterfaceType) || |
| defer(tc.supertype.asInterfaceType); |
| } |
| if (t is TypedefType) { |
| return t.typeArguments.any(defer); |
| } |
| if (t is FunctionType) { |
| return defer(t.returnType) || |
| t.positionalParameters.any(defer) || |
| t.namedParameters.any((np) => defer(np.type)) || |
| t.typeParameters.any((tp) => defer(tp.bound)); |
| } |
| return false; |
| } |
| |
| return defer(t); |
| } |
| |
| 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) ?? visitInterfaceType(t); |
| } |
| |
| getBaseClass(int count) { |
| var base = emitDeferredType(c.thisType); |
| while (--count >= 0) { |
| base = js.call('#.__proto__', [base]); |
| } |
| return base; |
| } |
| |
| // Find the real (user declared) superclass and the list of mixins. |
| // We'll use this to unroll the intermediate classes. |
| // |
| // TODO(jmesserly): consider using Kernel's mixin unrolling. |
| var mixinClasses = <Class>[]; |
| var superclass = getSuperclassAndMixins(c, mixinClasses); |
| var supertype = identical(c.superclass, superclass) |
| ? c.supertype.asInterfaceType |
| : _hierarchy.getClassAsInstanceOf(c, superclass).asInterfaceType; |
| mixinClasses = mixinClasses.reversed.toList(); |
| var mixins = mixinClasses |
| .map((m) => _hierarchy.getClassAsInstanceOf(c, m).asInterfaceType) |
| .toList(); |
| |
| var hasUnnamedSuper = _hasUnnamedInheritedConstructor(superclass); |
| |
| void emitMixinConstructors( |
| js_ast.Expression className, InterfaceType mixin) { |
| js_ast.Statement mixinCtor; |
| if (_hasUnnamedConstructor(mixin.classNode)) { |
| mixinCtor = js.statement('#.#.call(this);', [ |
| emitClassRef(mixin), |
| _usesMixinNew(mixin.classNode) |
| ? runtimeCall('mixinNew') |
| : _constructorName('') |
| ]); |
| } |
| |
| for (var ctor in superclass.constructors) { |
| var savedUri = _currentUri; |
| _currentUri = ctor.enclosingClass.fileUri; |
| var jsParams = _emitParameters(ctor.function); |
| _currentUri = savedUri; |
| var name = ctor.name.name; |
| var ctorBody = [ |
| if (mixinCtor != null) mixinCtor, |
| if (name != '' || hasUnnamedSuper) |
| _emitSuperConstructorCall(className, name, jsParams), |
| ]; |
| body.add(_addConstructorToClass( |
| c, className, name, js_ast.Fun(jsParams, js_ast.Block(ctorBody)))); |
| } |
| } |
| |
| var savedTopLevelClass = _classEmittingExtends; |
| _classEmittingExtends = c; |
| |
| // Unroll mixins. |
| if (shouldDefer(supertype)) { |
| deferredSupertypes.add(runtimeStatement('setBaseClass(#, #)', [ |
| getBaseClass(isMixinAliasClass(c) ? 0 : mixins.length), |
| emitDeferredType(supertype), |
| ])); |
| supertype = _coreTypes.legacyRawType(supertype.classNode); |
| } |
| var baseClass = emitClassRef(supertype); |
| |
| if (isMixinAliasClass(c)) { |
| // Given `class C = Object with M [implements I1, I2 ...];` |
| // The resulting class C should work as a mixin. |
| // |
| // TODO(jmesserly): is there any way to merge this with the other mixin |
| // code paths, or will these always need special handling? |
| body.add(_emitClassStatement(c, className, baseClass, [])); |
| |
| var m = c.mixedInType.asInterfaceType; |
| bool deferMixin = shouldDefer(m); |
| var mixinBody = deferMixin ? deferredSupertypes : body; |
| var mixinClass = deferMixin ? emitDeferredType(m) : emitClassRef(m); |
| var classExpr = deferMixin ? getBaseClass(0) : className; |
| |
| mixinBody |
| .add(runtimeStatement('applyMixin(#, #)', [classExpr, mixinClass])); |
| |
| if (methods.isNotEmpty) { |
| // However we may need to add some methods to this class that call |
| // `super` such as covariance checks. |
| // |
| // We do this with the following pattern: |
| // |
| // applyMixin(C, class C$ extends M { <methods> }); |
| mixinBody.add(runtimeStatement('applyMixin(#, #)', [ |
| classExpr, |
| js_ast.ClassExpression( |
| _emitTemporaryId(getLocalClassName(c)), mixinClass, methods) |
| ])); |
| } |
| |
| emitMixinConstructors(className, m); |
| |
| _classEmittingExtends = savedTopLevelClass; |
| return; |
| } |
| |
| // 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 (int i = 0; i < mixins.length; i++) { |
| var m = mixins[i]; |
| var mixinName = |
| getLocalClassName(superclass) + '_' + getLocalClassName(m.classNode); |
| var mixinId = _emitTemporaryId(mixinName + '\$'); |
| // Bind the mixin class to a name to workaround a V8 bug with es6 classes |
| // and anonymous function names. |
| // TODO(leafp:) Eliminate this once the bug is fixed: |
| // https://bugs.chromium.org/p/v8/issues/detail?id=7069 |
| body.add(js.statement("const # = #", [ |
| mixinId, |
| js_ast.ClassExpression(_emitTemporaryId(mixinName), baseClass, []) |
| ])); |
| |
| emitMixinConstructors(mixinId, m); |
| hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(m.classNode); |
| |
| if (shouldDefer(m)) { |
| deferredSupertypes.add(runtimeStatement('applyMixin(#, #)', |
| [getBaseClass(mixins.length - i), emitDeferredType(m)])); |
| } else { |
| body.add( |
| runtimeStatement('applyMixin(#, #)', [mixinId, emitClassRef(m)])); |
| } |
| |
| baseClass = mixinId; |
| } |
| |
| if (c.isMixinDeclaration) { |
| _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 || isMixinAliasClass(c)) { |
| // We already handled this when we defined the class. |
| return body; |
| } |
| |
| addConstructor(String 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; |
| addConstructor(ctor.name.name, _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; |
| } |
| |
| js_ast.Statement _emitClassTypeTests( |
| Class c, js_ast.Expression className, List<js_ast.Statement> body) { |
| js_ast.Expression getInterfaceSymbol(Class interface) { |
| var library = interface.enclosingLibrary; |
| if (library == _coreTypes.coreLibrary || |
| library == _coreTypes.asyncLibrary) { |
| switch (interface.name) { |
| case 'List': |
| case 'Map': |
| case 'Iterable': |
| case 'Future': |
| case 'Stream': |
| case 'StreamSubscription': |
| return runtimeCall('is' + interface.name); |
| } |
| } |
| return null; |
| } |
| |
| void markSubtypeOf(js_ast.Expression testSymbol) { |
| body.add(js.statement('#.prototype[#] = true', [className, testSymbol])); |
| } |
| |
| for (var iface in c.implementedTypes) { |
| var prop = getInterfaceSymbol(iface.classNode); |
| if (prop != null) markSubtypeOf(prop); |
| } |
| |
| // TODO(jmesserly): share these hand coded type checks with the old back |
| // end, perhaps by factoring them into a common file, or move them to be |
| // static methdos in the SDK. (Or wait until we delete the old back end.) |
| if (c.enclosingLibrary == _coreTypes.coreLibrary) { |
| if (c == _coreTypes.objectClass) { |
| // Everything is an Object. |
| body.add(js.statement( |
| '#.is = function is_Object(o) { return true; }', [className])); |
| body.add(js.statement( |
| '#.as = function as_Object(o) { return o; }', [className])); |
| body.add(js.statement( |
| '#._check = function check_Object(o) { return o; }', [className])); |
| return null; |
| } |
| if (c == _coreTypes.stringClass) { |
| body.add(js.statement( |
| '#.is = function is_String(o) { return typeof o == "string"; }', |
| className)); |
| body.add(js.statement( |
| '#.as = function as_String(o) {' |
| ' if (typeof o == "string" || o == null) return o;' |
| ' return #.as(o, #, false);' |
| '}', |
| [className, runtimeModule, className])); |
| body.add(js.statement( |
| '#._check = function check_String(o) {' |
| ' if (typeof o == "string" || o == null) return o;' |
| ' return #.as(o, #, true);' |
| '}', |
| [className, runtimeModule, className])); |
| return null; |
| } |
| if (c == _coreTypes.functionClass) { |
| body.add(js.statement( |
| '#.is = function is_Function(o) { return typeof o == "function"; }', |
| className)); |
| body.add(js.statement( |
| '#.as = function as_Function(o) {' |
| ' if (typeof o == "function" || o == null) return o;' |
| ' return #.as(o, #, false);' |
| '}', |
| [className, runtimeModule, className])); |
| body.add(js.statement( |
| '#._check = function check_String(o) {' |
| ' if (typeof o == "function" || o == null) return o;' |
| ' return #.as(o, #, true);' |
| '}', |
| [className, runtimeModule, className])); |
| return null; |
| } |
| if (c == _coreTypes.intClass) { |
| body.add(js.statement( |
| '#.is = function is_int(o) {' |
| ' return typeof o == "number" && Math.floor(o) == o;' |
| '}', |
| className)); |
| body.add(js.statement( |
| '#.as = function as_int(o) {' |
| ' if ((typeof o == "number" && Math.floor(o) == o) || o == null)' |
| ' return o;' |
| ' return #.as(o, #, false);' |
| '}', |
| [className, runtimeModule, className])); |
| body.add(js.statement( |
| '#._check = function check_int(o) {' |
| ' if ((typeof o == "number" && Math.floor(o) == o) || o == null)' |
| ' return o;' |
| ' return #.as(o, #, true);' |
| '}', |
| [className, runtimeModule, className])); |
| return null; |
| } |
| if (c == _coreTypes.nullClass) { |
| body.add(js.statement( |
| '#.is = function is_Null(o) { return o == null; }', className)); |
| body.add(js.statement( |
| '#.as = function as_Null(o) {' |
| ' if (o == null) return o;' |
| ' return #.as(o, #, false);' |
| '}', |
| [className, runtimeModule, className])); |
| body.add(js.statement( |
| '#._check = function check_Null(o) {' |
| ' if (o == null) return o;' |
| ' return #.as(o, #, true);' |
| '}', |
| [className, runtimeModule, className])); |
| return null; |
| } |
| if (c == _coreTypes.numClass || c == _coreTypes.doubleClass) { |
| body.add(js.statement( |
| '#.is = function is_num(o) { return typeof o == "number"; }', |
| className)); |
| body.add(js.statement( |
| '#.as = function as_num(o) {' |
| ' if (typeof o == "number" || o == null) return o;' |
| ' return #.as(o, #, false);' |
| '}', |
| [className, runtimeModule, className])); |
| body.add(js.statement( |
| '#._check = function check_num(o) {' |
| ' if (typeof o == "number" || o == null) return o;' |
| ' return #.as(o, #, true);' |
| '}', |
| [className, runtimeModule, className])); |
| return null; |
| } |
| if (c == _coreTypes.boolClass) { |
| body.add(js.statement( |
| '#.is = function is_bool(o) { return o === true || o === false; }', |
| className)); |
| body.add(js.statement( |
| '#.as = function as_bool(o) {' |
| ' if (o === true || o === false || o == null) return o;' |
| ' return #.as(o, #, false);' |
| '}', |
| [className, runtimeModule, className])); |
| body.add(js.statement( |
| '#._check = function check_bool(o) {' |
| ' if (o === true || o === false || o == null) return o;' |
| ' return #.as(o, #, true);' |
| '}', |
| [className, runtimeModule, className])); |
| return null; |
| } |
| } |
| if (c.enclosingLibrary == _coreTypes.asyncLibrary) { |
| if (c == _coreTypes.futureOrClass) { |
| var typeParam = TypeParameterType(c.typeParameters[0]); |
| var typeT = visitTypeParameterType(typeParam); |
| var futureOfT = visitInterfaceType( |
| InterfaceType(_coreTypes.futureClass, [typeParam])); |
| body.add(js.statement(''' |
| #.is = function is_FutureOr(o) { |
| return #.is(o) || #.is(o); |
| } |
| ''', [className, typeT, futureOfT])); |
| // TODO(jmesserly): remove the fallback to `dart.as`. It's only for the |
| // _ignoreTypeFailure logic. |
| body.add(js.statement(''' |
| #.as = function as_FutureOr(o) { |
| if (o == null || #.is(o) || #.is(o)) return o; |
| return #.as(o, this, false); |
| } |
| ''', [className, typeT, futureOfT, runtimeModule])); |
| body.add(js.statement(''' |
| #._check = function check_FutureOr(o) { |
| if (o == null || #.is(o) || #.is(o)) return o; |
| return #.as(o, this, true); |
| } |
| ''', [className, typeT, futureOfT, runtimeModule])); |
| return null; |
| } |
| } |
| |
| body.add(runtimeStatement('addTypeTests(#)', [className])); |
| |
| if (c.typeParameters.isEmpty) return null; |
| |
| // For generics, testing against the default instantiation is common, |
| // so optimize that. |
| var isClassSymbol = getInterfaceSymbol(c); |
| if (isClassSymbol == null) { |
| // TODO(jmesserly): we could export these symbols, if we want to mark |
| // implemented interfaces for user-defined classes. |
| var id = _emitTemporaryId("_is_${getLocalClassName(c)}_default"); |
| moduleItems.add( |
| js.statement('const # = Symbol(#);', [id, js.string(id.name, "'")])); |
| isClassSymbol = id; |
| } |
| // Marking every generic type instantiation as a subtype of its default |
| // instantiation. |
| markSubtypeOf(isClassSymbol); |
| |
| // Define the type tests on the default instantiation to check for that |
| // marker. |
| var defaultInst = _emitTopLevelName(c); |
| |
| // Return this `addTypeTests` call so we can emit it outside of the generic |
| // type parameter scope. |
| return runtimeStatement('addTypeTests(#, #)', [defaultInst, isClassSymbol]); |
| } |
| |
| 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 static fields for a class, and initialize them eagerly if possible, |
| /// otherwise define them as lazy properties. |
| void _emitStaticFields(Class c, List<js_ast.Statement> body) { |
| var fields = c.fields |
| .where((f) => f.isStatic && getRedirectingFactories(f) == null) |
| .toList(); |
| if (c.isEnum) { |
| // We know enum fields can be safely emitted as const fields, as long |
| // as the `values` field is emitted last. |
| var classRef = _emitTopLevelName(c); |
| var valueField = fields.firstWhere((f) => f.name.name == 'values'); |
| fields.remove(valueField); |
| fields.add(valueField); |
| for (var f in fields) { |
| assert(f.isConst); |
| body.add(defineValueOnClass( |
| c, |
| classRef, |
| _emitStaticMemberName(f.name.name), |
| _visitInitializer(f.initializer, f.annotations)) |
| .toStatement()); |
| } |
| } else if (fields.isNotEmpty) { |
| body.add(_emitLazyFields(_emitTopLevelName(c), fields, |
| (n) => _emitStaticMemberName(n.name.name))); |
| } |
| } |
| |
| void _emitClassMetadata(List<Expression> metadata, |
| js_ast.Expression className, List<js_ast.Statement> body) { |
| // Metadata |
| if (_options.emitMetadata && metadata.isNotEmpty) { |
| body.add(js.statement('#[#.metadata] = #;', [ |
| className, |
| runtimeModule, |
| _arrowFunctionWithLetScope(() => js_ast.ArrayInitializer( |
| metadata.map(_instantiateAnnotation).toList())) |
| ])); |
| } |
| } |
| |
| /// 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(js.statement('#.#(#, #);', [ |
| runtimeModule, |
| 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 savedClass = _classEmittingSignatures; |
| _classEmittingSignatures = c; |
| |
| var interfaces = c.implementedTypes.toList() |
| ..addAll(c.superclassConstraints()); |
| if (interfaces.isNotEmpty) { |
| body.add(js.statement('#[#.implements] = () => [#];', [ |
| className, |
| runtimeModule, |
| interfaces.map((i) => _emitType(i.asInterfaceType)) |
| ])); |
| } |
| |
| void emitSignature(String name, List<js_ast.Property> elements) { |
| if (elements.isEmpty) return; |
| |
| if (!name.startsWith('Static')) { |
| var proto = c == _coreTypes.objectClass |
| ? js.call('Object.create(null)') |
| : runtimeCall('get${name}s(#.__proto__)', [className]); |
| elements.insert(0, js_ast.Property(propertyName('__proto__'), proto)); |
| } |
| body.add(runtimeStatement('set${name}Signature(#, () => #)', [ |
| className, |
| js_ast.ObjectInitializer(elements, multiline: elements.length > 1) |
| ])); |
| } |
| |
| var extMethods = _classProperties.extensionMethods; |
| var extAccessors = _classProperties.extensionAccessors; |
| var staticMethods = <js_ast.Property>[]; |
| var instanceMethods = <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) { |
| 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/methods cannot be called with dynamic dispatch, |
| // nor can they be torn off. |
| if (!_options.emitMetadata && member.isStatic) continue; |
| |
| var name = member.name.name; |
| var reifiedType = _getMemberRuntimeType(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 != _getMemberRuntimeType(memberOverride, c); |
| |
| if (needsSignature) { |
| js_ast.Expression type; |
| if (member.isAccessor) { |
| type = _emitAnnotatedResult( |
| _emitType(member.isGetter |
| ? reifiedType.returnType |
| : reifiedType.positionalParameters[0]), |
| member.annotations, |
| member); |
| } else { |
| type = _emitAnnotatedFunctionType(reifiedType, member); |
| } |
| var property = js_ast.Property(_declareMemberName(member), type); |
| var signatures = getSignatureList(member); |
| signatures.add(property); |
| if (!member.isStatic && |
| (extMethods.contains(name) || extAccessors.contains(name))) { |
| signatures.add(js_ast.Property( |
| _declareMemberName(member, useExtension: true), type)); |
| } |
| } |
| } |
| |
| emitSignature('Method', instanceMethods); |
| emitSignature('StaticMethod', staticMethods); |
| emitSignature('Getter', instanceGetters); |
| emitSignature('Setter', instanceSetters); |
| emitSignature('StaticGetter', staticGetters); |
| emitSignature('StaticSetter', staticSetters); |
| body.add(runtimeStatement('setLibraryUri(#, #)', [ |
| className, |
| js.escapedString(jsLibraryDebuggerName(c.enclosingLibrary)) |
| ])); |
| |
| var instanceFields = <js_ast.Property>[]; |
| var staticFields = <js_ast.Property>[]; |
| |
| var classFields = c.fields.toList(); |
| for (var field in classFields) { |
| // Only instance fields need to be saved for dynamic dispatch. |
| var isStatic = field.isStatic; |
| if (!_options.emitMetadata && isStatic) continue; |
| |
| var memberName = _declareMemberName(field); |
| var fieldSig = _emitFieldSignature(field, c); |
| (isStatic ? staticFields : instanceFields) |
| .add(js_ast.Property(memberName, fieldSig)); |
| } |
| emitSignature('Field', instanceFields); |
| emitSignature('StaticField', staticFields); |
| |
| if (_options.emitMetadata) { |
| var constructors = <js_ast.Property>[]; |
| var allConstructors = [ |
| ...c.constructors, |
| ...c.procedures.where((p) => p.isFactory), |
| ]; |
| for (var ctor in allConstructors) { |
| var memberName = _constructorName(ctor.name.name); |
| var type = _emitAnnotatedFunctionType( |
| ctor.function.thisFunctionType.withoutTypeParameters, ctor); |
| constructors.add(js_ast.Property(memberName, type)); |
| } |
| emitSignature('Constructor', constructors); |
| } |
| |
| // Add static property dart._runtimeType to Object. |
| // All other Dart classes will (statically) inherit this property. |
| if (c == _coreTypes.objectClass) { |
| body.add(runtimeStatement('lazyFn(#, () => #.#)', |
| [className, emitLibraryName(_coreTypes.coreLibrary), 'Type'])); |
| } |
| |
| _classEmittingSignatures = savedClass; |
| } |
| |
| js_ast.Expression _emitFieldSignature(Field field, Class fromClass) { |
| var type = _getTypeFromClass(field.type, field.enclosingClass, fromClass); |
| var args = [_emitType(type)]; |
| var annotations = field.annotations; |
| if (_options.emitMetadata && |
| annotations != null && |
| annotations.isNotEmpty) { |
| var savedUri = _currentUri; |
| _currentUri = field.enclosingClass.fileUri; |
| args.add(js_ast.ArrayInitializer( |
| annotations.map(_instantiateAnnotation).toList())); |
| _currentUri = savedUri; |
| } |
| return runtimeCall( |
| field.isFinal ? 'finalFieldType(#)' : 'fieldType(#)', [args]); |
| } |
| |
| DartType _getMemberRuntimeType(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)) { |
| result = f.thisFunctionType; |
| } else { |
| reifyParameter(VariableDeclaration p) => |
| isCovariantParameter(p) ? _coreTypes.objectClass.thisType : p.type; |
| reifyNamedParameter(VariableDeclaration p) => |
| NamedType(p.name, reifyParameter(p)); |
| |
| // TODO(jmesserly): do covariant type parameter bounds also need to be |
| // reified as `Object`? |
| result = FunctionType( |
| f.positionalParameters.map(reifyParameter).toList(), f.returnType, |
| namedParameters: f.namedParameters.map(reifyNamedParameter).toList() |
| ..sort(), |
| typeParameters: f.thisFunctionType.typeParameters, |
| requiredParameterCount: f.requiredParameterCount); |
| } |
| return _getTypeFromClass(result, member.enclosingClass, fromClass) |
| as FunctionType; |
| } |
| |
| DartType _getTypeFromClass(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 ?? savedUri; |
| var params = _emitParameters(node.function); |
| var body = _withCurrentFunction( |
| node.function, |
| () => _superDisallowed( |
| () => _emitConstructorBody(node, fields, className))); |
| |
| var end = _nodeEnd(node.fileEndOffset); |
| _currentUri = savedUri; |
| end ??= _nodeEnd(node.enclosingClass.fileEndOffset); |
| |
| return js_ast.Fun(params, js_ast.Block(body))..sourceInformation = end; |
| } |
| |
| 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); |
| |
| // Redirecting constructors: these are not allowed to have initializers, |
| // and the redirecting ctor invocation runs before field initializers. |
| var redirectCall = node.initializers |
| .firstWhere((i) => i is RedirectingInitializer, orElse: () => null) |
| as RedirectingInitializer; |
| |
| if (redirectCall != null) { |
| body.add(_emitRedirectingConstructor(redirectCall, 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.firstWhere((i) => i is SuperInitializer, |
| orElse: () => null) as SuperInitializer; |
| var jsSuper = _emitSuperConstructorCallIfNeeded(cls, className, superCall); |
| if (jsSuper != null) { |
| body.add(jsSuper..sourceInformation = _nodeStart(superCall)); |
| } |
| |
| body.add(_emitFunctionScopedBody(fn)); |
| return body; |
| } |
| |
| js_ast.Expression _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( |
| RedirectingInitializer node, js_ast.Expression className) { |
| var ctor = node.target; |
| // We can't dispatch to the constructor with `this.new` as that might hit a |
| // derived class constructor with the same name. |
| return js.statement('#.#.call(this, #);', [ |
| className, |
| _constructorName(ctor.name.name), |
| _emitArgumentList(node.arguments, types: false) |
| ]); |
| } |
| |
| 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; |
| args = _emitArgumentList(superInit.arguments, types: false); |
| } |
| // 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.name == '' && !_hasUnnamedSuperConstructor(c)) { |
| return null; |
| } |
| return _emitSuperConstructorCall(className, ctor.name.name, args); |
| } |
| |
| js_ast.Statement _emitSuperConstructorCall( |
| js_ast.Expression className, String name, List<js_ast.Expression> args) { |
| return js.statement('#.__proto__.#.call(this, #);', |
| [className, _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. |
| Set<Field> ctorFields; |
| if (ctor != null) { |
| ctorFields = ctor.initializers |
| .map((c) => c is FieldInitializer ? c.field : null) |
| .toSet() |
| ..remove(null); |
| } |
| |
| var body = <js_ast.Statement>[]; |
| emitFieldInit(Field f, Expression initializer, TreeNode hoverInfo) { |
| var access = _classProperties.virtualFields[f] ?? _declareMemberName(f); |
| 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; |
| } |
| emitFieldInit(f, init, 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) { |
| if (expr == null) return null; |
| 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, |
| String name, js_ast.Expression jsCtor) { |
| jsCtor = defineValueOnClass(c, className, _constructorName(name), jsCtor); |
| return js.statement('#.prototype = #.prototype;', [jsCtor, className]); |
| } |
| |
| @override |
| 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 name = Name(memberName, c.enclosingLibrary); |
| while (true) { |
| c = c.superclass; |
| if (c == null) return false; |
| for (var m in c.members) { |
| if (m.name == name && |
| (m is Procedure && m.isStatic || m is Field && m.isStatic)) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| List<js_ast.Method> _emitClassMethods(Class c) { |
| var virtualFields = _classProperties.virtualFields; |
| |
| var jsMethods = <js_ast.Method>[]; |
| bool hasJsPeer = _extensionTypes.isNativeClass(c); |
| bool 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 " + #.typeName(#.getReifiedType(this)) + |
| ".new(...)` to create a Dart object"); |
| }''', [runtimeModule, runtimeModule]))); |
| } 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 []; }'))); |
| } |
| |
| Set<Member> redirectingFactories; |
| for (var m in c.fields) { |
| if (m.isStatic) { |
| redirectingFactories ??= getRedirectingFactories(m)?.toSet(); |
| } else if (_extensionTypes.isNativeClass(c)) { |
| jsMethods.addAll(_emitNativeFieldAccessors(m)); |
| } else if (virtualFields.containsKey(m)) { |
| jsMethods.addAll(_emitVirtualFieldAccessor(m)); |
| } |
| } |
| |
| var getters = Map<String, Procedure>(); |
| var setters = Map<String, Procedure>(); |
| for (var m in c.procedures) { |
| if (m.isAbstract) continue; |
| if (m.isGetter) { |
| getters[m.name.name] = m; |
| } else if (m.isSetter) { |
| setters[m.name.name] = m; |
| } |
| } |
| |
| var savedUri = _currentUri; |
| for (var m in c.procedures) { |
| // 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 ?? savedUri); |
| if (_isForwardingStub(m)) { |
| // TODO(jmesserly): is there any other kind of forwarding stub? |
| jsMethods.addAll(_emitCovarianceCheckStub(m)); |
| } else if (m.isFactory) { |
| // Skip redirecting factories (they've already been resolved). |
| if (redirectingFactories?.contains(m) ?? false) continue; |
| jsMethods.add(_emitFactoryConstructor(m)); |
| } else if (m.isAccessor) { |
| jsMethods.add(_emitMethodDeclaration(m)); |
| jsMethods.add(_emitSuperAccessorWrapper(m, getters, setters)); |
| if (!hasJsPeer && m.isGetter && m.name.name == 'iterator') { |
| hasIterator = true; |
| jsMethods.add(_emitIterable(c)); |
| } |
| } else { |
| jsMethods.add(_emitMethodDeclaration(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.where((m) => m != null).toList(); |
| } |
| |
| bool _isForwardingStub(Procedure member) { |
| if (member.isForwardingStub || member.isForwardingSemiStub) { |
| if (_currentLibrary.importUri.scheme != '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 = _emitFunction(member.function, member.name.name); |
| } |
| |
| return js_ast.Method(_declareMemberName(member), fn, |
| isGetter: member.isGetter, |
| isSetter: member.isSetter, |
| isStatic: member.isStatic) |
| ..sourceInformation = _nodeEnd(member.fileEndOffset); |
| } |
| |
| js_ast.Fun _emitNativeFunctionBody(Procedure node) { |
| String name = _annotationName(node, isJSAnnotation) ?? node.name.name; |
| if (node.isGetter) { |
| return js_ast.Fun([], js.block('{ return this.#; }', [name])); |
| } else if (node.isSetter) { |
| var params = _emitParameters(node.function); |
| return js_ast.Fun( |
| params, js.block('{ this.# = #; }', [name, params.last])); |
| } else { |
| return js.fun( |
| 'function (...args) { return this.#.apply(this, args); }', name); |
| } |
| } |
| |
| 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.forwardingStubSuperTarget ?? |
| member.forwardingStubInterfaceTarget; |
| |
| if (superMember == null) return const []; |
| |
| substituteType(DartType t) { |
| return _getTypeFromClass(t, superMember.enclosingClass, enclosingClass); |
| } |
| |
| var name = _declareMemberName(member); |
| if (member.isSetter) { |
| if (superMember is Field && isCovariantField(superMember) || |
| superMember is Procedure && |
| isCovariantParameter( |
| superMember.function.positionalParameters[0])) { |
| return const []; |
| } |
| var setterType = substituteType(superMember.setterType); |
| 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(superMember.function.thisFunctionType) 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(superMember.function.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(superMember.function.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 (# in #) #;', [ |
| name, |
| namedArgumentTemp, |
| _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 jsBody = _emitSyncFunctionBody(function); |
| |
| return js_ast.Method(_constructorName(node.name.name), |
| js_ast.Fun(_emitParameters(function), jsBody), |
| isStatic: true) |
| ..sourceInformation = _nodeEnd(node.fileEndOffset); |
| } |
| |
| @override |
| js_ast.Expression emitConstructorAccess(InterfaceType type) { |
| return _emitJSInterop(type.classNode) ?? visitInterfaceType(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 name = _declareMemberName(field); |
| |
| var getter = js.fun('function() { return this[#]; }', [virtualField]); |
| var jsGetter = js_ast.Method(name, getter, isGetter: true) |
| ..sourceInformation = _nodeStart(field); |
| |
| var args = |
| field.isFinal ? [js_ast.Super(), name] : [js_ast.This(), virtualField]; |
| |
| js_ast.Expression value = _emitIdentifier('value'); |
| if (!field.isFinal && isCovariantField(field)) { |
| value = _emitCast(value, field.type); |
| } |
| args.add(value); |
| |
| var jsSetter = js_ast.Method( |
| name, js.fun('function(value) { #[#] = #; }', args), |
| 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.name; |
| // 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.name; |
| 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; |
| |
| // 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(c.superclass, Name('iterator')); |
| if (parent != null) return null; |
| |
| var parentIterable = |
| _hierarchy.getClassAsInstanceOf(c.superclass, _coreTypes.iterableClass); |
| if (parentIterable != null) return null; |
| |
| if (c.enclosingLibrary.importUri.scheme == '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'), |
| js.call('function() { return new #.JsIterator(this.#); }', [ |
| runtimeModule, |
| _emitMemberName('iterator', memberClass: _coreTypes.iterableClass) |
| ]) as js_ast.Fun); |
| } |
| |
| js_ast.Expression _instantiateAnnotation(Expression node) => |
| _visitExpression(node); |
| |
| void _registerExtensionType( |
| Class c, String jsPeerName, List<js_ast.Statement> body) { |
| var className = _emitTopLevelName(c); |
| if (_typeRep.isPrimitive(_coreTypes.legacyRawType(c))) { |
| body.add(runtimeStatement( |
| 'definePrimitiveHashCode(#.prototype)', [className])); |
| } |
| body.add(runtimeStatement( |
| 'registerExtension(#, #)', [js.string(jsPeerName), className])); |
| } |
| |
| void _emitTopLevelFields(List<Field> fields) { |
| if (isSdkInternalRuntime(_currentLibrary)) { |
| /// Treat dart:_runtime fields as safe to eagerly evaluate. |
| // TODO(jmesserly): it'd be nice to avoid this special case. |
| var lazyFields = <Field>[]; |
| var savedUri = _currentUri; |
| |
| // Helper functions to test if a constructor invocation is internal and |
| // should be eagerly evaluated. |
| var isInternalConstructor = (ConstructorInvocation node) { |
| var type = node.getStaticType(_types) as InterfaceType; |
| var library = type.classNode.enclosingLibrary; |
| return isSdkInternalRuntime(library); |
| }; |
| for (var field in fields) { |
| 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(_types) as InterfaceType; |
| _emitClass(type.classNode); |
| } |
| _currentUri = field.fileUri; |
| moduleItems.add(js.statement('# = #;', [ |
| _emitTopLevelName(field), |
| _visitInitializer(init, field.annotations) |
| ])); |
| } else { |
| lazyFields.add(field); |
| } |
| } |
| |
| _currentUri = savedUri; |
| fields = lazyFields; |
| } |
| |
| if (fields.isEmpty) return; |
| moduleItems.add(_emitLazyFields( |
| emitLibraryName(_currentLibrary), fields, _emitTopLevelMemberName)); |
| } |
| |
| js_ast.Statement _emitLazyFields( |
| js_ast.Expression objExpr, |
| Iterable<Field> fields, |
| js_ast.Expression Function(Field f) emitFieldName) { |
| var accessors = <js_ast.Method>[]; |
| var savedUri = _currentUri; |
| |
| for (var field in fields) { |
| _currentUri = field.fileUri; |
| var access = emitFieldName(field); |
| accessors.add(js_ast.Method(access, _emitStaticFieldInitializer(field), |
| isGetter: true) |
| ..sourceInformation = _hoverComment( |
| js_ast.PropertyAccess(objExpr, access), |
| field.fileOffset, |
| field.name.name.length)); |
| |
| // TODO(jmesserly): currently uses a dummy setter to indicate writable. |
| if (!field.isFinal && !field.isConst) { |
| accessors.add(js_ast.Method( |
| access, js.call('function(_) {}') as js_ast.Fun, |
| isSetter: true)); |
| } |
| } |
| _currentUri = _currentLibrary.fileUri; |
| |
| _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> visitBody()) { |
| var savedLetVariables = _letVariables; |
| _letVariables = []; |
| |
| var body = visitBody(); |
| var letVars = _initLetVariables(); |
| if (letVars != null) body.insert(0, letVars); |
| |
| _letVariables = savedLetVariables; |
| return body; |
| } |
| |
| js_ast.ArrowFun _arrowFunctionWithLetScope(js_ast.Expression visitBody()) { |
| var savedLetVariables = _letVariables; |
| _letVariables = []; |
| |
| var expr = visitBody(); |
| var letVars = _initLetVariables(); |
| |
| _letVariables = savedLetVariables; |
| return js_ast.ArrowFun( |
| [], letVars == null ? expr : js_ast.Block([letVars, expr.toReturn()])); |
| } |
| |
| js_ast.PropertyAccess _emitTopLevelName(NamedNode n, {String suffix = ''}) { |
| return _emitJSInterop(n) ?? _emitTopLevelNameNoInterop(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}) { |
| return _emitMemberName(m.name.name, |
| isStatic: m is Field ? m.isStatic : (m as Procedure).isStatic, |
| useExtension: |
| useExtension ?? _extensionTypes.isNativeClass(m.enclosingClass), |
| member: m); |
| } |
| |
| /// This handles member renaming for private names and operators. |
| /// |
| /// Private names are generated using ES6 symbols: |
| /// |
| /// // At the top of the module: |
| /// let _x = Symbol('_x'); |
| /// let _y = Symbol('_y'); |
| /// ... |
| /// |
| /// class Point { |
| /// Point(x, y) { |
| /// this[_x] = x; |
| /// this[_y] = y; |
| /// } |
| /// get x() { return this[_x]; } |
| /// get y() { return this[_y]; } |
| /// } |
| /// |
| /// For user-defined operators the following names are allowed: |
| /// |
| /// <, >, <=, >=, ==, -, +, /, ~/, *, %, |, ^, &, <<, >>, []=, [], ~ |
| /// |
| /// They generate code like: |
| /// |
| /// x['+'](y) |
| /// |
| /// There are three exceptions: [], []= and unary -. |
| /// The indexing operators we use `get` and `set` instead: |
| /// |
| /// x.get('hi') |
| /// x.set('hi', 123) |
| /// |
| /// This follows the same pattern as ECMAScript 6 Map: |
| /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map> |
| /// |
| /// Unary minus looks like: `x._negate()`. |
| /// |
| /// Equality is a bit special, it is generated via the Dart `equals` runtime |
| /// helper, that checks for null. The user defined method is called '=='. |
| /// |
| js_ast.Expression _emitMemberName(String name, |
| {bool isStatic = false, |
| bool useExtension, |
| Member member, |
| Class memberClass}) { |
| // Static members skip the rename steps and may require JS interop renames. |
| if (isStatic) { |
| return _emitStaticMemberName(name, member); |
| } |
| |
| // We allow some (illegal in Dart) member names to be used in our private |
| // SDK code. These renames need to be included at every declaration, |
| // including overrides in subclasses. |
| if (member != null) { |
| var runtimeName = _jsExportName(member); |
| if (runtimeName != null) { |
| var parts = runtimeName.split('.'); |
| if (parts.length < 2) return propertyName(runtimeName); |
| |
| js_ast.Expression result = _emitIdentifier(parts[0]); |
| for (int i = 1; i < parts.length; i++) { |
| result = js_ast.PropertyAccess(result, propertyName(parts[i])); |
| } |
| return result; |
| } |
| } |
| |
| memberClass ??= member?.enclosingClass; |
| if (name.startsWith('_')) { |
| // Use the library that this private member's name is scoped to. |
| var memberLibrary = member?.name?.library ?? |
| memberClass?.enclosingLibrary ?? |
| _currentLibrary; |
| return emitPrivateNameSymbol(memberLibrary, name); |
| } |
| |
| useExtension ??= _isSymbolizedMember(memberClass, name); |
| name = js_ast.memberNameForDartMember(name, _isExternal(member)); |
| if (useExtension) { |
| return getExtensionSymbolInternal(name); |
| } |
| return propertyName(name); |
| } |
| |
| /// Don't symbolize native members that just forward to the underlying |
| /// native member. We limit this to non-renamed members as the receiver |
| /// may be a mock type. |
| /// |
| /// Note, this is an underlying assumption here that, if another native type |
| /// subtypes this one, it also forwards this member to its underlying native |
| /// one without renaming. |
| bool _isSymbolizedMember(Class c, String name) { |
| if (c == null) { |
| return _isObjectMember(name); |
| } |
| c = _typeRep.getImplementationClass(_coreTypes.legacyRawType(c)) ?? c; |
| if (_extensionTypes.isNativeClass(c)) { |
| var member = _lookupForwardedMember(c, name); |
| |
| // Fields on a native class are implicitly native. |
| // Methods/getters/setters are marked external/native. |
| if (member is Field || _isExternal(member)) { |
| var jsName = _annotationName(member, isJSName); |
| return jsName != null && jsName != name; |
| } else { |
| // Non-external members must be symbolized. |
| return true; |
| } |
| } |
| // If the receiver *may* be a native type (i.e., an interface allowed to |
| // be implemented by a native class), conservatively symbolize - we don't |
| // know whether it'll be implemented via forwarding. |
| // TODO(vsm): Consider CHA here to be less conservative. |
| return _extensionTypes.isNativeInterface(c); |
| } |
| |
| var _forwardingCache = HashMap<Class, Map<String, Member>>(); |
| |
| Member _lookupForwardedMember(Class c, String name) { |
| // We only care about public methods. |
| if (name.startsWith('_')) return null; |
| |
| var map = _forwardingCache.putIfAbsent(c, () => {}); |
| |
| return map.putIfAbsent( |
| name, |
| () => |
| _hierarchy.getDispatchTarget(c, Name(name)) ?? |
| _hierarchy.getDispatchTarget(c, Name(name), setter: true)); |
| } |
| |
| js_ast.Expression _emitStaticMemberName(String name, [NamedNode member]) { |
| if (member != null) { |
| var jsName = _emitJSInteropStaticMemberName(member); |
| if (jsName != null) return jsName; |
| } |
| |
| switch (name) { |
| // Reserved for the compiler to do `x as T`. |
| case 'as': |
| // Reserved for the compiler to do implicit cast `T x = y`. |
| case '_check': |
| // Reserved for the SDK to compute `Type.toString()`. |
| case 'name': |
| // Reserved by JS, not a valid static member name. |
| case 'prototype': |
| name += '_'; |
| break; |
| default: |
| // All trailing underscores static names are reserved for the compiler |
| // or SDK libraries. |
| // |
| // If user code uses them, add an extra `_`. |
| // |
| // This also avoids collision with the renames above, e.g. `static as` |
| // and `static as_` will become `as_` and `as__`. |
| if (name.endsWith('_')) { |
| name += '_'; |
| } |
| } |
| return propertyName(name); |
| } |
| |
| js_ast.Expression _emitJSInteropStaticMemberName(NamedNode n) { |
| if (!usesJSInterop(n)) return null; |
| var name = _annotationName(n, isPublicJSAnnotation); |
| if (name != null) { |
| if (name.contains('.')) { |
| throw UnsupportedError( |
| 'static members do not support "." in their names. ' |
| 'See https://github.com/dart-lang/sdk/issues/27926'); |
| } |
| } else { |
| name = getTopLevelName(n); |
| } |
| return js.escapedString(name, "'"); |
| } |
| |
| js_ast.PropertyAccess _emitTopLevelNameNoInterop(NamedNode n, |
| {String suffix = ''}) { |
| return js_ast.PropertyAccess(emitLibraryName(getLibrary(n)), |
| _emitTopLevelMemberName(n, suffix: suffix)); |
| } |
| |
| /// Emits the member name portion of a top-level member. |
| /// |
| /// NOTE: usually you should use [_emitTopLevelName] instead of this. This |
| /// function does not handle JS interop. |
| js_ast.Expression _emitTopLevelMemberName(NamedNode n, {String suffix = ''}) { |
| var name = _jsExportName(n) ?? getTopLevelName(n); |
| return propertyName(name + suffix); |
| } |
| |
| bool _isExternal(Member m) { |
| // Corresponds to the names in memberNameForDartMember in |
| // compiler/js_names.dart. |
| const renamedJsMembers = ["prototype", "constructor"]; |
| if (m is Procedure) { |
| if (m.isExternal) return true; |
| if (m.isNoSuchMethodForwarder) { |
| if (renamedJsMembers.contains(m.name.name)) { |
| return _hasExternalProcedure(m.enclosingClass, m.name.name); |
| } |
| } |
| } |
| return false; |
| } |
| |
| /// Returns true if anything up the class hierarchy externally defines a |
| /// procedure with name = [name]. |
| /// |
| /// Used to determine when we should alias Dart-JS reserved members |
| /// (e.g., 'prototype' and 'constructor'). |
| bool _hasExternalProcedure(Class c, String name) { |
| var classes = Queue<Class>()..add(c); |
| |
| while (classes.isNotEmpty) { |
| var c = classes.removeFirst(); |
| var classesToCheck = [ |
| if (c.supertype != null) c.supertype.classNode, |
| for (var t in c.implementedTypes) if (t.classNode != null) t.classNode, |
| ]; |
| classes.addAll(classesToCheck); |
| for (var procedure in c.procedures) { |
| if (procedure.name.name == name && !procedure.isNoSuchMethodForwarder) { |
| return procedure.isExternal; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| String _getJSNameWithoutGlobal(NamedNode n) { |
| if (!usesJSInterop(n)) return null; |
| var libraryJSName = _annotationName(getLibrary(n), isPublicJSAnnotation); |
| var jsName = _annotationName(n, isPublicJSAnnotation) ?? getTopLevelName(n); |
| return libraryJSName != null ? '$libraryJSName.$jsName' : jsName; |
| } |
| |
| js_ast.PropertyAccess _emitJSInterop(NamedNode n) { |
| var jsName = _getJSNameWithoutGlobal(n); |
| if (jsName == null) return null; |
| return _emitJSInteropForGlobal(jsName); |
| } |
| |
| js_ast.PropertyAccess _emitJSInteropForGlobal(String name) { |
| var parts = name.split('.'); |
| if (parts.isEmpty) parts = ['']; |
| js_ast.PropertyAccess access; |
| for (var part in parts) { |
| access = js_ast.PropertyAccess( |
| access ?? runtimeCall('global'), js.escapedString(part, "'")); |
| } |
| return access; |
| } |
| |
| void _emitLibraryProcedures(Library library) { |
| var procedures = library.procedures |
| .where((p) => !p.isExternal && !p.isAbstract) |
| .toList(); |
| moduleItems.addAll(procedures |
| .where((p) => !p.isAccessor) |
| .map(_emitLibraryFunction) |
| .toList()); |
| _emitLibraryAccessors(procedures.where((p) => p.isAccessor).toList()); |
| } |
| |
| void _emitLibraryAccessors(Iterable<Procedure> accessors) { |
| if (accessors.isEmpty) return; |
| moduleItems.add(runtimeStatement('copyProperties(#, { # })', [ |
| emitLibraryName(_currentLibrary), |
| accessors.map(_emitLibraryAccessor).toList() |
| ])); |
| } |
| |
| js_ast.Method _emitLibraryAccessor(Procedure node) { |
| var savedUri = _currentUri; |
| _currentUri = node.fileUri; |
| |
| var name = node.name.name; |
| var result = js_ast.Method( |
| propertyName(name), _emitFunction(node.function, node.name.name), |
| isGetter: node.isGetter, isSetter: node.isSetter) |
| ..sourceInformation = _nodeEnd(node.fileEndOffset); |
| |
| _currentUri = savedUri; |
| return result; |
| } |
| |
| js_ast.Statement _emitLibraryFunction(Procedure p) { |
| var savedUri = _currentUri; |
| _currentUri = p.fileUri; |
| |
| var body = <js_ast.Statement>[]; |
| var fn = _emitFunction(p.function, p.name.name) |
| ..sourceInformation = _nodeEnd(p.fileEndOffset); |
| |
| if (_currentLibrary.importUri.scheme == 'dart' && |
| _isInlineJSFunction(p.function.body)) { |
| fn = js_ast.simplifyPassThroughArrowFunCallBody(fn); |
| } |
| |
| var nameExpr = _emitTopLevelName(p); |
| body.add(js.statement('# = #', |
| [nameExpr, js_ast.NamedFunction(_emitTemporaryId(p.name.name), fn)])); |
| // Function types of top-level/static functions are only needed when |
| // dart:mirrors is enabled. |
| // TODO(jmesserly): do we even need this for mirrors, since statics are not |
| // commonly reflected on? |
| if (_options.emitMetadata && _reifyFunctionType(p.function)) { |
| body.add(_emitFunctionTagged(nameExpr, p.function.thisFunctionType, |
| topLevel: true) |
| .toStatement()); |
| } |
| |
| _currentUri = savedUri; |
| return js_ast.Statement.from(body); |
| } |
| |
| js_ast.Expression _emitFunctionTagged(js_ast.Expression fn, FunctionType type, |
| {bool topLevel = false}) { |
| var lazy = topLevel && !_canEmitTypeAtTopLevel(type); |
| var typeRep = visitFunctionType(type, lazy: lazy); |
| return runtimeCall(lazy ? 'lazyFn(#, #)' : 'fn(#, #)', [fn, typeRep]); |
| } |
| |
| /// Whether the expression for [type] can be evaluated at this point in the JS |
| /// module. |
| /// |
| /// Types cannot be evaluated if they depend on something that hasn't been |
| /// defined yet. For example: |
| /// |
| /// C foo() => null; |
| /// class C {} |
| /// |
| /// If we're emitting the type information for `foo`, we cannot refer to `C` |
| /// yet, so we must evaluate foo's type lazily. |
| bool _canEmitTypeAtTopLevel(DartType type) { |
| if (type is InterfaceType) { |
| return !_pendingClasses.contains(type.classNode) && |
| type.typeArguments.every(_canEmitTypeAtTopLevel); |
| } |
| if (type is FunctionType) { |
| // Generic functions are always safe to emit, because they're lazy until |
| // type arguments are applied. |
| if (type.typeParameters.isNotEmpty) return true; |
| |
| return (_canEmitTypeAtTopLevel(type.returnType) && |
| type.positionalParameters.every(_canEmitTypeAtTopLevel) && |
| type.namedParameters.every((n) => _canEmitTypeAtTopLevel(n.type))); |
| } |
| if (type is TypedefType) { |
| return type.typeArguments.every(_canEmitTypeAtTopLevel); |
| } |
| return true; |
| } |
| |
| /// Emits a Dart [type] into code. |
| js_ast.Expression _emitType(DartType type) => type.accept(this); |
| |
| js_ast.Expression _emitInvalidNode(Node node, [String message = '']) { |
| if (message.isNotEmpty) message += ' '; |
| return runtimeCall('throwUnimplementedError(#)', |
| [js.escapedString('node <${node.runtimeType}> $message`$node`')]); |
| } |
| |
| @override |
| js_ast.Expression defaultDartType(DartType type) => _emitInvalidNode(type); |
| |
| @override |
| js_ast.Expression visitInvalidType(InvalidType type) => defaultDartType(type); |
| |
| @override |
| js_ast.Expression visitDynamicType(DynamicType type) => |
| runtimeCall('dynamic'); |
| |
| @override |
| js_ast.Expression visitVoidType(VoidType type) => runtimeCall('void'); |
| |
| @override |
| js_ast.Expression visitBottomType(BottomType type) => runtimeCall('bottom'); |
| |
| @override |
| js_ast.Expression visitInterfaceType(InterfaceType type) { |
| var c = type.classNode; |
| _declareBeforeUse(c); |
| |
| // Type parameters don't matter as JS interop types cannot be reified. |
| // We have to use lazy JS types because until we have proper module |
| // loading for JS libraries bundled with Dart libraries, we will sometimes |
| // need to load Dart libraries before the corresponding JS libraries are |
| // actually loaded. |
| // Given a JS type such as: |
| // @JS('google.maps.Location') |
| // class Location { ... } |
| // We can't emit a reference to MyType because the JS library that defines |
| // it may be loaded after our code. So for now, we use a special lazy type |
| // object to represent MyType. |
| // Anonymous JS types do not have a corresponding concrete JS type so we |
| // have to use a helper to define them. |
| if (isJSAnonymousType(c)) { |
| return runtimeCall( |
| 'anonymousJSType(#)', [js.escapedString(getLocalClassName(c))]); |
| } |
| var jsName = _getJSNameWithoutGlobal(c); |
| if (jsName != null) { |
| return runtimeCall('lazyJSType(() => #, #)', |
| [_emitJSInteropForGlobal(jsName), js.escapedString(jsName)]); |
| } |
| |
| var args = type.typeArguments; |
| Iterable<js_ast.Expression> jsArgs; |
| if (args.any((a) => a != const DynamicType())) { |
| jsArgs = args.map(_emitType); |
| } |
| if (jsArgs != null) { |
| var typeRep = _emitGenericClassType(type, jsArgs); |
| return _cacheTypes ? _typeTable.nameType(type, typeRep) : typeRep; |
| } |
| |
| return _emitTopLevelNameNoInterop(type.classNode); |
| } |
| |
| bool get _emittingClassSignatures => |
| _currentClass != null && |
| identical(_currentClass, _classEmittingSignatures); |
| |
| bool get _emittingClassExtends => |
| _currentClass != null && identical(_currentClass, _classEmittingExtends); |
| |
| bool get _cacheTypes => |
| !_emittingClassExtends && !_emittingClassSignatures || |
| _currentFunction != null; |
| |
| js_ast.Expression _emitGenericClassType( |
| InterfaceType t, Iterable<js_ast.Expression> typeArgs) { |
| var genericName = _emitTopLevelNameNoInterop(t.classNode, suffix: '\$'); |
| return js.call('#(#)', [genericName, typeArgs]); |
| } |
| |
| @override |
| js_ast.Expression visitFunctionType(type, |
| {Member member, bool lazy = false}) { |
| var requiredTypes = |
| type.positionalParameters.take(type.requiredParameterCount).toList(); |
| var function = member?.function; |
| var requiredParams = function?.positionalParameters |
| ?.take(type.requiredParameterCount) |
| ?.toList(); |
| var optionalTypes = |
| type.positionalParameters.skip(type.requiredParameterCount).toList(); |
| var optionalParams = function?.positionalParameters |
| ?.skip(type.requiredParameterCount) |
| ?.toList(); |
| |
| var namedTypes = type.namedParameters; |
| var rt = _emitType(type.returnType); |
| var ra = _emitTypeNames(requiredTypes, requiredParams, member); |
| |
| List<js_ast.Expression> typeParts; |
| if (namedTypes.isNotEmpty) { |
| assert(optionalTypes.isEmpty); |
| // TODO(vsm): Pass in annotations here as well. |
| var na = _emitTypeProperties(namedTypes); |
| typeParts = [rt, ra, na]; |
| } else if (optionalTypes.isNotEmpty) { |
| assert(namedTypes.isEmpty); |
| var oa = _emitTypeNames(optionalTypes, optionalParams, member); |
| typeParts = [rt, ra, oa]; |
| } else { |
| typeParts = [rt, ra]; |
| } |
| |
| var typeFormals = type.typeParameters; |
| String helperCall; |
| if (typeFormals.isNotEmpty) { |
| var tf = _emitTypeFormals(typeFormals); |
| |
| addTypeFormalsAsParameters(List<js_ast.Expression> elements) { |
| var names = _typeTable.discharge(typeFormals); |
| return names.isEmpty |
| ? js.call('(#) => [#]', [tf, elements]) |
| : js.call('(#) => {#; return [#];}', [tf, names, elements]); |
| } |
| |
| typeParts = [addTypeFormalsAsParameters(typeParts)]; |
| |
| helperCall = 'gFnType(#)'; |
| |
| /// Whether the type parameter [t] has an explicit bound, like |
| /// `<T extends C>`, `<T extends Object>` or `<T extends dynamic>`. |
| /// |
| /// In contrast, a type parameter like `<T>` has an implicit bound. |
| /// Implicit bounds are a bit unusual, in that `Object` is used as the |
| /// bound for checking, but `dynamic` is filled in as the default value. |
| /// |
| /// Kernel represents `<T>` as `<T extends Object = dynamic>`. We can find |
| /// explicit bounds by looking for anything *except* that. |
| typeParameterHasExplicitBound(TypeParameter t) => |
| t.bound != _types.coreTypes.objectLegacyRawType || |
| t.defaultType != const DynamicType(); |
| |
| // If any explicit bounds were passed, emit them. |
| if (typeFormals.any(typeParameterHasExplicitBound)) { |
| /// Emits the bound of the type parameter [t] for use in runtime |
| /// checking and the default value (e.g. for dynamic class). |
| /// |
| /// For most type parameters we can use [TypeParameter.bound]. However, |
| /// for *implicit* bounds such as `<T>` (represented in Kernel as |
| /// `<T extends Object = dynamic>`) we need to emit `dynamic` so we use |
| /// the correct default value at runtime. |
| /// |
| /// Because `dynamic` and `Object` are both top types, they'll behave |
| /// identically for the purposes of type checks. |
| emitTypeParameterBound(TypeParameter t) => |
| typeParameterHasExplicitBound(t) |
| ? _emitType(t.bound) |
| : visitDynamicType(const DynamicType()); |
| |
| var bounds = typeFormals.map(emitTypeParameterBound).toList(); |
| typeParts.add(addTypeFormalsAsParameters(bounds)); |
| } |
| } else { |
| helperCall = 'fnType(#)'; |
| } |
| var typeRep = runtimeCall(helperCall, [typeParts]); |
| return _cacheTypes |
| ? _typeTable.nameFunctionType(type, typeRep, lazy: lazy) |
| : typeRep; |
| } |
| |
| js_ast.Expression _emitAnnotatedFunctionType( |
| FunctionType type, Member member) { |
| var result = visitFunctionType(type, member: member); |
| |
| var annotations = member.annotations; |
| if (_options.emitMetadata && annotations.isNotEmpty) { |
| // TODO(jmesserly): should we disable source info for annotations? |
| var savedUri = _currentUri; |
| _currentUri = member.enclosingClass.fileUri; |
| result = js_ast.ArrayInitializer( |
| [result]..addAll(annotations.map(_instantiateAnnotation))); |
| _currentUri = savedUri; |
| } |
| return result; |
| } |
| |
| /// Emits an expression that lets you access statics on a [type] from code. |
| js_ast.Expression _emitConstructorAccess(InterfaceType type) { |
| return _emitJSInterop(type.classNode) ?? _emitType(type); |
| } |
| |
| js_ast.Expression _emitConstructorName(InterfaceType type, Member c) { |
| return _emitJSInterop(type.classNode) ?? |
| js_ast.PropertyAccess( |
| _emitConstructorAccess(type), _constructorName(c.name.name)); |
| } |
| |
| /// Emits an expression that lets you access statics on an [c] from code. |
| js_ast.Expression _emitStaticClassName(Class c) { |
| _declareBeforeUse(c); |
| return _emitTopLevelName(c); |
| } |
| |
| // Wrap a result - usually a type - with its metadata. The runtime is |
| // responsible for unpacking this. |
| js_ast.Expression _emitAnnotatedResult( |
| js_ast.Expression result, List<Expression> metadata, Member member) { |
| if (_options.emitMetadata && metadata.isNotEmpty) { |
| // TODO(jmesserly): should we disable source info for annotations? |
| var savedUri = _currentUri; |
| _currentUri = member.enclosingClass.fileUri; |
| result = js_ast.ArrayInitializer( |
| [result]..addAll(metadata.map(_instantiateAnnotation))); |
| _currentUri = savedUri; |
| } |
| return result; |
| } |
| |
| js_ast.ObjectInitializer _emitTypeProperties(Iterable<NamedType> types) { |
| return js_ast.ObjectInitializer(types |
| .map((t) => js_ast.Property(propertyName(t.name), _emitType(t.type))) |
| .toList()); |
| } |
| |
| js_ast.ArrayInitializer _emitTypeNames(List<DartType> types, |
| List<VariableDeclaration> parameters, Member member) { |
| var result = <js_ast.Expression>[]; |
| for (int i = 0; i < types.length; ++i) { |
| var type = _emitType(types[i]); |
| if (parameters != null) { |
| type = _emitAnnotatedResult(type, parameters[i].annotations, member); |
| } |
| result.add(type); |
| } |
| return js_ast.ArrayInitializer(result); |
| } |
| |
| @override |
| js_ast.Expression visitTypeParameterType(TypeParameterType type) => |
| _emitTypeParameter(type.parameter); |
| |
| js_ast.Identifier _emitTypeParameter(TypeParameter t) => |
| _emitIdentifier(getTypeParameterName(t)); |
| |
| @override |
| js_ast.Expression visitTypedefType(TypedefType type) => |
| visitFunctionType(type.unalias as FunctionType); |
| |
| js_ast.Fun _emitFunction(FunctionNode f, String name) { |
| // normal function (sync), vs (sync*, async, async*) |
| var isSync = f.asyncMarker == AsyncMarker.Sync; |
| var formals = _emitParameters(f); |
| var typeFormals = _emitTypeFormals(f.typeParameters); |
| |
| var parent = f.parent; |
| if (_reifyGenericFunction(parent is Member ? parent : null)) { |
| formals.insertAll(0, typeFormals); |
| } |
| |
| // TODO(jmesserly): need a way of determining if parameters are |
| // potentially mutated in Kernel. For now we assume all parameters are. |
| super.enterFunction(name, formals, () => true); |
| |
| js_ast.Block block = |
| isSync ? _emitSyncFunctionBody(f) : _emitGeneratorFunctionBody(f, name); |
| |
| block = super.exitFunction(name, formals, block); |
| return js_ast.Fun(formals, block); |
| } |
| |
| List<js_ast.Parameter> _emitParameters(FunctionNode f) { |
| var positional = f.positionalParameters; |
| var result = List<js_ast.Parameter>.of(positional.map(_emitVariableDef)); |
| if (positional.isNotEmpty && |
| f.requiredParameterCount == positional.length && |
| positional.last.annotations.any(isJsRestAnnotation)) { |
| result.last = js_ast.RestParameter(result.last as js_ast.Identifier); |
| } |
| if (f.namedParameters.isNotEmpty) result.add(namedArgumentTemp); |
| return result; |
| } |
| |
| void _emitVirtualFieldSymbols(Class c, List<js_ast.Statement> body) { |
| _classProperties.virtualFields.forEach((field, virtualField) { |
| var symbol = emitClassPrivateNameSymbol( |
| c.enclosingLibrary, getLocalClassName(c), field.name.name); |
| body.add(js.statement('const # = #;', [virtualField, symbol])); |
| }); |
| } |
| |
| List<js_ast.Identifier> _emitTypeFormals(List<TypeParameter> typeFormals) { |
| return typeFormals |
| .map((t) => _emitIdentifier(getTypeParameterName(t))) |
| .toList(); |
| } |
| |
| /// Transforms `sync*` `async` and `async*` function bodies |
| /// using ES6 generators. |
| /// |
| |