Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embind: Class function not available on subclasses #18722

Closed
JeanChristopheMorinPerso opened this issue Feb 12, 2023 · 3 comments · Fixed by #18794
Closed

Embind: Class function not available on subclasses #18722

JeanChristopheMorinPerso opened this issue Feb 12, 2023 · 3 comments · Fixed by #18794
Labels

Comments

@JeanChristopheMorinPerso
Copy link
Contributor

Please include the following in your bug report:

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.30 (cfe2bdfe2692457cb5f5770672f6e5ccb3ffc2f2)
clang version 16.0.0 (https://github.com/llvm/llvm-project 800f0f1546b2352ba42a4777149afb13cb874fcd)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/jcmorin/jcmenv/aswf/OpenTimelineIO-JS-Bindings/emsdk/upstream/bin

Problem:

When binding classes using Embind, functions declared as class functions are not available in subclasses. For example, I have this code:

#include <string>

#include <emscripten/bind.h>

namespace ems = emscripten;

class A
{
public:
    A(){};

    static std::string from_json_string(std::string const& input)
    {
        return input;
    };
};

class B : public A
{
public:
    B(){};
};

class C : public B
{
public:
    C(){};
};

EMSCRIPTEN_BINDINGS(inheritance)
{
    ems::class_<A>("A").constructor<>().class_function(
        "from_json_string",
        &A::from_json_string);

    ems::class_<B, ems::base<A>>("B").constructor<>();

    ems::class_<C, ems::base<B>>("C").constructor<>();
}

and I compile it with emcc -lembind -o lib.js bindings.cpp -s MODULARIZE=1, and then run it in JS:

const factory = require('./lib');

factory().then((lib) => {
    console.log(lib.A.from_json_string("From A"))
    console.log(lib.B.from_json_string("From B"))
    console.log(lib.C.from_json_string("From C"))
})

It raises:

From A
/home/jcmorin/jcmenv/aswf/OpenTimelineIO-JS-Bindings/repros/inheritance/lib.js:165
      throw ex;
      ^

TypeError: lib.B.from_json_string is not a function
    at /home/jcmorin/jcmenv/aswf/OpenTimelineIO-JS-Bindings/repros/inheritance/test.js:5:23

Node.js v19.5.0

We can see that that A.from_json_string() works but B.from_json_string() and C.from_json_string() don't work.

I was kind of expecting class function to be available in sub-classes... Is this a bug or just how it should work?

Thanks a lot!

@kripken kripken added the embind label Feb 13, 2023
@brendandahl
Copy link
Collaborator

It looks inheritance for static class functions was never implemented. The embind code pre-dates JS classes so it was probably just hard to implement this at the time using prototype inheritance.

@JeanChristopheMorinPerso
Copy link
Contributor Author

Thanks for the answer. After looking into it, it seems like it might be simple to implement. Or at least I'm hoping that it's simple enough. I'll see how far I can get.

Basically, from what I see, ES5 classes don't inherit class functions by default. You have to do it manually. With ES6 classes, class functions are automatically inherited. https://www.bennadel.com/blog/3300-static-methods-are-inherited-when-using-es6-extends-syntax-in-javascript-and-node-js.htm.

@JeanChristopheMorinPerso
Copy link
Contributor Author

Example in pure JS:

// Create class A without syntax sugar
var A = function () { }

A.class_function = function () {
    console.log(`${this.name}.class_function`)
}

// Create class B without syntax sugar
var B = function () { }

var instanceProperty = Object.create(A.prototype, {
    constructor: { value: B }
})

B.prototype = instanceProperty

// Test if class_function exists on class B. It should print false.
console.log(B.hasOwnProperty('class_function'))

for (const key in A) {
    if (A.hasOwnProperty(key) && typeof (A[key] == 'function')) {
        B[key] = A[key]
    }
}

// Test if class_function exists on class B. It should print true.
console.log(B.hasOwnProperty('class_function'))

A.class_function()
B.class_function()

class C {
    static class_function() {
        console.log(`${this.name}.class_function`)
    }
}

class D extends C {
}

C.class_function()
D.class_function()

outputs

false
true
A.class_function
B.class_function
C.class_function
D.class_function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants