fix: accept scalar-vector unions in floor and sign - #2962
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new identity-generic signatures for cpuFloor/cpuSign introduce type unsoundness for scalar numeric literals unless a scalar number -> number overload is retained.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates the TypeScript typing of the CPU-facing std.floor and std.sign implementations so that inputs typed as scalar/vector unions (e.g. number | d.v2f) are accepted and preserve union return types, and adds a regression test covering the union case.
Changes:
- Simplifies
cpuFloor/cpuSignoverload structure to allow scalar-vector union inputs to type-check. - Adds a regression test exercising
signandfloorwith anumber | v2f-typed value.
File summaries
| File | Description |
|---|---|
| packages/typegpu/src/std/numeric.ts | Adjusts CPU-side function typings for floor and sign to support scalar/vector union inputs. |
| packages/typegpu/tests/std/numeric/sign.test.ts | Adds a regression test ensuring sign and floor accept `number |
Review details
Suppressed comments (1)
packages/typegpu/src/std/numeric.ts:1005
cpuSignis currently typed as an identity generic (<T>(e: T): T), which makes scalar numeric literals preserve their literal type (e.g.const x = 0.5 as const; sign(x)would be typed as0.5but runtime returns1). Keeping a scalar overload returningnumberavoids this unsoundness while still allowingnumber | v2f-style unions via a single generic overload (same pattern ascpuAbsat numeric.ts:114-116).
function cpuSign<T extends AnySignedVecInstance | number>(e: T): T {
return generalizeFn(Math.sign, [e]);
}
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Important
The union fix works, but the PR's claim that "narrow scalar and vector calls keep their precise return types" isn't fully true: the merged single generic makes scalar literal calls return the literal type instead of number. E.g. floor(1.5) is now typed 1.5 while its runtime value is 1, and sign(2.5) is typed 2.5 while returning 1. The repo already has an established pattern that keeps the scalar overload and accepts unions (cpuFract, cpuMax, cpuMin, cpuMix), so this can be fixed without giving up the new union support.
Reviewed changes
packages/typegpu/src/std/numeric.ts— collapsed thenumber/AnyFloatVecInstanceoverload pairs ofcpuFloorandcpuSigninto single identity generics over… | number, so anumber | vecunion is accepted while the implementation is unchanged.packages/typegpu/tests/std/numeric/sign.test.ts— added a scalar-vector union regression test exercising bothsignandfloorwith anumber | v2fvalue (real regression guard viatest:types, since theascast is erased at runtime).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Since the prior Pullfrog review (on 40e3649e), commit afb81e44 addresses the flagged scalar-literal precision loss:
packages/typegpu/src/std/numeric.ts— restored the scalar(value: number): numberoverload on top of the union generic for bothcpuFloorandcpuSign, matching the establishedcpuAbs/cpuFract/cpuMax/cpuMin/cpuMixpattern. Scalar calls again widen tonumber, whilenumber | vecunions still pass through the generic.packages/typegpu/tests/std/numeric/sign.test.ts— addedexpectTypeOf(sign(2.5 as const)).toEqualTypeOf<number>()andexpectTypeOf(floor(1.5 as const)).toEqualTypeOf<number>()to pin the widened scalar return type; verified these fail (TS2344) if the scalar overloads are removed, so they genuinely guard the fix.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

The CPU-facing overloads for
std.floorandstd.signsplit scalars and vectors into separate signatures. TypeScript therefore rejected a value typed asnumber | d.v2f, even though both sides of that union are valid inputs and the implementation already handles both.This replaces the split overloads with one identity-preserving generic over the existing scalar/vector domain. Narrow scalar and vector calls keep their precise return types, while unions now pass through as the same union. The regression exercises both functions with a
number | v2finput.Verification:
typegpupackage buildCloses #2821.