blob: cee5682e6429e5b4bfa2ae40a18b94c15784732d [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:collection' show HashMap, HashSet;
import 'dart:math' show min, max;
import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
import 'package:analyzer/dart/ast/standard_ast_factory.dart';
import 'package:analyzer/dart/ast/standard_resolution_map.dart';
import 'package:analyzer/dart/ast/token.dart' show Token, TokenType;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/token.dart' show StringToken;
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/generated/constant.dart'
show DartObject, DartObjectImpl;
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
import 'package:analyzer/src/generated/resolver.dart'
show TypeProvider, NamespaceBuilder;
import 'package:analyzer/src/generated/type_system.dart'
show StrongTypeSystemImpl;
import 'package:analyzer/src/summary/idl.dart' show UnlinkedUnit;
import 'package:analyzer/src/summary/link.dart' as summary_link;
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/summary/summarize_ast.dart'
show serializeAstUnlinked;
import 'package:analyzer/src/summary/summarize_elements.dart'
show PackageBundleAssembler;
import 'package:analyzer/src/summary/summary_sdk.dart';
import 'package:analyzer/src/task/strong/ast_properties.dart';
import 'package:path/path.dart' as path;
import 'package:source_span/source_span.dart' show SourceLocation;
import '../compiler/js_metalet.dart' as JS;
import '../compiler/js_names.dart' as JS;
import '../compiler/js_utils.dart' as JS;
import '../compiler/module_builder.dart' show pathToJSIdentifier;
import '../compiler/shared_compiler.dart';
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 'ast_builder.dart';
import 'element_helpers.dart';
import 'error_helpers.dart';
import 'extension_types.dart' show ExtensionTypeSet;
import 'js_interop.dart';
import 'js_typerep.dart';
import 'module_compiler.dart' show BuildUnit, CompilerOptions, JSModuleFile;
import 'nullable_type_inference.dart' show NullableTypeInference;
import 'property_model.dart';
import 'reify_coercions.dart' show CoercionReifier;
import 'side_effect_analysis.dart'
show ConstFieldVisitor, isStateless, isPotentiallyMutated;
import 'type_utilities.dart';
/// The code generator for Dart Dev Compiler.
///
/// Takes as input resolved Dart ASTs for every compilation unit in every
/// library in the module. Produces a single JavaScript AST for the module as
// output, along with its source map.
///
/// This class attempts to preserve identifier names and structure of the input
/// Dart code, whenever this is possible to do in the generated code.
///
// TODO(jmesserly): we should use separate visitors for statements and
// expressions. Declarations are handled directly, and many minor component
// AST nodes aren't visited, so the visitor pattern isn't helping except for
// expressions (which result in JS.Expression) and statements
// (which result in (JS.Statement).
class CodeGenerator extends Object
with NullableTypeInference, SharedCompiler<LibraryElement>
implements AstVisitor<JS.Node> {
final AnalysisContext context;
final SummaryDataStore summaryData;
final CompilerOptions options;
final StrongTypeSystemImpl rules;
/// Errors that were produced during compilation, if any.
final List<AnalysisError> errors;
JSTypeRep jsTypeRep;
/// 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 = Map<LibraryElement, JS.Identifier>();
/// Imported libraries, and the temporaries used to refer to them.
final _imports = Map<LibraryElement, JS.TemporaryId>();
/// The list of dart:_runtime SDK functions; these are assumed by other code
/// in the SDK to be generated before anything else.
final _internalSdkFunctions = <JS.ModuleItem>[];
/// Table of named and possibly hoisted types.
TypeTable _typeTable;
/// The global extension type table.
final ExtensionTypeSet _extensionTypes;
/// The variable for the target of the current `..` cascade expression.
///
/// Usually a [SimpleIdentifier], but it can also be other expressions
/// that are safe to evaluate multiple times, such as `this`.
Expression _cascadeTarget;
/// The variable for the current catch clause
SimpleIdentifier _catchParameter;
/// In an async* function, this represents the stream controller parameter.
JS.TemporaryId _asyncStarController;
final _initializingFormalTemps = HashMap<ParameterElement, JS.TemporaryId>();
JS.Identifier _extensionSymbolsModule;
final _extensionSymbols = Map<String, JS.TemporaryId>();
/// The type provider from the current Analysis [context].
final TypeProvider types;
final LibraryElement coreLibrary;
final LibraryElement dartJSLibrary;
/// The dart:async `StreamIterator<T>` type.
final InterfaceType _asyncStreamIterator;
/// The dart:core `identical` element.
final FunctionElement _coreIdentical;
/// Classes and types defined in the SDK.
final ClassElement _jsArray;
final ClassElement boolClass;
final ClassElement intClass;
final ClassElement doubleClass;
final ClassElement interceptorClass;
final ClassElement nullClass;
final ClassElement numClass;
final ClassElement objectClass;
final ClassElement stringClass;
final ClassElement functionClass;
final ClassElement privateSymbolClass;
final InterfaceType linkedHashMapImplType;
final InterfaceType identityHashMapImplType;
final InterfaceType linkedHashSetImplType;
final InterfaceType identityHashSetImplType;
final InterfaceType syncIterableType;
final InterfaceType asyncStarImplType;
ConstFieldVisitor _constants;
/// The current function body being compiled.
FunctionBody _currentFunction;
Map<TypeDefiningElement, AstNode> _declarationNodes;
/// The class that's currently emitting top-level (module-level) JS code.
///
/// This is primarily used to forward declare classes so they are available
/// to JS class `extends`.
///
/// This is not set when inside method bodies, because they are run after we
/// load modules, so they can freely access all classes.
TypeDefiningElement _topLevelClass;
/// The current element being loaded.
/// We can use this to determine if we're loading top-level code or not:
///
/// _currentElement == _topLevelClass
///
/// This is also used to find the current compilation unit for emitting source
/// mappings.
Element _currentElement;
final _deferredProperties = HashMap<PropertyAccessorElement, JS.Method>();
BuildUnit _buildUnit;
String _libraryRoot;
bool _superAllowed = true;
final _superHelpers = Map<String, JS.Method>();
List<TypeParameterType> _typeParamInConst;
/// Whether we are currently generating code for the body of a `JS()` call.
bool _isInForeignJS = false;
/// 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 _usedCovariantPrivateMembers = HashSet<ExecutableElement>();
CodeGenerator(AnalysisContext c, this.summaryData, this.options,
this._extensionTypes, this.errors)
: context = c,
rules = StrongTypeSystemImpl(c.typeProvider),
types = c.typeProvider,
_asyncStreamIterator = getClass(c, 'dart:async', 'StreamIterator').type,
_coreIdentical = _getLibrary(c, 'dart:core')
.publicNamespace
.get('identical') as FunctionElement,
_jsArray = getClass(c, 'dart:_interceptors', 'JSArray'),
interceptorClass = getClass(c, 'dart:_interceptors', 'Interceptor'),
coreLibrary = _getLibrary(c, 'dart:core'),
boolClass = getClass(c, 'dart:core', 'bool'),
intClass = getClass(c, 'dart:core', 'int'),
doubleClass = getClass(c, 'dart:core', 'double'),
numClass = getClass(c, 'dart:core', 'num'),
nullClass = getClass(c, 'dart:core', 'Null'),
objectClass = getClass(c, 'dart:core', 'Object'),
stringClass = getClass(c, 'dart:core', 'String'),
functionClass = getClass(c, 'dart:core', 'Function'),
privateSymbolClass = getClass(c, 'dart:_js_helper', 'PrivateSymbol'),
linkedHashMapImplType =
getClass(c, 'dart:_js_helper', 'LinkedMap').type,
identityHashMapImplType =
getClass(c, 'dart:_js_helper', 'IdentityMap').type,
linkedHashSetImplType = getClass(c, 'dart:collection', '_HashSet').type,
identityHashSetImplType =
getClass(c, 'dart:collection', '_IdentityHashSet').type,
syncIterableType = getClass(c, 'dart:_js_helper', 'SyncIterable').type,
asyncStarImplType = getClass(c, 'dart:async', '_AsyncStarImpl').type,
dartJSLibrary = _getLibrary(c, 'dart:js') {
jsTypeRep = JSTypeRep(rules, c);
}
LibraryElement get currentLibrary => _currentElement.library;
Uri get currentLibraryUri => _currentElement.librarySource.uri;
CompilationUnitElement get _currentCompilationUnit {
for (var e = _currentElement;; e = e.enclosingElement) {
if (e is CompilationUnitElement) return e;
}
}
/// The main entry point to JavaScript code generation.
///
/// Takes the metadata for the build unit, as well as resolved trees and
/// errors, and computes the output module code and optionally the source map.
JSModuleFile compile(BuildUnit unit, List<CompilationUnit> compilationUnits) {
_buildUnit = unit;
_libraryRoot = _buildUnit.libraryRoot;
if (!_libraryRoot.endsWith(path.separator)) {
_libraryRoot += path.separator;
}
invalidModule() =>
JSModuleFile.invalid(unit.name, formatErrors(context, errors), options);
if (!options.unsafeForceCompile && errors.any(_isFatalError)) {
return invalidModule();
}
try {
var module = _emitModule(compilationUnits, unit.name);
if (!options.unsafeForceCompile && errors.any(_isFatalError)) {
return invalidModule();
}
var dartApiSummary = _summarizeModule(compilationUnits);
return JSModuleFile(unit.name, formatErrors(context, errors), options,
module, dartApiSummary);
} catch (e) {
if (errors.any(_isFatalError)) {
// Force compilation failed. Suppress the exception and report
// the static errors instead.
assert(options.unsafeForceCompile);
return invalidModule();
}
rethrow;
}
}
bool _isFatalError(AnalysisError e) {
if (errorSeverity(context, e) != ErrorSeverity.ERROR) return false;
// These errors are not fatal in the REPL compile mode as we
// allow access to private members across library boundaries
// and those accesses will show up as undefined members unless
// additional analyzer changes are made to support them.
// TODO(jacobr): consider checking that the identifier name
// referenced by the error is private.
return !options.replCompile ||
(e.errorCode != StaticTypeWarningCode.UNDEFINED_GETTER &&
e.errorCode != StaticTypeWarningCode.UNDEFINED_SETTER &&
e.errorCode != StaticTypeWarningCode.UNDEFINED_METHOD);
}
List<int> _summarizeModule(List<CompilationUnit> units) {
if (!options.summarizeApi) return null;
if (!units.any((u) => u.declaredElement.source.isInSystemLibrary)) {
var sdk = context.sourceFactory.dartSdk;
summaryData.addBundle(
null,
sdk is SummaryBasedDartSdk
? sdk.bundle
: (sdk as FolderBasedDartSdk).getSummarySdkBundle());
}
var assembler = PackageBundleAssembler();
var uriToUnit = Map<String, UnlinkedUnit>.fromIterables(
units.map((u) => u.declaredElement.source.uri.toString()),
units.map((unit) {
var unlinked = serializeAstUnlinked(unit);
assembler.addUnlinkedUnit(unit.declaredElement.source, unlinked);
return unlinked;
}));
summary_link
.link(
uriToUnit.keys.toSet(),
(uri) => summaryData.linkedMap[uri],
(uri) => summaryData.unlinkedMap[uri] ?? uriToUnit[uri],
context.declaredVariables.get)
.forEach(assembler.addLinkedLibrary);
var bundle = assembler.assemble();
// Preserve only API-level information in the summary.
bundle.flushInformative();
return bundle.toBuffer();
}
JS.Program _emitModule(List<CompilationUnit> compilationUnits, String name) {
if (moduleItems.isNotEmpty) {
throw StateError('Can only call emitModule once.');
}
for (var unit in compilationUnits) {
_usedCovariantPrivateMembers.addAll(getCovariantPrivateMembers(unit));
}
// Transform the AST to make coercions explicit.
compilationUnits = CoercionReifier.reify(compilationUnits);
var items = <JS.ModuleItem>[];
var root = JS.Identifier('_root');
items.add(js.statement('const # = Object.create(null)', [root]));
var isBuildingSdk = compilationUnits
.any((u) => isSdkInternalRuntime(u.declaredElement.library));
if (isBuildingSdk) {
// 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 = JS.Identifier('dart');
_extensionSymbolsModule = JS.Identifier('dartx');
} else {
// Otherwise allow these to be renamed so users can write them.
runtimeModule = JS.TemporaryId('dart');
_extensionSymbolsModule = JS.TemporaryId('dartx');
}
_typeTable = TypeTable(runtimeModule);
// Initialize our library variables.
var exports = <JS.NameSpecifier>[];
void emitLibrary(JS.Identifier id) {
items.add(js.statement('const # = Object.create(#)', [id, root]));
exports.add(JS.NameSpecifier(id));
}
for (var unit in compilationUnits) {
var library = unit.declaredElement.library;
if (unit.declaredElement != library.definingCompilationUnit) continue;
var libraryTemp = isSdkInternalRuntime(library)
? runtimeModule
: JS.TemporaryId(jsLibraryName(_libraryRoot, 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 (isBuildingSdk) emitLibrary(_extensionSymbolsModule);
items.add(JS.ExportDeclaration(JS.ExportClause(exports)));
// Collect all class/type Element -> Node mappings
// in case we need to forward declare any classes.
_declarationNodes = HashMap<TypeDefiningElement, AstNode>.identity();
for (var unit in compilationUnits) {
for (var declaration in unit.declarations) {
var element = declaration.declaredElement;
if (element is TypeDefiningElement) {
_declarationNodes[element] = declaration;
}
}
}
if (compilationUnits.isNotEmpty) {
_constants = ConstFieldVisitor(context,
dummySource: resolutionMap
.elementDeclaredByCompilationUnit(compilationUnits.first)
.source);
}
// Add implicit dart:core dependency so it is first.
emitLibraryName(coreLibrary);
// Visit each compilation unit and emit its code.
//
// NOTE: declarations 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.
compilationUnits.forEach(visitCompilationUnit);
assert(_deferredProperties.isEmpty);
// Visit directives (for exports)
compilationUnits.forEach(_emitExportDirectives);
// Declare imports
_finishImports(items);
// Initialize extension symbols
_extensionSymbols.forEach((name, id) {
JS.Expression value =
JS.PropertyAccess(_extensionSymbolsModule, _propertyName(name));
if (isBuildingSdk) {
value = js.call('# = Symbol(#)', [value, js.string("dartx.$name")]);
}
items.add(js.statement('const # = #;', [id, value]));
});
_emitDebuggerExtensionInfo(name);
// Discharge the type table cache variables and
// hoisted definitions.
items.addAll(_typeTable.discharge());
items.addAll(_internalSdkFunctions);
// Add the module's code (produced by visiting compilation units, above)
_copyAndFlattenBlocks(items, moduleItems);
// Build the module.
return JS.Program(items, name: _buildUnit.name);
}
void _emitDebuggerExtensionInfo(String name) {
var properties = <JS.Property>[];
_libraries.forEach((library, value) {
// TODO(jacobr): we could specify a short library name instead of the
// full library uri if we wanted to save space.
properties.add(JS.Property(
js.escapedString(jsLibraryDebuggerName(_libraryRoot, library)),
value));
});
// Track the module name for each library in the module.
// This data is only required for debugging.
moduleItems.add(js
.statement('#.trackLibraries(#, #, ${JSModuleFile.sourceMapHoleID});', [
runtimeModule,
js.string(name),
JS.ObjectInitializer(properties, multiline: true)
]));
}
/// If [e] is a property accessor element, this returns the
/// (possibly synthetic) field that corresponds to it, otherwise returns [e].
Element _getNonAccessorElement(Element e) =>
e is PropertyAccessorElement ? e.variable : e;
/// Returns the name of [e] but removes trailing `=` from setter names.
// TODO(jmesserly): it would be nice if Analyzer had something like this.
// `Element.displayName` is close, but it also normalizes operator names in
// a way we don't want.
String _getElementName(Element e) => _getNonAccessorElement(e).name;
bool _isExternal(Element e) =>
e is ExecutableElement && e.isExternal ||
e is PropertyInducingElement &&
((e.getter?.isExternal ?? false) || (e.setter?.isExternal ?? false));
/// Returns true iff this element is a JS interop member.
///
/// The element's library must have `@JS(...)` annotation from `package:js`.
///
/// If the element is a class, it must also be marked with `@JS`. Other
/// elements, such as class members and top-level functions/accessors, should
/// be marked `external`.
//
// TODO(jmesserly): if the element is a member, shouldn't we check that the
// class is a JS interop class?
bool _usesJSInterop(Element e) =>
e?.library != null &&
_hasJSInteropAnnotation(e.library) &&
(_isExternal(e) || e is ClassElement && _hasJSInteropAnnotation(e));
String _getJSNameWithoutGlobal(Element e) {
if (!_usesJSInterop(e)) return null;
var libraryJSName = getAnnotationName(e.library, isPublicJSAnnotation);
var jsName =
getAnnotationName(e, isPublicJSAnnotation) ?? _getElementName(e);
return libraryJSName != null ? '$libraryJSName.$jsName' : jsName;
}
JS.PropertyAccess _emitJSInterop(Element e) {
var jsName = _getJSNameWithoutGlobal(e);
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 = JS.PropertyAccess(
access ?? runtimeCall('global'), js.escapedString(part, "'"));
}
return access;
}
JS.Expression _emitJSInteropStaticMemberName(Element e) {
if (!_usesJSInterop(e)) return null;
var name = getAnnotationName(e, 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 = _getElementName(e);
}
return js.escapedString(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);
}
}
}
String _libraryToModule(LibraryElement library) {
assert(!_libraries.containsKey(library));
var source = library.source;
// TODO(jmesserly): we need to split out HTML.
if (source.uri.scheme == 'dart') {
return JS.dartSdkModule;
}
var summaryPath = (source as InSummarySource).summaryPath;
var moduleName = options.summaryModules[summaryPath];
if (moduleName == null) {
throw StateError('Could not find module name for library "$library" '
'from summary path "$summaryPath".');
}
return moduleName;
}
void _finishImports(List<JS.ModuleItem> items) {
var modules = Map<String, List<LibraryElement>>();
for (var import in _imports.keys) {
modules.putIfAbsent(_libraryToModule(import), () => []).add(import);
}
String coreModuleName;
if (!_libraries.containsKey(coreLibrary)) {
coreModuleName = _libraryToModule(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) => JS.NameSpecifier(_imports[l])).toList();
if (module == coreModuleName) {
imports.add(JS.NameSpecifier(runtimeModule));
imports.add(JS.NameSpecifier(_extensionSymbolsModule));
}
items.add(JS.ImportDeclaration(
namedImports: imports, from: js.string(module, "'")));
});
}
/// 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 _emitTypeDeclaration(TypeDefiningElement e) {
var node = _declarationNodes.remove(e);
if (node == null) return; // not from this module or already loaded.
var savedElement = _currentElement;
_currentElement = e;
// TODO(jmesserly): this is not really the right place for this.
// Ideally we do this per function body.
//
// We'll need to be consistent about when we're generating functions, and
// only run this on the outermost function, and not any closures.
inferNullableTypes(node);
moduleItems.add(node.accept(this) as JS.ModuleItem);
_currentElement = savedElement;
}
/// To emit top-level module items, 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(TypeDefiningElement e) {
if (e == null) return;
if (_topLevelClass != null && identical(_currentElement, _topLevelClass)) {
// If the item is from our library, try to emit it now.
_emitTypeDeclaration(e);
}
}
@override
visitCompilationUnit(CompilationUnit unit) {
// NOTE: this method isn't the right place to initialize
// per-compilation-unit state. Declarations can be visited out of order,
// this is only to catch things that haven't been emitted yet.
//
// See _emitTypeDeclaration.
var savedElement = _currentElement;
_currentElement = unit.declaredElement;
var isInternalSdk = isSdkInternalRuntime(currentLibrary);
List<VariableDeclaration> fields;
for (var declaration in unit.declarations) {
if (declaration is TopLevelVariableDeclaration) {
inferNullableTypes(declaration);
var variables = declaration.variables.variables;
var lazyFields =
isInternalSdk ? _emitInternalSdkFields(variables) : variables;
if (lazyFields.isNotEmpty) {
(fields ??= []).addAll(lazyFields);
}
continue;
}
if (fields != null) {
_emitTopLevelFields(fields);
fields = null;
}
var element = declaration.declaredElement;
if (element is TypeDefiningElement) {
_emitTypeDeclaration(element);
continue;
}
inferNullableTypes(declaration);
var item = declaration.accept(this) as JS.ModuleItem;
if (isInternalSdk && element is FunctionElement) {
_internalSdkFunctions.add(item);
} else {
moduleItems.add(item);
}
}
if (fields != null) _emitTopLevelFields(fields);
_currentElement = savedElement;
return null;
}
void _emitExportDirectives(CompilationUnit unit) {
var savedElement = _currentElement;
for (var directive in unit.directives) {
_currentElement = directive.element;
directive.accept(this);
}
_currentElement = savedElement;
}
@override
visitLibraryDirective(LibraryDirective node) => null;
@override
visitImportDirective(ImportDirective node) {
// We don't handle imports here.
//
// Instead, we collect imports whenever we need to generate a reference
// to another library. This has the effect of collecting the actually used
// imports.
//
// TODO(jmesserly): if this is a prefixed import, consider adding the prefix
// as an alias?
return null;
}
@override
visitPartDirective(PartDirective node) => null;
@override
visitPartOfDirective(PartOfDirective node) => null;
@override
visitExportDirective(ExportDirective node) {
ExportElement element = node.element;
var currentLibrary = element.library;
var currentNames = currentLibrary.publicNamespace.definedNames;
var exportedNames =
NamespaceBuilder().createExportNamespaceForDirective(element);
// 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 export = exportedNames.get('main');
if (export is FunctionElement) {
// Don't allow redefining names from this library.
if (currentNames.containsKey(export.name)) return null;
var name = _emitTopLevelName(export);
moduleItems.add(js.statement(
'#.# = #;', [emitLibraryName(currentLibrary), name.selector, name]));
}
return null;
}
@override
visitAsExpression(AsExpression node) {
Expression fromExpr = node.expression;
var from = getStaticType(fromExpr);
var to = node.type.type;
var jsFrom = _visitExpression(fromExpr);
// If the check was put here by static analysis to ensure soundness, we
// can't skip it. This happens because of unsound covariant generics:
//
// typedef F<T>(T t);
// class C<T> {
// F<T> f;
// add(T t) {
// // required check `t as T`
// }
// }
// main() {
// C<Object> c = new C<int>()..f = (int x) => x.isEven;
// c.f('hi'); // required check `c.f as F<Object>`
// c.add('hi);
// }
//
// NOTE: due to implementation details, we do not currently reify the the
// `C<T>.add` check in CoercionReifier, so it does not reach this point;
// rather we check for it explicitly when emitting methods and fields.
// However we do reify the `c.f` check, so we must not eliminate it.
var isImplicit = CoercionReifier.isImplicit(node);
if (!isImplicit && rules.isSubtypeOf(from, to)) return jsFrom;
// Handle implicit tearoff of the `.call` method.
//
// TODO(jmesserly): this is handled here rather than in CoercionReifier, in
// hopes that we can remove that extra visit and tree cloning step (which
// has been error prone because AstCloner isn't used much, and making
// synthetic resolved Analyzer ASTs is difficult).
if (isImplicit &&
from is InterfaceType &&
rules.acceptsFunctionType(to) &&
!_usesJSInterop(from.element)) {
// Dart allows an implicit coercion from an interface type to a function
// type, via a tearoff of the `call` method.
var callMethod = from.lookUpInheritedMethod('call');
if (callMethod != null) {
var callName = _emitMemberName('call', type: from, element: callMethod);
var callTearoff = runtimeCall('bindCall(#, #)', [jsFrom, callName]);
if (rules.isSubtypeOf(callMethod.type, to)) return callTearoff;
// We may still need an implicit coercion as well, if another downcast
// is involved.
return js.call('#._check(#)', [_emitType(to), callTearoff]);
}
}
// All Dart number types map to a JS double.
if (jsTypeRep.isNumber(from) && jsTypeRep.isNumber(to)) {
// Make sure to check when converting to int.
if (from != types.intType && to == types.intType) {
// TODO(jmesserly): fuse this with notNull check.
// TODO(jmesserly): this does not correctly distinguish user casts from
// required-for-soundness casts.
return runtimeCall('asInt(#)', jsFrom);
}
// A no-op in JavaScript.
return jsFrom;
}
var code = isImplicit ? '#._check(#)' : '#.as(#)';
return js.call(code, [_emitType(to), jsFrom]);
}
@override
visitIsExpression(IsExpression node) {
// Generate `is` as `dart.is` or `typeof` depending on the RHS type.
JS.Expression result;
var type = node.type.type;
var lhs = _visitExpression(node.expression);
var typeofName = jsTypeRep.typeFor(type).primitiveTypeOf;
// Inline primitives other than int (which requires a Math.floor check).
if (typeofName != null && type != types.intType) {
result = js.call('typeof # == #', [lhs, js.string(typeofName, "'")]);
} else {
// Always go through a runtime helper, because implicit interfaces.
var castType = _emitType(type);
result = js.call('#.is(#)', [castType, lhs]);
}
if (node.notOperator != null) {
return js.call('!#', result);
}
return result;
}
@override
visitFunctionTypeAlias(FunctionTypeAlias node) => _emitTypedef(node);
@override
visitGenericTypeAlias(GenericTypeAlias node) => _emitTypedef(node);
JS.Statement _emitTypedef(TypeAlias node) {
var element = node.declaredElement as FunctionTypeAliasElement;
FunctionType type;
var typeFormals = element.typeParameters;
if (element is GenericTypeAliasElement) {
type = element.function.type;
} else {
type = element.type;
if (typeFormals.isNotEmpty) {
// Skip past the type formals, we'll add them back below, so these
// type parameter names will end up in scope in the generated JS.
type = type.instantiate(typeFormals.map((f) => f.type).toList());
}
}
JS.Expression body = runtimeCall('typedef(#, () => #)', [
js.string(element.name, "'"),
_emitFunctionType(type, nameType: false)
]);
if (typeFormals.isNotEmpty) {
return _defineClassTypeArguments(element, typeFormals,
js.statement('const # = #;', [element.name, body]));
} else {
return js.statement('# = #;', [_emitTopLevelName(element), body]);
}
}
@override
JS.Expression visitTypeName(node) => _emitTypeAnnotation(node);
@override
JS.Expression visitGenericFunctionType(node) => _emitTypeAnnotation(node);
JS.Expression _emitTypeAnnotation(TypeAnnotation node) {
var type = node.type;
if (type == null) {
// TODO(jmesserly): if the type fails to resolve, should we generate code
// that throws instead?
assert(options.unsafeForceCompile || options.replCompile);
type = types.dynamicType;
}
return _emitType(type);
}
@override
JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
return _emitClassDeclaration(
node, node.declaredElement as ClassElement, []);
}
@override
JS.Statement visitClassDeclaration(ClassDeclaration node) {
return _emitClassDeclaration(node, node.declaredElement, node.members);
}
JS.Statement _emitClassDeclaration(Declaration classNode,
ClassElement classElem, List<ClassMember> members) {
// If this class is annotated with `@JS`, then there is nothing to emit.
if (_hasJSInteropAnnotation(classElem)) 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 = classElem.typeParameters.isNotEmpty
? (classElem == _jsArray
? JS.Identifier(classElem.name)
: JS.TemporaryId(classElem.name))
: _emitTopLevelName(classElem);
var savedClassProperties = _classProperties;
_classProperties = ClassPropertyModel.build(
_extensionTypes,
virtualFields,
classElem,
getClassCovariantParameters(classNode),
_usedCovariantPrivateMembers);
var memberMap = Map<Element, Declaration>();
for (var m in members) {
if (m is FieldDeclaration) {
for (var f in m.fields.variables) {
memberMap[f.declaredElement as FieldElement] = f;
}
} else {
memberMap[m.declaredElement] = m;
}
}
var jsCtors =
_defineConstructors(classElem, className, memberMap, classNode);
var jsMethods = _emitClassMethods(classElem, members);
_emitSuperclassCovarianceChecks(classNode, jsMethods);
var body = <JS.Statement>[];
_emitSuperHelperSymbols(body);
var deferredSupertypes = <JS.Statement>[];
// Emit the class, e.g. `core.Object = class Object { ... }`
_defineClass(classElem, className, jsMethods, body, deferredSupertypes);
body.addAll(jsCtors);
// Emit things that come after the ES6 `class ... { ... }`.
var jsPeerNames = _extensionTypes.getNativePeers(classElem);
if (jsPeerNames.length == 1 && classElem.typeParameters.isNotEmpty) {
// Special handling for JSArray<E>
body.add(runtimeStatement('setExtensionBaseClass(#, #.global.#)',
[className, runtimeModule, jsPeerNames[0]]));
}
var finishGenericTypeTest = _emitClassTypeTests(classElem, className, body);
_emitVirtualFieldSymbols(classElem, body);
_emitClassSignature(classElem, className, memberMap, body);
_initExtensionSymbols(classElem);
if (!classElem.isMixin) {
_defineExtensionMembers(className, body);
}
_emitClassMetadata(classNode.metadata, className, body);
var classDef = JS.Statement.from(body);
var typeFormals = classElem.typeParameters;
if (typeFormals.isNotEmpty) {
classDef = _defineClassTypeArguments(
classElem, typeFormals, classDef, className, deferredSupertypes);
} else {
body.addAll(deferredSupertypes);
}
body = [classDef];
_emitStaticFields(classElem, memberMap, body);
if (finishGenericTypeTest != null) body.add(finishGenericTypeTest);
for (var peer in jsPeerNames) {
_registerExtensionType(classElem, peer, body);
}
_classProperties = savedClassProperties;
return JS.Statement.from(body);
}
JS.Statement _emitClassTypeTests(ClassElement classElem,
JS.Expression className, List<JS.Statement> body) {
JS.Expression getInterfaceSymbol(ClassElement c) {
var library = c.library;
if (library.isDartCore || library.isDartAsync) {
switch (c.name) {
case 'List':
case 'Map':
case 'Iterable':
case 'Future':
case 'Stream':
case 'StreamSubscription':
return runtimeCall('is' + c.name);
}
}
return null;
}
void markSubtypeOf(JS.Expression testSymbol) {
body.add(js.statement('#.prototype[#] = true', [className, testSymbol]));
}
for (var iface in classElem.interfaces) {
var prop = getInterfaceSymbol(iface.element);
if (prop != null) markSubtypeOf(prop);
}
if (classElem.library.isDartCore) {
if (classElem == 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 (classElem == 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 (classElem == 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_Function(o) {'
' if (typeof o == "function" || o == null) return o;'
' return #.as(o, #, true);'
'}',
[className, runtimeModule, className]));
return null;
}
if (classElem == 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 (classElem == 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 (classElem == numClass || classElem == 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 (classElem == 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 (classElem.library.isDartAsync) {
if (classElem == types.futureOrType.element) {
var typeParamT = classElem.typeParameters[0].type;
var typeT = _emitType(typeParamT);
var futureOfT = _emitType(types.futureType.instantiate([typeParamT]));
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 (classElem.typeParameters.isEmpty) return null;
// For generics, testing against the default instantiation is common,
// so optimize that.
var isClassSymbol = getInterfaceSymbol(classElem);
if (isClassSymbol == null) {
// TODO(jmesserly): we could export these symbols, if we want to mark
// implemented interfaces for user-defined classes.
var id = JS.TemporaryId("_is_${classElem.name}_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(classElem);
// Return this `addTypeTests` call so we can emit it outside of the generic
// type parameter scope.
return runtimeStatement('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();
}
void _emitVirtualFieldSymbols(
ClassElement classElement, List<JS.Statement> body) {
_classProperties.virtualFields.forEach((field, virtualField) {
body.add(js.statement('const # = Symbol(#);',
[virtualField, js.string('${classElement.name}.${field.name}')]));
});
}
List<JS.Identifier> _emitTypeFormals(List<TypeParameterElement> typeFormals) {
return typeFormals
.map((t) => JS.Identifier(t.name))
.toList(growable: false);
}
@override
JS.Statement visitEnumDeclaration(EnumDeclaration node) {
return _emitClassDeclaration(node, node.declaredElement, []);
}
@override
JS.Statement visitMixinDeclaration(MixinDeclaration node) {
return _emitClassDeclaration(node, node.declaredElement, node.members);
}
/// Wraps a possibly generic class in its type arguments.
JS.Statement _defineClassTypeArguments(TypeDefiningElement element,
List<TypeParameterElement> formals, JS.Statement body,
[JS.Expression className, List<JS.Statement> deferredBaseClass]) {
assert(formals.isNotEmpty);
var jsFormals = _emitTypeFormals(formals);
var typeConstructor = js.call('(#) => { #; #; return #; }', [
jsFormals,
_typeTable.discharge(formals),
body,
className ?? JS.Identifier(element.name)
]);
var genericArgs = [typeConstructor];
if (deferredBaseClass != null && deferredBaseClass.isNotEmpty) {
genericArgs.add(js.call('(#) => { #; }', [jsFormals, deferredBaseClass]));
}
var genericCall = runtimeCall('generic(#)', [genericArgs]);
var genericName = _emitTopLevelNameNoInterop(element, suffix: '\$');
return js.statement('{ # = #; # = #(); }',
[genericName, genericCall, _emitTopLevelName(element), genericName]);
}
JS.Statement _emitClassStatement(
ClassElement classElem,
JS.Expression className,
JS.Expression heritage,
List<JS.Method> methods) {
if (classElem.typeParameters.isNotEmpty) {
return JS.ClassExpression(className as JS.Identifier, heritage, methods)
.toStatement();
}
var classExpr =
JS.ClassExpression(JS.TemporaryId(classElem.name), heritage, methods);
return js.statement('# = #;', [className, classExpr]);
}
/// Like [_emitClassStatement] but emits a Dart 2.1 mixin represented by
/// [classElem].
///
/// 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(
ClassElement classElem,
JS.Expression className,
JS.Expression heritage,
List<JS.Method> methods,
List<JS.Statement> body) {
assert(classElem.isMixin);
var staticMethods = methods.where((m) => m.isStatic).toList();
var instanceMethods = methods.where((m) => !m.isStatic).toList();
body.add(
_emitClassStatement(classElem, className, heritage, staticMethods));
var superclassId = JS.TemporaryId(
classElem.superclassConstraints.map((t) => t.name).join('_'));
var classId =
className is JS.Identifier ? className : JS.TemporaryId(classElem.name);
var mixinMemberClass =
JS.ClassExpression(classId, superclassId, instanceMethods);
JS.Node arrowFnBody = mixinMemberClass;
var extensionInit = <JS.Statement>[];
_defineExtensionMembers(classId, extensionInit);
if (extensionInit.isNotEmpty) {
extensionInit.insert(0, mixinMemberClass.toStatement());
extensionInit.add(classId.toReturn());
arrowFnBody = JS.Block(extensionInit);
}
body.add(js.statement('#[#.mixinOn] = #', [
className,
runtimeModule,
JS.ArrowFun([superclassId], arrowFnBody)
]));
}
void _defineClass(
ClassElement classElem,
JS.Expression className,
List<JS.Method> methods,
List<JS.Statement> body,
List<JS.Statement> deferredSupertypes) {
if (classElem.type.isObject) {
body.add(_emitClassStatement(classElem, className, null, methods));
return;
}
JS.Expression emitDeferredType(DartType t) {
if (t is InterfaceType && t.typeArguments.isNotEmpty) {
_declareBeforeUse(t.element);
return _emitGenericClassType(
t, t.typeArguments.map(emitDeferredType).toList());
}
return _emitType(t, nameType: false);
}
bool shouldDefer(DartType t) {
var visited = Set<DartType>();
bool defer(DartType t) {
if (classElem == t.element) return true;
if (t.isObject) return false;
if (t is ParameterizedType) {
if (!visited.add(t)) return false;
if (t.typeArguments.any(defer)) return true;
if (t is InterfaceType) {
var e = t.element;
if (e.mixins.any(defer)) return true;
var supertype = e.supertype;
return supertype != null && defer(supertype);
}
}
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.element) ?? _emitType(t, nameType: false);
}
getBaseClass(int count) {
var base = emitDeferredType(classElem.type);
while (--count >= 0) {
base = js.call('#.__proto__', [base]);
}
return base;
}
var supertype = classElem.isMixin ? types.objectType : classElem.supertype;
var hasUnnamedSuper = _hasUnnamedConstructor(supertype.element);
void emitMixinConstructors(JS.Expression className, [InterfaceType mixin]) {
var supertype = classElem.supertype;
JS.Statement mixinCtor;
if (mixin != null && _hasUnnamedConstructor(mixin.element)) {
mixinCtor = js.statement('#.#.call(this);', [
emitClassRef(mixin),
_usesMixinNew(mixin.element)
? runtimeCall('mixinNew')
: _constructorName('')
]);
}
for (var ctor in supertype.constructors) {
var jsParams = _emitParametersForElement(ctor);
var ctorBody = <JS.Statement>[];
if (mixinCtor != null) ctorBody.add(mixinCtor);
if (ctor.name != '' || hasUnnamedSuper) {
ctorBody
.add(_emitSuperConstructorCall(className, ctor.name, jsParams));
}
body.add(_addConstructorToClass(
className, ctor.name, JS.Fun(jsParams, JS.Block(ctorBody))));
}
}
var savedTopLevel = _topLevelClass;
_topLevelClass = classElem;
// Unroll mixins.
var mixinLength = classElem.mixins.length;
if (shouldDefer(supertype)) {
deferredSupertypes.add(runtimeStatement('setBaseClass(#, #)', [
getBaseClass(isMixinAliasClass(classElem) ? 0 : mixinLength),
emitDeferredType(supertype),
]));
supertype = fillDynamicTypeArgs(supertype);
}
var baseClass = emitClassRef(supertype);
// TODO(jmesserly): conceptually we could use isMixinApplication, however,
// avoiding the extra level of nesting is only required if the class itself
// is a valid mixin.
if (isMixinAliasClass(classElem)) {
// Given `class C = Object with M [implements I1, I2 ...];`
// The resulting class C should work as a mixin.
body.add(_emitClassStatement(classElem, className, baseClass, []));
var m = classElem.mixins.single;
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]));
_topLevelClass = savedTopLevel;
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.ClassExpression(
JS.TemporaryId(classElem.name), mixinClass, methods)
]));
}
emitMixinConstructors(className, m);
return;
}
for (int i = 0; i < mixinLength; i++) {
var m = classElem.mixins[i];
var mixinString = classElem.supertype.name + '_' + m.name;
var mixinClassName = JS.TemporaryId(mixinString);
var mixinId = JS.TemporaryId(mixinString + '\$');
var mixinClassExpression =
JS.ClassExpression(mixinClassName, baseClass, []);
// 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
var mixinClassDef =
js.statement("const # = #", [mixinId, mixinClassExpression]);
body.add(mixinClassDef);
// Add constructors
emitMixinConstructors(mixinId, m);
hasUnnamedSuper = hasUnnamedSuper || _hasUnnamedConstructor(m.element);
if (shouldDefer(m)) {
deferredSupertypes.add(runtimeStatement('applyMixin(#, #)',
[getBaseClass(mixinLength - i), emitDeferredType(m)]));
} else {
body.add(
runtimeStatement('applyMixin(#, #)', [mixinId, emitClassRef(m)]));
}
baseClass = mixinId;
}
_topLevelClass = savedTopLevel;
if (classElem.isMixin) {
// TODO(jmesserly): we could make this more efficient, as this creates
// an extra unnecessary class. But it's the easiest way to handle the
// current system.
_emitMixinStatement(classElem, className, baseClass, methods, body);
} else {
body.add(_emitClassStatement(classElem, className, baseClass, methods));
}
if (classElem.isMixinApplication) emitMixinConstructors(className);
}
/// 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(FieldDeclaration node) {
// 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 (!node.isStatic) {
for (var decl in node.fields.variables) {
var field = decl.declaredElement as FieldElement;
var name = getAnnotationName(field, isJSName) ?? field.name;
// Generate getter
var fn = JS.Fun([], js.block('{ return this.#; }', [name]));
var method =
JS.Method(_declareMemberName(field.getter), fn, isGetter: true);
jsMethods.add(method);
// Generate setter
if (!decl.isFinal) {
var value = JS.TemporaryId('value');
fn = JS.Fun([value], js.block('{ this.# = #; }', [name, value]));
method =
JS.Method(_declareMemberName(field.setter), fn, isSetter: true);
jsMethods.add(method);
}
}
}
return jsMethods;
}
List<JS.Method> _emitClassMethods(
ClassElement classElem, List<ClassMember> memberNodes) {
var type = classElem.type;
var virtualFields = _classProperties.virtualFields;
var jsMethods = <JS.Method>[];
bool hasJsPeer = _extensionTypes.isNativeClass(classElem);
bool hasIterator = false;
if (type.isObject) {
// Dart does not use ES6 constructors.
// Add an error to catch any invalid usage.
jsMethods
.add(JS.Method(_propertyName('constructor'), js.fun(r'''function() {
throw Error("use `new " + #.typeName(#.getReifiedType(this)) +
".new(...)` to create a Dart object");
}''', [runtimeModule, runtimeModule])));
} else if (classElem.isEnum) {
// Generate Enum.toString()
var fields = classElem.fields.where((f) => f.type == type).toList();
var mapMap = List<JS.Property>(fields.length);
for (var i = 0; i < fields.length; ++i) {
mapMap[i] = JS.Property(
js.number(i), js.string('${type.name}.${fields[i].name}'));
}
jsMethods.add(JS.Method(
_declareMemberName(types.objectType.getMethod('toString')),
js.fun('function() { return #[this.index]; }',
JS.ObjectInitializer(mapMap, multiline: true))));
}
for (var m in memberNodes) {
if (m is ConstructorDeclaration) {
if (m.factoryKeyword != null &&
m.externalKeyword == null &&
m.body is! NativeFunctionBody) {
jsMethods.add(_emitFactoryConstructor(m));
}
} else if (m is MethodDeclaration) {
jsMethods.add(_emitMethodDeclaration(m));
if (m.declaredElement is PropertyAccessorElement) {
jsMethods.add(_emitSuperAccessorWrapper(m, type));
}
if (!hasJsPeer && m.isGetter && m.name.name == 'iterator') {
hasIterator = true;
jsMethods.add(_emitIterable(type));
}
} else if (m is FieldDeclaration) {
if (_extensionTypes.isNativeClass(classElem)) {
jsMethods.addAll(_emitNativeFieldAccessors(m));
continue;
}
if (m.isStatic) continue;
for (VariableDeclaration field in m.fields.variables) {
if (virtualFields.containsKey(field.declaredElement)) {
jsMethods.addAll(_emitVirtualFieldAccessor(field));
}
}
}
}
jsMethods.addAll(_classProperties.mockMembers.values
.map((e) => _implementMockMember(e, type)));
// 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 && _implementsIterable(type)) {
jsMethods.add(_emitIterable(type));
}
// Add all of the super helper methods
jsMethods.addAll(_superHelpers.values);
return jsMethods.where((m) => m != null).toList();
}
void _emitSuperclassCovarianceChecks(
Declaration node, List<JS.Method> methods) {
var covariantParams = getSuperclassCovariantParameters(node);
if (covariantParams == null) return;
for (var member in covariantParams
.map((p) => p.enclosingElement as ExecutableElement)
.toSet()) {
var name = _declareMemberName(member);
if (member is PropertyAccessorElement) {
var param =
covariantParams.lookup(member.parameters[0]) as ParameterElement;
methods.add(JS.Method(
name,
js.fun('function(x) { return super.# = #._check(x); }',
[name, _emitType(param.type)]),
isSetter: true));
methods.add(JS.Method(
name, js.fun('function() { return super.#; }', [name]),
isGetter: true));
} else if (member is MethodElement) {
var type = member.type;
var body = <JS.Statement>[];
_emitCovarianceBoundsCheck(type.typeFormals, covariantParams, body);
var typeFormals = _emitTypeFormals(type.typeFormals);
var jsParams = List<JS.Parameter>.from(typeFormals);
bool foundNamedParams = false;
for (var param in member.parameters) {
param = covariantParams.lookup(param) as ParameterElement;
if (param == null) continue;
if (param.kind == ParameterKind.NAMED) {
foundNamedParams = true;
var name = _propertyName(param.name);
body.add(js.statement('if (# in #) #._check(#.#);', [
name,
namedArgumentTemp,
_emitType(param.type),
namedArgumentTemp,
name
]));
} else {
var jsParam = _emitParameter(param);
jsParams.add(jsParam);
if (param.kind == ParameterKind.POSITIONAL) {
body.add(js.statement('if (# !== void 0) #._check(#);',
[jsParam, _emitType(param.type), jsParam]));
} else {
body.add(js
.statement('#._check(#);', [_emitType(param.type), jsParam]));
}
}
}
if (foundNamedParams) 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 = JS.Fun(jsParams, JS.Block(body));
methods.add(JS.Method(name, fn));
} else {
throw StateError(
'unable to generate a covariant check for element: `$member` '
'(${member.runtimeType})');
}
}
}
/// Emits a Dart factory constructor to a JS static method.
JS.Method _emitFactoryConstructor(ConstructorDeclaration node) {
if (isUnsupportedFactoryConstructor(node)) return null;
var element = node.declaredElement;
var name = _constructorName(element.name);
JS.Fun fun;
var savedFunction = _currentFunction;
_currentFunction = node.body;
var redirect = node.redirectedConstructor;
if (redirect != null) {
// Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz;
var newKeyword = redirect.staticElement.isFactory ? '' : 'new';
// Pass along all arguments verbatim, and let the callee handle them.
// TODO(jmesserly): we'll need something different once we have
// rest/spread support, but this should work for now.
var params = _emitParameters(node.parameters?.parameters);
fun = JS.Fun(
params,
JS.Block([
js.statement('return $newKeyword #(#)',
[visitConstructorName(redirect), params])
..sourceInformation = _nodeStart(redirect)
]));
} else {
// Normal factory constructor
var body = <JS.Statement>[];
var init = _emitArgumentInitializers(element, node.parameters);
if (init != null) body.add(init);
body.add(_visitStatement(node.body));
var params = _emitParameters(node.parameters?.parameters);
fun = JS.Fun(params, JS.Block(body));
}
_currentFunction = savedFunction;
return JS.Method(name, fun, isStatic: true)
..sourceInformation = _functionEnd(node);
}
/// 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(food) {
/// return core.bool.as(this.noSuchMethod(
/// new dart.InvocationImpl.new('eatFood', [food])));
/// }
JS.Method _implementMockMember(ExecutableElement method, InterfaceType type) {
var invocationProps = <JS.Property>[];
addProperty(String name, JS.Expression value) {
invocationProps.add(JS.Property(js.string(name), value));
}
var typeParams = _emitTypeFormals(method.type.typeFormals);
var fnArgs = List<JS.Parameter>.from(typeParams);
var args = _emitParametersForElement(method);
fnArgs.addAll(args);
var argInit = _emitArgumentInitializers(method);
if (method is PropertyAccessorElement) {
if (method.isGetter) {
addProperty('isGetter', js.boolean(true));
} else {
assert(method.isSetter);
addProperty('isSetter', js.boolean(true));
}
} else {
addProperty('isMethod', js.boolean(true));
}
var positionalArgs = args;
var namedParameterTypes = method.type.namedParameterTypes;
if (namedParameterTypes.isNotEmpty) {
// Sort the names to match dart2js order.
var sortedNames = (namedParameterTypes.keys.toList())..sort();
var named = sortedNames
.map((n) => JS.Property(_propertyName(n), JS.Identifier(n)));
addProperty('namedArguments', JS.ObjectInitializer(named.toList()));
positionalArgs.removeLast();
}
if (typeParams.isNotEmpty) {
addProperty('typeArguments', JS.ArrayInitializer(typeParams));
}
var fnBody =
js.call('this.noSuchMethod(new #.InvocationImpl.new(#, [#], #))', [
runtimeModule,
_declareMemberName(method),
args,
JS.ObjectInitializer(invocationProps)
]);
if (!method.returnType.isDynamic) {
fnBody = js.call('#._check(#)', [_emitType(method.returnType), fnBody]);
}
var fnBlock = argInit != null
? JS.Block([argInit, fnBody.toReturn()])
: fnBody.toReturn().toBlock();
return JS.Method(
_declareMemberName(method,
useExtension: _extensionTypes.isNativeClass(type.element)),
JS.Fun(fnArgs, fnBlock),
isGetter: method is PropertyAccessorElement && method.isGetter,
isSetter: method is PropertyAccessorElement && method.isSetter,
isStatic: false);
}
/// 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(VariableDeclaration field) {
var element = field.declaredElement as FieldElement;
var virtualField = _classProperties.virtualFields[element];
var result = <JS.Method>[];
var name = _declareMemberName(element.getter);
var mocks = _classProperties.mockMembers;
if (!mocks.containsKey(element.name)) {
var getter = js.fun('function() { return this[#]; }', [virtualField]);
result.add(JS.Method(name, getter, isGetter: true)
..sourceInformation = _functionSpan(field.name));
}
if (!mocks.containsKey(element.name + '=')) {
var args = field.isFinal ? [JS.Super(), name] : [JS.This(), virtualField];
String jsCode;
var setter = element.setter;
var covariantParams = _classProperties.covariantParameters;
if (setter != null &&
covariantParams != null &&
covariantParams.contains(setter.parameters[0])) {
args.add(_emitType(setter.parameters[0].type));
jsCode = 'function(value) { #[#] = #._check(value); }';
} else {
jsCode = 'function(value) { #[#] = value; }';
}
result.add(JS.Method(name, js.fun(jsCode, args), isSetter: true)
..sourceInformation = _functionSpan(field.name));
}
return result;
}
/// 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(
MethodDeclaration member, InterfaceType type) {
var accessorElement = member.declaredElement as PropertyAccessorElement;
var field = accessorElement.variable;
if (!field.isSynthetic || accessorElement.isAbstract) return null;
// Generate a corresponding virtual getter / setter.
var name = _declareMemberName(accessorElement);
if (member.isGetter) {
var setter = field.setter;
if ((setter == null || setter.isAbstract) &&
_classProperties.inheritedSetters.contains(field.name)) {
// Generate a setter that forwards to super.
var fn = js.fun('function(value) { super[#] = value; }', [name]);
return JS.Method(name, fn, isSetter: true);
}
} else {
var getter = field.getter;
if ((getter == null || getter.isAbstract) &&
_classProperties.inheritedGetters.contains(field.name)) {
// Generate a getter that forwards to super.
var fn = js.fun('function() { return super[#]; }', [name]);
return JS.Method(name, fn, isGetter: true);
}
}
return null;
}
bool _implementsIterable(InterfaceType t) =>
t.interfaces.any((i) => i.element.type == types.iterableType);
/// 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(InterfaceType t) {
// 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 = t.lookUpGetterInSuperclass('iterator', t.element.library);
if (parent != null) return null;
var parentType = findSupertype(t, _implementsIterable);
if (parentType != null) return null;
if (t.element.source.isInSystemLibrary &&
t.methods.any((m) => getJSExportName(m) == 'Symbol.iterator')) {
return null;
}
// Otherwise, emit the adapter method, which wraps the Dart iterator in
// an ES6 iterator.
return JS.Method(
js.call('Symbol.iterator'),
js.call('function() { return new #.JsIterator(this.#); }',
[runtimeModule, _emitMemberName('iterator', type: t)]) as JS.Fun);
}
JS.Expression _instantiateAnnotation(Annotation node) {
var element = node.element;
if (element is ConstructorElement) {
return _emitInstanceCreationExpression(
element,
element.returnType as InterfaceType,
() => _emitArgumentList(node.arguments),
isConst: true);
} else {
return _visitExpression(node.name);
}
}
void _registerExtensionType(
ClassElement classElem, String jsPeerName, List<JS.Statement> body) {
var className = _emitTopLevelName(classElem);
if (jsTypeRep.isPrimitive(classElem.type)) {
body.add(
runtimeStatement('definePrimitiveHashCode(#.prototype)', className));
}
body.add(runtimeStatement(
'registerExtension(#, #)', [js.string(jsPeerName), className]));
}
/// Defines all constructors for this class as ES5 constructors.
List<JS.Statement> _defineConstructors(
ClassElement classElem,
JS.Expression className,
Map<Element, Declaration> memberMap,
Declaration classNode) {
var body = <JS.Statement>[];
if (classElem.isMixinApplication) {
// We already handled this when we defined the class.
return body;
}
addConstructor(String name, JS.Expression jsCtor) {
body.add(_addConstructorToClass(className, name, jsCtor));
}
if (classElem.isEnum) {
addConstructor('', js.call('function(x) { this.index = x; }'));
return body;
}
var fields = List<VariableDeclaration>.from(memberMap.values.where((m) =>
m is VariableDeclaration &&
!(m.declaredElement as FieldElement).isStatic));
// Iff no constructor is specified for a class C, it implicitly has a
// default constructor `C() : super() {}`, unless C is class Object.
var defaultCtor = classElem.unnamedConstructor;
if (defaultCtor != null && defaultCtor.isSynthetic) {
assert(classElem.constructors.length == 1,
'default constructor only if no other constructors');
var superCall = _emitSuperConstructorCallIfNeeded(classElem, className);
var ctorBody = <JS.Statement>[_initializeFields(fields)];
if (superCall != null) ctorBody.add(superCall);
addConstructor(
'',
JS.Fun([], JS.Block(ctorBody))
..sourceInformation = _functionEnd(classNode));
return body;
}
for (var element in classElem.constructors) {
if (element.isSynthetic || element.isFactory || element.isExternal) {
continue;
}
var ctor = memberMap[element] as ConstructorDeclaration;
if (ctor.body is NativeFunctionBody) continue;
addConstructor(element.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(classElem)) {
body.add(
js.statement('(#[#] = function() { # }).prototype = #.prototype;', [
className,
runtimeCall('mixinNew'),
[_initializeFields(fields)],
className
]));
}
return body;
}
/// 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.
bool _usesMixinNew(ClassElement mixin) {
return (mixin.supertype?.isObject ?? false) &&
mixin.constructors.every((c) =>
c.isSynthetic && c.name != '' || c.isFactory || 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]);
}
/// Emits static fields for a class, and initialize them eagerly if possible,
/// otherwise define them as lazy properties.
void _emitStaticFields(ClassElement classElem,
Map<Element, Declaration> members, List<JS.Statement> body) {
if (classElem.isEnum) {
// Emit enum static fields
var type = classElem.type;
void addField(FieldElement e, JS.Expression value) {
body.add(defineValueOnClass(_emitStaticClassName(classElem),
_declareMemberName(e.getter), value)
.toStatement());
}
int index = 0;
var values = <JS.Expression>[];
for (var f in classElem.fields) {
if (f.type != type) continue;
// static const E id_i = const E(i);
values.add(JS.PropertyAccess(
_emitStaticClassName(classElem), _declareMemberName(f.getter)));
var enumValue = runtimeCall('const(new (#.#)(#))', [
_emitConstructorAccess(type),
_constructorName(''),
js.number(index++)
]);
addField(f, enumValue);
}
// static const List<E> values = const <E>[id_0 . . . id_n−1];
addField(classElem.getField('values'), _emitConstList(type, values));
return;
}
var lazyStatics = classElem.fields
.where((f) => f.isStatic && !f.isSynthetic)
.map((f) => members[f] as VariableDeclaration)
.toList();
if (lazyStatics.isNotEmpty) {
body.add(_emitLazyFields(_emitStaticClassName(classElem), lazyStatics,
(e) => _emitStaticMemberName(e.name, e)));
}
}
void _emitClassMetadata(List<Annotation> metadata, JS.Expression className,
List<JS.Statement> body) {
// Metadata
if (options.emitMetadata && metadata.isNotEmpty) {
body.add(js.statement('#[#.metadata] = () => [#];',
[className, runtimeModule, metadata.map(_instantiateAnnotation)]));
}
}
/// Ensure `dartx.` symbols we will use are present.
void _initExtensionSymbols(ClassElement classElem) {
if (_extensionTypes.hasNativeSubtype(classElem.type) ||
classElem.type.isObject) {
for (var members in [classElem.methods, classElem.accessors]) {
for (var m in members) {
if (!m.isAbstract && !m.isStatic && m.isPublic) {
_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,
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(ClassElement classElem, JS.Expression className,
Map<Element, Declaration> annotatedMembers, List<JS.Statement> body) {
if (classElem.interfaces.isNotEmpty ||
classElem.superclassConstraints.isNotEmpty) {
var interfaces = classElem.interfaces.toList()
..addAll(classElem.superclassConstraints);
body.add(js.statement('#[#.implements] = () => [#];',
[className, runtimeModule, interfaces.map(_emitType)]));
}
void emitSignature(String name, List<JS.Property> elements) {
if (elements.isEmpty) return;
if (!name.startsWith('Static')) {
var proto = classElem.type.isObject
? js.call('Object.create(null)')
: runtimeCall('get${name}s(#.__proto__)', [className]);
elements.insert(0, JS.Property(_propertyName('__proto__'), proto));
}
body.add(runtimeStatement('set${name}Signature(#, () => #)', [
className,
JS.ObjectInitializer(elements, multiline: elements.length > 1)
]));
}
var mockMembers = _classProperties.mockMembers;
{
var extMembers = _classProperties.extensionMethods;
var staticMethods = <JS.Property>[];
var instanceMethods = <JS.Property>[];
var classMethods = classElem.methods.where((m) => !m.isAbstract).toList();
for (var m in mockMembers.values) {
if (m is MethodElement) classMethods.add(m);
}
for (var method in classMethods) {
var isStatic = method.isStatic;
if (isStatic && !options.emitMetadata) continue;
var name = method.name;
var reifiedType = _getMemberRuntimeType(method);
var memberOverride =
classElem.type.lookUpMethodInSuperclass(name, currentLibrary);
// 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 needsSignature = memberOverride == null ||
memberOverride.isAbstract ||
_getMemberRuntimeType(memberOverride) != reifiedType;
if (needsSignature) {
var annotationNode = annotatedMembers[method] as MethodDeclaration;
var type = _emitAnnotatedFunctionType(
reifiedType, annotationNode?.metadata,
parameters: annotationNode?.parameters?.parameters);
var property = JS.Property(_declareMemberName(method), type);
if (isStatic) {
staticMethods.add(property);
} else {
instanceMethods.add(property);
if (extMembers.contains(name)) {
instanceMethods.add(JS.Property(
_declareMemberName(method, useExtension: true), type));
}
}
}
}
emitSignature('Method', instanceMethods);
emitSignature('StaticMethod', staticMethods);
}
{
var extMembers = _classProperties.extensionAccessors;
var staticGetters = <JS.Property>[];
var instanceGetters = <JS.Property>[];
var staticSetters = <JS.Property>[];
var instanceSetters = <JS.Property>[];
var classAccessors = classElem.accessors
.where((m) => !m.isAbstract && !m.isSynthetic)
.toList();
for (var m in mockMembers.values) {
if (m is PropertyAccessorElement) classAccessors.add(m);
}
for (var accessor in classAccessors) {
// Static getters/setters cannot be called with dynamic dispatch, nor
// can they be torn off.
var isStatic = accessor.isStatic;
if (isStatic && !options.emitMetadata) continue;
var name = accessor.name;
var isGetter = accessor.isGetter;
var memberOverride = isGetter
? classElem.type.lookUpGetterInSuperclass(name, currentLibrary)
: classElem.type.lookUpSetterInSuperclass(name, currentLibrary);
var reifiedType = accessor.type;
// 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 needsSignature = memberOverride == null ||
memberOverride.isAbstract ||
memberOverride.type != reifiedType;
if (needsSignature) {
var annotationNode = annotatedMembers[accessor] as MethodDeclaration;
var type = _emitAnnotatedResult(
_emitType(
isGetter
? reifiedType.returnType
: reifiedType.parameters[0].type,
nameType: false),
annotationNode?.metadata);
var property = JS.Property(_declareMemberName(accessor), type);
if (isStatic) {
(isGetter ? staticGetters : staticSetters).add(property);
} else {
var accessors = isGetter ? instanceGetters : instanceSetters;
accessors.add(property);
if (extMembers.contains(accessor.variable.name)) {
accessors.add(JS.Property(
_declareMemberName(accessor, useExtension: true), type));
}
}
}
}
emitSignature('Getter', instanceGetters);
emitSignature('Setter', instanceSetters);
emitSignature('StaticGetter', staticGetters);
emitSignature('StaticSetter', staticSetters);
}
{
var instanceFields = <JS.Property>[];
var staticFields = <JS.Property>[];
for (var field in classElem.fields) {
if (field.isSynthetic && !classElem.isEnum) continue;
// Only instance fields need to be saved for dynamic dispatch.
var isStatic = field.isStatic;
if (isStatic && !options.emitMetadata) continue;
var fieldNode = annotatedMembers[field] as VariableDeclaration;
var metadata = fieldNode != null
? (fieldNode.parent.parent as FieldDeclaration).metadata
: null;
var memberName = _declareMemberName(field.getter);
var fieldSig = _emitFieldSignature(field.type,
metadata: metadata, isFinal: field.isFinal);
(isStatic ? staticFields : instanceFields)
.add(JS.Property(memberName, fieldSig));
}
emitSignature('Field', instanceFields);
emitSignature('StaticField', staticFields);
}
if (options.emitMetadata) {
var constructors = <JS.Property>[];
for (var ctor in classElem.constructors) {
var annotationNode = annotatedMembers[ctor] as ConstructorDeclaration;
var memberName = _constructorName(ctor.name);
var type = _emitAnnotatedFunctionType(
ctor.type, annotationNode?.metadata,
parameters: annotationNode?.parameters?.parameters);
constructors.add(JS.Property(memberName, type));
}
emitSignature('Constructor', constructors);
}
// Add static property dart._runtimeType to Object.
//
// All other Dart classes will (statically) inherit this property.
if (classElem == objectClass) {
body.add(runtimeStatement('lazyFn(#, () => #.#)',
[className, emitLibraryName(coreLibrary), 'Type']));
}
}
JS.Expression _emitConstructor(ConstructorDeclaration node,
List<VariableDeclaration> fields, JS.Expression className) {
var params = _emitParameters(node.parameters?.parameters);
var savedFunction = _currentFunction;
_currentFunction = node.body;
var savedSuperAllowed = _superAllowed;
_superAllowed = false;
var body = _emitConstructorBody(node, fields, className);
_superAllowed = savedSuperAllowed;
_currentFunction = savedFunction;
return JS.Fun(params, body)..sourceInformation = _functionEnd(node);
}
FunctionType _getMemberRuntimeType(ExecutableElement element) {
// Check whether we have any covariant parameters.
// Usually we don't, so we can use the same type.
if (!element.parameters.any(_isCovariant)) return element.type;
var parameters = element.parameters
.map((p) => ParameterElementImpl.synthetic(p.name,
_isCovariant(p) ? objectClass.type : p.type, p.parameterKind))
.toList();
var function = FunctionElementImpl("", -1)
..isSynthetic = true
..returnType = element.returnType
// TODO(jmesserly): do covariant type parameter bounds also need to be
// reified as `Object`?
..shareTypeParameters(element.typeParameters)
..parameters = parameters;
return function.type = FunctionTypeImpl(function);
}
JS.Expression _constructorName(String name) {
if (name == '') {
// Default constructors (factory or not) use `new` as their name.
return _propertyName('new');
}
return _emitStaticMemberName(name);
}
JS.Block _emitConstructorBody(ConstructorDeclaration node,
List<VariableDeclaration> fields, JS.Expression className) {
var body = <JS.Statement>[];
ClassDeclaration cls = node.parent;
// 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 init = _emitArgumentInitializers(node.declaredElement, node.parameters);
if (init != null) body.add(init);
// Redirecting constructors: these are not allowed to have initializers,
// and the redirecting ctor invocation runs before field initializers.
for (var init in node.initializers) {
if (init is RedirectingConstructorInvocation) {
body.add(_emitRedirectingConstructor(init, className));
return JS.Block(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));
var superCall = node.initializers.firstWhere(
(i) => i is SuperConstructorInvocation,
orElse: () => null) as SuperConstructorInvocation;
// 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 superCallArgs =
superCall != null ? _emitArgumentList(superCall.argumentList) : null;
var jsSuper = _emitSuperConstructorCallIfNeeded(cls.declaredElement,
className, superCall?.staticElement, superCallArgs);
if (jsSuper != null) {
if (superCall != null) jsSuper.sourceInformation = _nodeStart(superCall);
body.add(jsSuper);
}
body.add(_emitFunctionScopedBody(node.body, node.declaredElement));
return JS.Block(body);
}
JS.Statement _emitRedirectingConstructor(
RedirectingConstructorInvocation node, JS.Expression className) {
var ctor = node.staticElement;
// 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),
_emitArgumentList(node.argumentList)
]);
}
JS.Statement _emitSuperConstructorCallIfNeeded(
ClassElement element, JS.Expression className,
[ConstructorElement superCtor, List<JS.Expression> args]) {
// Get the supertype's unnamed constructor.
superCtor ??= element.supertype?.element?.unnamedConstructor;
if (superCtor == null) {
assert(element.type.isObject ||
element.isMixin ||
options.unsafeForceCompile);
return null;
}
// We can skip the super call if it's empty. Typically this happens for
// things that extend Object.
if (superCtor.name == '' && !_hasUnnamedSuperConstructor(element)) {
return null;
}
return _emitSuperConstructorCall(className, superCtor.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(ClassElement e) {
var supertype = e.supertype;
// Object or mixin declaration.
if (supertype == null) return false;
if (_hasUnnamedConstructor(supertype.element)) return true;
for (var mixin in e.mixins) {
if (_hasUnnamedConstructor(mixin.element)) return true;
}
return false;
}
bool _hasUnnamedConstructor(ClassElement e) {
if (e.type.isObject) return false;
var ctor = e.unnamedConstructor;
if (ctor == null) return false;
if (!ctor.isSynthetic) return true;
if (e.fields.any((f) => !f.isStatic && !f.isSynthetic)) return true;
return _hasUnnamedSuperConstructor(e);
}
/// 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<VariableDeclaration> fieldDecls,
[ConstructorDeclaration ctor]) {
Set<FieldElement> ctorFields;
emitFieldInit(FieldElement f, Expression initializer, AstNode hoverInfo) {
ctorFields?.add(f);
var access =
_classProperties.virtualFields[f] ?? _declareMemberName(f.getter);
var jsInit = _visitInitializer(initializer, f);
return jsInit
.toAssignExpression(js.call('this.#', [access])
..sourceInformation = _nodeSpan(hoverInfo))
.toStatement();
}
var body = <JS.Statement>[];
if (ctor != null) {
ctorFields = HashSet<FieldElement>();
// Run constructor parameter initializers such as `this.foo`
for (var p in ctor.parameters.parameters) {
var element = p.declaredElement;
if (element is FieldFormalParameterElement) {
body.add(emitFieldInit(element.field, p.identifier, p.identifier));
}
}
// Run constructor field initializers such as `: foo = bar.baz`
for (var init in ctor.initializers) {
if (init is ConstructorFieldInitializer) {
var field = init.fieldName;
var element = field.staticElement as FieldElement;
body.add(emitFieldInit(element, init.expression, field));
} else if (init is AssertInitializer) {
body.add(_emitAssert(init.condition, init.message));
}
}
}
// Run field initializers if needed.
//
// We can skip fields where the initializer doesn't have side effects
// (for example, it's a literal value such as implicit `null`) and where
// there's another explicit initialization (either in the initializer list
// like `field = value`, or via a `this.field` parameter).
var fieldInit = <JS.Statement>[];
for (var field in fieldDecls) {
var f = field.declaredElement as FieldElement;
if (f.isStatic) continue;
if (ctorFields != null &&
ctorFields.contains(f) &&
_constants.isFieldInitConstant(field)) {
continue;
}
fieldInit.add(emitFieldInit(f, field.initializer, field.name));
}
// Run field initializers before the other ones.
fieldInit.addAll(body);
return JS.Statement.from(fieldInit);
}
/// Emits argument initializers, which handles optional/named args, as well
/// as generic type checks needed due to our covariance.
JS.Statement _emitArgumentInitializers(ExecutableElement element,
[FormalParameterList parameterNodes]) {
var body = <JS.Statement>[];
_emitCovarianceBoundsCheck(
element.typeParameters, _classProperties?.covariantParameters, body);
for (int i = 0, n = element.parameters.length; i < n; i++) {
var param = element.parameters[i];
var paramNode =
parameterNodes != null ? parameterNodes.parameters[i] : null;
var jsParam = _emitParameter(param);
if (parameterNodes != null) {