-- 5y9: int64_as_number codegen option. Verifies that the opt-in `_n`
-- decoders return Lua number when the decoded value fits in [-2^53, 2^53]
-- and fall back to cdata otherwise. Encodes via the default (cdata-returning)
-- module and decodes via the `_n` module; assertions cover both type and
-- value across every 64-bit kind in c_int64.Wide.
--
-- examples/expected/full_n/c_int64/c_int64_pb.lua is regenerated by
-- `just gen-int64-as-number`, which runs the plugin with
-- `int64_as_number=true` for this single fixture so the default tree
-- stays cdata-typed and existing tests are unaffected. (5y9)
local t = require('luatest')
local ffi = require('ffi')
local INT64 = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')
local wide_default = require('full.c_int64.c_int64_pb')
local wide_n = require('full_n.c_int64.c_int64_pb')
local g = t.group('int64_as_number')
-- The opt-in `_n` decoder lives in the pure-Lua codegen path. With
-- PB_ENABLE_C=1 the generated _decode prologue short-circuits to the
-- C runtime, which makes its own number-vs-cdata choice independent
-- of this codegen flag (luaL_pushint64 returns Lua number for values
-- that fit in double and cdata otherwise — the Tarantool convention).
-- Skip the type-stability assertions in that mode; the value-equality
-- behavior is still covered by the existing c_runtime_int64_test suite.
g.before_each(function()
if os.getenv('PB_ENABLE_C') == '1' then
t.skip('int64_as_number flag is a no-op under PB_ENABLE_C=1 — ' ..
'the C runtime decides number-vs-cdata on its own')
end
end)
-- Confirm the two fixtures share a wire format — the codegen flag only
-- changes the Lua return type, never the bytes that come off the wire.
g.test_encode_bytes_identical = function()
local p = {
a_int64 = INT64(-1234567),
a_uint64 = UINT64(8765432),
a_sint64 = INT64(-987654),
a_fixed64 = UINT64(42),
a_sfixed64 = INT64(-42),
}
t.assert_equals(wide_n.Wide_encode(p), wide_default.Wide_encode(p))
end
-- Values inside the 2^53 window should come back as plain Lua numbers
-- under the _n decoder, but stay cdata under the default decoder.
g.test_small_values_return_number = function()
local p = {
a_int64 = INT64(-123),
a_uint64 = UINT64(456),
a_sint64 = INT64(-789),
a_fixed64 = UINT64(1011),
a_sfixed64 = INT64(-1213),
}
local bytes = wide_default.Wide_encode(p)
local d_n = wide_n.Wide_decode(bytes)
t.assert_equals(type(d_n.a_int64), 'number')
t.assert_equals(type(d_n.a_uint64), 'number')
t.assert_equals(type(d_n.a_sint64), 'number')
t.assert_equals(type(d_n.a_fixed64), 'number')
t.assert_equals(type(d_n.a_sfixed64), 'number')
t.assert_equals(d_n.a_int64, -123)
t.assert_equals(d_n.a_uint64, 456)
t.assert_equals(d_n.a_sint64, -789)
t.assert_equals(d_n.a_fixed64, 1011)
t.assert_equals(d_n.a_sfixed64, -1213)
-- Default decoder stays cdata for everything 64-bit.
local d = wide_default.Wide_decode(bytes)
t.assert_equals(type(d.a_int64), 'cdata')
t.assert_equals(type(d.a_uint64), 'cdata')
t.assert_equals(type(d.a_sint64), 'cdata')
t.assert_equals(type(d.a_fixed64), 'cdata')
t.assert_equals(type(d.a_sfixed64), 'cdata')
end
-- Exactly 2^53 / -2^53 sit on the inclusive boundary (both are powers of
-- two and round-trip a Lua double exactly). Anything one notch past the
-- boundary on the magnitude side falls back to cdata.
g.test_boundary_2p53_returns_number = function()
local p = {
a_int64 = INT64( 0x20000000000000LL), -- 2^53
a_sint64 = INT64(-0x20000000000000LL), -- -2^53
a_uint64 = UINT64(0x20000000000000ULL), -- 2^53
a_fixed64 = UINT64(0x20000000000000ULL), -- 2^53
a_sfixed64 = INT64(-0x20000000000000LL), -- -2^53
}
local d_n = wide_n.Wide_decode(wide_default.Wide_encode(p))
t.assert_equals(type(d_n.a_int64), 'number')
t.assert_equals(type(d_n.a_sint64), 'number')
t.assert_equals(type(d_n.a_uint64), 'number')
t.assert_equals(type(d_n.a_fixed64), 'number')
t.assert_equals(type(d_n.a_sfixed64), 'number')
-- 2^53 is exact as a double; the value comparison passes.
t.assert_equals(d_n.a_int64, 2 ^ 53)
t.assert_equals(d_n.a_uint64, 2 ^ 53)
t.assert_equals(d_n.a_fixed64, 2 ^ 53)
t.assert_equals(d_n.a_sint64, -(2 ^ 53))
t.assert_equals(d_n.a_sfixed64, -(2 ^ 53))
end
g.test_past_2p53_returns_cdata = function()
local p = {
a_int64 = INT64( 0x40000000000000LL), -- 2^54
a_uint64 = UINT64(0x40000000000000ULL),
a_sint64 = INT64(-0x40000000000000LL),
a_fixed64 = UINT64(0x40000000000000ULL),
a_sfixed64 = INT64(-0x40000000000000LL),
}
local d_n = wide_n.Wide_decode(wide_default.Wide_encode(p))
t.assert_equals(type(d_n.a_int64), 'cdata')
t.assert_equals(type(d_n.a_uint64), 'cdata')
t.assert_equals(type(d_n.a_sint64), 'cdata')
t.assert_equals(type(d_n.a_fixed64), 'cdata')
t.assert_equals(type(d_n.a_sfixed64), 'cdata')
-- Value still round-trips correctly (just as cdata, not number).
t.assert_equals(d_n.a_int64, INT64( 0x40000000000000LL))
t.assert_equals(d_n.a_uint64, UINT64(0x40000000000000ULL))
t.assert_equals(d_n.a_sint64, INT64(-0x40000000000000LL))
t.assert_equals(d_n.a_fixed64, UINT64(0x40000000000000ULL))
t.assert_equals(d_n.a_sfixed64, INT64(-0x40000000000000LL))
end
-- Lua-number inputs round-trip clean too: encode accepts numbers (via the
-- existing encode wrappers), decode returns numbers under _n.
g.test_lua_number_inputs_round_trip = function()
local p = {
a_int64 = 100, a_uint64 = 200, a_sint64 = -300,
a_fixed64 = 400, a_sfixed64 = -500,
}
local d_n = wide_n.Wide_decode(wide_default.Wide_encode(p))
t.assert_equals(d_n.a_int64, 100)
t.assert_equals(d_n.a_uint64, 200)
t.assert_equals(d_n.a_sint64, -300)
t.assert_equals(d_n.a_fixed64, 400)
t.assert_equals(d_n.a_sfixed64, -500)
end