Skip to content

feat: arrow functions support#957

Open
michaeldk wants to merge 22 commits intotwigjs:masterfrom
michaeldk:feature/arrow-functions
Open

feat: arrow functions support#957
michaeldk wants to merge 22 commits intotwigjs:masterfrom
michaeldk:feature/arrow-functions

Conversation

@michaeldk
Copy link
Copy Markdown

Arrow function support

Fixes #652

Arrow function support matching the Twig template language specification, with five filters that consume them: filter, map, reduce, sort, and find.

{% for product in products|filter(p => p.stock > 10) %}
    {{ product.name }}
{% endfor %}

{{ items|map(v => v.name)|join(', ') }}

{{ totals|reduce((carry, v) => carry + v, 0) }}

{{ items|sort((a, b) => a.price - b.price) }}

{{ users|find(u => u.id == target_id) }}

All forms from the Twig spec are supported:

  • Single parameter without parentheses: v => v > 3
  • Single parameter with parentheses: (v) => v > 3
  • Multiple parameters: (value, key) => key ~ ': ' ~ value
  • Access to outer template context: v => v > min_threshold
  • Complex body expressions: ternaries, nested filters, operators, property access
  • Nested arrow functions: items|map(v => v.children|filter(c => c.active))
  • Chaining: items|filter(v => v > 0)|map(v => v * 2)|sort((a, b) => a - b)

How it works

Arrow functions are handled by a pre-compilation pass inserted between tokenize and compile:

tokenize → preprocessArrows → compile (shunting-yard) → parse
  1. => is registered as a token type (arrowOperator) so the tokenizer doesn't crash on it
  2. preprocessArrows scans the flat token array, finds => tokens, extracts parameter names (backward look), collects body tokens (forward look with depth tracking), recursively handles nested arrows, sub-compiles the body into an RPN stack, and replaces the entire span with a single arrowFunction token
  3. The shunting-yard compiler sees arrowFunction as a single atomic token — no special cases needed
  4. During parse, the arrow token captures the current context and passes through parseParams as an opaque object, landing as params[0] in the filter function
  5. Filters invoke the arrow via evaluateArrow, which creates a scoped context (outer context + arrow params), clones the body tokens (since parse mutates tokens), and evaluates via parseAsync

The shunting-yard loop is untouched. No existing handler's compile or parse method is modified. The only change to an existing function is one line added to Twig.expression.compile to call preprocessArrows.

Async support

All filters fully support async expressions inside arrow bodies via renderAsync(), addressing the concern raised by @ericmorand in #874.

  • filter, map, find: use Twig.Promise.all — handles sync and async transparently
  • reduce: uses Twig.async.forEach for sequential iteration (each step depends on previous carry)
  • sort: tries native Array.sort with sync evaluation first (zero overhead for the common case); if an async expression is detected, falls back to an async merge sort that chains comparisons through Twig.Promise
{# async works in all filters #}
{{ items|map(v => v|asyncTransform)|join(',') }}
{{ items|filter(v => v|asyncCheck)|join(',') }}
{{ items|sort((a, b) => a|asyncKey - b|asyncKey) }}

…ue in arrays and objects using arrow functions
@michaeldk michaeldk changed the title Feature/arrow functions Arrow functions support Mar 28, 2026
@michaeldk michaeldk changed the title Arrow functions support feat: arrow functions support Apr 1, 2026
@michaeldk
Copy link
Copy Markdown
Author

michaeldk commented Apr 4, 2026

Any idea if this could get discussed anytime soon? @willrowe

@willrowe
Copy link
Copy Markdown
Collaborator

@michaeldk unfortunately, this is a very tricky feature to implement.

@michaeldk
Copy link
Copy Markdown
Author

@willrowe Any issue with the one I am suggesting here?

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.

Add filter, map and reduce filters with support for arrow functions

2 participants