Chromium Code Reviews| Index: lib/src/codegen/js_codegen.dart |
| diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
| index 1ca3993e1a8c9303368e813f868f5389a17da4ea..e2bb50047502b2e78f5abe54d1862de01e92e131 100644 |
| --- a/lib/src/codegen/js_codegen.dart |
| +++ b/lib/src/codegen/js_codegen.dart |
| @@ -38,7 +38,6 @@ import 'js_module_item_order.dart'; |
| import 'js_names.dart'; |
| import 'js_printer.dart' show writeJsLibrary; |
| import 'side_effect_analysis.dart'; |
| -import 'package:collection/equality.dart'; |
| // Various dynamic helpers we call. |
| // If renaming these, make sure to check other places like the |
| @@ -52,8 +51,6 @@ const DSETINDEX = 'dsetindex'; |
| const DCALL = 'dcall'; |
| const DSEND = 'dsend'; |
| -const ListEquality _listEquality = const ListEquality(); |
| - |
| class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| final AbstractCompiler compiler; |
| final CodegenOptions options; |
| @@ -94,6 +91,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| final _dartxVar = new JS.Identifier('dartx'); |
| final _exportsVar = new JS.TemporaryId('exports'); |
| final _runtimeLibVar = new JS.Identifier('dart'); |
| + final _utilsLibVar = new JS.TemporaryId('utils'); |
| + final _classesLibVar = new JS.TemporaryId('classes'); |
| + final _rttiLibVar = new JS.TemporaryId('rtti'); |
| final _namedArgTemp = new JS.TemporaryId('opts'); |
| final TypeProvider _types; |
| @@ -108,7 +108,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| /// The default value of the module object. See [visitLibraryDirective]. |
| String _jsModuleValue; |
| - bool _isDartUtils; |
| + bool _isDartRuntime; |
| Map<String, DartType> _objectMembers; |
| @@ -123,10 +123,24 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| var src = context.sourceFactory.forUri('dart:_interceptors'); |
| var interceptors = context.computeLibraryElement(src); |
| _jsArray = interceptors.getType('JSArray'); |
| - _isDartUtils = currentLibrary.source.uri.toString() == 'dart:_utils'; |
| + |
| + _isDartRuntime = _runtimeLibUris.contains(_getLibUri(currentLibrary)); |
| _objectMembers = getObjectMemberMap(types); |
| } |
| + String _getLibUri(LibraryElement lib) => lib.source.uri.toString(); |
| + |
| + static final _runtimeLibUris = new Set<String>.from([ |
|
Jennifer Messerly
2016/01/07 20:18:04
ideally we wouldn't need to hard code these, or ge
ochafik
2016/01/13 13:13:20
Dropped this but kept _isDartRuntime (testing on d
|
| + 'dart:_utils', |
| + 'dart:_runtime', |
| + 'dart:_operations', |
| + 'dart:_errors', |
| + 'dart:_classes', |
| + 'dart:_generators', |
| + 'dart:_operations', |
| + 'dart:_types', |
| + 'dart:_rtti' |
| + ]); |
| TypeProvider get types => rules.provider; |
| @@ -196,39 +210,59 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| // TODO(jmesserly): it would be great to run the renamer on the body, |
| // then figure out if we really need each of these parameters. |
| // See ES6 modules: https://github.com/dart-lang/dev_compiler/issues/34 |
| - var params = [_exportsVar, _runtimeLibVar]; |
| - var processImport = |
| - (LibraryElement library, JS.TemporaryId temp, List list) { |
| - params.add(temp); |
| - list.add(js.string(compiler.getModuleName(library.source.uri), "'")); |
| - }; |
| + var params = [_exportsVar]; |
| + var lazyParams = []; |
| - var needsDartRuntime = !_isDartUtils; |
| + var libUri = _getLibUri(currentLibrary); |
| var imports = <JS.Expression>[]; |
| + var lazyImports = <JS.Expression>[]; |
| var moduleStatements = <JS.Statement>[]; |
| - if (needsDartRuntime) { |
| - imports.add(js.string('dart/_runtime')); |
| + |
| + addImport(String name, JS.Expression libVar, {bool eager: true}) { |
| + (eager ? imports : lazyImports).add(js.string(name, "'")); |
| + (eager ? params : lazyParams).add(libVar); |
| + } |
| + |
| + if (!_isDartRuntime) { |
| + addImport('dart/_runtime', _runtimeLibVar); |
|
Jennifer Messerly
2016/01/07 20:18:04
can we just import this library explicitly?
/
ochafik
2016/01/13 13:13:20
That's mainly for the "mere mortal" files
|
| var dartxImport = |
| js.statement("let # = #.dartx;", [_dartxVar, _runtimeLibVar]); |
| moduleStatements.add(dartxImport); |
| + } else { |
| + if (libUri == 'dart:_generators') { |
| + addImport('dart/_classes', _classesLibVar); |
|
Jennifer Messerly
2016/01/07 20:18:04
same here, can we import this explicitly?
ochafik
2016/01/13 13:13:20
Dropped now that all runtime is in dart:_runtime
|
| + } |
| } |
| moduleStatements.addAll(_moduleItems); |
| - _imports.forEach((library, temp) { |
| - if (_loader.libraryIsLoaded(library)) { |
| - processImport(library, temp, imports); |
| - } |
| - }); |
| + bool shouldImportEagerly(lib) { |
| + var otherLibUri = _getLibUri(lib); |
| - var lazyImports = <JS.Expression>[]; |
| - _imports.forEach((library, temp) { |
| - if (!_loader.libraryIsLoaded(library)) { |
| - processImport(library, temp, lazyImports); |
| - } |
| + // utils.dart contains very low-level utils and doesn't import anyone. |
| + if (otherLibUri == 'dart:_utils') return true; |
|
Jennifer Messerly
2016/01/07 20:18:05
can we just add these cases to the existing coreli
ochafik
2016/01/13 13:13:20
Ack
|
| + // Types in types.dart extend rtti.LazyTagged in a way that isn't |
| + // recognized by the loader: we help it out here. |
|
Jennifer Messerly
2016/01/07 20:18:04
can you explain the problem with this? I'm not fol
ochafik
2016/01/13 13:13:20
Dropped special logic
|
| + if (libUri == 'dart:_types' && otherLibUri == 'dart:_rtti') return true; |
| + if (libUri == 'dart:_runtime') return otherLibUri != 'dart:_js_helper'; |
| + // runtime.dart needs to import everything but utils.dart lazily. |
| + if (_isDartRuntime) return false; |
| + |
| + return _loader.libraryIsLoaded(lib); |
| + } |
| + |
| + var importsEagerness = new Map<LibraryElement, bool>.fromIterable( |
| + _imports.keys, |
| + value: shouldImportEagerly); |
| + |
| + _imports.forEach((LibraryElement lib, JS.TemporaryId temp) { |
| + bool eager = importsEagerness[lib]; |
| + addImport(compiler.getModuleName(lib.source.uri), temp, eager: eager); |
| }); |
| + params.addAll(lazyParams); |
| + |
| var module = |
| js.call("function(#) { 'use strict'; #; }", [params, moduleStatements]); |
| @@ -264,9 +298,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| void visitLibraryDirective(LibraryDirective node) { |
| assert(_jsModuleValue == null); |
| - var jsName = findAnnotation(node.element, isJSAnnotation); |
| - _jsModuleValue = |
| - getConstantField(jsName, 'name', types.stringType)?.toStringValue(); |
| + _jsModuleValue = _getJsName(node.element); |
| + } |
| + |
| + String _getJsName(Element e) { |
|
Jennifer Messerly
2016/01/07 20:18:04
I'm a little confused about this function. It coul
ochafik
2016/01/13 13:13:20
Spun this out in https://codereview.chromium.org/1
|
| + var jsName = findAnnotation(e, isJSAnnotation); |
| + return getConstantField(jsName, 'name', types.stringType)?.toStringValue(); |
| } |
| @override |
| @@ -294,8 +331,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| var hide = node.combinators.firstWhere((c) => c is HideCombinator, |
| orElse: () => null) as HideCombinator; |
| if (show != null) { |
| + // If the import has a single shown name + a JsName(jsName) annotation, |
| + // then we use that jsName as the exported name. |
| + var singleJsName = _getJsName(node.element); |
| + if (singleJsName != null && show.shownNames.length != 1) { |
| + throw new StateError('Cannot set js name on more than one export'); |
|
Jennifer Messerly
2016/01/07 20:18:04
Can this error be triggered by user code?
If so,
ochafik
2016/01/13 13:13:20
Dropped that (no need to re-export anymore with th
|
| + } |
| shownNames.addAll(show.shownNames |
| - .map((i) => i.name) |
| + .map((i) => singleJsName ?? i.name) |
| .where((s) => !currentLibNames.containsKey(s)) |
| .map((s) => js.string(s, "'"))); |
| } |
| @@ -305,7 +348,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| args.add(new JS.ArrayInitializer(shownNames)); |
| args.add(new JS.ArrayInitializer(hiddenNames)); |
| } |
| - _moduleItems.add(js.statement('dart.export_(#);', [args])); |
| + |
| + // When we compile _runtime.js, we need to source export_ from _utils.js: |
| + _moduleItems.add(js.statement('#(#);', [_dartExport, args])); |
| } |
| JS.Identifier _initSymbol(JS.Identifier id) { |
| @@ -579,10 +624,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| var genericName = '$name\$'; |
| var typeParams = type.typeParameters.map((p) => p.name); |
| if (isPublic(name)) _exports.add(genericName); |
| - return js.statement('const # = dart.generic(function(#) { #; return #; });', |
| - [genericName, typeParams, body, name]); |
| + |
| + return js.statement('const # = #(function(#) { #; return #; });', |
| + [genericName, _dartGeneric, typeParams, body, name]); |
| } |
| + get _dartGeneric => |
| + js.call('#.generic', [_isDartRuntime ? _classesLibVar : _runtimeLibVar]); |
|
Jennifer Messerly
2016/01/07 20:18:05
I made this comment elsewhere, but I don't think w
ochafik
2016/01/13 13:13:20
Parts FTW!
|
| + |
| + get _dartExport => |
| + js.call('#.export', [_isDartRuntime ? _utilsLibVar : _runtimeLibVar]); |
| + |
| + get _dartFn => js.call('#.fn', _isDartRuntime ? _rttiLibVar : _runtimeLibVar); |
| + |
| + get _dartSetSignature => js.call( |
| + '#.setSignature', [_isDartRuntime ? _classesLibVar : _runtimeLibVar]); |
| + |
| final _hasDeferredSupertype = new HashSet<ClassElement>(); |
| bool _deferIfNeeded(DartType type, ClassElement current) { |
| @@ -832,7 +889,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| if (!sigFields.isEmpty || extensions.isNotEmpty) { |
| var sig = new JS.ObjectInitializer(sigFields); |
| var classExpr = new JS.Identifier(name); |
| - body.add(js.statement('dart.setSignature(#, #);', [classExpr, sig])); |
| + body.add(js.statement('#(#, #);', [_dartSetSignature, classExpr, sig])); |
| } |
| } |
| @@ -1305,15 +1362,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| var body = <JS.Statement>[]; |
| _flushLibraryProperties(body); |
| - var name = node.name.name; |
| + var name = _getJsName(node.element) ?? node.name.name; |
| var fn = _visit(node.functionExpression); |
| - bool needsTagging = true; |
| + // Don't tag functions defined in the low-level runtime. |
| + bool needsTagging = !_isDartRuntime; |
| if (currentLibrary.source.isInSystemLibrary && |
| _isInlineJSFunction(node.functionExpression)) { |
| fn = _simplifyPassThroughArrowFunCallBody(fn); |
| - needsTagging = !_isDartUtils; |
| } |
| var id = new JS.Identifier(name); |
| @@ -1345,23 +1402,17 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| bool _isJSInvocation(Expression expr) => |
| expr is MethodInvocation && isInlineJS(expr.methodName.staticElement); |
| - // Simplify `(args) => ((x, y) => { ... })(x, y)` to `(args) => { ... }`. |
| - // Note: we don't check if the top-level args match the ones passed through |
| - // the arrow function, which allows silently passing args through to the |
| - // body (which only works if we don't do weird renamings of Dart params). |
| + // Simplify `(args) => (() => { ... })()` to `(args) => { ... }`. |
| + // Note: this allows silently passing args through to the body, which only |
|
Jennifer Messerly
2016/01/07 20:18:05
I don't think you need the "Note" anymore?
ochafik
2016/01/13 13:13:20
Done.
|
| + // works if we don't do weird renamings of Dart params. |
| JS.Fun _simplifyPassThroughArrowFunCallBody(JS.Fun fn) { |
| - String getIdent(JS.Node node) => node is JS.Identifier ? node.name : null; |
| - List<String> getIdents(List params) => |
| - params.map(getIdent).toList(growable: false); |
| - |
| 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) { |
| - var passedArgs = getIdents(call.arguments); |
| + if (call.target is JS.ArrowFun && call.arguments.isEmpty) { |
| JS.ArrowFun innerFun = call.target; |
| - if (_listEquality.equals(getIdents(innerFun.params), passedArgs)) { |
| + if (innerFun.params.isEmpty) { |
| return new JS.Fun(fn.params, innerFun.body); |
| } |
| } |
| @@ -1409,12 +1460,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| type.optionalParameterTypes.isEmpty && |
| type.namedParameterTypes.isEmpty && |
| type.normalParameterTypes.every((t) => t.isDynamic)) { |
| - return js.call('dart.fn(#)', [clos]); |
| + return js.call('#(#)', [_dartFn, clos]); |
| } |
| if (lazy) { |
| - return js.call('dart.fn(#, () => #)', [clos, _emitFunctionRTTI(type)]); |
| + return js.call( |
| + '#(#, () => #)', [_dartFn, clos, _emitFunctionRTTI(type)]); |
| } |
| - return js.call('dart.fn(#, #)', [clos, _emitFunctionTypeParts(type)]); |
| + return js.call('#(#, #)', [_dartFn, clos, _emitFunctionTypeParts(type)]); |
| } |
| throw 'Function has non function type: $type'; |
| } |
| @@ -1569,7 +1621,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| _loader.declareBeforeUse(element); |
| - var name = element.name; |
| + var name = _getJsName(element) ?? element.name; |
|
Jennifer Messerly
2016/01/07 20:18:04
This is related to my other comment on _getJsName,
ochafik
2016/01/13 13:13:20
Moved the exported name resolution logic to down t
|
| // type literal |
| if (element is ClassElement || |
| @@ -1581,7 +1633,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| // library member |
| if (element.enclosingElement is CompilationUnitElement) { |
| - return _maybeQualifiedName(element); |
| + return _maybeQualifiedName(element, name); |
| } |
| // Unqualified class member. This could mean implicit-this, or implicit |
| @@ -1884,11 +1936,32 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| new JS.Block(_visitList(node.statements) as List<JS.Statement>, |
| isScope: true); |
| + /// Return the type constructor `_foolib.Bar$` given `Bar` from lib `_foolib`. |
| + /// |
| + /// This implements / expands the `genericTypeConstructor` intrinsic defined |
| + /// in `foreign_helper.dart`. |
| + JS.Expression _emitGenericTypeConstructor(Expression typeExpression) { |
|
Jennifer Messerly
2016/01/07 20:18:04
I don't understand why this is needed. Given a typ
ochafik
2016/01/13 13:13:20
This implements the new intrinsic used in the foll
|
| + var ref = _visit(typeExpression); |
| + if (ref is JS.PropertyAccess) { |
| + var name = (ref.selector as JS.LiteralString).valueWithoutQuotes; |
| + return new JS.PropertyAccess( |
| + ref.receiver, new JS.LiteralString("'$name\$'")); |
| + } else if (ref is JS.MaybeQualifiedId) { |
| + var name = (ref.name as JS.Identifier).name; |
| + return new JS.PropertyAccess(ref.qualifier, new JS.Identifier('$name\$')); |
| + } else { |
| + throw new ArgumentError('Invalid type ref: $ref (${ref?.runtimeType})'); |
| + } |
| + } |
| + |
| @override |
| visitMethodInvocation(MethodInvocation node) { |
| if (node.operator != null && node.operator.lexeme == '?.') { |
| return _emitNullSafe(node); |
| } |
| + if (isGenericTypeConstructorInvocation(node)) { |
| + return _emitGenericTypeConstructor(node.argumentList.arguments.single); |
|
Jennifer Messerly
2016/01/07 20:18:04
I don't understand why this case is needed. It see
ochafik
2016/01/13 13:13:20
Sorry for the lacking doc, this is a new intrinsic
|
| + } |
| var target = _getTarget(node); |
| var result = _emitForeignJS(node); |
| @@ -2214,7 +2287,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| var isJSTopLevel = field.isFinal && _isFinalJSDecl(field); |
| if (isJSTopLevel) eagerInit = true; |
| - var fieldName = field.name.name; |
| + var fieldName = _getJsName(element) ?? field.name.name; |
| + |
| if ((field.isConst && eagerInit && element is TopLevelVariableElement) || |
| isJSTopLevel) { |
| // constant fields don't change, so we can generate them as `let` |
| @@ -2850,7 +2924,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| } else if (_requiresStaticDispatch(target, memberId.name)) { |
| var type = member.type; |
| var clos = js.call('dart.#.bind(#)', [name, _visit(target)]); |
| - return js.call('dart.fn(#, #)', [clos, _emitFunctionTypeParts(type)]); |
| + return js.call( |
| + '#(#, #)', [_dartFn, clos, _emitFunctionTypeParts(type)]); |
| } |
| code = 'dart.bind(#, #)'; |
| } else if (_requiresStaticDispatch(target, memberId.name)) { |
| @@ -3419,8 +3494,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ClosureAnnotator { |
| /// declaration) as it doesn't have any meaningful rules enforced. |
| JS.Identifier _libraryName(LibraryElement library) { |
| if (library == currentLibrary) return _exportsVar; |
| - return _imports.putIfAbsent( |
| - library, () => new JS.TemporaryId(jsLibraryName(library))); |
| + return _imports.putIfAbsent(library, () { |
| + var name = library.name; |
| + if (name == 'dart._utils') return _utilsLibVar; |
|
Jennifer Messerly
2016/01/07 20:18:04
I don't think these special cases could be needed.
ochafik
2016/01/13 13:13:20
Done.
|
| + else if (name == 'dart._classes') return _classesLibVar; |
|
Jennifer Messerly
2016/01/07 20:18:04
also, relying on the library name is super risky.
ochafik
2016/01/13 13:13:20
Acknowledged.
|
| + else if (name == 'dart._rtti') return _rttiLibVar; |
| + else return new JS.TemporaryId(jsLibraryName(library)); |
| + }); |
| } |
| DartType getStaticType(Expression e) => rules.getStaticType(e); |