Hold a clipping widget to its box, and check that it is

`Scroll` reports `LEFTOVER` on both axes because it clips its content to its
box: it can neither take less of one nor honestly ask for more. `Masked` is
the other widget that clips and was passing its inner's size up, so a mask
over something taller than its box asked to be placed at the length it had
just cut off. It reports its box now, for the same reason.

The `debug_assert` the handoff has been asking for is the one that would have
caught both, narrowed to what is actually true: a widget that set a mask this
draw has to report inside the box it drew in. Reported as "does not exceed
the box" it fires on ordinary overflow instead -- measured, a hundred fuzzer
trees produce thousands of them, every one a text too tall for the box it was
offered, which is what a text is meant to say.

`tests/cases/scroll.rs` has a clipping widget that reports its content, to
show the assertion catches it.

Checked: fmt, clippy, 104 tests, all five shrinker cases at 300 seeds, 100
generated seeds, five examples byte-identical at 1920x1200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 03:48:23 -04:00
1 parent bdab55824f
commit 95fb4f962c
3 files changed
+49 -1

No files matched your search

+19
View File
@@ -366,6 +366,16 @@ impl UiRenderState {
x: rules.x.apply(size.x),
y: rules.y.apply(size.y),
};
// A widget that clipped its contents to its box drew nothing outside
// it, so reporting more than the box asks to be placed at a length it
// does not occupy -- and its parent would place the part it cut off.
// Overflowing is otherwise ordinary: a text too tall for the box it
// was offered reports the height it needs.
debug_assert!(
mask == info.mask || AXES.into_iter().all(|axis| within_box(size, px, axis)),
"'{}' ({id:?}) clips to {px:?} and reports {size}",
rsc.widgets().label(id),
);
let holds = [own[0].and(under[0]), own[1].and(under[1])];
debug_assert!(
holds[0].contains(px.x) && holds[1].contains(px.y),
@@ -1011,6 +1021,15 @@ impl UiRenderState {
}
}
/// Whether what a widget reports along `axis` is inside the box it drew in.
/// A share is a length only to whoever divides one, so it is not a claim
/// about this box and cannot exceed it.
fn within_box(size: Size, px: PxVec2, axis: Axis) -> bool {
let len = size.axis(axis);
let box_len = px.axis(axis);
len.leftover != Weight::ZERO || box_len.mul(len.rel) + len.px <= box_len
}
/// The same box is the same number of steps, both of these being lengths on
/// the grid rather than floats to be compared for nearness.
fn same_px(a: PxVec2, b: PxVec2) -> bool {