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

Getting the return type of a Lua function #1500

Closed
Razakhel opened this issue Jun 10, 2023 · 2 comments
Closed

Getting the return type of a Lua function #1500

Razakhel opened this issue Jun 10, 2023 · 2 comments

Comments

@Razakhel
Copy link

Razakhel commented Jun 10, 2023

Hey,

First of all, thanks for this excellent library!

To give some context for my issue/question, I'm using sol2 to add scripting capabilities to my game engine. I'm aiming to allow giving a Lua script containing a specific function returning a boolean, and said function would be run every frame. To check that it does actually return a boolean, I'm currently trying to get the return type of a Lua function, without having to execute it as it is not supposed to be at that point.

I've seen from the tutorials that the following is supposed to work (also shown here):

state.script("update = function () return true end");
// I actually do
//    state.script("function update() return true end");
// but I suppose it is the same thing; both give the same result anyway,
// likewise if giving C++ lambas as demonstrated below

if (!state["update"].is<std::function<bool()>>())
  // Error

However, is<std::function<T()>> seems to return true for any T:

sol::state lua;
lua.open_libraries(sol::lib::base);

lua["returns_void"]   = [] () {};
lua["returns_int"]    = [] () { return 100; };
lua["returns_bool"]   = [] () { return true; };
lua["returns_string"] = [] () { return std::string("test"); };

sol::function returns_void   = lua["returns_void"];
sol::function returns_int    = lua["returns_int"];
sol::function returns_bool   = lua["returns_bool"];
sol::function returns_string = lua["returns_string"];

std::cout << "returns_void:\n";
if (returns_void.is<std::function<void()>>()) std::cout << "\tReturns void\n";
if (returns_void.is<std::function<int()>>()) std::cout << "\tReturns int\n";
if (returns_void.is<std::function<bool()>>()) std::cout << "\tReturns bool\n";
if (returns_void.is<std::function<std::string()>>()) std::cout << "\tReturns string\n";

std::cout << "returns_int:\n";
if (returns_int.is<std::function<void()>>()) std::cout << "\tReturns void\n";
if (returns_int.is<std::function<int()>>()) std::cout << "\tReturns int\n";
if (returns_int.is<std::function<bool()>>()) std::cout << "\tReturns bool\n";
if (returns_int.is<std::function<std::string()>>()) std::cout << "\tReturns string\n";

std::cout << "returns_bool:\n";
if (returns_bool.is<std::function<void()>>()) std::cout << "\tReturns void\n";
if (returns_bool.is<std::function<int()>>()) std::cout << "\tReturns int\n";
if (returns_bool.is<std::function<bool()>>()) std::cout << "\tReturns bool\n";
if (returns_bool.is<std::function<std::string()>>()) std::cout << "\tReturns string\n";

std::cout << "returns_string:\n";
if (returns_string.is<std::function<void()>>()) std::cout << "\tReturns void\n";
if (returns_string.is<std::function<int()>>()) std::cout << "\tReturns int\n";
if (returns_string.is<std::function<bool()>>()) std::cout << "\tReturns bool\n";
if (returns_string.is<std::function<std::string()>>()) std::cout << "\tReturns string\n";
returns_void:
	Returns void
	Returns int
	Returns bool
	Returns string
returns_int:
	Returns void
	Returns int
	Returns bool
	Returns string
returns_bool:
	Returns void
	Returns int
	Returns bool
	Returns string
returns_string:
	Returns void
	Returns int
	Returns bool
	Returns string

(Tested with with and without SOL_ALL_SAFETIES_ON, using MinGW & MSVC under Windows, and GCC & Clang through WSL.)

Was this functionality working before and broke at some point?

Of course, I could simply execute the function and test its result type as follows:

sol::function_result res = state["update"]();
if (res.get_type() != sol::type::boolean)
  // Error

However, I'd really rather not do this, as it will imply testing it each frame; doing it only once at startup would be ideal.

Is there any other way, again without executing the function? If worse comes to worst, I'll simply test its result each frame with an assertion, or not at all.

Thanks!

@Rochet2
Copy link

Rochet2 commented Jun 10, 2023

I dont think that is possible.
Lua is a dynamically typed language, so if the code would for example be the following, would the evaluation determine that this functions if valid nor not?

function update()
    return global_var
end

It is not possible to do static analysis on the code to know the return value type. Any code anywhere (in lua or in c++) could assign global_var to be anything - boolean or not. And thus we can only analyze that the function does return a value, and that it returns one value instead of multiple, but we do now know what is the type of the value or its actual value. I think analyzing all of that information also proably requires one the interpret the bytecode or AST of the string.dump() output of the update function for example. Sol does not do any of this as far as I know.

You would need to make sure that the function is never changed (by taking a reference to it for example) and then analyze that the function indeed returns a constant boolean value instead of a variable - or that the variable is a constant value.

What is the point of checking if the value is a boolean? Why not accept any value and convert it to boolean using Lua's logic (nil and false are falsy, all other values are truthy)? It sounds as if you would be trying to make lua typed.

And on the example from Sol, it could work if Sol is able to peer into the value and see that it is a C++ function. Then extract the type of the function and compare the signatures. But that would only work for C++ functions and lambdas, not lua defined functions. So you would not be able to define the function in lua. It is possible that there is a bug or its not intended to check the signatures.

I do not know your requirements, but I would just use lua's boolean logic for evaluating the return value and ignore the type or have the runtime check for return type.

@Razakhel
Copy link
Author

Any code anywhere (in lua or in c++) could assign global_var to be anything - boolean or not. And thus we can only analyze that the function does return a value, and that it returns one value instead of multiple, but we do now know what is the type of the value or its actual value. I think analyzing all of that information also proably requires one the interpret the bytecode or AST of the string.dump() output of the update function for example. Sol does not do any of this as far as I know.

I understand that. The fact that it was part of the tutorials was what made me wonder if Sol indeed did what you described last. Isn't it very misleading then? It may be worthwhile to remove it from the documentation, or even prevent using is<std::function<T>>() if it's never relevant 😕

And on the example from Sol, it could work if Sol is able to peer into the value and see that it is a C++ function. Then extract the type of the function and compare the signatures. But that would only work for C++ functions and lambdas, not lua defined functions. So you would not be able to define the function in lua. It is possible that there is a bug or its not intended to check the signatures.

I considered that, but I highly doubted the behavior differed according to the way it was set, and my example using C++ lambdas does show that it doesn't.

Why not accept any value and convert it to boolean using Lua's logic (nil and false are falsy, all other values are truthy)?

I do not know your requirements, but I would just use lua's boolean logic for evaluating the return value and ignore the type or have the runtime check for return type.

I thought about that a while after my post, and that seems like the best choice indeed. I guess I'll just go with Lua's boolean logic 😄

Thank you very much for your insights!

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

No branches or pull requests

2 participants