Chromium Code Reviews| 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..da03a5b13acadd657d6e86ab3716c6da1dc5d15a 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,319 @@ 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); |
| + } |
| + } |
| + |
| + String _libraryToModule(LibraryElement library) { |
| + assert(!_libraries.containsKey(library)); |
| + var moduleName = _buildUnit.libraryToModule(library.source); |
| + if (moduleName == null) { |
| + throw new StateError('Could not find module containing "$library".'); |
| + } |
| + return moduleName; |
| + } |
| + |
| + void _finishImports(List<JS.ModuleItem> items) { |
| + var modules = new Map<String, List<LibraryElement>>(); |
| - var dartxImport = |
| - js.statement("let # = #.dartx;", [_dartxVar, _runtimeLibVar]); |
| - items.add(dartxImport); |
| + for (var import in _imports.keys) { |
| + modules.putIfAbsent(_libraryToModule(import), () => []).add(import); |
| } |
| - items.addAll(_moduleItems); |
| - _imports.forEach((LibraryElement lib, JS.TemporaryId temp) { |
| - moduleBuilder.addImport(compiler.getModuleName(lib.source.uri), temp, |
| - isLazy: _isDartRuntime || !_loader.libraryIsLoaded(lib)); |
| + 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); |
| - |
| - var currentLibNames = currentLibrary.publicNamespace.definedNames; |
| + ExportElement element = node.element; |
| + var currentLibrary = element.library; |
| - var args = [_exportsVar, exportName]; |
| - if (node.combinators.isNotEmpty) { |
| - var shownNames = <JS.Expression>[]; |
| - var hiddenNames = <JS.Expression>[]; |
| + var currentNames = currentLibrary.publicNamespace.definedNames; |
| + var exportedNames = |
| + new NamespaceBuilder().createExportNamespaceForDirective(element); |
| - 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])); |
| - } |
| - |
| - JS.Identifier _initSymbol(JS.Identifier id) { |
| - var s = |
| - js.statement('const # = $_SYMBOL(#);', [id, js.string(id.name, "'")]); |
| - _moduleItems.add(s); |
| - return id; |
| - } |
| + for (var export in exportedNames.definedNames.values) { |
| + // Don't allow redefining names from this library. |
| + if (currentNames.containsKey(export.name)) continue; |
| - // 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 +411,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 +444,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 +475,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 +486,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 +535,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 +607,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 +628,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 +647,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 +682,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 +690,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 +705,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 +717,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
| } |
| _loader.finishTopLevel(element); |
| + |
| return heritage; |
| } |
| @@ -662,12 +734,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 +824,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 +839,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 pper for this Dart type, if any, otherwise null. |
|
vsm
2016/04/14 00:18:24
pper -> peer
Jennifer Messerly
2016/04/14 18:28:40
Done.
|
| + /// |
| + /// 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 +951,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 +1102,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 +1233,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 +1295,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 +1474,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 +1545,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 +1569,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 +1585,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 +1663,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 +1842,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 +1863,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 +1966,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 +1996,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 +2009,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 +2024,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 +2096,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 +2161,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 +2176,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 +2236,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor |
| FunctionExpressionInvocation node) { |
| var code; |
| if (DynamicInvoke.get(node.function)) { |
| - code = 'dart.$DCALL(#, #)'; |
| + code = 'dart.dcall(#, #)'; |
| } else { |
| code = '#(#)'; |
| } |
| @@ -2217,7 +2291,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 +2399,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 +2417,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 +2481,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 +2496,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 +2517,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 +2565,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 +2584,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 +2642,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 +2697,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 +3046,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 +3080,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 +3216,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 +3521,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 +3590,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 +3620,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 +3659,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 +3688,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 +3715,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 +3742,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'; |