ariovistus / pyd

Interoperability between Python and D
MIT License
158 stars 32 forks source link

Allow @safe pure nothrow member functions #83

Closed atilaneves closed 6 years ago

atilaneves commented 6 years ago

I don't see why there is an artificial limitation in pyd - Python doesn't care what attributes the D member functions have, calling them should be the same as calling any other member function.

One could simply have "regular" @system functions with no other attributes for the sake of pyd but this fails when the struct is a template:

struct Foo(T) {
    T value;
    void foo() { /* ... */ }
}

One can slap @system on foo, but there's nothing that can be done about pure and nothrow - the compiler will infer attributes.

atilaneves commented 6 years ago

Ping @ariovistus

ariovistus commented 6 years ago

Sorry, been busy with work and things. I think the limitation had something to do with reflection. It may be an artifact of older compilers that isn't relevant now, but I would have to dig to find out for sure. Might have some time this week end.

ariovistus commented 6 years ago

So the reason this limitation exists is due to the way class wrapping is implemented - for every class you pass to wrap_class, pyd actually generates a subclass of it. Wrapped methods are overridden and must call reflection apis that cannot be nothrow, pure, etc. I want to say this exists so that it is possible to declare a class in D, extend the class in python, and call the class methods with appropriate polymorphism in D. Nothing to be done for that, however I think that use case does not apply for structs, and probably should be at least opt-outable for classes. I would envision a separate wrapper, e.g. wrap_class_noinherit, that you could use if you need to wrap pure nothrow, and don't need the inherit mechanisms.

atilaneves commented 6 years ago

CPython reflection APIs? There's no problem in marking their D bindings pure, and nothrow and @system are trivial to workaround by catching all exceptions (in fact, there shouldn't be any since it's C code and really they should be marked nothrow) and making all calls to such APIs go through a @trusted lambda.

If it's D code, then I don't see how it can't be made to work with @safe pure nothrow.

I could simply implement wrap_class_noinherit and the problem would be solved for structs, but not template classes. I don't use the latter very often if at all, but it may impact other users.

Again, Python doesn't care about D attributes on functions and it shouldn't matter what attributes exist.

atilaneves commented 6 years ago

Oh, and this patch works with the structs I mentioned with no ill effects. I didn't try classes though.

ariovistus commented 6 years ago

wrappers around cpython apis. They do throw, pyd goes to some effort to ensure that a raised python exception is translated to a thrown D exception and vice versa. Semantically, the cpython apis aren't pure due to the way exceptions are implemented in python.

Let's see here. class X declared in D:

class X {
  void foo() {
  ...
  }
}

and exposed to python off screen

class Y in python:

class Y(X):
  def foo():
     ...

class Y used in D:

X x = python_make_y_instance();
x.foo();

x.foo() should call Y.foo, not X.foo. in order to do that, it needs to call some cpython apis to get the necessary info together to make the call. Since python is dynamic, a fair number of nasty things can happen here, and if they do, you really want an exception to tell you about them.

So that's the mechanism that is incompatible with pure nothrow. Not including it would make wrap_class_noinherit conceptually much simpler, and then there should be no issue with wrapping any attributes you like. I'm not sure it's a trivial change, since the Def template et al are rather tightly coupled to wrap_class atm, but it is doable.

atilaneves commented 6 years ago

I didn't consider the Python -> D direction, mostly because I only care about the D -> Python one. When calling D code from Python, there is no exception wrapping needed if the D code is nothrow. Likewise, Python doesn't care about the D notion of purity, and is similar to unpure functions being able to call pure ones in D.

In any case I'll just implement wrap_class_noinherit. I'll have to read the pyd code for a while to figure out how to though, but it seems to me that it's the only way forward for me to be able to wrap template structs.