path.resolve(base, computedShortString) throws ERR_INVALID_ARG_TYPE where node returns the path.
Repro
import * as path from "node:path";
function seg(n: number): string { return "s" + String(n); }
console.log(path.resolve("/root", "s1")); // node: /root/s1 perry: /root/s1
console.log(path.resolve("/root", seg(1))); // node: /root/s1 perry: TypeError
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string.
Exit code 1. The two-argument and the three-argument forms both fail; only literal segments work.
It is the SSO inline representation, not GC staleness
Bisected by string length — a computed segment longer than the SSO inline threshold works, a short one does not:
function longSeg(n: number): string { return "segment-that-is-definitely-longer-than-sso-" + String(n); }
path.resolve("/root", longSeg(1)); // OK, /root/segment-that-is-...-1
path.resolve("/root", seg(1)); // TypeError
path.resolve(a, b, c) folds to PathResolveJoin(PathResolveJoin(a, b), c) (HIR), and Expr::PathResolveJoin's lowering in crates/perry-codegen/src/expr/arrays_finds.rs unboxes both operands with unbox_to_i64:
let a_handle = unbox_to_i64(blk, &a_box);
let b_handle = unbox_to_i64(blk, &b_box);
let result = blk.call(I64, "js_path_resolve_join", &[(I64, &a_handle), (I64, &b_handle)]);
unbox_to_i64 masks the low 48 bits. For an interned literal that is a real StringHeader*; for an SSO string those bits are the inline characters, so js_path_resolve_join dereferences character data as a header. This is the #214 SSO class — the same hazard Expr::EnvGetDynamic two hundred lines away already documents and routes around with unbox_str_handle.
Same shape, unaudited, in the neighbouring arms: PathExtname, PathNormalize, PathResolve, PathMatchesGlob.
Why it is not a one-line fix
unbox_str_handle calls js_get_string_pointer_unified, whose SSO branch allocates (it materialises the inline string onto the heap). Substituting it into PathResolveJoin therefore introduces a collection point between the two operand unboxes — the #7213 window — so the fix has to arrive with the rooting strategy for that window, not on its own.
Found while migrating expr/arrays_finds.rs for the Layer 1 rooting campaign (#7620 / #7615), and deliberately left there: that PR is IR-identical-or-justified by construction and a semantic fix to five path arms does not belong inside a refactor. Confirmed present on both arms of that PR, i.e. on main.
path.resolve(base, computedShortString)throwsERR_INVALID_ARG_TYPEwhere node returns the path.Repro
Exit code 1. The two-argument and the three-argument forms both fail; only literal segments work.
It is the SSO inline representation, not GC staleness
Bisected by string length — a computed segment longer than the SSO inline threshold works, a short one does not:
path.resolve(a, b, c)folds toPathResolveJoin(PathResolveJoin(a, b), c)(HIR), andExpr::PathResolveJoin's lowering incrates/perry-codegen/src/expr/arrays_finds.rsunboxes both operands withunbox_to_i64:unbox_to_i64masks the low 48 bits. For an interned literal that is a realStringHeader*; for an SSO string those bits are the inline characters, sojs_path_resolve_joindereferences character data as a header. This is the #214 SSO class — the same hazardExpr::EnvGetDynamictwo hundred lines away already documents and routes around withunbox_str_handle.Same shape, unaudited, in the neighbouring arms:
PathExtname,PathNormalize,PathResolve,PathMatchesGlob.Why it is not a one-line fix
unbox_str_handlecallsjs_get_string_pointer_unified, whose SSO branch allocates (it materialises the inline string onto the heap). Substituting it intoPathResolveJointherefore introduces a collection point between the two operand unboxes — the #7213 window — so the fix has to arrive with the rooting strategy for that window, not on its own.Found while migrating
expr/arrays_finds.rsfor the Layer 1 rooting campaign (#7620 / #7615), and deliberately left there: that PR is IR-identical-or-justified by construction and a semantic fix to five path arms does not belong inside a refactor. Confirmed present on both arms of that PR, i.e. onmain.