| // Copyright (c) 2017, 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'; |
| import 'package:kernel/kernel.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:kernel/src/incremental_class_hierarchy.dart'; |
| import 'package:front_end/src/fasta/type_inference/type_schema_environment.dart'; |
| import 'package:path/path.dart' as path; |
| import '../compiler/js_metalet.dart' as JS; |
| import '../compiler/js_names.dart' as JS; |
| import '../compiler/js_utils.dart' as JS; |
| import '../compiler/module_builder.dart' show pathToJSIdentifier; |
| import '../js_ast/js_ast.dart' as JS; |
| import '../js_ast/js_ast.dart' show js; |
| import 'js_interop.dart'; |
| import 'js_typerep.dart'; |
| import 'kernel_helpers.dart'; |
| import 'native_types.dart'; |
| import 'type_table.dart'; |
| |
| class ProgramCompiler |
| implements |
| StatementVisitor<JS.Statement>, |
| ExpressionVisitor<JS.Expression>, |
| DartTypeVisitor<JS.Expression> { |
| /// The list of output module items, in the order they need to be emitted in. |
| final _moduleItems = <JS.ModuleItem>[]; |
| |
| /// 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<Library, JS.Identifier>.identity(); |
| |
| /// Imported libraries, and the temporaries used to refer to them. |
| final _imports = new Map<Library, JS.TemporaryId>(); |
| |
| JS.Identifier _extensionSymbolsModule; |
| final _extensionSymbols = new Map<String, JS.TemporaryId>(); |
| |
| JS.Identifier _runtimeModule; |
| final namedArgumentTemp = new JS.TemporaryId('opts'); |
| |
| Set<Class> _pendingClasses; |
| |
| /// The stack of currently emitting elements, if generating top-level code |
| /// for them. This is not used when inside method bodies, because order does |
| /// not matter for those. |
| Class _classEmittingTopLevel; |
| |
| /// The current element being loaded. |
| /// We can use this to determine if we're loading top-level code or not: |
| /// |
| /// _currentClass == _classEmittingTopLevel |
| /// |
| Class _currentClass; |
| |
| Library _currentLibrary; |
| |
| FunctionNode _currentFunction; |
| |
| List<TypeParameter> _typeParamInConst; |
| |
| /// Table of named and possibly hoisted types. |
| TypeTable _typeTable; |
| |
| /// The global extension type table. |
| // TODO(jmesserly): rename to `_nativeTypes` |
| final NativeTypeSet nativeTypes; |
| |
| final CoreTypes types; |
| |
| final TypeEnvironment rules; |
| |
| JSTypeRep _typeRep; |
| |
| ProgramCompiler(NativeTypeSet nativeTypes) |
| : nativeTypes = nativeTypes, |
| types = nativeTypes.types, |
| rules = new TypeSchemaEnvironment( |
| nativeTypes.types, new IncrementalClassHierarchy(), true); |
| |
| JS.Program emitProgram(Program p) { |
| if (_moduleItems.isNotEmpty) { |
| throw new StateError('Can only call emitModule once.'); |
| } |
| |
| var libraries = p.libraries.where((l) => !l.isExternal); |
| var ddcRuntime = |
| libraries.firstWhere(isSdkInternalRuntime, orElse: () => null); |
| if (ddcRuntime != null) { |
| // 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 library in libraries) { |
| var libraryTemp = library == ddcRuntime |
| ? _runtimeModule |
| : new JS.TemporaryId(jsLibraryName(library)); |
| _libraries[library] = libraryTemp; |
| items.add(new JS.ExportDeclaration( |
| js.call('const # = Object.create(null)', [libraryTemp]))); |
| |
| // dart:_runtime has a magic module that holds extension method symbols. |
| // TODO(jmesserly): find a cleaner design for this. |
| if (library == ddcRuntime) { |
| items.add(new JS.ExportDeclaration(js |
| .call('const # = Object.create(null)', [_extensionSymbolsModule]))); |
| } |
| } |
| |
| // Collect all class/type Element -> Node mappings |
| // in case we need to forward declare any classes. |
| _pendingClasses = new HashSet.identity(); |
| for (var l in libraries) { |
| _pendingClasses.addAll(l.classes); |
| } |
| |
| // Add implicit dart:core dependency so it is first. |
| emitLibraryName(types.coreLibrary); |
| |
| // Visit each library and emit its code. |
| // |
| // NOTE: clases 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. |
| libraries.forEach(_emitLibrary); |
| |
| // Visit directives (for exports) |
| libraries.forEach(_emitExports); |
| |
| // Declare imports |
| _finishImports(items); |
| // Initialize extension symbols |
| _extensionSymbols.forEach((name, id) { |
| var value = |
| new JS.PropertyAccess(_extensionSymbolsModule, _propertyName(name)); |
| if (ddcRuntime != null) { |
| value = js.call('# = Symbol(#)', [value, js.string("dartx.$name")]); |
| } |
| items.add(js.statement('const # = #;', [id, value])); |
| }); |
| |
| // 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: p.root.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 if (item != null) { |
| result.add(item); |
| } |
| } |
| } |
| |
| /// Returns the canonical name to refer to the Dart library. |
| JS.Identifier emitLibraryName(Library library) { |
| // It's either one of the libraries in this module, or it's an import. |
| return _libraries[library] ?? |
| _imports.putIfAbsent( |
| library, () => new JS.TemporaryId(jsLibraryName(library))); |
| } |
| |
| String _libraryToModule(Library library) { |
| assert(!_libraries.containsKey(library)); |
| if (library.importUri.scheme == 'dart') { |
| // TODO(jmesserly): we need to split out HTML. |
| return JS.dartSdkModule; |
| } |
| // TODO(jmesserly): to implement modular compilation, we need to know |
| // how libraries are grouped into modules. |
| var moduleName = path.basenameWithoutExtension(library.fileUri); |
| return moduleName; |
| } |
| |
| void _finishImports(List<JS.ModuleItem> items) { |
| var modules = new Map<String, List<Library>>(); |
| |
| for (var import in _imports.keys) { |
| modules.putIfAbsent(_libraryToModule(import), () => []).add(import); |
| } |
| |
| String coreModuleName; |
| if (!_libraries.containsKey(types.coreLibrary)) { |
| coreModuleName = _libraryToModule(types.coreLibrary); |
| } |
| 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, "'"))); |
| }); |
| } |
| |
| void _emitLibrary(Library library) { |
| // NOTE: this method isn't the right place to initialize per-library state. |
| // Classes can be visited out of order, so this is only to catch things that |
| // haven't been emitted yet. |
| // |
| // See _emitClass. |
| assert(_currentLibrary == null); |
| _currentLibrary = library; |
| |
| // `dart:_runtime` uses a different order for bootstrapping. |
| bool bootstrap = isSdkInternalRuntime(library); |
| if (bootstrap) _emitLibraryProcedures(library); |
| |
| library.classes.forEach(_emitClass); |
| _moduleItems.addAll(library.typedefs.map(_emitTypedef)); |
| if (bootstrap) { |
| _emitInternalSdkFields(library.fields); |
| } else { |
| _emitLibraryProcedures(library); |
| var fields = library.fields; |
| if (fields.isNotEmpty) _moduleItems.add(_emitLazyFields(fields)); |
| } |
| |
| _currentLibrary = null; |
| } |
| |
| void _emitExports(Library library) { |
| assert(_currentLibrary == null); |
| _currentLibrary = library; |
| |
| library.additionalExports.forEach(_emitExport); |
| |
| _currentLibrary = null; |
| } |
| |
| void _emitExport(Reference export) { |
| var library = _currentLibrary; |
| |
| // We only need to export main as it is the only method part 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 node = export.node; |
| if (node is Procedure && node.name.name == 'main') { |
| // Don't allow redefining names from this library. |
| var name = _emitTopLevelName(export.node); |
| _moduleItems.add(js.statement( |
| '#.# = #;', [emitLibraryName(library), name.selector, name])); |
| } |
| } |
| |
| /// Called to emit class 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 _emitClass(Class c) { |
| if (!_pendingClasses.remove(c)) return; |
| |
| var savedClass = _currentClass; |
| var savedLibrary = _currentLibrary; |
| _currentClass = c; |
| _currentLibrary = c.enclosingLibrary; |
| |
| _moduleItems.add(_emitClassDeclaration(c)); |
| |
| _currentClass = savedClass; |
| _currentLibrary = savedLibrary; |
| } |
| |
| /// To emit top-level classes, we sometimes need to reorder them. |
| /// |
| /// This function takes care of that, and also detects cases where reordering |
| /// failed, and we need to resort to lazy loading, by marking the element as |
| /// lazy. All elements need to be aware of this possibility and generate code |
| /// accordingly. |
| /// |
| /// If we are not emitting top-level code, this does nothing, because all |
| /// declarations are assumed to be available before we start execution. |
| /// See [startTopLevel]. |
| void _declareBeforeUse(Class c) { |
| if (c == null) return; |
| if (identical(_currentClass, _classEmittingTopLevel)) _emitClass(c); |
| } |
| |
| JS.Statement _emitClassDeclaration(Class c) { |
| // TODO(jmesserly): move this to another class, to encapsulate how |
| // we emit classes? Classes and class members introduce a lot of complexity. |
| throw new UnimplementedError(); |
| } |
| |
| JS.Statement _emitTypedef(Typedef t) { |
| throw new UnimplementedError(); |
| } |
| |
| /// Treat dart:_runtime fields as safe to eagerly evaluate. |
| // TODO(jmesserly): it'd be nice to avoid this special case. |
| JS.Statement _emitInternalSdkFields(Iterable<Field> fields) { |
| throw new UnimplementedError(); |
| } |
| |
| JS.Statement _emitLazyFields(Iterable<Field> fields) { |
| throw new UnimplementedError(); |
| } |
| |
| JS.PropertyAccess _emitTopLevelName(NamedNode n, {String suffix: ''}) { |
| return _emitJSInterop(n) ?? _emitTopLevelNameNoInterop(n, suffix: suffix); |
| } |
| |
| JS.PropertyAccess _emitTopLevelNameNoInterop(NamedNode n, |
| {String suffix: ''}) { |
| var name = getJSExportName(n) ?? getTopLevelName(n); |
| return new JS.PropertyAccess( |
| emitLibraryName(getLibrary(n)), _propertyName(name + suffix)); |
| } |
| |
| String _getJSNameWithoutGlobal(NamedNode n) { |
| if (!isJSReference(n)) return null; |
| var libraryJSName = getAnnotationName(getLibrary(n), isPublicJSAnnotation); |
| var jsName = |
| getAnnotationName(n, isPublicJSAnnotation) ?? getTopLevelName(n); |
| return libraryJSName != null ? '$libraryJSName.$jsName' : jsName; |
| } |
| |
| JS.Expression _emitJSInterop(NamedNode n) { |
| var jsName = _getJSNameWithoutGlobal(n); |
| if (jsName == null) return null; |
| return _emitJSInteropForGlobal(jsName); |
| } |
| |
| JS.Expression _emitJSInteropForGlobal(String name) { |
| var access = callHelper('global'); |
| for (var part in name.split('.')) { |
| access = new JS.PropertyAccess(access, js.escapedString(part, "'")); |
| } |
| return access; |
| } |
| |
| void _emitLibraryProcedures(Library library) { |
| var procedures = |
| library.procedures.where((p) => !p.isExternal && !p.isAbstract); |
| _moduleItems.addAll( |
| procedures.where((p) => !p.isAccessor).map(_emitLibraryFunction)); |
| _moduleItems |
| .add(_emitLibraryAccessors(procedures.where((p) => p.isAccessor))); |
| } |
| |
| JS.Statement _emitLibraryAccessors(Iterable<Procedure> accessors) { |
| return callHelperStatement('copyProperties(#, { # });', [ |
| emitLibraryName(_currentLibrary), |
| accessors.map(_emitLibraryAccessor).toList() |
| ]); |
| } |
| |
| JS.Method _emitLibraryAccessor(Procedure accessor) { |
| throw new UnimplementedError(); |
| } |
| |
| JS.Statement _emitLibraryFunction(Procedure p) { |
| var body = <JS.Statement>[]; |
| var fn = _emitFunction(p.function); |
| |
| if (_currentLibrary.importUri.scheme == 'dart' && |
| _isInlineJSFunction(p.function.body)) { |
| fn = JS.simplifyPassThroughArrowFunCallBody(fn); |
| } |
| |
| var nameExpr = _emitTopLevelName(p); |
| body.add(js.statement('# = #', [nameExpr, fn])); |
| if (!isSdkInternalRuntime(_currentLibrary)) { |
| body.add( |
| _emitFunctionTagged(nameExpr, p.function.functionType, topLevel: true) |
| .toStatement()); |
| } |
| |
| return JS.Statement.from(body); |
| } |
| |
| JS.Expression _emitFunctionTagged(JS.Expression fn, FunctionType type, |
| {topLevel: false}) { |
| var lazy = topLevel && !_typeIsLoaded(type); |
| var typeRep = visitFunctionType(type); |
| if (lazy) { |
| return callHelper('lazyFn(#, () => #)', [fn, typeRep]); |
| } else { |
| return callHelper('fn(#, #)', [fn, typeRep]); |
| } |
| } |
| |
| bool _typeIsLoaded(DartType type) { |
| if (type is InterfaceType) { |
| return !_pendingClasses.contains(type.classNode) && |
| type.typeArguments.every(_typeIsLoaded); |
| } |
| if (type is FunctionType) { |
| return (_typeIsLoaded(type.returnType) && |
| type.positionalParameters.every(_typeIsLoaded) && |
| type.namedParameters.every((n) => _typeIsLoaded(n.type))); |
| } |
| if (type is TypedefType) { |
| return type.typeArguments.every(_typeIsLoaded); |
| } |
| return true; |
| } |
| |
| /// Emits a Dart [type] into code. |
| JS.Expression _emitType(DartType type) => type.accept(this); |
| |
| @override |
| defaultDartType(type) => throw new UnimplementedError(); |
| |
| @override |
| visitInvalidType(type) => defaultDartType(type); |
| |
| @override |
| visitDynamicType(type) => callHelper('dynamic'); |
| |
| @override |
| visitVoidType(type) => callHelper('void'); |
| |
| @override |
| visitBottomType(type) => callHelper('bottom'); |
| |
| @override |
| visitInterfaceType(type, {bool lowerGeneric: false}) { |
| var c = type.classNode; |
| _declareBeforeUse(c); |
| |
| // Type parameters don't matter as JS interop types cannot be reified. |
| // We have to use lazy JS types because until we have proper module |
| // loading for JS libraries bundled with Dart libraries, we will sometimes |
| // need to load Dart libraries before the corresponding JS libraries are |
| // actually loaded. |
| // Given a JS type such as: |
| // @JS('google.maps.Location') |
| // class Location { ... } |
| // We can't emit a reference to MyType because the JS library that defines |
| // it may be loaded after our code. So for now, we use a special lazy type |
| // object to represent MyType. |
| // Anonymous JS types do not have a corresponding concrete JS type so we |
| // have to use a helper to define them. |
| if (isJSAnonymousType(c)) { |
| return callHelper('anonymousJSType(#)', js.escapedString(c.name)); |
| } |
| var jsName = _getJSNameWithoutGlobal(c); |
| if (jsName != null) { |
| return callHelper('lazyJSType(() => #, #)', |
| [_emitJSInteropForGlobal(jsName), js.escapedString(jsName)]); |
| } |
| |
| var args = type.typeArguments; |
| Iterable jsArgs = null; |
| if (args.any((a) => a != const DynamicType())) { |
| jsArgs = args.map(_emitType); |
| } else if (lowerGeneric) { |
| jsArgs = []; |
| } |
| if (jsArgs != null) { |
| var genericName = _emitTopLevelNameNoInterop(c, suffix: '\$'); |
| var typeRep = js.call('#(#)', [genericName, jsArgs]); |
| return _typeTable.nameType(type, typeRep); |
| } |
| |
| return _emitTopLevelNameNoInterop(c); |
| } |
| |
| @override |
| visitVectorType(type) => defaultDartType(type); |
| |
| @override |
| visitFunctionType(type, {bool lowerTypedef: false}) { |
| var parameterTypes = |
| type.positionalParameters.take(type.requiredParameterCount); |
| var optionalTypes = |
| type.positionalParameters.skip(type.requiredParameterCount); |
| var namedTypes = type.namedParameters; |
| var rt = _emitType(type.returnType); |
| |
| var ra = _emitTypeNames(parameterTypes); |
| |
| List<JS.Expression> typeParts; |
| if (namedTypes.isNotEmpty) { |
| assert(optionalTypes.isEmpty); |
| // TODO(vsm): Pass in annotations here as well. |
| var na = _emitTypeProperties(namedTypes); |
| typeParts = [rt, ra, na]; |
| } else if (optionalTypes.isNotEmpty) { |
| assert(namedTypes.isEmpty); |
| var oa = _emitTypeNames(optionalTypes); |
| typeParts = [rt, ra, oa]; |
| } else { |
| typeParts = [rt, ra]; |
| } |
| |
| JS.Expression fullType; |
| var typeFormals = type.typeParameters; |
| String helperCall; |
| if (typeFormals.isNotEmpty) { |
| var tf = _emitTypeFormals(typeFormals); |
| |
| addTypeFormalsAsParameters(List<JS.Expression> elements) { |
| var names = _typeTable.discharge(typeFormals); |
| var array = new JS.ArrayInitializer(elements); |
| return names.isEmpty |
| ? js.call('(#) => #', [tf, array]) |
| : js.call('(#) => {#; return #;}', [tf, names, array]); |
| } |
| |
| typeParts = [addTypeFormalsAsParameters(typeParts)]; |
| |
| helperCall = 'gFnType(#)'; |
| // If any explicit bounds were passed, emit them. |
| if (typeFormals.any((t) => t.bound != null)) { |
| var bounds = typeFormals.map((t) => _emitType(t.bound)).toList(); |
| typeParts.add(addTypeFormalsAsParameters(bounds)); |
| } |
| } else { |
| helperCall = 'fnType(#)'; |
| } |
| fullType = callHelper(helperCall, [typeParts]); |
| return _typeTable.nameType(type, fullType); |
| } |
| |
| JS.ObjectInitializer _emitTypeProperties(Iterable<NamedType> types) { |
| return new JS.ObjectInitializer(types |
| .map((t) => new JS.Property(_propertyName(t.name), _emitType(t.type))) |
| .toList()); |
| } |
| |
| JS.ArrayInitializer _emitTypeNames(Iterable<DartType> types) { |
| return new JS.ArrayInitializer(types.map(_emitType).toList()); |
| } |
| |
| @override |
| visitTypeParameterType(type) { |
| _typeParamInConst?.add(type.parameter); |
| return new JS.Identifier(type.parameter.name); |
| } |
| |
| @override |
| visitTypedefType(type, {bool lowerGeneric: false}) { |
| var args = type.typeArguments; |
| Iterable jsArgs = null; |
| if (args.any((a) => a != const DynamicType())) { |
| jsArgs = args.map(_emitType); |
| } else if (lowerGeneric) { |
| jsArgs = []; |
| } |
| if (jsArgs != null) { |
| var genericName = |
| _emitTopLevelNameNoInterop(type.typedefNode, suffix: '\$'); |
| var typeRep = js.call('#(#)', [genericName, jsArgs]); |
| return _typeTable.nameType(type, typeRep); |
| } |
| |
| return _emitTopLevelNameNoInterop(type.typedefNode); |
| } |
| |
| JS.Fun _emitFunction(FunctionNode f, [Procedure method]) { |
| // normal function (sync), vs (sync*, async, async*) |
| var isSync = f.asyncMarker == AsyncMarker.Sync; |
| var formals = _emitParameters(f); |
| var typeFormals = _emitTypeFormals(f.typeParameters); |
| formals.insertAll(0, typeFormals); |
| |
| JS.Block code = isSync |
| ? _emitFunctionBody(f) |
| : new JS.Block([_emitGeneratorFunction(f).toReturn()]); |
| |
| if (method != null && formals.isNotEmpty) { |
| var name = method.name.name; |
| if (name == '[]=') { |
| // []= methods need to return the value. We could also address this at |
| // call sites, but it's cleaner to instead transform the operator method. |
| code = JS.alwaysReturnLastParameter(code, formals.last); |
| } else if (name == '==' && |
| method.enclosingLibrary.importUri.scheme != 'dart') { |
| // In Dart `operator ==` methods are not called with a null argument. |
| // This is handled before calling them. For performance reasons, we push |
| // this check inside the method, to simplify our `equals` helper. |
| // |
| // TODO(jmesserly): in most cases this check is not necessary, because |
| // the Dart code already handles it (typically by an `is` check). |
| // Eliminate it when possible. |
| code = new JS.Block([ |
| js.statement('if (# == null) return false;', [formals.first]), |
| code |
| ]); |
| } |
| } |
| |
| return new JS.Fun(formals, code); |
| } |
| |
| List<JS.Parameter> _emitParameters(FunctionNode f) { |
| var result = |
| f.positionalParameters.map((p) => new JS.Identifier(p.name)).toList(); |
| if (f.namedParameters.isNotEmpty) { |
| result.add(namedArgumentTemp); |
| } |
| return result; |
| } |
| |
| List<JS.Parameter> _emitTypeFormals(List<TypeParameter> typeFormals) { |
| return typeFormals |
| .map((t) => new JS.Identifier(t.name)) |
| .toList(growable: false); |
| } |
| |
| JS.Expression _emitGeneratorFunction(FunctionNode f) { |
| throw new UnimplementedError(); |
| } |
| |
| JS.Block _emitFunctionBody(FunctionNode f) { |
| var savedFunction = _currentFunction; |
| _currentFunction = f; |
| |
| var initArgs = _emitArgumentInitializers(f); |
| var block = _visitStatement(f.body); |
| |
| if (initArgs != null) { |
| block = new JS.Block([initArgs, block]); |
| } else if (block is! JS.Block) { |
| // Kernel function bodies are statements, not blocks. |
| block = new JS.Block([block]); |
| } |
| |
| var body = f.body; |
| if (body is Block) { |
| var params = new Set<String>() |
| ..addAll(f.positionalParameters.map((p) => p.name)) |
| ..addAll(f.namedParameters.map((p) => p.name)); |
| bool shadowsParam = body.statements |
| .any((s) => s is VariableDeclaration && params.contains(s.name)); |
| ; |
| |
| if (shadowsParam) { |
| block = new JS.Block([ |
| new JS.Block([block], isScope: true) |
| ]); |
| } |
| } |
| |
| _currentFunction = savedFunction; |
| |
| return block; |
| } |
| |
| /// Emits argument initializers, which handles optional/named args, as well |
| /// as generic type checks needed due to our covariance. |
| JS.Statement _emitArgumentInitializers(FunctionNode f) { |
| if (f.positionalParameters.isEmpty && f.namedParameters.isEmpty) |
| return null; |
| |
| var body = <JS.Statement>[]; |
| |
| _emitCovarianceBoundsCheck(f.typeParameters, body); |
| |
| initParameter(VariableDeclaration p, JS.Identifier jsParam) { |
| if (p.isCovariant || p.isGenericCovariantImpl) { |
| var castType = _emitType(p.type); |
| body.add(js.statement('#._check(#);', [castType, jsParam])); |
| } |
| if (_annotatedNullCheck(p)) { |
| body.add(_nullParameterCheck(jsParam)); |
| } |
| } |
| |
| for (var p in f.positionalParameters.take(f.requiredParameterCount)) { |
| var jsParam = new JS.Identifier(p.name); |
| initParameter(p, jsParam); |
| } |
| for (var p in f.positionalParameters.skip(f.requiredParameterCount)) { |
| var jsParam = new JS.Identifier(p.name); |
| var defaultValue = _defaultParamValue(p); |
| if (defaultValue != null) { |
| body.add(js.statement( |
| 'if (# === void 0) # = #;', [jsParam, jsParam, defaultValue])); |
| } |
| initParameter(p, jsParam); |
| } |
| for (var p in f.namedParameters) { |
| // Parameters will be passed using their real names, not the (possibly |
| // renamed) local variable. |
| var jsParam = new JS.Identifier(p.name); |
| var paramName = js.string(p.name, "'"); |
| var defaultValue = _defaultParamValue(p); |
| if (defaultValue != null) { |
| // TODO(ochafik): Fix `'prop' in obj` to please Closure's renaming. |
| body.add(js.statement('let # = # && # in # ? #.# : #;', [ |
| jsParam, |
| namedArgumentTemp, |
| paramName, |
| namedArgumentTemp, |
| namedArgumentTemp, |
| paramName, |
| defaultValue, |
| ])); |
| } else { |
| body.add(js.statement('let # = # && #.#;', [ |
| jsParam, |
| namedArgumentTemp, |
| namedArgumentTemp, |
| paramName, |
| ])); |
| } |
| initParameter(p, jsParam); |
| } |
| return body.isEmpty ? null : JS.Statement.from(body); |
| } |
| |
| // TODO(jmesserly): fix this. Figure out where kernel stores these. |
| bool _annotatedNullCheck(VariableDeclaration d) => false; |
| |
| JS.Statement _nullParameterCheck(JS.Expression param) { |
| var call = callHelper('argumentError((#))', [param]); |
| return js.statement('if (# == null) #;', [param, call]); |
| } |
| |
| JS.Expression _defaultParamValue(VariableDeclaration p) { |
| if (p.initializer != null) { |
| var value = p.initializer; |
| return _isJSUndefined(value) ? null : _visitExpression(value); |
| } else { |
| return new JS.LiteralNull(); |
| } |
| } |
| |
| bool _isJSUndefined(Expression expr) { |
| expr = expr is AsExpression ? expr.operand : expr; |
| if (expr is StaticGet) { |
| var t = expr.target; |
| return isSdkInternalRuntime(getLibrary(t)) && t.name.name == 'undefined'; |
| } |
| return false; |
| } |
| |
| void _emitCovarianceBoundsCheck( |
| List<TypeParameter> typeFormals, List<JS.Statement> body) { |
| for (var t in typeFormals) { |
| if (t.isGenericCovariantImpl) { |
| body.add(callHelperStatement('checkTypeBound(#, #, #)', [ |
| _emitType(new TypeParameterType(t)), |
| _emitType(t.bound), |
| _propertyName(t.name) |
| ])); |
| } |
| } |
| } |
| |
| JS.Expression callHelper(String code, [args]) { |
| if (args is List) { |
| args.insert(0, _runtimeModule); |
| } else if (args != null) { |
| args = [_runtimeModule, args]; |
| } else { |
| args = _runtimeModule; |
| } |
| return js.call('#.$code', args); |
| } |
| |
| JS.Statement callHelperStatement(String code, args) { |
| if (args is List) { |
| args.insert(0, _runtimeModule); |
| } else { |
| args = [_runtimeModule, args]; |
| } |
| return js.statement('#.$code', args); |
| } |
| |
| JS.Statement _visitStatement(Statement s) { |
| // TODO(jmesserly): attach source mapping to statements |
| return s.accept(this); |
| } |
| |
| JS.Expression _visitExpression(Expression e) { |
| return e.accept(this); |
| } |
| |
| JS.Expression visitAndMarkExpression(Expression e) { |
| // TODO(jmesserly): attach source mapping to expressions if needed |
| return e.accept(this); |
| } |
| |
| @override |
| defaultStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitInvalidStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitExpressionStatement(node) => |
| _visitExpression(node.expression).toStatement(); |
| |
| @override |
| visitBlock(node) => |
| new JS.Block(node.statements.map(_visitStatement).toList(), |
| isScope: true); |
| |
| @override |
| visitEmptyStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitAssertStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitLabeledStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitBreakStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitWhileStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitDoStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitForStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitForInStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitSwitchStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitContinueSwitchStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitIfStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| JS.Statement visitReturnStatement(ReturnStatement node) { |
| var e = node.expression; |
| if (e == null) return new JS.Return(); |
| return _visitExpression(e).toReturn(); |
| } |
| |
| @override |
| visitTryCatch(node) => throw new UnimplementedError(); |
| |
| @override |
| visitTryFinally(node) => throw new UnimplementedError(); |
| |
| @override |
| visitYieldStatement(node) => throw new UnimplementedError(); |
| |
| @override |
| visitVariableDeclaration(node) => throw new UnimplementedError(); |
| |
| @override |
| visitFunctionDeclaration(node) => throw new UnimplementedError(); |
| |
| @override |
| defaultExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| defaultBasicLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitInvalidExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitVariableGet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitVariableSet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitPropertyGet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitPropertySet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitDirectPropertyGet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitDirectPropertySet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitSuperPropertyGet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitSuperPropertySet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitStaticGet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitStaticSet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitMethodInvocation(node) => throw new UnimplementedError(); |
| |
| @override |
| visitDirectMethodInvocation(node) => throw new UnimplementedError(); |
| |
| @override |
| visitSuperMethodInvocation(node) => throw new UnimplementedError(); |
| |
| @override |
| visitStaticInvocation(node) { |
| var result = _emitForeignJS(node); |
| if (result != null) return result; |
| return _emitFunctionCall(node); |
| } |
| |
| /// Emits a function call, to a top-level function, local function, or |
| /// an expression. |
| JS.Expression _emitFunctionCall(StaticInvocation node) { |
| if (_isCoreIdentical(node.target)) { |
| return _emitCoreIdenticalCall(node); |
| } |
| var fn = _emitTopLevelName(node.target); |
| var args = _emitArgumentList(node.arguments); |
| return new JS.Call(fn, args); |
| } |
| |
| List<JS.Expression> _emitArgumentList(Arguments node) { |
| var args = <JS.Expression>[]; |
| for (var typeArg in node.types) { |
| args.add(_emitType(typeArg)); |
| } |
| for (var arg in node.positional) { |
| if (arg is StaticInvocation && |
| isJSSpreadInvocation(arg.target) && |
| arg.arguments.positional.length == 1) { |
| args.add(new JS.RestParameter( |
| _visitExpression(arg.arguments.positional[0]))); |
| } else { |
| args.add(_visitExpression(arg)); |
| } |
| } |
| var named = <JS.Property>[]; |
| for (var arg in node.named) { |
| named.add(new JS.Property( |
| _propertyName(arg.name), _visitExpression(arg.value))); |
| } |
| if (named.isNotEmpty) { |
| args.add(new JS.ObjectInitializer(named)); |
| } |
| return args; |
| } |
| |
| /// Emits code for the `JS(...)` macro. |
| JS.Expression _emitForeignJS(StaticInvocation node) { |
| if (isInlineJS(node.target)) { |
| throw new UnimplementedError(); |
| } |
| return null; |
| } |
| |
| bool _isNull(Expression expr) => |
| expr is NullLiteral || |
| expr.getStaticType(rules) == types.nullClass.rawType; |
| |
| bool _doubleEqIsIdentity(Expression left, Expression right) { |
| // If we statically know LHS or RHS is null we can use ==. |
| if (_isNull(left) || _isNull(right)) return true; |
| // If the representation of the two types will not induce conversion in |
| // JS then we can use == . |
| return !_typeRep.equalityMayConvert( |
| left.getStaticType(rules), right.getStaticType(rules)); |
| } |
| |
| bool _tripleEqIsIdentity(Expression left, Expression right) { |
| // If either is non-nullable, then we don't need to worry about |
| // equating null and undefined, and so we can use triple equals. |
| return !isNullable(left) || !isNullable(right); |
| } |
| |
| /// Returns true if [expr] can be null, optionally using [localIsNullable] |
| /// for locals. |
| /// |
| /// If [localIsNullable] is not supplied, this will use the known list of |
| /// [_notNullLocals]. |
| bool isNullable(Expression expr) { |
| // TODO(jmesserly): we do recursive calls in a few places. This could |
| // leads to O(depth) cost for calling this function. We could store the |
| // resulting value if that becomes an issue, so we maintain the invariant |
| // that each node is visited once. |
| if (expr is PropertyGet) { |
| var target = expr.interfaceTarget; |
| // tear-offs are not null, other accessors are |
| return target is Procedure && target.isAccessor; |
| } |
| if (expr is StaticGet) { |
| var target = expr.target; |
| // tear-offs are not null, other accessors are |
| return target is Procedure && target.isAccessor; |
| } |
| |
| if (expr is TypeLiteral) return false; |
| if (expr is BasicLiteral) return expr.value != null; |
| if (expr is IsExpression) return false; |
| if (expr is FunctionExpression) return false; |
| if (expr is ThisExpression) return false; |
| if (expr is ConditionalExpression) { |
| return isNullable(expr.then) || isNullable(expr.otherwise); |
| } |
| if (expr is ConstructorInvocation) return false; |
| if (expr is LogicalExpression) return false; |
| if (expr is Not) return false; |
| if (expr is StaticInvocation) { |
| return !_isCoreIdentical(expr.target); |
| } |
| if (expr is DirectMethodInvocation) { |
| // TODO(jmesserly): this is to capture that our primitive classes |
| // (int, double, num, bool, String) do not return null from their |
| // operator methods. |
| return !isPrimitiveType(expr.target.enclosingClass.rawType); |
| } |
| // TODO(jmesserly): handle other cases. |
| return true; |
| } |
| |
| bool isPrimitiveType(DartType t) => _typeRep.isPrimitive(t); |
| |
| bool _isCoreIdentical(Procedure node) { |
| return node.name.name == 'identical' && |
| node.enclosingLibrary == types.coreLibrary; |
| } |
| |
| JS.Expression _emitJSDoubleEq(List<JS.Expression> args, |
| {bool negated = false}) { |
| var op = negated ? '# != #' : '# == #'; |
| return js.call(op, args); |
| } |
| |
| JS.Expression _emitJSTripleEq(List<JS.Expression> args, |
| {bool negated = false}) { |
| var op = negated ? '# !== #' : '# === #'; |
| return js.call(op, args); |
| } |
| |
| JS.Expression _emitCoreIdenticalCall(StaticInvocation node, |
| {bool negated = false}) { |
| var args = node.arguments.positional; |
| if (args.length != 2 || node.arguments.named.isNotEmpty) { |
| // Shouldn't happen in typechecked code |
| return callHelper( |
| 'throw(Error("compile error: calls to `identical` require 2 args")'); |
| } |
| var left = args[0]; |
| var right = args[1]; |
| var jsArgs = [_visitExpression(left), _visitExpression(right)]; |
| if (_tripleEqIsIdentity(left, right)) { |
| return _emitJSTripleEq(jsArgs, negated: negated); |
| } |
| if (_doubleEqIsIdentity(left, right)) { |
| return _emitJSDoubleEq(jsArgs, negated: negated); |
| } |
| var code = negated ? '!#' : '#'; |
| return js.call(code, new JS.Call(_emitTopLevelName(node.target), jsArgs)); |
| } |
| |
| @override |
| visitConstructorInvocation(node) => throw new UnimplementedError(); |
| |
| @override |
| visitNot(node) => throw new UnimplementedError(); |
| |
| @override |
| visitLogicalExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitConditionalExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitStringConcatenation(node) => throw new UnimplementedError(); |
| |
| @override |
| visitIsExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitAsExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitSymbolLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitTypeLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitThisExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitRethrow(node) => throw new UnimplementedError(); |
| |
| @override |
| visitThrow(node) => throw new UnimplementedError(); |
| |
| @override |
| visitListLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitMapLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitAwaitExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitFunctionExpression(node) => throw new UnimplementedError(); |
| |
| @override |
| visitStringLiteral(node) => js.escapedString(node.value, '"'); |
| |
| @override |
| visitIntLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitDoubleLiteral(node) => throw new UnimplementedError(); |
| @override |
| visitBoolLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitNullLiteral(node) => throw new UnimplementedError(); |
| |
| @override |
| visitLet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitLoadLibrary(node) => throw new UnimplementedError(); |
| |
| @override |
| visitCheckLibraryIsLoaded(node) => throw new UnimplementedError(); |
| |
| @override |
| visitVectorCreation(node) => throw new UnimplementedError(); |
| |
| @override |
| visitVectorGet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitVectorSet(node) => throw new UnimplementedError(); |
| |
| @override |
| visitVectorCopy(node) => throw new UnimplementedError(); |
| |
| @override |
| visitClosureCreation(node) => throw new UnimplementedError(); |
| } |
| |
| bool isSdkInternalRuntime(Library l) => |
| l.importUri.toString() == 'dart:_runtime'; |
| |
| /// Choose a canonical name from the [library] element. |
| /// |
| /// This never uses the library's name (the identifier in the `library` |
| /// declaration) as it doesn't have any meaningful rules enforced. |
| String jsLibraryName(Library library) { |
| var uri = library.importUri; |
| if (uri.scheme == 'dart') return uri.path; |
| |
| // TODO(vsm): This is not necessarily unique if '__' appears in a file name. |
| Iterable<String> segments; |
| if (uri.scheme == 'package') { |
| // Strip the package name. |
| // TODO(vsm): This is not unique if an escaped '/'appears in a filename. |
| // E.g., "foo/bar.dart" and "foo__bar.dart" would collide. |
| segments = uri.pathSegments.skip(1); |
| } else { |
| segments = path.split(path.relative(uri.toFilePath())); |
| } |
| |
| var qualifiedPath = segments.map((p) => p == '..' ? '' : p).join('__'); |
| return pathToJSIdentifier(qualifiedPath); |
| } |
| |
| /// Shorthand for identifier-like property names. |
| /// For now, we emit them as strings and the printer restores them to |
| /// identifiers if it can. |
| // TODO(jmesserly): avoid the round tripping through quoted form. |
| JS.LiteralString _propertyName(String name) => js.string(name, "'"); |
| |
| bool _isInlineJSFunction(Statement body) { |
| var block = body; |
| if (block is Block) { |
| var statements = block.statements; |
| if (statements.length != 1) return false; |
| body = statements[0]; |
| } |
| if (body is ReturnStatement) { |
| var e = body.expression; |
| return e is MethodInvocation && isInlineJS(e.interfaceTarget); |
| } |
| return false; |
| } |
| |
| bool isInlineJS(Member e) => |
| e is Procedure && |
| e.name == 'JS' && |
| e.enclosingLibrary.importUri.toString() == 'dart:_foreign_helper'; |