Make {} optional for the types tag

This commit is contained in:
Fabien Potencier
2025-01-06 13:51:14 +01:00
parent fb715039c3
commit dff6fdd675
4 changed files with 39 additions and 17 deletions
+1
View File
@@ -1,5 +1,6 @@
# 3.19.0 (2025-XX-XX) # 3.19.0 (2025-XX-XX)
* Make `{}` optional for the `types` tag
* Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes * Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes
# 3.18.0 (2024-12-29) # 3.18.0 (2024-12-29)
+22 -9
View File
@@ -6,17 +6,23 @@
The ``types`` tag was added in Twig 3.13. This tag is **experimental** and The ``types`` tag was added in Twig 3.13. This tag is **experimental** and
can change based on usage and feedback. can change based on usage and feedback.
The ``types`` tag declares the types of template variables. Use the ``types`` tag to declare the type of a variable:
.. note:: .. code-block:: twig
The types declared in a template are local to that template and must not be {% types is_correct: 'boolean' %}
propagated to included templates. This is because a template can be {% types score: 'number' %}
included from multiple different places, each potentially having different
variable types.
Here is how to declare that ``is_correct`` is a boolean, while ``score`` is a Or multiple variables:
number (see note below):
.. code-block:: twig
{% types
is_correct: 'boolean',
score: 'number',
%}
You can also enclose types with ``{}``:
.. code-block:: twig .. code-block:: twig
@@ -25,7 +31,7 @@ number (see note below):
score: 'number', score: 'number',
} %} } %}
You can declare variables as optional by adding the ``?`` suffix: Declare optional variables by adding a ``?`` suffix:
.. code-block:: twig .. code-block:: twig
@@ -43,6 +49,13 @@ validate variables or their types, this tag enables extensions to do this.
Additionally, :ref:`Twig extensions <creating_extensions>` can analyze these Additionally, :ref:`Twig extensions <creating_extensions>` can analyze these
tags to perform compile-time and runtime analysis of templates. tags to perform compile-time and runtime analysis of templates.
.. note::
The types declared in a template are local to that template and must not be
propagated to included templates. This is because a template can be
included from multiple different places, each potentially having different
variable types.
.. note:: .. note::
The syntax for and contents of type strings are intentionally left out of The syntax for and contents of type strings are intentionally left out of
+7 -8
View File
@@ -31,9 +31,7 @@ final class TypesTokenParser extends AbstractTokenParser
public function parse(Token $token): Node public function parse(Token $token): Node
{ {
$stream = $this->parser->getStream(); $stream = $this->parser->getStream();
$types = $this->parseSimpleMappingExpression($stream); $types = $this->parseSimpleMappingExpression($stream);
$stream->expect(Token::BLOCK_END_TYPE); $stream->expect(Token::BLOCK_END_TYPE);
return new TypesNode($types, $token->getLine()); return new TypesNode($types, $token->getLine());
@@ -46,17 +44,15 @@ final class TypesTokenParser extends AbstractTokenParser
*/ */
private function parseSimpleMappingExpression(TokenStream $stream): array private function parseSimpleMappingExpression(TokenStream $stream): array
{ {
$stream->expect(Token::PUNCTUATION_TYPE, '{', 'A mapping element was expected'); $enclosed = null !== $stream->nextIf(Token::PUNCTUATION_TYPE, '{');
$types = []; $types = [];
$first = true; $first = true;
while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { while (!($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE))) {
if (!$first) { if (!$first) {
$stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma'); $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A type string must be followed by a comma');
// trailing ,? // trailing ,?
if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { if ($stream->test(Token::PUNCTUATION_TYPE, '}') || $stream->test(Token::BLOCK_END_TYPE)) {
break; break;
} }
} }
@@ -78,7 +74,10 @@ final class TypesTokenParser extends AbstractTokenParser
'optional' => $isOptional, 'optional' => $isOptional,
]; ];
} }
$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed');
if ($enclosed) {
$stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened mapping is not properly closed');
}
return $types; return $types;
} }
@@ -64,6 +64,15 @@ class TypesTokenParserTest extends TestCase
'baz' => ['type' => 'baz', 'optional' => false], 'baz' => ['type' => 'baz', 'optional' => false],
], ],
], ],
// without {} enclosing
[
'{% types foo: "foo", bar: "bar" %}',
[
'foo' => ['type' => 'foo', 'optional' => false],
'bar' => ['type' => 'bar', 'optional' => false],
],
],
]; ];
} }
} }