Summary
Perry has a partial fancy_regex fallback for JavaScript RegExp features that Rust's regex crate rejects, including lookbehind assertions. The fallback is used by String.prototype.match() and RegExp.prototype.exec(), but RegExp.prototype.test() still calls the placeholder Rust regex directly, so lookbehind tests return false even when Node returns true.
This leaves Node-compatible packages broken when they use lookbehind through .test(), including dynamic receiver cases that dispatch through the same runtime helper.
Expected Node behavior
On current Node:
/(?<=@)\w+/.test("user@example") // true
/(?<!@)\w+/.test("user@example") // true
/(?<=@)\w+/.exec("user@example")?.[0] // "example"
"user@example".match(/(?<=@)\w+/)?.[0] // "example"
Current Perry evidence
The current runtime already detects unsupported regex patterns in crates/perry-runtime/src/regex.rs:
get_or_compile_regex() falls back to fancy_regex::Regex when regex::Regex::new() rejects the translated pattern, then stores a never-matching [^^\s\S] placeholder for the ordinary regex pointer.
js_string_match() checks lookup_fancy_regex(re) before using the placeholder, so lookbehind .match() can work.
js_regexp_exec() checks FANCY_CACHE before using the placeholder, so lookbehind .exec() can work.
js_regexp_test() does not check lookup_fancy_regex() or FANCY_CACHE; it only does regex.is_match(str_data), which means unsupported patterns test against the never-matching placeholder.
crates/perry-codegen/src/expr/instance_misc1.rs lowers regex.test(str) directly to js_regexp_test, and dynamic RegExp receiver dispatch in the runtime also calls js_regexp_test for the test method.
test-parity/known_failures.json still has test_gap_regexp_advanced for lookbehind assertions, but the current implementation suggests the remaining sharp edge is specifically the .test() path rather than all lookbehind matching.
Suggested test surface
Add focused parity coverage for:
console.log(/(?<=@)\w+/.test("user@example"));
console.log(/(?<!@)\w+/.test("user@example"));
const build = () => /(?<=@)\w+/;
console.log(build().test("user@example"));
The expected output should match Node. It would also be useful to keep .match() and .exec() assertions nearby to prevent regressions in the paths that already use the fallback.
Scope / non-goals
This issue is not asking for full RegExp spec parity, Unicode property escape support, or a replacement for the regex engine. It is scoped to routing RegExp.prototype.test() through the existing fancy_regex fallback whenever the runtime has already cached one for the pattern.
Duplicate checks
Searched issues and PRs for:
RegExp lookbehind
regex lookbehind
lookbehind test regex
RegExp.test
js_regexp_test lookbehind
fancy_regex regexp test
Related but not duplicates: closed #17 covered broad lookbehind support, and merged PR #975 wired string.match() to the fallback. I did not find an open issue for the remaining .test() path.
Summary
Perry has a partial
fancy_regexfallback for JavaScript RegExp features that Rust'sregexcrate rejects, including lookbehind assertions. The fallback is used byString.prototype.match()andRegExp.prototype.exec(), butRegExp.prototype.test()still calls the placeholder Rust regex directly, so lookbehind tests returnfalseeven when Node returnstrue.This leaves Node-compatible packages broken when they use lookbehind through
.test(), including dynamic receiver cases that dispatch through the same runtime helper.Expected Node behavior
On current Node:
Current Perry evidence
The current runtime already detects unsupported regex patterns in
crates/perry-runtime/src/regex.rs:get_or_compile_regex()falls back tofancy_regex::Regexwhenregex::Regex::new()rejects the translated pattern, then stores a never-matching[^^\s\S]placeholder for the ordinary regex pointer.js_string_match()checkslookup_fancy_regex(re)before using the placeholder, so lookbehind.match()can work.js_regexp_exec()checksFANCY_CACHEbefore using the placeholder, so lookbehind.exec()can work.js_regexp_test()does not checklookup_fancy_regex()orFANCY_CACHE; it only doesregex.is_match(str_data), which means unsupported patterns test against the never-matching placeholder.crates/perry-codegen/src/expr/instance_misc1.rslowersregex.test(str)directly tojs_regexp_test, and dynamic RegExp receiver dispatch in the runtime also callsjs_regexp_testfor thetestmethod.test-parity/known_failures.jsonstill hastest_gap_regexp_advancedfor lookbehind assertions, but the current implementation suggests the remaining sharp edge is specifically the.test()path rather than all lookbehind matching.Suggested test surface
Add focused parity coverage for:
The expected output should match Node. It would also be useful to keep
.match()and.exec()assertions nearby to prevent regressions in the paths that already use the fallback.Scope / non-goals
This issue is not asking for full RegExp spec parity, Unicode property escape support, or a replacement for the regex engine. It is scoped to routing
RegExp.prototype.test()through the existingfancy_regexfallback whenever the runtime has already cached one for the pattern.Duplicate checks
Searched issues and PRs for:
RegExp lookbehindregex lookbehindlookbehind test regexRegExp.testjs_regexp_test lookbehindfancy_regex regexp testRelated but not duplicates: closed #17 covered broad lookbehind support, and merged PR #975 wired
string.match()to the fallback. I did not find an open issue for the remaining.test()path.