sol2 icon indicating copy to clipboard operation
sol2 copied to clipboard

How to get metatable of base classes from an object of derived type

Open Malacath-92 opened this issue 2 years ago • 4 comments

For a game project I am implementing a console with tab-autocompletion. To get suggestions from . or : I traverse the metatable of an object, however for derived types the base members are missing in the metatable and I can't find any way to access just the member names from the derived type.

First I thought if I could access just the names of base classes I could manually get those meta tables and merge them to collect suggestions. However I can not find any reference to the base classes either inside of the derived class, plus the interesting information does not seem to be there either way.

#include <limits>
#include <string_view>

#include <sol/sol.hpp>
#include <fmt/format.h>

struct Base {
    int foo{ 0 };
};
struct Derived : Base {
    int bar{ -1 };
};

int main() {
    sol::state lua;

    lua.new_usertype<Base>("Base", "foo", &Base::foo);
    lua.new_usertype<Derived>("Derived", "bar", &Derived::bar, sol::base_classes, sol::bases<Base>());

    lua["base"] = lua.safe_script("return Base.new()");
    lua["derived"] = lua.safe_script("return Derived.new()");

    auto print_keys = [](auto table)
    {
        fmt::print("{{\n");
        for (auto& [k, v] : table)
        {
            fmt::print("\t{},\n", k.template as<std::string_view>());
        }
        fmt::print("}}\n\n");
    };

    fmt::print("'base' metatable keys:\n");
    print_keys(lua["base"][sol::metatable_key].get<sol::table>());
    // How to get this ^^^^ from this vvvv
    fmt::print("'derived' metatable keys:\n");
    print_keys(lua["derived"][sol::metatable_key].get<sol::table>());

    fmt::print("'Base' metatable keys:\n");
    print_keys(lua["Base"][sol::metatable_key].get<sol::table>());
    // Or get this ^^^^ from this vvvv, although the required information does not seem to exist here either way
    fmt::print("'Derived' metatable keys:\n");
    print_keys(lua["Derived"][sol::metatable_key].get<sol::table>());
}

Ignoring all the meta stuff this gives me

'base' metatable keys:
{
	foo,
	class_cast,
	new,
	class_check,
}

'derived' metatable keys:
{
	class_cast,
	bar,
	new,
	class_check,
}

'Base' metatable keys:
{
	class_cast,
	class_check,
}

'Derived' metatable keys:
{
	class_cast,
	class_check,
}

I do not think I can use class_cast in this case unless I would have a list of potential classes a-priori.

Perhaps this is even the wrong approach for tab-completion, I am open to any other suggestions. I am also aware that we could "easily" solve this by including all base members in the derived types, that's not feasible for use tho since it is not scalable and drastically impacts compile times.

Malacath-92 avatar Jul 19 '21 13:07 Malacath-92

I've fixed this for now with the class of user types that are most prelevant for us by creating a global table that maps a type to its direct base type and using that information to cast it down one level. It's not a general solution however. What would be a general solution that I can imagine being feasible is to make a new sol::state::new_reflectable_usertype or adding a sol::reflectable{} to sol::state::new_usertype that generates a table with all members. I assume it would have a bunch of overhead because there is no restriction of the order of usertype registration so said table would have to be built at runtime. I nonetheless believe there is value in having reflectable user types and restricting ourselves to register user types in order of inheritance (base to derived) I could potentially implement it myself without too much overhead (although I am no expert on sol and/or Lua perf).

Malacath-92 avatar Jul 20 '21 11:07 Malacath-92

This is actually a general thing I am working on implementing (and gating behind a by-default flag). I never thought of using the word sol::reflectable since it was just providing information (and not real callables), but that might be a decent starter on picking a name. Maybe something like sol::binding_info{} as a way to tag some of the stuff....?

I'll keep this open as I keep working on it. Apologies for not being quick on the uptake these days! 🙇‍♀️

ThePhD avatar Jul 27 '21 23:07 ThePhD

No worries and no hurry (from my side at least)

I admit sol::reflectable is not a great choice, maybe sol::introspectable would be more accurate. However I think your name is either way much better since it's quite a lot more explicit with what it provides.

Thanks for taking this on!

Malacath-92 avatar Jul 28 '21 07:07 Malacath-92

This is tangentially related so I am not sure if I should make another issue for it. It's also a longshot but I figure asking can't hurt.

Would it be possible to get some introspection for functions as well? Anywhere from a very basic number of expected args over names of args to types of args? This is also something I would use for tab-completion but also for generating an "external Lua library" that can be used with text editors/IDEs for auto-completion and hinting.

Malacath-92 avatar Aug 02 '21 09:08 Malacath-92