diff --git a/crates/benilla-ui/src/script/stdlib.rs b/crates/benilla-ui/src/script/stdlib.rs index 8fd77600..bd108152 100644 --- a/crates/benilla-ui/src/script/stdlib.rs +++ b/crates/benilla-ui/src/script/stdlib.rs @@ -311,11 +311,20 @@ do local _gsub = string.gsub function wipe(t) - -- `t = t or {}` and a returned table: TurtleWoW's `Blizzard_RaidUI.lua:77,116` clears its - -- `RAID_SUBGROUP_LISTS` with `t[i] = wipe(t[i])` and then `tinsert`s into it, so `wipe` of - -- a never-populated entry must hand back a table to insert into. - t = t or {} - for k in pairs(t) do t[k] = nil end + -- `Globals.lua:126`'s body: the `tremove` walk down from `getn` resets 5.0's remembered + -- table size, which a bare `pairs` clear leaves stale — a list refilled after `wipe` would + -- then count the old length and read holes (LFT's group list). A nil (or non-table) hands + -- back a table, as TurtleWoW's `Blizzard_RaidUI.lua:77,116` clears `RAID_SUBGROUP_LISTS` + -- with `t[i] = wipe(t[i])` and then `tinsert`s into it. + if type(t) ~= "table" then + return {} + end + for i = getn(t), 1, -1 do + tremove(t, i) + end + for k in pairs(t) do + rawset(t, k, nil) + end return t end diff --git a/crates/benilla-ui/src/script/tests/stdlib.rs b/crates/benilla-ui/src/script/tests/stdlib.rs index 5de1dab1..9cee177c 100644 --- a/crates/benilla-ui/src/script/tests/stdlib.rs +++ b/crates/benilla-ui/src/script/tests/stdlib.rs @@ -71,29 +71,40 @@ fn stdlib_aliases_and_helpers() { } /// `wipe` empties the table IN PLACE (the reference's `UIFrameFade_CreateInfo` reuses the same -/// `fadeInfo` table across calls) and returns nothing. +/// `fadeInfo` table across calls) and hands the same table back, as TurtleWoW's `Globals.lua:126` +/// does; the `tremove` walk resets 5.0's remembered size. #[test] -fn wipe_empties_in_place_and_returns_nothing() { +fn wipe_empties_in_place_and_returns_the_table() { let s = script(); assert_eq!( s.eval::( r#" local t = { "a", 2, [ "k" ] = true } local held = t - wipe(t) - return getn(t) == 0 and t.k == nil and held == t and 1 or 0 + local back = wipe(t) + return getn(t) == 0 and t.k == nil and held == t and back == t and 1 or 0 "# ) .unwrap(), 1, - "wipe must empty the caller's own table, not replace it, and must leave no keys" + "wipe must empty the caller's own table, leave no keys, and return it" + ); + // The remembered size resets, so a list refilled after `wipe` counts from 1: a stale size is + // the nil-index crash TurtleWoW's LFT group list hit on its second fill. + assert_eq!( + s.eval::( + r#" + local t = {} + tinsert(t, "a"); tinsert(t, "b"); tinsert(t, "c") + wipe(t) + tinsert(t, "x") + return getn(t) + "# + ) + .unwrap(), + 1, + "a refill after wipe must count from 1, not the wiped length" ); - // 1.12's VM has no `select` (a 5.1 addition), so the arity is read by packing into locals: - // only a no-value return leaves both nil. - let returns_nothing: bool = s - .eval("local a, b = wipe({1}) return a == nil and b == nil") - .unwrap(); - assert!(returns_nothing, "wipe returns no values"); } /// `strsplit` splits on PLAIN text, not Lua patterns: a `.` is a full stop, not "any character".