There are some very confusing behaviors in Lua C API. Here are some explanations for these. (Tested on Lua 5.4)

1. Type of number, integer, string

Operations like lua_isnumber, lua_pushinteger, lua_isstring don’t mean checking the value’s type:

  • Values with type number can also get true of lua_isstring. (Number is also string)
  • There are no integer types in Lua. Values generated by lua_pushinteger has type number.
  • Only values generated by lua_pushinteger can get true of lua_isinteger. (But get false after lua_tostring)
  • lua_isnumber gets true if the value is a real number or a string but convertible to number.

lua_tostring will implicitly convert number to string:

  • After calling lua_tostring on a value of type number, the value’s type will change to string implicitly, but it is still lua_isnumber.
  • After calling lua_tostring on a value generated by lua_pushinteger, it cannot get true of lua_isinteger anymore. (String is always not integer)

Test cases

Lua value generated by lua_type lua_typename lua_isinteger lua_isnumber lua_isstring
lua_pushinteger LUA_TNUMBER : 3 number true true true
lua_pushinteger then lua_tostring LUA_TSTRING : 4 string false true true
lua_pushnumber[1] LUA_TNUMBER : 3 number false true true
lua_pushnumber[1] then lua_tostring LUA_TSTRING : 4 string false true true
lua_pushstring on string of number[2] LUA_TSTRING : 4 string false true true
lua_pushstring on string of not number[3] LUA_TSTRING : 4 string false false true

[1] Applies to all float or integer numbers. e.g. lua_pushnumber(L, 0), lua_pushnumber(L, 123), lua_pushnumber(L, 0.1), etc.

[2] e.g. “123”, “1.23”, “12.”, “.12”, “-1.23”, “+1.23”, “1.2e3”, “-1.2E-3”, etc.

[3] e.g. “1.2.3”, “1+2”, “hello”, “inf”, etc.

Conclusion

  1. Integer is not a type. Integer is a special case of number.
  2. Number is also string.
  3. String is also number if it is convertible to number.
  4. String is always not integer.
  5. lua_tostring implicitly convert number to string.

More to say

Not like lua_tostring, luaL_tolstring won’t convert number to string. But it can convert any value to string, while lua_tostring will return NULL if the value is not a string or number.

2. Type of userdata, light userdata

Userdata and light userdata have different type ID, but they have a same type name. lua_islightuserdata is a subset of lua_isuserdata:

  • lua_isuserdata returns true for both userdata and light userdata. (Light userdata is userdata)
  • lua_islightuserdata only returns true for light userdata.

Test cases

Lua value generated by lua_type lua_typename lua_islightuserdata lua_isuserdata
lua_pushlightuserdata LUA_TLIGHTUSERDATA : 2 userdata true true
lua_newuserdata LUA_TUSERDATA : 7 userdata false true

Conclusion

  1. Type ID of light userdata is “LUA_TLIGHTUSERDATA” (value 2), type ID of userdata is “LUA_TUSERDATA” (value 7), but they have a same type name “userdata”.
  2. Light userdata is a subset of userdata, although they have different type ID.
  3. Light userdata is userdata.

3. Metatable of light userdata

Light userdata (unlike heavy userdata) have no per-value metatables. All light userdata share the same metatable, which by default is not set (nil).