Skip to content

fix(runtime): string.match() honors fancy-regex fallback (date-fns format)#975

Merged
proggeramlug merged 1 commit into
mainfrom
fix/string-match-fancy-regex-fallback
May 18, 2026
Merged

fix(runtime): string.match() honors fancy-regex fallback (date-fns format)#975
proggeramlug merged 1 commit into
mainfrom
fix/string-match-fancy-regex-fallback

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

  • date-fns 4.x format() got past constructFrom (PR feat(runtime): .constructor on Date/Array/Object instances #973) but immediately tripped on formatStr.match(/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g).map(...) because the regex contains a (\w)\1* backreference. The Rust regex crate rejects backreferences, so get_or_compile_regex substituted a never-match [^\s\S] placeholder and stashed the real pattern in FANCY_CACHE for fancy-regex to handle.
  • RegExp.prototype.exec already wired up the fancy fallback (lookup before falling through), but String.prototype.match didn't. formatStr.match(...) therefore returned null, and the chained .map(...) blew up with NULL_PTR_METHOD_CALL.
  • Add lookup_fancy_regex(re) helper, have js_string_match consult FANCY_CACHE first. Global flag walks fre.find_iter; non-global returns the full match + capture groups via fre.captures. Pattern + flags + array shape match the standard-regex path exactly.

Validation:

  • format(new Date(2024, 0, 15), 'yyyy-MM-dd')2024-01-15 (matches Node).
  • Also exercised 'HH:mm:ss', 'yyyy-MM-dd HH:mm:ss', 'MMM yyyy', 'EEEE, MMMM do yyyy' — byte-for-byte parity.
  • test-files/test_date_fns_format.ts covers the underlying regex shape (global + non-global + no-match) without importing date-fns directly.

Follow-ups (not in this PR): js_string_match_all, js_regexp_test, and js_string_search_regex still consult only the placeholder for fancy patterns. None are on date-fns' format() hot path; the same lookup_fancy_regex helper can be wired in when needed.

Test plan

  • cargo build --release -p perry-runtime -p perry-stdlib -p perry
  • format(new Date(2024, 0, 15), 'yyyy-MM-dd') returns 2024-01-15 via compilePackages: ["date-fns"]
  • Multi-format parity check vs node --experimental-strip-types
  • test-files/test_date_fns_format.ts matches Node for (\w)\1* global + non-global + no-match
  • CI: lint, cargo-test, parity, compile-smoke, api-docs-drift, security-audit

…te-fns format)

date-fns 4.x format() crashed at `formatStr.match(formattingTokensRegExp).map(...)`
because the format-token regex `(\w)\1*` uses a backreference that the Rust regex
crate rejects. get_or_compile_regex already stashed the compiled fancy-regex in
FANCY_CACHE and substituted a never-match `[^\s\S]` placeholder, but js_string_match
never consulted FANCY_CACHE — so `string.match(re)` always returned null for
backreferenced patterns, and the downstream `.map(...)` crashed with
NULL_PTR_METHOD_CALL.

Add lookup_fancy_regex() helper and have js_string_match check FANCY_CACHE before
falling through to the standard regex.crate path, mirroring the same fallback that
js_regexp_exec already had. Global flag uses fre.find_iter; non-global returns
the full match + capture groups via fre.captures.

Validation: format(new Date(2024,0,15), 'yyyy-MM-dd') -> 2024-01-15 (matches Node),
plus 'HH:mm:ss', 'yyyy-MM-dd HH:mm:ss', 'MMM yyyy', 'EEEE, MMMM do yyyy' all
byte-for-byte parity with `node --experimental-strip-types`.
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.

1 participant