Skip to content

Commit

Permalink
Partial fix for call methods #542
Browse files Browse the repository at this point in the history
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 .
  • Loading branch information
John Messerly committed Jun 16, 2016
1 parent 715c845 commit 430e8f1
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 31 deletions.
14 changes: 12 additions & 2 deletions pkg/dev_compiler/lib/runtime/dart_sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -837,8 +837,8 @@ dart_library.library('dart_sdk', null, /* Imports */[
dart.defineNamedConstructor = function(clazz, name) {
let proto = clazz.prototype;
let initMethod = proto[name];
let ctor = function() {
return initMethod.apply(this, arguments);
let ctor = function(...args) {
initMethod.apply(this, args);
};
ctor.prototype = proto;
dart.defineProperty(clazz, name, {value: ctor, configurable: true});
Expand Down Expand Up @@ -934,6 +934,16 @@ dart_library.library('dart_sdk', null, /* Imports */[
derived.prototype[dart._extensionType] = derived;
derived.prototype.__proto__ = base.prototype;
};
dart.callableClass = function(callableCtor, classExpr) {
callableCtor.prototype = classExpr.prototype;
callableCtor.prototype.constructor = callableCtor;
callableCtor.__proto__ = classExpr.__proto__;
return callableCtor;
};
dart.defineNamedConstructorCallable = function(clazz, name, ctor) {
ctor.prototype = clazz.prototype;
dart.defineProperty(clazz, name, {value: ctor, configurable: true});
};
dart.throwCastError = function(object, actual, type) {
dart.throw(new _js_helper.CastErrorImplementation(object, dart.typeName(actual), dart.typeName(type)));
};
Expand Down
89 changes: 79 additions & 10 deletions pkg/dev_compiler/lib/src/compiler/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -701,13 +701,27 @@ 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);
} else if (member is FieldDeclaration) {
(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;
}
}
}

Expand Down Expand Up @@ -738,15 +752,15 @@ 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);
_setBaseClass(classElem, className, jsPeerName, body);

_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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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]));
}
}

Expand Down Expand Up @@ -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));
}
}
}
Expand Down
28 changes: 12 additions & 16 deletions pkg/dev_compiler/test/browser/language_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,11 @@
'bool_test': skip_fail,
'bound_closure_equality_test': skip_fail,
'branch_canonicalization_test': skip_fail, // JS bit operations truncate to 32 bits.
'call_closurization_test': skip_fail,
'call_function_apply_test': skip_fail,
'call_operator_test': skip_fail,
'call_property_test': skip_fail,
'call_test': skip_fail,
'call_this_test': skip_fail,
'call_through_null_getter_test': skip_fail,
'call_with_no_such_method_test': skip_fail,
'call_closurization_test': fail, // Functions do not expose a "call" method.
'call_function_apply_test': fail, // Function.apply not really implemented.
'call_test': fail, // Functions do not expose a "call" method.
'call_through_null_getter_test': fail, // null errors are not converted to NoSuchMethodErrors.
'call_with_no_such_method_test': fail, // Function.apply not really implemented.
'canonical_const2_test': skip_fail,
'canonical_const_test': skip_fail,
'cascade_precedence_test': skip_fail,
Expand Down Expand Up @@ -164,13 +161,13 @@
'function_subtype_bound_closure5_test': skip_fail,
'function_subtype_bound_closure5a_test': skip_fail,
'function_subtype_bound_closure6_test': skip_fail,
'function_subtype_call0_test': skip_fail,
'function_subtype_call1_test': skip_fail,
'function_subtype_call2_test': skip_fail,
'function_subtype_cast0_test': skip_fail,
'function_subtype_cast1_test': skip_fail,
'function_subtype_cast2_test': skip_fail,
'function_subtype_cast3_test': skip_fail,
'function_subtype_call0_test': fail, // Strong mode "is" rejects some type tests.
'function_subtype_call1_test': fail,
'function_subtype_call2_test': fail,
'function_subtype_cast0_test': fail,
'function_subtype_cast1_test': fail,
'function_subtype_cast2_test': fail,
'function_subtype_cast3_test': fail,
'function_subtype_factory0_test': skip_fail,
'function_subtype_inline0_test': skip_fail,
'function_subtype_local0_test': skip_fail,
Expand All @@ -197,7 +194,6 @@
'function_type_alias4_test': skip_fail,
'function_type_alias6_test_none_multi': skip_fail,
'function_type_alias_test': skip_fail,
'function_type_call_getter_test': skip_fail,
'gc_test': skip_fail,
'generic_field_mixin2_test': skip_fail,
'generic_field_mixin3_test': skip_fail,
Expand Down
42 changes: 39 additions & 3 deletions pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/classes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -256,20 +256,22 @@ setSignature(f, signature) => JS('', '''(() => {

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

///
/// Given a class and an initializer method name, creates a constructor
/// function with the same name. For example `new SomeClass.name(args)`.
/// function with the same name.
///
/// After we define the named constructor, the class can be constructed with
/// `new SomeClass.name(args)`.
defineNamedConstructor(clazz, name) => JS('', '''(() => {
let proto = $clazz.prototype;
let initMethod = proto[$name];
let ctor = function() { return initMethod.apply(this, arguments); };
let ctor = function(...args) { initMethod.apply(this, args); };
ctor.prototype = proto;
// Use defineProperty so we don't hit a property defined on Function,
// like `caller` and `arguments`.
$defineProperty($clazz, $name, { value: ctor, configurable: true });
})()''');


final _extensionType = JS('', 'Symbol("extensionType")');

getExtensionType(obj) => JS('', '#[#]', obj, _extensionType);
Expand Down Expand Up @@ -438,3 +440,37 @@ setExtensionBaseClass(derived, base) {
// Link the prototype objects
JS('', '#.prototype.__proto__ = #.prototype', derived, base);
}

/// Given a special constructor function that creates a function instances,
/// and a class with a `call` method, merge them so the constructor function
/// will have the correct methods and prototype.
///
/// For example:
///
/// lib.Foo = dart.callableClass(
/// function Foo { function call(...args) { ... } ... return call; },
/// class Foo { call(x) { ... } });
/// ...
/// let f = new lib.Foo();
/// f(42);
callableClass(callableCtor, classExpr) {
JS('', '#.prototype = #.prototype', callableCtor, classExpr);
// We're not going to use the original class, so we can safely replace it to
// point at this constructor for the runtime type information.
JS('', '#.prototype.constructor = #', callableCtor, callableCtor);
JS('', '#.__proto__ = #.__proto__', callableCtor, classExpr);
return callableCtor;
}

/// Given a class and an initializer method name and a call method, creates a
/// constructor function with the same name.
///
/// For example it can be called with `new SomeClass.name(args)`.
///
/// The constructor
defineNamedConstructorCallable(clazz, name, ctor) => JS('', '''(() => {
ctor.prototype = $clazz.prototype;
// Use defineProperty so we don't hit a property defined on Function,
// like `caller` and `arguments`.
$defineProperty($clazz, $name, { value: ctor, configurable: true });
})()''');

0 comments on commit 430e8f1

Please sign in to comment.