compat: SetPoint("POINT") with nothing after it, because ClassicAPI writes it
**A deviation, and the reasoning matters more than the code.** 1.12 raises `Usage:` on a
one-argument `SetPoint`, and that is traced rather than assumed: the `lua_type(L,3)` tag gate
at `0x7a25e6`..`0x7a2620` accepts four tags and an absent argument is none of them, which
this file's own header has said all along. Later clients read it as the default parent at the
same point with no offset, and that is what every Era-era addon writes —
`Cinematic/CameraWheel.lua:243` is `face:SetPoint("CENTER")`.
Allowed because **the real `ClassicAPI.dll` writes that form itself**: its embedded Lua
carries `f:SetPoint("CENTER");` and `tx:SetPoint(q[4]);`, so on the client this build already
announces itself as (`CLASSIC_API_VERSION` is set) the one-argument form has to work, or
ClassicAPI's own windows would not build. Claiming that client and withholding this is the
inconsistent position `stdlib_backport.rs` settled for the stdlib half.
It costs nothing in the ladder because of how it is spelled: the leg resolves to exactly what
the reference's own **three-argument** shorthand does (`RelTarget::DefaultParent`,
`0x7a270e`) — default parent, `rel_point` defaulted to `point`, offsets falling through to
(0, 0). No anchor shape is added; one spelling is mapped onto another the reference answers.
**It lives in `parse_set_point` and not in `compat/`, and that is load-bearing.** My first
attempt wrapped `SetPoint` per method table and `tests::region_map` caught it: the reference
registers it once in the Region method table, so every widget type runs the same function
object, and three wrapped copies broke an identity `region_map.rs` maintains on purpose (an
addon can pull `WorldFrame.GetHeight` off and apply it to a Texture). The header now says so,
so the wrapper route is not tried again.
Two tests pinned the old behaviour and are rewritten rather than deleted:
- The trailing-nil test gets **stronger**. It existed because `lua_type` tells `LUA_TNIL`
(the screen root, `0x7a2710`) from `LUA_TNONE`, and the ladder must see the count Lua saw.
Both forms resolve now, so it resolves a real layout and asserts they land in *different*
places: the explicit nil at the screen's corner, the absent one on its parent.
- One genuine ordering change, asserted: with no third argument there is no tag to gate, so
`SetPoint("MIDDLE")` reports "Unknown region point" where it reported `Usage:`. The
tag-gate-runs-first claim is kept for a *bad* tag, `SetPoint("MIDDLE", true)`.
Movement: Cinematic's load `CameraWheel.lua:243` → `MenuWheel.lua:312` (the secure-template
family, which `compat/classic_api/frame.rs` declines with its own reason).
benilla-ui 2077 passed, 0 failed. Full chain green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
//! The argument ladder of `SetPoint` (`0x7a2540`) and `SetAllPoints` (`0x7a2940`): the reference
|
||||
//! registers both once, in the Region method table (`0x87c9b8`), so every widget type runs the
|
||||
//! same function. Every failure leg raises; none falls back to the parent or does nothing.
|
||||
//! same function. Every failure leg raises; none falls back to the parent or does nothing — with
|
||||
//! the single exception of an **absent** `relativeTo`, which `compat/` reads as the default parent
|
||||
//! and whose reasoning sits at that leg in [`parse_set_point`]. It is written there rather than as
|
||||
//! a wrapper in `compat/` precisely because of the sentence above: one function serves every
|
||||
//! widget type, and three wrapped copies would break the identity `region_map.rs` maintains and
|
||||
//! `tests::region_map` pins.
|
||||
|
||||
use mlua::{Lua, MultiValue, Table, Value};
|
||||
|
||||
@@ -94,7 +99,30 @@ pub(crate) fn parse_set_point(
|
||||
};
|
||||
|
||||
let Some(rel_arg) = args.get(1) else {
|
||||
return Err(usage(who));
|
||||
// **The one relaxation of this ladder, and it is `compat/`'s, not the reference's.** 1.12
|
||||
// raises `Usage:` here — the tag gate above accepts four tags and an absent argument is
|
||||
// none of them — and later clients made `SetPoint("POINT")` mean the default parent at the
|
||||
// same point with no offset.
|
||||
//
|
||||
// Allowed because the real `ClassicAPI.dll` writes that form in its own embedded Lua
|
||||
// (`f:SetPoint("CENTER");`, `tx:SetPoint(q[4]);`), so on the client this build already
|
||||
// announces itself as (`CLASSIC_API_VERSION`) it has to work, or ClassicAPI's own windows
|
||||
// would not build. Same judgement, and the same inconsistency if withheld, as
|
||||
// `compat/classic_api/stdlib_backport.rs`'s; a build dropping `compat/` wants this leg
|
||||
// back as `Err(usage(who))` and nothing else.
|
||||
//
|
||||
// It resolves to exactly what the reference's own *three-argument* shorthand does
|
||||
// (`RelTarget::DefaultParent`, `0x7a270e`): the default parent, `rel_point` defaulted to
|
||||
// `point` below, and the offsets falling through to (0, 0) because there are none to read.
|
||||
// So this adds no anchor shape — it spells one the ladder already answers.
|
||||
let point = point_from_str(&point_str).ok_or_else(|| unknown_point(who))?;
|
||||
return Ok(PointArgs {
|
||||
point,
|
||||
target: RelTarget::DefaultParent,
|
||||
rel_point: point,
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
});
|
||||
};
|
||||
let target = match rel_arg {
|
||||
Value::String(s) => RelTarget::Named(match s.to_str() {
|
||||
|
||||
@@ -126,22 +126,22 @@ fn setpoint_unresolved_name_raises() {
|
||||
assert!(s.take_warnings().is_empty());
|
||||
}
|
||||
|
||||
/// The reference's other `SetPoint` raises: Usage (`0x87cc28`) when `relativeTo` is absent, since
|
||||
/// `lua_type(L,3)` is `TNONE` at the gate (`0x7a25e6-0x7a2620`); "Unknown region point"
|
||||
/// (`0x87cd04`, the 9-entry scan `0x6f1840`); "trying to anchor to itself" (`0x87cca8`,
|
||||
/// `0x87cd54`). The type gates run before the point scan, so an unknown point with no
|
||||
/// `relativeTo` answers Usage.
|
||||
/// The reference's other `SetPoint` raises: "Unknown region point" (`0x87cd04`, the 9-entry scan
|
||||
/// `0x6f1840`) and "trying to anchor to itself" (`0x87cca8`, `0x87cd54`); Usage (`0x87cc28`) for a
|
||||
/// `relativeTo` whose `lua_type` is none of the four the gate takes
|
||||
/// (`0x7a25e6-0x7a2620`). The type gates run before the point scan, so an unknown point with a
|
||||
/// *bad* `relativeTo` answers Usage.
|
||||
///
|
||||
/// **The absent-`relativeTo` leg is no longer one of them**: `object/anchor_args.rs` takes
|
||||
/// `SetPoint("POINT")` as the default parent, because the real `ClassicAPI.dll` writes that form
|
||||
/// itself. With no third argument there is no tag to gate, so an unknown point with no
|
||||
/// `relativeTo` now reports the point rather than Usage — asserted below, since that ordering is
|
||||
/// the thing the change moves.
|
||||
#[test]
|
||||
fn the_other_setpoint_raises() {
|
||||
let s = script();
|
||||
s.run(r#"f = CreateFrame("Frame", "Ladder")"#).unwrap();
|
||||
|
||||
let e = s
|
||||
.run(r#"Ladder:SetPoint("CENTER")"#)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(e.contains("Usage: Ladder:SetPoint(\"point\""), "{e}");
|
||||
|
||||
let e = s
|
||||
.run(r#"Ladder:SetPoint("MIDDLE", nil, "MIDDLE", 0, 0)"#)
|
||||
.unwrap_err()
|
||||
@@ -152,7 +152,17 @@ fn the_other_setpoint_raises() {
|
||||
.run(r#"Ladder:SetPoint("MIDDLE")"#)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(e.contains("Usage:"), "the tag gate runs first: {e}");
|
||||
assert!(
|
||||
e.contains("Ladder:SetPoint(): Unknown region point"),
|
||||
"nothing to tag-gate, so the point scan is what answers: {e}"
|
||||
);
|
||||
|
||||
// A tag the gate does refuse still answers Usage, and still before the point scan.
|
||||
let e = s
|
||||
.run(r#"Ladder:SetPoint("MIDDLE", true)"#)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(e.contains("Usage: Ladder:SetPoint(\"point\""), "{e}");
|
||||
|
||||
let e = s
|
||||
.run(r#"Ladder:SetPoint("CENTER", Ladder, "CENTER", 0, 0)"#)
|
||||
@@ -164,19 +174,49 @@ fn the_other_setpoint_raises() {
|
||||
);
|
||||
}
|
||||
|
||||
/// `lua_type(L,3)` tells `LUA_TNIL` (0, the screen root) from `LUA_TNONE` (-1, Usage), so the
|
||||
/// raise ladder must see the argument count Lua saw, not a padded or trimmed tuple.
|
||||
/// `lua_type(L,3)` tells `LUA_TNIL` (0, the screen root) from `LUA_TNONE` (-1, which the
|
||||
/// reference refuses and `compat/` reads as the default parent), so the ladder must see the
|
||||
/// argument count Lua saw, not a padded or trimmed tuple.
|
||||
///
|
||||
/// Both forms resolve now, and they must resolve to **different anchors** — that distinction is
|
||||
/// the whole reason the count has to survive. A trailing nil is the screen root; an absent one is
|
||||
/// the parent, which here is a 100x100 frame nowhere near the screen's corner.
|
||||
#[test]
|
||||
fn a_trailing_nil_relative_to_is_present_where_an_absent_one_is_not() {
|
||||
let s = script();
|
||||
s.run(r#"f = CreateFrame("Frame", "Trail")"#).unwrap();
|
||||
s.run(r#"Trail:SetPoint("CENTER", nil)"#).unwrap();
|
||||
assert_eq!(s.eval::<i64>("return Trail:GetNumPoints()").unwrap(), 1);
|
||||
let e = s
|
||||
.run(r#"Trail:SetPoint("CENTER")"#)
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(e.contains("Usage:"), "{e}");
|
||||
fn a_trailing_nil_relative_to_is_the_screen_where_an_absent_one_is_the_parent() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
host = CreateFrame("Frame", "TrailHost")
|
||||
TrailHost:SetPoint("BOTTOMLEFT", nil, "BOTTOMLEFT", 300, 200)
|
||||
TrailHost:SetWidth(100) TrailHost:SetHeight(100)
|
||||
nilled = CreateFrame("Frame", "TrailNil", TrailHost)
|
||||
TrailNil:SetWidth(10) TrailNil:SetHeight(10)
|
||||
TrailNil:SetPoint("BOTTOMLEFT", nil)
|
||||
absent = CreateFrame("Frame", "TrailAbsent", TrailHost)
|
||||
TrailAbsent:SetWidth(10) TrailAbsent:SetHeight(10)
|
||||
TrailAbsent:SetPoint("BOTTOMLEFT")
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return TrailNil:GetNumPoints()").unwrap(),
|
||||
1,
|
||||
"a trailing nil is an argument, not an absence"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<(f32, f32)>("return TrailNil:GetLeft(), TrailNil:GetBottom()")
|
||||
.unwrap(),
|
||||
(0.0, 0.0),
|
||||
"the explicit nil anchored to the screen root"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<(f32, f32)>("return TrailAbsent:GetLeft(), TrailAbsent:GetBottom()")
|
||||
.unwrap(),
|
||||
(300.0, 200.0),
|
||||
"the absent one anchored to the parent, as the three-argument shorthand does"
|
||||
);
|
||||
}
|
||||
|
||||
/// An explicit `nil` `relativeTo` is the screen root, not the parent (`0x7a2710` loads it from
|
||||
|
||||
Reference in New Issue
Block a user