Search code examples
emscriptenembind

Not all overloads in a bound C++ class are available in JavaScript, using emscripten


One of the classes in a library that I want to make available via WebAssembly in JavaScript contains 3 overloads of a method:

class RuleContext : public tree::ParseTree {
  public:
    virtual std::string toStringTree(Parser *recog, bool pretty = false) override;
    virtual std::string toStringTree(std::vector<std::string> &ruleNames, bool pretty = false);
    virtual std::string toStringTree(bool pretty = false) override;
  };

I have bound all 3 as usual:

  class_<RuleContext, base<tree::ParseTree>>("RuleContext")
    .function("toStringTree", select_overload<std::string(Parser *, bool)>(&RuleContext::toStringTree),
              allow_raw_pointers())
    .function("toStringTree",
              select_overload<std::string(std::vector<std::string> &, bool)>(&RuleContext::toStringTree))
    .function("toStringTree", select_overload<std::string(bool)>(&RuleContext::toStringTree))
    .allow_subclass<RuleContextWrapper>("RuleContextWrapper");

The TypeScript declaration is nothing special:

export declare class RuleContext extends Extendable {
    public toStringTree(recognizer: Parser, pretty: boolean): string;
    public toStringTree(ruleNames: Vector<string>, pretty: boolean): string;
    public toStringTree(pretty: boolean): string;
}

Yet I can only use 2 of the overloads and I wonder why. What works is:

console.log(tree.toStringTree(names, true));
console.log(tree.toStringTree(true));

Trying to use console.log(tree.toStringTree(parser, true)); leads to this error:

BindingError: Expected null or instance of StringVector, got an instance of Recognizer
    at throwBindingError (file:///Volumes/Extern/Work/projects/antlr4wasm/wasm/antlr4-runtime-wasm.js:3910:8)
    at upcastPointer (file:///Volumes/Extern/Work/projects/antlr4wasm/wasm/antlr4-runtime-wasm.js:4652:4)
    at RegisteredPointer.nonConstNoSmartPtrRawPointerToWireType [as toWireType] (file:///Volumes/Extern/Work/projects/antlr4wasm/wasm/antlr4-runtime-wasm.js:4760:12)
    at Test1Context.RuleContext$toStringTree (eval at newFunc (file:///Volumes/Extern/Work/projects/antlr4wasm/wasm/antlr4-runtime-wasm.js:4957:22), <anonymous>:9:26)
    at proto.<computed> [as toStringTree] (file:///Volumes/Extern/Work/projects/antlr4wasm/wasm/antlr4-runtime-wasm.js:4612:61)
    at file:///Volumes/Extern/Work/projects/antlr4wasm/tests/test.ts:24:22

For completeness: StringVector represents the std::vector<std::string> binding and Recognizer is the base class of Parser, but that shouldn't matter here.


Solution

  • Actually, this behavior is documented:

    Constructors and functions can be overloaded on the number of arguments, but embind does not support overloading based on type. When specifying an overload, use the select_overload() helper function to select the appropriate signature.

    I missed the "... and functions.." part somehow, which is even made worse by embind printing an error when you have constructor overloads with the same number of arguments, but nothing is printed, whatsoever, for functions.

    A solution for the above problem is not to bind the overload with the names parameter. Alternatively, you could add a dummy parameter to disambiguate in one of the overloads.