Skip to content

Commit 430e8f1

Browse files
author
John Messerly
committed
Partial fix for call methods #542
This fixes the generated code to support these. Subtype checks are not fixed yet. R=vsm@google.com Review URL: https://codereview.chromium.org/2069903002 .
1 parent 715c845 commit 430e8f1

File tree

4 files changed

+142
-31
lines changed

4 files changed

+142
-31
lines changed

pkg/dev_compiler/lib/runtime/dart_sdk.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -837,8 +837,8 @@ dart_library.library('dart_sdk', null, /* Imports */[
837837
dart.defineNamedConstructor = function(clazz, name) {
838838
let proto = clazz.prototype;
839839
let initMethod = proto[name];
840-
let ctor = function() {
841-
return initMethod.apply(this, arguments);
840+
let ctor = function(...args) {
841+
initMethod.apply(this, args);
842842
};
843843
ctor.prototype = proto;
844844
dart.defineProperty(clazz, name, {value: ctor, configurable: true});
@@ -934,6 +934,16 @@ dart_library.library('dart_sdk', null, /* Imports */[
934934
derived.prototype[dart._extensionType] = derived;
935935
derived.prototype.__proto__ = base.prototype;
936936
};
937+
dart.callableClass = function(callableCtor, classExpr) {
938+
callableCtor.prototype = classExpr.prototype;
939+
callableCtor.prototype.constructor = callableCtor;
940+
callableCtor.__proto__ = classExpr.__proto__;
941+
return callableCtor;
942+
};
943+
dart.defineNamedConstructorCallable = function(clazz, name, ctor) {
944+
ctor.prototype = clazz.prototype;
945+
dart.defineProperty(clazz, name, {value: ctor, configurable: true});
946+
};
937947
dart.throwCastError = function(object, actual, type) {
938948
dart.throw(new _js_helper.CastErrorImplementation(object, dart.typeName(actual), dart.typeName(type)));
939949
};

pkg/dev_compiler/lib/src/compiler/code_generator.dart

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -701,13 +701,27 @@ class CodeGenerator extends GeneralizingAstVisitor
701701
var fields = <FieldDeclaration>[];
702702
var staticFields = <FieldDeclaration>[];
703703
var methods = <MethodDeclaration>[];
704+
705+
// True if a "call" method or getter exists. This can also be
706+
// "noSuchMethod" method, because nSM could implement "call".
707+
bool isCallable = false;
704708
for (var member in node.members) {
705709
if (member is ConstructorDeclaration) {
706710
ctors.add(member);
707711
} else if (member is FieldDeclaration) {
708712
(member.isStatic ? staticFields : fields).add(member);
709713
} else if (member is MethodDeclaration) {
710714
methods.add(member);
715+
var name = member.name.name;
716+
if (name == 'call' && !member.isSetter) {
717+
isCallable = true;
718+
} else if (name == 'noSuchMethod' &&
719+
!member.isGetter &&
720+
!member.isSetter &&
721+
// Exclude SDK because we know they don't use nSM to implement call.
722+
!classElem.library.source.isInSystemLibrary) {
723+
isCallable = true;
724+
}
711725
}
712726
}
713727

@@ -738,15 +752,15 @@ class CodeGenerator extends GeneralizingAstVisitor
738752
_emitSuperHelperSymbols(_superHelperSymbols, body);
739753

740754
// Emit the class, e.g. `core.Object = class Object { ... }`
741-
_defineClass(classElem, className, classExpr, body);
755+
_defineClass(classElem, className, classExpr, isCallable, body);
742756

743757
// Emit things that come after the ES6 `class ... { ... }`.
744758
var jsPeerName = _getJSPeerName(classElem);
745759
_setBaseClass(classElem, className, jsPeerName, body);
746760

747761
_emitClassTypeTests(classElem, className, body);
748762

749-
_defineNamedConstructors(ctors, body, className);
763+
_defineNamedConstructors(ctors, body, className, isCallable);
750764
_emitVirtualFieldSymbols(virtualFieldSymbols, body);
751765
_emitClassSignature(methods, classElem, ctors, extensions, className, body);
752766
_defineExtensionMembers(extensions, className, body);
@@ -764,6 +778,36 @@ class CodeGenerator extends GeneralizingAstVisitor
764778
return _statement(body);
765779
}
766780

781+
/// Emits code to support a class with a "call" method and an unnamed
782+
/// constructor.
783+
///
784+
/// This ensures instances created by the unnamed constructor are functions.
785+
/// Named constructors are handled elsewhere, see [_defineNamedConstructors].
786+
JS.Expression _emitCallableClass(JS.ClassExpression classExpr,
787+
ConstructorElement unnamedCtor) {
788+
var ctor = new JS.NamedFunction(
789+
classExpr.name, _emitCallableClassConstructor(unnamedCtor));
790+
791+
// Name the constructor function the same as the class.
792+
return js.call('dart.callableClass(#, #)', [ctor, classExpr]);
793+
}
794+
795+
JS.Fun _emitCallableClassConstructor(
796+
ConstructorElement ctor) {
797+
798+
return js.call(
799+
r'''function (...args) {
800+
const self = this;
801+
function call(...args) {
802+
return self.call.apply(self, args);
803+
}
804+
call.__proto__ = this.__proto__;
805+
call.#.apply(call, args);
806+
return call;
807+
}''',
808+
[_constructorName(ctor)]);
809+
}
810+
767811
void _emitClassTypeTests(ClassElement classElem, JS.Expression className,
768812
List<JS.Statement> body) {
769813
if (classElem == objectClass) {
@@ -978,12 +1022,26 @@ class CodeGenerator extends GeneralizingAstVisitor
9781022
}
9791023
}
9801024

981-
void _defineClass(ClassElement classElem, JS.Expression className,
982-
JS.ClassExpression classExpr, List<JS.Statement> body) {
1025+
void _defineClass(
1026+
ClassElement classElem,
1027+
JS.Expression className,
1028+
JS.ClassExpression classExpr,
1029+
bool isCallable,
1030+
List<JS.Statement> body) {
1031+
JS.Expression callableClass;
1032+
if (isCallable && classElem.unnamedConstructor != null) {
1033+
callableClass = _emitCallableClass(
1034+
classExpr, classElem.unnamedConstructor);
1035+
}
1036+
9831037
if (classElem.typeParameters.isNotEmpty) {
984-
body.add(new JS.ClassDeclaration(classExpr));
1038+
if (callableClass != null) {
1039+
body.add(js.statement('const # = #;', [classExpr.name, callableClass]));
1040+
} else {
1041+
body.add(new JS.ClassDeclaration(classExpr));
1042+
}
9851043
} else {
986-
body.add(js.statement('# = #;', [className, classExpr]));
1044+
body.add(js.statement('# = #;', [className, callableClass ?? classExpr]));
9871045
}
9881046
}
9891047

@@ -1400,12 +1458,23 @@ class CodeGenerator extends GeneralizingAstVisitor
14001458
}
14011459
}
14021460

1403-
void _defineNamedConstructors(List<ConstructorDeclaration> ctors,
1404-
List<JS.Statement> body, JS.Expression className) {
1461+
void _defineNamedConstructors(
1462+
List<ConstructorDeclaration> ctors,
1463+
List<JS.Statement> body,
1464+
JS.Expression className,
1465+
bool isCallable) {
1466+
var code = isCallable
1467+
? 'dart.defineNamedConstructorCallable(#, #, #);'
1468+
: 'dart.defineNamedConstructor(#, #)';
1469+
14051470
for (ConstructorDeclaration member in ctors) {
14061471
if (member.name != null && member.factoryKeyword == null) {
1407-
body.add(js.statement('dart.defineNamedConstructor(#, #);',
1408-
[className, _constructorName(member.element)]));
1472+
var args = [className, _constructorName(member.element)];
1473+
if (isCallable) {
1474+
args.add(_emitCallableClassConstructor(member.element));
1475+
}
1476+
1477+
body.add(js.statement(code, args));
14091478
}
14101479
}
14111480
}

pkg/dev_compiler/test/browser/language_tests.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,11 @@
6060
'bool_test': skip_fail,
6161
'bound_closure_equality_test': skip_fail,
6262
'branch_canonicalization_test': skip_fail, // JS bit operations truncate to 32 bits.
63-
'call_closurization_test': skip_fail,
64-
'call_function_apply_test': skip_fail,
65-
'call_operator_test': skip_fail,
66-
'call_property_test': skip_fail,
67-
'call_test': skip_fail,
68-
'call_this_test': skip_fail,
69-
'call_through_null_getter_test': skip_fail,
70-
'call_with_no_such_method_test': skip_fail,
63+
'call_closurization_test': fail, // Functions do not expose a "call" method.
64+
'call_function_apply_test': fail, // Function.apply not really implemented.
65+
'call_test': fail, // Functions do not expose a "call" method.
66+
'call_through_null_getter_test': fail, // null errors are not converted to NoSuchMethodErrors.
67+
'call_with_no_such_method_test': fail, // Function.apply not really implemented.
7168
'canonical_const2_test': skip_fail,
7269
'canonical_const_test': skip_fail,
7370
'cascade_precedence_test': skip_fail,
@@ -164,13 +161,13 @@
164161
'function_subtype_bound_closure5_test': skip_fail,
165162
'function_subtype_bound_closure5a_test': skip_fail,
166163
'function_subtype_bound_closure6_test': skip_fail,
167-
'function_subtype_call0_test': skip_fail,
168-
'function_subtype_call1_test': skip_fail,
169-
'function_subtype_call2_test': skip_fail,
170-
'function_subtype_cast0_test': skip_fail,
171-
'function_subtype_cast1_test': skip_fail,
172-
'function_subtype_cast2_test': skip_fail,
173-
'function_subtype_cast3_test': skip_fail,
164+
'function_subtype_call0_test': fail, // Strong mode "is" rejects some type tests.
165+
'function_subtype_call1_test': fail,
166+
'function_subtype_call2_test': fail,
167+
'function_subtype_cast0_test': fail,
168+
'function_subtype_cast1_test': fail,
169+
'function_subtype_cast2_test': fail,
170+
'function_subtype_cast3_test': fail,
174171
'function_subtype_factory0_test': skip_fail,
175172
'function_subtype_inline0_test': skip_fail,
176173
'function_subtype_local0_test': skip_fail,
@@ -197,7 +194,6 @@
197194
'function_type_alias4_test': skip_fail,
198195
'function_type_alias6_test_none_multi': skip_fail,
199196
'function_type_alias_test': skip_fail,
200-
'function_type_call_getter_test': skip_fail,
201197
'gc_test': skip_fail,
202198
'generic_field_mixin2_test': skip_fail,
203199
'generic_field_mixin3_test': skip_fail,

pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/classes.dart

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,20 +256,22 @@ setSignature(f, signature) => JS('', '''(() => {
256256

257257
hasMethod(obj, name) => JS('', '$getMethodType($obj, $name) !== void 0');
258258

259-
///
260259
/// Given a class and an initializer method name, creates a constructor
261-
/// function with the same name. For example `new SomeClass.name(args)`.
260+
/// function with the same name.
262261
///
262+
/// After we define the named constructor, the class can be constructed with
263+
/// `new SomeClass.name(args)`.
263264
defineNamedConstructor(clazz, name) => JS('', '''(() => {
264265
let proto = $clazz.prototype;
265266
let initMethod = proto[$name];
266-
let ctor = function() { return initMethod.apply(this, arguments); };
267+
let ctor = function(...args) { initMethod.apply(this, args); };
267268
ctor.prototype = proto;
268269
// Use defineProperty so we don't hit a property defined on Function,
269270
// like `caller` and `arguments`.
270271
$defineProperty($clazz, $name, { value: ctor, configurable: true });
271272
})()''');
272273

274+
273275
final _extensionType = JS('', 'Symbol("extensionType")');
274276

275277
getExtensionType(obj) => JS('', '#[#]', obj, _extensionType);
@@ -438,3 +440,37 @@ setExtensionBaseClass(derived, base) {
438440
// Link the prototype objects
439441
JS('', '#.prototype.__proto__ = #.prototype', derived, base);
440442
}
443+
444+
/// Given a special constructor function that creates a function instances,
445+
/// and a class with a `call` method, merge them so the constructor function
446+
/// will have the correct methods and prototype.
447+
///
448+
/// For example:
449+
///
450+
/// lib.Foo = dart.callableClass(
451+
/// function Foo { function call(...args) { ... } ... return call; },
452+
/// class Foo { call(x) { ... } });
453+
/// ...
454+
/// let f = new lib.Foo();
455+
/// f(42);
456+
callableClass(callableCtor, classExpr) {
457+
JS('', '#.prototype = #.prototype', callableCtor, classExpr);
458+
// We're not going to use the original class, so we can safely replace it to
459+
// point at this constructor for the runtime type information.
460+
JS('', '#.prototype.constructor = #', callableCtor, callableCtor);
461+
JS('', '#.__proto__ = #.__proto__', callableCtor, classExpr);
462+
return callableCtor;
463+
}
464+
465+
/// Given a class and an initializer method name and a call method, creates a
466+
/// constructor function with the same name.
467+
///
468+
/// For example it can be called with `new SomeClass.name(args)`.
469+
///
470+
/// The constructor
471+
defineNamedConstructorCallable(clazz, name, ctor) => JS('', '''(() => {
472+
ctor.prototype = $clazz.prototype;
473+
// Use defineProperty so we don't hit a property defined on Function,
474+
// like `caller` and `arguments`.
475+
$defineProperty($clazz, $name, { value: ctor, configurable: true });
476+
})()''');

0 commit comments

Comments
 (0)