views:

108

answers:

3

This works...

if ( tileType == "water" or 
  ( otherObj and otherObj:GetType() == "IceBlock" )) then
  self:SetNoClip( true )
else
  self:SetNoClip( false )
end

- These don't...

self:SetNoClip( tileType == "water" or 
  ( otherObj and otherObj:GetType() == "IceBlock" ))

//----------------------------------------------------------

local noClip = ( tileType == "water" or 
  ( otherObj and otherObj:GetType == "IceBlock" ))
self:SetNoClip( noClip )

The otherObj test just evaluates to whether otherObj is nil or not. The variables given are retrieved in a previous line. The error I get when the application runs is:

unprotected error to call in Lua API(script path...: Did not pass boolean to SetNoClip).

SetNoClip is a function in the application that grabs the argument pushed onto the lua stack via lua_toboolean.

So why does the first work and the second and third return errors?

EDIT:

SetNoClip had this definition.

int GameObject::LuaSetNoClip( lua_State *L ) {
  if ( !lua_isboolean( L, -1 )) {
    LogLuaErr( "Did not pass boolean to SetNoClip for GameObject: " + m_type );
    return luaL_error( L, "Did not pass boolean to SetNoClip" );
  }
  m_noClip = lua_toboolean( L, -1 );
  return 0;
}

The problem is that lua_isboolean doesn't do any implicit type conversion (but lua_toboolean does) and will only return true for literal boolean values. So if it sees nil, it will return that a boolean wasn't passed. I just removed the error check for a boolean literal, since people (including me) commonly rely on arguments that aren't boolean literals being treated correctly as booleans.

A: 

It depends on what goes on inside SetNoClip (i.e. this won't necessarily cause problems in general). However, I'm pretty sure the problem is that and and or return the values on either side rather than evaluating to true or false. In other words,

foo and bar

will return foo if foo is either nil or false, otherwise it will return bar.

In your case, the original code passes true or false to SetNoClip whereas in the other examples you're either passing "water", or a boolean to SetNoClip. SetNoClip might be choking on the string.

Cogwheel - Matthew Orlando
+1  A: 

The and operator returns its first argument if that value is something considered non-true, and its second argument otherwise.

The or operator returns its first argument if it is something considered true, and its second argument otherwise.

Thus, A or (B and C) can theoretically return any of the following:

  • A if A is a value considered true
  • B if B is a value considered false and A is considered false
  • C if neither of the above is the case

Note that A, B, and C are not required to be actual boolean values - only things that can be interpreted as booleans. Since nil is considered a false value, it's possible that the second case is occurring for you, and the argument being passed to SetNoClip is nil instead of true or false.

One option to fix this would be to explicitly compare with nil, instead of just using the object:

( otherObj ~= nil and otherObj:GetType() == "IceBlock" )

since the ~= operator is guaranteed to return a boolean.

Amber
Thanks, exactly what I needed to know. However, that doesn't explain while it is still evaluated to a boolean in the if conditional. Do if statements do implicit type conversion while argument passing does not (i.e. if ( nil ) converts nil to false, but SetNoClip( nil ) does not).
random
Because that's how `if` was designed. When we say "considered true" and "considered false", `if` is one of the things doing the considering. All values are considered `true` except `false` and `nil`.
Cogwheel - Matthew Orlando
Yes, but why wouldn't that be applied to passing function arguments as well? If the function is expecting a boolean, and receives nil, you would think that it would convert it to false, like what conditional statements are designed to do. Is there a reason for this?
random
It's a design flaw of the function, IMO. Functions that expect booleans should treat values the same way `if` does.
Cogwheel - Matthew Orlando
Well, SetNoClip uses lua_toboolean. And the documentation says...Converts the Lua value at the given acceptable index to a C boolean value (0 or 1). Like all tests in Lua, lua_toboolean returns 1 for any Lua value different from false and nil; otherwise it returns 0. It also returns 0 when called with a non-valid index. (If you want to accept only actual boolean values, use lua_isboolean to test the value's type.)So nil should be evaluated to false when lua_toboolean is called. What is the problem here?P.S. Had to unanswer. Doesn't work. (Didn't save file before testing).
random
Setting this as the answer, since it hits the main point of the problem, but part of it is something that (unfortunately) couldn't have been solved from the information I gave. Posting more info in edit.
random
+1  A: 

As you have seen, any object can have a boolean interpretation in Lua. Because of this, your implementation of LuaSetNoClip is not true to the spirit & practice of Lua if you expect just a boolean object to be passed. You should use lua_toboolean directly.

I think the only argument check you can reasonably do is luaL_checkany:

int GameObject::LuaSetNoClip( lua_State *L ) {
  luaL_checkany(L, 1);
  m_noClip = lua_toboolean( L, 1 );
  return 0;
}
kaizer.se
Yes, that is pretty much exactly how the function has been changed. Note the part in the edit saying 'had' this definition.
random