Skip to content

Commit

Permalink
Improved inheritance support. Fixed ctor-related bug.
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.dsource.org/projects/pyd/trunk@91 1df65b71-e716-0410-9316-ac55df2b1602
  • Loading branch information
KirkMcDonald authored and KirkMcDonald committed Feb 1, 2007
1 parent a77b292 commit 105fd06
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 36 deletions.
39 changes: 32 additions & 7 deletions examples/inherit/inherit.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import pyd.pyd;
import std.stdio;

class Base {
this(int i) { writefln("Base.this(): ", i); }
void foo() {
writefln("Base.foo");
}
Expand All @@ -13,13 +14,26 @@ class Base {
}

class Derived : Base {
this(int i) { super(i); writefln("Derived.this(): ", i); }
void foo() {
writefln("Derived.foo");
}
}

class WrapDerive : Derived {
class BaseWrap : Base {
mixin OverloadShim;
this(int i) { super(i); }
void foo() {
get_overload(&super.foo, "foo");
}
void bar() {
get_overload(&super.bar, "bar");
}
}

class DeriveWrap : Derived {
mixin OverloadShim;
this(int i) { super(i); }
void foo() {
get_overload(&super.foo, "foo");
}
Expand All @@ -33,17 +47,17 @@ void call_poly(Base b) {
Base b1, b2, b3;

Base return_poly_base() {
if (b1 is null) b1 = new Base;
if (b1 is null) b1 = new Base(1);
return b1;
}

Base return_poly_derived() {
if (b2 is null) b2 = new Derived;
if (b2 is null) b2 = new Derived(2);
return b2;
}

Base return_poly_wrap() {
if (b3 is null) b3 = new WrapDerive;
if (b3 is null) b3 = new DeriveWrap(3);
return b3;
}

Expand All @@ -56,15 +70,26 @@ extern(C) void PydMain() {
module_init();

wrapped_class!(Base) b;
b.hide();
b.def!(Base.foo);
b.def!(Base.bar);
finalize_class(b);

wrapped_class!(Derived) d;
d.hide();
d.def!(Derived.foo);
finalize_class(d);

wrapped_class!(WrapDerive) w;
w.def!(WrapDerive.foo);
finalize_class(w);
wrapped_class!(BaseWrap, "Base") bw;
bw.init!(void function(int));
bw.def!(BaseWrap.foo);
bw.def!(BaseWrap.bar);
finalize_class(bw);

wrapped_class!(DeriveWrap, "Derived") dw;
dw.init!(void function(int));
dw.parent!(BaseWrap);
dw.def!(DeriveWrap.foo);
finalize_class(dw);
}

22 changes: 12 additions & 10 deletions examples/inherit/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

import inherit

b = inherit.Base()
d = inherit.Derived()
b = inherit.Base(1)
d = inherit.Derived(2)

b.foo()
b.bar()
Expand All @@ -25,31 +25,33 @@
inherit.call_poly(b)
inherit.call_poly(d)

w = inherit.WrapDerive()
inherit.call_poly(w)
#w = inherit.WrapDerive()
#inherit.call_poly(w)

class PyClass(inherit.WrapDerive):
class PyClass(inherit.Derived):
def foo(self):
print 'PyClass.foo'

p = PyClass()
p = PyClass(3)
#print "The basic inheritance support breaks down here:"
inherit.call_poly(p)

print

b1 = inherit.return_poly_base()
print "inherit.return_poly_base returned instance of Base"
assert type(b1) == inherit.Base
b1.foo()
b1.bar()
#assert type(b1) == inherit.Base
b2 = inherit.return_poly_derived()
b2a = inherit.return_poly_derived()
print "inherit.return_poly_derived returned instance of Derived"
assert type(b2) == inherit.Derived
#assert type(b2) == inherit.Derived
print "inherit.return_poly_derived returned the same object twice"
assert b2 is b2a
b3 = inherit.return_poly_wrap()
print "inherit.return_poly_wrap returned instance of WrapDerive"
assert type(b3) == inherit.WrapDerive
print "inherit.return_poly_wrap returned instance of DeriveWrap"
assert type(b3) == inherit.Derived

print
print '-------'
Expand Down
6 changes: 6 additions & 0 deletions html_doc/class_wrapping.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ <h1>Class wrapping</h1>
<dt><code>static void init(<span class="t_arg">C</span> ...) ();</code></dt>
<dd>This allows you to expose the class's constructors to Python. If the class provides a zero-argument constructor, there is no need to specify it; it is always available. Each element of <span class="t_arg">C</span> should be a function type. Each function type should correspond to a constructor. (That is, the arguments to the function should be the same as arguments to the class constructor. The return type is ignored.) There is an additional limitation at this time: No two constructors may have the same number of arguments. Pyd will always attempt to call the first constructor with the right number of arguments. If you wish to support a constructor with default arguments, you must specify each possible constructor call as a different template argument to this function. The examples show a few uses of the <code>init</code> function.</dd>

<dt><code>static void parent(<span class="t_arg">Parent</span>) ();</code></dt>
<dd>This allows the user to manually specify a class as this class's parent. This is intended for a very specific purpose (related to how Pyd handles <a href="inherit.html">inheritance</a>), and should not be used heedlessly. If a class's parent was previously wrapped, then Pyd will detect this and set up a parent-child relationship automatically, in which case it is not neccessary to specify this.</dd>

<dt><code>static void hide();</code></dt>
<dd>Causes this class to be wrapped, but not actually directly exposed to Python. This can be useful if you want to return instances of a class without allowing Python code to instantiate them. This is mainly used when handling <a href="inherit.html">inheritance</a>.</dd>

<dt><code>static void iter(<span class="t_arg">iter_t</span>) ();</code></dt>
<dd>This allows the user to specify a different overload of opApply than the default. (The default is always the one that is lexically first.) The <span class="t_arg">iter_t</span> argument should be the type of the delegate that forms the argument to opApply. This might be e.g. <code>int delegate(inout int)</code>. Don't forget the <code>inout</code> modifiers! (This is not available in Linux; see the note below on opApply wrapping.)</dd>

Expand Down
64 changes: 64 additions & 0 deletions html_doc/inherit.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,70 @@ <h1>Inheritance</h1>
&gt;&gt;&gt; polymorphic_call(p)
PyClass.foo</pre>

<p>However, BaseWrap has no particular relationship to Derived. You may remember that Derived overloads <tt>bar</tt> but not <tt>foo</tt>. When we wrapped Derived in <tt>PydMain</tt>, we specified the <tt>foo</tt> overload but not the <tt>bar</tt> overload. Because Derived's parent class is no longer wrapped, Pyd no longer has any way to know about the <tt>bar</tt> method of the Derived class.</tt></p>

<p>The solution is to explicitly tell Pyd that Derived's parent is BaseWrap. Furthermore, it is probably best to go the extra mile, by wrapping an <tt>OverloadShim</tt> subclass of Derived (call it DerivedWrap), and telling Pyd that BaseWrap is <em>its</em> parent. Additionally, the original <tt>Base</tt> and <tt>Derived</tt> classes should still be wrapped, in the event that functions return instances of them to Python, but should not actually be exposed to Python. The complete solution ends up looking like this:</p>

<pre class="code"><span class="keyword">import</span> pyd.pyd;
<span class="keyword">import</span> std.stdio;

<span class="keyword">class</span> Base {
<span class="keyword">void</span> foo() { writefln(<span class="string">"Base.foo"</span>); }
<span class="keyword">void</span> bar() { writefln(<span class="string">"Base.bar"</span>); }
}

<span class="keyword">class</span> Derived : Base {
<span class="keyword">void</span> foo() { writefln(<span class="string">"Derived.foo"</span>); }
}

<span class="keyword">class</span> BaseWrap : Base {
<span class="keyword">mixin</span> OverloadShim;
<span class="keyword">void</span> foo() {
get_overload(&amp;<span class="keyword">super</span>.foo, <span class="string">"foo"</span>);
}
<span class="keyword">void</span> bar() {
get_overload(&amp;<span class="keyword">super</span>.bar, <span class="string">"bar"</span>);
}
}

<span class="keyword">class</span> DerivedWrap : Derived {
<span class="keyword">mixin</span> OverloadShim;
<span class="keyword">void</span> foo() {
get_overload(&amp;<span class="keyword">super</span>.foo, <span class="string">"foo"</span>);
}
}

<span class="keyword">extern</span> (C) <span class="keyword">void</span> PydMain() {
module_init();

wrapped_class!(Base) b;
w.hide();
w.def!(Base.foo);
w.def!(Base.bar);
finalize_class(w);

wrapped_class!(Derived) d;
d.hide();
d.def!(Derived.foo);
finalize_class(d);

wrapped_class!(BaseWrap, <span class="string">"Base"</span>) bw;
bw.def!(BaseWrap.foo);
bw.def!(BaseWrap.bar);
finalize_class(bw);

wrapped_class!(DerivedWrap, <span class="string">"Derived"</span>) dw;
dw.parent!(BaseWrap);
dw.def!(DerivedWrap.foo);
finalize_class(dw);
}</pre>

<p><i>(I recognize that this is astoundingly ugly. However, it is the best solution I can come up with without resorting to code generation.)</i></p>

<p>The <a href="http://dsource.org/projects/pyd/browser/trunk/examples/inherit/inherit.d"><tt>inherit</tt> example</a> in the Pyd distribution provides a more complete version of this example, including how wrapper code should handle constructors.</p>

<p><i>(TODO: Add support for interfaces and abstract classes.)</i></p>

</div>

</body>
Expand Down
56 changes: 37 additions & 19 deletions infrastructure/pyd/class_wrap.d
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ template wrapped_class_type(T) {
null, /*tp_getattro*/
null, /*tp_setattro*/
null, /*tp_as_buffer*/
0, /*tp_flags*/
0, /*tp_flags*/
null, /*tp_doc*/
null, /*tp_traverse*/
null, /*tp_clear*/
Expand Down Expand Up @@ -249,6 +249,7 @@ template wrapped_set(T, alias Fn) {
struct wrapped_class(T, char[] classname = symbolnameof!(T)) {
static if (is(T == class)) pragma(msg, "wrapped_class: " ~ classname);
static const char[] _name = classname;
static bool _private = false;
alias T wrapped_type;
/**
* Wraps a member function of the class.
Expand Down Expand Up @@ -332,6 +333,14 @@ struct wrapped_class(T, char[] classname = symbolnameof!(T)) {
&wrapped_ctors!(T, C).init_func;
}

static void parent(Parent) () {
wrapped_class_type!(T).tp_base = &wrapped_class_type!(Parent);
}

static void hide() {
_private = true;
}

// Iteration wrapping support requires StackThreads
version(Pyd_with_StackThreads) {

Expand Down Expand Up @@ -393,12 +402,14 @@ void finalize_class(CLS) (CLS cls, char[] docstring="", char[] modulename="") {
type.tp_methods = wrapped_method_list!(T).ptr;
type.tp_name = (module_name ~ "." ~ name ~ \0).ptr;

// Check for wrapped parent classes
static if (is(T B == super)) {
foreach (C; B) {
static if (is(C == class) && !is(C == Object)) {
if (is_wrapped!(C)) {
type.tp_base = &wrapped_class_type!(C);
// Check for wrapped parent classes, if one was not explicitly supplied.
if (type.tp_base is null) {
static if (is(T B == super)) {
foreach (C; B) {
static if (is(C == class) && !is(C == Object)) {
if (is_wrapped!(C)) {
type.tp_base = &wrapped_class_type!(C);
}
}
}
}
Expand Down Expand Up @@ -436,21 +447,27 @@ void finalize_class(CLS) (CLS cls, char[] docstring="", char[] modulename="") {
type.tp_call = cast(ternaryfunc)&method_wrap!(T, T.opCall, typeof(&T.opCall)).func;
}

// If a ctor wasn't supplied, try the default.
if (type.tp_init is null) {
static if (is(T == class)) {
type.tp_init = &wrapped_init!(T).init;
} else {
type.tp_init = &wrapped_struct_init!(T).init;
if (CLS._private) {
type.tp_init = null;
} else {
// If a ctor wasn't supplied, try the default.
static if (is(typeof(new T))) {
if (type.tp_init is null) {
static if (is(T == class)) {
type.tp_init = &wrapped_init!(T).init;
} else {
type.tp_init = &wrapped_struct_init!(T).init;
}
}
}
}
if (PyType_Ready(&type) < 0) {
// XXX: This will probably crash the interpreter, as it isn't normally
// caught and translated.
throw new Exception("Couldn't ready wrapped type!");
}
Py_INCREF(cast(PyObject*)&type);
PyModule_AddObject(Pyd_Module_p(modulename), name.ptr, cast(PyObject*)&type);
if (!CLS._private) {
PyModule_AddObject(Pyd_Module_p(modulename), name.ptr, cast(PyObject*)&type);
}
is_wrapped!(T) = true;
static if (is(T == class)) {
wrapped_classes[T.classinfo] = &type;
Expand All @@ -459,12 +476,13 @@ void finalize_class(CLS) (CLS cls, char[] docstring="", char[] modulename="") {

template OverloadShim() {
std.traits.ReturnType!(dg_t) get_overload(dg_t, T ...) (dg_t dg, char[] name, T t) {
python.PyObject* _pyobj = wrapped_gc_objects[cast(void*)this];
if (_pyobj.ob_type != &wrapped_class_type!(typeof(this))) {
python.PyObject** _pyobj = cast(void*)this in wrapped_gc_objects;
python.PyTypeObject** _pytype = this.classinfo in wrapped_classes;
if (_pyobj is null || _pytype is null || (*_pyobj).ob_type != *_pytype) {
// If this object's type is not the wrapped class's type (that is,
// if this object is actually a Python subclass of the wrapped
// class), then call the Python object.
python.PyObject* method = python.PyObject_GetAttrString(_pyobj, (name ~ \0).ptr);
python.PyObject* method = python.PyObject_GetAttrString(*_pyobj, (name ~ \0).ptr);
if (method is null) handle_exception();
dg_t pydg = PydCallable_AsDelegate!(dg_t)(method);
python.Py_DECREF(method);
Expand Down
6 changes: 6 additions & 0 deletions raw_html/class_wrapping.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ <h1>Class wrapping</h1>
<dt><code>static void init(<span class="t_arg">C</span> ...) ();</code></dt>
<dd>This allows you to expose the class's constructors to Python. If the class provides a zero-argument constructor, there is no need to specify it; it is always available. Each element of <span class="t_arg">C</span> should be a function type. Each function type should correspond to a constructor. (That is, the arguments to the function should be the same as arguments to the class constructor. The return type is ignored.) There is an additional limitation at this time: No two constructors may have the same number of arguments. Pyd will always attempt to call the first constructor with the right number of arguments. If you wish to support a constructor with default arguments, you must specify each possible constructor call as a different template argument to this function. The examples show a few uses of the <code>init</code> function.</dd>

<dt><code>static void parent(<span class="t_arg">Parent</span>) ();</code></dt>
<dd>This allows the user to manually specify a class as this class's parent. This is intended for a very specific purpose (related to how Pyd handles <a href="inherit.html">inheritance</a>), and should not be used heedlessly. If a class's parent was previously wrapped, then Pyd will detect this and set up a parent-child relationship automatically, in which case it is not neccessary to specify this.</dd>

<dt><code>static void hide();</code></dt>
<dd>Causes this class to be wrapped, but not actually directly exposed to Python. This can be useful if you want to return instances of a class without allowing Python code to instantiate them. This is mainly used when handling <a href="inherit.html">inheritance</a>.</dd>

<dt><code>static void iter(<span class="t_arg">iter_t</span>) ();</code></dt>
<dd>This allows the user to specify a different overload of opApply than the default. (The default is always the one that is lexically first.) The <span class="t_arg">iter_t</span> argument should be the type of the delegate that forms the argument to opApply. This might be e.g. <code>int delegate(inout int)</code>. Don't forget the <code>inout</code> modifiers! (This is not available in Linux; see the note below on opApply wrapping.)</dd>

Expand Down
Loading

0 comments on commit 105fd06

Please sign in to comment.