Skip to content

test(builtin): document physical_equal on a #valtype struct - #4093

Closed
bobzhang wants to merge 1 commit into
mainfrom
test/physical-equal-valtype
Closed

test(builtin): document physical_equal on a #valtype struct#4093
bobzhang wants to merge 1 commit into
mainfrom
test/physical-equal-valtype

Conversation

@bobzhang

Copy link
Copy Markdown
Contributor

What

Adds tests pinning what physical_equal currently does with a #valtype struct Point { x : Int, y : Int }. Separate from #4092 (the NaN cases), though they share a theme.

Why

#valtype changes a type's representation, and physical_equal is %refeq — a comparison of the representation. Together they produce the sharpest instance of the warning already sitting on physical_equal, that its result "may not be consistent across different backends": for a two-field Point, the targets disagree outright.

case native / llvm / wasm js / wasm-gc
physical_equal(p, p) true true
physical_equal(p, q), distinct constructions, equal fields true false
fields differ in x, or only in y false false
physical_equal(Some(p), Some(p)) false true
same via a generic fn[T] call site unchanged unchanged
ordinary (non-#valtype) struct, equal fields false false

Two mechanisms explain the whole table:

  • Where Point is genuinely unboxed (native/llvm/wasm) there is no identity to compare, so physical_equal degrades to a field-wise value comparison — and it is a full one, not first-field-only, since a difference in y alone is caught. Where it stays a heap object (js/wasm-gc), physical_equal remains identity and #valtype changes nothing.
  • Some(p) inverts between the same two groups for the same reason: where Point is a value it can't be represented as a pointer, so Some must allocate and two Some(p)s are distinct; where Point is already a pointer, Some(p) is p and the two are equal.

Arity matters too. A single-field #valtype struct is unwrapped to its field on every target, js and wasm-gc included. So a Double wrapper inherits NaN's non-reflexivity straight through the struct — physical_equal(nan_wrapper, nan_wrapper) is false everywhere. The native/js split above is specifically about multi-field structs.

Layout

Cases that agree on every target live in physical_equal_valtype_test.mbt; the two divergent groups get a file each, split by options(targets:) in builtin/moon.pkg. That way each group's answer is written down explicitly rather than hidden behind a conditional, and a representation change on either side shows up as a visible diff.

Documentation, not specification

Same caveat as #4092, and it bites harder here: intrinsics.mbt warns the result may vary by backend and optimization settings, and this PR is a demonstration of exactly that. Nothing outside these files should depend on the answers.

Testing

Run on all four targets — native, js, wasm, wasm-gc — all green. moon check clean on each; moon info produces no .mbti change (test-only, no public surface).

🤖 Generated with Claude Code

`#valtype` changes a type's representation, and `physical_equal` is
`%refeq` -- a comparison *of* the representation. Putting the two
together produces the sharpest case of the warning already on
`physical_equal`, that its result "may not be consistent across
different backends": for a two-field `#valtype struct Point`, the
targets disagree outright.

  * native / llvm / wasm -- `Point` is genuinely unboxed, so there is no
    identity to compare and `physical_equal` degrades to a field-wise
    value comparison. Two independently constructed `Point`s with equal
    fields *are* physically equal.
  * js / wasm-gc -- a multi-field `#valtype` struct is still a heap
    object, so `physical_equal` stays identity and those same two
    `Point`s are *not* equal, exactly as for an ordinary struct.

`Some(p)` inverts between the same two groups, for the same underlying
reason: where `Point` is a value it cannot be represented as a pointer,
so `Some` allocates and two `Some(p)`s are distinct; where `Point` is
already a pointer, `Some(p)` is `p` itself and the two are equal.

Arity matters as well. A *single*-field `#valtype` struct is unwrapped
to its field on every target including js and wasm-gc, so a `Double`
wrapper inherits NaN's non-reflexivity straight through the struct and
is not physically equal even to itself. The split above is specifically
about multi-field structs.

The agreeing cases live in one file; the two divergent groups get a file
each, split via `options(targets:)` in `moon.pkg`, so each group's
answer is written down explicitly instead of hidden behind a
conditional. As with the other `physical_equal` tests these document
rather than specify -- nothing outside these files should depend on the
answers; they are pinned so a representation change shows up as a
visible diff.

Verified on all four targets (native, js, wasm, wasm-gc); `moon check`
clean on each, and no `.mbti` change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 18, 2026 02:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds builtin tests that document (not specify) how physical_equal behaves for #valtype structs across different backends, including the key divergence between unboxed (native/llvm/wasm) and boxed (js/wasm-gc) representations.

Changes:

  • Introduces a shared test file covering physical_equal cases that are consistent across all targets (reflexivity, unequal fields, array round-trips, and a single-field #valtype wrapper).
  • Adds two target-specific test files that pin the divergent behaviors for multi-field #valtype structs on unboxed vs boxed targets.
  • Updates builtin/moon.pkg to include the per-target test files via options(targets:).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
builtin/physical_equal_valtype_test.mbt Shared, cross-target documentation tests and the #valtype/non-#valtype fixture types.
builtin/physical_equal_valtype_unboxed_test.mbt Pins behavior on targets where multi-field #valtype structs are unboxed (native/llvm/wasm).
builtin/physical_equal_valtype_boxed_test.mbt Pins behavior on targets where multi-field #valtype structs remain boxed (js/wasm-gc).
builtin/moon.pkg Routes the divergent tests to the appropriate targets.
Suppressed comments (2)

builtin/physical_equal_valtype_test.mbt:50

  • RefPoint appears to be a test-local helper type. Consider making it priv to keep the fixture scoped to the test package and avoid accidental export of an abstract-public type.
struct RefPoint {

builtin/physical_equal_valtype_test.mbt:119

  • Same as Point: since DoubleWrapper is just a test fixture, marking it priv keeps the test surface tidy and avoids exporting an abstract-public type from a _test.mbt file.
struct DoubleWrapper {

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

/// The struct under test, and an ordinary (reference-represented) struct with
/// the same fields to contrast against.
#valtype
struct Point {
@coveralls

Copy link
Copy Markdown
Collaborator

Coverage Report for CI Build 6141

Coverage remained the same at 90.859%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 17821
Covered Lines: 16192
Line Coverage: 90.86%
Coverage Strength: 332599.92 hits per line

💛 - Coveralls

@bobzhang bobzhang closed this Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants