Conversation
Follow-up to pjankiewicz#37. The symptom there was real (eval::<f64>() of -0.0 lost the sign bit) but the proposed fix regressed serde: skipping the integer normalization for zero folds plain 0 into Value::Number(0.0), breaking deserialize_any for integer targets and changing the JSON shape. mlua doesn't hit this because its floats bypass the Value representation entirely via the FromLua::from_stack fast path (lua_convert_float!). We add the same layer, mirroring mlua's shape: - FromLua gains an internal from_stack hook (default: materialize a Value via value_from_stack, then from_lua — same as mlua's stack_value path). - FromLuaMulti gains an internal from_stack_multi hook (default: collect Values above base, then from_lua_multi). The blanket single-value impl forwards to from_stack so float fast paths apply on multi-value paths. - f64/f32 override from_stack to read the raw lua_Number with lua_tonumberx directly, falling back to the value-level conversion (string coercion / error messages unchanged). - Function::call and Lua::exec_raw collect results through R::from_stack_multi instead of eagerly materializing Values. Value's whole-number normalization is untouched, so serde and the JSON shape are unaffected (the maintainer's i64/u32/f64 round-trip passes). Value-level from_lua behavior is unchanged. Tests: test_num_conversion's DEVIATION pin is replaced with mlua-parity assertions (sign bit survives), and a new test_negative_zero_round_trips_as_number covers f64/f32 eval, script round trip, argument round trip, and 0/42 integer behavior. Verified the maintainer's serde case (Cfg { count: 0, idx: 0, ratio: 0 }) passes with --features serde.
…ghten tests Review feedback on pjankiewicz#40: - Tuple FromLuaMulti impls now override from_stack_multi: each FromLua slot reads its element directly from the stack via from_stack (so eval::<(f64,)>() and friends keep -0.0's sign bit; previously tuples fell back to the Value-materializing default), and the FromLuaMulti last slot consumes the remainder. The 1-tuple forwards wholesale. - test_negative_zero_round_trips_as_number: replace the duplicated constant-eval assertions with a genuine Rust→Lua→Rust round trip (call with -0.0, return it, including a mixed (i32, f64) tuple), which is the shape the checksum use case actually needs. - f32's value-level from_lua delegates to f64::from_lua again, so its conversion-error messages stay byte-identical to pre-PR behavior (the PR text's 'error messages unchanged' claim now holds exactly). - The maintainer's serde regression case is now a committed test: test_from_value_whole_number_floats_stay_integers in mlua_serde.rs (Cfg { count: 0, idx: 0, ratio: 0 } + JSON shape check). cargo test -p luaur-rt: 253 passed; --features serde: 21 passed in mlua_serde; workspace: 5725 passed, 0 failed. fmt clean.
vi2q
added a commit
to vi2q/luaur
that referenced
this pull request
Sep 14, 2026
`value_from_stack` normalizes whole-number floats to `Value::Integer`, which cannot carry the sign bit of `-0.0`. A host that dispatches dynamically receives call arguments as `Value`s (`MultiValue`) and has no typed float accessor to fall back on, so `-0.0` silently arrives as `+0.0`. Exclude only `-0.0` from the normalization. Plain `0` still becomes `Value::Integer(0)`, so serde integer shapes and `Value::Integer` pattern matches are unaffected -- the regression that reverted the earlier value-level attempt (PR pjankiewicz#37) came from excluding every zero, not just the negative one. Fork-local: upstream keeps the fold for mlua parity and preserves the sign on the typed `f64`/`f32` stack path instead (PR pjankiewicz#40).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #37, using the stack fast path you suggested. Thanks for the detailed review.
What changed
The
value_from_stackchange in #37 regressed serde: plain0becameValue::Number(0.0), which breaksdeserialize_anyfor integer targets. The actual gap was that luaur-rt had no equivalent of mlua's stack-level float conversion.This PR adds that layer while leaving
value_from_stackunchanged:FromLuagains a hiddenfrom_stackhook. Its default materializes aValueand forwards tofrom_lua.FromLuaMultigainsfrom_stack_multi(base, nvals, lua). The single-value and tuple implementations propagate stack conversion to each element, including mixed tuples such as(i32, f64).f64andf32read numeric stack slots directly withlua_tonumberx. Non-number values still use the existing value-level conversion, including its string coercion and error messages.Function::callandLua::exec_rawconvert results throughR::from_stack_multiinstead of materializing every result as aValuefirst.Valuestill folds whole-number floats, including-0.0, toValue::Integer, matching mlua'sstack_value. The sign bit is preserved when converting a numeric stack slot directly tof64orf32; serde and JSON integer shapes remain unchanged.Testing
test_num_conversionnow checks thateval::<f64>()preserves the sign of-0.0.test_negative_zero_round_trips_as_numbercoversf64andf32, a Rust → Lua → Rust round trip,(f64, f64)and(i32, f64)tuple returns, and the IEEE 754-0.0 + 0.0 == +0.0case.test_from_value_whole_number_floats_stay_integersrecords the serde regression from fix(rt): preserve the sign bit of -0.0 #37 and checks the JSON shape.cargo test -p luaur-rt: 253 passed.The original checksum use case (
f64::to_bitsover script-owned state) now works without changing theValuerepresentation. The stack hooks are#[doc(hidden)]; I can adjust their shape if you would prefer a different internal API.