| 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..88f137ab9f8c8f009b671d032e355e4f311f08fd 100644
|
| --- a/lib/src/codegen/js_codegen.dart
|
| +++ b/lib/src/codegen/js_codegen.dart
|
| @@ -42,6 +42,12 @@ 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;
|
| @@ -65,6 +71,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| final _properties = <FunctionDeclaration>[];
|
| final _privateNames = new HashSet<String>();
|
| final _pendingPrivateNames = <String>[];
|
| + final _extensionMethodNames = new HashSet<String>();
|
| + final _pendingExtensionMethodNames = <String>[];
|
| +
|
| + // 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> get _JsExtensionMethodTypes =>
|
| + <InterfaceType>[rules.provider.listType, rules.provider.iterableType];
|
|
|
| /// Classes we have not emitted yet. Values can be [ClassDeclaration] or
|
| /// [ClassTypeAlias].
|
| @@ -143,6 +158,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, allowRename: false), 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 +190,10 @@ 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);
|
| }
|
| }
|
| @@ -504,8 +526,76 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| return heritage;
|
| }
|
|
|
| + // TODO(jacobr): why doesn't the generic i1.isSubTypeOf(i2) work?
|
| + bool _isInterfaceSubTypeOf(InterfaceType i1, InterfaceType i2) {
|
| + if (i1 == i2) return true;
|
| +
|
| + if (i1.element == i2.element) {
|
| + return true;
|
| + }
|
| +
|
| + if (i2.isDartCoreFunction) {
|
| + if (i1.element.getMethod("call") != null) return true;
|
| + }
|
| +
|
| + if (i1 == rules.provider.objectType) return false;
|
| +
|
| + if (_isInterfaceSubTypeOf(i1.superclass, i2)) return true;
|
| +
|
| + for (final parent in i1.interfaces) {
|
| + if (_isInterfaceSubTypeOf(parent, i2)) return true;
|
| + }
|
| +
|
| + for (final parent in i1.mixins) {
|
| + if (_isInterfaceSubTypeOf(parent, i2)) return true;
|
| + }
|
| +
|
| + return false;
|
| + }
|
| +
|
| /// Emit class members that can be generated as methods.
|
| /// Anything not handled here will be addressed in [_finishClassMembers].
|
| + List<InterfaceType> getMatchingExtensionMethodTypes(InterfaceType type) {
|
| + var extensionTypes = <InterfaceType>[];
|
| + for (var extensionType in _JsExtensionMethodTypes) {
|
| + if (_isInterfaceSubTypeOf(type, extensionType)) {
|
| + extensionTypes.add(extensionType);
|
| + }
|
| + }
|
| + return extensionTypes;
|
| + }
|
| +
|
| + LibraryElement lookupExtensionLibrary(Iterable<InterfaceType> extensionTypes, String name, {bool isGetter: false, bool isSetter: false}) {
|
| + var extensionLibrary;
|
| + assert (!isGetter || !isSetter);
|
| + for (var extensionType in extensionTypes) {
|
| + var match;
|
| + if (isGetter) {
|
| + match = extensionType.getGetter(name);
|
| + } else if (isSetter) {
|
| + match = extensionType.getSetter(name);
|
| + } else {
|
| + match = extensionType.getMethod(name);
|
| + }
|
| + // It is possible that the same method could need to be an extension
|
| + // method for classes defined in multiple libraries. Instead of handling
|
| + // that case we currently assert. The correct behavior is unclear so we
|
| + // might need to prevent this with a compile time error.
|
| + if (match != null) {
|
| + assert(extensionLibrary == null || extensionLibrary == extensionType.element.library);
|
| + extensionLibrary = extensionType.element.library;
|
| + }
|
| + }
|
| + return extensionLibrary;
|
| + }
|
| +
|
| + JS.Expression nameIfExtension(DartType targetType, String name, {bool isGetter: false, bool isSetter: false}) {
|
| + if (targetType is! InterfaceType) return null;
|
| + var extensionLibrary = lookupExtensionLibrary(getMatchingExtensionMethodTypes(targetType), name, isGetter: isGetter, isSetter: isSetter);
|
| + if (extensionLibrary == null) return null;
|
| + return js.call('#.#', [_libraryName(extensionLibrary), _extensionMethodName(name)]);
|
| + }
|
| +
|
| List<JS.Method> _emitClassMethods(ClassDeclaration node,
|
| List<ConstructorDeclaration> ctors, List<FieldDeclaration> fields) {
|
| var element = node.element;
|
| @@ -518,12 +608,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(_visitMethodDeclaration(member, extensionTypes));
|
| }
|
| }
|
|
|
| @@ -858,8 +948,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| }
|
| }
|
|
|
| - @override
|
| - JS.Method visitMethodDeclaration(MethodDeclaration node) {
|
| + JS.Method _visitMethodDeclaration(MethodDeclaration node, List<InterfaceType> extensionTypes) {
|
| if (node.isAbstract || _externalOrNative(node)) {
|
| return null;
|
| }
|
| @@ -867,7 +956,25 @@ 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),
|
| + var extensionLibrary;
|
| + var memberName;
|
| + if (node.isStatic == false &&
|
| + (extensionLibrary = lookupExtensionLibrary(extensionTypes, node.name.name, isGetter: node.isGetter, isSetter: node.isSetter)) != null) {
|
| + var extensionMethodName = _extensionMethodNameRaw(node.name.name);
|
| + if (extensionLibrary == libraryInfo.library.library) {
|
| + // TODO(jacobr): need to do a better job insuring 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);
|
| + _exports.add(extensionMethodName);
|
| + }
|
| + }
|
| + memberName= js.call('#.#', [_libraryName(extensionLibrary), js.string(extensionMethodName, "'")]);
|
| + } else {
|
| + memberName = _jsMemberName(node.name.name, isStatic: node.isStatic);
|
| + }
|
| + return new JS.Method(memberName,
|
| new JS.Fun(params, _visit(node.body)),
|
| isGetter: node.isGetter,
|
| isSetter: node.isSetter,
|
| @@ -1032,7 +1139,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 +1156,27 @@ 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)]);
|
| + var methodName = nameIfExtension(rules.getStaticType(target), "[]=");
|
| + return js.call('#.#(#, #)', [_visit(target), methodName != null ? methodName: js.string('set'), _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 +1251,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
|
|
| var targetJs;
|
| if (target != null) {
|
| - targetJs = js.call('#.#', [_visit(target), node.methodName.name]);
|
| + var methodNameJs = nameIfExtension(rules.getStaticType(target), node.methodName.name);
|
| + targetJs = js.call('#.#', [_visit(target), methodNameJs != null ? methodNameJs : node.methodName.name]);
|
| } else {
|
| targetJs = _visit(node.methodName);
|
| }
|
| @@ -1559,7 +1667,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 +1703,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
|
| @@ -1723,38 +1831,44 @@ 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('#.#', [
|
| + bool isStatic = e is ExecutableElement && e.isStatic;
|
| + var memberName;
|
| + if (!isStatic) {
|
| + memberName = nameIfExtension(rules.getStaticType(target), name.name, isGetter: true);
|
| + }
|
| +
|
| + var ret = js.call('#.#', [
|
| _visit(target),
|
| - _jsMemberName(name.name, isStatic: e is ExecutableElement && e.isStatic)
|
| + memberName != null ? memberName : _jsMemberName(name.name, isStatic: isStatic)
|
| ]);
|
| + 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)]);
|
| +
|
| + var targetJs = nameIfExtension(rules.getStaticType(target), '[]');
|
| + return js.call('#.#(#)', [_visit(target), targetJs != null ? targetJs : js.string('get'), _visit(node.index)]);
|
| }
|
|
|
| /// Gets the target of a [PropertyAccess] or [IndexExpression].
|
| @@ -2146,23 +2260,33 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
|
| 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(_transformMemberNameHelper(name, unary: unary, isStatic: isStatic));
|
| + }
|
| +
|
| + String _transformMemberNameHelper(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;
|
| }
|
|
|
| +
|
| + // TODO(jacobr): we need to avoid possible collisions between extension
|
| + // methods names and regular names.
|
| + JS.LiteralString _extensionMethodName(String name) =>
|
| + js.string(_extensionMethodNameRaw(name), "'");
|
| +
|
| + String _extensionMethodNameRaw(String name) => '\$${_transformMemberNameHelper(name)}';
|
| +
|
| bool _externalOrNative(node) =>
|
| node.externalKeyword != null || _functionBody(node) is NativeFunctionBody;
|
|
|
|
|