Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(575)

Unified Diff: lib/src/codegen/js_codegen.dart

Issue 1059583002: Extension method support to move us closer to a valid List implementation. (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: done Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « lib/runtime/dart/isolate.js ('k') | test/codegen/expect/BenchmarkBase.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/src/codegen/js_codegen.dart
diff --git a/lib/src/codegen/js_codegen.dart b/lib/src/codegen/js_codegen.dart
index d1cd2a15ac8f67b9e600fa1083e731704f33118a..a5e739df0b364a2f93d863717fa8ad3cc3059a6f 100644
--- a/lib/src/codegen/js_codegen.dart
+++ b/lib/src/codegen/js_codegen.dart
@@ -42,6 +42,11 @@ Annotation _getAnnotation(AnnotatedNode node, String name) => node.metadata
Annotation _getJsNameAnnotation(AnnotatedNode node) =>
_getAnnotation(node, "JsName");
+// TODO(jacobr): we would like to do something like the following
+// but we don't have summary support yet.
+// bool _supportJsExtensionMethod(AnnotatedNode node) =>
+// _getAnnotation(node, "SupportJsExtensionMethod") != null;
+
class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
final LibraryInfo libraryInfo;
final TypeRules rules;
@@ -60,11 +65,46 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
ClassDeclaration currentClass;
ConstantEvaluator _constEvaluator;
- final _exports = <String>[];
+ final _exports = new Set<String>();
final _lazyFields = <VariableDeclaration>[];
final _properties = <FunctionDeclaration>[];
final _privateNames = new HashSet<String>();
final _pendingPrivateNames = <String>[];
+ final _extensionMethodNames = new HashSet<String>();
+ final _pendingExtensionMethodNames = <String>[];
+
+ InterfaceType _fillDynamicTypeArgs(InterfaceType t) {
+ var d = rules.provider.dynamicType;
+ return t.substitute4(new List.filled(t.typeArguments.length, d));
+ }
+ // 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.
+ List<InterfaceType> _jsExtensionMethodTypes;
+ List<InterfaceType> get jsExtensionMethodTypes {
+ if (_jsExtensionMethodTypes != null) return _jsExtensionMethodTypes;
+ _jsExtensionMethodTypes = <InterfaceType>[
+ rules.provider.listType,
+ rules.provider.iterableType
+ ].map(_fillDynamicTypeArgs).toList();
+ return _jsExtensionMethodTypes;
+ }
+
+ Map<ClassElement, Set<String>> _extensionMethods;
+
+ Map<ClassElement, Set<String>> get extensionMethods {
+ if (_extensionMethods != null) return _extensionMethods;
+ _extensionMethods = new HashMap<ClassElement, HashSet<String>>();
+
+ for (var type in jsExtensionMethodTypes) {
+ var names = new HashSet<String>();
+ var e = type.element;
+ names.addAll(e.methods.map((m) => m.name));
+ names.addAll(e.accessors.map((m) => m.name));
+ _extensionMethods[e] = names;
+ }
+ return _extensionMethods;
+ }
/// Classes we have not emitted yet. Values can be [ClassDeclaration] or
/// [ClassTypeAlias].
@@ -143,6 +183,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
JS.Statement _initPrivateSymbol(String name) => js.statement(
'let # = $_SYMBOL(#);', [new JSTemporary(name), js.string(name, "'")]);
+ JS.Statement _initExtensionMethodSymbol(String name) => js.statement(
+ 'let # = $_SYMBOL(#);', [new JS.Identifier(name), js.string(name, "'")]);
+
// TODO(jmesserly): this is a temporary workaround for `Symbol` in core,
// until we have better name tracking.
String get _SYMBOL {
@@ -172,6 +215,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
body.addAll(_pendingPrivateNames.map(_initPrivateSymbol));
_pendingPrivateNames.clear();
}
+ if (_pendingExtensionMethodNames.isNotEmpty) {
+ body.addAll(
+ _pendingExtensionMethodNames.map(_initExtensionMethodSymbol));
+ _pendingExtensionMethodNames.clear();
+ }
body.add(code);
}
}
@@ -360,11 +408,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
]);
}
- if (isPublic(name)) _exports.add(name);
+ if (isPublic(name)) _addExport(name);
if (genericDef != null) {
body = js.statement('{ #; let # = #; }', [genericDef, name, genericInst]);
- if (isPublic(name)) _exports.add(genericName);
+ if (isPublic(name)) _addExport(genericName);
}
if (classElem.type.isObject) return body;
@@ -506,6 +554,34 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
/// Emit class members that can be generated as methods.
/// Anything not handled here will be addressed in [_finishClassMembers].
+ Iterable<InterfaceType> getMatchingExtensionMethodTypes(InterfaceType type) =>
+ jsExtensionMethodTypes.where((t) => rules.isSubTypeOf(type, t));
+
+ LibraryElement getExtensionLibrary(
+ Iterable<InterfaceType> extensionTypes, String name) {
+ var library = null;
+ for (var type in extensionTypes) {
+ var element = type.element;
+ if (extensionMethods[element].contains(name)) {
+ assert(library == null || library == element.library);
+ library = element.library;
+ }
+ }
+ return library;
+ }
+
+ JS.Expression nameIfExtension(Expression target, String name) {
+ var targetType = rules.getStaticType(target);
+ if (targetType is! InterfaceType) return null;
+ var extensionLibrary =
+ getExtensionLibrary(getMatchingExtensionMethodTypes(targetType), name);
+ if (extensionLibrary == null) return null;
+ return js.call('#.#', [
+ _libraryName(extensionLibrary),
+ _emitExtensionMethodName(name)
+ ]);
+ }
+
List<JS.Method> _emitClassMethods(ClassDeclaration node,
List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
var element = node.element;
@@ -518,12 +594,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (ctors.isEmpty && !isObject) {
jsMethods.add(_emitImplicitConstructor(node, name, fields));
}
-
+ var extensionTypes = getMatchingExtensionMethodTypes(element.type);
for (var member in node.members) {
if (member is ConstructorDeclaration) {
jsMethods.add(_emitConstructor(member, name, fields, isObject));
} else if (member is MethodDeclaration) {
- jsMethods.add(_visit(member));
+ jsMethods.add(_emitMethodDeclaration(member, extensionTypes));
}
}
@@ -570,7 +646,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (member.name != null) {
body.add(js.statement('dart.defineNamedConstructor(#, #);', [
name,
- _jsMemberName(member.name.name, isStatic: true)
+ _emitMemberName(member.name.name, isStatic: true)
]));
}
}
@@ -655,7 +731,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
JS.Expression _constructorName(String className, SimpleIdentifier name) {
if (name == null) return js.string(className, "'");
- return _jsMemberName(name.name, isStatic: true);
+ return _emitMemberName(name.name, isStatic: true);
}
JS.Block _emitConstructorBody(
@@ -766,7 +842,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (p is FieldFormalParameter) {
var name = p.identifier.name;
body.add(
- js.statement('this.# = #;', [_jsMemberName(name), _visit(p)]));
+ js.statement('this.# = #;', [_emitMemberName(name), _visit(p)]));
unsetFields.remove(name);
}
}
@@ -796,7 +872,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
value = new JS.LiteralNull();
}
}
- body.add(js.statement('this.# = #;', [_jsMemberName(name), value]));
+ body.add(js.statement('this.# = #;', [_emitMemberName(name), value]));
});
return _statement(body);
@@ -858,8 +934,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
}
}
- @override
- JS.Method visitMethodDeclaration(MethodDeclaration node) {
+ JS.Method _emitMethodDeclaration(
+ MethodDeclaration node, Iterable<InterfaceType> extensionTypes) {
if (node.isAbstract || _externalOrNative(node)) {
return null;
}
@@ -867,8 +943,32 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var params = _visit(node.parameters);
if (params == null) params = [];
- return new JS.Method(_jsMemberName(node.name.name, isStatic: node.isStatic),
- new JS.Fun(params, _visit(node.body)),
+ var memberName;
+ var extensionLibrary;
+
+ if (!node.isStatic) {
+ extensionLibrary = getExtensionLibrary(extensionTypes, node.name.name);
+ }
+
+ if (extensionLibrary != null) {
+ var extensionMethodName = _extensionMethodName(node.name.name);
+ if (extensionLibrary == libraryInfo.library.library) {
+ // 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(extensionMethodName)) {
+ _pendingExtensionMethodNames.add(extensionMethodName);
+ _addExport(extensionMethodName);
+ }
+ }
+ memberName = js.call('#.#', [
+ _libraryName(extensionLibrary),
+ js.string(extensionMethodName, "'")
+ ]);
+ } else {
+ memberName = _emitMemberName(node.name.name, isStatic: node.isStatic);
+ }
+ return new JS.Method(memberName, new JS.Fun(params, _visit(node.body)),
isGetter: node.isGetter,
isSetter: node.isSetter,
isStatic: node.isStatic);
@@ -895,7 +995,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
body.add(new JS.FunctionDeclaration(
new JS.Identifier(name), _visit(node.functionExpression)));
- if (isPublic(name)) _exports.add(name);
+ if (isPublic(name)) _addExport(name);
return _statement(body);
}
@@ -966,7 +1066,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
// instance member
if (currentClass != null && _needsImplicitThis(e)) {
- return js.call('this.#', _jsMemberName(name));
+ return js.call('this.#', _emitMemberName(name));
}
// static member
@@ -974,7 +1074,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
e.isStatic &&
variable.enclosingElement is ClassElement) {
var className = (variable.enclosingElement as ClassElement).name;
- return js.call('#.#', [className, _jsMemberName(name, isStatic: true)]);
+ return js.call('#.#', [className, _emitMemberName(name, isStatic: true)]);
}
// initializing formal parameter, e.g. `Point(this.x)`
@@ -1032,7 +1132,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
element is ClassElement && _lazyClass(element));
}
- JS.Node _emitDPutIfDynamic(
+ JS.Node _emitDSetIfDynamic(
Expression target, SimpleIdentifier id, Expression rhs) {
if (rules.isDynamicTarget(target)) {
return js.call('dart.dput(#, #, #)', [
@@ -1049,27 +1149,31 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
JS.Node visitAssignmentExpression(AssignmentExpression node) {
var lhs = node.leftHandSide;
var rhs = node.rightHandSide;
- return _emitAssignment(lhs, rhs, node.parent);
+ return _emitSet(lhs, rhs, node.parent);
}
- JS.Node _emitAssignment(Expression lhs, Expression rhs, [AstNode parent]) {
+ JS.Node _emitSet(Expression lhs, Expression rhs, [AstNode parent]) {
if (lhs is IndexExpression) {
String code;
var target = _getTarget(lhs);
if (rules.isDynamicTarget(target)) {
code = 'dart.dsetindex(#, #, #)';
- } else {
- code = '#.set(#, #)';
+ return js.call(code, [_visit(target), _visit(lhs.index), _visit(rhs)]);
}
- return js.call(code, [_visit(target), _visit(lhs.index), _visit(rhs)]);
+ return js.call('#.#(#, #)', [
+ _visit(target),
+ _emitMemberName('[]=', target: target),
+ _visit(lhs.index),
+ _visit(rhs)
+ ]);
}
if (lhs is PropertyAccess) {
- var result = _emitDPutIfDynamic(_getTarget(lhs), lhs.propertyName, rhs);
+ var result = _emitDSetIfDynamic(_getTarget(lhs), lhs.propertyName, rhs);
if (result != null) return result;
} else if (lhs is PrefixedIdentifier) {
// TODO(vsm): Is this the right code if the prefix is a library?
- var result = _emitDPutIfDynamic(lhs.prefix, lhs.identifier, rhs);
+ var result = _emitDSetIfDynamic(lhs.prefix, lhs.identifier, rhs);
if (result != null) return result;
}
@@ -1144,7 +1248,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var targetJs;
if (target != null) {
- targetJs = js.call('#.#', [_visit(target), node.methodName.name]);
+ targetJs = js.call('#.#', [
+ _visit(target),
+ _emitMemberName(node.methodName.name, target: target)
+ ]);
} else {
targetJs = _visit(node.methodName);
}
@@ -1255,7 +1362,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var name = field.name.name;
body.add(js.statement(
'let # = #;', [new JS.Identifier(name), _visitInitializer(field)]));
- if (isPublic(name)) _exports.add(name);
+ if (isPublic(name)) _addExport(name);
} else if (_isFieldInitConstant(field)) {
body.add(js.statement(
'# = #;', [_visit(field.name), _visitInitializer(field)]));
@@ -1267,6 +1374,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
return _statement(body);
}
+ _addExport(String name) {
+ if (!_exports.add(name)) throw 'Duplicate top level name found: $name';
+ }
+
@override
visitVariableDeclarationList(VariableDeclarationList node) {
var last = node.variables.last;
@@ -1361,7 +1472,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var typeName = _visit(node.type);
if (node.name != null) {
return js.call(
- '#.#', [typeName, _jsMemberName(node.name.name, isStatic: true)]);
+ '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]);
}
return typeName;
}
@@ -1559,7 +1670,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
one.staticType = rules.provider.intType;
var increment = AstBuilder.binaryExpression(tmp, op.lexeme[0], one);
increment.staticType = type;
- var write = _emitAssignment(expr, increment);
+ var write = _emitSet(expr, increment);
var bindThis = _maybeBindThis(expr);
return js.call("((#) => (#, #))$bindThis(#)", [
@@ -1595,7 +1706,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var one = AstBuilder.integerLiteral(1);
one.staticType = rules.provider.intType;
var increment = AstBuilder.binaryExpression(expr, op.lexeme[0], one);
- return _emitAssignment(expr, increment);
+ return _emitSet(expr, increment);
}
@override
@@ -1623,7 +1734,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
}
// Call the operator
- var opString = _jsMemberName(op.lexeme, unary: true);
+ var opString = _emitMemberName(op.lexeme, unary: true);
if (rules.isDynamicTarget(expr)) {
// dynamic dispatch
return js.call('dart.dunary(#, #)', [opString, _visit(expr)]);
@@ -1723,38 +1834,42 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (node.prefix.staticElement is PrefixElement) {
return _visit(node.identifier);
} else {
- return _visitGet(node.prefix, node.identifier);
+ return _emitGet(node.prefix, node.identifier);
}
}
@override
visitPropertyAccess(PropertyAccess node) =>
- _visitGet(_getTarget(node), node.propertyName);
+ _emitGet(_getTarget(node), node.propertyName);
/// Shared code for [PrefixedIdentifier] and [PropertyAccess].
- _visitGet(Expression target, SimpleIdentifier name) {
+ _emitGet(Expression target, SimpleIdentifier name) {
if (rules.isDynamicTarget(target)) {
return js.call(
'dart.dload(#, #)', [_visit(target), js.string(name.name, "'")]);
} else {
var e = name.staticElement;
- return js.call('#.#', [
+ var ret = js.call('#.#', [
_visit(target),
- _jsMemberName(name.name, isStatic: e is ExecutableElement && e.isStatic)
+ _emitMemberName(name.name,
+ isStatic: e is ExecutableElement && e.isStatic, target: target)
]);
+ return ret;
}
}
@override
visitIndexExpression(IndexExpression node) {
var target = _getTarget(node);
- var code;
if (rules.isDynamicTarget(target)) {
- code = 'dart.dindex(#, #)';
- } else {
- code = '#.get(#)';
+ return js.call('dart.dindex(#, #)', [_visit(target), _visit(node.index)]);
}
- return js.call(code, [_visit(target), _visit(node.index)]);
+
+ return js.call('#.#(#)', [
+ _visit(target),
+ _emitMemberName('[]', target: target),
+ _visit(node.index)
+ ]);
}
/// Gets the target of a [PropertyAccess] or [IndexExpression].
@@ -2140,29 +2255,39 @@ 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 _jsMemberName(String name,
- {bool unary: false, bool isStatic: false}) {
+ JS.Expression _emitMemberName(String name,
+ {bool unary: false, bool isStatic: false, Expression target}) {
+ if (isStatic == false && target != null) {
+ var ret = nameIfExtension(target, name);
+ if (ret != null) return ret;
+ }
if (name.startsWith('_')) {
if (_privateNames.add(name)) _pendingPrivateNames.add(name);
return new JSTemporary(name);
}
- if (name == '[]') {
- name = 'get';
- } else if (name == '[]=') {
- name = 'set';
- } else if (unary && name == '-') {
- name = 'unary-';
- } else if (isStatic && invalidJSStaticMethodName(name)) {
+ return _propertyName(_jsMemberName(name, unary: unary, isStatic: isStatic));
+ }
+
+ String _jsMemberName(String name, {bool unary: false, bool isStatic: false}) {
+ if (name == '[]') return 'get';
+ if (name == '[]=') return 'set';
+ if (unary && name == '-') return 'unary-';
+ if (isStatic && invalidJSStaticMethodName(name)) {
// Choose an string name. Use an invalid identifier so it won't conflict
// with any valid member names.
// TODO(jmesserly): this works around the problem, but I'm pretty sure we
// don't need it, as static methods seemed to work. The only concrete
// issue we saw was in the defineNamedConstructor helper function.
- name = '$name*';
+ return '$name*';
}
- return _propertyName(name);
+ return name;
}
+ JS.LiteralString _emitExtensionMethodName(String name) =>
+ js.string(_extensionMethodName(name), "'");
+
+ String _extensionMethodName(String name) => '\$${_jsMemberName(name)}';
+
bool _externalOrNative(node) =>
node.externalKeyword != null || _functionBody(node) is NativeFunctionBody;
« no previous file with comments | « lib/runtime/dart/isolate.js ('k') | test/codegen/expect/BenchmarkBase.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698