Summary
String.prototype.replace(regex, replacement) returns [object Object] instead of a string whenever the replacement contains a backreference like $1. typeof the result is "object", not "string". Works correctly in Node/V8.
Repro
const s = "abc123";
const out = s.replace(/^"(.*)"$/, "$1"); // pattern doesn't even match here
console.log("out=" + out); // prints: out=[object Object]
console.log("typeof=" + typeof out); // prints: typeof=object
Compile and run:
perry compile repro.ts -o repro
./repro
Expected vs actual
out=abc123 // JS semantics: pattern doesn't match, original string returned
typeof=string
Actual:
out=[object Object]
typeof=object
Impact
This silently breaks any code that uses .replace(regex, "$1") (or $2, $&, etc.) to strip wrappers, extract submatches, normalize values, etc. Downstream code then gets [object Object] wherever a string was expected — no error thrown, so the failure surfaces far from the root cause.
Real-world hit: perry-hub's multipart parser does boundaryMatch[1].replace(/^"(.*)"$/, '$1') to strip optional quotes from a Content-Type boundary. The delimiter becomes --[object Object], nothing splits, every POST /api/v1/build returns Missing 'manifest' field regardless of the actual payload. Patched in the hub by dropping the backreference and using explicit startsWith/endsWith + substring instead.
Minimal workaround
// Instead of: s.replace(/^"(.*)"$/, "$1")
let out = s;
if (out.startsWith('"') && out.endsWith('"') && out.length >= 2) {
out = out.substring(1, out.length - 1);
}
For generic capture-group extraction, use .match()[n] directly — match() with capture groups works (just verified: m[1] returns the captured string with typeof === "string"). It's the replacement-string $n / $& path that's broken.
Other replacement forms — not tested yet
Also worth sanity-checking whether .replace(regex, (match, p1) => ...) (function replacer) works, and whether $& / $' / $ / $$ are similarly broken. I haven't tested those — if any of those also return [object Object], it points to a single shared codepath for replacement-string interpolation.
Environment
- perry version: 0.5.164 (also observed at 0.5.162, 0.5.158)
- Platform-independent — codegen/runtime bug.
Suggested compile-time warning
Same pattern as the follow-up to #136: when .replace(regex, string) is called and the string literal contains $n / $& / $' / $` / $$, either make it work or emit a compile-time error until it does. Today it's a silent no-op that produces [object Object] and no diagnostic.
Summary
String.prototype.replace(regex, replacement)returns[object Object]instead of a string whenever the replacement contains a backreference like$1.typeofthe result is"object", not"string". Works correctly in Node/V8.Repro
Compile and run:
Expected vs actual
Actual:
Impact
This silently breaks any code that uses
.replace(regex, "$1")(or$2,$&, etc.) to strip wrappers, extract submatches, normalize values, etc. Downstream code then gets[object Object]wherever a string was expected — no error thrown, so the failure surfaces far from the root cause.Real-world hit: perry-hub's multipart parser does
boundaryMatch[1].replace(/^"(.*)"$/, '$1')to strip optional quotes from a Content-Type boundary. The delimiter becomes--[object Object], nothing splits, everyPOST /api/v1/buildreturnsMissing 'manifest' fieldregardless of the actual payload. Patched in the hub by dropping the backreference and using explicitstartsWith/endsWith+substringinstead.Minimal workaround
For generic capture-group extraction, use
.match()[n]directly —match()with capture groups works (just verified:m[1]returns the captured string withtypeof === "string"). It's the replacement-string$n/$&path that's broken.Other replacement forms — not tested yet
Also worth sanity-checking whether
.replace(regex, (match, p1) => ...)(function replacer) works, and whether$&/$'/$/$$are similarly broken. I haven't tested those — if any of those also return[object Object], it points to a single shared codepath for replacement-string interpolation.Environment
Suggested compile-time warning
Same pattern as the follow-up to #136: when
.replace(regex, string)is called and the string literal contains$n/$&/$'/$`/$$, either make it work or emit a compile-time error until it does. Today it's a silent no-op that produces[object Object]and no diagnostic.