Index: lib/src/compiler/code_generator.dart |
diff --git a/lib/src/compiler/code_generator.dart b/lib/src/compiler/code_generator.dart |
index d0bdf2043190b9a91a642c7f84ea0751b1be688b..b04accc3c6d593fedc0d3257bed286404e5e4159 100644 |
--- a/lib/src/compiler/code_generator.dart |
+++ b/lib/src/compiler/code_generator.dart |
@@ -701,6 +701,10 @@ class CodeGenerator extends GeneralizingAstVisitor |
var fields = <FieldDeclaration>[]; |
var staticFields = <FieldDeclaration>[]; |
var methods = <MethodDeclaration>[]; |
+ |
+ // True if a "call" method or getter exists. This can also be |
+ // "noSuchMethod" method, because nSM could implement "call". |
+ bool isCallable = false; |
for (var member in node.members) { |
if (member is ConstructorDeclaration) { |
ctors.add(member); |
@@ -708,6 +712,16 @@ class CodeGenerator extends GeneralizingAstVisitor |
(member.isStatic ? staticFields : fields).add(member); |
} else if (member is MethodDeclaration) { |
methods.add(member); |
+ var name = member.name.name; |
+ if (name == 'call' && !member.isSetter) { |
+ isCallable = true; |
+ } else if (name == 'noSuchMethod' && |
+ !member.isGetter && |
+ !member.isSetter && |
+ // Exclude SDK because we know they don't use nSM to implement call. |
+ !classElem.library.source.isInSystemLibrary) { |
+ isCallable = true; |
+ } |
} |
} |
@@ -738,7 +752,7 @@ class CodeGenerator extends GeneralizingAstVisitor |
_emitSuperHelperSymbols(_superHelperSymbols, body); |
// Emit the class, e.g. `core.Object = class Object { ... }` |
- _defineClass(classElem, className, classExpr, body); |
+ _defineClass(classElem, className, classExpr, isCallable, body); |
// Emit things that come after the ES6 `class ... { ... }`. |
var jsPeerName = _getJSPeerName(classElem); |
@@ -746,7 +760,7 @@ class CodeGenerator extends GeneralizingAstVisitor |
_emitClassTypeTests(classElem, className, body); |
- _defineNamedConstructors(ctors, body, className); |
+ _defineNamedConstructors(ctors, body, className, isCallable); |
_emitVirtualFieldSymbols(virtualFieldSymbols, body); |
_emitClassSignature(methods, classElem, ctors, extensions, className, body); |
_defineExtensionMembers(extensions, className, body); |
@@ -764,6 +778,36 @@ class CodeGenerator extends GeneralizingAstVisitor |
return _statement(body); |
} |
+ /// Emits code to support a class with a "call" method and an unnamed |
+ /// constructor. |
+ /// |
+ /// This ensures instances created by the unnamed constructor are functions. |
+ /// Named constructors are handled elsewhere, see [_defineNamedConstructors]. |
+ JS.Expression _emitCallableClass(JS.ClassExpression classExpr, |
+ ConstructorElement unnamedCtor) { |
+ var ctor = new JS.NamedFunction( |
+ classExpr.name, _emitCallableClassConstructor(unnamedCtor)); |
+ |
+ // Name the constructor function the same as the class. |
+ return js.call('dart.callableClass(#, #)', [ctor, classExpr]); |
+ } |
+ |
+ JS.Fun _emitCallableClassConstructor( |
+ ConstructorElement ctor) { |
+ |
+ return js.call( |
+ r'''function (...args) { |
+ const self = this; |
+ function call(...args) { |
+ return self.call.apply(self, args); |
+ } |
+ call.__proto__ = this.__proto__; |
+ call.#.apply(call, args); |
+ return call; |
+ }''', |
+ [_constructorName(ctor)]); |
+ } |
+ |
void _emitClassTypeTests(ClassElement classElem, JS.Expression className, |
List<JS.Statement> body) { |
if (classElem == objectClass) { |
@@ -978,12 +1022,26 @@ class CodeGenerator extends GeneralizingAstVisitor |
} |
} |
- void _defineClass(ClassElement classElem, JS.Expression className, |
- JS.ClassExpression classExpr, List<JS.Statement> body) { |
+ void _defineClass( |
+ ClassElement classElem, |
+ JS.Expression className, |
+ JS.ClassExpression classExpr, |
+ bool isCallable, |
+ List<JS.Statement> body) { |
+ JS.Expression callableClass; |
+ if (isCallable && classElem.unnamedConstructor != null) { |
+ callableClass = _emitCallableClass( |
+ classExpr, classElem.unnamedConstructor); |
+ } |
+ |
if (classElem.typeParameters.isNotEmpty) { |
- body.add(new JS.ClassDeclaration(classExpr)); |
+ if (callableClass != null) { |
+ body.add(js.statement('const # = #;', [classExpr.name, callableClass])); |
+ } else { |
+ body.add(new JS.ClassDeclaration(classExpr)); |
+ } |
} else { |
- body.add(js.statement('# = #;', [className, classExpr])); |
+ body.add(js.statement('# = #;', [className, callableClass ?? classExpr])); |
} |
} |
@@ -1400,12 +1458,23 @@ class CodeGenerator extends GeneralizingAstVisitor |
} |
} |
- void _defineNamedConstructors(List<ConstructorDeclaration> ctors, |
- List<JS.Statement> body, JS.Expression className) { |
+ void _defineNamedConstructors( |
+ List<ConstructorDeclaration> ctors, |
+ List<JS.Statement> body, |
+ JS.Expression className, |
+ bool isCallable) { |
+ var code = isCallable |
+ ? 'dart.defineNamedConstructorCallable(#, #, #);' |
+ : 'dart.defineNamedConstructor(#, #)'; |
+ |
for (ConstructorDeclaration member in ctors) { |
if (member.name != null && member.factoryKeyword == null) { |
- body.add(js.statement('dart.defineNamedConstructor(#, #);', |
- [className, _constructorName(member.element)])); |
+ var args = [className, _constructorName(member.element)]; |
+ if (isCallable) { |
+ args.add(_emitCallableClassConstructor(member.element)); |
+ } |
+ |
+ body.add(js.statement(code, args)); |
} |
} |
} |