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