blob: d3f8a9fc5d562b645c45237c595fe5ab61a12e12 [file] [log] [blame]
// 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:math' show max, min;
import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart' hide ConstantVisitor;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/type_environment.dart';
import 'package:source_span/source_span.dart' show SourceLocation;
import '../compiler/js_names.dart' as JS;
import '../compiler/js_utils.dart' as JS;
import '../compiler/module_builder.dart' show pathToJSIdentifier;
import '../js_ast/js_ast.dart' as JS;
import '../js_ast/js_ast.dart' show js;
import '../js_ast/source_map_printer.dart' show NodeEnd, NodeSpan, HoverComment;
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
implements
StatementVisitor<JS.Statement>,
ExpressionVisitor<JS.Expression>,
DartTypeVisitor<JS.Expression> {
/// The list of output module items, in the order they need to be emitted in.
final _moduleItems = <JS.ModuleItem>[];
/// The set of libraries we are currently compiling, and the temporaries used
/// to refer to them.
///
/// We sometimes special case codegen for a single library, as it simplifies
/// name scoping requirements.
final _libraries = new Map<Library, JS.Identifier>.identity();
/// Maps a library URI import, that is not in [_libraries], to the
/// corresponding Kernel summary module we imported it with.
final _importToSummary = new Map<Library, Component>.identity();
/// Maps a summary to the file URI we used to load it from disk.
final _summaryToUri = new Map<Component, Uri>.identity();
/// Imported libraries, and the temporaries used to refer to them.
final _imports = new Map<Library, JS.TemporaryId>();
/// The variable for the current catch clause
VariableDeclaration _catchParameter;
/// In an async* function, this represents the stream controller parameter.
JS.TemporaryId _asyncStarController;
// TODO(jmesserly): fuse this with notNull check.
final _privateNames = new HashMap<Library, HashMap<String, JS.TemporaryId>>();
JS.Identifier _extensionSymbolsModule;
final _extensionSymbols = new Map<String, JS.TemporaryId>();
JS.Identifier _runtimeModule;
final namedArgumentTemp = new JS.TemporaryId('opts');
Set<Class> _pendingClasses;
/// Temporary variables mapped to their corresponding JavaScript variable.
final _tempVariables = <VariableDeclaration, JS.TemporaryId>{};
/// Let variables collected for the given function.
List<JS.TemporaryId> _letVariables;
/// The class when it's emitting top-level code, used to order classes when
/// they extend each other.
///
/// This is not used when inside method bodies, or for other type information
/// such as `implements`.
Class _classEmittingTopLevel;
/// 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;
List<TypeParameter> _typeParamInConst;
/// 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;
/// 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 = new VirtualFieldModel();
final JSTypeRep _typeRep;
bool _superAllowed = true;
final _superHelpers = new Map<String, JS.Method>();
final bool emitMetadata;
final bool replCompile;
final Map<String, String> declaredVariables;
// 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 = new 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 = new HashMap<Statement, String>.identity();
final Class _jsArrayClass;
final Class _jsBoolClass;
final Class _jsNumberClass;
final Class _jsStringClass;
final Class privateSymbolClass;
final Class linkedHashMapImplClass;
final Class identityHashMapImplClass;
final Class linkedHashSetImplClass;
final Class identityHashSetImplClass;
final Class syncIterableClass;
/// The dart:async `StreamIterator<T>` type.
final Class _asyncStreamIteratorClass;
final ConstantVisitor _constants;
final NullableInference _nullableInference;
factory ProgramCompiler(Component component,
{bool emitMetadata: true,
bool replCompile: false,
Map<String, String> declaredVariables: const {}}) {
var nativeTypes = new NativeTypeSet(component);
var types = new TypeSchemaEnvironment(
nativeTypes.coreTypes, new ClassHierarchy(component), true);
return new ProgramCompiler._(
nativeTypes, new JSTypeRep(types, nativeTypes.sdk),
emitMetadata: emitMetadata,
replCompile: replCompile,
declaredVariables: declaredVariables);
}
ProgramCompiler._(NativeTypeSet nativeTypes, this._typeRep,
{this.emitMetadata, this.replCompile, this.declaredVariables})
: _extensionTypes = nativeTypes,
types = _typeRep.types,
coreTypes = nativeTypes.coreTypes,
_constants = new ConstantVisitor(nativeTypes.coreTypes),
_jsArrayClass =
nativeTypes.sdk.getClass('dart:_interceptors', 'JSArray'),
_jsBoolClass = nativeTypes.sdk.getClass('dart:_interceptors', 'JSBool'),
_jsNumberClass =
nativeTypes.sdk.getClass('dart:_interceptors', 'JSNumber'),
_jsStringClass =
nativeTypes.sdk.getClass('dart:_interceptors', 'JSString'),
_asyncStreamIteratorClass =
nativeTypes.sdk.getClass('dart:async', 'StreamIterator'),
privateSymbolClass =
nativeTypes.sdk.getClass('dart:_js_helper', 'PrivateSymbol'),
linkedHashMapImplClass =
nativeTypes.sdk.getClass('dart:_js_helper', 'LinkedMap'),
identityHashMapImplClass =
nativeTypes.sdk.getClass('dart:_js_helper', 'IdentityMap'),
linkedHashSetImplClass =
nativeTypes.sdk.getClass('dart:collection', '_HashSet'),
identityHashSetImplClass =
nativeTypes.sdk.getClass('dart:collection', '_IdentityHashSet'),
syncIterableClass =
nativeTypes.sdk.getClass('dart:_js_helper', 'SyncIterable'),
_nullableInference = new NullableInference(_typeRep);
ClassHierarchy get hierarchy => types.hierarchy;
JS.Program emitProgram(
Component p, List<Component> summaries, List<Uri> summaryUris) {
if (_moduleItems.isNotEmpty) {
throw new StateError('Can only call emitModule once.');
}
_component = p;
for (var i = 0; i < summaries.length; i++) {
var summary = summaries[i];
var summaryUri = summaryUris[i];
for (var l in summary.libraries) {
assert(!_importToSummary.containsKey(l));
_importToSummary[l] = summary;
_summaryToUri[summary] = summaryUri;
}
}
var libraries = p.libraries.where((l) => !l.isExternal);
var ddcRuntime =
libraries.firstWhere(isSdkInternalRuntime, orElse: () => null);
if (ddcRuntime != null) {
// Don't allow these to be renamed when we're building the SDK.
// There is JS code in dart:* that depends on their names.
_runtimeModule = new JS.Identifier('dart');
_extensionSymbolsModule = new JS.Identifier('dartx');
_nullableInference.allowNotNullDeclarations = true;
} else {
// Otherwise allow these to be renamed so users can write them.
_runtimeModule = new JS.TemporaryId('dart');
_extensionSymbolsModule = new JS.TemporaryId('dartx');
}
_typeTable = new TypeTable(_runtimeModule);
// Initialize our library variables.
var items = <JS.ModuleItem>[];
var exports = <JS.NameSpecifier>[];
var root = new JS.Identifier('_root');
items.add(js.statement('const # = Object.create(null)', [root]));
void emitLibrary(JS.Identifier id) {
items.add(js.statement('const # = Object.create(#)', [id, root]));
exports.add(new JS.NameSpecifier(id));
}
for (var library in libraries) {
var libraryTemp = library == ddcRuntime
? _runtimeModule
: new JS.TemporaryId(jsLibraryName(library));
_libraries[library] = libraryTemp;
emitLibrary(libraryTemp);
}
// dart:_runtime has a magic module that holds extension method symbols.
// TODO(jmesserly): find a cleaner design for this.
if (ddcRuntime != null) emitLibrary(_extensionSymbolsModule);
items.add(new JS.ExportDeclaration(new JS.ExportClause(exports)));
// Collect all class/type Element -> Node mappings
// in case we need to forward declare any classes.
_pendingClasses = new HashSet.identity();
for (var l in libraries) {
_pendingClasses.addAll(l.classes);
}
// 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);
// Visit directives (for exports)
libraries.forEach(_emitExports);
// Declare imports
_finishImports(items);
// Initialize extension symbols
_extensionSymbols.forEach((name, id) {
JS.Expression value =
new JS.PropertyAccess(_extensionSymbolsModule, _propertyName(name));
if (ddcRuntime != null) {
value = js.call('# = Symbol(#)', [value, js.string("dartx.$name")]);
}
items.add(js.statement('const # = #;', [id, value]));
});
// Discharge the type table cache variables and
// hoisted definitions.
items.addAll(_typeTable.discharge());
// Add the module's code (produced by visiting compilation units, above)
_copyAndFlattenBlocks(items, _moduleItems);
// Build the module.
return new JS.Program(items, name: p.root.name);
}
/// Flattens blocks in [items] to a single list.
///
/// This will not flatten blocks that are marked as being scopes.
void _copyAndFlattenBlocks(
List<JS.ModuleItem> result, Iterable<JS.ModuleItem> items) {
for (var item in items) {
if (item is JS.Block && !item.isScope) {
_copyAndFlattenBlocks(result, item.statements);
} else if (item != null) {
result.add(item);
}
}
}
/// Returns the canonical name to refer to the Dart library.
JS.Identifier emitLibraryName(Library library) {
// It's either one of the libraries in this module, or it's an import.
return _libraries[library] ??
_imports.putIfAbsent(
library, () => new JS.TemporaryId(jsLibraryName(library)));
}
String _libraryToModule(Library library) {
assert(!_libraries.containsKey(library));
if (library.importUri.scheme == 'dart') {
// TODO(jmesserly): we need to split out HTML.
return JS.dartSdkModule;
}
var summary = _importToSummary[library];
assert(summary != null);
// TODO(jmesserly): look up the appropriate relative import path if the user
// specified that on the command line.
var uri = _summaryToUri[summary];
var summaryPath = uri.path;
var extensionIndex = summaryPath.lastIndexOf('.');
// Note: These URIs do not contain absolute paths from the physical file
// system, but only the relevant path within a user's project. This path
// will match the path where the .js file is generated, so we use it as
// the module name.
var moduleName = summaryPath.substring(1, extensionIndex);
return moduleName;
}
void _finishImports(List<JS.ModuleItem> items) {
var modules = new Map<String, List<Library>>();
for (var import in _imports.keys) {
modules.putIfAbsent(_libraryToModule(import), () => []).add(import);
}
String coreModuleName;
if (!_libraries.containsKey(coreTypes.coreLibrary)) {
coreModuleName = _libraryToModule(coreTypes.coreLibrary);
}
modules.forEach((module, libraries) {
// Generate import directives.
//
// Our import variables are temps and can get renamed. Since our renaming
// is integrated into js_ast, it is aware of this possibility and will
// generate an "as" if needed. For example:
//
// import {foo} from 'foo'; // if no rename needed
// import {foo as foo$} from 'foo'; // if rename was needed
//
var imports =
libraries.map((l) => new JS.NameSpecifier(_imports[l])).toList();
if (module == coreModuleName) {
imports.add(new JS.NameSpecifier(_runtimeModule));
imports.add(new JS.NameSpecifier(_extensionSymbolsModule));
}
items.add(new JS.ImportDeclaration(
namedImports: imports, from: js.string(module, "'")));
});
}
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;
// `dart:_runtime` uses a different order for bootstrapping.
bool bootstrap = isSdkInternalRuntime(library);
if (bootstrap) _emitLibraryProcedures(library);
library.classes.forEach(_emitClass);
library.typedefs.forEach(_emitTypedef);
if (bootstrap) {
_moduleItems.add(_emitInternalSdkFields(library.fields));
} else {
_emitLibraryProcedures(library);
var fields = library.fields;
if (fields.isNotEmpty)
_moduleItems.add(_emitLazyFields(emitLibraryName(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));
_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) return;
if (identical(_currentClass, _classEmittingTopLevel)) _emitClass(c);
}
JS.Statement _emitClassDeclaration(Class c) {
// If this class is annotated with `@JS`, then there is nothing to emit.
if (findAnnotation(c, isPublicJSAnnotation) != null) return null;
// If this is a JavaScript type, emit it now and then exit.
var jsTypeDef = _emitJSType(c);
if (jsTypeDef != null) return jsTypeDef;
JS.Expression className;
if (c.typeParameters.isNotEmpty) {
// Generic classes will be defined inside a function that closes over the
// type parameter. So we can use their local variable name directly.
className = new JS.Identifier(getLocalClassName(c));
} else {
className = _emitTopLevelName(c);
}
var savedClassProperties = _classProperties;
_classProperties =
new ClassPropertyModel.build(types, _extensionTypes, virtualFields, c);
var jsCtors = _defineConstructors(c, className);
var jsMethods = _emitClassMethods(c);
var body = <JS.Statement>[];
_emitSuperHelperSymbols(body);
var deferredSupertypes = <JS.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 = _getJSPeerNames(c);
if (jsPeerNames.length == 1 && c.typeParameters.isNotEmpty) {
// Special handling for JSArray<E>
body.add(_callHelperStatement('setExtensionBaseClass(#, #.global.#);',
[className, _runtimeModule, jsPeerNames[0]]));
}
var finishGenericTypeTest = _emitClassTypeTests(c, className, body);
_emitVirtualFieldSymbols(c, body);
_emitClassSignature(c, className, body);
_initExtensionSymbols(c);
_defineExtensionMembers(className, body);
_emitClassMetadata(c.annotations, className, body);
var classDef = JS.Statement.from(body);
var typeFormals = c.typeParameters;
if (typeFormals.isNotEmpty) {
classDef = _defineClassTypeArguments(
c, typeFormals, classDef, className, deferredSupertypes);
} else {
body.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.Statement.from(body);
}
/// Wraps a possibly generic class in its type arguments.
JS.Statement _defineClassTypeArguments(
NamedNode c, List<TypeParameter> formals, JS.Statement body,
[JS.Expression className, List<JS.Statement> deferredBaseClass]) {
assert(formals.isNotEmpty);
var name = getTopLevelName(c);
var jsFormals = _emitTypeFormals(formals);
var typeConstructor = js.call('(#) => { #; #; return #; }', [
jsFormals,
_typeTable.discharge(formals),
body,
className ?? new JS.Identifier(name)
]);
var genericArgs = [typeConstructor];
if (deferredBaseClass != null && deferredBaseClass.isNotEmpty) {
genericArgs.add(js.call('(#) => { #; }', [jsFormals, deferredBaseClass]));
}
var genericCall = _callHelper('generic(#)', [genericArgs]);
var genericName = _emitTopLevelNameNoInterop(c, suffix: '\$');
return js.statement('{ # = #; # = #(); }',
[genericName, genericCall, _emitTopLevelName(c), genericName]);
}
JS.Statement _emitClassStatement(Class c, JS.Expression className,
JS.Expression heritage, List<JS.Method> methods) {
var name = getLocalClassName(c);
var classExpr =
new JS.ClassExpression(new JS.Identifier(name), heritage, methods);
if (c.typeParameters.isNotEmpty) {
return classExpr.toStatement();
} else {
return js.statement('# = #;', [className, classExpr]);
}
}
void _defineClass(Class c, JS.Expression className, List<JS.Method> methods,
List<JS.Statement> body, List<JS.Statement> deferredSupertypes) {
if (c == coreTypes.objectClass) {
body.add(_emitClassStatement(c, className, null, methods));
return;
}
JS.Expression emitDeferredType(DartType t) {
if (t is InterfaceType && t.typeArguments.isNotEmpty) {
return _emitGenericClassType(t, t.typeArguments.map(emitDeferredType));
}
return _emitType(t);
}
bool shouldDefer(InterfaceType t) {
var visited = new 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;
}
var mixins = <InterfaceType>[];
var mixedInType = c.mixedInType;
var superclass = c.superclass;
var supertype = c.supertype.asInterfaceType;
if (mixedInType != null) {
mixins.add(mixedInType.asInterfaceType);
for (;
superclass.isSyntheticMixinImplementation;
superclass = superclass.superclass) {
mixins.add(hierarchy
.getClassAsInstanceOf(c, superclass.mixedInClass)
.asInterfaceType);
}
if (mixins.length > 1) mixins = mixins.reversed.toList();
supertype = hierarchy.getClassAsInstanceOf(c, superclass).asInterfaceType;
}
var hasUnnamedSuper = _hasUnnamedConstructor(superclass);
var isCallable = isCallableClass(c);
void emitMixinConstructors(JS.Expression className, InterfaceType mixin) {
JS.Statement mixinCtor;
if (_hasUnnamedConstructor(mixin.classNode)) {
mixinCtor = js.statement('#.#.call(this);', [
emitClassRef(mixin),
_usesMixinNew(mixin.classNode)
? _callHelper('mixinNew')
: _constructorName('')
]);
}
for (var ctor in superclass.constructors) {
var jsParams = _emitFormalParameters(ctor.function);
var ctorBody = <JS.Statement>[];
if (mixinCtor != null) ctorBody.add(mixinCtor);
var name = ctor.name.name;
if (name != '' || hasUnnamedSuper) {
ctorBody.add(_emitSuperConstructorCall(className, name, jsParams));
}
body.add(_addConstructorToClass(
className,
name,
_finishConstructorFunction(
jsParams, new JS.Block(ctorBody), isCallable)));
}
}
var savedTopLevelClass = _classEmittingTopLevel;
_classEmittingTopLevel = c;
// Unroll mixins.
if (shouldDefer(supertype)) {
deferredSupertypes.add(_callHelperStatement('setBaseClass(#, #)', [
getBaseClass(isMixinAliasClass(c) ? 0 : mixins.length),
emitDeferredType(supertype),
]));
supertype = supertype.classNode.rawType;
}
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(
_callHelperStatement('mixinMembers(#, #)', [classExpr, mixinClass]));
_classEmittingTopLevel = savedTopLevelClass;
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:
//
// mixinMembers(C, class C$ extends M { <methods> });
mixinBody.add(_callHelperStatement('mixinMembers(#, #)', [
classExpr,
new JS.ClassExpression(
new JS.TemporaryId(getLocalClassName(c)), mixinClass, methods)
]));
}
emitMixinConstructors(className, m);
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 = new JS.TemporaryId(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,
new JS.ClassExpression(new JS.TemporaryId(mixinName), baseClass, [])
]));
emitMixinConstructors(mixinId, m);
hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(m.classNode);
if (shouldDefer(m)) {
deferredSupertypes.add(_callHelperStatement('mixinMembers(#, #)',
[getBaseClass(mixins.length - i), emitDeferredType(m)]));
} else {
body.add(_callHelperStatement(
'mixinMembers(#, #)', [mixinId, emitClassRef(m)]));
}
baseClass = mixinId;
}
_classEmittingTopLevel = savedTopLevelClass;
body.add(_emitClassStatement(c, className, baseClass, methods));
}
/// Defines all constructors for this class as ES5 constructors.
List<JS.Statement> _defineConstructors(Class c, JS.Expression className) {
var isCallable = isCallableClass(c);
var body = <JS.Statement>[];
if (isCallable) {
// Our class instances will have JS `typeof this == "function"`,
// so make sure to attach the runtime type information the same way
// we would do it for function types.
body.add(js.statement('#.prototype[#] = #;',
[className, _callHelper('_runtimeType'), className]));
}
if (c.isSyntheticMixinImplementation || isMixinAliasClass(c)) {
// We already handled this when we defined the class.
return body;
}
addConstructor(String name, JS.Expression jsCtor) {
body.add(_addConstructorToClass(className, name, jsCtor));
}
var fields = c.fields;
for (var ctor in c.constructors) {
if (ctor.isExternal) continue;
addConstructor(ctor.name.name,
_emitConstructor(ctor, fields, isCallable, 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,
_callHelper('mixinNew'),
[_initializeFields(fields)],
className
]));
}
return body;
}
JS.Statement _emitClassTypeTests(
Class c, JS.Expression className, List<JS.Statement> body) {
JS.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 _callHelper('is' + interface.name);
}
}
return null;
}
void markSubtypeOf(JS.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 = new TypeParameterType(c.typeParameters[0]);
var typeT = visitTypeParameterType(typeParam);
var futureOfT = visitInterfaceType(
new 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(_callHelperStatement('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 = new JS.TemporaryId("_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 _callHelperStatement(
'addTypeTests(#, #);', [defaultInst, isClassSymbol]);
}
void _emitSymbols(Iterable<JS.TemporaryId> vars, List<JS.ModuleItem> body) {
for (var id in vars) {
body.add(js.statement('const # = Symbol(#)', [id, js.string(id.name)]));
}
}
void _emitSuperHelperSymbols(List<JS.Statement> body) {
_emitSymbols(
_superHelpers.values.map((m) => m.name as JS.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.Statement> body) {
var fields = c.fields.where((f) => f.isStatic).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(
classRef,
_emitStaticMemberName(f.name.name),
_visitInitializer(f.initializer, f.annotations))
.toStatement());
}
} else if (fields.isNotEmpty) {
body.add(_emitLazyFields(_emitTopLevelName(c), fields));
}
}
void _emitClassMetadata(List<Expression> metadata, JS.Expression className,
List<JS.Statement> body) {
// Metadata
if (emitMetadata && metadata.isNotEmpty) {
body.add(js.statement('#[#.metadata] = #;', [
className,
_runtimeModule,
_arrowFunctionWithLetScope(() => new JS.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.Expression className, List<JS.Statement> body) {
void emitExtensions(String helperName, Iterable<String> extensions) {
if (extensions.isEmpty) return;
var names = extensions
.map((e) => _propertyName(JS.memberNameForDartMember(e)))
.toList();
body.add(js.statement('#.#(#, #);', [
_runtimeModule,
helperName,
className,
new JS.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.Expression className, List<JS.Statement> body) {
if (c.implementedTypes.isNotEmpty) {
body.add(js.statement('#[#.implements] = () => [#];', [
className,
_runtimeModule,
c.implementedTypes.map((i) => _emitType(i.asInterfaceType))
]));
}
void emitSignature(String name, List<JS.Property> elements) {
if (elements.isEmpty) return;
if (!name.startsWith('Static')) {
var proto = c == coreTypes.objectClass
? js.call('Object.create(null)')
: _callHelper('get${name}s(#.__proto__)', [className]);
elements.insert(0, new JS.Property(_propertyName('__proto__'), proto));
}
body.add(_callHelperStatement('set${name}Signature(#, () => #)', [
className,
new JS.ObjectInitializer(elements, multiline: elements.length > 1)
]));
}
var extMembers = _classProperties.extensionMethods;
var staticMethods = <JS.Property>[];
var instanceMethods = <JS.Property>[];
var staticGetters = <JS.Property>[];
var instanceGetters = <JS.Property>[];
var staticSetters = <JS.Property>[];
var instanceSetters = <JS.Property>[];
List<JS.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;
}
}
}
for (var member in c.procedures) {
if (member.isAbstract) continue;
// Static getters/setters cannot be called with dynamic dispatch, nor
// can they be torn off.
// TODO(jmesserly): can we attach static method type info at the tearoff
// point, and avoid saving the information otherwise? Same trick would
// work for top-level functions.
if (!emitMetadata && member.isAccessor && member.isStatic) {
continue;
}
var name = member.name.name;
var reifiedType = _getMemberRuntimeType(member);
// 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 !=
Substitution
.fromSupertype(hierarchy.getClassAsInstanceOf(
c, memberOverride.enclosingClass))
.substituteType(_getMemberRuntimeType(memberOverride));
if (needsSignature) {
var type = _emitAnnotatedFunctionType(reifiedType, member.annotations,
function: member.function);
var property = new JS.Property(_declareMemberName(member), type);
var signatures = getSignatureList(member);
signatures.add(property);
if (!member.isStatic && extMembers.contains(name)) {
signatures.add(new JS.Property(
_declareMemberName(member, useExtension: true), type));
}
}
}
emitSignature('Method', instanceMethods);
emitSignature('StaticMethod', staticMethods);
emitSignature('Getter', instanceGetters);
emitSignature('Setter', instanceSetters);
emitSignature('StaticGetter', staticGetters);
emitSignature('StaticSetter', staticSetters);
var instanceFields = <JS.Property>[];
var staticFields = <JS.Property>[];
for (var field in c.fields) {
// Only instance fields need to be saved for dynamic dispatch.
var isStatic = field.isStatic;
if (!emitMetadata && isStatic) continue;
var memberName = _declareMemberName(field);
var fieldSig = _emitFieldSignature(field.type,
metadata: field.annotations, isFinal: field.isFinal);
(isStatic ? staticFields : instanceFields)
.add(new JS.Property(memberName, fieldSig));
}
emitSignature('Field', instanceFields);
emitSignature('StaticField', staticFields);
var constructors = <JS.Property>[];
if (emitMetadata) {
for (var ctor in c.constructors) {
var memberName = _constructorName(ctor.name.name);
var type = _emitAnnotatedFunctionType(
ctor.function.functionType.withoutTypeParameters, ctor.annotations,
function: ctor.function, nameType: false, definite: true);
constructors.add(new JS.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(_callHelperStatement('tagComputed(#, () => #.#);',
[className, emitLibraryName(coreTypes.coreLibrary), 'Type']));
}
}
JS.Expression _emitFieldSignature(DartType type,
{List<Expression> metadata, bool isFinal: true}) {
var args = [_emitType(type)];
if (emitMetadata && metadata != null && metadata.isNotEmpty) {
args.add(new JS.ArrayInitializer(
metadata.map(_instantiateAnnotation).toList()));
}
return _callHelper(isFinal ? 'finalFieldType(#)' : 'fieldType(#)', [args]);
}
FunctionType _getMemberRuntimeType(Member member) {
var f = member.function;
if (f == null) {
assert(member is Field);
return new FunctionType([], member.getterType);
}
if (!f.positionalParameters.any(isCovariant) &&
!f.namedParameters.any(isCovariant)) {
return f.functionType;
}
reifyParameter(VariableDeclaration p) =>
isCovariant(p) ? coreTypes.objectClass.thisType : p.type;
reifyNamedParameter(VariableDeclaration p) =>
new NamedType(p.name, reifyParameter(p));
// TODO(jmesserly): do covariant type parameter bounds also need to be
// reified as `Object`?
return new FunctionType(
f.positionalParameters.map(reifyParameter).toList(), f.returnType,
namedParameters: f.namedParameters.map(reifyNamedParameter).toList()
..sort(),
typeParameters: f.functionType.typeParameters,
requiredParameterCount: f.requiredParameterCount);
}
JS.Expression _emitConstructor(Constructor node, List<Field> fields,
bool isCallable, JS.Expression className) {
var params = _emitFormalParameters(node.function);
var body = _withCurrentFunction(
node.function,
() => _superDisallowed(
() => _emitConstructorBody(node, fields, className)));
return _finishConstructorFunction(params, new JS.Block(body), isCallable)
..sourceInformation = _nodeEnd(node.fileEndOffset) ??
_nodeEnd(node.enclosingClass.fileEndOffset);
}
List<JS.Statement> _emitConstructorBody(
Constructor node, List<Field> fields, JS.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.Expression _constructorName(String name) {
if (name == '') {
// Default constructors (factory or not) use `new` as their name.
return _propertyName('new');
}
return _emitStaticMemberName(name);
}
JS.Statement _emitRedirectingConstructor(
RedirectingInitializer node, JS.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)
]);
}
JS.Statement _emitSuperConstructorCallIfNeeded(
Class c, JS.Expression className,
[SuperInitializer superInit]) {
if (c == coreTypes.objectClass) return null;
Constructor ctor;
List<JS.Expression> args;
if (superInit == null) {
ctor = unnamedConstructor(c.superclass);
args = [];
} else {
ctor = superInit.target;
args = _emitArgumentList(superInit.arguments);
}
// 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.Statement _emitSuperConstructorCall(
JS.Expression className, String name, List<JS.Expression> args) {
return js.statement('#.__proto__.#.call(this, #);',
[className, _constructorName(name), args ?? []]);
}
bool _hasUnnamedSuperConstructor(Class c) {
if (c == null) return false;
return _hasUnnamedConstructor(c.superclass) ||
_hasUnnamedConstructor(c.mixedInClass);
}
bool _hasUnnamedConstructor(Class c) {
if (c == null || c == coreTypes.objectClass) return false;
var ctor = unnamedConstructor(c);
if (ctor != null && !ctor.isSynthetic) return true;
if (c.fields.any((f) => !f.isStatic)) return true;
return _hasUnnamedSuperConstructor(c);
}
JS.Expression _finishConstructorFunction(
List<JS.Parameter> params, JS.Block body, bool isCallable) {
// We consider a class callable if it inherits from anything with a `call`
// method. As a result, we can know the callable JS function was created
// at the first constructor that was hit.
if (!isCallable) return new JS.Fun(params, body);
return js.call(r'''function callableClass(#) {
if (typeof this !== "function") {
function self(...args) {
return self.call.apply(self, args);
}
self.__proto__ = this.__proto__;
callableClass.call(self, #);
return self;
}
#
}''', [params, params, body]);
}
/// 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.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.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.Statement.from(body);
}
JS.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 new JS.LiteralNull();
return _annotatedNullCheck(annotations)
? notNull(init)
: _visitExpression(init);
}
JS.Expression notNull(Expression expr) {
if (expr == null) return null;
var jsExpr = _visitExpression(expr);
if (!isNullable(expr)) return jsExpr;
return _callHelper('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) {
return mixin.superclass?.superclass == null &&
mixin.constructors.every((c) => c.isExternal);
}
JS.Statement _addConstructorToClass(
JS.Expression className, String name, JS.Expression jsCtor) {
jsCtor = _defineValueOnClass(className, _constructorName(name), jsCtor);
return js.statement('#.prototype = #.prototype;', [jsCtor, className]);
}
JS.Expression _defineValueOnClass(
JS.Expression className, JS.Expression name, JS.Expression value) {
var args = [className, name, value];
if (name is JS.LiteralString &&
JS.invalidStaticFieldName(name.valueWithoutQuotes)) {
return _callHelper('defineValue(#, #, #)', args);
}
return js.call('#.# = #', args);
}
List<JS.Method> _emitClassMethods(Class c) {
var virtualFields = _classProperties.virtualFields;
var jsMethods = <JS.Method>[];
bool hasJsPeer = findAnnotation(c, isJsPeerInterface) != null;
bool hasIterator = false;
if (c == coreTypes.objectClass) {
// Dart does not use ES6 constructors.
// Add an error to catch any invalid usage.
jsMethods.add(
new JS.Method(_propertyName('constructor'), js.fun(r'''function() {
throw Error("use `new " + #.typeName(#.getReifiedType(this)) +
".new(...)` to create a Dart object");
}''', [_runtimeModule, _runtimeModule])));
}
for (var m in c.fields) {
if (_extensionTypes.isNativeClass(c)) {
jsMethods.addAll(_emitNativeFieldAccessors(m));
continue;
}
if (m.isStatic) continue;
if (virtualFields.containsKey(m)) {
jsMethods.addAll(_emitVirtualFieldAccessor(m));
}
}
var getters = new Map<String, Procedure>();
var setters = new 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;
}
}
for (var m in c.procedures) {
if (m.isForwardingStub) {
// TODO(jmesserly): is there any other kind of forwarding stub?
jsMethods.addAll(_emitCovarianceCheckStub(m));
} else if (m.isFactory) {
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));
}
}
for (Member m in _classProperties.mockMembers.values) {
_addMockMembers(m, c, jsMethods);
}
// 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();
}
/// Emits a method, getter, or setter.
JS.Method _emitMethodDeclaration(Procedure member) {
if (member.isAbstract) {
return null;
}
JS.Fun fn;
if (member.isExternal) {
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 new JS.Method(_declareMemberName(member), fn,
isGetter: member.isGetter,
isSetter: member.isSetter,
isStatic: member.isStatic)
..sourceInformation = _nodeEnd(member.fileEndOffset);
}
JS.Fun _emitNativeFunctionBody(Procedure node) {
String name = getAnnotationName(node, isJSAnnotation) ?? node.name.name;
if (node.isGetter) {
return new JS.Fun([], js.block('{ return this.#; }', [name]));
} else if (node.isSetter) {
var params = _emitFormalParameters(node.function);
return new JS.Fun(
params, js.block('{ this.# = #; }', [name, params.last]));
} else {
return js.fun(
'function (...args) { return this.#.apply(this, args); }', name);
}
}
List<JS.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 [];
var enclosingClass = member.enclosingClass;
var superMember = member.forwardingStubSuperTarget ??
member.forwardingStubInterfaceTarget;
if (superMember == null) return [];
var superSubstition = Substitution.fromSupertype(hierarchy
.getClassAsInstanceOf(enclosingClass, superMember.enclosingClass));
var name = _declareMemberName(member);
if (member.isSetter) {
if (superMember is Field && superMember.isGenericCovariantImpl ||
superMember is Procedure &&
isCovariant(superMember.function.positionalParameters[0])) {
return [];
}
return [
new JS.Method(
name,
js.fun('function(x) { return super.# = #._check(x); }', [
name,
_emitType(superSubstition.substituteType(superMember.setterType))
]),
isSetter: true),
new JS.Method(name, js.fun('function() { return super.#; }', [name]),
isGetter: true)
];
}
assert(!member.isAccessor);
var superMethodType = superSubstition
.substituteType(superMember.function.functionType) as FunctionType;
var function = member.function;
var body = <JS.Statement>[];
var typeParameters = superMethodType.typeParameters;
_emitCovarianceBoundsCheck(typeParameters, body);
var typeFormals = _emitTypeFormals(typeParameters);
var jsParams = new List<JS.Parameter>.from(typeFormals);
var positionalParameters = function.positionalParameters;
for (var i = 0, n = positionalParameters.length; i < n; i++) {
var param = positionalParameters[i];
var jsParam = new JS.Identifier(param.name);
jsParams.add(jsParam);
if (isCovariant(param) &&
!isCovariant(superMember.function.positionalParameters[i])) {
var check = js.call('#._check(#)',
[_emitType(superMethodType.positionalParameters[i]), jsParam]);
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 (isCovariant(param) &&
!isCovariant(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 #) #._check(#.#);', [
name,
namedArgumentTemp,
_emitType(paramType.type),
namedArgumentTemp,
name
]));
}
}
if (namedParameters.isNotEmpty) jsParams.add(namedArgumentTemp);
if (typeFormals.isEmpty) {
body.add(js.statement('return super.#(#);', [name, jsParams]));
} else {
body.add(
js.statement('return super.#(#)(#);', [name, typeFormals, jsParams]));
}
var fn = new JS.Fun(jsParams, new JS.Block(body));
return [new JS.Method(name, fn)];
}
/// Emits a Dart factory constructor to a JS static method.
JS.Method _emitFactoryConstructor(Procedure node) {
if (isUnsupportedFactoryConstructor(node)) return null;
return new JS.Method(
_constructorName(node.name.name),
new JS.Fun(_emitFormalParameters(node.function),
_emitFunctionBody(node.function)),
isStatic: true)
..sourceInformation = _nodeEnd(node.fileEndOffset);
}
/// Emits an expression that lets you access statics on a [type] from code.
///
/// If [nameType] is true, then the type will be named. In addition,
/// if [hoistType] is true, then the named type will be hoisted.
JS.Expression emitConstructorAccess(InterfaceType type) {
return _emitJSInterop(type.classNode) ?? visitInterfaceType(type);
}
/// Given a class C that implements method M from interface I, but does not
/// declare M, this will generate an implementation that forwards to
/// noSuchMethod.
///
/// For example:
///
/// class Cat {
/// bool eatFood(String food) => true;
/// }
/// class MockCat implements Cat {
/// noSuchMethod(Invocation invocation) => 3;
/// }
///
/// It will generate an `eatFood` that looks like:
///
/// eatFood(...args) {
/// return core.bool.as(this.noSuchMethod(
/// new dart.InvocationImpl.new('eatFood', args)));
/// }
///
/// Same technique is applied if interface I has fields, and C doesn't declare
/// neither the fields nor the corresponding getters and setters.
void _addMockMembers(Member member, Class c, List<JS.Method> jsMethods) {
JS.Method implementMockMember(
List<TypeParameter> typeParameters,
List<VariableDeclaration> namedParameters,
ProcedureKind mockMemberKind,
DartType returnType) {
assert(mockMemberKind != ProcedureKind.Factory);
var invocationProps = <JS.Property>[];
addProperty(String name, JS.Expression value) {
invocationProps.add(new JS.Property(js.string(name), value));
}
var args = new JS.TemporaryId('args');
var typeParams = _emitTypeFormals(typeParameters);
var fnArgs = new List<JS.Parameter>.from(typeParams);
JS.Expression positionalArgs;
if (namedParameters.isNotEmpty) {
addProperty(
'namedArguments', _callHelper('extractNamedArgs(#)', [args]));
}
if (mockMemberKind != ProcedureKind.Getter &&
mockMemberKind != ProcedureKind.Setter) {
addProperty('isMethod', js.boolean(true));
fnArgs.add(new JS.RestParameter(args));
positionalArgs = args;
} else {
if (mockMemberKind == ProcedureKind.Getter) {
addProperty('isGetter', js.boolean(true));
positionalArgs = new JS.ArrayInitializer([]);
} else if (mockMemberKind == ProcedureKind.Setter) {
addProperty('isSetter', js.boolean(true));
fnArgs.add(args);
positionalArgs = new JS.ArrayInitializer([args]);
}
}
if (typeParams.isNotEmpty) {
addProperty('typeArguments', new JS.ArrayInitializer(typeParams));
}
var fnBody =
js.call('this.noSuchMethod(new #.InvocationImpl.new(#, #, #))', [
_runtimeModule,
_declareMemberName(member),
positionalArgs,
new JS.ObjectInitializer(invocationProps)
]);
if (!types.isTop(returnType)) {
fnBody = js.call('#._check(#)', [_emitType(returnType), fnBody]);
}
var fn = new JS.Fun(fnArgs, js.block('{ return #; }', [fnBody]),
typeParams: typeParams);
return new JS.Method(
_declareMemberName(member,
useExtension: _extensionTypes.isNativeClass(c)),
fn,
isGetter: mockMemberKind == ProcedureKind.Getter,
isSetter: mockMemberKind == ProcedureKind.Setter,
isStatic: false);
}
if (member is Field) {
jsMethods.add(implementMockMember(
const <TypeParameter>[],
const <VariableDeclaration>[],
ProcedureKind.Getter,
Substitution
.fromSupertype(
hierarchy.getClassAsInstanceOf(c, member.enclosingClass))
.substituteType(member.type)));
if (!member.isFinal) {
jsMethods.add(implementMockMember(
const <TypeParameter>[],
const <VariableDeclaration>[],
ProcedureKind.Setter,
new DynamicType()));
}
} else {
Procedure procedure = member as Procedure;
FunctionNode function = procedure.function;
jsMethods.add(implementMockMember(
function.typeParameters,
function.namedParameters,
procedure.kind,
Substitution
.fromSupertype(
hierarchy.getClassAsInstanceOf(c, member.enclosingClass))
.substituteType(function.returnType)));
}
}
/// 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.Method> _emitVirtualFieldAccessor(Field field) {
var virtualField = _classProperties.virtualFields[field];
var result = <JS.Method>[];
var name = _declareMemberName(field);
var mocks = _classProperties.mockMembers;
if (!mocks.containsKey(field.name.name)) {
var getter = js.fun('function() { return this[#]; }', [virtualField]);
result.add(new JS.Method(name, getter, isGetter: true)
..sourceInformation = _nodeStart(field));
}
if (!mocks.containsKey(field.name.name + '=')) {
var args = field.isFinal
? [new JS.Super(), name]
: [new JS.This(), virtualField];
String jsCode;
if (!field.isFinal && field.isGenericCovariantImpl) {
args.add(_emitType(field.type));
jsCode = 'function(value) { #[#] = #._check(value); }';
} else {
jsCode = 'function(value) { #[#] = value; }';
}
result.add(new JS.Method(name, js.fun(jsCode, args), isSetter: true)
..sourceInformation = _nodeStart(field));
}
return result;
}
/// 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.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.Method>[];
if (field.isStatic) return jsMethods;
var name = getAnnotationName(field, isJSName) ?? field.name.name;
// Generate getter
var fn = new JS.Fun([], js.block('{ return this.#; }', [name]));
var method = new JS.Method(_declareMemberName(field), fn, isGetter: true);
jsMethods.add(method);
// Generate setter
if (!field.isFinal) {
var value = new JS.TemporaryId('value');
fn = new JS.Fun([value], js.block('{ this.# = #; }', [name, value]));
method = new JS.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.Method _emitSuperAccessorWrapper(Procedure method,
Map<String, Procedure> getters, Map<String, Procedure> setters) {
var name = method.name.name;
var memberName = _declareMemberName(method);
if (method.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 new JS.Method(memberName, fn, isSetter: true);
}
} else {
assert(method.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 new JS.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.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, new 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) => getJSExportName(m) == 'Symbol.iterator')) {
return null;
}
// Otherwise, emit the adapter method, which wraps the Dart iterator in
// an ES6 iterator.
return new JS.Method(
js.call('Symbol.iterator'),
js.call('function() { return new #.JsIterator(this.#); }', [
_runtimeModule,
_emitMemberName('iterator', type: iterable.asInterfaceType)
]) as JS.Fun);
}
JS.Expression _instantiateAnnotation(Expression node) =>
_visitExpression(node);
/// Gets the JS peer for this Dart type if any, otherwise null.
///
/// For example for dart:_interceptors `JSArray` this will return "Array",
/// referring to the JavaScript built-in `Array` type.
List<String> _getJSPeerNames(Class c) {
var jsPeerNames = getAnnotationName(
c,
(a) =>
isJsPeerInterface(a) ||
isNativeAnnotation(a) && _extensionTypes.isNativeClass(c));
if (c == coreTypes.objectClass) return ['Object'];
if (jsPeerNames == null) return [];
// Omit the special name "!nonleaf" and any future hacks starting with "!"
var result =
jsPeerNames.split(',').where((peer) => !peer.startsWith("!")).toList();
return result;
}
void _registerExtensionType(
Class c, String jsPeerName, List<JS.Statement> body) {
var className = _emitTopLevelName(c);
if (isPrimitiveType(c.rawType)) {
body.add(_callHelperStatement(
'definePrimitiveHashCode(#.prototype)', className));
}
body.add(_callHelperStatement(
'registerExtension(#, #);', [js.string(jsPeerName), className]));
}
JS.Statement _emitJSType(Class c) {
var jsTypeName = getAnnotationName(c, isJSAnnotation);
if (jsTypeName == null || jsTypeName == c.name) return null;
// We export the JS type as if it was a Dart type. For example this allows
// `dom.InputElement` to actually be HTMLInputElement.
// TODO(jmesserly): if we had the JS name on the Element, we could just
// generate it correctly when we refer to it.
return js.statement('# = #;', [_emitTopLevelName(c), jsTypeName]);
}
void _emitTypedef(Typedef t) {
var savedUri = _currentUri;
_currentUri = t.fileUri;
var body = _callHelper(
'typedef(#, () => #)', [js.string(t.name, "'"), _emitType(t.type)]);
JS.Statement result;
if (t.typeParameters.isNotEmpty) {
result = _defineClassTypeArguments(
t, t.typeParameters, js.statement('const # = #;', [t.name, body]));
} else {
result = js.statement('# = #;', [_emitTopLevelName(t), body]);
}
_currentUri = savedUri;
_moduleItems.add(result);
}
/// Treat dart:_runtime fields as safe to eagerly evaluate.
// TODO(jmesserly): it'd be nice to avoid this special case.
JS.Statement _emitInternalSdkFields(Iterable<Field> fields) {
var lazyFields = <Field>[];
var savedUri = _currentUri;
for (var field in fields) {
// Skip our magic undefined constant.
if (field.name.name == 'undefined') continue;
var init = field.initializer;
if (init == null ||
init is BasicLiteral ||
init is StaticInvocation && isInlineJS(init.target) ||
init is ConstructorInvocation &&
isSdkInternalRuntime(init.target.enclosingLibrary)) {
_currentUri = field.fileUri;
_moduleItems.add(js.statement('# = #;', [
_emitTopLevelName(field),
_visitInitializer(init, field.annotations)
]));
} else {
lazyFields.add(field);
}
}
_currentUri = savedUri;
return _emitLazyFields(emitLibraryName(_currentLibrary), lazyFields);
}
JS.Statement _emitLazyFields(JS.Expression objExpr, Iterable<Field> fields) {
var accessors = <JS.Method>[];
var savedUri = _currentUri;
for (var field in fields) {
_currentUri = field.fileUri;
var name = field.name.name;
var access = _emitStaticMemberName(name);
accessors.add(new JS.Method(access, _emitStaticFieldInitializer(field),
isGetter: true)
..sourceInformation = _hoverComment(
new JS.PropertyAccess(objExpr, access),
field.fileOffset,
name.length));
// TODO(jmesserly): currently uses a dummy setter to indicate writable.
if (!field.isFinal && !field.isConst) {
accessors.add(new JS.Method(access, js.call('function(_) {}') as JS.Fun,
isSetter: true));
}
}
_currentUri = _currentLibrary.fileUri;
_currentUri = savedUri;
return _callHelperStatement('defineLazy(#, { # });', [objExpr, accessors]);
}
JS.Fun _emitStaticFieldInitializer(Field field) {
return new JS.Fun(
[],
new JS.Block(_withLetScope(() => [
new JS.Return(
_visitInitializer(field.initializer, field.annotations))
])));
}
List<JS.Statement> _withLetScope(List<JS.Statement> visitBody()) {
var savedLetVariables = _letVariables;
_letVariables = [];
var body = visitBody();
var letVars = _initLetVariables();
if (letVars != null) body.insert(0, letVars);
_letVariables = savedLetVariables;
return body;
}
JS.ArrowFun _arrowFunctionWithLetScope(JS.Expression visitBody()) {
var savedLetVariables = _letVariables;
_letVariables = [];
var expr = visitBody();
var letVars = _initLetVariables();
_letVariables = savedLetVariables;
return new JS.ArrowFun(
[], letVars == null ? expr : new JS.Block([letVars, expr.toReturn()]));
}
JS.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.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.Expression _emitMemberName(String name,
{DartType type,
bool isStatic: false,
bool useExtension,
NamedNode member}) {
// 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 = getJSExportName(member);
if (runtimeName != null) {
var parts = runtimeName.split('.');
if (parts.length < 2) return _propertyName(runtimeName);
JS.Expression result = new JS.Identifier(parts[0]);
for (int i = 1; i < parts.length; i++) {
result = new JS.PropertyAccess(result, _propertyName(parts[i]));
}
return result;
}
}
if (name.startsWith('_')) {
return _emitPrivateNameSymbol(_currentLibrary, name);
}
useExtension ??= _isSymbolizedMember(type, name);
// TODO(vsm): Do not rename members that conflict with standard JS members
// if we are actually try to access those JS members via interop.
name = JS.memberNameForDartMember(name);
if (useExtension) {
return _getExtensionSymbolInternal(name);
}
return _propertyName(name);
}
/// This is an internal method used by [_emitMemberName] and the
/// optimized `dart:_runtime extensionSymbol` builtin to get the symbol
/// for `dartx.<name>`.
///
/// Do not call this directly; you want [_emitMemberName], which knows how to
/// handle the many details involved in naming.
JS.TemporaryId _getExtensionSymbolInternal(String name) {
return _extensionSymbols.putIfAbsent(
name,
() => new JS.TemporaryId(
'\$${JS.friendlyNameForDartOperator[name] ?? 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(DartType type, String name) {
while (type is TypeParameterType) {
type = (type as TypeParameterType).bound;
}
if (type == null ||
type == const DynamicType() ||
type == coreTypes.objectClass) {
return isObjectMember(name);
} else if (type is InterfaceType) {
var c = getImplementationClass(type) ?? type.classNode;
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 || member is Procedure && member.isExternal) {
var jsName = getAnnotationName(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);
} else if (type is FunctionType) {
return true;
}
return false;
}
var _forwardingCache = new 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, new Name(name)) ??
hierarchy.getDispatchTarget(c, new Name(name), setter: true));
}
JS.TemporaryId _emitPrivateNameSymbol(Library library, String name) {
return _privateNames
.putIfAbsent(library, () => new HashMap())
.putIfAbsent(name, () {
var id = new JS.TemporaryId(name);
_moduleItems.add(
js.statement('const # = Symbol(#);', [id, js.string(id.name, "'")]));
return id;
});
}
JS.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.Expression _emitJSInteropStaticMemberName(NamedNode n) {
if (!isJSElement(n)) return null;
var name = getAnnotationName(n, isPublicJSAnnotation);
if (name != null) {
if (name.contains('.')) {
throw new 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.PropertyAccess _emitTopLevelNameNoInterop(NamedNode n,
{String suffix: ''}) {
var name = getJSExportName(n) ?? getTopLevelName(n);
return new JS.PropertyAccess(
emitLibraryName(getLibrary(n)), _propertyName(name + suffix));
}
String _getJSNameWithoutGlobal(NamedNode n) {
if (!isJSElement(n)) return null;
var libraryJSName = getAnnotationName(getLibrary(n), isPublicJSAnnotation);
var jsName =
getAnnotationName(n, isPublicJSAnnotation) ?? getTopLevelName(n);
return libraryJSName != null ? '$libraryJSName.$jsName' : jsName;
}
JS.PropertyAccess _emitJSInterop(NamedNode n) {
var jsName = _getJSNameWithoutGlobal(n);
if (jsName == null) return null;
return _emitJSInteropForGlobal(jsName);
}
JS.PropertyAccess _emitJSInteropForGlobal(String name) {
var parts = name.split('.');
if (parts.isEmpty) parts = [''];
JS.PropertyAccess access;
for (var part in parts) {
access = new JS.PropertyAccess(
access ?? _callHelper('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(_callHelperStatement('copyProperties(#, { # });', [
emitLibraryName(_currentLibrary),
accessors.map(_emitLibraryAccessor).toList()
]));
}
JS.Method _emitLibraryAccessor(Procedure node) {
var savedUri = _currentUri;
_currentUri = node.fileUri;
var name = node.name.name;
var result = new JS.Method(
_propertyName(name), _emitFunction(node.function, node.name.name),
isGetter: node.isGetter, isSetter: node.isSetter)
..sourceInformation = _nodeEnd(node.fileEndOffset);
_currentUri = savedUri;
return result;
}
JS.Statement _emitLibraryFunction(Procedure p) {
var savedUri = _currentUri;
_currentUri = p.fileUri;
var body = <JS.Statement>[];
var fn = _emitFunction(p.function, p.name.name)
..sourceInformation = _nodeEnd(p.fileEndOffset);
if (_currentLibrary.importUri.scheme == 'dart' &&
_isInlineJSFunction(p.function.body)) {
fn = JS.simplifyPassThroughArrowFunCallBody(fn);
}
var nameExpr = _emitTopLevelName(p);
body.add(js.statement('# = #', [nameExpr, fn]));
if (!isSdkInternalRuntime(_currentLibrary)) {
body.add(
_emitFunctionTagged(nameExpr, p.function.functionType, topLevel: true)
.toStatement());
}
_currentUri = savedUri;
return JS.Statement.from(body);
}
JS.Expression _emitFunctionTagged(JS.Expression fn, FunctionType type,
{bool topLevel: false}) {
var lazy = topLevel && !_typeIsLoaded(type);
var typeRep = visitFunctionType(type);
return _callHelper(lazy ? 'lazyFn(#, () => #)' : 'fn(#, #)', [fn, typeRep]);
}
bool _typeIsLoaded(DartType type) {
if (type is InterfaceType) {
return !_pendingClasses.contains(type.classNode) &&
type.typeArguments.every(_typeIsLoaded);
}
if (type is FunctionType) {
return (_typeIsLoaded(type.returnType) &&
type.positionalParameters.every(_typeIsLoaded) &&
type.namedParameters.every((n) => _typeIsLoaded(n.type)));
}
if (type is TypedefType) {
return type.typeArguments.every(_typeIsLoaded);
}
return true;
}
/// Emits a Dart [type] into code.
JS.Expression _emitType(DartType type) => type.accept(this) as JS.Expression;
JS.Expression _emitInvalidNode(Node node, [String message = '']) {
if (message.isNotEmpty) message += ' ';
return _callHelper('throwUnimplementedError(#)',
[js.escapedString('node <${node.runtimeType}> $message`$node`')]);
}
JS.Expression _nameType(DartType type, JS.Expression typeRep) =>
_currentFunction != null ? _typeTable.nameType(type, typeRep) : typeRep;
@override
defaultDartType(type) => _emitInvalidNode(type);
@override
visitInvalidType(type) => defaultDartType(type);
@override
visitDynamicType(type) => _callHelper('dynamic');
@override
visitVoidType(type) => _callHelper('void');
@override
visitBottomType(type) => _callHelper('bottom');
@override
visitInterfaceType(type, {bool lowerGeneric: false}) {
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 _callHelper(
'anonymousJSType(#)', js.escapedString(getLocalClassName(c)));
}
var jsName = _getJSNameWithoutGlobal(c);
if (jsName != null) {
return _callHelper('lazyJSType(() => #, #)',
[_emitJSInteropForGlobal(jsName), js.escapedString(jsName)]);
}
var args = type.typeArguments;
Iterable<JS.Expression> jsArgs = null;
if (args.any((a) => a != const DynamicType())) {
jsArgs = args.map(_emitType);
} else if (lowerGeneric) {
jsArgs = [];
}
if (jsArgs != null) {
return _nameType(type, _emitGenericClassType(type, jsArgs));
}
return _emitTopLevelNameNoInterop(type.classNode);
}
JS.Expression _emitGenericClassType(
InterfaceType t, Iterable<JS.Expression> typeArgs) {
var genericName = _emitTopLevelNameNoInterop(t.classNode, suffix: '\$');
return js.call('#(#)', [genericName, typeArgs]);
}
@override
visitVectorType(type) => defaultDartType(type);
@override
visitFunctionType(type, {bool lowerTypedef: false, FunctionNode function}) {
var requiredTypes =
type.positionalParameters.take(type.requiredParameterCount).toList();
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);
List<JS.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);
typeParts = [rt, ra, oa];
} else {
typeParts = [rt, ra];
}
var typeFormals = type.typeParameters;
String helperCall;
if (typeFormals.isNotEmpty) {
var tf = _emitTypeFormals(typeFormals);
addTypeFormalsAsParameters(List<JS.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(#)';
// If any explicit bounds were passed, emit them.
if (typeFormals.any((t) => t.bound != null)) {
var bounds = typeFormals.map((t) => _emitType(t.bound)).toList();
typeParts.add(addTypeFormalsAsParameters(bounds));
}
} else {
helperCall = 'fnType(#)';
}
return _nameType(type, _callHelper(helperCall, [typeParts]));
}
JS.Expression _emitAnnotatedFunctionType(
FunctionType type, List<Expression> metadata,
{FunctionNode function, bool nameType: true, bool definite: false}) {
var result = visitFunctionType(type, function: function);
return _emitAnnotatedResult(result, metadata);
}
/// Emits an expression that lets you access statics on a [type] from code.
JS.Expression _emitConstructorAccess(InterfaceType type) {
return _emitJSInterop(type.classNode) ?? _emitType(type);
}
JS.Expression _emitConstructorName(InterfaceType type, Member c) {
return _emitJSInterop(type.classNode) ??
new JS.PropertyAccess(
_emitConstructorAccess(type), _constructorName(c.name.name));
}
/// Emits an expression that lets you access statics on an [element] from code.
JS.Expression _emitStaticAccess(Class c) {
_declareBeforeUse(c);
return _emitTopLevelName(c);
}
// Wrap a result - usually a type - with its metadata. The runtime is
// responsible for unpacking this.
JS.Expression _emitAnnotatedResult(
JS.Expression result, List<Expression> metadata) {
if (emitMetadata && metadata != null && metadata.isNotEmpty) {
result = new JS.ArrayInitializer(
[result]..addAll(metadata.map(_instantiateAnnotation)));
}
return result;
}
JS.ObjectInitializer _emitTypeProperties(Iterable<NamedType> types) {
return new JS.ObjectInitializer(types
.map((t) => new JS.Property(_propertyName(t.name), _emitType(t.type)))
.toList());
}
JS.ArrayInitializer _emitTypeNames(
List<DartType> types, List<VariableDeclaration> parameters) {
var result = <JS.Expression>[];
for (int i = 0; i < types.length; ++i) {
var metadata = parameters != null ? parameters[i].annotations : null;
result.add(_emitAnnotatedResult(_emitType(types[i]), metadata));
}
return new JS.ArrayInitializer(result);
}
@override
visitTypeParameterType(type) => _emitTypeParameter(type.parameter);
JS.Identifier _emitTypeParameter(TypeParameter t) {
_typeParamInConst?.add(t);
return new JS.Identifier(getTypeParameterName(t));
}
@override
visitTypedefType(type, {bool lowerGeneric: false}) {
var args = type.typeArguments;
List<JS.Expression> jsArgs = null;
if (args.any((a) => a != const DynamicType())) {
jsArgs = args.map(_emitType).toList();
} else if (lowerGeneric) {
jsArgs = [];
}
if (jsArgs != null) {
var genericName =
_emitTopLevelNameNoInterop(type.typedefNode, suffix: '\$');
return _nameType(type, new JS.Call(genericName, jsArgs));
}
return _emitTopLevelNameNoInterop(type.typedefNode);
}
JS.Fun _emitFunction(FunctionNode f, String name) {
// normal function (sync), vs (sync*, async, async*)
var isSync = f.asyncMarker == AsyncMarker.Sync;
var formals = _emitFormalParameters(f);
var typeFormals = _emitTypeFormals(f.typeParameters);
formals.insertAll(0, typeFormals);
JS.Block code = isSync
? _emitFunctionBody(f)
: new JS.Block([
_emitGeneratorFunction(f, name).toReturn()
..sourceInformation = _nodeStart(f)
]);
if (name != null && formals.isNotEmpty) {
if (name == '[]=') {
// []= methods need to return the value. We could also address this at
// call sites, but it's cleaner to instead transform the operator method.
code = JS.alwaysReturnLastParameter(code, formals.last);
} else if (name == '==' && _currentLibrary.importUri.scheme != 'dart') {
// In Dart `operator ==` methods are not called with a null argument.
// This is handled before calling them. For performance reasons, we push
// this check inside the method, to simplify our `equals` helper.
//
// TODO(jmesserly): in most cases this check is not necessary, because
// the Dart code already handles it (typically by an `is` check).
// Eliminate it when possible.
code = new JS.Block([
js.statement('if (# == null) return false;', [formals.first]),
code
]);
}
}
return new JS.Fun(formals, code);
}
// TODO(jmesserly): rename _emitParameters
List<JS.Parameter> _emitFormalParameters(FunctionNode f) {
var result = f.positionalParameters.map(_emitVariableDef).toList();
if (f.namedParameters.isNotEmpty) result.add(namedArgumentTemp);
return result;
}
void _emitVirtualFieldSymbols(Class c, List<JS.Statement> body) {
_classProperties.virtualFields.forEach((field, virtualField) {
body.add(js.statement('const # = Symbol(#);', [
virtualField,
js.string('${getLocalClassName(c)}.${field.name.name}')
]));
});
}
List<JS.Identifier> _emitTypeFormals(List<TypeParameter> typeFormals) {
return typeFormals
.map((t) => new JS.Identifier(getTypeParameterName(t)))
.toList();
}