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

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

Issue 1083763003: refactor emitMemberName to be used more consistently (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 8 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_runtime.js ('k') | lib/src/utils.dart » ('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 7a01c69a0338153604a85b35a7cc0243c56c8533..5fb5c1de19f7393be8f5e68a35f02f6f4ec0bf1c 100644
--- a/lib/src/codegen/js_codegen.dart
+++ b/lib/src/codegen/js_codegen.dart
@@ -11,6 +11,7 @@ import 'package:analyzer/analyzer.dart' hide ConstantEvaluator;
import 'package:analyzer/src/generated/ast.dart' hide ConstantEvaluator;
import 'package:analyzer/src/generated/constant.dart';
import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
import 'package:analyzer/src/generated/scanner.dart'
show StringToken, Token, TokenType;
import 'package:source_maps/source_maps.dart' as srcmaps show Printer;
@@ -51,6 +52,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
final LibraryInfo libraryInfo;
final TypeRules rules;
+ /// The global extension method table.
+ final HashMap<String, List<InterfaceType>> _extensionMethods;
+
/// The variable for the target of the current `..` cascade expression.
SimpleIdentifier _cascadeTarget;
/// The variable for the current catch clause
@@ -67,38 +71,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
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;
- }
+ /// The name for the library's exports inside itself.
+ /// This much be a constant because we interpolate it into template strings,
+ /// and otherwise it would break caching for them.
+ /// `exports` was chosen as the most similar to ES module patterns.
+ final JSTemporary _exportsVar = new JSTemporary('exports');
+ final JSTemporary _namedArgTemp = new JSTemporary('opts');
/// Classes we have not emitted yet. Values can be [ClassDeclaration] or
/// [ClassTypeAlias].
@@ -110,16 +88,10 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
/// Memoized results of [_inLibraryCycle].
final _libraryCycleMemo = new HashMap<LibraryElement, bool>();
- JSCodegenVisitor(this.libraryInfo, this.rules);
+ JSCodegenVisitor(this.libraryInfo, this.rules, this._extensionMethods);
LibraryElement get currentLibrary => libraryInfo.library;
-
- /// The name for the library's exports inside itself.
- /// This much be a constant because we interpolate it into template strings,
- /// and otherwise it would break caching for them.
- /// `exports` was chosen as the most similar to ES module patterns.
- final JSTemporary _exportsVar = new JSTemporary('exports');
- final JSTemporary _namedArgTemp = new JSTemporary('opts');
+ TypeProvider get types => rules.provider;
JS.Program emitLibrary(LibraryUnit library) {
var jsDefaultValue = '{}';
@@ -194,7 +166,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
JS.Statement visitCompilationUnit(CompilationUnit node) {
var source = node.element.source;
- _constEvaluator = new ConstantEvaluator(source, rules.provider);
+ _constEvaluator = new ConstantEvaluator(source, types);
// TODO(jmesserly): scriptTag, directives.
var body = <JS.Statement>[];
@@ -412,7 +384,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
'{ #; dart.defineLazyClassGeneric(#, #, { get: # }); }', [
genericDef,
_exportsVar,
- js.string(name, "'"),
+ _propertyName(name),
genericName
]);
}
@@ -443,7 +415,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
_emitClassIfNeeded(classDefs, m);
}
} else if (type is FunctionType) {
- _emitClassIfNeeded(classDefs, rules.provider.functionType);
+ _emitClassIfNeeded(classDefs, types.functionType);
}
classDefs.add(body);
return _statement(classDefs);
@@ -472,7 +444,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
result = _typeMightNotBeLoaded(type.superclass) ||
type.mixins.any(_typeMightNotBeLoaded);
} else if (type is FunctionType) {
- result = _typeMightNotBeLoaded(rules.provider.functionType);
+ result = _typeMightNotBeLoaded(types.functionType);
}
return _lazyClassMemo[type.element] = result;
}
@@ -568,7 +540,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (node.extendsClause != null) {
heritage = _visit(node.extendsClause.superclass);
} else {
- heritage = _emitTypeName(rules.provider.objectType);
+ heritage = _emitTypeName(types.objectType);
}
if (node.withClause != null) {
var mixins = _visitList(node.withClause.mixinTypes);
@@ -578,40 +550,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
return heritage;
}
- /// 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;
- var isObject = element.type.isObject;
+ var type = element.type;
+ var isObject = type.isObject;
var name = node.name.name;
var jsMethods = <JS.Method>[];
@@ -620,12 +563,11 @@ 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(_emitMethodDeclaration(member, extensionTypes));
+ jsMethods.add(_emitMethodDeclaration(type, member));
}
}
@@ -732,7 +674,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
// TODO(jmesserly): we'll need to rethink this once the ES6 spec and V8
// settles. See <https://github.com/dart-lang/dev_compiler/issues/51>.
// Performance of this pattern is likely to be bad.
- name = js.string('constructor', "'");
+ name = _propertyName('constructor');
// Mark the parameter as no-rename.
var args = new JS.Identifier('arguments', allowRename: false);
body = js.statement('''{
@@ -756,7 +698,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
}
JS.Expression _constructorName(String className, SimpleIdentifier name) {
- if (name == null) return js.string(className, "'");
+ if (name == null) return _propertyName(className);
return _emitMemberName(name.name, isStatic: true);
}
@@ -849,11 +791,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var body = <JS.Statement>[];
// Run field initializers if they can have side-effects.
- var unsetFields = new Map<String, VariableDeclaration>();
+ var unsetFields = new Map<FieldElement, VariableDeclaration>();
for (var declaration in fields) {
for (var field in declaration.fields.variables) {
if (_isFieldInitConstant(field)) {
- unsetFields[field.name.name] = field;
+ unsetFields[field.element] = field;
} else {
body.add(js.statement(
'# = #;', [_visit(field.name), _visitInitializer(field)]));
@@ -866,10 +808,14 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
for (var p in parameters.parameters) {
if (p is DefaultFormalParameter) p = p.parameter;
if (p is FieldFormalParameter) {
- var name = p.identifier.name;
- body.add(
- js.statement('this.# = #;', [_emitMemberName(name), _visit(p)]));
- unsetFields.remove(name);
+ var field = (p.element as FieldFormalParameterElement).field;
+ // Use the getter to initialize the field. This is a bit strange, but
+ // final fields don't have a setter element that we could use instead.
+
+ var memberName =
+ _emitMemberName(field.name, type: field.enclosingElement.type);
+ body.add(js.statement('this.# = #;', [memberName, _visit(p)]));
+ unsetFields.remove(field);
}
}
}
@@ -880,25 +826,26 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (init is ConstructorFieldInitializer) {
body.add(js.statement(
'# = #;', [_visit(init.fieldName), _visit(init.expression)]));
- unsetFields.remove(init.fieldName.name);
+ unsetFields.remove(init.fieldName.staticElement);
}
}
}
// Initialize all remaining fields
- unsetFields.forEach((name, field) {
+ unsetFields.forEach((field, fieldNode) {
JS.Expression value;
- if (field.initializer != null) {
- value = _visit(field.initializer);
+ if (fieldNode.initializer != null) {
+ value = _visit(fieldNode.initializer);
} else {
- var type = rules.elementType(field.element);
+ var type = rules.elementType(field);
+ value = new JS.LiteralNull();
if (rules.maybeNonNullableType(type)) {
- value = js.call('dart.as(null, #)', _emitTypeName(type));
- } else {
- value = new JS.LiteralNull();
+ value = js.call('dart.as(#, #)', [value, _emitTypeName(type)]);
}
}
- body.add(js.statement('this.# = #;', [_emitMemberName(name), value]));
+ var memberName =
+ _emitMemberName(field.name, type: field.enclosingElement.type);
+ body.add(js.statement('this.# = #;', [memberName, value]));
});
return _statement(body);
@@ -960,8 +907,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
}
}
- JS.Method _emitMethodDeclaration(
- MethodDeclaration node, Iterable<InterfaceType> extensionTypes) {
+ JS.Method _emitMethodDeclaration(DartType type, MethodDeclaration node) {
if (node.isAbstract || _externalOrNative(node)) {
return null;
}
@@ -969,31 +915,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var params = _visit(node.parameters);
if (params == null) params = [];
- 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);
- }
+ 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)),
isGetter: node.isGetter,
isSetter: node.isSetter,
@@ -1078,8 +1001,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
'Unimplemented unknown name', new JS.Identifier(node.name));
}
- var name = node.name;
var variable = e is PropertyAccessorElement ? e.variable : e;
+ var name = variable.name;
// library member
if (e.enclosingElement is CompilationUnitElement &&
@@ -1090,7 +1013,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
// instance member
if (currentClass != null && _needsImplicitThis(e)) {
- return js.call('this.#', _emitMemberName(name));
+ return js.call(
+ 'this.#', _emitMemberName(name, type: currentClass.element.type));
}
// static member
@@ -1113,7 +1037,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (name[0] == '#') {
return new JS.InterpolatedExpression(name.substring(1));
} else {
- return new JSTemporary(e.name);
+ return new JSTemporary(name);
}
}
@@ -1183,7 +1107,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
// https://github.com/dart-lang/dev_compiler/commit/a212d59ad046085a626dd8d16881cdb8e8b9c3fa
if (type is! FunctionType || element is FunctionTypeAlias) {
var args = type.typeArguments;
- if (args.any((a) => a != rules.provider.dynamicType)) {
+ if (args.any((a) => a != types.dynamicType)) {
name = '$name\$';
typeArgs = args.map(_emitTypeName);
}
@@ -1212,62 +1136,52 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
return false;
}
- JS.Node _emitDSetIfDynamic(
- Expression target, SimpleIdentifier id, Expression rhs) {
- if (rules.isDynamicTarget(target)) {
- return js.call('dart.dput(#, #, #)', [
- _visit(target),
- js.string(id.name, "'"),
- _visit(rhs)
- ]);
- } else {
- return null;
- }
- }
-
@override
JS.Expression visitAssignmentExpression(AssignmentExpression node) {
var left = node.leftHandSide;
var right = node.rightHandSide;
if (node.operator.type == TokenType.EQ) return _emitSet(left, right);
- return _emitOpAssign(left, right, node.operator.lexeme[0], context: node);
+ return _emitOpAssign(
+ left, right, node.operator.lexeme[0], node.staticElement,
+ context: node);
}
- JSMetaLet _emitOpAssign(Expression left, Expression right, String op,
+ JSMetaLet _emitOpAssign(
+ Expression left, Expression right, String op, ExecutableElement element,
{Expression context}) {
// Desugar `x += y` as `x = x + y`, ensuring that if `x` has subexpressions
// (for example, x is IndexExpression) we evaluate those once.
var vars = {};
var lhs = _bindLeftHandSide(vars, left, context: context);
var inc = AstBuilder.binaryExpression(lhs, op, right);
- inc.staticType = rules.getStaticType(left);
+ inc.staticElement = element;
+ inc.staticType = getStaticType(left);
return new JSMetaLet(vars, [_emitSet(lhs, inc)]);
}
JS.Expression _emitSet(Expression lhs, Expression rhs) {
if (lhs is IndexExpression) {
- String code;
- var target = _getTarget(lhs);
- if (rules.isDynamicTarget(target)) {
- code = 'dart.dsetindex(#, #, #)';
- return js.call(code, [_visit(target), _visit(lhs.index), _visit(rhs)]);
- }
- return js.call('#.#(#, #)', [
- _visit(target),
- _emitMemberName('[]=', target: target),
- _visit(lhs.index),
- _visit(rhs)
- ]);
+ return _emitSend(_getTarget(lhs), '[]=', [lhs.index, rhs]);
}
+ Expression target = null;
+ SimpleIdentifier id;
if (lhs is PropertyAccess) {
- var result = _emitDSetIfDynamic(_getTarget(lhs), lhs.propertyName, rhs);
- if (result != null) return result;
+ target = _getTarget(lhs);
+ id = lhs.propertyName;
} else if (lhs is PrefixedIdentifier) {
- // TODO(vsm): Is this the right code if the prefix is a library?
- var result = _emitDSetIfDynamic(lhs.prefix, lhs.identifier, rhs);
- if (result != null) return result;
+ target = lhs.prefix;
+ id = lhs.identifier;
}
+
+ if (target != null && rules.isDynamicTarget(target)) {
+ return js.call('dart.$DPUT(#, #, #)', [
+ _visit(target),
+ _emitMemberName(id.name, type: getStaticType(target)),
+ _visit(rhs)
+ ]);
+ }
+
return _visit(rhs).toAssignExpression(_visit(lhs));
}
@@ -1299,33 +1213,32 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var result = _emitForeignJS(node);
if (result != null) return result;
- if (rules.isDynamicCall(node.methodName)) {
- var args = _visit(node.argumentList);
- if (target != null) {
- return js.call('dart.dsend(#, #, #)', [
- _visit(target),
- js.string(node.methodName.name, "'"),
- args
- ]);
+ // TODO(jmesserly): if we try to call a getter returning a function with
+ // a call method, we don't generate the `.call` correctly.
+ String code;
+ if (target == null) {
+ if (rules.isDynamicCall(node.methodName)) {
+ code = 'dart.$DCALL(#, #)';
} else {
- return js.call('dart.dcall(#, #)', [_visit(node.methodName), args]);
+ code = '#(#)';
}
+ return js.call(
+ code, [_visit(node.methodName), _visit(node.argumentList)]);
}
- // TODO(jmesserly): if this resolves to a getter returning a function with
- // a call method, we don't generate the `.call` correctly.
-
- var targetJs;
- if (target != null) {
- targetJs = js.call('#.#', [
- _visit(target),
- _emitMemberName(node.methodName.name, target: target)
- ]);
+ // TODO(jmesserly): if the methodName resolves statically but the call is
+ // dynamic (e.g. `obj.method` is resolved to a field of type `Function`), we
+ // could generate call(#.#, #). Not sure if that's worth it.
+ if (rules.isDynamicCall(node.methodName)) {
+ code = 'dart.$DSEND(#, #, #)';
} else {
- targetJs = _visit(node.methodName);
+ code = '#.#(#)';
}
-
- return js.call('#(#)', [targetJs, _visit(node.argumentList)]);
+ return js.call(code, [
+ _visit(target),
+ _emitMemberName(node.methodName.name, type: getStaticType(target)),
+ _visit(node.argumentList)
+ ]);
}
/// Emits code for the `JS(...)` builtin.
@@ -1352,7 +1265,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
FunctionExpressionInvocation node) {
var code;
if (rules.isDynamicCall(node.function)) {
- code = 'dart.dcall(#, #)';
+ code = 'dart.$DCALL(#, #)';
} else {
code = '#(#)';
}
@@ -1518,8 +1431,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
visitConstructorName(ConstructorName node) {
var typeName = _visit(node.type);
if (node.name != null) {
- return js.call(
- '#.#', [typeName, _emitMemberName(node.name.name, isStatic: true)]);
+ return js.call('#.#', [typeName, _emitMemberName(node.name.name)]);
}
return typeName;
}
@@ -1553,14 +1465,13 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
bool _isNonNullableExpression(Expression expr) {
// If the type is non-nullable, no further checking needed.
- if (rules.isNonNullableType(rules.getStaticType(expr))) return true;
+ if (rules.isNonNullableType(getStaticType(expr))) return true;
// TODO(vsm): Revisit whether we really need this when we get
// better non-nullability in the type system.
- if (expr is Literal && expr is! NullLiteral) {
- return true;
- }
+ if (expr is Literal && expr is! NullLiteral) return true;
+ if (expr is IsExpression) return true;
if (expr is ParenthesizedExpression) {
return _isNonNullableExpression(expr.expression);
}
@@ -1569,11 +1480,11 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
}
DartType type = null;
if (expr is BinaryExpression) {
- type = rules.getStaticType(expr.leftOperand);
+ type = getStaticType(expr.leftOperand);
} else if (expr is PrefixExpression) {
- type = rules.getStaticType(expr.operand);
+ type = getStaticType(expr.operand);
} else if (expr is PostfixExpression) {
- type = rules.getStaticType(expr.operand);
+ type = getStaticType(expr.operand);
}
if (type != null && typeIsPrimitiveInJS(type)) {
return true;
@@ -1617,8 +1528,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
var op = node.operator;
var left = node.leftOperand;
var right = node.rightOperand;
- var leftType = rules.getStaticType(left);
- var rightType = rules.getStaticType(right);
+ var leftType = getStaticType(left);
+ var rightType = getStaticType(right);
var code;
if (op.type.isEqualityOperator) {
@@ -1631,7 +1542,9 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
code = '${bang}dart.equals(#, #)';
}
return js.call(code, [_visit(left), _visit(right)]);
- } else if (binaryOperationIsPrimitive(leftType, rightType)) {
+ }
+
+ if (binaryOperationIsPrimitive(leftType, rightType)) {
// special cases where we inline the operation
// these values are assumed to be non-null (determined by the checker)
// TODO(jmesserly): it would be nice to just inline the method from core,
@@ -1644,40 +1557,15 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
code = '# $op #';
}
return js.call(code, [notNull(left), notNull(right)]);
- } else {
- var opString = js.string(op.lexeme, "'");
- if (rules.isDynamicTarget(left)) {
- // dynamic dispatch
- return js.call(
- 'dart.dsend(#, #, #)', [_visit(left), opString, _visit(right)]);
- } else if (_isJSBuiltinType(leftType)) {
- // TODO(jmesserly): we'd get better readability from the static-dispatch
- // pattern below. Consider:
- //
- // "hello"['+']"world"
- // vs
- // core.String['+']("hello", "world")
- //
- // Infix notation is much more readable, which is a bit part of why
- // C# added its extension methods feature. However this would require
- // adding these methods to String.prototype/Number.prototype in JS.
- return js.call('#.#(#, #)', [
- _emitTypeName(leftType),
- opString,
- _visit(left),
- _visit(right)
- ]);
- } else {
- // Generic static-dispatch, user-defined operator code path.
- return js.call('#.#(#)', [_visit(left), opString, _visit(right)]);
- }
}
+
+ return _emitSend(left, op.lexeme, [right]);
}
/// If the type [t] is [int] or [double], returns [num].
/// Otherwise returns [t].
DartType _canonicalizeNumTypes(DartType t) {
- var numType = rules.provider.numType;
+ var numType = types.numType;
if (t is InterfaceType && t.superclass == numType) return numType;
return t;
}
@@ -1685,8 +1573,8 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
bool _canUsePrimitiveEquality(Expression left, Expression right) {
if (_isNull(left) || _isNull(right)) return true;
- var leftType = _canonicalizeNumTypes(rules.getStaticType(left));
- var rightType = _canonicalizeNumTypes(rules.getStaticType(right));
+ var leftType = _canonicalizeNumTypes(getStaticType(left));
+ var rightType = _canonicalizeNumTypes(getStaticType(right));
return _isJSBuiltinType(leftType) && leftType == rightType;
}
@@ -1709,46 +1597,6 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
bool _isTemporary(Element node) => node.nameOffset == -1;
- /// Desugars postfix increment.
- ///
- /// In the general case [expr] can be one of [IndexExpression],
- /// [PrefixExpression] or [PropertyAccess] and we need to
- /// ensure sub-expressions are evaluated once.
- ///
- /// We also need to ensure we can return the original value of the expression,
- /// and that it is only evaluated once.
- ///
- /// We desugar this using let*.
- ///
- /// For example, `expr1[expr2]++` can be transformed to this:
- ///
- /// // psuedocode mix of Scheme and JS:
- /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t })
- ///
- /// The [JSMetaLet] nodes automatically simplify themselves if they can.
- /// For example, if the result value is not used, then `t` goes away.
- JSMetaLet _emitPostfixIncrement(Expression expr, Token op) {
- var type = rules.getStaticType(expr);
- assert(type != null);
-
- // Handle the left hand side, to ensure each of its subexpressions are
- // evaluated only once.
- var vars = {};
- var left = _bindLeftHandSide(vars, expr, context: expr);
-
- // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value
- // and `x1` is the new value for `x`.
- var x = _bindValue(vars, 'x', left, context: expr);
-
- var one = AstBuilder.integerLiteral(1);
- one.staticType = rules.provider.intType;
- var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one);
- increment.staticType = type;
-
- var body = [_emitSet(left, increment), _visit(x)];
- return new JSMetaLet(vars, body, statelessResult: true);
- }
-
/// Returns a new expression, which can be be used safely *once* on the
/// left hand side, and *once* on the right side of an assignment.
/// For example: `expr1[expr2] += y` can be compiled as
@@ -1809,12 +1657,30 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
return t;
}
+ /// Desugars postfix increment.
+ ///
+ /// In the general case [expr] can be one of [IndexExpression],
+ /// [PrefixExpression] or [PropertyAccess] and we need to
+ /// ensure sub-expressions are evaluated once.
+ ///
+ /// We also need to ensure we can return the original value of the expression,
+ /// and that it is only evaluated once.
+ ///
+ /// We desugar this using let*.
+ ///
+ /// For example, `expr1[expr2]++` can be transformed to this:
+ ///
+ /// // psuedocode mix of Scheme and JS:
+ /// (let* (x1=expr1, x2=expr2, t=expr1[expr2]) { x1[x2] = t + 1; t })
+ ///
+ /// The [JSMetaLet] nodes automatically simplify themselves if they can.
+ /// For example, if the result value is not used, then `t` goes away.
@override
JS.Expression visitPostfixExpression(PostfixExpression node) {
var op = node.operator;
var expr = node.operand;
- var dispatchType = rules.getStaticType(expr);
+ var dispatchType = getStaticType(expr);
if (unaryOperationIsPrimitive(dispatchType)) {
if (_isNonNullableExpression(expr)) {
return js.call('#$op', _visit(expr));
@@ -1822,16 +1688,31 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
}
assert(op.lexeme == '++' || op.lexeme == '--');
- return _emitPostfixIncrement(expr, op);
+
+ // Handle the left hand side, to ensure each of its subexpressions are
+ // evaluated only once.
+ var vars = {};
+ var left = _bindLeftHandSide(vars, expr, context: expr);
+
+ // Desugar `x++` as `(x1 = x0 + 1, x0)` where `x0` is the original value
+ // and `x1` is the new value for `x`.
+ var x = _bindValue(vars, 'x', left, context: expr);
+
+ var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
+ var increment = AstBuilder.binaryExpression(x, op.lexeme[0], one)
+ ..staticElement = node.staticElement
+ ..staticType = getStaticType(expr);
+
+ var body = [_emitSet(left, increment), _visit(x)];
+ return new JSMetaLet(vars, body, statelessResult: true);
}
@override
JS.Expression visitPrefixExpression(PrefixExpression node) {
- return _emitPrefixExpression(node.operator, node.operand);
- }
+ var op = node.operator;
+ var expr = node.operand;
- JS.Expression _emitPrefixExpression(Token op, Expression expr) {
- var dispatchType = rules.getStaticType(expr);
+ var dispatchType = getStaticType(expr);
if (unaryOperationIsPrimitive(dispatchType)) {
if (_isNonNullableExpression(expr)) {
return js.call('$op#', _visit(expr));
@@ -1851,23 +1732,12 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
// Increment or decrement requires expansion.
// Desugar `++x` as `x = x + 1`, ensuring that if `x` has subexpressions
// (for example, x is IndexExpression) we evaluate those once.
- var one = AstBuilder.integerLiteral(1)
- ..staticType = rules.provider.intType;
- return _emitOpAssign(expr, one, op.lexeme[0], context: expr);
+ var one = AstBuilder.integerLiteral(1)..staticType = types.intType;
+ return _emitOpAssign(expr, one, op.lexeme[0], node.staticElement,
+ context: expr);
}
- // Call the operator
- var opString = _emitMemberName(op.lexeme, unary: true);
- if (rules.isDynamicTarget(expr)) {
- // dynamic dispatch
- return js.call('dart.dsend(#, #)', [_visit(expr), opString]);
- } else if (_isJSBuiltinType(dispatchType)) {
- return js.call(
- '#.#(#)', [_emitTypeName(dispatchType), opString, _visit(expr)]);
- } else {
- // Generic static-dispatch, user-defined operator code path.
- return js.call('#.#()', [_visit(expr), opString]);
- }
+ return _emitSend(expr, op.lexeme[0], []);
}
// Cascades can contain [IndexExpression], [MethodInvocation] and
@@ -1906,42 +1776,57 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
if (node.prefix.staticElement is PrefixElement) {
return _visit(node.identifier);
} else {
- return _emitGet(node.prefix, node.identifier);
+ return _emitGet(node.prefix, node.identifier.name);
}
}
@override
visitPropertyAccess(PropertyAccess node) =>
- _emitGet(_getTarget(node), node.propertyName);
+ _emitGet(_getTarget(node), node.propertyName.name);
/// Shared code for [PrefixedIdentifier] and [PropertyAccess].
- _emitGet(Expression target, SimpleIdentifier name) {
+ JS.Expression _emitGet(Expression target, String memberName) {
+ var name = _emitMemberName(memberName, type: getStaticType(target));
if (rules.isDynamicTarget(target)) {
- return js.call(
- 'dart.dload(#, #)', [_visit(target), js.string(name.name, "'")]);
+ return js.call('dart.$DLOAD(#, #)', [_visit(target), name]);
} else {
- var e = name.staticElement;
- var ret = js.call('#.#', [
+ return js.call('#.#', [_visit(target), name]);
+ }
+ }
+
+ JS.Expression _emitSend(
+ Expression target, String name, List<Expression> args) {
+ var type = getStaticType(target);
+ var memberName = _emitMemberName(name, unary: args.isEmpty, type: type);
+ if (rules.isDynamicTarget(target)) {
+ // dynamic dispatch
+ var dynamicHelper = const {'[]': DINDEX, '[]=': DSETINDEX}[name];
+ if (dynamicHelper != null) {
+ return js.call(
+ 'dart.$dynamicHelper(#, #)', [_visit(target), _visitList(args)]);
+ }
+ return js.call('dart.$DSEND(#, #, #)', [
+ _visit(target),
+ memberName,
+ _visitList(args)
+ ]);
+ }
+ if (_isJSBuiltinType(type)) {
+ // static call pattern for bultins.
+ return js.call('#.#(#, #)', [
+ _emitTypeName(type),
+ memberName,
_visit(target),
- _emitMemberName(name.name,
- isStatic: e is ExecutableElement && e.isStatic, target: target)
+ _visitList(args)
]);
- return ret;
}
+ // Generic dispatch to a statically known method.
+ return js.call('#.#(#)', [_visit(target), memberName, _visitList(args)]);
}
@override
visitIndexExpression(IndexExpression node) {
- var target = _getTarget(node);
- if (rules.isDynamicTarget(target)) {
- return js.call('dart.dindex(#, #)', [_visit(target), _visit(node.index)]);
- }
-
- return js.call('#.#(#)', [
- _visit(target),
- _emitMemberName('[]', target: target),
- _visit(node.index)
- ]);
+ return _emitSend(_getTarget(node), '[]', [node.index]);
}
/// Gets the target of a [PropertyAccess] or [IndexExpression].
@@ -2043,7 +1928,7 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
// Special case for a single catch.
_catchParameter = clauses.single.exceptionParameter;
} else {
- _catchParameter = _createTemporary('e', rules.provider.dynamicType);
+ _catchParameter = _createTemporary('e', types.dynamicType);
}
JS.Statement catchBody = js.statement('throw #;', _visit(_catchParameter));
@@ -2319,37 +2204,72 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
/// helper, that checks for null. The user defined method is called '=='.
///
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;
- }
+ {DartType type, bool unary: false, bool isStatic: false}) {
if (name.startsWith('_')) {
if (_privateNames.add(name)) _pendingPrivateNames.add(name);
return new JSTemporary(name);
}
- return _propertyName(_jsMemberName(name, unary: unary, isStatic: isStatic));
- }
+ // Check for extension method:
+ var extLibrary = _findExtensionLibrary(name, type);
+
+ if (name == '[]') {
+ name = 'get';
+ } else if (name == '[]=') {
+ name = 'set';
+ } else if (name == '-' && unary) {
+ name = 'unary-';
+ }
- 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.
- return '$name*';
+ name = '$name*';
}
- return name;
+
+ if (extLibrary != null) {
+ return js.call('#.#', [
+ _libraryName(extLibrary),
+ _propertyName(_addExtensionMethodName(name, extLibrary))
+ ]);
+ }
+
+ return _propertyName(name);
}
- JS.LiteralString _emitExtensionMethodName(String name) =>
- js.string(_extensionMethodName(name), "'");
+ LibraryElement _findExtensionLibrary(String name, DartType type) {
+ if (type is! InterfaceType) return null;
- String _extensionMethodName(String name) => '\$${_jsMemberName(name)}';
+ 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;
+ }
+
+ String _addExtensionMethodName(String name, LibraryElement extLibrary) {
+ var extensionMethodName = '\$$name';
+ if (extLibrary == 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(extensionMethodName)) {
+ _pendingExtensionMethodNames.add(extensionMethodName);
+ _addExport(extensionMethodName);
+ }
+ }
+ return extensionMethodName;
+ }
bool _externalOrNative(node) =>
node.externalKeyword != null || _functionBody(node) is NativeFunctionBody;
@@ -2365,19 +2285,52 @@ class JSCodegenVisitor extends GeneralizingAstVisitor with ConversionVisitor {
return new JS.Identifier(jsLibraryName(library));
}
+ DartType getStaticType(Expression e) => rules.getStaticType(e);
+
static bool _needsImplicitThis(Element e) =>
e is PropertyAccessorElement && !e.variable.isStatic ||
e is ClassMemberElement && !e.isStatic && e is! ConstructorElement;
+
+ static const DPUT = 'dput';
Jacob 2015/04/14 21:41:06 nit: no idea if dart has an official convention bu
Jennifer Messerly 2015/04/14 22:17:19 Hmm. I guess I absorbed a rule at some point from
Jacob 2015/04/14 22:25:00 I don't care deeply about it but I'd personally ma
Jennifer Messerly 2015/04/14 22:45:28 OK. Moved to top, added comment.
+ static const DLOAD = 'dload';
+ static const DINDEX = 'dindex';
+ static const DSETINDEX = 'dsetindex';
+ static const DCALL = 'dcall';
+ static const DSEND = 'dsend';
}
class JSGenerator extends CodeGenerator {
final JSCodeOptions options;
+ /// 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(String outDir, Uri root, TypeRules rules, this.options)
- : super(outDir, root, rules);
+ : super(outDir, root, rules) {
+
+ // 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);
+ }
+ }
+ }
+
+ TypeProvider get types => rules.provider;
String generateLibrary(LibraryUnit unit, LibraryInfo info) {
- var jsTree = new JSCodegenVisitor(info, rules).emitLibrary(unit);
+ var jsTree =
+ new JSCodegenVisitor(info, rules, _extensionMethods).emitLibrary(unit);
var outputPath = path.join(outDir, jsOutputPath(info, root));
new Directory(path.dirname(outputPath)).createSync(recursive: true);
« no previous file with comments | « lib/runtime/dart_runtime.js ('k') | lib/src/utils.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698