blob: 1d36734b76b084371fbf3c7527a9a3a470782927 [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/ast.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'
show LocalVariableElementImpl;
import 'package:analyzer/src/dart/element/type.dart' show DynamicTypeImpl;
import 'package:analyzer/src/dart/sdk/sdk.dart';
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'
show isDynamicInvoke, setIsDynamicInvoke, getImplicitAssignmentCast;
import 'package:path/path.dart' show separator;
import '../closure/closure_annotator.dart' show ClosureAnnotator;
import '../js_ast/js_ast.dart' as JS;
import '../js_ast/js_ast.dart' show js;
import 'ast_builder.dart' show AstBuilder;
import 'compiler.dart' show BuildUnit, CompilerOptions, JSModuleFile;
import 'element_helpers.dart';
import 'element_loader.dart' show ElementLoader;
import 'extension_types.dart' show ExtensionTypeSet;
import 'js_field_storage.dart' show checkForPropertyOverride, getSuperclasses;
import 'js_interop.dart';
import 'js_metalet.dart' as JS;
import 'js_names.dart' as JS;
import 'js_typeref_codegen.dart' show JsTypeRefCodegen;
import 'module_builder.dart' show pathToJSIdentifier;
import 'nullable_type_inference.dart' show NullableTypeInference;
import 'reify_coercions.dart' show CoercionReifier;
import 'side_effect_analysis.dart' show ConstFieldVisitor, isStateless;
import 'type_utilities.dart';
class CodeGenerator extends GeneralizingAstVisitor
with ClosureAnnotator, JsTypeRefCodegen, NullableTypeInference {
final AnalysisContext context;
final SummaryDataStore summaryData;
final CompilerOptions options;
final rules = new StrongTypeSystemImpl();
/// 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<LibraryElement, JS.Identifier>();
/// Imported libraries, and the temporaries used to refer to them.
final _imports = new Map<LibraryElement, JS.TemporaryId>();
/// The list of output module items, in the order they need to be emitted in.
final _moduleItems = <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;
// TODO(jmesserly): fuse this with notNull check.
final _privateNames =
new HashMap<LibraryElement, HashMap<String, JS.TemporaryId>>();
final _initializingFormalTemps =
new HashMap<ParameterElement, JS.TemporaryId>();
JS.Identifier _extensionSymbolsModule;
JS.Identifier _runtimeModule;
final namedArgumentTemp = new JS.TemporaryId('opts');
final _hasDeferredSupertype = new HashSet<ClassElement>();
final _eagerTopLevelFields = new HashSet<Element>.identity();
/// The type provider from the current Analysis [context].
final TypeProvider types;
final LibraryElement dartCoreLibrary;
final LibraryElement dartJSLibrary;
/// The dart:async `StreamIterator<>` type.
final InterfaceType _asyncStreamIterator;
/// The dart:_interceptors JSArray element.
final ClassElement _jsArray;
final ClassElement boolClass;
final ClassElement intClass;
final ClassElement interceptorClass;
final ClassElement nullClass;
final ClassElement numClass;
final ClassElement objectClass;
final ClassElement stringClass;
final ClassElement symbolClass;
ConstFieldVisitor _constants;
/// The current function body being compiled.
FunctionBody _currentFunction;
/// Helper class for emitting elements in the proper order to allow
/// JS to load the module.
ElementLoader _loader;
BuildUnit _buildUnit;
String _libraryRoot;
bool _superAllowed = true;
List<JS.TemporaryId> _superHelperSymbols = <JS.TemporaryId>[];
List<JS.Method> _superHelpers = <JS.Method>[];
List<TypeParameterType> _typeParamInConst = null;
/// Whether we are currently generating code for the body of a `JS()` call.
bool _isInForeignJS = false;
CodeGenerator(
AnalysisContext c, this.summaryData, this.options, this._extensionTypes)
: context = c,
types = c.typeProvider,
_asyncStreamIterator =
_getLibrary(c, 'dart:async').getType('StreamIterator').type,
_jsArray = _getLibrary(c, 'dart:_interceptors').getType('JSArray'),
interceptorClass =
_getLibrary(c, 'dart:_interceptors').getType('Interceptor'),
dartCoreLibrary = _getLibrary(c, 'dart:core'),
boolClass = _getLibrary(c, 'dart:core').getType('bool'),
intClass = _getLibrary(c, 'dart:core').getType('int'),
numClass = _getLibrary(c, 'dart:core').getType('num'),
nullClass = _getLibrary(c, 'dart:core').getType('Null'),
objectClass = _getLibrary(c, 'dart:core').getType('Object'),
stringClass = _getLibrary(c, 'dart:core').getType('String'),
symbolClass = _getLibrary(c, 'dart:_internal').getType('Symbol'),
dartJSLibrary = _getLibrary(c, 'dart:js');
LibraryElement get currentLibrary => _loader.currentElement.library;
/// 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,
List<String> errors) {
_buildUnit = unit;
_libraryRoot = _buildUnit.libraryRoot;
if (!_libraryRoot.endsWith(separator)) {
_libraryRoot = '$_libraryRoot${separator}';
}
var module = _emitModule(compilationUnits);
var dartApiSummary = _summarizeModule(compilationUnits);
return new JSModuleFile(unit.name, errors, options, module, dartApiSummary);
}
List<int> _summarizeModule(List<CompilationUnit> units) {
if (!options.summarizeApi) return null;
if (!units.any((u) => u.element.librarySource.isInSystemLibrary)) {
var sdk = context.sourceFactory.dartSdk;
summaryData.addBundle(
null,
sdk is SummaryBasedDartSdk
? sdk.bundle
: (sdk as FolderBasedDartSdk).getSummarySdkBundle(true));
}
var assembler = new PackageBundleAssembler();
assembler.recordDependencies(summaryData);
var uriToUnit = new Map<String, UnlinkedUnit>.fromIterable(units,
key: (u) => u.element.source.uri.toString(), value: (unit) {
var unlinked = serializeAstUnlinked(unit);
assembler.addUnlinkedUnit(unit.element.source, unlinked);
return unlinked;
});
summary_link
.link(
uriToUnit.keys.toSet(),
(uri) => summaryData.linkedMap[uri],
(uri) => summaryData.unlinkedMap[uri] ?? uriToUnit[uri],
context.declaredVariables.get,
true)
.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) {
if (_moduleItems.isNotEmpty) {
throw new StateError('Can only call emitModule once.');
}
// Transform the AST to make coercions explicit.
compilationUnits = CoercionReifier.reify(compilationUnits);
if (compilationUnits.any((u) => _isDartRuntime(u.element.library))) {
// 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');
} 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>[];
for (var unit in compilationUnits) {
var library = unit.element.library;
if (unit.element != library.definingCompilationUnit) continue;
var libraryTemp = _isDartRuntime(library)
? _runtimeModule
: new JS.TemporaryId(jsLibraryName(_libraryRoot, library));
_libraries[library] = libraryTemp;
items.add(new JS.ExportDeclaration(
js.call('const # = Object.create(null)', [libraryTemp])));
// dart:_runtime has a magic module that holds extension method symbols.
// TODO(jmesserly): find a cleaner design for this.
if (_isDartRuntime(library)) {
items.add(new JS.ExportDeclaration(js
.call('const # = Object.create(null)', [_extensionSymbolsModule])));
}
}
// Collect all Element -> Node mappings, in case we need to forward declare
// any nodes.
var nodes = new HashMap<Element, AstNode>.identity();
var sdkBootstrappingFns = new List<FunctionElement>();
for (var unit in compilationUnits) {
if (_isDartRuntime(unit.element.library)) {
sdkBootstrappingFns.addAll(unit.element.functions);
}
_collectElements(unit, nodes);
}
_loader = new ElementLoader(nodes);
if (compilationUnits.isNotEmpty) {
_constants = new ConstFieldVisitor(context,
dummySource: compilationUnits.first.element.source);
}
// Add implicit dart:core dependency so it is first.
emitLibraryName(dartCoreLibrary);
// Emit SDK bootstrapping functions first, if any.
sdkBootstrappingFns.forEach(_emitDeclaration);
// 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(_finishDeclarationsInUnit);
// Declare imports
_finishImports(items);
// 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: _buildUnit.name);
}
List<String> _getJSName(Element e) {
if (e.library == null ||
findAnnotation(e.library, isPublicJSAnnotation) == null) {
return null;
}
var libraryJSName = getAnnotationName(e.library, isPublicJSAnnotation);
var libraryPrefix = <String>[];
if (libraryJSName != null && libraryJSName.isNotEmpty) {
libraryPrefix.addAll(libraryJSName.split('.'));
}
String elementJSName;
if (findAnnotation(e, isPublicJSAnnotation) != null) {
elementJSName = getAnnotationName(e, isPublicJSAnnotation) ?? '';
}
if (e is TopLevelVariableElement) {
elementJSName = _jsInteropStaticMemberName(e);
}
if (elementJSName == null) return null;
var elementJSParts = <String>[];
if (elementJSName.isNotEmpty) {
elementJSParts.addAll(elementJSName.split('.'));
} else {
elementJSParts.add(e.name);
}
return libraryPrefix..addAll(elementJSParts);
}
JS.Expression _emitJSInterop(Element e) {
var jsName = _getJSName(e);
if (jsName == null) return null;
var fullName = ['global']..addAll(jsName);
JS.Expression access = _runtimeModule;
for (var part in fullName) {
access = new JS.PropertyAccess(access, js.string(part));
}
return access;
}
String _jsInteropStaticMemberName(Element e, {String name}) {
if (e == null ||
e.library == null ||
findAnnotation(e.library, isPublicJSAnnotation) == null) {
return null;
}
if (e is PropertyInducingElement) {
// Assume properties have consistent JS names for getters and setters.
return _jsInteropStaticMemberName(e.getter, name: e.name) ??
_jsInteropStaticMemberName(e.setter, name: e.name);
}
if (e is ExecutableElement &&
e.isExternal &&
findAnnotation(e, isPublicJSAnnotation) != null) {
return getAnnotationName(e, isPublicJSAnnotation) ?? name ?? e.name;
}
return null;
}
JS.Expression _emitJSInteropStaticMemberName(Element e) {
var name = _jsInteropStaticMemberName(e);
if (name == null) return null;
// We do not support statics names with JS annotations containing dots.
// See https://github.com/dart-lang/sdk/issues/27926
if (name.contains('.')) {
throw new UnimplementedError(
'We do not support JS annotations containing dots on static members. '
'See https://github.com/dart-lang/sdk/issues/27926');
}
return js.string(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 {
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 'dart_sdk';
}
var moduleName = _buildUnit.libraryToModule(source);
if (moduleName == null) {
throw new StateError('Could not find module containing "$library".');
}
return moduleName;
}
void _finishImports(List<JS.ModuleItem> items) {
var modules = new Map<String, List<LibraryElement>>();
for (var import in _imports.keys) {
modules.putIfAbsent(_libraryToModule(import), () => []).add(import);
}
String coreModuleName;
if (!_libraries.containsKey(dartCoreLibrary)) {
coreModuleName = _libraryToModule(dartCoreLibrary);
}
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, "'")));
});
}
/// Collect toplevel elements and nodes we need to emit, and returns
/// an ordered map of these.
static void _collectElements(
CompilationUnit unit, Map<Element, AstNode> map) {
for (var declaration in unit.declarations) {
if (declaration is TopLevelVariableDeclaration) {
for (var field in declaration.variables.variables) {
map[field.element] = field;
}
} else {
map[declaration.element] = declaration;
}
}
}
/// Called to emit all top-level 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 _emitDeclaration(Element e) {
var item = _loader.emitDeclaration(e, (AstNode node) {
// 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);
return _visit(node);
});
if (item != null) _moduleItems.add(item);
}
void _declareBeforeUse(Element e) {
_loader.declareBeforeUse(e, _emitDeclaration);
}
void _finishDeclarationsInUnit(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 _emitDeclaration.
for (var declaration in unit.declarations) {
var element = declaration.element;
if (element != null) {
_emitDeclaration(element);
} else {
declaration.accept(this);
}
}
for (var directive in unit.directives) {
directive.accept(this);
}
}
@override
void visitLibraryDirective(LibraryDirective node) {}
@override
void 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?
}
@override
void visitPartDirective(PartDirective node) {}
@override
void visitPartOfDirective(PartOfDirective node) {}
@override
void visitExportDirective(ExportDirective node) {
ExportElement element = node.element;
var currentLibrary = element.library;
var currentNames = currentLibrary.publicNamespace.definedNames;
var exportedNames =
new NamespaceBuilder().createExportNamespaceForDirective(element);
var libraryName = emitLibraryName(currentLibrary);
// TODO(jmesserly): we could collect all of the names for bulk re-export,
// but this is easier to implement for now.
void emitExport(Element export, {String suffix: ''}) {
var name = _emitTopLevelName(export, suffix: suffix);
if (export is TypeDefiningElement ||
export is FunctionElement ||
_eagerTopLevelFields.contains(export)) {
// classes, typedefs, functions, and eager init fields can be assigned
// directly.
// TODO(jmesserly): we don't know about eager init fields from other
// modules we import, so we will never go down this code path for them.
_moduleItems
.add(js.statement('#.# = #;', [libraryName, name.selector, name]));
}
}
// We only need to export main as it is the only method party 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 == null) return;
if (export is PropertyAccessorElement) {
export = (export as PropertyAccessorElement).variable;
}
// Don't allow redefining names from this library.
if (currentNames.containsKey(export.name)) return;
if (export.isSynthetic && export is PropertyInducingElement) {
_emitDeclaration(export.getter);
_emitDeclaration(export.setter);
} else {
_emitDeclaration(export);
}
emitExport(export);
}
@override
visitAsExpression(AsExpression node) {
Expression fromExpr = node.expression;
var from = getStaticType(fromExpr);
var to = node.type.type;
JS.Expression jsFrom = _visit(fromExpr);
if (_inWhitelistCode(node)) return jsFrom;
// Skip the cast if it's not needed.
if (rules.isSubtypeOf(from, to)) return jsFrom;
// All Dart number types map to a JS double.
if (_isNumberInJS(from) && _isNumberInJS(to)) {
// Make sure to check when converting to int.
if (from != types.intType && to == types.intType) {
// TODO(jmesserly): fuse this with notNull check.
return _callHelper('asInt(#)', jsFrom);
}
// A no-op in JavaScript.
return jsFrom;
}
var type = _emitType(to,
nameType: options.nameTypeTests || options.hoistTypeTests,
hoistType: options.hoistTypeTests);
if (CoercionReifier.isImplicitCast(node)) {
return js.call('#._check(#)', [type, jsFrom]);
} else {
return js.call('#.as(#)', [type, 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 = _visit(node.expression);
var typeofName = _jsTypeofName(type);
if (typeofName != null) {
result = js.call('typeof # == #', [lhs, js.string(typeofName, "'")]);
} else {
// Always go through a runtime helper, because implicit interfaces.
var castType = _emitType(type,
nameType: options.nameTypeTests || options.hoistTypeTests,
hoistType: options.hoistTypeTests);
result = js.call('#.is(#)', [castType, lhs]);
}
if (node.notOperator != null) {
return js.call('!#', result);
}
return result;
}
String _jsTypeofName(DartType t) {
if (_isNumberInJS(t)) return 'number';
if (t == types.stringType) return 'string';
if (t == types.boolType) return 'boolean';
return null;
}
@override
visitFunctionTypeAlias(FunctionTypeAlias node) {
FunctionTypeAliasElement element = node.element;
JS.Expression body = annotate(
_callHelper('typedef(#, () => #)', [
js.string(element.name, "'"),
_emitType(element.type, nameType: false, lowerTypedef: true)
]),
node,
element);
var typeFormals = element.typeParameters;
if (typeFormals.isNotEmpty) {
return _defineClassTypeArguments(element, typeFormals,
js.statement('const # = #;', [element.name, body]));
} else {
return js.statement('# = #;', [_emitTopLevelName(element), body]);
}
}
@override
JS.Expression visitTypeName(TypeName node) {
if (node.type == null) {
// TODO(jmesserly): if the type fails to resolve, should we generate code
// that throws instead?
assert(options.unsafeForceCompile || options.replCompile);
return _callHelper('dynamic');
}
return _emitType(node.type);
}
@override
JS.Statement visitClassTypeAlias(ClassTypeAlias node) {
ClassElement element = node.element;
// Forward all generative constructors from the base class.
var methods = <JS.Method>[];
var supertype = element.supertype;
if (!supertype.isObject) {
for (var ctor in element.constructors) {
var parentCtor = supertype.lookUpConstructor(ctor.name, ctor.library);
// TODO(jmesserly): this avoids spread args for perf. Revisit.
var jsParams = <JS.Identifier>[];
for (var p in ctor.parameters) {
if (p.parameterKind != ParameterKind.NAMED) {
jsParams.add(new JS.Identifier(p.name));
} else {
jsParams.add(new JS.TemporaryId('namedArgs'));
break;
}
}
var fun = js.call('function(#) { super.#(#); }',
[jsParams, _constructorName(parentCtor), jsParams]) as JS.Fun;
methods.add(new JS.Method(_constructorName(ctor), fun));
}
}
var classExpr = _emitClassExpression(element, methods);
var typeFormals = element.typeParameters;
if (typeFormals.isNotEmpty) {
return _defineClassTypeArguments(
element, typeFormals, new JS.ClassDeclaration(classExpr));
} else {
return js.statement('# = #;', [_emitTopLevelName(element), classExpr]);
}
}
JS.Statement _emitJsType(Element e) {
var jsTypeName = getAnnotationName(e, isJSAnnotation);
if (jsTypeName == null || jsTypeName == e.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(e), jsTypeName]);
}
@override
JS.Statement visitClassDeclaration(ClassDeclaration node) {
var classElem = node.element;
// If this class is annotated with `@JS`, then there is nothing to emit.
if (findAnnotation(classElem, isPublicJSAnnotation) != null) return null;
// If this is a JavaScript type, emit it now and then exit.
var jsTypeDef = _emitJsType(classElem);
if (jsTypeDef != null) return jsTypeDef;
var ctors = <ConstructorDeclaration>[];
var fields = <FieldDeclaration>[];
var staticFields = <FieldDeclaration>[];
var methods = <MethodDeclaration>[];
// True if a "call" method or getter exists.
bool isCallable = false;
for (var member in node.members) {
if (member is ConstructorDeclaration) {
ctors.add(member);
} else if (member is FieldDeclaration) {
(member.isStatic ? staticFields : fields).add(member);
} else if (member is MethodDeclaration) {
methods.add(member);
if (member.name.name == 'call' && !member.isSetter) {
//
// Make sure "call" has a statically known function type:
//
// - if it's a method, then it does because all methods do,
// - if it's a getter, check the return type.
//
// Other cases like a getter returning dynamic/Object/Function will be
// handled at runtime by the dynamic call mechanism. So we only
// concern ourselves with statically known function types.
//
// For the same reason, we can ignore "noSuchMethod".
// call-implemented-by-nSM will be dispatched by dcall at runtime.
//
isCallable = !member.isGetter || member.returnType is FunctionType;
}
}
}
JS.Expression className;
if (classElem.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(classElem.name);
} else {
className = _emitTopLevelName(classElem);
}
var allFields = fields.toList()..addAll(staticFields);
var superclasses = getSuperclasses(classElem);
var virtualFields = <FieldElement, JS.TemporaryId>{};
var virtualFieldSymbols = <JS.Statement>[];
var staticFieldOverrides = new HashSet<FieldElement>();
var extensions = _extensionsToImplement(classElem);
_registerPropertyOverrides(classElem, className, superclasses, allFields,
virtualFields, virtualFieldSymbols, staticFieldOverrides, extensions);
var classExpr = _emitClassExpression(classElem,
_emitClassMethods(node, ctors, fields, superclasses, virtualFields),
fields: allFields);
var body = <JS.Statement>[];
_initExtensionSymbols(classElem, methods, fields, body);
_emitSuperHelperSymbols(_superHelperSymbols, body);
// Emit the class, e.g. `core.Object = class Object { ... }`
_defineClass(classElem, className, classExpr, isCallable, body);
// Emit things that come after the ES6 `class ... { ... }`.
var jsPeerNames = _getJSPeerNames(classElem);
JS.Statement deferredBaseClass =
_setBaseClass(classElem, className, jsPeerNames, body);
_emitClassTypeTests(classElem, className, body);
_defineNamedConstructors(ctors, body, className, isCallable);
body.addAll(virtualFieldSymbols);
_emitClassSignature(
methods, allFields, classElem, ctors, extensions, className, body);
_defineExtensionMembers(extensions, className, body);
_emitClassMetadata(node.metadata, className, body);
JS.Statement classDef = _statement(body);
var typeFormals = classElem.typeParameters;
if (typeFormals.isNotEmpty) {
classDef = _defineClassTypeArguments(
classElem, typeFormals, classDef, className, deferredBaseClass);
}
body = <JS.Statement>[classDef];
_emitStaticFields(staticFields, staticFieldOverrides, classElem, body);
for (var peer in jsPeerNames) {
_registerExtensionType(classElem, peer, body);
}
return _statement(body);
}
/// Emits code to support a class with a "call" method and an unnamed
/// constructor.
///
/// This ensures instances created by the unnamed constructor are functions.
/// Named constructors are handled elsewhere, see [_defineNamedConstructors].
JS.Expression _emitCallableClass(
JS.ClassExpression classExpr, ConstructorElement unnamedCtor) {
var ctor = new JS.NamedFunction(
classExpr.name, _emitCallableClassConstructor(unnamedCtor));
// Name the constructor function the same as the class.
return _callHelper('callableClass(#, #)', [ctor, classExpr]);
}
/// Emits a constructor that ensures instances of this class are callable as
/// functions in JavaScript.
JS.Fun _emitCallableClassConstructor(ConstructorElement ctor) {
return js.call(
r'''function (...args) {
function call(...args) {
return call.call.apply(call, args);
}
call.__proto__ = this.__proto__;
call.#.apply(call, args);
return call;
}''',
[_constructorName(ctor)]);
}
void _emitClassTypeTests(ClassElement classElem, JS.Expression className,
List<JS.Statement> body) {
if (classElem == objectClass) {
// We rely on ES6 static inheritance. All types that are represented by
// class constructor functions will see these definitions, with [this]
// being bound to the class constructor.
// The 'instanceof' checks don't work for primitive types (which have fast
// definitions below) and don't work for native types. In those cases we
// fall through to the general purpose checking code.
body.add(js.statement(
'#.is = function is_Object(o) {'
' if (o instanceof this) return true;'
' return #.is(o, this);'
'}',
[className, _runtimeModule]));
body.add(js.statement(
'#.as = function as_Object(o) {'
' if (o == null || o instanceof this) return o;'
' return #.as(o, this);'
'}',
[className, _runtimeModule]));
body.add(js.statement(
'#._check = function check_Object(o) {'
' if (o == null || o instanceof this) return o;'
' return #.check(o, this);'
'}',
[className, _runtimeModule]));
return;
}
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, #);'
'}',
[className, _runtimeModule, className]));
body.add(js.statement(
'#._check = function check_String(o) {'
' if (typeof o == "string" || o == null) return o;'
' return #.check(o, #);'
'}',
[className, _runtimeModule, className]));
return;
}
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, #);'
'}',
[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 #.check(o, #);'
'}',
[className, _runtimeModule, className]));
return;
}
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, #);'
'}',
[className, _runtimeModule, className]));
body.add(js.statement(
'#._check = function check_Null(o) {'
' if (o == null) return o;'
' return #.check(o, #);'
'}',
[className, _runtimeModule, className]));
return;
}
if (classElem == numClass) {
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, #);'
'}',
[className, _runtimeModule, className]));
body.add(js.statement(
'#._check = function check_num(o) {'
' if (typeof o == "number" || o == null) return o;'
' return #.check(o, #);'
'}',
[className, _runtimeModule, className]));
return;
}
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, #);'
'}',
[className, _runtimeModule, className]));
body.add(js.statement(
'#._check = function check_bool(o) {'
' if (o === true || o === false || o == null) return o;'
' return #.check(o, #);'
'}',
[className, _runtimeModule, className]));
return;
}
// TODO(sra): Add special cases for hot tests like `x is html.Element`.
// `instanceof` check is futile for classes that are Interceptor classes.
ClassElement parent = classElem;
while (parent != objectClass) {
if (parent == interceptorClass) {
if (classElem == interceptorClass) {
// Place non-instanceof version of checks on Interceptor. All
// interceptor classes will inherit the methods via ES6 class static
// inheritance.
body.add(_callHelperStatement('addTypeTests(#);', className));
// TODO(sra): We could place on the extension type a pointer to the
// peer constructor and use that for the `instanceof` check, e.g.
//
// if (o instanceof this[_peerConstructor]) return o;
//
}
return;
}
parent = parent.type.superclass.element;
}
// Choose between 'simple' checks, which are often accelerated by
// `instanceof`, and other checks, which are slowed down by taking time to
// do an `instanceof` check that is futile or likely futile.
//
// The `instanceof` check is futile for (1) a class that is only used as a
// mixin, or (2) is only used as an interface in an `implements` clause, and
// is likely futile (3) if the class has type parameters, since `Foo` aka
// `Foo<dynamic>` is not a superclass of `Foo<int>`. The first two are
// whole-program properites, but we can check for the last case.
// Since ES6 classes have inheritance of static properties, we need only
// install checks that differ from the parent.
bool isSimple(ClassElement classElement) {
if (classElement.typeParameters.isNotEmpty) return false;
return true;
}
assert(classElem != objectClass);
bool thisIsSimple = isSimple(classElem);
bool superIsSimple = isSimple(classElem.type.superclass.element);
if (thisIsSimple == superIsSimple) return;
if (thisIsSimple) {
body.add(_callHelperStatement('addSimpleTypeTests(#);', className));
} else {
body.add(_callHelperStatement('addTypeTests(#);', className));
}
}
void _emitSuperHelperSymbols(
List<JS.TemporaryId> superHelperSymbols, List<JS.Statement> body) {
for (var id in superHelperSymbols) {
body.add(js.statement('const # = Symbol(#)', [id, js.string(id.name)]));
}
superHelperSymbols.clear();
}
void _registerPropertyOverrides(
ClassElement classElem,
JS.Expression className,
List<ClassElement> superclasses,
List<FieldDeclaration> fields,
Map<FieldElement, JS.TemporaryId> virtualFields,
List<JS.Statement> virtualFieldSymbols,
Set<FieldElement> staticFieldOverrides,
Iterable<ExecutableElement> extensionMembers) {
var extensionNames =
new HashSet<String>.from(extensionMembers.map((e) => e.name));
for (var field in fields) {
for (VariableDeclaration fieldDecl in field.fields.variables) {
var field = fieldDecl.element as FieldElement;
var overrideInfo = checkForPropertyOverride(field, superclasses);
if (overrideInfo.foundGetter ||
overrideInfo.foundSetter ||
extensionNames.contains(field.name)) {
if (field.isStatic) {
staticFieldOverrides.add(field);
} else {
var virtualField = new JS.TemporaryId(field.name);
virtualFields[field] = virtualField;
virtualFieldSymbols.add(js.statement(
'const # = Symbol(#.name + "." + #.toString());',
[virtualField, className, _declareMemberName(field.getter)]));
}
}
}
}
}
void _defineClass(ClassElement classElem, JS.Expression className,
JS.ClassExpression classExpr, bool isCallable, List<JS.Statement> body) {
JS.Expression callableClass;
if (isCallable && classElem.unnamedConstructor != null) {
callableClass =
_emitCallableClass(classExpr, classElem.unnamedConstructor);
}
if (classElem.typeParameters.isNotEmpty) {
if (callableClass != null) {
body.add(js.statement('const # = #;', [classExpr.name, callableClass]));
} else {
body.add(new JS.ClassDeclaration(classExpr));
}
} else {
body.add(js.statement('# = #;', [className, callableClass ?? classExpr]));
}
}
List<JS.Identifier> _emitTypeFormals(List<TypeParameterElement> typeFormals) {
return typeFormals
.map((t) => new JS.Identifier(t.name))
.toList(growable: false);
}
/// Emits a field declaration for TypeScript & Closure's ES6_TYPED
/// (e.g. `class Foo { i: string; }`)
JS.VariableDeclarationList _emitTypeScriptField(FieldDeclaration field) {
return new JS.VariableDeclarationList(
field.isStatic ? 'static' : null,
field.fields.variables
.map((decl) => new JS.VariableInitialization(
new JS.Identifier(
// TODO(ochafik): use a refactored _emitMemberName instead.
decl.name.name,
type: emitTypeRef(decl.element.type)),
null))
.toList(growable: false));
}
@override
JS.Statement visitEnumDeclaration(EnumDeclaration node) {
var element = node.element;
var type = element.type;
// Generate a class per section 13 of the spec.
// TODO(vsm): Generate any accompanying metadata
// Create constructor and initialize index
var constructor = new JS.Method(_propertyName('new'),
js.call('function(index) { this.index = index; }') as JS.Fun);
var fields = new List<FieldElement>.from(
element.fields.where((f) => f.type == type));
// Create toString() method
var nameProperties = new List<JS.Property>(fields.length);
for (var i = 0; i < fields.length; ++i) {
nameProperties[i] = new JS.Property(
js.number(i), js.string('${type.name}.${fields[i].name}'));
}
var nameMap = new JS.ObjectInitializer(nameProperties, multiline: true);
var toStringF = new JS.Method(js.string('toString'),
js.call('function() { return #[this.index]; }', nameMap) as JS.Fun);
// Create enum class
var classExpr = new JS.ClassExpression(new JS.Identifier(type.name),
_emitClassHeritage(element), [constructor, toStringF]);
var id = _emitTopLevelName(element);
var result = [
js.statement('# = #', [id, classExpr])
];
// defineEnumValues internally depends on dart.constList which uses
// _interceptors.JSArray.
_declareBeforeUse(_jsArray);
// Create static fields for each enum value, and the "values" getter
result.add(_callHelperStatement('defineEnumValues(#, #);', [
id,
new JS.ArrayInitializer(fields.map((f) => _propertyName(f.name)).toList(),
multiline: true)
]));
return _statement(result);
}
/// Wraps a possibly generic class in its type arguments.
JS.Statement _defineClassTypeArguments(TypeDefiningElement element,
List<TypeParameterElement> formals, JS.Statement body,
[JS.Expression className, JS.Statement deferredBaseClass]) {
assert(formals.isNotEmpty);
var typeConstructor = js.call('(#) => { #; #; return #; }', [
_emitTypeFormals(formals),
_typeTable.discharge(formals),
body,
element.name
]);
var genericArgs = [typeConstructor];
if (deferredBaseClass != null) {
genericArgs.add(js.call('(#) => { #; }', [className, deferredBaseClass]));
}
var genericCall = _callHelper('generic(#)', [genericArgs]);
if (element.library.isDartAsync &&
(element.name == "Future" || element.name == "_Future")) {
genericCall = _callHelper('flattenFutures(#)', [genericCall]);
}
var genericDef = js.statement(
'# = #;', [_emitTopLevelName(element, suffix: r'$'), genericCall]);
var dynType = fillDynamicTypeArgs(element.type);
var genericInst = _emitType(dynType, lowerGeneric: true);
return js.statement(
'{ #; # = #; }', [genericDef, _emitTopLevelName(element), genericInst]);
}
bool _deferIfNeeded(DartType type, ClassElement current) {
if (type is ParameterizedType) {
var typeArguments = type.typeArguments;
for (var typeArg in typeArguments) {
var typeElement = typeArg.element;
// FIXME(vsm): This does not track mutual recursive dependences.
if (current == typeElement || _deferIfNeeded(typeArg, current)) {
return true;
}
}
}
return false;
}
JS.ClassExpression _emitClassExpression(
ClassElement element, List<JS.Method> methods,
{List<FieldDeclaration> fields}) {
String name = element.name;
var heritage = _emitClassHeritage(element);
var typeParams = _emitTypeFormals(element.typeParameters);
var jsFields = fields?.map(_emitTypeScriptField)?.toList();
return new JS.ClassExpression(new JS.Identifier(name), heritage, methods,
typeParams: typeParams, fields: jsFields);
}
JS.Expression _emitClassHeritage(ClassElement element) {
var type = element.type;
if (type.isObject) return null;
_loader.startTopLevel(element);
// Find the super type
JS.Expression heritage;
var supertype = type.superclass;
if (_deferIfNeeded(supertype, element)) {
// Fall back to raw type.
supertype = fillDynamicTypeArgs(supertype.element.type);
_hasDeferredSupertype.add(element);
}
// We could choose to name the superclasses, but it's
// not clear that there's much benefit
heritage = _emitType(supertype, nameType: false);
if (type.mixins.isNotEmpty) {
var mixins =
type.mixins.map((t) => _emitType(t, nameType: false)).toList();
mixins.insert(0, heritage);
heritage = _callHelper('mixin(#)', [mixins]);
}
_loader.finishTopLevel(element);
return heritage;
}
/// 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.element as FieldElement;
var name = getAnnotationName(field, isJsName) ?? field.name;
// Generate getter
var fn = new JS.Fun([], js.statement('{ return this.#; }', [name]));
var method =
new JS.Method(_declareMemberName(field.getter), fn, isGetter: true);
jsMethods.add(method);
// Generate setter
if (!decl.isFinal) {
var value = new JS.TemporaryId('value');
fn = new JS.Fun(
[value], js.statement('{ this.# = #; }', [name, value]));
method = new JS.Method(_declareMemberName(field.setter), fn,
isSetter: true);
jsMethods.add(method);
}
}
}
return jsMethods;
}
List<JS.Method> _emitClassMethods(
ClassDeclaration node,
List<ConstructorDeclaration> ctors,
List<FieldDeclaration> fields,
List<ClassElement> superclasses,
Map<FieldElement, JS.TemporaryId> virtualFields) {
var element = node.element;
var type = element.type;
var isObject = type.isObject;
// Iff no constructor is specified for a class C, it implicitly has a
// default constructor `C() : super() {}`, unless C is class Object.
var jsMethods = <JS.Method>[];
if (isObject) {
// Implements Dart constructor behavior.
//
// Because of ES6 constructor restrictions (`this` is not available until
// `super` is called), we cannot emit an actual ES6 `constructor` on our
// classes and preserve the Dart initialization order.
//
// Instead we use the same trick as named constructors, and do them as
// instance methods that perform initialization.
//
// Therefore, dart:core Object gets the one real `constructor` and
// immediately bounces to the `new() { ... }` initializer, letting us
// bypass the ES6 restrictions.
//
// TODO(jmesserly): we'll need to rethink this.
// See <https://github.com/dart-lang/dev_compiler/issues/51>.
// This level of indirection will hurt performance.
jsMethods.add(new JS.Method(
_propertyName('constructor'),
js.call('function(...args) { return this.new.apply(this, args); }')
as JS.Fun));
} else if (ctors.isEmpty) {
jsMethods.add(_emitImplicitConstructor(node, fields, virtualFields));
}
bool hasJsPeer = findAnnotation(element, isJsPeerInterface) != null;
bool hasIterator = false;
for (var m in node.members) {
if (m is ConstructorDeclaration) {
jsMethods
.add(_emitConstructor(m, type, fields, virtualFields, isObject));
} else if (m is MethodDeclaration) {
jsMethods.add(_emitMethodDeclaration(type, m));
if (m.element is PropertyAccessorElement) {
jsMethods.add(_emitSuperAccessorWrapper(m, type, superclasses));
}
if (!hasJsPeer && m.isGetter && m.name.name == 'iterator') {
hasIterator = true;
jsMethods.add(_emitIterable(type));
}
} else if (m is FieldDeclaration) {
if (_extensionTypes.isNativeClass(element)) {
jsMethods.addAll(_emitNativeFieldAccessors(m));
continue;
}
if (m.isStatic) continue;
for (VariableDeclaration field in m.fields.variables) {
if (virtualFields.containsKey(field.element)) {
jsMethods.addAll(_emitVirtualFieldAccessor(field, virtualFields));
}
}
}
}
jsMethods.addAll(_implementMockInterfaces(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);
_superHelpers.clear();
return jsMethods.where((m) => m != null).toList(growable: false);
}
Iterable<ExecutableElement> _collectMockMethods(InterfaceType type) {
var element = type.element;
if (!_hasNoSuchMethod(element)) {
return [];
}
// Collect all unimplemented members.
//
// Initially, we track abstract and concrete members separately, then
// remove concrete from the abstract set. This is done because abstract
// members are allowed to "override" concrete ones in Dart.
// (In that case, it will still be treated as a concrete member and can be
// called at run time.)
var abstractMembers = new Map<String, ExecutableElement>();
var concreteMembers = new HashSet<String>();
void visit(InterfaceType type, bool isAbstract) {
if (type == null) return;
visit(type.superclass, isAbstract);
for (var m in type.mixins) visit(m, isAbstract);
for (var i in type.interfaces) visit(i, true);
var members = <ExecutableElement>[]
..addAll(type.methods)
..addAll(type.accessors);
for (var m in members) {
if (isAbstract || m.isAbstract) {
// Inconsistent signatures are disallowed, even with nSM, so we don't
// need to worry too much about which abstract member we save.
abstractMembers[m.name] = m;
} else {
concreteMembers.add(m.name);
}
}
}
visit(type, false);
concreteMembers.forEach(abstractMembers.remove);
return abstractMembers.values;
}
Iterable<JS.Method> _implementMockInterfaces(InterfaceType type) {
// TODO(jmesserly): every type with nSM will generate new stubs for all
// abstract members. For example:
//
// class C { m(); noSuchMethod(...) { ... } }
// class D extends C { m(); noSuchMethod(...) { ... } }
//
// We'll generate D.m even though it is not necessary.
//
// Doing better is a bit tricky, as our current codegen strategy for the
// mock methods encodes information about the number of arguments (and type
// arguments) that D expects.
return _collectMockMethods(type).map(_implementMockMethod);
}
/// 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('eatFood', args)));
/// }
JS.Method _implementMockMethod(ExecutableElement method) {
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 fnArgs = <JS.Parameter>[];
JS.Expression positionalArgs;
if (method.type.namedParameterTypes.isNotEmpty) {
addProperty('namedArguments', _callHelper('extractNamedArgs(#)', [args]));
}
if (method is MethodElement) {
addProperty('isMethod', js.boolean(true));
fnArgs.add(new JS.RestParameter(args));
positionalArgs = args;
} else {
var property = method as PropertyAccessorElement;
if (property.isGetter) {
addProperty('isGetter', js.boolean(true));
positionalArgs = new JS.ArrayInitializer([]);
} else if (property.isSetter) {
addProperty('isSetter', js.boolean(true));
fnArgs.add(args);
positionalArgs = new JS.ArrayInitializer([args]);
}
}
var fnBody = js.call('this.noSuchMethod(new #.InvocationImpl(#, #, #))', [
_runtimeModule,
_declareMemberName(method),
positionalArgs,
new JS.ObjectInitializer(invocationProps)
]);
if (!method.returnType.isDynamic) {
fnBody = js.call('#._check(#)', [_emitType(method.returnType), fnBody]);
}
var fn = new JS.Fun(fnArgs, js.statement('{ return #; }', [fnBody]),
typeParams: _emitTypeFormals(method.type.typeFormals));
// TODO(jmesserly): generic type arguments will get dropped.
// We have a similar issue with `dgsend` helpers.
return new JS.Method(
_declareMemberName(method,
useExtension:
_extensionTypes.isNativeClass(method.enclosingElement)),
_makeGenericFunction(fn),
isGetter: method is PropertyAccessorElement && method.isGetter,
isSetter: method is PropertyAccessorElement && method.isSetter,
isStatic: false);
}
/// Return `true` if the given [classElement] has a noSuchMethod() method
/// distinct from the one declared in class Object, as per the Dart Language
/// Specification (section 10.4).
// TODO(jmesserly): this was taken from error_verifier.dart
bool _hasNoSuchMethod(ClassElement classElement) {
// TODO(jmesserly): this is slow in Analyzer. It's a linear scan through all
// methods, up through the class hierarchy.
MethodElement method = classElement.lookUpMethod(
FunctionElement.NO_SUCH_METHOD_METHOD_NAME, classElement.library);
var definingClass = method?.enclosingElement;
return definingClass != null && !definingClass.type.isObject;
}
/// 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,
Map<FieldElement, JS.TemporaryId> virtualFields) {
var virtualField = virtualFields[field.element];
var result = <JS.Method>[];
var name = _declareMemberName((field.element as FieldElement).getter);
var getter = js.call('function() { return this[#]; }', [virtualField]);
result.add(new JS.Method(name, getter, isGetter: true));
if (field.isFinal) {
var setter = js.call('function(value) { super[#] = value; }', [name]);
result.add(new JS.Method(name, setter, isSetter: true));
} else {
var setter =
js.call('function(value) { this[#] = value; }', [virtualField]);
result.add(new JS.Method(name, setter, isSetter: true));
}
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 method,
InterfaceType type, List<ClassElement> superclasses) {
var methodElement = method.element as PropertyAccessorElement;
var field = methodElement.variable;
if (!field.isSynthetic) return null;
var propertyOverrideResult =
checkForPropertyOverride(methodElement.variable, superclasses);
// Generate a corresponding virtual getter / setter.
var name = _declareMemberName(methodElement);
if (method.isGetter) {
// Generate a setter
if (field.setter != null || !propertyOverrideResult.foundSetter)
return null;
var fn = js.call('function(value) { super[#] = value; }', [name]);
return new JS.Method(name, fn, isSetter: true);
} else {
// Generate a getter
if (field.getter != null || !propertyOverrideResult.foundGetter)
return null;
var fn = js.call('function() { return super[#]; }', [name]);
return new JS.Method(name, fn, isGetter: true);
}
}
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;
// 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: t)]) as JS.Fun);
}
JS.Expression _instantiateAnnotation(Annotation node) {
var element = node.element;
if (element is ConstructorElement) {
return _emitInstanceCreationExpression(element, element.returnType,
node.constructorName, node.arguments, true);
} else {
return _visit(node.name);
}
}
/// 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(ClassElement classElem) {
var jsPeerNames = getAnnotationName(
classElem,
(a) =>
isJsPeerInterface(a) ||
isNativeAnnotation(a) && _extensionTypes.isNativeClass(classElem));
if (jsPeerNames != null) {
// Omit the special name "!nonleaf" and any future hacks starting with "!"
return jsPeerNames
.split(',')
.where((peer) => !peer.startsWith("!"))
.toList();
} else {
return [];
}
}
void _registerExtensionType(
ClassElement classElem, String jsPeerName, List<JS.Statement> body) {
if (jsPeerName != null) {
body.add(_callHelperStatement('registerExtension(#.global.#, #);', [
_runtimeModule,
_propertyName(jsPeerName),
_emitTopLevelName(classElem)
]));
}
}
JS.Statement _setBaseClass(ClassElement classElem, JS.Expression className,
List<String> jsPeerNames, List<JS.Statement> body) {
var typeFormals = classElem.typeParameters;
if (jsPeerNames.isNotEmpty && typeFormals.isNotEmpty) {
for (var peer in jsPeerNames) {
// TODO(jmesserly): we should just extend Array in the first place
var newBaseClass = _callHelper('global.#', [peer]);
body.add(_callHelperStatement(
'setExtensionBaseClass(#, #);', [className, newBaseClass]));
}
} else if (_hasDeferredSupertype.contains(classElem)) {
var newBaseClass = _emitType(classElem.type.superclass,
nameType: false, subClass: classElem, className: className);
var deferredBaseClass = _callHelperStatement(
'setBaseClass(#, #);', [className, newBaseClass]);
if (typeFormals.isNotEmpty) return deferredBaseClass;
body.add(deferredBaseClass);
}
return null;
}
void _defineNamedConstructors(List<ConstructorDeclaration> ctors,
List<JS.Statement> body, JS.Expression className, bool isCallable) {
var code = isCallable
? 'defineNamedConstructorCallable(#, #, #);'
: 'defineNamedConstructor(#, #)';
for (ConstructorDeclaration member in ctors) {
if (member.name != null && member.factoryKeyword == null) {
var args = [className, _constructorName(member.element)];
if (isCallable) {
args.add(_emitCallableClassConstructor(member.element));
}
body.add(_callHelperStatement(code, args));
}
}
}
/// Emits static fields for a class, and initialize them eagerly if possible,
/// otherwise define them as lazy properties.
void _emitStaticFields(
List<FieldDeclaration> staticFields,
Set<FieldElement> staticFieldOverrides,
ClassElement classElem,
List<JS.Statement> body) {
var lazyStatics = <VariableDeclaration>[];
for (FieldDeclaration member in staticFields) {
for (VariableDeclaration field in member.fields.variables) {
JS.Statement eagerField =
_emitConstantStaticField(classElem, field, staticFieldOverrides);
if (eagerField != null) {
body.add(eagerField);
} else {
lazyStatics.add(field);
}
}
}
if (lazyStatics.isNotEmpty) {
body.add(_emitLazyFields(classElem, lazyStatics));
}
}
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,
new JS.ArrayInitializer(
new List<JS.Expression>.from(metadata.map(_instantiateAnnotation)))
]));
}
}
/// If a concrete class implements one of our extensions, we might need to
/// add forwarders.
void _defineExtensionMembers(List<ExecutableElement> extensions,
JS.Expression className, List<JS.Statement> body) {
// If a concrete class implements one of our extensions, we might need to
// add forwarders.
if (extensions.isNotEmpty) {
var methodNames = <JS.Expression>[];
for (var e in extensions) {
methodNames.add(_declareMemberName(e, useExtension: false));
}
body.add(_callHelperStatement('defineExtensionMembers(#, #);', [
className,
new JS.ArrayInitializer(methodNames, multiline: methodNames.length > 4)
]));
}
}
/// Emit the signature on the class recording the runtime type information
void _emitClassSignature(
List<MethodDeclaration> methods,
List<FieldDeclaration> fields,
ClassElement classElem,
List<ConstructorDeclaration> ctors,
List<ExecutableElement> extensions,
JS.Expression className,
List<JS.Statement> body) {
if (classElem.interfaces.isNotEmpty) {
body.add(js.statement('#[#.implements] = () => #;', [
className,
_runtimeModule,
new JS.ArrayInitializer(
new List<JS.Expression>.from(classElem.interfaces.map(_emitType)))
]));
}
var tStaticMethods = <JS.Property>[];
var tInstanceMethods = <JS.Property>[];
var tStaticGetters = <JS.Property>[];
var tInstanceGetters = <JS.Property>[];
var tStaticSetters = <JS.Property>[];
var tInstanceSetters = <JS.Property>[];
var sNames = <JS.Expression>[];
for (MethodDeclaration node in methods) {
var name = node.name.name;
var element = node.element;
// TODO(vsm): Clean up all the nasty duplication.
if (node.isAbstract) {
continue;
}
Function lookup;
List<JS.Property> tMember;
JS.Expression type;
if (node.isGetter) {
lookup = classElem.lookUpInheritedConcreteGetter;
tMember = node.isStatic ? tStaticGetters : tInstanceGetters;
} else if (node.isSetter) {
lookup = classElem.lookUpInheritedConcreteSetter;
tMember = node.isStatic ? tStaticSetters : tInstanceSetters;
} else {
// Method
lookup = classElem.lookUpInheritedConcreteMethod;
tMember = node.isStatic ? tStaticMethods : tInstanceMethods;
}
type = _emitAnnotatedFunctionType(element.type, node.metadata,
parameters: node.parameters?.parameters,
nameType: options.hoistSignatureTypes,
hoistType: options.hoistSignatureTypes,
definite: true);
var inheritedElement = lookup(name, currentLibrary);
if (inheritedElement != null && inheritedElement.type == element.type) {
continue;
}
var memberName = _declareMemberName(element);
var property = new JS.Property(memberName, type);
tMember.add(property);
// TODO(vsm): Why do we need this?
if (node.isStatic && !node.isGetter && !node.isSetter) {
sNames.add(memberName);
}
}
var tInstanceFields = <JS.Property>[];
var tStaticFields = <JS.Property>[];
for (FieldDeclaration node in fields) {
for (VariableDeclaration field in node.fields.variables) {
var element = field.element as FieldElement;
var memberName = _declareMemberName(element.getter);
var type = _emitAnnotatedType(element.type, node.metadata);
var property = new JS.Property(memberName, type);
(node.isStatic ? tStaticFields : tInstanceFields).add(property);
}
}
var tCtors = <JS.Property>[];
for (ConstructorDeclaration node in ctors) {
var memberName = _constructorName(node.element);
var element = node.element;
var type = _emitAnnotatedFunctionType(element.type, node.metadata,
parameters: node.parameters.parameters,
nameType: options.hoistSignatureTypes,
hoistType: options.hoistSignatureTypes,
definite: true);
var property = new JS.Property(memberName, type);
tCtors.add(property);
}
JS.Property build(String name, List<JS.Property> elements) {
var o =
new JS.ObjectInitializer(elements, multiline: elements.length > 1);
// TODO(vsm): Remove
var e = js.call('() => #', o);
return new JS.Property(_propertyName(name), e);
}
var sigFields = <JS.Property>[];
if (!tCtors.isEmpty) {
sigFields.add(build('constructors', tCtors));
}
if (!tInstanceFields.isEmpty) {
sigFields.add(build('fields', tInstanceFields));
}
if (!tInstanceGetters.isEmpty) {
sigFields.add(build('getters', tInstanceGetters));
}
if (!tInstanceSetters.isEmpty) {
sigFields.add(build('setters', tInstanceSetters));
}
if (!tInstanceMethods.isEmpty) {
sigFields.add(build('methods', tInstanceMethods));
}
if (!tStaticFields.isEmpty) {
sigFields.add(build('sfields', tStaticFields));
}
if (!tStaticGetters.isEmpty) {
sigFields.add(build('sgetters', tStaticGetters));
}
if (!tStaticSetters.isEmpty) {
sigFields.add(build('ssetters', tStaticSetters));
}
if (!tStaticMethods.isEmpty) {
assert(!sNames.isEmpty);
// TODO(vsm): Why do we need this names field?
var aNames = new JS.Property(
_propertyName('names'), new JS.ArrayInitializer(sNames));
sigFields.add(build('statics', tStaticMethods));
sigFields.add(aNames);
}
if (!sigFields.isEmpty || extensions.isNotEmpty) {
var sig = new JS.ObjectInitializer(sigFields);
body.add(_callHelperStatement('setSignature(#, #);', [className, sig]));
}
// Add static property dart._runtimeType to Object.
// All other Dart classes will (statically) inherit this property.
if (classElem == objectClass) {
body.add(_callHelperStatement('tagComputed(#, () => #.#);',
[className, emitLibraryName(dartCoreLibrary), 'Type']));
}
}
/// Ensure `dartx.` symbols we will use are present.
void _initExtensionSymbols(
ClassElement classElem,
List<MethodDeclaration> methods,
List<FieldDeclaration> fields,
List<JS.Statement> body) {
if (_extensionTypes.hasNativeSubtype(classElem.type)) {
var dartxNames = <JS.Expression>[];
for (var m in methods) {
if (!m.isAbstract && !m.isStatic && m.element.isPublic) {
dartxNames.add(_declareMemberName(m.element, useExtension: false));
}
}
for (var fieldDecl in fields) {
if (!fieldDecl.isStatic) {
for (var field in fieldDecl.fields.variables) {
var e = field.element as FieldElement;
if (e.isPublic) {
dartxNames.add(_declareMemberName(e.getter, useExtension: false));
}
}
}
}
if (dartxNames.isNotEmpty) {
body.add(_callHelperStatement('defineExtensionNames(#)',
[new JS.ArrayInitializer(dartxNames, multiline: true)]));
}
}
}
List<ExecutableElement> _extensionsToImplement(ClassElement element) {
var members = <ExecutableElement>[];
if (_extensionTypes.isNativeClass(element)) return members;
// Collect all extension types we implement.
var type = element.type;
var types = _extensionTypes.collectNativeInterfaces(element);
if (types.isEmpty) return members;
// Collect all possible extension method names.
var extensionMembers = new HashSet<String>();
for (var t in types) {
for (var m in [t.methods, t.accessors].expand((e) => e)) {
if (!m.isStatic && m.isPublic) extensionMembers.add(m.name);
}
}
// Collect all of extension methods this type implements.
for (var m in [type.methods, type.accessors].expand((e) => e)) {
if (!m.isStatic && !m.isAbstract && extensionMembers.contains(m.name)) {
members.add(m);
}
}
return members;
}
/// Generates the implicit default constructor for class C of the form
/// `C() : super() {}`.
JS.Method _emitImplicitConstructor(
ClassDeclaration node,
List<FieldDeclaration> fields,
Map<FieldElement, JS.TemporaryId> virtualFields) {
// If we don't have a method body, skip this.
var superCall = _superConstructorCall(node.element);
if (fields.isEmpty && superCall == null) return null;
var initFields = _initializeFields(node, fields, virtualFields);
List<JS.Statement> body = [initFields];
if (superCall != null) {
body.add(superCall);
}
var name = _constructorName(node.element.unnamedConstructor);
return annotate(
new JS.Method(name, js.call('function() { #; }', [body]) as JS.Fun),
node,
node.element);
}
JS.Method _emitConstructor(
ConstructorDeclaration node,
InterfaceType type,
List<FieldDeclaration> fields,
Map<FieldElement, JS.TemporaryId> virtualFields,
bool isObject) {
if (_externalOrNative(node)) return null;
var name = _constructorName(node.element);
var returnType = emitTypeRef(node.element.enclosingElement.type);
// Wacky factory redirecting constructors: factory Foo.q(x, y) = Bar.baz;
var redirect = node.redirectedConstructor;
if (redirect != null) {
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 =
visitFormalParameterList(node.parameters, destructure: false);
var fun = new JS.Fun(
params,
js.statement('{ return $newKeyword #(#); }',
[_visit(redirect) as JS.Node, params]),
returnType: returnType);
return annotate(
new JS.Method(name, fun, isStatic: true), node, node.element);
}
// For const constructors we need to ensure default values are
// available for use by top-level constant initializers.
ClassDeclaration cls = node.parent;
if (node.constKeyword != null) _loader.startTopLevel(cls.element);
var params = visitFormalParameterList(node.parameters);
if (node.constKeyword != null) _loader.finishTopLevel(cls.element);
// Factory constructors are essentially static methods.
if (node.factoryKeyword != null) {
var body = <JS.Statement>[];
var init = _emitArgumentInitializers(node, constructor: true);
if (init != null) body.add(init);
body.add(_visit(node.body));
var fun = new JS.Fun(params, new JS.Block(body), returnType: returnType);
return annotate(
new JS.Method(name, fun, isStatic: true), node, node.element);
}
// Code generation for Object's constructor.
var savedFunction = _currentFunction;
_currentFunction = node.body;
var body = _emitConstructorBody(node, fields, virtualFields);
_currentFunction = savedFunction;
// We generate constructors as initializer methods in the class;
// this allows use of `super` for instance methods/properties.
// It also avoids V8 restrictions on `super` in default constructors.
return annotate(
new JS.Method(name, new JS.Fun(params, body, returnType: returnType)),
node,
node.element);
}
JS.Expression _constructorName(ConstructorElement ctor) {
var name = ctor.name;
if (name == '') {
// Default constructors (factory or not) use `new` as their name.
return _propertyName('new');
}
return _emitMemberName(name, isStatic: true);
}
JS.Block _emitConstructorBody(
ConstructorDeclaration node,
List<FieldDeclaration> fields,
Map<FieldElement, JS.TemporaryId> virtualFields) {
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.
if (node.constKeyword != null) _loader.startTopLevel(cls.element);
var init = _emitArgumentInitializers(node, constructor: true);
if (node.constKeyword != null) _loader.finishTopLevel(cls.element);
if (init != null) body.add(init);
// 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 RedirectingConstructorInvocation,
orElse: () => null);
if (redirectCall != null) {
body.add(_visit(redirectCall));
return new 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(cls, fields, virtualFields, 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 jsSuper = _superConstructorCall(cls.element, superCall);
if (jsSuper != null) body.add(jsSuper);
body.add(_visit(node.body));
return new JS.Block(body)..sourceInformation = node;
}
@override
JS.Statement visitRedirectingConstructorInvocation(
RedirectingConstructorInvocation node) {
var ctor = node.staticElement;
var cls = ctor.enclosingElement;
// 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('#.prototype.#.call(this, #);', [
new JS.Identifier(cls.name),
_constructorName(ctor),
_visit(node.argumentList)
]);
}
JS.Statement _superConstructorCall(ClassElement element,
[SuperConstructorInvocation node]) {
if (element.supertype == null) {
assert(element.type.isObject || options.unsafeForceCompile);
return null;
}
ConstructorElement superCtor;
if (node != null) {
superCtor = node.staticElement;
} else {
// Get the supertype's unnamed constructor.
superCtor = element.supertype.element.unnamedConstructor;
}
if (superCtor == null) {
// This will only happen if the code has errors:
// we're trying to generate an implicit constructor for a type where
// we don't have a default constructor in the supertype.
assert(options.unsafeForceCompile);
return null;
}
if (superCtor.name == '' && !_hasUnnamedSuperConstructor(element)) {
return null;
}
var name = _constructorName(superCtor);
var args = node != null ? _visit(node.argumentList) : [];
return annotate(js.statement('super.#(#);', [name, args]), node);
}
bool _hasUnnamedSuperConstructor(ClassElement e) {
var supertype = e.supertype;
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;
if (!e.unnamedConstructor.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(
ClassDeclaration cls,
List<FieldDeclaration> fieldDecls,
Map<FieldElement, JS.TemporaryId> virtualFields,
[ConstructorDeclaration ctor]) {
bool isConst = ctor != null && ctor.constKeyword != null;
if (isConst) _loader.startTopLevel(cls.element);
// Run field initializers if they can have side-effects.
var fields = new Map<FieldElement, JS.Expression>();
var unsetFields = new Map<FieldElement, VariableDeclaration>();
for (var declaration in fieldDecls) {
for (var fieldNode in declaration.fields.variables) {
var element = fieldNode.element;
if (_constants.isFieldInitConstant(fieldNode)) {
unsetFields[element as FieldElement] = fieldNode;
} else {
fields[element as FieldElement] = _visitInitializer(fieldNode);
}
}
}
// Initialize fields from `this.fieldName` parameters.
if (ctor != null) {
for (var p in ctor.parameters.parameters) {
var element = p.element;
if (element is FieldFormalParameterElement) {
fields[element.field] = _emitSimpleIdentifier(p.identifier);
}
}
// Run constructor field initializers such as `: foo = bar.baz`
for (var init in ctor.initializers) {
if (init is ConstructorFieldInitializer) {
fields[init.fieldName.staticElement as FieldElement] =
_visit(init.expression);
}
}
}
for (var f in fields.keys) unsetFields.remove(f);
// Initialize all remaining fields
unsetFields.forEach((element, fieldNode) {
JS.Expression value;
if (fieldNode.initializer != null) {
value = _visit(fieldNode.initializer);
} else {
value = new JS.LiteralNull();
}
fields[element] = value;
});
var body = <JS.Statement>[];
fields.forEach((FieldElement e, JS.Expression initialValue) {
JS.Expression access = virtualFields[e] ?? _declareMemberName(e.getter);
body.add(js.statement('this.# = #;', [access, initialValue]));
});
if (isConst) _loader.finishTopLevel(cls.element);
return _statement(body);
}
FormalParameterList _parametersOf(node) {
// TODO(jmesserly): clean this up. If we can model ES6 spread/rest args, we
// could handle argument initializers more consistently in a separate
// lowering pass.
if (node is ConstructorDeclaration) return node.parameters;
if (node is MethodDeclaration) return node.parameters;
if (node is FunctionDeclaration) node = node.functionExpression;
return (node as FunctionExpression).parameters;
}
/// Emits argument initializers, which handles optional/named args, as well
/// as generic type checks needed due to our covariance.
JS.Statement _emitArgumentInitializers(node, {bool constructor: false}) {
// Constructor argument initializers are emitted earlier in the code, rather
// than always when we visit the function body, so we control it explicitly.
if (node is ConstructorDeclaration != constructor) return null;
var parameters = _parametersOf(node);
if (parameters == null) return null;
var body = <JS.Statement>[];
for (var param in parameters.parameters) {
var jsParam = _emitSimpleIdentifier(param.identifier);
if (!options.destructureNamedParams) {
if (param.kind == ParameterKind.NAMED) {
// Parameters will be passed using their real names, not the (possibly
// renamed) local variable.
var paramName = js.string(param.identifier.name, "'");
// TODO(ochafik): Fix `'prop' in obj` to please Closure's renaming.
body.add(js.statement('let # = # && # in # ? #.# : #;', [
jsParam,
namedArgumentTemp,
paramName,
namedArgumentTemp,
namedArgumentTemp,
paramName,
_defaultParamValue(param),
]));
} else if (param.kind == ParameterKind.POSITIONAL) {
body.add(js.statement('if (# === void 0) # = #;',
[jsParam, jsParam, _defaultParamValue(param)]));
}
}
// TODO(jmesserly): various problems here, see:
// https://github.com/dart-lang/dev_compiler/issues/116
var paramType = param.element.type;
if (node is MethodDeclaration &&
(param.element.isCovariant || _unsoundCovariant(paramType, true)) &&
!_inWhitelistCode(node)) {
var castType = _emitType(paramType,
nameType: options.nameTypeTests || options.hoistTypeTests,
hoistType: options.hoistTypeTests);
body.add(js.statement('#._check(#);', [castType, jsParam]));
}
}
return body.isEmpty ? null : _statement(body);
}
/// Given a type [t], return whether or not t is unsoundly covariant.
/// If [contravariant] is true, then t appears in a contravariant
/// position.
bool _unsoundCovariant(DartType t, bool contravariant) {
if (t is TypeParameterType) {
return contravariant && t.element.enclosingElement is ClassElement;
}
if (t is FunctionType) {
if (_unsoundCovariant(t.returnType, contravariant)) return true;
return t.parameters.any((p) => _unsoundCovariant(p.type, !contravariant));
}
if (t is ParameterizedType) {
return t.typeArguments.any((t) => _unsoundCovariant(t, contravariant));
}
return false;
}
JS.Expression _defaultParamValue(FormalParameter param) {
if (param is DefaultFormalParameter && param.defaultValue != null) {
return _visit(param.defaultValue);
} else {
return new JS.LiteralNull();
}
}
JS.Fun _emitNativeFunctionBody(MethodDeclaration node) {
String name =
getAnnotationName(node.element, isJSAnnotation) ?? node.name.name;
if (node.isGetter) {
return new JS.Fun([], js.statement('{ return this.#; }', [name]));
} else if (node.isSetter) {
var params =
visitFormalParameterList(node.parameters, destructure: false);
return new JS.Fun(
params, js.statement('{ this.# = #; }', [name, params.last]));
} else {
return js.call(
'function (...args) { return this.#.apply(this, args); }', name);
}
}
JS.Method _emitMethodDeclaration(InterfaceType type, MethodDeclaration node) {
if (node.isAbstract) {
return null;
}
JS.Fun fn;
if (_externalOrNative(node)) {
if (node.isStatic) {
// TODO(vsm): Do we need to handle this case?
return null;
}
fn = _emitNativeFunctionBody(node);
} else {
fn = _emitFunctionBody(node.element, node.parameters, node.body);
if (node.operatorKeyword != null &&
node.name.name == '[]=' &&
fn.params.isNotEmpty) {
// []= methods need to return the value. We could also address this at
// call sites, but it's cleaner to instead transform the operator method.
fn = _alwaysReturnLastParameter(fn);
}
fn = _makeGenericFunction(fn);
}
return annotate(
new JS.Method(_declareMemberName(node.element), fn,
isGetter: node.isGetter,
isSetter: node.isSetter,
isStatic: node.isStatic),
node,
node.element);
}
/// Transform the function so the last parameter is always returned.
///
/// This is useful for indexed set methods, which otherwise would not have
/// the right return value in JS.
JS.Fun _alwaysReturnLastParameter(JS.Fun fn) {
var body = fn.body;
if (JS.Return.foundIn(fn)) {
// If a return is inside body, transform `(params) { body }` to
// `(params) { (() => { body })(); return value; }`.
// TODO(jmesserly): we could instead generate the return differently,
// and avoid the immediately invoked function.
body = new JS.Call(new JS.ArrowFun([], fn.body), []).toStatement();
}
// Rewrite the function to include the return.
return new JS.Fun(
fn.params, new JS.Block([body, new JS.Return(fn.params.last)]),
typeParams: fn.typeParams,
returnType: fn.returnType)..sourceInformation = fn.sourceInformation;
}
@override
JS.Statement visitFunctionDeclaration(FunctionDeclaration node) {
assert(node.parent is CompilationUnit);
if (_externalOrNative(node)) return null;
// If we have a getter/setter pair, they need to be defined together.
if (node.isGetter) {
PropertyAccessorElement element = node.element;
var props = <JS.Method>[_emitTopLevelProperty(node)];
var setter = element.correspondingSetter;
if (setter != null) {
props.add(_loader.emitDeclaration(
setter, (node) => _emitTopLevelProperty(node)));
}
return _callHelperStatement('copyProperties(#, { # });',
[emitLibraryName(currentLibrary), props]);
}
if (node.isSetter) {
PropertyAccessorElement element = node.element;
var props = <JS.Method>[_emitTopLevelProperty(node)];
var getter = element.correspondingGetter;
if (getter != null) {
props.add(_loader.emitDeclaration(
getter, (node) => _emitTopLevelProperty(node)));
}
return _callHelperStatement('copyProperties(#, { # });',
[emitLibraryName(currentLibrary), props]);
}
var body = <JS.Statement>[];
var fn = _emitFunction(node.functionExpression);
if (currentLibrary.source.isInSystemLibrary &&
_isInlineJSFunction(node.functionExpression)) {
fn = _simplifyPassThroughArrowFunCallBody(fn);
}
var element = node.element;
var nameExpr = _emitTopLevelName(element);
body.add(annotate(js.statement('# = #', [nameExpr, fn]), node, element));
if (!_isDartRuntime(element.library)) {
body.add(_emitFunctionTagged(nameExpr, element.type, topLevel: true)
.toStatement());
}
return _statement(body);
}
bool _isInlineJSFunction(FunctionExpression functionExpression) {
var body = functionExpression.body;
if (body is ExpressionFunctionBody) {
return _isJSInvocation(body.expression);
} else if (body is BlockFunctionBody) {
var statements = body.block.statements;
if (statements.length == 1) {
var stat = statements[0];
if (stat is ReturnStatement) {
return _isJSInvocation(stat.expression);
}
}
}
return false;
}
bool _isJSInvocation(Expression expr) =>
expr is MethodInvocation && isInlineJS(expr.methodName.staticElement);
// Simplify `(args) => (() => { ... })()` to `(args) => { ... }`.
// Note: this allows silently passing args through to the body, which only
// works if we don't do weird renamings of Dart params.
JS.Fun _simplifyPassThroughArrowFunCallBody(JS.Fun fn) {
if (fn.body is JS.Block && fn.body.statements.length == 1) {
var stat = fn.body.statements.single;
if (stat is JS.Return && stat.value is JS.Call) {
JS.Call call = stat.value;
if (call.target is JS.ArrowFun && call.arguments.isEmpty) {
JS.ArrowFun innerFun = call.target;
if (innerFun.params.isEmpty) {
return new JS.Fun(fn.params, innerFun.body,
typeParams: fn.typeParams, returnType: fn.returnType);
}
}
}
}
return fn;
}
JS.Method _emitTopLevelProperty(FunctionDeclaration node) {
var name = node.name.name;
return annotate(
new JS.Method(
_propertyName(name), _emitFunction(node.functionExpression),
isGetter: node.isGetter, isSetter: node.isSetter),
node,
node.element);
}
bool _executesAtTopLevel(AstNode node) {
var ancestor = node.getAncestor((n) =>
n is FunctionBody ||
(n is FieldDeclaration && n.staticKeyword == null) ||
(n is ConstructorDeclaration && n.constKeyword == null));
return ancestor == null;
}
bool _typeIsLoaded(DartType type) {
if (type is FunctionType && (type.name == '' || type.name == null)) {
return (_typeIsLoaded(type.returnType) &&
type.optionalParameterTypes.every(_typeIsLoaded) &&
type.namedParameterTypes.values.every(_typeIsLoaded) &&
type.normalParameterTypes.every(_typeIsLoaded));
}
if (type.isDynamic || type.isVoid || type.isBottom) return true;
if (type is ParameterizedType && !type.typeArguments.every(_typeIsLoaded)) {
return false;
}
return _loader.isLoaded(type.element);
}
JS.Expression _emitFunctionTagged(JS.Expression fn, DartType type,
{topLevel: false}) {
var lazy = topLevel && !_typeIsLoaded(type);
var typeRep = _emitFunctionType(type, definite: true);
if (lazy) {
return _callHelper('lazyFn(#, () => #)', [fn, typeRep]);
} else {
return _callHelper('fn(#, #)', [fn, typeRep]);
}
}
/// Emits an arrow FunctionExpression node.
///
/// This should be used for all places in Dart's AST where FunctionExpression
/// appears and the function is actually in an Expression context. These
/// correspond to arrow functions in Dart.
///
/// Contrast with [_emitFunction].
@override
JS.Expression visitFunctionExpression(FunctionExpression node) {
assert(node.parent is! FunctionDeclaration &&
node.parent is! MethodDeclaration);
return _emitFunctionTagged(_emitArrowFunction(node), getStaticType(node),
topLevel: _executesAtTopLevel(node));
}
JS.ArrowFun _emitArrowFunction(FunctionExpression node) {
JS.Fun f = _emitFunctionBody(node.element, node.parameters, node.body);
JS.Node body = f.body;
// Simplify `=> { return e; }` to `=> e`
if (body is JS.Block) {
JS.Block block = body;
if (block.statements.length == 1) {
JS.Statement s = block.statements[0];
if (s is JS.Return && s.value != null) body = s.value;
}
}
// Convert `function(...) { ... }` to `(...) => ...`
// This is for readability, but it also ensures correct `this` binding.
var fn = new JS.ArrowFun(f.params, body,
typeParams: f.typeParams, returnType: f.returnType);
return annotate(_makeGenericArrowFun(fn), node);
}
JS.ArrowFun _makeGenericArrowFun(JS.ArrowFun fn) {
if (fn.typeParams == null || fn.typeParams.isEmpty) return fn;
return new JS.ArrowFun(fn.typeParams, fn);
}
JS.Fun _makeGenericFunction(JS.Fun fn) {
if (fn.typeParams == null || fn.typeParams.isEmpty) return fn;
// TODO(jmesserly): we could make these default to `dynamic`.
return new JS.Fun(
fn.typeParams,
new JS.Block([
// Convert the function to an => function, to ensure `this` binding.
new JS.Return(new JS.ArrowFun(fn.params, fn.body,
typeParams: fn.typeParams, returnType: fn.returnType))
]));
}
/// Emits a non-arrow FunctionExpression node.
///
/// This should be used for all places in Dart's AST where FunctionExpression
/// appears but the function is not actually in an Expression context, such
/// as methods, properties, and top-level functions.
///
/// Contrast with [visitFunctionExpression].
JS.Fun _emitFunction(FunctionExpression node) {
var fn = _emitFunctionBody(node.element, node.parameters, node.body);
return annotate(_makeGenericFunction(fn), node);
}
JS.Fun _emitFunctionBody(ExecutableElement element,
FormalParameterList parameters, FunctionBody body) {
FunctionType type = element.type;
// normal function (sync), vs (sync*, async, async*)
var stdFn = !(element.isAsynchronous || element.isGenerator);
var formals = visitFormalParameterList(parameters, destructure: stdFn);
var code = (stdFn)
? _visit(body)
: new JS.Block(
[_emitGeneratorFunctionBody(element, parameters, body).toReturn()]);
var typeFormals = _emitTypeFormals(type.typeFormals);
var returnType = emitTypeRef(type.returnType);
if (type.typeFormals.isNotEmpty) {
code = new JS.Block(<JS.Statement>[
new JS.Block(_typeTable.discharge(type.typeFormals)),
code
]);
}
return new JS.Fun(formals, code,
typeParams: typeFormals, returnType: returnType);
}
JS.Expression _emitGeneratorFunctionBody(ExecutableElement element,
FormalParameterList parameters, FunctionBody body) {
var kind = element.isSynchronous ? 'sync' : 'async';
if (element.isGenerator) kind += 'Star';
// Transforms `sync*` `async` and `async*` function bodies
// using ES6 generators.
//
// `sync*` wraps a generator in a Dart Iterable<T>:
//
// function name(<args>) {
// return dart.syncStar(function*(<args>) {
// <body>
// }, T, <args>).bind(this);
// }
//
// We need to include <args> in case any are mutated, so each `.iterator`
// gets the same initial values.
//
// TODO(jmesserly): we could omit the args for the common case where args
// are not mutated inside the generator.
//
// In the future, we might be able to simplify this, see:
// https://github.com/dart-lang/dev_compiler/issues/247.
//
// `async` works the same, but uses the `dart.async` helper.
//
// In the body of a `sync*` and `async`, `yield`/`await` are both generated
// simply as `yield`.
//
// `async*` uses the `dart.asyncStar` helper, and also has an extra `stream`
// argument to the generator, which is used for passing values to the
// _AsyncStarStreamController implementation type.
// `yield` is specially generated inside `async*`, see visitYieldStatement.
// `await` is generated as `yield`.
// runtime/_generators.js has an example of what the code is generated as.
var savedController = _asyncStarController;
var jsParams = visitFormalParameterList(parameters);
if (kind == 'asyncStar') {
_asyncStarController = new JS.TemporaryId('stream');
jsParams.insert(0, _asyncStarController);
} else {
_asyncStarController = null;
}
var savedSuperAllowed = _superAllowed;
_superAllowed = false;
// Visit the body with our async* controller set.
var jsBody = _visit(body);
_superAllowed = savedSuperAllowed;
_asyncStarController = savedController;
DartType returnType = _getExpectedReturnType(element);
JS.Expression gen = new JS.Fun(jsParams, jsBody,
isGenerator: true, returnType: emitTypeRef(returnType));
if (JS.This.foundIn(gen)) {
gen = js.call('#.bind(this)', gen);
}
var T = _emitType(returnType);
return _callHelper('#(#)', [
kind,
[gen, T]..addAll(visitFormalParameterList(parameters, destructure: false))
]);
}
@override
JS.Statement visitFunctionDeclarationStatement(
FunctionDeclarationStatement node) {
var func = node.functionDeclaration;
if (func.isGetter || func.isSetter) {
return js.comment('Unimplemented function get/set statement: $node');
}
var fn = _emitFunction(func.functionExpression);
var name = new JS.Identifier(func.name.name);
JS.Statement declareFn;
if (JS.This.foundIn(fn)) {
declareFn = js.statement('const # = #.bind(this);', [name, fn]);
} else {
declareFn = new JS.FunctionDeclaration(name, fn);
}
declareFn = annotate(declareFn, node, node.functionDeclaration.element);
return new JS.Block([
declareFn,
_emitFunctionTagged(name, func.element.type).toStatement()
]);
}
/// Emits a simple identifier, including handling an inferred generic
/// function instantiation.
@override
JS.Expression visitSimpleIdentifier(SimpleIdentifier node) {
var typeArgs = _getTypeArgs(node.staticElement, node.staticType);
var simpleId = _emitSimpleIdentifier(node);
if (typeArgs == null) {
return simpleId;
}
return _callHelper('gbind(#, #)', [simpleId, typeArgs]);
}
/// Emits a simple identifier, handling implicit `this` as well as
/// going through the qualified library name if necessary, but *not* handling
/// inferred generic function instantiation.
JS.Expression _emitSimpleIdentifier(SimpleIdentifier node) {
var accessor = node.staticElement;
if (accessor == null) {
return js.commentExpression(
'Unimplemented unknown name', new JS.Identifier(node.name));
}
// Get the original declaring element. If we had a property accessor, this
// indirects back to a (possibly synthetic) field.
var element = accessor;
if (accessor is PropertyAccessorElement) element = accessor.variable;
_declareBeforeUse(element);
// type literal
if (element is TypeDefiningElement) {
var typeName = _emitType(fillDynamicTypeArgs(element.type));
// If the type is a type literal expression in Dart code, wrap the raw
// runtime type in a "Type" instance.
if (!_isInForeignJS && _isTypeLiteral(node)) {
typeName = _callHelper('wrapType(#)', typeName);
}
return typeName;
}
// library member
if (element.enclosingElement is CompilationUnitElement) {
return _emitTopLevelName(element);
}
var name = element.name;
// Unqualified class member. This could mean implicit-this, or implicit
// call to a static from the same class.
if (element is ClassMemberElement && element is! ConstructorElement) {
bool isStatic = element.isStatic;
var type = element.enclosingElement.type;
var member = _emitMemberName(name,
isStatic: isStatic, type: type, element: element);
if (isStatic) {
var dynType = _emitStaticAccess(type);
return new JS.PropertyAccess(dynType, member);
}
// For instance members, we add implicit-this.
// For method tear-offs, we ensure it's a bound method.
var tearOff = element is MethodElement && !inInvocationContext(node);
if (tearOff) return _callHelper('bind(this, #)', member);
return js.call('this.#', member);
}
if (element is ParameterElement) {
return _emitParameter(element);
}
// If this is one of our compiler's temporary variables, return its JS form.
if (element is TemporaryVariableElement) {
return element.jsVariable;
}
return new JS.Identifier(name);
}
/// Returns `true` if the type name referred to by [node] is used in a
/// position where it should evaluate as a type literal -- an object of type
/// Type.
bool _isTypeLiteral(SimpleIdentifier node) {
var parent = node.parent;
// Static member call.
if (parent is MethodInvocation || parent is PropertyAccess) return false;
// An expression like "a.b".
if (parent is PrefixedIdentifier) {
// In "a.b", "b" may be a type literal, but "a", is not.
if (node != parent.identifier) return false;
// If the prefix expression is itself used as an invocation, like
// "a.b.c", then "b" is not a type literal.
var grand = parent.parent;
if (grand is MethodInvocation || grand is PropertyAccess) return false;
return true;
}
// In any other context, it's a type literal.
return true;
}
JS.Identifier _emitParameter(ParameterElement element,
{bool declaration: false}) {
// initializing formal parameter, e.g. `Point(this._x)`
// TODO(jmesserly): type ref is not attached in this case.
if (element.isInitializingFormal && element.isPrivate) {
/// Rename private names so they don't shadow the private field symbol.