Index: lib/src/compiler/code_generator.dart |
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/compiler/code_generator.dart |
similarity index 78% |
rename from lib/src/codegen/js_codegen.dart |
rename to lib/src/compiler/code_generator.dart |
index 35ebdb741cea72b4a74b53caea4c525f115fad63..ca56b1f7938fadaa362d2916ea0bc13415cc0d81 100644 |
--- a/lib/src/codegen/js_codegen.dart |
+++ b/lib/src/compiler/code_generator.dart |
@@ -2,76 +2,76 @@ |
// 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 'dart:collection' show HashMap, HashSet; |
import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
-import 'package:analyzer/dart/ast/token.dart'; |
+import 'package:analyzer/dart/ast/token.dart' show Token, TokenType; |
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; |
+ show 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/resolver.dart' |
+ show TypeProvider, NamespaceBuilder; |
+import 'package:analyzer/src/dart/ast/token.dart' show StringToken; |
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 'package:analyzer/src/summary/summarize_elements.dart' |
+ show PackageBundleAssembler; |
+import 'package:analyzer/src/task/strong/info.dart' show DynamicInvoke; |
+import '../js_ast/js_ast.dart' as JS; |
+import '../js_ast/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 'ast_builder.dart' show AstBuilder; |
+import 'compiler.dart' |
+ show BuildUnit, CompilerOptions, JSModuleFile, ModuleFormat; |
+import 'element_helpers.dart'; |
+import 'element_loader.dart' show ElementLoader; |
+import 'extension_types.dart' show ExtensionTypeSet; |
+import 'js_field_storage.dart' show findFieldsNeedingStorage; |
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 |
+import 'js_typeref_codegen.dart' show JsTypeRefCodegen; |
+import 'module_builder.dart' |
+ show LegacyModuleBuilder, NodeModuleBuilder, pathToJSIdentifier; |
+import 'nullable_type_inference.dart' show NullableTypeInference; |
+import 'reify_coercions.dart' show CoercionReifier; |
+import 'side_effect_analysis.dart' show ConstFieldVisitor, isStateless; |
+import 'source_map_printer.dart' show SourceMapPrintingContext; |
+import 'package:source_maps/source_maps.dart'; |
+ |
+class CodeGenerator extends GeneralizingAstVisitor |
with ClosureAnnotator, JsTypeRefCodegen, NullableTypeInference { |
- final AbstractCompiler compiler; |
- final CodegenOptions options; |
- final LibraryElement currentLibrary; |
- final StrongTypeSystemImpl rules; |
+ final AnalysisContext context; |
+ final CompilerOptions options; |
+ final rules = new StrongTypeSystemImpl(); |
+ |
+ /// The set of libraries we are currently compiling, and the temporaries used |
+ /// to refer to them. |
+ /// |
+ /// We sometimes special case codegen for a single library, as it simplifies |
+ /// name scoping requirements. |
+ final _libraries = new Map<LibraryElement, JS.Identifier>(); |
+ |
+ /// Imported libraries, and the temporaries used to refer to them. |
+ final _imports = new Map<LibraryElement, JS.TemporaryId>(); |
+ |
+ /// The list of output module items, in the order they need to be emitted in. |
+ final _moduleItems = <JS.ModuleItem>[]; |
/// 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; |
+ HashSet<FieldElement> _fieldsNeedingStorage; |
/// The variable for the target of the current `..` cascade expression. |
/// |
@@ -85,212 +85,324 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
/// 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 _privateNames = |
+ new HashMap<LibraryElement, HashMap<String, JS.TemporaryId>>(); |
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; |
+ final _hasDeferredSupertype = new HashSet<ClassElement>(); |
- ConstFieldVisitor _constField; |
+ /// The type provider from the current Analysis [context]. |
+ final TypeProvider types; |
- ModuleItemLoadOrder _loader; |
+ final LibraryElement dartCoreLibrary; |
+ final LibraryElement dartJSLibrary; |
- /// _interceptors.JSArray<E>, used for List literals. |
- ClassElement _jsArray; |
+ /// The dart:async `StreamIterator<>` type. |
+ final InterfaceType _asyncStreamIterator; |
+ |
+ /// The dart:_interceptors JSArray element. |
+ final ClassElement _jsArray; |
+ |
+ ConstFieldVisitor _constField; |
/// The current function body being compiled. |
FunctionBody _currentFunction; |
- /// The default value of the module object. See [visitLibraryDirective]. |
- String _jsModuleValue; |
+ /// Helper class for emitting elements in the proper order to allow |
+ /// JS to load the module. |
+ ElementLoader _loader; |
- bool _isDartRuntime; |
+ BuildUnit _buildUnit; |
- 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); |
+ CodeGenerator(AnalysisContext c, this.options, this._extensionTypes) |
+ : context = c, |
+ types = c.typeProvider, |
+ _asyncStreamIterator = |
+ _getLibrary(c, 'dart:async').getType('StreamIterator').type, |
+ _jsArray = _getLibrary(c, 'dart:_interceptors').getType('JSArray'), |
+ dartCoreLibrary = _getLibrary(c, 'dart:core'), |
+ dartJSLibrary = _getLibrary(c, 'dart:js'); |
- 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'; |
- } |
+ LibraryElement get currentLibrary => _loader.currentElement.library; |
- TypeProvider get types => _types; |
+ /// The main entry point to JavaScript code generation. |
+ /// |
+ /// Takes the metadata for the build unit, as well as resolved trees and |
+ /// errors, and computes the output module code and optionally the source map. |
+ JSModuleFile compile(BuildUnit unit, List<CompilationUnit> compilationUnits, |
+ List<String> errors) { |
+ _buildUnit = unit; |
- JS.Program emitLibrary(List<CompilationUnit> units) { |
- // Modify the AST to make coercions explicit. |
- units = new CoercionReifier().reify(units); |
+ var jsTree = _emitModule(compilationUnits); |
+ var codeAndSourceMap = _writeJSText(unit, jsTree); |
- units.last.directives.forEach(_visit); |
+ List<int> summary; |
+ if (options.summarizeApi) { |
+ var assembler = new PackageBundleAssembler(); |
+ compilationUnits |
+ .map((u) => u.element.library) |
+ .toSet() |
+ .forEach(assembler.serializeLibraryElement); |
+ summary = assembler.assemble().toBuffer(); |
+ } |
- // 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); |
+ return new JSModuleFile( |
+ unit.name, errors, codeAndSourceMap.e0, codeAndSourceMap.e1, summary); |
+ } |
- // 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); |
+ Tuple2<String, Map> _writeJSText(BuildUnit unit, JS.Program jsTree) { |
+ var opts = new JS.JavaScriptPrintingOptions( |
+ emitTypes: options.closure, |
+ allowKeywordsInProperties: true, |
+ allowSingleLineIfStatements: true); |
+ JS.SimpleJavaScriptPrintingContext printer; |
+ SourceMapBuilder sourceMap; |
+ if (options.sourceMap) { |
+ var sourceMapContext = new SourceMapPrintingContext(); |
+ sourceMap = sourceMapContext.sourceMap; |
+ printer = sourceMapContext; |
+ } else { |
+ printer = new JS.SimpleJavaScriptPrintingContext(); |
+ } |
- _constField = new ConstFieldVisitor(types, currentLibrary.source); |
+ jsTree.accept(new JS.Printer(opts, printer, |
+ localNamer: new JS.TemporaryNamer(jsTree))); |
- for (var unit in units) { |
- for (var decl in unit.declarations) { |
- if (decl is TopLevelVariableDeclaration) { |
- visitTopLevelVariableDeclaration(decl); |
- } else { |
- _loader.loadDeclaration(decl, decl.element); |
- } |
- } |
+ if (options.sourceMap && options.sourceMapComment) { |
+ printer.emit('\n//# sourceMappingURL=${unit.name}.js.map\n'); |
} |
- // Flush any unwritten fields/properties. |
- _flushLibraryProperties(_moduleItems); |
+ return new Tuple2(printer.getText(), sourceMap?.build(unit.name + '.js')); |
+ } |
- // 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)); |
+ JS.Program _emitModule(List<CompilationUnit> compilationUnits) { |
+ if (_moduleItems.isNotEmpty) { |
+ throw new StateError('Can only call emitModule once.'); |
} |
- var moduleBuilder = new ModuleBuilder(options.moduleFormat); |
+ _fieldsNeedingStorage = findFieldsNeedingStorage( |
+ compilationUnits.map((u) => u.element), _extensionTypes); |
- _exports.forEach(moduleBuilder.addExport); |
+ // Transform the AST to make coercions explicit. |
+ compilationUnits = CoercionReifier.reify(compilationUnits); |
- var currentModuleName = compiler.getModuleName(currentLibrary.source.uri); |
+ // Initialize our library variables. |
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); |
- } |
+ for (var unit in compilationUnits) { |
+ var library = unit.element.library; |
+ if (unit.element != library.definingCompilationUnit) continue; |
+ |
+ var libraryTemp = _isDartRuntime(library) |
+ ? _runtimeLibVar |
+ : new JS.TemporaryId(jsLibraryName(library)); |
+ _libraries[library] = libraryTemp; |
+ items.add(new JS.ExportDeclaration( |
+ js.call('const # = Object.create(null)', [libraryTemp]))); |
+ |
+ // dart:_runtime has a magic module that holds extenstion method symbols. |
+ // TODO(jmesserly): find a cleaner design for this. |
+ if (_isDartRuntime(library)) { |
+ items.add(new JS.ExportDeclaration( |
+ js.call('const # = Object.create(null)', [_dartxVar]))); |
+ } |
+ } |
+ |
+ // Collect all Element -> Node mappings, in case we need to forward declare |
+ // any nodes. |
+ var nodes = new HashMap<Element, AstNode>.identity(); |
+ compilationUnits.map(_collectElements).forEach(nodes.addAll); |
+ _loader = new ElementLoader(_emitModuleItem, nodes); |
+ |
+ // Add implicit dart:core dependency so it is first. |
+ emitLibraryName(dartCoreLibrary); |
+ |
+ // |
+ // Visit each compilation unit and emit its code. |
+ // |
+ // NOTE: declarations are not necessarily emitted in this order. |
+ // Order will be changed as needed so the resulting code can execute. |
+ // This is done by forward declaring items. |
+ compilationUnits.forEach(visitCompilationUnit); |
+ |
+ // Declare imports |
+ _finishImports(items); |
+ |
+ // Add the module's code (produced by visiting compilation units, above) |
+ _copyAndFlattenBlocks(items, _moduleItems); |
+ |
+ // Build the module. |
+ var module = new JS.Program(items, name: _buildUnit.name); |
+ |
+ // Optional: lower module format. Otherwise just return it. |
+ switch (options.moduleFormat) { |
+ case ModuleFormat.legacy: |
+ return new LegacyModuleBuilder().build(module); |
+ case ModuleFormat.node: |
+ return new NodeModuleBuilder().build(module); |
+ case ModuleFormat.es6: |
+ return module; |
+ } |
+ } |
+ |
+ /// Flattens blocks in [items] to a single list. |
+ /// |
+ /// This will not flatten blocks that are marked as being scopes. |
+ void _copyAndFlattenBlocks( |
+ List<JS.ModuleItem> result, Iterable<JS.ModuleItem> items) { |
+ for (var item in items) { |
+ if (item is JS.Block && !item.isScope) { |
+ _copyAndFlattenBlocks(result, item.statements); |
+ } else { |
+ result.add(item); |
} |
- moduleBuilder.addImport('dart/_runtime', _runtimeLibVar); |
+ } |
+ } |
- var dartxImport = |
- js.statement("let # = #.dartx;", [_dartxVar, _runtimeLibVar]); |
- items.add(dartxImport); |
+ String _libraryToModule(LibraryElement library) { |
+ assert(!_libraries.containsKey(library)); |
+ var source = library.source; |
+ // TODO(jmesserly): we need to split out HTML. |
+ if (source.uri.scheme == 'dart') { |
+ return 'dart_sdk'; |
+ } |
+ var moduleName = _buildUnit.libraryToModule(source); |
+ if (moduleName == null) { |
+ throw new StateError('Could not find module containing "$library".'); |
} |
- items.addAll(_moduleItems); |
+ return moduleName; |
+ } |
+ |
+ void _finishImports(List<JS.ModuleItem> items) { |
+ var modules = new Map<String, List<LibraryElement>>(); |
- _imports.forEach((LibraryElement lib, JS.TemporaryId temp) { |
- moduleBuilder.addImport(compiler.getModuleName(lib.source.uri), temp, |
- isLazy: _isDartRuntime || !_loader.libraryIsLoaded(lib)); |
+ for (var import in _imports.keys) { |
+ modules.putIfAbsent(_libraryToModule(import), () => []).add(import); |
+ } |
+ |
+ String coreModuleName; |
+ if (!_libraries.containsKey(dartCoreLibrary)) { |
+ coreModuleName = _libraryToModule(dartCoreLibrary); |
+ } |
+ modules.forEach((module, libraries) { |
+ // Generate import directives. |
+ // |
+ // Our import variables are temps and can get renamed. Since our renaming |
+ // is integrated into js_ast, it is aware of this possibility and will |
+ // generate an "as" if needed. For example: |
+ // |
+ // import {foo} from 'foo'; // if no rename needed |
+ // import {foo as foo$} from 'foo'; // if rename was needed |
+ // |
+ var imports = |
+ libraries.map((l) => new JS.NameSpecifier(_imports[l])).toList(); |
+ if (module == coreModuleName) { |
+ imports.add(new JS.NameSpecifier(_runtimeLibVar)); |
+ imports.add(new JS.NameSpecifier(_dartxVar)); |
+ } |
+ items.add(new JS.ImportDeclaration( |
+ namedImports: imports, from: js.string(module, "'"))); |
}); |
+ } |
- // 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); |
+ /// Collect toplevel elements and nodes we need to emit, and returns |
+ /// an ordered map of these. |
+ static Map<Element, AstNode> _collectElements(CompilationUnit unit) { |
+ var map = <Element, AstNode>{}; |
+ for (var declaration in unit.declarations) { |
+ if (declaration is TopLevelVariableDeclaration) { |
+ for (var field in declaration.variables.variables) { |
+ map[field.element] = field; |
+ } |
+ } else { |
+ map[declaration.element] = declaration; |
+ } |
+ } |
+ return map; |
} |
void _emitModuleItem(AstNode node) { |
- // Attempt to group adjacent properties. |
- if (node is! FunctionDeclaration) _flushLibraryProperties(_moduleItems); |
+ // 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. |
+ inferNullableTypes(node); |
var code = _visit(node); |
if (code != null) _moduleItems.add(code); |
} |
@override |
- void visitLibraryDirective(LibraryDirective node) { |
- assert(_jsModuleValue == null); |
+ void visitCompilationUnit(CompilationUnit unit) { |
+ _constField = new ConstFieldVisitor(types, unit.element.source); |
- var jsName = findAnnotation(node.element, isJSAnnotation); |
- _jsModuleValue = |
- getConstantField(jsName, 'name', types.stringType)?.toStringValue(); |
+ for (var declaration in unit.declarations) { |
+ var element = declaration.element; |
+ if (element != null) { |
+ _loader.emitDeclaration(element); |
+ } else { |
+ declaration.accept(this); |
+ } |
+ } |
+ for (var directive in unit.directives) { |
+ directive.accept(this); |
+ } |
} |
@override |
+ void visitLibraryDirective(LibraryDirective node) {} |
+ |
+ @override |
void visitImportDirective(ImportDirective node) { |
- // Nothing to do yet, but we'll want to convert this to an ES6 import once |
- // we have support for modules. |
+ // We don't handle imports here. |
+ // |
+ // Instead, we collect imports whenever we need to generate a reference |
+ // to another library. This has the effect of collecting the actually used |
+ // imports. |
+ // |
+ // TODO(jmesserly): if this is a prefixed import, consider adding the prefix |
+ // as an alias? |
} |
@override |
void visitPartDirective(PartDirective node) {} |
+ |
@override |
void visitPartOfDirective(PartOfDirective node) {} |
@override |
void visitExportDirective(ExportDirective node) { |
- var exportName = emitLibraryName(node.uriElement); |
+ ExportElement element = node.element; |
+ var currentLibrary = element.library; |
- var currentLibNames = currentLibrary.publicNamespace.definedNames; |
+ var currentNames = currentLibrary.publicNamespace.definedNames; |
+ var exportedNames = |
+ new NamespaceBuilder().createExportNamespaceForDirective(element); |
- 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)); |
+ // TODO(jmesserly): we could collect all of the names for bulk re-export, |
+ // but this is easier to implement for now. |
+ void emitExport(Element export, {String suffix: ''}) { |
+ var name = _emitTopLevelName(export, suffix: suffix); |
+ _moduleItems.add(js.statement( |
+ '#.# = #;', [emitLibraryName(currentLibrary), name.selector, name])); |
} |
- _moduleItems.add(js.statement('dart.export(#);', [args])); |
- } |
+ for (var export in exportedNames.definedNames.values) { |
+ // Don't allow redefining names from this library. |
+ if (currentNames.containsKey(export.name)) continue; |
- 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'; |
+ _loader.emitDeclaration(export); |
+ if (export is ClassElement && export.typeParameters.isNotEmpty) { |
+ // Export the generic name as well. |
+ // TODO(jmesserly): revisit generic classes |
+ emitExport(export, suffix: r'$'); |
+ } |
+ emitExport(export); |
+ } |
} |
- bool isPublic(String name) => !name.startsWith('_'); |
- |
@override |
visitAsExpression(AsExpression node) { |
var from = getStaticType(node.expression); |
@@ -304,7 +416,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// 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) { |
+ if (from != types.intType && to == types.intType) { |
return js.call('dart.asInt(#)', [fromExpr]); |
} |
@@ -337,27 +449,30 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
String _jsTypeofName(DartType t) { |
if (_isNumberInJS(t)) return 'number'; |
- if (t == _types.stringType) return 'string'; |
- if (t == _types.boolType) return 'boolean'; |
+ 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; |
+ FunctionTypeAliasElement element = node.element; |
- var fnType = annotate( |
- js.statement('const # = dart.typedef(#, () => #);', [ |
- name, |
- js.string(name, "'"), |
- _emitTypeName(type, lowerTypedef: true) |
+ JS.Expression body = annotate( |
+ js.call('dart.typedef(#, () => #)', [ |
+ js.string(element.name, "'"), |
+ _emitTypeName(element.type, lowerTypedef: true) |
]), |
node, |
- node.element); |
+ element); |
- return _finishClassDef(type, fnType); |
+ var typeFormals = element.typeParameters; |
+ if (typeFormals.isNotEmpty) { |
+ return _defineClassTypeArguments(element, typeFormals, |
+ js.statement('const # = #;', [element.name, body])); |
+ } else { |
+ return js.statement('# = #;', [_emitTopLevelName(element), body]); |
+ } |
} |
@override |
@@ -365,10 +480,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
@override |
JS.Statement visitClassTypeAlias(ClassTypeAlias node) { |
- var element = node.element; |
+ ClassElement element = node.element; |
// Forward all generative constructors from the base class. |
- var body = <JS.Method>[]; |
+ var methods = <JS.Method>[]; |
var supertype = element.supertype; |
if (!supertype.isObject) { |
@@ -376,36 +491,39 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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)); |
+ methods.add(new JS.Method(_constructorName(ctor), fun)); |
} |
} |
- var cls = _emitClassDeclaration(element, body); |
- return _finishClassDef(element.type, cls); |
+ var classExpr = _emitClassExpression(element, methods); |
+ |
+ var typeFormals = element.typeParameters; |
+ if (typeFormals.isNotEmpty) { |
+ return _defineClassTypeArguments( |
+ element, typeFormals, new JS.ClassDeclaration(classExpr)); |
+ } else { |
+ return js.statement('# = #;', [_emitTopLevelName(element), classExpr]); |
+ } |
} |
- JS.Statement _emitJsType(String dartClassName, DartObject jsName) { |
- var jsTypeName = |
- getConstantField(jsName, 'name', types.stringType)?.toStringValue(); |
+ JS.Statement _emitJsType(Element e) { |
+ var jsTypeName = getAnnotationName(e, isJSAnnotation); |
+ if (jsTypeName == null || jsTypeName == e.name) return null; |
- 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; |
+ // We export the JS type as if it was a Dart type. For example this allows |
+ // `dom.InputElement` to actually be HTMLInputElement. |
+ // TODO(jmesserly): if we had the JS name on the Element, we could just |
+ // generate it correctly when we refer to it. |
+ return js.statement('# = #;', [_emitTopLevelName(e), jsTypeName]); |
} |
@override |
JS.Statement visitClassDeclaration(ClassDeclaration node) { |
var classElem = node.element; |
- var type = classElem.type; |
- var jsName = findAnnotation(classElem, isJSAnnotation); |
- if (jsName != null) return _emitJsType(node.name.name, jsName); |
+ // If this is a JavaScript type, emit it now and then exit. |
+ var jsTypeDef = _emitJsType(classElem); |
+ if (jsTypeDef != null) return jsTypeDef; |
var ctors = <ConstructorDeclaration>[]; |
var fields = <FieldDeclaration>[]; |
@@ -422,48 +540,50 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
var allFields = new List.from(fields)..addAll(staticFields); |
- |
- var classDecl = _emitClassDeclaration( |
+ var classExpr = _emitClassExpression( |
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 = <JS.Statement>[]; |
+ var extensions = _extensionsToImplement(classElem); |
+ _initExtensionSymbols(classElem, methods, fields, body); |
- var body = _finishClassMembers(classElem, classDecl, ctors, fields, |
- staticFields, methods, node.metadata, jsPeerName); |
+ // Emit the class, e.g. `core.Object = class Object { ... }` |
+ JS.Expression className = _defineClass(classElem, classExpr, body); |
- var result = _finishClassDef(type, body); |
+ // Emit things that come after the ES6 `class ... { ... }`. |
+ _setBaseClass(classElem, className, body); |
+ _defineNamedConstructors(ctors, body, className); |
+ _emitVirtualFields(fields, className, body); |
+ _emitClassSignature(methods, classElem, ctors, extensions, className, body); |
+ _defineExtensionMembers(extensions, className, body); |
+ _emitClassMetadata(node.metadata, className, 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)); |
+ JS.Statement classDef = _statement(body); |
+ var typeFormals = classElem.typeParameters; |
+ if (typeFormals.isNotEmpty) { |
+ classDef = _defineClassTypeArguments(classElem, typeFormals, classDef); |
+ } |
- // 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]); |
+ body = <JS.Statement>[classDef]; |
+ _emitStaticFields(staticFields, classElem, body); |
+ _registerExtensionType(classElem, body); |
+ return _statement(body); |
+ } |
+ |
+ JS.Expression _defineClass(ClassElement classElem, |
+ JS.ClassExpression classExpr, List<JS.Statement> body) { |
+ JS.Expression className; |
+ if (classElem.typeParameters.isNotEmpty) { |
+ // Generic classes will be defined inside a function that closes over the |
+ // type parameter. So we can use their local variable name directly. |
+ className = classExpr.name; |
+ body.add(new JS.ClassDeclaration(classExpr)); |
+ } else { |
+ className = _emitTopLevelName(classElem); |
+ body.add(js.statement('# = #;', [className, classExpr])); |
} |
- return result; |
+ return className; |
} |
List<JS.Identifier> _emitTypeFormals(List<TypeParameterElement> typeFormals) { |
@@ -492,7 +612,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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 |
@@ -514,9 +633,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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)]; |
+ var classExpr = new JS.ClassExpression(new JS.Identifier(type.name), |
+ _emitClassHeritage(element), [constructor, toStringF]); |
+ var id = _emitTopLevelName(element); |
+ var result = [ |
+ js.statement('# = #', [id, classExpr]) |
+ ]; |
// Create static fields for each enum value |
for (var i = 0; i < fields.length; ++i) { |
@@ -530,58 +652,27 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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; |
- } |
+ /// Wraps a possibly generic class in its type arguments. |
+ JS.Statement _defineClassTypeArguments(TypeDefiningElement element, |
+ List<TypeParameterElement> formals, JS.Statement body) { |
+ assert(formals.isNotEmpty); |
+ var genericDef = |
+ js.statement('# = dart.generic((#) => { #; return #; });', [ |
+ _emitTopLevelName(element, suffix: r'$'), |
+ _emitTypeFormals(formals), |
+ body, |
+ element.name |
+ ]); |
- 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]); |
+ var dynType = fillDynamicTypeArgs(element.type); |
+ var genericInst = _emitTypeName(dynType, lowerGeneric: true); |
+ return js.statement( |
+ '{ #; # = #; }', [genericDef, _emitTopLevelName(element), genericInst]); |
} |
- final _hasDeferredSupertype = new HashSet<ClassElement>(); |
- |
bool _deferIfNeeded(DartType type, ClassElement current) { |
if (type is ParameterizedType) { |
var typeArguments = type.typeArguments; |
@@ -596,7 +687,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
return false; |
} |
- JS.Statement _emitClassDeclaration( |
+ JS.ClassExpression _emitClassExpression( |
ClassElement element, List<JS.Method> methods, |
{List<FieldDeclaration> fields}) { |
String name = element.name; |
@@ -604,29 +695,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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; |
+ return new JS.ClassExpression(new JS.Identifier(name), heritage, methods, |
+ typeParams: typeParams, fields: jsFields); |
} |
JS.Expression _emitClassHeritage(ClassElement element) { |
var type = element.type; |
if (type.isObject) return null; |
- // Assume we can load eagerly, until proven otherwise. |
_loader.startTopLevel(element); |
// Find the super type |
@@ -634,7 +710,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
var supertype = type.superclass; |
if (_deferIfNeeded(supertype, element)) { |
// Fall back to raw type. |
- supertype = fillDynamicTypeArgs(supertype.element.type, _types); |
+ supertype = fillDynamicTypeArgs(supertype.element.type); |
_hasDeferredSupertype.add(element); |
} |
heritage = _emitTypeName(supertype); |
@@ -646,6 +722,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
_loader.finishTopLevel(element); |
+ |
return heritage; |
} |
@@ -662,12 +739,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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(); |
- } |
+ var name = getAnnotationName(field, isJsName) ?? field.name; |
// Generate getter |
var fn = new JS.Fun([], js.statement('{ return this.#; }', [name])); |
var method = |
@@ -757,7 +829,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// Otherwise, emit the adapter method, which wraps the Dart iterator in |
// an ES6 iterator. |
return new JS.Method( |
- js.call('$_SYMBOL.iterator'), |
+ js.call('Symbol.iterator'), |
js.call('function() { return new dart.JsIterator(this.#); }', |
[_emitMemberName('iterator', type: t)]) as JS.Fun); |
} |
@@ -772,147 +844,110 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
} |
- /// 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)])); |
- } |
+ /// Gets the JS peer for this Dart type if any, otherwise null. |
+ /// |
+ /// For example for dart:_interceptors `JSArray` this will return "Array", |
+ /// referring to the JavaScript built-in `Array` type. |
+ String _getJSPeerName(ClassElement classElem) { |
+ var jsPeerName = getAnnotationName( |
+ classElem, |
+ (a) => |
+ isJsPeerInterface(a) || |
+ isNativeAnnotation(a) && _extensionTypes.contains(classElem)); |
+ if (jsPeerName != null && jsPeerName.contains(',')) { |
+ jsPeerName = jsPeerName.split(',')[0]; |
+ } |
+ return jsPeerName; |
+ } |
+ |
+ void _registerExtensionType(ClassElement classElem, List<JS.Statement> body) { |
+ var jsPeerName = _getJSPeerName(classElem); |
+ if (jsPeerName != null) { |
+ // 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. |
+ body.add(js.statement('dart.registerExtension(dart.global.#, #);', |
+ [_propertyName(jsPeerName), _emitTopLevelName(classElem)])); |
} |
+ } |
- body.add(classDecl); |
- |
- // TODO(jmesserly): we should really just extend native Array. |
+ void _setBaseClass(ClassElement classElem, JS.Expression className, |
+ List<JS.Statement> body) { |
+ String jsPeerName = _getJSPeerName(classElem); |
+ JS.Expression newBaseClass; |
if (jsPeerName != null && classElem.typeParameters.isNotEmpty) { |
- body.add(js.statement('dart.setBaseClass(#, dart.global.#);', |
- [classElem.name, _propertyName(jsPeerName)])); |
+ // TODO(jmesserly): we should really just extend Array in the first place. |
+ newBaseClass = js.call('dart.global.#', [jsPeerName]); |
+ } else if (_hasDeferredSupertype.contains(classElem)) { |
+ newBaseClass = _emitTypeName(classElem.type.superclass); |
} |
- |
- // Deferred Superclass |
- if (_hasDeferredSupertype.contains(classElem)) { |
- body.add(js.statement('#.prototype.__proto__ = #.prototype;', |
- [name, _emitTypeName(classElem.type.superclass)])); |
+ if (newBaseClass != null) { |
+ body.add( |
+ js.statement('dart.setBaseClass(#, #);', [className, newBaseClass])); |
} |
+ } |
- // Interfaces |
- if (classElem.interfaces.isNotEmpty) { |
- body.add(js.statement('#[dart.implements] = () => #;', [ |
- name, |
- new JS.ArrayInitializer(new List<JS.Expression>.from( |
- classElem.interfaces.map(_emitTypeName))) |
- ])); |
+ /// Emits instance fields, if they are virtual |
+ /// (in other words, they override a getter/setter pair). |
+ void _emitVirtualFields(List<FieldDeclaration> fields, |
+ JS.Expression className, List<JS.Statement> body) { |
+ for (FieldDeclaration member in fields) { |
+ for (VariableDeclaration field in member.fields.variables) { |
+ if (_fieldsNeedingStorage.contains(field.element)) { |
+ body.add(_overrideField(className, field.element)); |
+ } |
+ } |
} |
+ } |
- // Named constructors |
+ void _defineNamedConstructors(List<ConstructorDeclaration> ctors, |
+ List<JS.Statement> body, JS.Expression className) { |
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)])); |
+ [className, _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) { |
+ /// Emits static fields for a class, and initialize them eagerly if possible, |
+ /// otherwise define them as lazy properties. |
+ void _emitStaticFields(List<FieldDeclaration> staticFields, |
+ ClassElement classElem, List<JS.Statement> body) { |
+ var lazyStatics = <VariableDeclaration>[]; |
+ for (FieldDeclaration member in staticFields) { |
for (VariableDeclaration field in member.fields.variables) { |
- if (_fieldsNeedingStorage.contains(field.element)) { |
- body.add(_overrideField(field.element)); |
+ JS.Statement eagerField = _emitConstantStaticField(classElem, field); |
+ if (eagerField != null) { |
+ body.add(eagerField); |
+ } else { |
+ lazyStatics.add(field); |
} |
} |
} |
+ if (lazyStatics.isNotEmpty) { |
+ body.add(_emitLazyFields(classElem, lazyStatics)); |
+ } |
+ } |
- // 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])); |
- } |
+ void _emitClassMetadata(List<Annotation> metadata, JS.Expression className, |
+ List<JS.Statement> body) { |
+ // TODO(vsm): Make this optional per #268. |
+ // Metadata |
+ if (metadata.isNotEmpty) { |
+ body.add(js.statement('#[dart.metadata] = () => #;', [ |
+ className, |
+ new JS.ArrayInitializer( |
+ new List<JS.Expression>.from(metadata.map(_instantiateAnnotation))) |
+ ])); |
} |
+ } |
+ /// If a concrete class implements one of our extensions, we might need to |
+ /// add forwarders. |
+ void _defineExtensionMembers(List<ExecutableElement> extensions, |
+ JS.Expression className, List<JS.Statement> body) { |
// If a concrete class implements one of our extensions, we might need to |
// add forwarders. |
if (extensions.isNotEmpty) { |
@@ -921,39 +956,113 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
methodNames.add(_elementMemberName(e)); |
} |
body.add(js.statement('dart.defineExtensionMembers(#, #);', [ |
- name, |
+ className, |
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))) |
+ /// Emit the signature on the class recording the runtime type information |
+ void _emitClassSignature( |
+ List<MethodDeclaration> methods, |
+ ClassElement classElem, |
+ List<ConstructorDeclaration> ctors, |
+ List<ExecutableElement> extensions, |
+ JS.Expression className, |
+ List<JS.Statement> body) { |
+ if (classElem.interfaces.isNotEmpty) { |
+ body.add(js.statement('#[dart.implements] = () => #;', [ |
+ className, |
+ new JS.ArrayInitializer(new List<JS.Expression>.from( |
+ classElem.interfaces.map(_emitTypeName))) |
])); |
} |
- // 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); |
+ 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 { |
- lazyStatics.add(field); |
+ tMethods.add(property); |
} |
} |
} |
- if (lazyStatics.isNotEmpty) { |
- body.add(_emitLazyFields(classElem, lazyStatics)); |
+ |
+ 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); |
} |
- return _statement(body); |
+ 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); |
+ body.add(js.statement('dart.setSignature(#, #);', [className, sig])); |
+ } |
+ } |
+ |
+ /// Ensure `dartx.` symbols we will use are present. |
+ void _initExtensionSymbols( |
+ ClassElement classElem, |
+ List<MethodDeclaration> methods, |
+ List<FieldDeclaration> fields, |
+ List<JS.Statement> body) { |
+ 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)])); |
+ } |
+ } |
} |
List<ExecutableElement> _extensionsToImplement(ClassElement element) { |
@@ -998,10 +1107,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
_collectExtensions(type.superclass, types); |
} |
- JS.Statement _overrideField(FieldElement e) { |
+ JS.Statement _overrideField(JS.Expression className, FieldElement e) { |
var cls = e.enclosingElement; |
return js.statement('dart.virtualField(#, #)', |
- [cls.name, _emitMemberName(e.name, type: cls.type)]); |
+ [className, _emitMemberName(e.name, type: cls.type)]); |
} |
/// Generates the implicit default constructor for class C of the form |
@@ -1129,13 +1238,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
JS.Block _emitConstructorBody( |
ConstructorDeclaration node, List<FieldDeclaration> fields) { |
var body = <JS.Statement>[]; |
+ ClassDeclaration cls = node.parent; |
// Generate optional/named argument value assignment. These can not have |
// side effects, and may be used by the constructor's initializers, so it's |
// nice to do them first. |
// Also for const constructors we need to ensure default values are |
// available for use by top-level constant initializers. |
- 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); |
@@ -1191,7 +1300,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// 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); |
+ assert(options.unsafeForceCompile); |
return null; |
} |
} |
@@ -1370,12 +1479,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
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(); |
- } |
+ String name = |
+ getAnnotationName(node.element, isJSAnnotation) ?? node.name.name; |
if (node.isGetter) { |
return new JS.Fun(params, js.statement('{ return this.#; }', [name])); |
} else if (node.isSetter) { |
@@ -1445,15 +1550,23 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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; |
+ // If we have a getter/setter pair, they need to be defined together. |
+ PropertyAccessorElement element = node.element; |
+ var props = <JS.Method>[]; |
+ var getter = element.variable.getter; |
+ if (getter != null) { |
+ props.add(_loader.customEmitDeclaration(getter, _emitTopLevelProperty)); |
+ } |
+ var setter = element.variable.setter; |
+ if (setter != null) { |
+ props.add(_loader.customEmitDeclaration(setter, _emitTopLevelProperty)); |
+ } |
+ |
+ return js.statement('dart.copyProperties(#, { # });', |
+ [emitLibraryName(currentLibrary), props]); |
} |
var body = <JS.Statement>[]; |
- _flushLibraryProperties(body); |
- |
- var name = node.name.name; |
var fn = _emitFunction(node.functionExpression); |
if (currentLibrary.source.isInSystemLibrary && |
@@ -1461,16 +1574,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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) |
+ var element = node.element; |
+ var nameExpr = _emitTopLevelName(element); |
+ body.add(annotate(js.statement('# = #', [nameExpr, fn]), node, element)); |
+ if (!_isDartRuntime(element.library)) { |
+ body.add(_emitFunctionTagged(nameExpr, element.type, topLevel: true) |
.toStatement()); |
} |
- if (isPublic(name)) { |
- _addExport(name, getJSExportName(node.element, types) ?? name); |
- } |
return _statement(body); |
} |
@@ -1479,8 +1590,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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; |
+ var statements = body.block.statements; |
+ if (statements.length == 1) { |
+ var stat = statements[0]; |
if (stat is ReturnStatement) { |
return _isJSInvocation(stat.expression); |
} |
@@ -1556,10 +1668,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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)]); |
+ |
+ String code = lazy ? '() => dart.definiteFunctionType(#)' : '#'; |
+ return js.call('dart.fn(#, $code)', [fn, _emitFunctionTypeParts(type)]); |
} |
throw 'Function has non function type: $type'; |
} |
@@ -1736,8 +1847,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// type literal |
if (element is TypeDefiningElement) { |
- return _emitTypeName( |
- fillDynamicTypeArgs((element as dynamic).type, types)); |
+ return _emitTypeName(fillDynamicTypeArgs(element.type)); |
} |
// library member |
@@ -1758,7 +1868,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// library prefix. We don't need those because static calls can't use |
// the generic type. |
if (isStatic) { |
- var dynType = _emitTypeName(fillDynamicTypeArgs(type, types)); |
+ var dynType = _emitTypeName(fillDynamicTypeArgs(type)); |
return new JS.PropertyAccess(dynType, member); |
} |
@@ -1861,11 +1971,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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 |
@@ -1896,9 +2001,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
// 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); |
+ type is TypeParameterType && |
+ type.element.enclosingElement is! TypeDefiningElement; |
if (_isGenericTypeParameter(type)) { |
return js.call('dart.dynamic'); |
@@ -1910,16 +2014,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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))) { |
+ if (args.any((a) => !a.isDynamic && !_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. |
+ } else if (lowerGeneric) { |
jsArgs = []; |
} |
if (jsArgs != null) { |
@@ -1931,29 +2029,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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; |
+ JS.PropertyAccess _emitTopLevelName(Element e, {String suffix: ''}) { |
+ String name = getJSExportName(e) + suffix; |
+ return new JS.PropertyAccess( |
+ emitLibraryName(e.library), _propertyName(name)); |
} |
@override |
@@ -2022,7 +2101,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
if (target != null && DynamicInvoke.get(target)) { |
- return js.call('dart.$DPUT(#, #, #)', |
+ return js.call('dart.dput(#, #, #)', |
[_visit(target), _emitMemberName(id.name), _visit(rhs)]); |
} |
@@ -2087,7 +2166,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
String code; |
if (target == null || isLibraryPrefix(target)) { |
if (DynamicInvoke.get(node.methodName)) { |
- code = 'dart.$DCALL(#, #)'; |
+ code = 'dart.dcall(#, #)'; |
} else { |
code = '#(#)'; |
} |
@@ -2102,12 +2181,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
var memberName = _emitMemberName(name, type: type, isStatic: isStatic); |
if (DynamicInvoke.get(target)) { |
- code = 'dart.$DSEND(#, #, #)'; |
+ 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(#.#, #)'; |
+ code = 'dart.dcall(#.#, #)'; |
} else if (_requiresStaticDispatch(target, name)) { |
// Object methods require a helper for null checks. |
return js.call('dart.#(#, #)', |
@@ -2162,7 +2241,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
FunctionExpressionInvocation node) { |
var code; |
if (DynamicInvoke.get(node.function)) { |
- code = 'dart.$DCALL(#, #)'; |
+ code = 'dart.dcall(#, #)'; |
} else { |
code = '#(#)'; |
} |
@@ -2217,7 +2296,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
JS.Expression name; |
JS.SimpleBindingPattern structure = null; |
String paramName = param.identifier.name; |
- if (invalidVariableName(paramName)) { |
+ if (JS.invalidVariableName(paramName)) { |
name = js.string(paramName); |
structure = new JS.SimpleBindingPattern(_visit(param.identifier)); |
} else { |
@@ -2325,29 +2404,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
@override |
visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) { |
- for (var v in node.variables.variables) { |
- _loader.loadDeclaration(v, v.element); |
+ for (var variable in node.variables.variables) { |
+ _loader.emitDeclaration(variable.element); |
} |
} |
- /// Emits static fields. |
- /// |
- /// Instance fields are emitted in [_initializeFields]. |
- /// |
- /// These are generally treated the same as top-level fields, see |
- /// [visitTopLevelVariableDeclaration]. |
+ /// This is not used--we emit fields as we are emitting the class, |
+ /// see [visitClassDeclaration]. |
@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; |
+ assert(false); |
} |
@override |
@@ -2356,8 +2422,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// 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; |
+ var variables = node.variables.variables; |
+ if (variables.length == 1) { |
+ var v = variables[0]; |
if (v.initializer != null) { |
var name = new JS.Identifier(v.name.name); |
return _visit(v.initializer).toVariableDeclaration(name); |
@@ -2419,7 +2486,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
if (eagerInit && !JS.invalidStaticFieldName(fieldName)) { |
return annotate( |
js.statement('#.# = #;', [ |
- classElem.name, |
+ _emitTopLevelName(classElem), |
_emitMemberName(fieldName, isStatic: true), |
jsInit |
]), |
@@ -2434,7 +2501,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
/// Emits a top-level field. |
- JS.Statement _emitTopLevelField(VariableDeclaration field) { |
+ JS.ModuleItem _emitTopLevelField(VariableDeclaration field) { |
TopLevelVariableElement element = field.element; |
assert(element.isStatic); |
@@ -2455,44 +2522,17 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// Treat `final x = JS('', '...')` as a const (non-lazy) to help compile |
// runtime helpers. |
+ // TODO(jmesserly): we don't track dependencies correctly for these. |
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); |
+ if (eagerInit || isJSTopLevel) { |
+ return annotate( |
+ js.statement('# = #;', [_emitTopLevelName(element), jsInit]), |
+ field, |
+ element); |
} |
- return _emitLazyFields(currentLibrary, [field]); |
+ assert(element.library == currentLibrary); |
+ return _emitLazyFields(element.library, [field]); |
} |
JS.Expression _visitInitializer(VariableDeclaration node) { |
@@ -2530,13 +2570,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
JS.Expression objExpr; |
if (target is ClassElement) { |
- objExpr = new JS.Identifier(target.type.name); |
+ objExpr = _emitTopLevelName(target); |
} else { |
objExpr = emitLibraryName(target); |
} |
- return js |
- .statement('dart.defineLazyProperties(#, { # });', [objExpr, methods]); |
+ return js.statement('dart.defineLazy(#, { # });', [objExpr, methods]); |
} |
PropertyAccessorElement _findAccessor(VariableElement element, |
@@ -2550,13 +2589,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
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); |
@@ -2615,10 +2647,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
/// "extension methods". This allows types to be extended without adding |
/// extensions directly on the prototype. |
bool isPrimitiveType(DartType t) => |
- typeIsPrimitiveInJS(t) || t == _types.stringType; |
+ typeIsPrimitiveInJS(t) || t == types.stringType; |
bool typeIsPrimitiveInJS(DartType t) => |
- _isNumberInJS(t) || t == _types.boolType; |
+ _isNumberInJS(t) || t == types.boolType; |
bool binaryOperationIsPrimitive(DartType leftT, DartType rightT) => |
typeIsPrimitiveInJS(leftT) && typeIsPrimitiveInJS(rightT); |
@@ -2670,7 +2702,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
if (binaryOperationIsPrimitive(leftType, rightType) || |
- leftType == _types.stringType && op.type == TokenType.PLUS) { |
+ 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, |
@@ -3019,7 +3051,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
var name = _emitMemberName(memberId.name, |
type: getStaticType(target), isStatic: isStatic); |
if (DynamicInvoke.get(target)) { |
- return js.call('dart.$DLOAD(#, #)', [_visit(target), name]); |
+ return js.call('dart.dload(#, #)', [_visit(target), name]); |
} |
String code; |
@@ -3053,12 +3085,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
var memberName = _emitMemberName(name, unary: args.isEmpty, type: type); |
if (DynamicInvoke.get(target)) { |
// dynamic dispatch |
- var dynamicHelper = const {'[]': DINDEX, '[]=': DSETINDEX}[name]; |
+ var dynamicHelper = const {'[]': 'dindex', '[]=': 'dsetindex'}[name]; |
if (dynamicHelper != null) { |
return js.call( |
'dart.$dynamicHelper(#, #)', [_visit(target), _visitList(args)]); |
} |
- return js.call('dart.$DSEND(#, #, #)', |
+ return js.call('dart.dsend(#, #, #)', |
[_visit(target), memberName, _visitList(args)]); |
} |
@@ -3189,20 +3221,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
// |
// 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 streamIterator = rules.instantiateToBounds(_asyncStreamIterator); |
var createStreamIter = _emitInstanceCreationExpression( |
- _streamIteratorType.element.unnamedConstructor, |
- _streamIteratorType, |
+ streamIterator.element.unnamedConstructor, |
+ streamIterator, |
null, |
AstBuilder.argumentList([node.iterable]), |
false); |
- var iter = |
- _visit(_createTemporary('it', _streamIteratorType, nullable: false)); |
+ var iter = _visit(_createTemporary('it', streamIterator, nullable: false)); |
var init = _visit(node.identifier); |
if (init == null) { |
@@ -3500,11 +3526,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
_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 |
@@ -3574,8 +3595,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
if (isStatic) return _propertyName(name); |
if (name.startsWith('_')) { |
- return _privateNames.putIfAbsent( |
- name, () => _initSymbol(new JS.TemporaryId(name)) as JS.TemporaryId); |
+ return _emitPrivateNameSymbol(currentLibrary, name); |
} |
if (name == '[]') { |
@@ -3605,20 +3625,29 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
return _propertyName(name); |
} |
+ JS.TemporaryId _emitPrivateNameSymbol(LibraryElement library, String name) { |
+ return _privateNames |
+ .putIfAbsent(library, () => new HashMap()) |
+ .putIfAbsent(name, () { |
+ var id = new JS.TemporaryId(name); |
+ _moduleItems.add( |
+ js.statement('const # = Symbol(#);', [id, js.string(id.name, "'")])); |
+ return id; |
+ }); |
+ } |
+ |
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. |
+ /// Returns the canonical name to refer to the Dart library. |
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))); |
+ // It's either one of the libraries in this module, or it's an import. |
+ return _libraries[library] ?? |
+ _imports.putIfAbsent( |
+ library, () => new JS.TemporaryId(jsLibraryName(library))); |
} |
JS.Node annotate(JS.Node node, AstNode original, [Element element]) { |
@@ -3635,15 +3664,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
/// |
/// 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 _isNumberInJS(DartType t) => rules.isSubtypeOf(t, types.numType); |
bool _isObjectGetter(String name) { |
- PropertyAccessorElement element = _types.objectType.element.getGetter(name); |
+ PropertyAccessorElement element = types.objectType.element.getGetter(name); |
return (element != null && !element.isStatic); |
} |
bool _isObjectMethod(String name) { |
- MethodElement element = _types.objectType.element.getMethod(name); |
+ MethodElement element = types.objectType.element.getMethod(name); |
return (element != null && !element.isStatic); |
} |
@@ -3664,16 +3693,16 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
if (element.isAsynchronous) { |
if (element.isGenerator) { |
// Stream<T> -> T |
- expectedType = _types.streamType; |
+ expectedType = types.streamType; |
} else { |
// Future<T> -> T |
// TODO(vsm): Revisit with issue #228. |
- expectedType = _types.futureType; |
+ expectedType = types.futureType; |
} |
} else { |
if (element.isGenerator) { |
// Iterable<T> -> T |
- expectedType = _types.iterableType; |
+ expectedType = types.iterableType; |
} else { |
// T -> T |
return type; |
@@ -3691,129 +3720,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
} |
} |
-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); |
+String jsLibraryName(LibraryElement library) { |
+ return pathToJSIdentifier(library.source.uri.pathSegments.last); |
+} |
/// Shorthand for identifier-like property names. |
/// For now, we emit them as strings and the printer restores them to |
@@ -3835,3 +3747,12 @@ class TemporaryVariableElement extends LocalVariableElementImpl { |
int get hashCode => identityHashCode(this); |
bool operator ==(Object other) => identical(this, other); |
} |
+ |
+bool isLibraryPrefix(Expression node) => |
+ node is SimpleIdentifier && node.staticElement is PrefixElement; |
+ |
+LibraryElement _getLibrary(AnalysisContext c, String uri) => |
+ c.computeLibraryElement(c.sourceFactory.forUri(uri)); |
+ |
+bool _isDartRuntime(LibraryElement l) => |
+ l.isInSdk && l.source.uri.toString() == 'dart:_runtime'; |