Index: lib/src/codegen/js_codegen.dart |
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart |
index 87d29fceef6978e42cd9af85c9c150f20aec3050..f89564d9eaa16176e00d5161f22de7f2b3f3b6f6 100644 |
--- a/lib/src/codegen/js_codegen.dart |
+++ b/lib/src/codegen/js_codegen.dart |
@@ -4,7 +4,7 @@ |
library dev_compiler.src.codegen.js_codegen; |
-import 'dart:collection' show HashSet, HashMap; |
+import 'dart:collection' show HashSet, HashMap, SplayTreeSet; |
import 'package:analyzer/analyzer.dart' hide ConstantEvaluator; |
import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator; |
@@ -55,8 +55,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
final TypeRules rules; |
final LibraryInfo libraryInfo; |
- /// The global extension method table. |
- final HashMap<String, List<InterfaceType>> _extensionMethods; |
+ /// The global extension type table. |
+ final HashSet<ClassElement> _extensionTypes; |
/// Information that is precomputed for this library, indicates which fields |
/// need storage slots. |
@@ -74,7 +74,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
final _lazyFields = <VariableDeclaration>[]; |
final _properties = <FunctionDeclaration>[]; |
final _privateNames = new HashMap<String, JS.TemporaryId>(); |
- final _extensionMethodNames = new HashSet<String>(); |
final _moduleItems = <JS.Statement>[]; |
final _temps = new HashMap<Element, JS.TemporaryId>(); |
final _qualifiedIds = new HashMap<Element, JS.MaybeQualifiedId>(); |
@@ -90,7 +89,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
ModuleItemLoadOrder _loader; |
JSCodegenVisitor(AbstractCompiler compiler, this.libraryInfo, |
- this._extensionMethods, this._fieldsNeedingStorage) |
+ this._extensionTypes, this._fieldsNeedingStorage) |
: compiler = compiler, |
options = compiler.options, |
rules = compiler.rules { |
@@ -559,6 +558,21 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
new JS.ArrayInitializer( |
classElem.interfaces.map(_emitTypeName).toList()) |
])); |
+ |
+ // If a concrete class implements one of our extensions, we might need to |
+ // add forwarders. |
+ var extensions = _extensionsToImplement(classElem); |
+ if (extensions.isNotEmpty) { |
+ var methodNames = []; |
+ for (var e in extensions) { |
+ methodNames.add(_emitMemberDeclarationName(e)); |
+ } |
+ body.add(js.statement('dart.defineExtensionMembers(#, #);', [ |
+ name, |
+ new JS.ArrayInitializer(methodNames, |
+ multiline: methodNames.length > 4) |
+ ])); |
+ } |
} |
// Named constructors |
@@ -586,7 +600,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
var tStatics = []; |
var tMethods = []; |
var sNames = []; |
- var cType = classElem.type; |
for (MethodDeclaration node in methods) { |
if (!(node.isSetter || node.isGetter || node.isAbstract)) { |
var name = node.name.name; |
@@ -595,9 +608,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
classElem.lookUpInheritedConcreteMethod(name, currentLibrary); |
if (inheritedElement != null && |
inheritedElement.type == element.type) continue; |
- var unary = node.parameters.parameters.isEmpty; |
- var memberName = _emitMemberName(name, |
- type: cType, unary: unary, isStatic: node.isStatic); |
+ var memberName = _emitMemberDeclarationName(element); |
var parts = |
_emitFunctionTypeParts(element.type, dynamicIsBottom: false); |
var property = |
@@ -605,7 +616,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
if (node.isStatic) { |
tStatics.add(property); |
sNames.add(memberName); |
- } else tMethods.add(property); |
+ } else { |
+ tMethods.add(property); |
+ } |
} |
} |
var tCtors = []; |
@@ -620,7 +633,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
} |
build(name, elements) { |
var o = |
- new JS.ObjectInitializer(elements, vertical: elements.length > 1); |
+ new JS.ObjectInitializer(elements, multiline: elements.length > 1); |
var e = js.call('() => #', o); |
var p = new JS.Property(_propertyName(name), e); |
return p; |
@@ -645,6 +658,48 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
return _statement(body); |
} |
+ List<ExecutableElement> _extensionsToImplement(ClassElement element) { |
+ var members = <ExecutableElement>[]; |
+ if (_extensionTypes.contains(element)) return members; |
+ |
+ // Collect all extension types we implement. |
+ var type = element.type; |
+ var types = new Set<ClassElement>(); |
+ _collectExtensions(type, types); |
+ 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) 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; |
+ } |
+ |
+ /// Collections the type and all supertypes, including interfaces, but |
+ /// excluding [Object]. |
+ void _collectExtensions(InterfaceType type, Set<ClassElement> types) { |
+ if (type.isObject) return; |
+ var element = type.element; |
+ if (_extensionTypes.contains(element)) types.add(element); |
+ for (var m in type.mixins.reversed) { |
+ _collectExtensions(m, types); |
+ } |
+ for (var i in type.interfaces) { |
+ _collectExtensions(i, types); |
+ } |
+ _collectExtensions(type.superclass, types); |
+ } |
+ |
JS.Statement _overrideField(FieldElement e) { |
var cls = e.enclosingElement; |
return js.statement('dart.virtualField(#, #)', [ |
@@ -993,9 +1048,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
var params = _visit(node.parameters); |
if (params == null) params = []; |
- var memberName = _emitMemberName(node.name.name, |
- type: type, unary: params.isEmpty, isStatic: node.isStatic); |
- return new JS.Method(memberName, new JS.Fun(params, _visit(node.body)), |
+ return new JS.Method(_emitMemberDeclarationName(node.element), |
+ new JS.Fun(params, _visit(node.body)), |
isGetter: node.isGetter, |
isSetter: node.isSetter, |
isStatic: node.isStatic); |
@@ -1667,8 +1721,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
@override |
visitConstructorName(ConstructorName node) { |
var typeName = _visit(node.type); |
- if (node.name != null || node.staticElement.isFactory) { |
- var namedCtor = _constructorName(node.staticElement); |
+ var element = node.staticElement; |
+ if (node.name != null || element.isFactory) { |
+ var namedCtor = _constructorName(element); |
return new JS.PropertyAccess(typeName, namedCtor); |
} |
return typeName; |
@@ -1677,9 +1732,22 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
@override |
visitInstanceCreationExpression(InstanceCreationExpression node) { |
emitNew() { |
- var ctor = _visit(node.constructorName); |
+ JS.Expression ctor; |
+ bool isFactory = false; |
+ var element = node.staticElement; |
+ if (element == null) { |
+ // TODO(jmesserly): this only happens if we had a static error. |
+ // Should we generate a throw instead? |
+ ctor = _visit(node.constructorName.type); |
+ var ctorName = node.constructorName.name; |
+ if (ctorName != null) { |
+ ctor = new JS.PropertyAccess(ctor, _propertyName(ctorName.name)); |
+ } |
+ } else { |
+ ctor = _visit(node.constructorName); |
+ isFactory = element.isFactory; |
+ } |
var args = _visit(node.argumentList); |
- var isFactory = node.staticElement.isFactory; |
return isFactory ? new JS.Call(ctor, args) : new JS.New(ctor, args); |
} |
if (node.isConst) return _emitConst(node, emitNew); |
@@ -2342,8 +2410,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
emitList() { |
JS.Expression list = new JS.ArrayInitializer(_visitList(node.elements)); |
ParameterizedType type = node.staticType; |
- if (type.typeArguments.any((a) => a != types.dynamicType)) { |
- list = js.call('dart.setType(#, #)', [list, _emitTypeName(type)]); |
+ var elementType = type.typeArguments.single; |
+ if (elementType != types.dynamicType) { |
+ list = js.call('dart.list(#, #)', [list, _emitTypeName(elementType)]); |
} |
return list; |
} |
@@ -2451,6 +2520,24 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
return new JS.Expression.binary(_visitList(nodes), operator); |
} |
+ /// Like [_emitMemberName], but for declaration sites. |
+ /// |
+ /// Unlike call sites, we always have an element available, so we can use it |
+ /// directly rather than computing the relevant options for [_emitMemberName]. |
+ JS.Expression _emitMemberDeclarationName(ExecutableElement e) { |
+ String name; |
+ if (e is PropertyAccessorElement) { |
+ name = e.variable.name; |
+ } else { |
+ name = e.name; |
+ } |
+ return _emitMemberName(name, |
+ type: (e.enclosingElement as ClassElement).type, |
+ unary: e.parameters.isEmpty, |
+ isStatic: e.isStatic, |
+ declaration: true); |
+ } |
+ |
/// This handles member renaming for private names and operators. |
/// |
/// Private names are generated using ES6 symbols: |
@@ -2492,8 +2579,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
/// Equality is a bit special, it is generated via the Dart `equals` runtime |
/// helper, that checks for null. The user defined method is called '=='. |
/// |
- JS.Expression _emitMemberName(String name, |
- {DartType type, bool unary: false, bool isStatic: false}) { |
+ JS.Expression _emitMemberName(String name, {DartType type, bool unary: false, |
+ bool isStatic: false, bool declaration: false}) { |
// Static members skip the rename steps. |
if (isStatic) return _propertyName(name); |
@@ -2503,9 +2590,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
name, () => _initSymbol(new JS.TemporaryId(name))); |
} |
- // Check for extension method: |
- var extLibrary = _findExtensionLibrary(name, type); |
- |
if (name == '[]') { |
name = 'get'; |
} else if (name == '[]=') { |
@@ -2514,46 +2598,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
name = 'unary-'; |
} |
- if (extLibrary != null) { |
- return _extensionMethodName(name, extLibrary); |
+ // Dart "extension" methods. Used for JS Array, Boolean, Number, String. |
+ if (!declaration && _extensionTypes.contains(type.element)) { |
+ // Special case `length`. We can call it directly. |
+ if (name != 'length') return js.call('dartx.#', _propertyName(name)); |
} |
return _propertyName(name); |
} |
- LibraryElement _findExtensionLibrary(String name, DartType type) { |
- if (type is! InterfaceType) return null; |
- |
- var extLibrary = null; |
- var extensionTypes = _extensionMethods[name]; |
- if (extensionTypes != null) { |
- // Normalize the type to ignore generics. |
- type = fillDynamicTypeArgs(type, types); |
- for (var t in extensionTypes) { |
- if (rules.isSubTypeOf(type, t)) { |
- assert(extLibrary == null || extLibrary == t.element.library); |
- extLibrary = t.element.library; |
- } |
- } |
- } |
- return extLibrary; |
- } |
- |
- JS.Expression _extensionMethodName(String name, LibraryElement library) { |
- var extName = '\$$name'; |
- if (library == currentLibrary) { |
- // TODO(jacobr): need to do a better job ensuring that extension method |
- // name symbols do not conflict with other symbols before we can let |
- // user defined libraries define extension methods. |
- if (_extensionMethodNames.add(extName)) { |
- _initSymbol(new JS.Identifier(extName)); |
- _addExport(extName); |
- } |
- return new JS.Identifier(extName); |
- } |
- return js.call('#.#', [_libraryName(library), _propertyName(extName)]); |
- } |
- |
bool _externalOrNative(node) => |
node.externalKeyword != null || _functionBody(node) is NativeFunctionBody; |
@@ -2573,35 +2626,31 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor { |
} |
class JSGenerator extends CodeGenerator { |
- /// For fast lookup of extension methods, we first check the name, then do a |
- /// (possibly expensive) subtype test to see if it matches one of the types |
- /// that declares that method. |
- final _extensionMethods = new HashMap<String, List<InterfaceType>>(); |
- |
- JSGenerator(AbstractCompiler context) : super(context) { |
+ final _extensionTypes = new HashSet<ClassElement>(); |
+ JSGenerator(AbstractCompiler compiler) : super(compiler) { |
// TODO(jacobr): determine the the set of types with extension methods from |
// the annotations rather than hard coding the list once the analyzer |
// supports summaries. |
- var extensionTypes = [types.listType, types.iterableType]; |
- for (var type in extensionTypes) { |
- type = fillDynamicTypeArgs(type, rules.provider); |
- var e = type.element; |
- var names = new HashSet<String>() |
- ..addAll(e.methods.map((m) => m.name)) |
- ..addAll(e.accessors.map((m) => m.name)); |
- for (var name in names) { |
- _extensionMethods.putIfAbsent(name, () => []).add(type); |
- } |
+ var context = compiler.context; |
+ var src = context.sourceFactory.forUri('dart:_interceptors'); |
+ var interceptors = context.computeLibraryElement(src); |
+ for (var t in ['JSArray', 'JSString', 'JSInt', 'JSDouble', 'JSBool']) { |
+ _addExtensionType(interceptors.getType(t).type); |
} |
} |
- TypeProvider get types => rules.provider; |
+ void _addExtensionType(InterfaceType t) { |
+ if (t.isObject || !_extensionTypes.add(t.element)) return; |
+ t = fillDynamicTypeArgs(t, rules.provider); |
+ t.interfaces.forEach(_addExtensionType); |
+ t.mixins.forEach(_addExtensionType); |
+ _addExtensionType(t.superclass); |
+ } |
String generateLibrary(LibraryUnit unit, LibraryInfo info) { |
var fields = findFieldsNeedingStorage(unit); |
- var codegen = |
- new JSCodegenVisitor(compiler, info, _extensionMethods, fields); |
+ var codegen = new JSCodegenVisitor(compiler, info, _extensionTypes, fields); |
var module = codegen.emitLibrary(unit); |
var dir = path.join(outDir, jsOutputPath(info, root)); |
return writeJsLibrary(module, dir, emitSourceMaps: options.emitSourceMaps); |