-- 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