Index: lib/src/codegen/js_codegen.dart |
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
deleted file mode 100644 |
index 35ebdb741cea72b4a74b53caea4c525f115fad63..0000000000000000000000000000000000000000 |
--- a/lib/src/codegen/js_codegen.dart |
+++ /dev/null |
@@ -1,3837 +0,0 @@ |
-// 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 HashSet, HashMap, SplayTreeSet; |
- |
-import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
-import 'package:analyzer/dart/ast/token.dart'; |
-import 'package:analyzer/dart/element/element.dart'; |
-import 'package:analyzer/dart/element/visitor.dart'; |
-import 'package:analyzer/dart/element/type.dart'; |
-import 'package:analyzer/dart/ast/ast.dart' hide ConstantEvaluator; |
-import 'package:analyzer/src/generated/constant.dart'; |
-//TODO(leafp): Remove deprecated dependency |
-//ignore: DEPRECATED_MEMBER_USE |
-import 'package:analyzer/src/generated/element.dart' |
- show DynamicElementImpl, DynamicTypeImpl, LocalVariableElementImpl; |
-import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
-import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
-import 'package:analyzer/src/dart/ast/token.dart' |
- show StringToken, Token, TokenType; |
-import 'package:analyzer/src/generated/type_system.dart' |
- show StrongTypeSystemImpl; |
-import 'package:analyzer/src/task/strong/info.dart'; |
- |
-import 'ast_builder.dart' show AstBuilder; |
-import 'reify_coercions.dart' show CoercionReifier, Tuple2; |
- |
-import '../js/js_ast.dart' as JS; |
-import '../js/js_ast.dart' show js; |
- |
-import '../closure/closure_annotator.dart' show ClosureAnnotator; |
-import '../compiler.dart' |
- show AbstractCompiler, corelibOrder, getCorelibModuleName; |
-import '../options.dart' show CodegenOptions; |
-import '../utils.dart'; |
- |
-import 'js_field_storage.dart'; |
-import 'js_interop.dart'; |
-import 'js_names.dart' as JS; |
-import 'js_metalet.dart' as JS; |
-import 'js_module_item_order.dart'; |
-import 'js_names.dart'; |
-import 'js_printer.dart' show writeJsLibrary; |
-import 'js_typeref_codegen.dart'; |
-import 'module_builder.dart'; |
-import 'nullable_type_inference.dart'; |
-import 'side_effect_analysis.dart'; |
- |
-// Various dynamic helpers we call. |
-// If renaming these, make sure to check other places like the |
-// _runtime.js file and comments. |
-// TODO(jmesserly): ideally we'd have a "dynamic call" dart library we can |
-// import and generate calls to, rather than dart_runtime.js |
-const DPUT = 'dput'; |
-const DLOAD = 'dload'; |
-const DINDEX = 'dindex'; |
-const DSETINDEX = 'dsetindex'; |
-const DCALL = 'dcall'; |
-const DSEND = 'dsend'; |
- |
-class JSCodegenVisitor extends GeneralizingAstVisitor |
- with ClosureAnnotator, JsTypeRefCodegen, NullableTypeInference { |
- final AbstractCompiler compiler; |
- final CodegenOptions options; |
- final LibraryElement currentLibrary; |
- final StrongTypeSystemImpl rules; |
- |
- /// The global extension type table. |
- final ExtensionTypeSet _extensionTypes; |
- |
- /// Information that is precomputed for this library, indicates which fields |
- /// need storage slots. |
- final HashSet<FieldElement> _fieldsNeedingStorage; |
- |
- /// 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; |
- |
- /// Imported libraries, and the temporaries used to refer to them. |
- final _imports = new Map<LibraryElement, JS.TemporaryId>(); |
- final _exports = <String, String>{}; |
- final _properties = <FunctionDeclaration>[]; |
- final _privateNames = new HashMap<String, JS.TemporaryId>(); |
- final _moduleItems = <JS.Statement>[]; |
- final _temps = new HashMap<Element, JS.TemporaryId>(); |
- final _qualifiedIds = new List<Tuple2<Element, JS.MaybeQualifiedId>>(); |
- |
- /// The name for the library's exports inside itself. |
- /// `exports` was chosen as the most similar to ES module patterns. |
- final _dartxVar = new JS.Identifier('dartx'); |
- final _exportsVar = new JS.TemporaryId('exports'); |
- final _runtimeLibVar = new JS.Identifier('dart'); |
- final namedArgumentTemp = new JS.TemporaryId('opts'); |
- |
- final TypeProvider _types; |
- |
- ConstFieldVisitor _constField; |
- |
- ModuleItemLoadOrder _loader; |
- |
- /// _interceptors.JSArray<E>, used for List literals. |
- ClassElement _jsArray; |
- |
- /// The current function body being compiled. |
- FunctionBody _currentFunction; |
- |
- /// The default value of the module object. See [visitLibraryDirective]. |
- String _jsModuleValue; |
- |
- bool _isDartRuntime; |
- |
- JSCodegenVisitor(AbstractCompiler compiler, this.rules, this.currentLibrary, |
- this._extensionTypes, this._fieldsNeedingStorage) |
- : compiler = compiler, |
- options = compiler.options.codegenOptions, |
- _types = compiler.context.typeProvider { |
- _loader = new ModuleItemLoadOrder(_emitModuleItem); |
- |
- var context = compiler.context; |
- var src = context.sourceFactory.forUri('dart:_interceptors'); |
- var interceptors = context.computeLibraryElement(src); |
- _jsArray = interceptors.getType('JSArray'); |
- _isDartRuntime = currentLibrary.source.uri.toString() == 'dart:_runtime'; |
- } |
- |
- TypeProvider get types => _types; |
- |
- JS.Program emitLibrary(List<CompilationUnit> units) { |
- // Modify the AST to make coercions explicit. |
- units = new CoercionReifier().reify(units); |
- |
- units.last.directives.forEach(_visit); |
- |
- // Rather than directly visit declarations, we instead use [_loader] to |
- // visit them. It has the ability to sort elements on demand, so |
- // dependencies between top level items are handled with a minimal |
- // reordering of the user's input code. The loader will call back into |
- // this visitor via [_emitModuleItem] when it's ready to visit the item |
- // for real. |
- _loader.collectElements(currentLibrary, units); |
- |
- // TODO(jmesserly): ideally we could do this at a smaller granularity. |
- // We'll need to be consistent about when we're generating functions, and |
- // only run this on the outermost function. |
- inferNullableTypesInLibrary(units); |
- |
- _constField = new ConstFieldVisitor(types, currentLibrary.source); |
- |
- for (var unit in units) { |
- for (var decl in unit.declarations) { |
- if (decl is TopLevelVariableDeclaration) { |
- visitTopLevelVariableDeclaration(decl); |
- } else { |
- _loader.loadDeclaration(decl, decl.element); |
- } |
- } |
- } |
- |
- // Flush any unwritten fields/properties. |
- _flushLibraryProperties(_moduleItems); |
- |
- // Mark all qualified names as qualified or not, depending on if they need |
- // to be loaded lazily or not. |
- for (var elementIdPairs in _qualifiedIds) { |
- var element = elementIdPairs.e0; |
- var id = elementIdPairs.e1; |
- id.setQualified(!_loader.isLoaded(element)); |
- } |
- |
- var moduleBuilder = new ModuleBuilder(options.moduleFormat); |
- |
- _exports.forEach(moduleBuilder.addExport); |
- |
- var currentModuleName = compiler.getModuleName(currentLibrary.source.uri); |
- var items = <JS.ModuleItem>[]; |
- if (!_isDartRuntime) { |
- if (currentLibrary.source.isInSystemLibrary) { |
- // Force the import order of runtime libs. |
- // TODO(ochafik): Reduce this to a minimum. |
- for (var libUri in corelibOrder.reversed) { |
- var moduleName = compiler.getModuleName(libUri); |
- if (moduleName == currentModuleName) continue; |
- moduleBuilder.addImport(moduleName, null); |
- } |
- } |
- moduleBuilder.addImport('dart/_runtime', _runtimeLibVar); |
- |
- var dartxImport = |
- js.statement("let # = #.dartx;", [_dartxVar, _runtimeLibVar]); |
- items.add(dartxImport); |
- } |
- items.addAll(_moduleItems); |
- |
- _imports.forEach((LibraryElement lib, JS.TemporaryId temp) { |
- moduleBuilder.addImport(compiler.getModuleName(lib.source.uri), temp, |
- isLazy: _isDartRuntime || !_loader.libraryIsLoaded(lib)); |
- }); |
- |
- // TODO(jmesserly): scriptTag support. |
- // Enable this if we know we're targetting command line environment? |
- // It doesn't work in browser. |
- // var jsBin = compiler.options.runnerOptions.v8Binary; |
- // String scriptTag = null; |
- // if (library.library.scriptTag != null) scriptTag = '/usr/bin/env $jsBin'; |
- return moduleBuilder.build( |
- currentModuleName, _jsModuleValue, _exportsVar, items); |
- } |
- |
- void _emitModuleItem(AstNode node) { |
- // Attempt to group adjacent properties. |
- if (node is! FunctionDeclaration) _flushLibraryProperties(_moduleItems); |
- |
- var code = _visit(node); |
- if (code != null) _moduleItems.add(code); |
- } |
- |
- @override |
- void visitLibraryDirective(LibraryDirective node) { |
- assert(_jsModuleValue == null); |
- |
- var jsName = findAnnotation(node.element, isJSAnnotation); |
- _jsModuleValue = |
- getConstantField(jsName, 'name', types.stringType)?.toStringValue(); |
- } |
- |
- @override |
- void visitImportDirective(ImportDirective node) { |
- // Nothing to do yet, but we'll want to convert this to an ES6 import once |
- // we have support for modules. |
- } |
- |
- @override |
- void visitPartDirective(PartDirective node) {} |
- @override |
- void visitPartOfDirective(PartOfDirective node) {} |
- |
- @override |
- void visitExportDirective(ExportDirective node) { |
- var exportName = emitLibraryName(node.uriElement); |
- |
- var currentLibNames = currentLibrary.publicNamespace.definedNames; |
- |
- var args = [_exportsVar, exportName]; |
- if (node.combinators.isNotEmpty) { |
- var shownNames = <JS.Expression>[]; |
- var hiddenNames = <JS.Expression>[]; |
- |
- var show = node.combinators.firstWhere((c) => c is ShowCombinator, |
- orElse: () => null) as ShowCombinator; |
- var hide = node.combinators.firstWhere((c) => c is HideCombinator, |
- orElse: () => null) as HideCombinator; |
- if (show != null) { |
- shownNames.addAll(show.shownNames |
- .map((i) => i.name) |
- .where((s) => !currentLibNames.containsKey(s)) |
- .map((s) => js.string(s, "'"))); |
- } |
- if (hide != null) { |
- hiddenNames.addAll(hide.hiddenNames.map((i) => js.string(i.name, "'"))); |
- } |
- args.add(new JS.ArrayInitializer(shownNames)); |
- args.add(new JS.ArrayInitializer(hiddenNames)); |
- } |
- |
- _moduleItems.add(js.statement('dart.export(#);', [args])); |
- } |
- |
- JS.Identifier _initSymbol(JS.Identifier id) { |
- var s = |
- js.statement('const # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); |
- _moduleItems.add(s); |
- return id; |
- } |
- |
- // TODO(jmesserly): this is a temporary workaround for `Symbol` in core, |
- // until we have better name tracking. |
- String get _SYMBOL { |
- var name = currentLibrary.name; |
- if (name == 'dart.core' || name == 'dart._internal') return 'dart.JsSymbol'; |
- return 'Symbol'; |
- } |
- |
- bool isPublic(String name) => !name.startsWith('_'); |
- |
- @override |
- visitAsExpression(AsExpression node) { |
- var from = getStaticType(node.expression); |
- var to = node.type.type; |
- |
- var fromExpr = _visit(node.expression); |
- |
- // Skip the cast if it's not needed. |
- if (rules.isSubtypeOf(from, to)) return fromExpr; |
- |
- // 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) { |
- return js.call('dart.asInt(#)', [fromExpr]); |
- } |
- |
- // A no-op in JavaScript. |
- return fromExpr; |
- } |
- |
- return js.call('dart.as(#, #)', [fromExpr, _emitTypeName(to)]); |
- } |
- |
- @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. |
- result = js.call('dart.is(#, #)', [lhs, _emitTypeName(type)]); |
- } |
- |
- 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) { |
- var element = node.element; |
- var type = element.type; |
- var name = element.name; |
- |
- var fnType = annotate( |
- js.statement('const # = dart.typedef(#, () => #);', [ |
- name, |
- js.string(name, "'"), |
- _emitTypeName(type, lowerTypedef: true) |
- ]), |
- node, |
- node.element); |
- |
- return _finishClassDef(type, fnType); |
- } |
- |
- @override |
- JS.Expression visitTypeName(TypeName node) => _emitTypeName(node.type); |
- |
- @override |
- JS.Statement visitClassTypeAlias(ClassTypeAlias node) { |
- var element = node.element; |
- |
- // Forward all generative constructors from the base class. |
- var body = <JS.Method>[]; |
- |
- var supertype = element.supertype; |
- if (!supertype.isObject) { |
- for (var ctor in element.constructors) { |
- var parentCtor = supertype.lookUpConstructor(ctor.name, ctor.library); |
- var fun = js.call('function() { super.#(...arguments); }', |
- [_constructorName(parentCtor)]) as JS.Fun; |
- body.add(new JS.Method(_constructorName(ctor), fun)); |
- } |
- } |
- |
- var cls = _emitClassDeclaration(element, body); |
- return _finishClassDef(element.type, cls); |
- } |
- |
- JS.Statement _emitJsType(String dartClassName, DartObject jsName) { |
- var jsTypeName = |
- getConstantField(jsName, 'name', types.stringType)?.toStringValue(); |
- |
- if (jsTypeName != null && jsTypeName != dartClassName) { |
- // 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. |
- if (isPublic(dartClassName)) _addExport(dartClassName); |
- return js.statement('const # = #;', [dartClassName, jsTypeName]); |
- } |
- return null; |
- } |
- |
- @override |
- JS.Statement visitClassDeclaration(ClassDeclaration node) { |
- var classElem = node.element; |
- var type = classElem.type; |
- var jsName = findAnnotation(classElem, isJSAnnotation); |
- |
- if (jsName != null) return _emitJsType(node.name.name, jsName); |
- |
- var ctors = <ConstructorDeclaration>[]; |
- var fields = <FieldDeclaration>[]; |
- var staticFields = <FieldDeclaration>[]; |
- var methods = <MethodDeclaration>[]; |
- 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); |
- } |
- } |
- |
- var allFields = new List.from(fields)..addAll(staticFields); |
- |
- var classDecl = _emitClassDeclaration( |
- classElem, _emitClassMethods(node, ctors, fields), |
- fields: allFields); |
- |
- String jsPeerName; |
- var jsPeer = findAnnotation(classElem, isJsPeerInterface); |
- // Only look at "Native" annotations on registered extension types. |
- // E.g., we're current ignoring the ones in dart:html. |
- if (jsPeer == null && _extensionTypes.contains(classElem)) { |
- jsPeer = findAnnotation(classElem, isNativeAnnotation); |
- } |
- if (jsPeer != null) { |
- jsPeerName = |
- getConstantField(jsPeer, 'name', types.stringType)?.toStringValue(); |
- if (jsPeerName.contains(',')) { |
- jsPeerName = jsPeerName.split(',')[0]; |
- } |
- } |
- |
- var body = _finishClassMembers(classElem, classDecl, ctors, fields, |
- staticFields, methods, node.metadata, jsPeerName); |
- |
- var result = _finishClassDef(type, body); |
- |
- if (jsPeerName != null) { |
- // This class isn't allowed to be lazy, because we need to set up |
- // the native JS type eagerly at this point. |
- // If we wanted to support laziness, we could defer the hookup until |
- // the end of the Dart library cycle load. |
- assert(_loader.isLoaded(classElem)); |
- |
- // TODO(jmesserly): this copies the dynamic members. |
- // Probably fine for objects coming from JS, but not if we actually |
- // want to support construction of instances with generic types other |
- // than dynamic. See issue #154 for Array and List<E> related bug. |
- var copyMembers = js.statement( |
- 'dart.registerExtension(dart.global.#, #);', |
- [_propertyName(jsPeerName), classElem.name]); |
- return _statement([result, copyMembers]); |
- } |
- return result; |
- } |
- |
- 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; |
- var name = js.string(type.name); |
- var id = new JS.Identifier(type.name); |
- |
- // 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( |
- name, 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 properties = new List<JS.Property>(); |
- for (var i = 0; i < fields.length; ++i) { |
- properties.add(new JS.Property( |
- js.number(i), js.string('${type.name}.${fields[i].name}'))); |
- } |
- var nameMap = new JS.ObjectInitializer(properties, 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( |
- id, _emitClassHeritage(element), [constructor, toStringF]); |
- var result = <JS.Statement>[js.statement('#', classExpr)]; |
- |
- // Create static fields for each enum value |
- for (var i = 0; i < fields.length; ++i) { |
- result.add(js.statement('#.# = dart.const(new #(#));', |
- [id, fields[i].name, id, js.number(i)])); |
- } |
- |
- // Create static values list |
- var values = new JS.ArrayInitializer(new List<JS.Expression>.from( |
- fields.map((f) => js.call('#.#', [id, f.name])))); |
- result.add(js.statement('#.values = dart.const(dart.list(#, #));', |
- [id, values, _emitTypeName(type)])); |
- |
- if (isPublic(type.name)) _addExport(type.name); |
- return _statement(result); |
- } |
- |
- /// Given a class element and body, complete the class declaration. |
- /// This handles generic type parameters, laziness (in library-cycle cases), |
- /// and ensuring dependencies are loaded first. |
- JS.Statement _finishClassDef(ParameterizedType type, JS.Statement body) { |
- var name = type.name; |
- var genericName = '$name\$'; |
- |
- JS.Statement genericDef = null; |
- if (_typeFormalsOf(type).isNotEmpty) { |
- genericDef = _emitGenericClassDef(type, body); |
- } |
- // The base class and all mixins must be declared before this class. |
- if (!_loader.isLoaded(type.element)) { |
- // TODO(jmesserly): the lazy class def is a simple solution for now. |
- // We may want to consider other options in the future. |
- |
- if (genericDef != null) { |
- return js.statement( |
- '{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', |
- [genericDef, _exportsVar, _propertyName(name), genericName]); |
- } |
- |
- return js.statement( |
- 'dart.defineLazyClass(#, { get #() { #; return #; } });', |
- [_exportsVar, _propertyName(name), body, name]); |
- } |
- |
- if (isPublic(name)) _addExport(name); |
- |
- if (genericDef != null) { |
- var dynType = fillDynamicTypeArgs(type, types); |
- var genericInst = _emitTypeName(dynType, lowerGeneric: true); |
- return js.statement('{ #; let # = #; }', [genericDef, name, genericInst]); |
- } |
- return body; |
- } |
- |
- JS.Statement _emitGenericClassDef(ParameterizedType type, JS.Statement body) { |
- var name = type.name; |
- var genericName = '$name\$'; |
- var typeParams = _typeFormalsOf(type).map((p) => p.name); |
- if (isPublic(name)) _addExport(genericName); |
- return js.statement('const # = dart.generic(function(#) { #; return #; });', |
- [genericName, typeParams, body, name]); |
- } |
- |
- final _hasDeferredSupertype = new HashSet<ClassElement>(); |
- |
- 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.Statement _emitClassDeclaration( |
- 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(); |
- |
- // Workaround for Closure: super classes must be qualified paths. |
- // TODO(jmesserly): is there a bug filed? we need to get out of the business |
- // of working around bugs in other transpilers... |
- JS.Statement workaroundSuper = null; |
- if (options.closure && heritage is JS.Call) { |
- var superVar = new JS.TemporaryId('$name\$super'); |
- workaroundSuper = js.statement('const # = #;', [superVar, heritage]); |
- heritage = superVar; |
- } |
- var decl = new JS.ClassDeclaration(new JS.ClassExpression( |
- new JS.Identifier(name), heritage, methods, |
- typeParams: typeParams, fields: jsFields)); |
- if (workaroundSuper != null) { |
- return new JS.Block([workaroundSuper, decl]); |
- } |
- return decl; |
- } |
- |
- JS.Expression _emitClassHeritage(ClassElement element) { |
- var type = element.type; |
- if (type.isObject) return null; |
- |
- // Assume we can load eagerly, until proven otherwise. |
- _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, _types); |
- _hasDeferredSupertype.add(element); |
- } |
- heritage = _emitTypeName(supertype); |
- |
- if (type.mixins.isNotEmpty) { |
- var mixins = type.mixins.map(_emitTypeName).toList(); |
- mixins.insert(0, heritage); |
- heritage = js.call('dart.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; |
- var name = decl.name.name; |
- var annotation = findAnnotation(field, isJsName); |
- if (annotation != null) { |
- name = getConstantField(annotation, 'name', types.stringType) |
- ?.toStringValue(); |
- } |
- // Generate getter |
- var fn = new JS.Fun([], js.statement('{ return this.#; }', [name])); |
- var method = |
- new JS.Method(_elementMemberName(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(_elementMemberName(field.setter), fn, |
- isSetter: true); |
- jsMethods.add(method); |
- } |
- } |
- } |
- return jsMethods; |
- } |
- |
- List<JS.Method> _emitClassMethods(ClassDeclaration node, |
- List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) { |
- 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 (ctors.isEmpty && !isObject) { |
- jsMethods.add(_emitImplicitConstructor(node, fields)); |
- } |
- |
- 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, isObject)); |
- } else if (m is MethodDeclaration) { |
- jsMethods.add(_emitMethodDeclaration(type, m)); |
- |
- if (!hasJsPeer && m.isGetter && m.name.name == 'iterator') { |
- hasIterator = true; |
- jsMethods.add(_emitIterable(type)); |
- } |
- } else if (m is FieldDeclaration && _extensionTypes.contains(element)) { |
- jsMethods.addAll(_emitNativeFieldAccessors(m)); |
- } |
- } |
- |
- // 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)); |
- } |
- |
- return jsMethods.where((m) => m != null).toList(growable: false); |
- } |
- |
- 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 dart.JsIterator(this.#); }', |
- [_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); |
- } |
- } |
- |
- /// Emit class members that need to come after the class declaration, such |
- /// as static fields. See [_emitClassMethods] for things that are emitted |
- /// inside the ES6 `class { ... }` node. |
- JS.Statement _finishClassMembers( |
- ClassElement classElem, |
- JS.Statement classDecl, |
- List<ConstructorDeclaration> ctors, |
- List<FieldDeclaration> fields, |
- List<FieldDeclaration> staticFields, |
- List<MethodDeclaration> methods, |
- List<Annotation> metadata, |
- String jsPeerName) { |
- var name = classElem.name; |
- var body = <JS.Statement>[]; |
- |
- if (_extensionTypes.contains(classElem)) { |
- var dartxNames = <JS.Expression>[]; |
- for (var m in methods) { |
- if (!m.isAbstract && !m.isStatic && m.element.isPublic) { |
- dartxNames.add(_elementMemberName(m.element, allowExtensions: false)); |
- } |
- } |
- for (var f in fields) { |
- if (!f.isStatic) { |
- for (var d in f.fields.variables) { |
- if (d.element.isPublic) { |
- dartxNames.add( |
- _elementMemberName(d.element.getter, allowExtensions: false)); |
- } |
- } |
- } |
- } |
- if (dartxNames.isNotEmpty) { |
- body.add(js.statement('dart.defineExtensionNames(#)', |
- [new JS.ArrayInitializer(dartxNames, multiline: true)])); |
- } |
- } |
- |
- body.add(classDecl); |
- |
- // TODO(jmesserly): we should really just extend native Array. |
- if (jsPeerName != null && classElem.typeParameters.isNotEmpty) { |
- body.add(js.statement('dart.setBaseClass(#, dart.global.#);', |
- [classElem.name, _propertyName(jsPeerName)])); |
- } |
- |
- // Deferred Superclass |
- if (_hasDeferredSupertype.contains(classElem)) { |
- body.add(js.statement('#.prototype.__proto__ = #.prototype;', |
- [name, _emitTypeName(classElem.type.superclass)])); |
- } |
- |
- // Interfaces |
- if (classElem.interfaces.isNotEmpty) { |
- body.add(js.statement('#[dart.implements] = () => #;', [ |
- name, |
- new JS.ArrayInitializer(new List<JS.Expression>.from( |
- classElem.interfaces.map(_emitTypeName))) |
- ])); |
- } |
- |
- // Named constructors |
- for (ConstructorDeclaration member in ctors) { |
- if (member.name != null && member.factoryKeyword == null) { |
- body.add(js.statement('dart.defineNamedConstructor(#, #);', |
- [name, _emitMemberName(member.name.name, isStatic: true)])); |
- } |
- } |
- |
- // Emits instance fields, if they are virtual |
- // (in other words, they override a getter/setter pair). |
- for (FieldDeclaration member in fields) { |
- for (VariableDeclaration field in member.fields.variables) { |
- if (_fieldsNeedingStorage.contains(field.element)) { |
- body.add(_overrideField(field.element)); |
- } |
- } |
- } |
- |
- // Emit the signature on the class recording the runtime type information |
- var extensions = _extensionsToImplement(classElem); |
- { |
- var tStatics = <JS.Property>[]; |
- var tMethods = <JS.Property>[]; |
- var sNames = <JS.Expression>[]; |
- for (MethodDeclaration node in methods) { |
- if (!(node.isSetter || node.isGetter || node.isAbstract)) { |
- var name = node.name.name; |
- var element = node.element; |
- var inheritedElement = |
- classElem.lookUpInheritedConcreteMethod(name, currentLibrary); |
- if (inheritedElement != null && |
- inheritedElement.type == element.type) { |
- continue; |
- } |
- var memberName = _elementMemberName(element); |
- var parts = _emitFunctionTypeParts(element.type); |
- var property = |
- new JS.Property(memberName, new JS.ArrayInitializer(parts)); |
- if (node.isStatic) { |
- tStatics.add(property); |
- sNames.add(memberName); |
- } else { |
- tMethods.add(property); |
- } |
- } |
- } |
- |
- var tCtors = <JS.Property>[]; |
- for (ConstructorDeclaration node in ctors) { |
- var memberName = _constructorName(node.element); |
- var element = node.element; |
- var parts = _emitFunctionTypeParts(element.type, node.parameters); |
- var property = |
- new JS.Property(memberName, new JS.ArrayInitializer(parts)); |
- tCtors.add(property); |
- } |
- |
- JS.Property build(String name, List<JS.Property> elements) { |
- var o = |
- new JS.ObjectInitializer(elements, multiline: elements.length > 1); |
- 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 (!tMethods.isEmpty) sigFields.add(build('methods', tMethods)); |
- if (!tStatics.isEmpty) { |
- assert(!sNames.isEmpty); |
- var aNames = new JS.Property( |
- _propertyName('names'), new JS.ArrayInitializer(sNames)); |
- sigFields.add(build('statics', tStatics)); |
- sigFields.add(aNames); |
- } |
- if (!sigFields.isEmpty || extensions.isNotEmpty) { |
- var sig = new JS.ObjectInitializer(sigFields); |
- var classExpr = new JS.Identifier(name); |
- body.add(js.statement('dart.setSignature(#, #);', [classExpr, sig])); |
- } |
- } |
- |
- // 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(_elementMemberName(e)); |
- } |
- body.add(js.statement('dart.defineExtensionMembers(#, #);', [ |
- name, |
- new JS.ArrayInitializer(methodNames, multiline: methodNames.length > 4) |
- ])); |
- } |
- |
- // TODO(vsm): Make this optional per #268. |
- // Metadata |
- if (metadata.isNotEmpty) { |
- body.add(js.statement('#[dart.metadata] = () => #;', [ |
- name, |
- new JS.ArrayInitializer( |
- new List<JS.Expression>.from(metadata.map(_instantiateAnnotation))) |
- ])); |
- } |
- |
- // Emits static fields. These are eager initialized if possible, otherwise |
- // they are made lazy. |
- var lazyStatics = <VariableDeclaration>[]; |
- for (FieldDeclaration member in staticFields) { |
- for (VariableDeclaration field in member.fields.variables) { |
- JS.Statement eagerField = _emitConstantStaticField(classElem, field); |
- if (eagerField != null) { |
- body.add(eagerField); |
- } else { |
- lazyStatics.add(field); |
- } |
- } |
- } |
- if (lazyStatics.isNotEmpty) { |
- body.add(_emitLazyFields(classElem, lazyStatics)); |
- } |
- |
- return _statement(body); |
- } |
- |
- List<ExecutableElement> _extensionsToImplement(ClassElement element) { |
- var members = <ExecutableElement>[]; |
- if (_extensionTypes.contains(element)) return members; |
- |
- // Collect all extension types we implement. |
- var type = element.type; |
- var types = new Set<ClassElement>(); |
- _collectExtensions(type, types); |
- 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) 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; |
- } |
- |
- /// Collections the type and all supertypes, including interfaces, but |
- /// excluding [Object]. |
- void _collectExtensions(InterfaceType type, Set<ClassElement> types) { |
- if (type.isObject) return; |
- var element = type.element; |
- if (_extensionTypes.contains(element)) types.add(element); |
- for (var m in type.mixins.reversed) { |
- _collectExtensions(m, types); |
- } |
- for (var i in type.interfaces) { |
- _collectExtensions(i, types); |
- } |
- _collectExtensions(type.superclass, types); |
- } |
- |
- JS.Statement _overrideField(FieldElement e) { |
- var cls = e.enclosingElement; |
- return js.statement('dart.virtualField(#, #)', |
- [cls.name, _emitMemberName(e.name, type: cls.type)]); |
- } |
- |
- /// Generates the implicit default constructor for class C of the form |
- /// `C() : super() {}`. |
- JS.Method _emitImplicitConstructor( |
- ClassDeclaration node, List<FieldDeclaration> fields) { |
- assert(_hasUnnamedConstructor(node.element) == fields.isNotEmpty); |
- |
- // If we don't have a method body, skip this. |
- var superCall = _superConstructorCall(node.element); |
- if (fields.isEmpty && superCall == null) return null; |
- |
- dynamic body = _initializeFields(node, fields); |
- if (superCall != null) { |
- body = [ |
- [body, 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, 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), 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. |
- JS.Block body; |
- if (isObject && |
- node.body is EmptyFunctionBody && |
- node.constKeyword != null && |
- node.name == null) { |
- // Implements Dart constructor behavior. Because of V8 `super` |
- // [constructor restrictions] |
- // (https://code.google.com/p/v8/issues/detail?id=3330#c65) |
- // we cannot currently emit actual ES6 constructors with super calls. |
- // Instead we use the same trick as named constructors, and do them as |
- // instance methods that perform initialization. |
- // TODO(jmesserly): we'll need to rethink this once the ES6 spec and V8 |
- // settles. See <https://github.com/dart-lang/dev_compiler/issues/51>. |
- // Performance of this pattern is likely to be bad. |
- name = _propertyName('constructor'); |
- // Mark the parameter as no-rename. |
- body = js.statement('''{ |
- // Get the class name for this instance. |
- let name = this.constructor.name; |
- // Call the default constructor. |
- let result = void 0; |
- if (name in this) result = this[name](...arguments); |
- return result === void 0 ? this : result; |
- }''') as JS.Block; |
- } else { |
- var savedFunction = _currentFunction; |
- _currentFunction = node.body; |
- body = _emitConstructorBody(node, fields); |
- _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 != '') { |
- return _emitMemberName(name, isStatic: true); |
- } |
- |
- // Factory default constructors use `new` as their name, for readability |
- // Other default constructors use the class name, as they aren't called |
- // from call sites, but rather from Object's constructor. |
- // TODO(jmesserly): revisit in the context of Dart metaclasses, and cleaning |
- // up constructors to integrate more closely with ES6. |
- return _propertyName(ctor.isFactory ? 'new' : ctor.enclosingElement.name); |
- } |
- |
- JS.Block _emitConstructorBody( |
- ConstructorDeclaration node, List<FieldDeclaration> fields) { |
- var body = <JS.Statement>[]; |
- |
- // 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. |
- ClassDeclaration cls = node.parent; |
- 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, 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 name = _constructorName(node.staticElement); |
- return js.statement('this.#(#);', [name, _visit(node.argumentList)]); |
- } |
- |
- JS.Statement _superConstructorCall(ClassElement element, |
- [SuperConstructorInvocation node]) { |
- 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.forceCompile); |
- return null; |
- } |
- } |
- |
- if (superCtor == null) { |
- print('Error generating: ${element.displayName}'); |
- } |
- if (superCtor.name == '' && !_shouldCallUnnamedSuperCtor(element)) { |
- return null; |
- } |
- |
- var name = _constructorName(superCtor); |
- var args = node != null ? _visit(node.argumentList) : []; |
- return annotate(js.statement('super.#(#);', [name, args]), node); |
- } |
- |
- bool _shouldCallUnnamedSuperCtor(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; |
- return e.fields.any((f) => !f.isStatic && !f.isSynthetic); |
- } |
- |
- /// 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, |
- [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 (_constField.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] = visitSimpleIdentifier(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) { |
- var access = _emitMemberName(e.name, type: e.enclosingElement.type); |
- 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 = visitSimpleIdentifier(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/161 |
- var paramType = param.element.type; |
- if (!constructor && _hasUnsoundTypeParameter(paramType)) { |
- body.add(js |
- .statement('dart.as(#, #);', [jsParam, _emitTypeName(paramType)])); |
- } |
- } |
- return body.isEmpty ? null : _statement(body); |
- } |
- |
- bool _isUnsoundTypeParameter(DartType t) => |
- t is TypeParameterType && t.element.enclosingElement is ClassElement; |
- |
- bool _hasUnsoundTypeParameter(DartType t) => |
- _isUnsoundTypeParameter(t) || |
- t is ParameterizedType && t.typeArguments.any(_hasUnsoundTypeParameter); |
- |
- 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) { |
- if (node.isStatic) { |
- // TODO(vsm): Do we need to handle this case? |
- return null; |
- } |
- |
- var params = visitFormalParameterList(node.parameters, destructure: false); |
- String name = node.name.name; |
- var annotation = findAnnotation(node.element, isJsName); |
- if (annotation != null) { |
- name = getConstantField(annotation, 'name', types.stringType) |
- ?.toStringValue(); |
- } |
- if (node.isGetter) { |
- return new JS.Fun(params, js.statement('{ return this.#; }', [name])); |
- } else if (node.isSetter) { |
- return new JS.Fun( |
- params, js.statement('{ this.# = #; }', [name, params.last])); |
- } else { |
- return new JS.Fun( |
- params, js.statement('{ return this.#(#); }', [name, params])); |
- } |
- } |
- |
- JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) { |
- if (node.isAbstract) { |
- return null; |
- } |
- |
- JS.Fun fn; |
- if (_externalOrNative(node)) { |
- fn = _emitNativeFunctionBody(node); |
- // TODO(vsm): Remove if / when we handle the static case above. |
- if (fn == null) return null; |
- } 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); |
- } |
- } |
- |
- return annotate( |
- new JS.Method(_elementMemberName(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 (node.isGetter || node.isSetter) { |
- // Add these later so we can use getter/setter syntax. |
- _properties.add(node); |
- return null; |
- } |
- |
- var body = <JS.Statement>[]; |
- _flushLibraryProperties(body); |
- |
- var name = node.name.name; |
- var fn = _emitFunction(node.functionExpression); |
- |
- if (currentLibrary.source.isInSystemLibrary && |
- _isInlineJSFunction(node.functionExpression)) { |
- fn = _simplifyPassThroughArrowFunCallBody(fn); |
- } |
- |
- var id = new JS.Identifier(name); |
- body.add(annotate(new JS.FunctionDeclaration(id, fn), node, node.element)); |
- if (!_isDartRuntime) { |
- body.add(_emitFunctionTagged(id, node.element.type, topLevel: true) |
- .toStatement()); |
- } |
- |
- if (isPublic(name)) { |
- _addExport(name, getJSExportName(node.element, types) ?? name); |
- } |
- return _statement(body); |
- } |
- |
- bool _isInlineJSFunction(FunctionExpression functionExpression) { |
- var body = functionExpression.body; |
- if (body is ExpressionFunctionBody) { |
- return _isJSInvocation(body.expression); |
- } else if (body is BlockFunctionBody) { |
- if (body.block.statements.length == 1) { |
- var stat = body.block.statements.single; |
- 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 name = type.name; |
- var lazy = topLevel && !_typeIsLoaded(type); |
- |
- if (type is FunctionType && (name == '' || name == null)) { |
- if (type.returnType.isDynamic && |
- type.optionalParameterTypes.isEmpty && |
- type.namedParameterTypes.isEmpty && |
- type.normalParameterTypes.every((t) => t.isDynamic)) { |
- return js.call('dart.fn(#)', [fn]); |
- } |
- if (lazy) { |
- return js.call('dart.fn(#, () => #)', [fn, _emitFunctionRTTI(type)]); |
- } |
- return js.call('dart.fn(#, #)', [fn, _emitFunctionTypeParts(type)]); |
- } |
- throw 'Function has non function type: $type'; |
- } |
- |
- /// 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); |
- var 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) body = s.value; |
- } |
- } |
- |
- // Convert `function(...) { ... }` to `(...) => ...` |
- // This is for readability, but it also ensures correct `this` binding. |
- return annotate( |
- new JS.ArrowFun(f.params, body, |
- typeParams: f.typeParams, returnType: f.returnType), |
- node); |
- } |
- |
- /// 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(fn, node); |
- } |
- |
- JS.Fun _emitFunctionBody(ExecutableElement element, |
- FormalParameterList parameters, FunctionBody body) { |
- var returnType = emitTypeRef(element.returnType); |
- |
- // sync*, async, async* |
- if (element.isAsynchronous || element.isGenerator) { |
- return new JS.Fun( |
- visitFormalParameterList(parameters, destructure: false), |
- js.statement('{ return #; }', |
- [_emitGeneratorFunctionBody(element, parameters, body)]), |
- returnType: returnType); |
- } |
- // normal function (sync) |
- return new JS.Fun(visitFormalParameterList(parameters), _visit(body), |
- typeParams: _emitTypeFormals(element.typeParameters), |
- 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; |
- List jsParams = visitFormalParameterList(parameters); |
- if (kind == 'asyncStar') { |
- _asyncStarController = new JS.TemporaryId('stream'); |
- jsParams.insert(0, _asyncStarController); |
- } else { |
- _asyncStarController = null; |
- } |
- // Visit the body with our async* controller set. |
- var jsBody = _visit(body); |
- _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 = _emitTypeName(returnType); |
- return js.call('dart.#(#)', [ |
- 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() |
- ]); |
- } |
- |
- /// Writes a simple identifier. This can handle implicit `this` as well as |
- /// going through the qualified library name if necessary. |
- @override |
- JS.Expression visitSimpleIdentifier(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; |
- |
- _loader.declareBeforeUse(element); |
- |
- // type literal |
- if (element is TypeDefiningElement) { |
- return _emitTypeName( |
- fillDynamicTypeArgs((element as dynamic).type, types)); |
- } |
- |
- // 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); |
- |
- // For static methods, we add the raw type name, without generics or |
- // library prefix. We don't need those because static calls can't use |
- // the generic type. |
- if (isStatic) { |
- var dynType = _emitTypeName(fillDynamicTypeArgs(type, types)); |
- 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); |
- var code = (tearOff) ? 'dart.bind(this, #)' : 'this.#'; |
- return js.call(code, member); |
- } |
- |
- if (element is ParameterElement) { |
- return _emitParameter(element); |
- } |
- |
- if (element is TemporaryVariableElement) { |
- if (name[0] == '#') { |
- return new JS.InterpolatedExpression(name.substring(1)); |
- } else { |
- return _getTemp(element, name); |
- } |
- } |
- |
- return new JS.Identifier(name); |
- } |
- |
- 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. |
- /// The renamer would handle this, but it would prefer to rename the |
- /// temporary used for the private symbol. Instead rename the parameter. |
- return _getTemp(element, '${element.name.substring(1)}'); |
- } |
- |
- var type = declaration ? emitTypeRef(element.type) : null; |
- return new JS.Identifier(element.name, type: type); |
- } |
- |
- JS.TemporaryId _getTemp(Element key, String name) => |
- _temps.putIfAbsent(key, () => new JS.TemporaryId(name)); |
- |
- List<Annotation> _parameterMetadata(FormalParameter p) => |
- (p is NormalFormalParameter) |
- ? p.metadata |
- : (p as DefaultFormalParameter).parameter.metadata; |
- |
- JS.ArrayInitializer _emitTypeNames(List<DartType> types, |
- [List<FormalParameter> parameters]) { |
- var result = <JS.Expression>[]; |
- for (int i = 0; i < types.length; ++i) { |
- var metadata = |
- parameters != null ? _parameterMetadata(parameters[i]) : []; |
- var typeName = _emitTypeName(types[i]); |
- var value = typeName; |
- // TODO(vsm): Make this optional per #268. |
- if (metadata.isNotEmpty) { |
- metadata = metadata.map(_instantiateAnnotation).toList(); |
- value = new JS.ArrayInitializer([typeName]..addAll(metadata)); |
- } |
- result.add(value); |
- } |
- return new JS.ArrayInitializer(result); |
- } |
- |
- JS.ObjectInitializer _emitTypeProperties(Map<String, DartType> types) { |
- var properties = <JS.Property>[]; |
- types.forEach((name, type) { |
- var key = _propertyName(name); |
- var value = _emitTypeName(type); |
- properties.add(new JS.Property(key, value)); |
- }); |
- return new JS.ObjectInitializer(properties); |
- } |
- |
- /// Emit the pieces of a function type, as an array of return type, |
- /// regular args, and optional/named args. |
- List<JS.Expression> _emitFunctionTypeParts(FunctionType type, |
- [FormalParameterList parameterList]) { |
- var parameters = parameterList?.parameters; |
- var returnType = type.returnType; |
- var parameterTypes = type.normalParameterTypes; |
- var optionalTypes = type.optionalParameterTypes; |
- var namedTypes = type.namedParameterTypes; |
- var rt = _emitTypeName(returnType); |
- var ra = _emitTypeNames(parameterTypes, parameters); |
- if (!namedTypes.isEmpty) { |
- assert(optionalTypes.isEmpty); |
- // TODO(vsm): Pass in annotations here as well. |
- var na = _emitTypeProperties(namedTypes); |
- return [rt, ra, na]; |
- } |
- if (!optionalTypes.isEmpty) { |
- assert(namedTypes.isEmpty); |
- var oa = _emitTypeNames( |
- optionalTypes, parameters?.sublist(parameterTypes.length)); |
- return [rt, ra, oa]; |
- } |
- return [rt, ra]; |
- } |
- |
- JS.Expression _emitFunctionRTTI(FunctionType type) { |
- var parts = _emitFunctionTypeParts(type); |
- return js.call('dart.definiteFunctionType(#)', [parts]); |
- } |
- |
- /// Emits a Dart [type] into code. |
- /// |
- /// If [lowerTypedef] is set, a typedef will be expanded as if it were a |
- /// function type. Similarly if [lowerGeneric] is set, the `List$()` form |
- /// will be used instead of `List`. These flags are used when generating |
- /// the definitions for typedefs and generic types, respectively. |
- JS.Expression _emitTypeName(DartType type, |
- {bool lowerTypedef: false, bool lowerGeneric: false}) { |
- // The void and dynamic types are not defined in core. |
- if (type.isVoid) { |
- return js.call('dart.void'); |
- } else if (type.isDynamic) { |
- return js.call('dart.dynamic'); |
- } else if (type.isBottom) { |
- return js.call('dart.bottom'); |
- } |
- |
- _loader.declareBeforeUse(type.element); |
- |
- // TODO(jmesserly): like constants, should we hoist function types out of |
- // methods? Similar issue with generic types. For all of these, we may want |
- // to canonicalize them too, at least when inside the same library. |
- var name = type.name; |
- var element = type.element; |
- if (name == '' || name == null || lowerTypedef) { |
- var parts = _emitFunctionTypeParts(type as FunctionType); |
- return js.call('dart.functionType(#)', [parts]); |
- } |
- // For now, reify generic method parameters as dynamic |
- bool _isGenericTypeParameter(DartType type) => |
- (type is TypeParameterType) && |
- !(type.element.enclosingElement is ClassElement || |
- type.element.enclosingElement is FunctionTypeAliasElement); |
- |
- if (_isGenericTypeParameter(type)) { |
- return js.call('dart.dynamic'); |
- } |
- |
- if (type is TypeParameterType) { |
- return new JS.Identifier(name); |
- } |
- |
- if (type is ParameterizedType) { |
- var args = type.typeArguments; |
- var isCurrentClass = |
- args.isNotEmpty && _loader.isCurrentElement(type.element); |
- Iterable jsArgs = null; |
- if (args |
- .any((a) => a != types.dynamicType && !_isGenericTypeParameter(a))) { |
- jsArgs = args.map(_emitTypeName); |
- } else if (lowerGeneric || isCurrentClass) { |
- // When creating a `new S<dynamic>` we try and use the raw form |
- // `new S()`, but this does not work if we're inside the same class, |
- // because `S` refers to the current S<T> we are generating. |
- jsArgs = []; |
- } |
- if (jsArgs != null) { |
- var genericName = _emitTopLevelName(element, suffix: '\$'); |
- return js.call('#(#)', [genericName, jsArgs]); |
- } |
- } |
- |
- return _emitTopLevelName(element); |
- } |
- |
- JS.Expression _emitTopLevelName(Element e, {String suffix: ''}) { |
- var libName = emitLibraryName(e.library); |
- |
- // Always qualify: |
- // * mutable top-level fields |
- // * elements from other libraries |
- bool mutableTopLevel = e is TopLevelVariableElement && |
- !e.isConst && |
- !_isFinalJSDecl(e.computeNode()); |
- bool fromAnotherLibrary = e.library != currentLibrary; |
- var nameExpr; |
- if (fromAnotherLibrary) { |
- nameExpr = _propertyName((getJSExportName(e, types) ?? e.name) + suffix); |
- } else { |
- nameExpr = _propertyName(e.name + suffix); |
- } |
- if (mutableTopLevel || fromAnotherLibrary) { |
- return new JS.PropertyAccess(libName, nameExpr); |
- } |
- |
- var id = new JS.MaybeQualifiedId(libName, nameExpr); |
- _qualifiedIds.add(new Tuple2(e, id)); |
- return id; |
- } |
- |
- @override |
- JS.Expression visitAssignmentExpression(AssignmentExpression node) { |
- var left = node.leftHandSide; |
- var right = node.rightHandSide; |
- if (node.operator.type == TokenType.EQ) return _emitSet(left, right); |
- var op = node.operator.lexeme; |
- assert(op.endsWith('=')); |
- op = op.substring(0, op.length - 1); // remove trailing '=' |
- return _emitOpAssign(left, right, op, node.staticElement, context: node); |
- } |
- |
- JS.MetaLet _emitOpAssign( |
- Expression left, Expression right, String op, MethodElement element, |
- {Expression context}) { |
- if (op == '??') { |
- // Desugar `l ??= r` as ((x) => x == null ? l = r : x)(l) |
- // Note that if `x` contains subexpressions, we need to ensure those |
- // are also evaluated only once. This is similar to desguaring for |
- // postfix expressions like `i++`. |
- |
- // Handle the left hand side, to ensure each of its subexpressions are |
- // evaluated only once. |
- var vars = <String, JS.Expression>{}; |
- var x = _bindLeftHandSide(vars, left, context: left); |
- // Capture the result of evaluating the left hand side in a temp. |
- var t = _bindValue(vars, 't', x, context: x); |
- return new JS.MetaLet(vars, [ |
- js.call('# == null ? # : #', [_visit(t), _emitSet(x, right), _visit(t)]) |
- ]); |
- } |
- |
- // Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions |
- // (for example, x is IndexExpression) we evaluate those once. |
- var vars = <String, JS.Expression>{}; |
- var lhs = _bindLeftHandSide(vars, left, context: context); |
- var inc = AstBuilder.binaryExpression(lhs, op, right); |
- inc.staticElement = element; |
- inc.staticType = getStaticType(left); |
- return new JS.MetaLet(vars, [_emitSet(lhs, inc)]); |
- } |
- |
- JS.Expression _emitSet(Expression lhs, Expression rhs) { |
- if (lhs is IndexExpression) { |
- var target = _getTarget(lhs); |
- if (_useNativeJsIndexer(target.staticType)) { |
- return js |
- .call('#[#] = #', [_visit(target), _visit(lhs.index), _visit(rhs)]); |
- } |
- return _emitSend(target, '[]=', [lhs.index, rhs]); |
- } |
- |
- Expression target = null; |
- SimpleIdentifier id; |
- if (lhs is PropertyAccess) { |
- if (lhs.operator.lexeme == '?.') { |
- return _emitNullSafeSet(lhs, rhs); |
- } |
- |
- target = _getTarget(lhs); |
- id = lhs.propertyName; |
- } else if (lhs is PrefixedIdentifier) { |
- target = lhs.prefix; |
- id = lhs.identifier; |
- } |
- |
- if (target != null && DynamicInvoke.get(target)) { |
- return js.call('dart.$DPUT(#, #, #)', |
- [_visit(target), _emitMemberName(id.name), _visit(rhs)]); |
- } |
- |
- return _visit(rhs).toAssignExpression(_visit(lhs)); |
- } |
- |
- JS.Expression _emitNullSafeSet(PropertyAccess node, Expression right) { |
- // Emit `obj?.prop = expr` as: |
- // |
- // (_ => _ == null ? null : _.prop = expr)(obj). |
- // |
- // We could use a helper, e.g.: `nullSafeSet(e1, _ => _.v = e2)` |
- // |
- // However with MetaLet, we get clean code in statement or void context, |
- // or when one of the expressions is stateless, which seems common. |
- var vars = <String, JS.Expression>{}; |
- var left = _bindValue(vars, 'l', node.target); |
- var body = js.call('# == null ? null : #', |
- [_visit(left), _emitSet(_stripNullAwareOp(node, left), right)]); |
- return new JS.MetaLet(vars, [body]); |
- } |
- |
- @override |
- JS.Block visitExpressionFunctionBody(ExpressionFunctionBody node) { |
- var savedFunction = _currentFunction; |
- _currentFunction = node; |
- var initArgs = _emitArgumentInitializers(node.parent); |
- var ret = new JS.Return(_visit(node.expression)); |
- _currentFunction = savedFunction; |
- return new JS.Block(initArgs != null ? [initArgs, ret] : [ret]); |
- } |
- |
- @override |
- JS.Block visitEmptyFunctionBody(EmptyFunctionBody node) => new JS.Block([]); |
- |
- @override |
- JS.Block visitBlockFunctionBody(BlockFunctionBody node) { |
- var savedFunction = _currentFunction; |
- _currentFunction = node; |
- var initArgs = _emitArgumentInitializers(node.parent); |
- var stmts = _visitList(node.block.statements) as List<JS.Statement>; |
- if (initArgs != null) stmts.insert(0, initArgs); |
- _currentFunction = savedFunction; |
- return new JS.Block(stmts); |
- } |
- |
- @override |
- JS.Block visitBlock(Block node) => |
- new JS.Block(_visitList(node.statements) as List<JS.Statement>, |
- isScope: true); |
- |
- @override |
- visitMethodInvocation(MethodInvocation node) { |
- if (node.operator != null && node.operator.lexeme == '?.') { |
- return _emitNullSafe(node); |
- } |
- |
- var target = _getTarget(node); |
- var result = _emitForeignJS(node); |
- if (result != null) return result; |
- |
- String code; |
- if (target == null || isLibraryPrefix(target)) { |
- if (DynamicInvoke.get(node.methodName)) { |
- code = 'dart.$DCALL(#, #)'; |
- } else { |
- code = '#(#)'; |
- } |
- return js |
- .call(code, [_visit(node.methodName), _visit(node.argumentList)]); |
- } |
- |
- var type = getStaticType(target); |
- var name = node.methodName.name; |
- var element = node.methodName.staticElement; |
- bool isStatic = element is ExecutableElement && element.isStatic; |
- var memberName = _emitMemberName(name, type: type, isStatic: isStatic); |
- |
- if (DynamicInvoke.get(target)) { |
- code = 'dart.$DSEND(#, #, #)'; |
- } else if (DynamicInvoke.get(node.methodName)) { |
- // This is a dynamic call to a statically known target. For example: |
- // class Foo { Function bar; } |
- // new Foo().bar(); // dynamic call |
- code = 'dart.$DCALL(#.#, #)'; |
- } else if (_requiresStaticDispatch(target, name)) { |
- // Object methods require a helper for null checks. |
- return js.call('dart.#(#, #)', |
- [memberName, _visit(target), _visit(node.argumentList)]); |
- } else { |
- code = '#.#(#)'; |
- } |
- |
- return js |
- .call(code, [_visit(target), memberName, _visit(node.argumentList)]); |
- } |
- |
- /// Emits code for the `JS(...)` builtin. |
- _emitForeignJS(MethodInvocation node) { |
- var e = node.methodName.staticElement; |
- if (isInlineJS(e)) { |
- var args = node.argumentList.arguments; |
- // arg[0] is static return type, used in `RestrictedStaticTypeAnalyzer` |
- var code = args[1]; |
- var templateArgs; |
- var source; |
- if (code is StringInterpolation) { |
- if (args.length > 2) { |
- throw new ArgumentError( |
- "Can't mix template args and string interpolation in JS calls."); |
- } |
- templateArgs = <Expression>[]; |
- source = code.elements.map((element) { |
- if (element is InterpolationExpression) { |
- templateArgs.add(element.expression); |
- return '#'; |
- } else { |
- return (element as InterpolationString).value; |
- } |
- }).join(); |
- } else { |
- templateArgs = args.skip(2); |
- source = (code as StringLiteral).stringValue; |
- } |
- |
- var template = js.parseForeignJS(source); |
- var result = template.instantiate(_visitList(templateArgs)); |
- // `throw` is emitted as a statement by `parseForeignJS`. |
- assert(result is JS.Expression || node.parent is ExpressionStatement); |
- return result; |
- } |
- return null; |
- } |
- |
- @override |
- JS.Expression visitFunctionExpressionInvocation( |
- FunctionExpressionInvocation node) { |
- var code; |
- if (DynamicInvoke.get(node.function)) { |
- code = 'dart.$DCALL(#, #)'; |
- } else { |
- code = '#(#)'; |
- } |
- return js.call(code, [_visit(node.function), _visit(node.argumentList)]); |
- } |
- |
- @override |
- List<JS.Expression> visitArgumentList(ArgumentList node) { |
- var args = <JS.Expression>[]; |
- var named = <JS.Property>[]; |
- for (var arg in node.arguments) { |
- if (arg is NamedExpression) { |
- named.add(_visit(arg)); |
- } else if (arg is MethodInvocation && isJsSpreadInvocation(arg)) { |
- args.add( |
- new JS.RestParameter(_visit(arg.argumentList.arguments.single))); |
- } else { |
- args.add(_visit(arg)); |
- } |
- } |
- if (named.isNotEmpty) { |
- args.add(new JS.ObjectInitializer(named)); |
- } |
- return args; |
- } |
- |
- @override |
- JS.Property visitNamedExpression(NamedExpression node) { |
- assert(node.parent is ArgumentList); |
- return new JS.Property( |
- _propertyName(node.name.label.name), _visit(node.expression)); |
- } |
- |
- @override |
- List<JS.Parameter> visitFormalParameterList(FormalParameterList node, |
- {bool destructure: true}) { |
- if (node == null) return []; |
- |
- destructure = destructure && options.destructureNamedParams; |
- |
- var result = <JS.Parameter>[]; |
- var namedVars = <JS.DestructuredVariable>[]; |
- var hasNamedArgsConflictingWithObjectProperties = false; |
- var needsOpts = false; |
- |
- for (FormalParameter param in node.parameters) { |
- if (param.kind == ParameterKind.NAMED) { |
- if (destructure) { |
- if (_jsObjectProperties.contains(param.identifier.name)) { |
- hasNamedArgsConflictingWithObjectProperties = true; |
- } |
- JS.Expression name; |
- JS.SimpleBindingPattern structure = null; |
- String paramName = param.identifier.name; |
- if (invalidVariableName(paramName)) { |
- name = js.string(paramName); |
- structure = new JS.SimpleBindingPattern(_visit(param.identifier)); |
- } else { |
- name = _visit(param.identifier); |
- } |
- namedVars.add(new JS.DestructuredVariable( |
- name: name, |
- structure: structure, |
- defaultValue: _defaultParamValue(param))); |
- } else { |
- needsOpts = true; |
- } |
- } else { |
- var jsParam = _visit(param); |
- result.add(param is DefaultFormalParameter && destructure |
- ? new JS.DestructuredVariable( |
- name: jsParam, defaultValue: _defaultParamValue(param)) |
- : jsParam); |
- } |
- } |
- |
- if (needsOpts) { |
- result.add(namedArgumentTemp); |
- } else if (namedVars.isNotEmpty) { |
- // Note: `var {valueOf} = {}` extracts `Object.prototype.valueOf`, so |
- // in case there are conflicting names we create an object without |
- // any prototype. |
- var defaultOpts = hasNamedArgsConflictingWithObjectProperties |
- ? js.call('Object.create(null)') |
- : js.call('{}'); |
- result.add(new JS.DestructuredVariable( |
- structure: new JS.ObjectBindingPattern(namedVars), |
- type: emitNamedParamsArgType(node.parameterElements), |
- defaultValue: defaultOpts)); |
- } |
- return result; |
- } |
- |
- /// See ES6 spec (and `Object.getOwnPropertyNames(Object.prototype)`): |
- /// http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-object-prototype-object |
- /// http://www.ecma-international.org/ecma-262/6.0/#sec-additional-properties-of-the-object.prototype-object |
- static final Set<String> _jsObjectProperties = new Set<String>() |
- ..addAll([ |
- "constructor", |
- "toString", |
- "toLocaleString", |
- "valueOf", |
- "hasOwnProperty", |
- "isPrototypeOf", |
- "propertyIsEnumerable", |
- "__defineGetter__", |
- "__lookupGetter__", |
- "__defineSetter__", |
- "__lookupSetter__", |
- "__proto__" |
- ]); |
- |
- @override |
- JS.Statement visitExpressionStatement(ExpressionStatement node) => |
- _visit(node.expression).toStatement(); |
- |
- @override |
- JS.EmptyStatement visitEmptyStatement(EmptyStatement node) => |
- new JS.EmptyStatement(); |
- |
- @override |
- JS.Statement visitAssertStatement(AssertStatement node) => |
- // TODO(jmesserly): only emit in checked mode. |
- js.statement('dart.assert(#);', _visit(node.condition)); |
- |
- @override |
- JS.Statement visitReturnStatement(ReturnStatement node) { |
- var e = node.expression; |
- if (e == null) return new JS.Return(); |
- return (_visit(e) as JS.Expression).toReturn(); |
- } |
- |
- @override |
- JS.Statement visitYieldStatement(YieldStatement node) { |
- JS.Expression jsExpr = _visit(node.expression); |
- var star = node.star != null; |
- if (_asyncStarController != null) { |
- // async* yields are generated differently from sync* yields. `yield e` |
- // becomes: |
- // |
- // if (stream.add(e)) return; |
- // yield; |
- // |
- // `yield* e` becomes: |
- // |
- // if (stream.addStream(e)) return; |
- // yield; |
- var helperName = star ? 'addStream' : 'add'; |
- return js.statement('{ if(#.#(#)) return; #; }', |
- [_asyncStarController, helperName, jsExpr, new JS.Yield(null)]); |
- } |
- // A normal yield in a sync* |
- return jsExpr.toYieldStatement(star: star); |
- } |
- |
- @override |
- JS.Expression visitAwaitExpression(AwaitExpression node) { |
- return new JS.Yield(_visit(node.expression)); |
- } |
- |
- @override |
- visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
- for (var v in node.variables.variables) { |
- _loader.loadDeclaration(v, v.element); |
- } |
- } |
- |
- /// Emits static fields. |
- /// |
- /// Instance fields are emitted in [_initializeFields]. |
- /// |
- /// These are generally treated the same as top-level fields, see |
- /// [visitTopLevelVariableDeclaration]. |
- @override |
- visitFieldDeclaration(FieldDeclaration node) { |
- if (!node.isStatic) return; |
- |
- node.fields.variables.forEach(_emitModuleItem); |
- } |
- |
- _addExport(String name, [String exportName]) { |
- if (_exports.containsKey(name)) { |
- throw 'Duplicate top level name found: $name'; |
- } |
- _exports[name] = exportName ?? name; |
- } |
- |
- @override |
- JS.Statement visitVariableDeclarationStatement( |
- VariableDeclarationStatement node) { |
- // Special case a single variable with an initializer. |
- // This helps emit cleaner code for things like: |
- // var result = []..add(1)..add(2); |
- if (node.variables.variables.length == 1) { |
- var v = node.variables.variables.single; |
- if (v.initializer != null) { |
- var name = new JS.Identifier(v.name.name); |
- return _visit(v.initializer).toVariableDeclaration(name); |
- } |
- } |
- return _visit(node.variables).toStatement(); |
- } |
- |
- @override |
- visitVariableDeclarationList(VariableDeclarationList node) { |
- return new JS.VariableDeclarationList( |
- 'let', _visitList(node.variables) as List<JS.VariableInitialization>); |
- } |
- |
- @override |
- visitVariableDeclaration(VariableDeclaration node) { |
- if (node.element is PropertyInducingElement) { |
- // Static and instance fields are handled elsewhere. |
- assert(node.element is TopLevelVariableElement); |
- return _emitTopLevelField(node); |
- } |
- |
- var name = |
- new JS.Identifier(node.name.name, type: emitTypeRef(node.element.type)); |
- return new JS.VariableInitialization(name, _visitInitializer(node)); |
- } |
- |
- bool _isFinalJSDecl(AstNode field) => |
- field is VariableDeclaration && |
- field.isFinal && |
- _isJSInvocation(field.initializer); |
- |
- /// Try to emit a constant static field. |
- /// |
- /// If the field's initializer does not cause side effects, and if all of |
- /// dependencies are safe to refer to while we are initializing the class, |
- /// then we can initialize it eagerly: |
- /// |
- /// // Baz must be const constructor, and the name "Baz" must be defined |
- /// // by this point. |
- /// Foo.bar = dart.const(new Baz(42)); |
- /// |
- /// Otherwise, we'll need to generate a lazy-static field. That ensures |
- /// correct visible behavior, as well as avoiding referencing something that |
- /// isn't defined yet (because it is defined later in the module). |
- JS.Statement _emitConstantStaticField( |
- ClassElement classElem, VariableDeclaration field) { |
- PropertyInducingElement element = field.element; |
- assert(element.isStatic); |
- |
- _loader.startCheckingReferences(); |
- JS.Expression jsInit = _visitInitializer(field); |
- bool isLoaded = _loader.finishCheckingReferences(); |
- |
- bool eagerInit = |
- isLoaded && (field.isConst || _constField.isFieldInitConstant(field)); |
- |
- var fieldName = field.name.name; |
- if (eagerInit && !JS.invalidStaticFieldName(fieldName)) { |
- return annotate( |
- js.statement('#.# = #;', [ |
- classElem.name, |
- _emitMemberName(fieldName, isStatic: true), |
- jsInit |
- ]), |
- field, |
- field.element); |
- } |
- |
- // This means it should be treated as a lazy field. |
- // TODO(jmesserly): we're throwing away the initializer expression, |
- // which will force us to regenerate it. |
- return null; |
- } |
- |
- /// Emits a top-level field. |
- JS.Statement _emitTopLevelField(VariableDeclaration field) { |
- TopLevelVariableElement element = field.element; |
- assert(element.isStatic); |
- |
- bool eagerInit; |
- JS.Expression jsInit; |
- if (field.isConst || _constField.isFieldInitConstant(field)) { |
- // If the field is constant, try and generate it at the top level. |
- _loader.startTopLevel(element); |
- jsInit = _visitInitializer(field); |
- _loader.finishTopLevel(element); |
- eagerInit = _loader.isLoaded(element); |
- } else { |
- // TODO(jmesserly): we're visiting the initializer here, and again |
- // later on when we emit lazy fields. That seems busted. |
- jsInit = _visitInitializer(field); |
- eagerInit = false; |
- } |
- |
- // Treat `final x = JS('', '...')` as a const (non-lazy) to help compile |
- // runtime helpers. |
- var isJSTopLevel = field.isFinal && _isFinalJSDecl(field); |
- if (isJSTopLevel) eagerInit = true; |
- |
- var fieldName = field.name.name; |
- var exportName = fieldName; |
- if (element is TopLevelVariableElement) { |
- exportName = getJSExportName(element, types) ?? fieldName; |
- } |
- if ((field.isConst && eagerInit && element is TopLevelVariableElement) || |
- isJSTopLevel) { |
- // constant fields don't change, so we can generate them as `let` |
- // but add them to the module's exports. However, make sure we generate |
- // anything they depend on first. |
- |
- if (isPublic(fieldName)) _addExport(fieldName, exportName); |
- var declKeyword = field.isConst || field.isFinal ? 'const' : 'let'; |
- if (isJSTopLevel && jsInit is JS.ClassExpression) { |
- return new JS.ClassDeclaration(jsInit); |
- } |
- return js.statement('#;', [ |
- annotate( |
- new JS.VariableDeclarationList(declKeyword, [ |
- new JS.VariableInitialization( |
- new JS.Identifier(fieldName, |
- type: emitTypeRef(field.element.type)), |
- jsInit) |
- ]), |
- field, |
- field.element) |
- ]); |
- } |
- |
- if (eagerInit && !JS.invalidStaticFieldName(fieldName)) { |
- return annotate(js.statement('# = #;', [_visit(field.name), jsInit]), |
- field, field.element); |
- } |
- |
- return _emitLazyFields(currentLibrary, [field]); |
- } |
- |
- JS.Expression _visitInitializer(VariableDeclaration node) { |
- var value = _visit(node.initializer); |
- // explicitly initialize to null, to avoid getting `undefined`. |
- // TODO(jmesserly): do this only for vars that aren't definitely assigned. |
- return value ?? new JS.LiteralNull(); |
- } |
- |
- JS.Statement _emitLazyFields( |
- Element target, List<VariableDeclaration> fields) { |
- var methods = []; |
- for (var node in fields) { |
- var name = node.name.name; |
- var element = node.element; |
- var access = _emitMemberName(name, isStatic: true); |
- methods.add(annotate( |
- new JS.Method( |
- access, |
- js.call('function() { return #; }', _visit(node.initializer)) |
- as JS.Fun, |
- isGetter: true), |
- node, |
- _findAccessor(element, getter: true))); |
- |
- // TODO(jmesserly): currently uses a dummy setter to indicate writable. |
- if (!node.isFinal && !node.isConst) { |
- methods.add(annotate( |
- new JS.Method(access, js.call('function(_) {}') as JS.Fun, |
- isSetter: true), |
- node, |
- _findAccessor(element, getter: false))); |
- } |
- } |
- |
- JS.Expression objExpr; |
- if (target is ClassElement) { |
- objExpr = new JS.Identifier(target.type.name); |
- } else { |
- objExpr = emitLibraryName(target); |
- } |
- |
- return js |
- .statement('dart.defineLazyProperties(#, { # });', [objExpr, methods]); |
- } |
- |
- PropertyAccessorElement _findAccessor(VariableElement element, |
- {bool getter}) { |
- var parent = element.enclosingElement; |
- if (parent is ClassElement) { |
- return getter |
- ? parent.getGetter(element.name) |
- : parent.getSetter(element.name); |
- } |
- return null; |
- } |
- |
- void _flushLibraryProperties(List<JS.Statement> body) { |
- if (_properties.isEmpty) return; |
- body.add(js.statement('dart.copyProperties(#, { # });', |
- [_exportsVar, _properties.map(_emitTopLevelProperty)])); |
- _properties.clear(); |
- } |
- |
- JS.Expression _emitConstructorName( |
- ConstructorElement element, DartType type, SimpleIdentifier name) { |
- var typeName = _emitTypeName(type); |
- if (name != null || element.isFactory) { |
- var namedCtor = _constructorName(element); |
- return new JS.PropertyAccess(typeName, namedCtor); |
- } |
- return typeName; |
- } |
- |
- @override |
- visitConstructorName(ConstructorName node) { |
- return _emitConstructorName(node.staticElement, node.type.type, node.name); |
- } |
- |
- JS.Expression _emitInstanceCreationExpression( |
- ConstructorElement element, |
- DartType type, |
- SimpleIdentifier name, |
- ArgumentList argumentList, |
- bool isConst) { |
- JS.Expression emitNew() { |
- JS.Expression ctor; |
- bool isFactory = false; |
- // var element = node.staticElement; |
- if (element == null) { |
- // TODO(jmesserly): this only happens if we had a static error. |
- // Should we generate a throw instead? |
- ctor = _emitTypeName(type); |
- if (name != null) { |
- ctor = new JS.PropertyAccess(ctor, _propertyName(name.name)); |
- } |
- } else { |
- ctor = _emitConstructorName(element, type, name); |
- isFactory = element.isFactory; |
- } |
- var args = _visit(argumentList) as List<JS.Expression>; |
- return isFactory ? new JS.Call(ctor, args) : new JS.New(ctor, args); |
- } |
- if (isConst) return _emitConst(emitNew); |
- return emitNew(); |
- } |
- |
- @override |
- visitInstanceCreationExpression(InstanceCreationExpression node) { |
- var element = node.staticElement; |
- var constructor = node.constructorName; |
- var name = constructor.name; |
- var type = constructor.type.type; |
- return _emitInstanceCreationExpression( |
- element, type, name, node.argumentList, node.isConst); |
- } |
- |
- /// True if this type is built-in to JS, and we use the values unwrapped. |
- /// For these types we generate a calling convention via static |
- /// "extension methods". This allows types to be extended without adding |
- /// extensions directly on the prototype. |
- bool isPrimitiveType(DartType t) => |
- typeIsPrimitiveInJS(t) || t == _types.stringType; |
- |
- bool typeIsPrimitiveInJS(DartType t) => |
- _isNumberInJS(t) || t == _types.boolType; |
- |
- bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => |
- typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); |
- |
- bool unaryOperationIsPrimitive(DartType t) => typeIsPrimitiveInJS(t); |
- |
- JS.Expression notNull(Expression expr) { |
- if (expr == null) return null; |
- var jsExpr = _visit(expr); |
- if (!isNullable(expr)) return jsExpr; |
- return js.call('dart.notNull(#)', jsExpr); |
- } |
- |
- @override |
- JS.Expression visitBinaryExpression(BinaryExpression node) { |
- var op = node.operator; |
- var left = node.leftOperand; |
- var right = node.rightOperand; |
- |
- var leftType = getStaticType(left); |
- var rightType = getStaticType(right); |
- |
- var code; |
- if (op.type.isEqualityOperator) { |
- // If we statically know LHS or RHS is null we can generate a clean check. |
- // We can also do this if both sides are the same primitive type. |
- if (_canUsePrimitiveEquality(left, right)) { |
- code = op.type == TokenType.EQ_EQ ? '# == #' : '# != #'; |
- } else if (left is SuperExpression) { |
- return _emitSend(left, op.lexeme, [right]); |
- } else { |
- var bang = op.type == TokenType.BANG_EQ ? '!' : ''; |
- code = '${bang}dart.equals(#, #)'; |
- } |
- return js.call(code, [_visit(left), _visit(right)]); |
- } |
- |
- if (op.type.lexeme == '??') { |
- // TODO(jmesserly): leave RHS for debugging? |
- // This should be a hint or warning for dead code. |
- if (!isNullable(left)) return _visit(left); |
- |
- var vars = <String, JS.Expression>{}; |
- // Desugar `l ?? r` as `l != null ? l : r` |
- var l = _visit(_bindValue(vars, 'l', left, context: left)); |
- return new JS.MetaLet(vars, [ |
- js.call('# != null ? # : #', [l, l, _visit(right)]) |
- ]); |
- } |
- |
- if (binaryOperationIsPrimitive(leftType, rightType) || |
- leftType == _types.stringType && op.type == TokenType.PLUS) { |
- // special cases where we inline the operation |
- // these values are assumed to be non-null (determined by the checker) |
- // TODO(jmesserly): it would be nice to just inline the method from core, |
- // instead of special cases here. |
- if (op.type == TokenType.TILDE_SLASH) { |
- // `a ~/ b` is equivalent to `(a / b).truncate()` |
- var div = AstBuilder.binaryExpression(left, '/', right) |
- ..staticType = node.staticType; |
- return _emitSend(div, 'truncate', []); |
- } else { |
- // TODO(vsm): When do Dart ops not map to JS? |
- code = '# $op #'; |
- } |
- return js.call(code, [notNull(left), notNull(right)]); |
- } |
- |
- return _emitSend(left, op.lexeme, [right]); |
- } |
- |
- /// If the type [t] is [int] or [double], or a type parameter |
- /// bounded by [int], [double] or [num] returns [num]. |
- /// Otherwise returns [t]. |
- DartType _canonicalizeNumTypes(DartType t) { |
- var numType = types.numType; |
- if (rules.isSubtypeOf(t, numType)) return numType; |
- return t; |
- } |
- |
- bool _canUsePrimitiveEquality(Expression left, Expression right) { |
- if (_isNull(left) || _isNull(right)) return true; |
- |
- var leftType = _canonicalizeNumTypes(getStaticType(left)); |
- var rightType = _canonicalizeNumTypes(getStaticType(right)); |
- return isPrimitiveType(leftType) && leftType == rightType; |
- } |
- |
- bool _isNull(Expression expr) => expr is NullLiteral; |
- |
- SimpleIdentifier _createTemporary(String name, DartType type, |
- {bool nullable: true}) { |
- // We use an invalid source location to signal that this is a temporary. |
- // See [_isTemporary]. |
- // TODO(jmesserly): alternatives are |
- // * (ab)use Element.isSynthetic, which isn't currently used for |
- // LocalVariableElementImpl, so we could repurpose to mean "temp". |
- // * add a new property to LocalVariableElementImpl. |
- // * create a new subtype of LocalVariableElementImpl to mark a temp. |
- var id = |
- new SimpleIdentifier(new StringToken(TokenType.IDENTIFIER, name, -1)); |
- id.staticElement = new TemporaryVariableElement.forNode(id); |
- id.staticType = type; |
- DynamicInvoke.set(id, type.isDynamic); |
- addTemporaryVariable(id.staticElement, nullable: nullable); |
- return id; |
- } |
- |
- JS.Expression _emitConst(JS.Expression expr()) { |
- // TODO(jmesserly): emit the constants at top level if possible. |
- // This wasn't quite working, so disabled for now. |
- return js.call('dart.const(#)', expr()); |
- } |
- |
- /// Returns a new expression, which can be be used safely *once* on the |
- /// left hand side, and *once* on the right side of an assignment. |
- /// For example: `expr1[expr2] += y` can be compiled as |
- /// `expr1[expr2] = expr1[expr2] + y`. |
- /// |
- /// The temporary scope will ensure `expr1` and `expr2` are only evaluated |
- /// once: `((x1, x2) => x1[x2] = x1[x2] + y)(expr1, expr2)`. |
- /// |
- /// If the expression does not end up using `x1` or `x2` more than once, or |
- /// if those expressions can be treated as stateless (e.g. they are |
- /// non-mutated variables), then the resulting code will be simplified |
- /// automatically. |
- /// |
- /// [scope] can be mutated to contain any new temporaries that were created, |
- /// unless [expr] is a SimpleIdentifier, in which case a temporary is not |
- /// needed. |
- Expression _bindLeftHandSide( |
- Map<String, JS.Expression> scope, Expression expr, |
- {Expression context}) { |
- Expression result; |
- if (expr is IndexExpression) { |
- IndexExpression index = expr; |
- result = new IndexExpression.forTarget( |
- _bindValue(scope, 'o', index.target, context: context), |
- index.leftBracket, |
- _bindValue(scope, 'i', index.index, context: context), |
- index.rightBracket); |
- } else if (expr is PropertyAccess) { |
- PropertyAccess prop = expr; |
- result = new PropertyAccess( |
- _bindValue(scope, 'o', _getTarget(prop), context: context), |
- prop.operator, |
- prop.propertyName); |
- } else if (expr is PrefixedIdentifier) { |
- PrefixedIdentifier ident = expr; |
- if (isLibraryPrefix(ident.prefix)) { |
- return expr; |
- } |
- result = new PrefixedIdentifier( |
- _bindValue(scope, 'o', ident.prefix, context: context) |
- as SimpleIdentifier, |
- ident.period, |
- ident.identifier); |
- } else { |
- return expr as SimpleIdentifier; |
- } |
- result.staticType = expr.staticType; |
- DynamicInvoke.set(result, DynamicInvoke.get(expr)); |
- return result; |
- } |
- |
- /// Creates a temporary to contain the value of [expr]. The temporary can be |
- /// used multiple times in the resulting expression. For example: |
- /// `expr ** 2` could be compiled as `expr * expr`. The temporary scope will |
- /// ensure `expr` is only evaluated once: `(x => x * x)(expr)`. |
- /// |
- /// If the expression does not end up using `x` more than once, or if those |
- /// expressions can be treated as stateless (e.g. they are non-mutated |
- /// variables), then the resulting code will be simplified automatically. |
- /// |
- /// [scope] will be mutated to contain the new temporary's initialization. |
- Expression _bindValue( |
- Map<String, JS.Expression> scope, String name, Expression expr, |
- {Expression context}) { |
- // No need to do anything for stateless expressions. |
- if (isStateless(_currentFunction, expr, context)) return expr; |
- |
- var t = _createTemporary('#$name', getStaticType(expr)); |
- scope[name] = _visit(expr); |
- return t; |
- } |
- |
- /// Desugars postfix increment. |
- /// |
- /// In the general case [expr] can be one of [IndexExpression], |
- /// [PrefixExpression] or [PropertyAccess] and we need to |
- /// ensure sub-expressions are evaluated once. |
- /// |
- /// We also need to ensure we can return the original value of the expression, |
- /// and that it is only evaluated once. |
- /// |
- /// We desugar this using let*. |
- /// |
- /// For example, `expr1[expr2]++` can be transformed to this: |
- /// |
- /// // psuedocode mix of Scheme and JS: |
- /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t }) |
- /// |
- /// The [JS.MetaLet] nodes automatically simplify themselves if they can. |
- /// For example, if the result value is not used, then `t` goes away. |
- @override |
- JS.Expression visitPostfixExpression(PostfixExpression node) { |
- var op = node.operator; |
- var expr = node.operand; |
- |
- var dispatchType = getStaticType(expr); |
- if (unaryOperationIsPrimitive(dispatchType)) { |
- if (!isNullable(expr)) { |
- return js.call('#$op', _visit(expr)); |
- } |
- } |
- |
- assert(op.lexeme == '++' || op.lexeme == '--'); |
- |
- // Handle the left hand side, to ensure each of its subexpressions are |
- // evaluated only once. |
- var vars = <String, JS.Expression>{}; |
- var left = _bindLeftHandSide(vars, expr, context: expr); |
- |
- // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value |
- // and `x1` is the new value for `x`. |
- var x = _bindValue(vars, 'x', left, context: expr); |
- |
- var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
- var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one) |
- ..staticElement = node.staticElement |
- ..staticType = getStaticType(expr); |
- |
- var body = <JS.Expression>[_emitSet(left, increment), _visit(x)]; |
- return new JS.MetaLet(vars, body, statelessResult: true); |
- } |
- |
- @override |
- JS.Expression visitPrefixExpression(PrefixExpression node) { |
- var op = node.operator; |
- var expr = node.operand; |
- |
- var dispatchType = getStaticType(expr); |
- if (unaryOperationIsPrimitive(dispatchType)) { |
- if (!isNullable(expr)) { |
- return js.call('$op#', _visit(expr)); |
- } else if (op.lexeme == '++' || op.lexeme == '--') { |
- // We need a null check, so the increment must be expanded out. |
- var vars = <String, JS.Expression>{}; |
- var x = _bindLeftHandSide(vars, expr, context: expr); |
- |
- var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
- var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one) |
- ..staticElement = node.staticElement |
- ..staticType = getStaticType(expr); |
- |
- return new JS.MetaLet(vars, [_emitSet(x, increment)]); |
- } else { |
- return js.call('$op#', notNull(expr)); |
- } |
- } |
- |
- if (op.lexeme == '++' || op.lexeme == '--') { |
- // Increment or decrement requires expansion. |
- // Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions |
- // (for example, x is IndexExpression) we evaluate those once. |
- var one = AstBuilder.integerLiteral(1)..staticType = types.intType; |
- return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement, |
- context: expr); |
- } |
- |
- return _emitSend(expr, op.lexeme[0], []); |
- } |
- |
- // Cascades can contain [IndexExpression], [MethodInvocation] and |
- // [PropertyAccess]. The code generation for those is handled in their |
- // respective visit methods. |
- @override |
- JS.Node visitCascadeExpression(CascadeExpression node) { |
- var savedCascadeTemp = _cascadeTarget; |
- |
- var vars = <String, JS.Expression>{}; |
- _cascadeTarget = _bindValue(vars, '_', node.target, context: node); |
- var sections = _visitList(node.cascadeSections) as List<JS.Expression>; |
- sections.add(_visit(_cascadeTarget)); |
- var result = new JS.MetaLet(vars, sections, statelessResult: true); |
- _cascadeTarget = savedCascadeTemp; |
- return result; |
- } |
- |
- @override |
- visitParenthesizedExpression(ParenthesizedExpression node) => |
- // The printer handles precedence so we don't need to. |
- _visit(node.expression); |
- |
- @override |
- visitFormalParameter(FormalParameter node) { |
- var id = _emitParameter(node.element, declaration: true); |
- var isRestArg = findAnnotation(node.element, isJsRestAnnotation) != null; |
- return isRestArg ? new JS.RestParameter(id) : id; |
- } |
- |
- @override |
- JS.This visitThisExpression(ThisExpression node) => new JS.This(); |
- |
- @override |
- JS.Super visitSuperExpression(SuperExpression node) => new JS.Super(); |
- |
- @override |
- visitPrefixedIdentifier(PrefixedIdentifier node) { |
- if (isLibraryPrefix(node.prefix)) { |
- return _visit(node.identifier); |
- } else { |
- return _emitGet(node.prefix, node.identifier); |
- } |
- } |
- |
- @override |
- visitPropertyAccess(PropertyAccess node) { |
- if (node.operator.lexeme == '?.') { |
- return _emitNullSafe(node); |
- } |
- return _emitGet(_getTarget(node), node.propertyName); |
- } |
- |
- JS.Expression _emitNullSafe(Expression node) { |
- // Desugar ?. sequence by passing a sequence of callbacks that applies |
- // each operation in sequence: |
- // |
- // obj?.foo()?.bar |
- // --> |
- // nullSafe(obj, _ => _.foo(), _ => _.bar); |
- // |
- // This pattern has the benefit of preserving order, as well as minimizing |
- // code expansion: each `?.` becomes `, _ => _`, plus one helper call. |
- // |
- // TODO(jmesserly): we could desugar with MetaLet instead, which may |
- // lead to higher performing code, but at the cost of readability. |
- var tail = <JS.Expression>[]; |
- for (;;) { |
- var op = _getOperator(node); |
- if (op != null && op.lexeme == '?.') { |
- var nodeTarget = _getTarget(node); |
- if (!isNullable(nodeTarget)) { |
- node = _stripNullAwareOp(node, nodeTarget); |
- break; |
- } |
- |
- var param = |
- _createTemporary('_', nodeTarget.staticType, nullable: false); |
- var baseNode = _stripNullAwareOp(node, param); |
- tail.add(new JS.ArrowFun([_visit(param)], _visit(baseNode))); |
- node = nodeTarget; |
- } else { |
- break; |
- } |
- } |
- if (tail.isEmpty) return _visit(node); |
- return js.call('dart.nullSafe(#, #)', [_visit(node), tail.reversed]); |
- } |
- |
- static Token _getOperator(Expression node) { |
- if (node is PropertyAccess) return node.operator; |
- if (node is MethodInvocation) return node.operator; |
- return null; |
- } |
- |
- // TODO(jmesserly): this is dropping source location. |
- Expression _stripNullAwareOp(Expression node, Expression newTarget) { |
- if (node is PropertyAccess) { |
- return AstBuilder.propertyAccess(newTarget, node.propertyName); |
- } else { |
- var invoke = node as MethodInvocation; |
- return AstBuilder.methodInvoke( |
- newTarget, invoke.methodName, invoke.argumentList.arguments); |
- } |
- } |
- |
- bool _requiresStaticDispatch(Expression target, String memberName) { |
- var type = getStaticType(target); |
- if (!isObjectProperty(memberName)) { |
- return false; |
- } |
- |
- // If the target could be `null`, we need static dispatch. |
- // If the target may be an extension type, we also use static dispatch |
- // as we don't symbolize object properties like hashCode. |
- return isNullable(target) || |
- (_extensionTypes.contains(type.element) && target is! SuperExpression); |
- } |
- |
- /// Shared code for [PrefixedIdentifier] and [PropertyAccess]. |
- JS.Expression _emitGet(Expression target, SimpleIdentifier memberId) { |
- var member = memberId.staticElement; |
- if (member is PropertyAccessorElement) { |
- member = (member as PropertyAccessorElement).variable; |
- } |
- bool isStatic = member is ClassMemberElement && member.isStatic; |
- var name = _emitMemberName(memberId.name, |
- type: getStaticType(target), isStatic: isStatic); |
- if (DynamicInvoke.get(target)) { |
- return js.call('dart.$DLOAD(#, #)', [_visit(target), name]); |
- } |
- |
- String code; |
- if (member != null && member is MethodElement && !isStatic) { |
- // Tear-off methods: explicitly bind it. |
- if (target is SuperExpression) { |
- return js.call('dart.bind(this, #, #.#)', [name, _visit(target), name]); |
- } else if (_requiresStaticDispatch(target, memberId.name)) { |
- var type = member.type; |
- var clos = js.call('dart.#.bind(#)', [name, _visit(target)]); |
- return js.call('dart.fn(#, #)', [clos, _emitFunctionTypeParts(type)]); |
- } |
- code = 'dart.bind(#, #)'; |
- } else if (_requiresStaticDispatch(target, memberId.name)) { |
- return js.call('dart.#(#)', [name, _visit(target)]); |
- } else { |
- code = '#.#'; |
- } |
- |
- return js.call(code, [_visit(target), name]); |
- } |
- |
- /// Emits a generic send, like an operator method. |
- /// |
- /// **Please note** this function does not support method invocation syntax |
- /// `obj.name(args)` because that could be a getter followed by a call. |
- /// See [visitMethodInvocation]. |
- JS.Expression _emitSend( |
- Expression target, String name, List<Expression> args) { |
- var type = getStaticType(target); |
- var memberName = _emitMemberName(name, unary: args.isEmpty, type: type); |
- if (DynamicInvoke.get(target)) { |
- // dynamic dispatch |
- var dynamicHelper = const {'[]': DINDEX, '[]=': DSETINDEX}[name]; |
- if (dynamicHelper != null) { |
- return js.call( |
- 'dart.$dynamicHelper(#, #)', [_visit(target), _visitList(args)]); |
- } |
- return js.call('dart.$DSEND(#, #, #)', |
- [_visit(target), memberName, _visitList(args)]); |
- } |
- |
- // Generic dispatch to a statically known method. |
- return js.call('#.#(#)', [_visit(target), memberName, _visitList(args)]); |
- } |
- |
- @override |
- visitIndexExpression(IndexExpression node) { |
- var target = _getTarget(node); |
- if (_useNativeJsIndexer(target.staticType)) { |
- return new JS.PropertyAccess(_visit(target), _visit(node.index)); |
- } |
- return _emitSend(target, '[]', [node.index]); |
- } |
- |
- // TODO(jmesserly): ideally we'd check the method and see if it is marked |
- // `external`, but that doesn't work because it isn't in the element model. |
- bool _useNativeJsIndexer(DartType type) => |
- findAnnotation(type.element, isJSAnnotation) != null; |
- |
- /// Gets the target of a [PropertyAccess], [IndexExpression], or |
- /// [MethodInvocation]. These three nodes can appear in a [CascadeExpression]. |
- Expression _getTarget(node) { |
- assert(node is IndexExpression || |
- node is PropertyAccess || |
- node is MethodInvocation); |
- return node.isCascaded ? _cascadeTarget : node.target; |
- } |
- |
- @override |
- visitConditionalExpression(ConditionalExpression node) { |
- return js.call('# ? # : #', [ |
- notNull(node.condition), |
- _visit(node.thenExpression), |
- _visit(node.elseExpression) |
- ]); |
- } |
- |
- @override |
- visitThrowExpression(ThrowExpression node) { |
- var expr = _visit(node.expression); |
- if (node.parent is ExpressionStatement) { |
- return js.statement('dart.throw(#);', expr); |
- } else { |
- return js.call('dart.throw(#)', expr); |
- } |
- } |
- |
- @override |
- visitRethrowExpression(RethrowExpression node) { |
- if (node.parent is ExpressionStatement) { |
- return js.statement('throw #;', _visit(_catchParameter)); |
- } else { |
- return js.call('throw #', _visit(_catchParameter)); |
- } |
- } |
- |
- /// Visits a statement, and ensures the resulting AST handles block scope |
- /// correctly. Essentially, we need to promote a variable declaration |
- /// statement into a block in some cases, e.g. |
- /// |
- /// do var x = 5; while (false); // Dart |
- /// do { let x = 5; } while (false); // JS |
- JS.Statement _visitScope(Statement stmt) { |
- var result = _visit(stmt); |
- if (result is JS.ExpressionStatement && |
- result.expression is JS.VariableDeclarationList) { |
- return new JS.Block([result]); |
- } |
- return result; |
- } |
- |
- @override |
- JS.If visitIfStatement(IfStatement node) { |
- return new JS.If(notNull(node.condition), _visitScope(node.thenStatement), |
- _visitScope(node.elseStatement)); |
- } |
- |
- @override |
- JS.For visitForStatement(ForStatement node) { |
- var init = _visit(node.initialization); |
- if (init == null) init = _visit(node.variables); |
- var update = _visitListToBinary(node.updaters, ','); |
- if (update != null) update = update.toVoidExpression(); |
- return new JS.For( |
- init, notNull(node.condition), update, _visitScope(node.body)); |
- } |
- |
- @override |
- JS.While visitWhileStatement(WhileStatement node) { |
- return new JS.While(notNull(node.condition), _visitScope(node.body)); |
- } |
- |
- @override |
- JS.Do visitDoStatement(DoStatement node) { |
- return new JS.Do(_visitScope(node.body), notNull(node.condition)); |
- } |
- |
- @override |
- JS.Statement visitForEachStatement(ForEachStatement node) { |
- if (node.awaitKeyword != null) { |
- return _emitAwaitFor(node); |
- } |
- |
- var init = _visit(node.identifier); |
- if (init == null) { |
- init = js.call('let #', node.loopVariable.identifier.name); |
- } |
- return new JS.ForOf(init, _visit(node.iterable), _visitScope(node.body)); |
- } |
- |
- JS.Statement _emitAwaitFor(ForEachStatement node) { |
- // Emits `await for (var value in stream) ...`, which desugars as: |
- // |
- // var iter = new StreamIterator(stream); |
- // try { |
- // while (await iter.moveNext()) { |
- // var value = iter.current; |
- // ... |
- // } |
- // } finally { |
- // await iter.cancel(); |
- // } |
- // |
- // Like the Dart VM, we call cancel() always, as it's safe to call if the |
- // stream has already been cancelled. |
- // |
- // TODO(jmesserly): we may want a helper if these become common. For now the |
- // full desugaring seems okay. |
- var context = compiler.context; |
- var dart_async = context |
- .computeLibraryElement(context.sourceFactory.forUri('dart:async')); |
- var _streamIteratorType = |
- rules.instantiateToBounds(dart_async.getType('StreamIterator').type); |
- |
- var createStreamIter = _emitInstanceCreationExpression( |
- _streamIteratorType.element.unnamedConstructor, |
- _streamIteratorType, |
- null, |
- AstBuilder.argumentList([node.iterable]), |
- false); |
- var iter = |
- _visit(_createTemporary('it', _streamIteratorType, nullable: false)); |
- |
- var init = _visit(node.identifier); |
- if (init == null) { |
- init = js |
- .call('let # = #.current', [node.loopVariable.identifier.name, iter]); |
- } else { |
- init = js.call('# = #.current', [init, iter]); |
- } |
- return js.statement( |
- '{' |
- ' let # = #;' |
- ' try {' |
- ' while (#) { #; #; }' |
- ' } finally { #; }' |
- '}', |
- [ |
- iter, |
- createStreamIter, |
- new JS.Yield(js.call('#.moveNext()', iter)), |
- init, |
- _visit(node.body), |
- new JS.Yield(js.call('#.cancel()', iter)) |
- ]); |
- } |
- |
- @override |
- visitBreakStatement(BreakStatement node) { |
- var label = node.label; |
- return new JS.Break(label?.name); |
- } |
- |
- @override |
- visitContinueStatement(ContinueStatement node) { |
- var label = node.label; |
- return new JS.Continue(label?.name); |
- } |
- |
- @override |
- visitTryStatement(TryStatement node) { |
- return new JS.Try(_visit(node.body), _visitCatch(node.catchClauses), |
- _visit(node.finallyBlock)); |
- } |
- |
- _visitCatch(NodeList<CatchClause> clauses) { |
- if (clauses == null || clauses.isEmpty) return null; |
- |
- // TODO(jmesserly): need a better way to get a temporary variable. |
- // This could incorrectly shadow a user's name. |
- var savedCatch = _catchParameter; |
- |
- if (clauses.length == 1 && clauses.single.exceptionParameter != null) { |
- // Special case for a single catch. |
- _catchParameter = clauses.single.exceptionParameter; |
- } else { |
- _catchParameter = _createTemporary('e', types.dynamicType); |
- } |
- |
- JS.Statement catchBody = js.statement('throw #;', _visit(_catchParameter)); |
- for (var clause in clauses.reversed) { |
- catchBody = _catchClauseGuard(clause, catchBody); |
- } |
- |
- var catchVarDecl = _visit(_catchParameter); |
- _catchParameter = savedCatch; |
- return new JS.Catch(catchVarDecl, new JS.Block([catchBody])); |
- } |
- |
- JS.Statement _catchClauseGuard(CatchClause clause, JS.Statement otherwise) { |
- var then = visitCatchClause(clause); |
- |
- // Discard following clauses, if any, as they are unreachable. |
- if (clause.exceptionType == null) return then; |
- |
- // TODO(jmesserly): this is inconsistent with [visitIsExpression], which |
- // has special case for typeof. |
- return new JS.If( |
- js.call('dart.is(#, #)', [ |
- _visit(_catchParameter), |
- _emitTypeName(clause.exceptionType.type), |
- ]), |
- then, |
- otherwise); |
- } |
- |
- JS.Statement _statement(List<JS.Statement> statements) { |
- // TODO(jmesserly): empty block singleton? |
- if (statements.length == 0) return new JS.Block([]); |
- if (statements.length == 1) return statements[0]; |
- return new JS.Block(statements); |
- } |
- |
- /// Visits the catch clause body. This skips the exception type guard, if any. |
- /// That is handled in [_visitCatch]. |
- @override |
- JS.Statement visitCatchClause(CatchClause node) { |
- var body = <JS.Statement>[]; |
- |
- var savedCatch = _catchParameter; |
- if (node.catchKeyword != null) { |
- var name = node.exceptionParameter; |
- if (name != null && name != _catchParameter) { |
- body.add(js |
- .statement('let # = #;', [_visit(name), _visit(_catchParameter)])); |
- _catchParameter = name; |
- } |
- if (node.stackTraceParameter != null) { |
- var stackVar = node.stackTraceParameter.name; |
- body.add(js.statement( |
- 'let # = dart.stackTrace(#);', [stackVar, _visit(name)])); |
- } |
- } |
- |
- body.add( |
- new JS.Block(_visitList(node.body.statements) as List<JS.Statement>)); |
- _catchParameter = savedCatch; |
- return _statement(body); |
- } |
- |
- @override |
- JS.Case visitSwitchCase(SwitchCase node) { |
- var expr = _visit(node.expression); |
- var body = _visitList(node.statements) as List<JS.Statement>; |
- if (node.labels.isNotEmpty) { |
- body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); |
- } |
- // TODO(jmesserly): make sure we are statically checking fall through |
- return new JS.Case(expr, new JS.Block(body)); |
- } |
- |
- @override |
- JS.Default visitSwitchDefault(SwitchDefault node) { |
- var body = _visitList(node.statements) as List<JS.Statement>; |
- if (node.labels.isNotEmpty) { |
- body.insert(0, js.comment('Unimplemented case labels: ${node.labels}')); |
- } |
- // TODO(jmesserly): make sure we are statically checking fall through |
- return new JS.Default(new JS.Block(body)); |
- } |
- |
- @override |
- JS.Switch visitSwitchStatement(SwitchStatement node) => new JS.Switch( |
- _visit(node.expression), |
- _visitList(node.members) as List<JS.SwitchClause>); |
- |
- @override |
- JS.Statement visitLabeledStatement(LabeledStatement node) { |
- var result = _visit(node.statement); |
- for (var label in node.labels.reversed) { |
- result = new JS.LabeledStatement(label.label.name, result); |
- } |
- return result; |
- } |
- |
- @override |
- visitIntegerLiteral(IntegerLiteral node) => js.number(node.value); |
- |
- @override |
- visitDoubleLiteral(DoubleLiteral node) => js.number(node.value); |
- |
- @override |
- visitNullLiteral(NullLiteral node) => new JS.LiteralNull(); |
- |
- @override |
- visitSymbolLiteral(SymbolLiteral node) { |
- JS.New emitSymbol() { |
- // TODO(vsm): When we canonicalize, we need to treat private symbols |
- // correctly. |
- var name = js.string(node.components.join('.'), "'"); |
- return new JS.New(_emitTypeName(types.symbolType), [name]); |
- } |
- return _emitConst(emitSymbol); |
- } |
- |
- @override |
- visitListLiteral(ListLiteral node) { |
- JS.Expression emitList() { |
- JS.Expression list = new JS.ArrayInitializer( |
- _visitList(node.elements) as List<JS.Expression>); |
- ParameterizedType type = node.staticType; |
- var elementType = type.typeArguments.single; |
- // TODO(jmesserly): analyzer will usually infer `List<Object>` because |
- // that is the least upper bound of the element types. So we rarely |
- // generate a plain `List<dynamic>` anymore. |
- if (!elementType.isDynamic) { |
- // dart.list helper internally depends on _interceptors.JSArray. |
- _loader.declareBeforeUse(_jsArray); |
- list = js.call('dart.list(#, #)', [list, _emitTypeName(elementType)]); |
- } |
- return list; |
- } |
- if (node.constKeyword != null) return _emitConst(emitList); |
- return emitList(); |
- } |
- |
- @override |
- visitMapLiteral(MapLiteral node) { |
- // TODO(jmesserly): we can likely make these faster. |
- JS.Expression emitMap() { |
- var entries = node.entries; |
- var mapArguments = null; |
- var typeArgs = node.typeArguments; |
- if (entries.isEmpty && typeArgs == null) { |
- mapArguments = []; |
- } else if (entries.every((e) => e.key is StringLiteral)) { |
- // Use JS object literal notation if possible, otherwise use an array. |
- // We could do this any time all keys are non-nullable String type. |
- // For now, support StringLiteral as the common non-nullable String case. |
- var props = <JS.Property>[]; |
- for (var e in entries) { |
- props.add(new JS.Property(_visit(e.key), _visit(e.value))); |
- } |
- mapArguments = new JS.ObjectInitializer(props); |
- } else { |
- var values = <JS.Expression>[]; |
- for (var e in entries) { |
- values.add(_visit(e.key)); |
- values.add(_visit(e.value)); |
- } |
- mapArguments = new JS.ArrayInitializer(values); |
- } |
- var types = <JS.Expression>[]; |
- if (typeArgs != null) { |
- types.addAll(typeArgs.arguments.map((e) => _emitTypeName(e.type))); |
- } |
- return js.call('dart.map(#, #)', [mapArguments, types]); |
- } |
- if (node.constKeyword != null) return _emitConst(emitMap); |
- return emitMap(); |
- } |
- |
- @override |
- JS.LiteralString visitSimpleStringLiteral(SimpleStringLiteral node) => |
- js.escapedString(node.value, node.isSingleQuoted ? "'" : '"'); |
- |
- @override |
- JS.Expression visitAdjacentStrings(AdjacentStrings node) => |
- _visitListToBinary(node.strings, '+'); |
- |
- @override |
- JS.TemplateString visitStringInterpolation(StringInterpolation node) { |
- // Assuming we implement toString() on our objects, we can avoid calling it |
- // in most cases. Builtin types may differ though. We could handle this with |
- // a tagged template. |
- return new JS.TemplateString(_visitList(node.elements)); |
- } |
- |
- @override |
- String visitInterpolationString(InterpolationString node) { |
- // TODO(jmesserly): this call adds quotes, and then we strip them off. |
- var str = js.escapedString(node.value, '`').value; |
- return str.substring(1, str.length - 1); |
- } |
- |
- @override |
- visitInterpolationExpression(InterpolationExpression node) => |
- _visit(node.expression); |
- |
- @override |
- visitBooleanLiteral(BooleanLiteral node) => js.boolean(node.value); |
- |
- @override |
- JS.Expression visitExpression(Expression node) => |
- _unimplementedCall('Unimplemented ${node.runtimeType}: $node'); |
- |
- JS.Expression _unimplementedCall(String comment) { |
- return js.call('dart.throw(#)', [js.escapedString(comment)]); |
- } |
- |
- @override |
- visitNode(AstNode node) { |
- // TODO(jmesserly): verify this is unreachable. |
- throw 'Unimplemented ${node.runtimeType}: $node'; |
- } |
- |
- _visit(AstNode node) { |
- if (node == null) return null; |
- var result = node.accept(this); |
- if (result is JS.Node) result = annotate(result, node); |
- return result; |
- } |
- |
- // TODO(jmesserly): this will need to be a generic method, if we ever want to |
- // self-host strong mode. |
- List/*<T>*/ _visitList/*<T>*/(Iterable<AstNode> nodes) { |
- if (nodes == null) return null; |
- var result = /*<T>*/ []; |
- for (var node in nodes) result.add(_visit(node)); |
- return result; |
- } |
- |
- /// Visits a list of expressions, creating a comma expression if needed in JS. |
- JS.Expression _visitListToBinary(List<Expression> nodes, String operator) { |
- if (nodes == null || nodes.isEmpty) return null; |
- return new JS.Expression.binary( |
- _visitList(nodes) as List<JS.Expression>, operator); |
- } |
- |
- /// Return the bound type parameters for a ParameterizedType |
- List<TypeParameterElement> _typeFormalsOf(ParameterizedType type) { |
- return type is FunctionType ? type.typeFormals : type.typeParameters; |
- } |
- |
- /// Like [_emitMemberName], but for declaration sites. |
- /// |
- /// Unlike call sites, we always have an element available, so we can use it |
- /// directly rather than computing the relevant options for [_emitMemberName]. |
- JS.Expression _elementMemberName(ExecutableElement e, |
- {bool allowExtensions: true}) { |
- String name; |
- if (e is PropertyAccessorElement) { |
- name = e.variable.name; |
- } else { |
- name = e.name; |
- } |
- return _emitMemberName(name, |
- type: (e.enclosingElement as ClassElement).type, |
- unary: e.parameters.isEmpty, |
- isStatic: e.isStatic, |
- allowExtensions: allowExtensions); |
- } |
- |
- /// This handles member renaming for private names and operators. |
- /// |
- /// Private names are generated using ES6 symbols: |
- /// |
- /// // At the top of the module: |
- /// let _x = Symbol('_x'); |
- /// let _y = Symbol('_y'); |
- /// ... |
- /// |
- /// class Point { |
- /// Point(x, y) { |
- /// this[_x] = x; |
- /// this[_y] = y; |
- /// } |
- /// get x() { return this[_x]; } |
- /// get y() { return this[_y]; } |
- /// } |
- /// |
- /// For user-defined operators the following names are allowed: |
- /// |
- /// <, >, <=, >=, ==, -, +, /, ~/, *, %, |, ^, &, <<, >>, []=, [], ~ |
- /// |
- /// They generate code like: |
- /// |
- /// x['+'](y) |
- /// |
- /// There are three exceptions: [], []= and unary -. |
- /// The indexing operators we use `get` and `set` instead: |
- /// |
- /// x.get('hi') |
- /// x.set('hi', 123) |
- /// |
- /// This follows the same pattern as EcmaScript 6 Map: |
- /// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map> |
- /// |
- /// Unary minus looks like: `x['unary-']()`. Note that [unary] must be passed |
- /// for this transformation to happen, otherwise binary minus is assumed. |
- /// |
- /// Equality is a bit special, it is generated via the Dart `equals` runtime |
- /// helper, that checks for null. The user defined method is called '=='. |
- /// |
- JS.Expression _emitMemberName(String name, |
- {DartType type, |
- bool unary: false, |
- bool isStatic: false, |
- bool allowExtensions: true}) { |
- // Static members skip the rename steps. |
- if (isStatic) return _propertyName(name); |
- |
- if (name.startsWith('_')) { |
- return _privateNames.putIfAbsent( |
- name, () => _initSymbol(new JS.TemporaryId(name)) as JS.TemporaryId); |
- } |
- |
- if (name == '[]') { |
- name = 'get'; |
- } else if (name == '[]=') { |
- name = 'set'; |
- } else if (name == '-' && unary) { |
- name = 'unary-'; |
- } else if (name == 'constructor' || name == 'prototype') { |
- // This uses an illegal (in Dart) character for a member, avoiding the |
- // conflict. We could use practically any character for this. |
- name = '+$name'; |
- } |
- |
- // Dart "extension" methods. Used for JS Array, Boolean, Number, String. |
- var baseType = type; |
- while (baseType is TypeParameterType) { |
- baseType = baseType.element.bound; |
- } |
- if (allowExtensions && |
- baseType != null && |
- _extensionTypes.contains(baseType.element) && |
- !isObjectProperty(name)) { |
- return js.call('dartx.#', _propertyName(name)); |
- } |
- |
- return _propertyName(name); |
- } |
- |
- bool _externalOrNative(node) => |
- node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; |
- |
- FunctionBody _functionBody(node) => |
- node is FunctionDeclaration ? node.functionExpression.body : node.body; |
- |
- /// Choose a canonical name from the library element. |
- /// This never uses the library's name (the identifier in the `library` |
- /// declaration) as it doesn't have any meaningful rules enforced. |
- JS.Identifier emitLibraryName(LibraryElement library) { |
- if (library == currentLibrary) return _exportsVar; |
- if (library.name == 'dart._runtime') return _runtimeLibVar; |
- return _imports.putIfAbsent( |
- library, () => new JS.TemporaryId(jsLibraryName(library))); |
- } |
- |
- JS.Node annotate(JS.Node node, AstNode original, [Element element]) { |
- if (options.closure && element != null) { |
- node = node.withClosureAnnotation(closureAnnotationFor( |
- node, original, element, namedArgumentTemp.name)); |
- } |
- return node..sourceInformation = original; |
- } |
- |
- /// Returns true if this is any kind of object represented by `Number` in JS. |
- /// |
- /// In practice, this is 4 types: num, int, double, and JSNumber. |
- /// |
- /// JSNumber is the type that actually "implements" all numbers, hence it's |
- /// a subtype of int and double (and num). It's in our "dart:_interceptors". |
- bool _isNumberInJS(DartType t) => rules.isSubtypeOf(t, _types.numType); |
- |
- bool _isObjectGetter(String name) { |
- PropertyAccessorElement element = _types.objectType.element.getGetter(name); |
- return (element != null && !element.isStatic); |
- } |
- |
- bool _isObjectMethod(String name) { |
- MethodElement element = _types.objectType.element.getMethod(name); |
- return (element != null && !element.isStatic); |
- } |
- |
- bool isObjectProperty(String name) { |
- return _isObjectGetter(name) || _isObjectMethod(name); |
- } |
- |
- // TODO(leafp): Various analyzer pieces computed similar things. |
- // Share this logic somewhere? |
- DartType _getExpectedReturnType(ExecutableElement element) { |
- FunctionType functionType = element.type; |
- if (functionType == null) { |
- return DynamicTypeImpl.instance; |
- } |
- var type = functionType.returnType; |
- |
- InterfaceType expectedType = null; |
- if (element.isAsynchronous) { |
- if (element.isGenerator) { |
- // Stream<T> -> T |
- expectedType = _types.streamType; |
- } else { |
- // Future<T> -> T |
- // TODO(vsm): Revisit with issue #228. |
- expectedType = _types.futureType; |
- } |
- } else { |
- if (element.isGenerator) { |
- // Iterable<T> -> T |
- expectedType = _types.iterableType; |
- } else { |
- // T -> T |
- return type; |
- } |
- } |
- if (type.isDynamic) { |
- return type; |
- } else if (type is InterfaceType && type.element == expectedType.element) { |
- return type.typeArguments[0]; |
- } else { |
- // TODO(leafp): The above only handles the case where the return type |
- // is exactly Future/Stream/Iterable. Handle the subtype case. |
- return DynamicTypeImpl.instance; |
- } |
- } |
-} |
- |
-class ExtensionTypeSet extends GeneralizingElementVisitor { |
- final AnalysisContext _context; |
- final TypeProvider _types; |
- |
- final _extensionTypes = new HashSet<ClassElement>(); |
- final _pendingLibraries = new HashSet<String>(); |
- |
- ExtensionTypeSet(AbstractCompiler compiler) |
- : _context = compiler.context, |
- _types = compiler.context.typeProvider; |
- |
- visitClassElement(ClassElement element) { |
- if (findAnnotation(element, isJsPeerInterface) != null || |
- findAnnotation(element, isNativeAnnotation) != null) { |
- _addExtensionType(element.type); |
- } |
- } |
- |
- void _addExtensionType(InterfaceType t) { |
- if (t.isObject || !_extensionTypes.add(t.element)) return; |
- t = fillDynamicTypeArgs(t, _types) as InterfaceType; |
- t.interfaces.forEach(_addExtensionType); |
- t.mixins.forEach(_addExtensionType); |
- _addExtensionType(t.superclass); |
- } |
- |
- void _addExtensionTypesForLibrary(String libraryUri, List<String> typeNames) { |
- var sourceFactory = _context.sourceFactory.forUri(libraryUri); |
- var library = _context.computeLibraryElement(sourceFactory); |
- for (var typeName in typeNames) { |
- var element = library.getType(typeName); |
- _addExtensionType(element.type); |
- } |
- } |
- |
- void _addExtensionTypes(String libraryUri) { |
- var sourceFactory = _context.sourceFactory.forUri(libraryUri); |
- var library = _context.computeLibraryElement(sourceFactory); |
- visitLibraryElement(library); |
- } |
- |
- void _addPendingExtensionTypes(String libraryUri) { |
- _pendingLibraries.add(libraryUri); |
- } |
- |
- bool contains(Element element) { |
- if (_extensionTypes.contains(element)) return true; |
- if (_pendingLibraries.isEmpty) return false; |
- if (element is ClassElement) { |
- var uri = element.library.source.uri.toString(); |
- if (_pendingLibraries.contains(uri)) { |
- // Load all pending libraries |
- for (var libraryUri in _pendingLibraries) { |
- _addExtensionTypes(libraryUri); |
- } |
- _pendingLibraries.clear(); |
- return _extensionTypes.contains(element); |
- } |
- } |
- return false; |
- } |
-} |
- |
-class JSGenerator { |
- final AbstractCompiler compiler; |
- final ExtensionTypeSet _extensionTypes; |
- final TypeProvider _types; |
- |
- JSGenerator(AbstractCompiler compiler) |
- : compiler = compiler, |
- _types = compiler.context.typeProvider, |
- _extensionTypes = new ExtensionTypeSet(compiler) { |
- // TODO(vsm): Eventually, we want to make this extensible - i.e., find |
- // annotations in user code as well. It would need to be summarized in |
- // the element model - not searched this way on every compile. To make this |
- // a little more efficient now, we do this in two phases. |
- |
- // First, core types: |
- _extensionTypes._addExtensionTypes('dart:_interceptors'); |
- _extensionTypes._addExtensionTypes('dart:_native_typed_data'); |
- // TODO(vsm): If we're analyzing against the main SDK, those |
- // types are not explicitly annotated. |
- _extensionTypes._addExtensionType(_types.intType); |
- _extensionTypes._addExtensionType(_types.doubleType); |
- _extensionTypes._addExtensionType(_types.boolType); |
- _extensionTypes._addExtensionType(_types.stringType); |
- // These are used natively by dart:html but also not annotated. |
- _extensionTypes |
- ._addExtensionTypesForLibrary('dart:core', ['Comparable', 'Map']); |
- _extensionTypes |
- ._addExtensionTypesForLibrary('dart:collection', ['ListMixin']); |
- _extensionTypes._addExtensionTypesForLibrary('dart:math', ['Rectangle']); |
- |
- // Second, html types - these are only searched if we use dart:html, etc.: |
- _extensionTypes._addPendingExtensionTypes('dart:html'); |
- _extensionTypes._addPendingExtensionTypes('dart:indexed_db'); |
- _extensionTypes._addPendingExtensionTypes('dart:svg'); |
- _extensionTypes._addPendingExtensionTypes('dart:web_audio'); |
- _extensionTypes._addPendingExtensionTypes('dart:web_gl'); |
- _extensionTypes._addPendingExtensionTypes('dart:web_sql'); |
- } |
- |
- void generateLibrary(List<CompilationUnit> units) { |
- var library = units.first.element.library; |
- var fields = |
- findFieldsNeedingStorage(units.map((c) => c.element), _extensionTypes); |
- var rules = new StrongTypeSystemImpl(); |
- var codegen = |
- new JSCodegenVisitor(compiler, rules, library, _extensionTypes, fields); |
- var module = codegen.emitLibrary(units); |
- var out = compiler.getOutputPath(library.source.uri); |
- var options = compiler.options.codegenOptions; |
- writeJsLibrary(module, out, compiler.inputBaseDir, |
- emitTypes: options.closure, |
- emitSourceMaps: options.emitSourceMaps, |
- fileSystem: compiler.fileSystem); |
- } |
-} |
- |
-/// Choose a canonical name from the library element. |
-/// This never uses the library's name (the identifier in the `library` |
-/// declaration) as it doesn't have any meaningful rules enforced. |
-String jsLibraryName(LibraryElement library) => canonicalLibraryName(library); |
- |
-/// Shorthand for identifier-like property names. |
-/// For now, we emit them as strings and the printer restores them to |
-/// identifiers if it can. |
-// TODO(jmesserly): avoid the round tripping through quoted form. |
-JS.LiteralString _propertyName(String name) => js.string(name, "'"); |
- |
-// TODO(jacobr): we would like to do something like the following |
-// but we don't have summary support yet. |
-// bool _supportJsExtensionMethod(AnnotatedNode node) => |
-// _getAnnotation(node, "SupportJsExtensionMethod") != null; |
- |
-/// A special kind of element created by the compiler, signifying a temporary |
-/// variable. These objects use instance equality, and should be shared |
-/// everywhere in the tree where they are treated as the same variable. |
-class TemporaryVariableElement extends LocalVariableElementImpl { |
- TemporaryVariableElement.forNode(Identifier name) : super.forNode(name); |
- |
- int get hashCode => identityHashCode(this); |
- bool operator ==(Object other) => identical(this, other); |
-} |