* 3.x: (26 commits)
Remove the documentation comments compilation overhead
Clarify source function trust requirements
Throw on PCRE errors in the matches operator
Document that reusing a non-rewindable iterator after destructuring is unsupported
Release destructuring temporaries after assignment
Deprecate prefixed macro definedness checks
Fix duplicate macro deprecation wording
Throw when list formatting fails
Document that sequence destructuring consumes one value per pattern slot
Fix the html_attr documentation about iterables in data attributes
Warn about untrusted input with the default Tempest markdown converter
Document that overriding MacroNode::compile() is not supported anymore
Merge overlapping CHANGELOG entries for the destructuring fatal error fix
Document that include_only keeps global variables available
Remove lazy macro import resolution
Honor date formatter prototype calendars
Fix Stringable keys for ArrayAccess implementations
Fix repeated object destructuring evaluation
Restore void return type compatibility for extension points
Reject destructuring patterns containing no variables
...
# Conflicts:
# CHANGELOG
# doc/deprecated.rst
# doc/filters/format_datetime.rst
# extra/twig-extra-bundle/DependencyInjection/Compiler/MissingExtensionSuggestorPass.php
# extra/twig-extra-bundle/DependencyInjection/TwigExtraExtension.php
# extra/twig-extra-bundle/TwigExtraBundle.php
# src/MacroNamespace.php
# src/Node/MacrosNode.php
# src/Parser.php
# src/Test/IntegrationTestCase.php
# src/Test/NodeTestCase.php
# tests/CallMacroTest.php
# tests/ExpressionParserTest.php
# tests/Fixtures/macros/duplicate_definition.legacy.test
# tests/Node/MacrosTest.php
# tests/ParserTest.php
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Remove the documentation comments compilation overhead
Documentation comments are now attached to semantic nodes during parsing, removing the dedicated AST traversal. Token parsers returning placeholder nodes can select the semantic documentation target.
Commits
-------
60eed4ccd5 Remove the documentation comments compilation overhead
This PR was merged into the 3.x branch.
Discussion
----------
Release destructuring temporaries after assignment
The compiled destructuring code kept the right-hand side alive in a hidden local until the end of rendering, wasting memory on large arrays and iterators. The temporary is now cleared as part of the compiled expression.
The way it works:
For this Twig expression:
```twig
{% do {name, email: address} = user %}
```
The previous generated expression was conceptually:
```php
[$context["name"], $context["address"]] = [
getAttribute($_v0 = $context["user"], "name"),
getAttribute($_v0, "email"),
];
```
The branch now generates:
```php
[
[$context["name"], $context["address"]] = [
getAttribute($_v0 = $context["user"], "name"),
getAttribute($_v0, "email"),
],
$_v0 = null,
][0]
```
The important wrapper is:
```php
[$originalExpression, $_v0 = null][0]
```
PHP evaluates array elements from left to right:
1. The original destructuring assignment runs.
2. The temporary variable is set to `null`, releasing its reference.
3. `[0]` returns the result of the original expression.
This preserves Twig’s rule that a destructuring expression returns its right-hand value.
Commits
-------
c459ef0bdd Release destructuring temporaries after assignment
This PR was merged into the 3.x branch.
Discussion
----------
Clarify source function trust requirements
Commits
-------
fdfef2b14a Clarify source function trust requirements
This PR was merged into the 3.x branch.
Discussion
----------
Report regular expression errors from the matches operator
The `matches` operator now throws a `RuntimeError` when PCRE cannot evaluate a regular expression instead of silently treating the error as a non-match.
This makes failures such as exhausted backtrack limits visible to template authors.
Commits
-------
e5347301d7 Throw on PCRE errors in the matches operator
This PR was merged into the 3.x branch.
Discussion
----------
Deprecate prefixed macro definedness checks
Testing a macro through a legacy `macro_`-prefixed name would have silently returned `false` in Twig 4.0 without ever warning; `has()` now triggers the same deprecation as `call()`.
Commits
-------
977aef7172 Deprecate prefixed macro definedness checks
This PR was merged into the 3.x branch.
Discussion
----------
Fix duplicate macro deprecation wording
With three or more definitions of the same macro, the deprecation mislabeled the previous duplicate as the "first definition"; it now reports "previous definition" and "new definition" lines accurately.
Commits
-------
8a37332def Fix duplicate macro deprecation wording
This PR was merged into the 3.x branch.
Discussion
----------
Throw when list formatting fails
`format_list` silently returned an empty string when `IntlListFormatter` fails (for instance on malformed UTF-8); it now throws a `RuntimeError` with the formatter's error message.
Commits
-------
87b930ae67 Throw when list formatting fails
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Fix documentation inaccuracies found during the 3.29 review
Aligns the docs with actual behavior: `include_only` globals, `html_attr` `data-*` iterables, destructuring slot consumption and iterator reuse, a caution for the Tempest converter, `MacroNode::compile()` overrides, and a duplicated CHANGELOG entry.
Commits
-------
59f7d67848 Document that reusing a non-rewindable iterator after destructuring is unsupported
099fa3471a Document that sequence destructuring consumes one value per pattern slot
abbdf82823 Fix the html_attr documentation about iterables in data attributes
be220e6fd8 Warn about untrusted input with the default Tempest markdown converter
1205b8b6ca Document that overriding MacroNode::compile() is not supported anymore
7bd052dd91 Merge overlapping CHANGELOG entries for the destructuring fatal error fix
207f873739 Document that include_only keeps global variables available
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Remove lazy macro import resolution
Reverts the fix for #4879
This removes the unreleased lazy resolution of template-level macro imports, which introduced complex and surprising behavior around template state, blocks, interrupted renders and sandboxing.
Macros called from another template should import their dependencies inside their own body. The documentation now explains this pattern.
Commits
-------
cf971e1a59 Remove lazy macro import resolution
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Honor date formatter prototype calendars
This fixes calendar selection when an `IntlDateFormatter` prototype is configured.
An explicit calendar now takes precedence, followed by the prototype calendar, with Gregorian used as the final fallback. In particular, the `TRADITIONAL` calendar is no longer mistaken for an absent value because its constant value is zero.
Commits
-------
1de0bfceb4 Honor date formatter prototype calendars
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Fix Stringable keys for ArrayAccess implementations
This restores support for `Stringable` keys on string-keyed `ArrayAccess` implementations such as `ArrayObject` and `ArrayIterator`, while preserving object keys for `SplObjectStorage`.
The object key is attempted first and is converted to a string only when the implementation rejects it. The optimized and strict lookup paths now share the same behavior without duplicate existence checks.
Commits
-------
f3f1649955 Fix Stringable keys for ArrayAccess implementations
This PR was merged into the 3.x branch.
Discussion
----------
Evaluate object destructuring expressions once
This ensures the right-hand expression of an object or mapping destructuring assignment is evaluated exactly once.
All properties are now read from the same resolved value, avoiding repeated side effects and unnecessary work while preserving assignment order and return semantics.
Commits
-------
609376f491 Fix repeated object destructuring evaluation
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Restore void return type compatibility for extension points
This restores the PHPDoc-only `void` return types on non-final extension points so subclasses written for Twig 3.28 continue to load on Twig 3.29.
Native return types remain on final classes and test methods where they are backward compatible. PHP CS Fixer is configured to preserve the compatible signatures.
Commits
-------
6bbbb49c3e Restore void return type compatibility for extension points
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Reject destructuring patterns containing no variables
This rejects sequence destructuring patterns containing only empty slots, such as `[,]` and `[,,]`.
These patterns previously compiled to an empty PHP list assignment and caused an uncatchable fatal error. They now produce a Twig `SyntaxError` with the template source and line.
Commits
-------
a3a318face Reject destructuring patterns containing no variables
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Extract `htmlAttrValue()` from `html_attr` for standalone attribute rendering
The per-value resolution behind `html_attr` is extracted into a new public `HtmlExtension::htmlAttrValue()`, returning the unescaped value or `null` to omit the attribute; `html_attr()` now delegates to it, output unchanged, existing tests untouched. This lets third parties render a single attribute exactly like `html_attr` without a Twig `Environment`, since the resolution is escaper-free. symfony/ux#3820 and symfony/ux#3821 depend on this PR.
The `data-*` branch only tested `is_scalar()`, so a `\Stringable` was JSON-encoded instead of using its string representation; the same object already rendered its string form in `title` or `class`, and `AttributeValueInterface` was already excluded from that branch.
| Value in `data-value` | Before | After |
| --- | --- | --- |
| a `\Stringable` | `data-value="{}"` | `data-value="hello"` |
| a `\Stringable` that is also `JsonSerializable` | `data-value=""01JABC""` | `data-value="01JABC"` |
Commits
-------
9b18e3757d Extract `htmlAttrValue()` from `html_attr` for standalone attribute rendering
This PR was merged into the 3.x branch.
Discussion
----------
Fix an empty destructuring pattern triggering a PHP fatal error instead of a SyntaxError
Commits
-------
a2b023397e Fix an empty destructuring pattern triggering a PHP fatal error instead of a SyntaxError
This PR was merged into the 3.x branch.
Discussion
----------
Fix array destructuring from a Traversable
Sequence destructuring compiled its right-hand side straight into `array_pad()`, which only accepts arrays. Destructuring from any `Traversable` crashed with an uncaught `TypeError` at render time:
```twig
{% do [a, b] = items %}
```
with `items` being e.g. an `ArrayIterator` or a generator:
```
array_pad(): Argument #1 ($array) must be of type array, ArrayIterator given
```
This change compiles the right-hand side through an `iterator_to_array()` coercion when it is a `Traversable`, matching how spread (`[...traversable]`), `merge`, and `slice` already accept Traversables:
```php
[$context["a"], $context["b"]] = array_pad(($_v0 = ($context["items"] ?? null)) instanceof \Traversable ? iterator_to_array($_v0) : $_v0, 2, null);
```
Behavior for arrays and other types is unchanged; scalars still fail as before (now via the same `array_pad` TypeError path).
Commits
-------
e2014eb92a Fix Traversable sequence destructuring semantics
9de1b3db98 Fix array destructuring from a Traversable
This PR was squashed before being merged into the 3.x branch.
Discussion
----------
Register the missing extra callables in MissingExtensionSuggestor
`Extensions.php` mirrors what each `twig/*-extra` package declares, so `MissingExtensionSuggestor` can answer an unknown name with `try running "composer require twig/string-extra"` rather than a bare `Unknown "slug" filter`.
This PR adds the twelve callables missing from it:
* `html-extra`: `html_attr_merge` and `html_attr_type` filters, `html_cva` and `html_attr` functions
* `intl-extra`: `format_list` filter, `language_names`, `script_names`, `country_names`, `locale_names`, `currency_names` and `timezone_names` functions
* `string-extra`: `slug`, `plural` and `singular` filters
`ExtensionsTest` checks the catalog both ways:
* every listed name resolves on an `Environment` holding the extension,
* every declared filter, function and tag is listed
Commits
-------
4212ac1303 Test extra extension catalog against local sources
dba79330db Register the missing extra callables in MissingExtensionSuggestor