Skip to content

Fix exponential backtracking in typescriptArrowFuncDef - #306

Merged
HerringtonDarkholme merged 2 commits into
HerringtonDarkholme:masterfrom
kokosabu:fix-arrowfunc-backtracking
Sep 29, 2026
Merged

HerringtonDarkholme merged 2 commits into
HerringtonDarkholme:masterfrom
kokosabu:fix-arrowfunc-backtracking

Conversation

@kokosabu

@kokosabu kokosabu commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The two paren-based typescriptArrowFuncDef patterns in
syntax/ts-common/function.vim contain a nested quantifier:

/(\%(\_[^()]\+\|(\_[^()]*)\)*)\_s*=>/
start=/(\%(\_[^()]\+\|(\_[^()]*)\)*):/

\%(\_[^()]\+\|...\)* is the classic (a+)* shape. When the pattern fails
(a parenthesized call argument list that is not followed by => / ):), the
backtracking engine tries every way to split the run of non-paren characters,
so matching time doubles with each extra character. This PR drops the inner
\+. The alternation still matches exactly the same strings, but the
backtracking becomes linear.

Related: #135 (same symptom, same shape of reproducer).

Reproduction

Vim with regexpengine=1 (this is the default of the Vim bundled with macOS,
see vim/vim#18782), no vimrc:

const outputDir = cond
  ? ''
  : path.join(this.options.cwd, options.output)
vim -u NONE -N --cmd 'set re=1' --cmd 'syntax on' repro.ts

Vim hangs and eventually reports 'redrawtime' exceeded, syntax highlighting disabled. The time roughly doubles per character inside the parens
(0.78s -> 2.6s -> 10.6s on macOS Vim 9.1 as the argument list grows by two
characters per step).

Verification

Built Vim from master (9.2, patches 1-1143) with its own runtime, ran
synID() over every line of the reproducer and of a ~490-line TypeScript file:

engine before after
re=0 ok ok
re=1 hang (> 6s, killed) ok (< 6s)
re=2 ok ok

Highlighting is unchanged: I dumped synstack() names for every character
position (14,543 positions, including 116 inside typescriptArrowFuncDef from
arrow functions with typed params, defaults, nested parens, generics and
arrow functions inside ternaries / call arguments). The dumps before and after
the change are identical (diff shows no differences).

The test suite that CI runs (Vader! syntax.vader tsx.vader in test/)
passes locally with this change: 149/149 (457 assertions) on macOS Vim 9.1.
I have not run it on Neovim.

merged/ is regenerated with node merge.js. Its only changes are the same
two lines in each of the three files.

The same patterns also exist in Vim's
runtime/syntax/shared/typescriptcommon.vim, lines 1951 and 1956. I can send
the same change to vim/vim after this is merged.

🤖 Generated with Claude Code

kokosabu and others added 2 commits September 30, 2026 00:12
The paren-based typescriptArrowFuncDef patterns contain a nested
quantifier, \%(\_[^()]\+\|...\)*, which backtracks exponentially with the
old regexp engine when the match fails. Drop the inner \+; the matched
language is unchanged.

Refs HerringtonDarkholme#135

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HerringtonDarkholme
HerringtonDarkholme requested a balanced review from Copilot September 29, 2026 15:19
@HerringtonDarkholme

Copy link
Copy Markdown
Owner

Thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The regex transformation preserves the accepted language, removes ambiguous nested repetition, and is consistently propagated to generated files.

Review effort: Balanced
Findings: None

What changed in this PR

Removes exponential backtracking from TypeScript arrow-function syntax matching while preserving highlighting behavior.

Changes:

  • Replaces nested quantified character runs with deterministic single-character repetition.
  • Synchronizes all generated TypeScript syntax bundles.
File Description
syntax/​ts-common/​function.vim Optimizes both parenthesized arrow-function patterns.
merged/​typescriptreact.vim Updates the generated TSX syntax bundle.
merged/​typescriptcommon.vim Updates the generated common syntax bundle.
merged/​typescript.vim Updates the generated TypeScript syntax bundle.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@HerringtonDarkholme
HerringtonDarkholme merged commit a7ad165 into HerringtonDarkholme:master Sep 29, 2026
2 checks passed
@kokosabu

Copy link
Copy Markdown
Contributor Author

Thanks for the quick merge! Sent the same change to Vim's runtime: vim/vim#21400

@kokosabu
kokosabu deleted the fix-arrowfunc-backtracking branch September 29, 2026 16:04
chrisbra pushed a commit to vim/vim that referenced this pull request Sep 29, 2026
…uncDef

Problem:  The paren-based typescriptArrowFuncDef patterns contain a nested
          quantifier, \%(\_[^()]\+\|...\)*, which backtracks exponentially
          with the old regexp engine when the match fails.
Solution: Drop the inner \+; the matched language is unchanged.

closes: #21400
related: HerringtonDarkholme/yats.vim#306

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Miura Yasuyuki <kokosabu@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
zeertzjq added a commit to neovim/neovim that referenced this pull request Sep 30, 2026
…in typescriptArrowFuncDef

Problem:  The paren-based typescriptArrowFuncDef patterns contain a nested
          quantifier, \%(\_[^()]\+\|...\)*, which backtracks exponentially
          with the old regexp engine when the match fails.
Solution: Drop the inner \+; the matched language is unchanged.

closes: vim/vim#21400
related: HerringtonDarkholme/yats.vim#306

vim/vim@bb1d7bc

Co-authored-by: Miura Yasuyuki <kokosabu@gmail.com>
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.

3 participants