Skip to content

Commit af40907

Browse files
committed
feature #4402 Rework macros handling (fabpot)
This PR was squashed before being merged into the 3.x branch. Discussion ---------- Rework macros handling Commits ------- f1481be Remove MacroAutoImportNodeVisitor 7269388 Simplify code 1105964 Rework macros handling
2 parents bcceaa3 + f1481be commit af40907

21 files changed

+317
-208
lines changed

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# 3.15.0 (2024-XX-XX)
22

3+
* Remove `MacroAutoImportNodeVisitor`
4+
* Deprecate `MethodCallExpression` in favor of `MacroReferenceExpression`
5+
* Fix support for the "is defined" test on `_self.xxx` (auto-imported) macros
6+
* Fix support for the "is defined" test on inherited macros
37
* Add named arguments support for the dot operator arguments (`foo.bar(some: arg)`)
48
* Add named arguments support for macros
59
* Add a new `guard` tag that allows to test if some Twig callables are available at compilation time

doc/deprecated.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ Nodes
170170
deprecated as of Twig 3.12: ``arguments``, ``callable``, ``is_variadic``,
171171
and ``dynamic_name``.
172172

173+
* The ``MethodCallExpression`` class is deprecated as of Twig 3.15, use
174+
``MacroReferenceExpression`` instead.
175+
173176
Node Visitors
174177
-------------
175178

src/ExpressionParser.php

Lines changed: 94 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
use Twig\Node\Expression\ConditionalExpression;
2525
use Twig\Node\Expression\ConstantExpression;
2626
use Twig\Node\Expression\GetAttrExpression;
27-
use Twig\Node\Expression\MethodCallExpression;
27+
use Twig\Node\Expression\MacroReferenceExpression;
2828
use Twig\Node\Expression\NameExpression;
2929
use Twig\Node\Expression\TempNameExpression;
3030
use Twig\Node\Expression\TestExpression;
@@ -33,6 +33,7 @@
3333
use Twig\Node\Expression\Unary\NotUnary;
3434
use Twig\Node\Expression\Unary\PosUnary;
3535
use Twig\Node\Expression\Unary\SpreadUnary;
36+
use Twig\Node\Expression\Variable\TemplateVariable;
3637
use Twig\Node\Node;
3738
use Twig\Node\Nodes;
3839

@@ -531,11 +532,7 @@ public function parsePostfixExpression($node)
531532
public function getFunctionNode($name, $line)
532533
{
533534
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
534-
$arguments = $this->createArguments($line);
535-
$node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
536-
$node->setAttribute('safe', true);
537-
538-
return $node;
535+
return new MacroReferenceExpression(new TemplateVariable($alias['node'], $line), $alias['name'], $this->createArguments($line), $line);
539536
}
540537

541538
$args = $this->parseOnlyArguments();
@@ -561,84 +558,11 @@ public function getFunctionNode($name, $line)
561558

562559
public function parseSubscriptExpression($node)
563560
{
564-
$stream = $this->parser->getStream();
565-
$token = $stream->next();
566-
$lineno = $token->getLine();
567-
$arguments = new ArrayExpression([], $lineno);
568-
$type = Template::ANY_CALL;
569-
if ('.' == $token->getValue()) {
570-
if ($stream->nextIf(Token::PUNCTUATION_TYPE, '(')) {
571-
$arg = $this->parseExpression();
572-
$stream->expect(Token::PUNCTUATION_TYPE, ')');
573-
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
574-
$type = Template::METHOD_CALL;
575-
$arguments = $this->createArguments($lineno);
576-
}
577-
578-
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
579-
}
580-
$token = $stream->next();
581-
if (
582-
Token::NAME_TYPE == $token->getType()
583-
||
584-
Token::NUMBER_TYPE == $token->getType()
585-
||
586-
(Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
587-
) {
588-
$arg = new ConstantExpression($token->getValue(), $lineno);
589-
590-
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
591-
$type = Template::METHOD_CALL;
592-
$arguments = $this->createArguments($lineno);
593-
}
594-
} else {
595-
throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext());
596-
}
597-
598-
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
599-
$name = $arg->getAttribute('value');
600-
601-
$node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
602-
$node->setAttribute('safe', true);
603-
604-
return $node;
605-
}
606-
} else {
607-
$type = Template::ARRAY_CALL;
608-
609-
// slice?
610-
$slice = false;
611-
if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {
612-
$slice = true;
613-
$arg = new ConstantExpression(0, $token->getLine());
614-
} else {
615-
$arg = $this->parseExpression();
616-
}
617-
618-
if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {
619-
$slice = true;
620-
}
621-
622-
if ($slice) {
623-
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
624-
$length = new ConstantExpression(null, $token->getLine());
625-
} else {
626-
$length = $this->parseExpression();
627-
}
628-
629-
$filter = $this->getFilter('slice', $token->getLine());
630-
$arguments = new Nodes([$arg, $length]);
631-
$filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine());
632-
633-
$stream->expect(Token::PUNCTUATION_TYPE, ']');
634-
635-
return $filter;
636-
}
637-
638-
$stream->expect(Token::PUNCTUATION_TYPE, ']');
561+
if ('.' === $this->parser->getStream()->next()->getValue()) {
562+
return $this->parseSubscriptExpressionDot($node);
639563
}
640564

641-
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
565+
return $this->parseSubscriptExpressionArray($node);
642566
}
643567

644568
public function parseFilterExpression($node)
@@ -825,8 +749,7 @@ private function parseTestExpression(Node $node): TestExpression
825749
}
826750

827751
if ('defined' === $test->getName() && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
828-
$node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
829-
$node->setAttribute('safe', true);
752+
$node = new MacroReferenceExpression(new TemplateVariable($alias['node'], $node->getTemplateLine()), $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
830753
}
831754

832755
$ready = $test instanceof TwigTest;
@@ -997,4 +920,91 @@ public function parseOnlyArguments()
997920

998921
return new Nodes($args);
999922
}
923+
924+
private function parseSubscriptExpressionDot(Node $node): AbstractExpression
925+
{
926+
$stream = $this->parser->getStream();
927+
$token = $stream->getCurrent();
928+
$lineno = $token->getLine();
929+
$arguments = new ArrayExpression([], $lineno);
930+
$type = Template::ANY_CALL;
931+
932+
if ($stream->nextIf(Token::PUNCTUATION_TYPE, '(')) {
933+
$attribute = $this->parseExpression();
934+
$stream->expect(Token::PUNCTUATION_TYPE, ')');
935+
} else {
936+
$token = $stream->next();
937+
if (
938+
Token::NAME_TYPE == $token->getType()
939+
||
940+
Token::NUMBER_TYPE == $token->getType()
941+
||
942+
(Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
943+
) {
944+
$attribute = new ConstantExpression($token->getValue(), $token->getLine());
945+
} else {
946+
throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $token->getLine(), $stream->getSourceContext());
947+
}
948+
}
949+
950+
if ($stream->test(Token::PUNCTUATION_TYPE, '(')) {
951+
$type = Template::METHOD_CALL;
952+
$arguments = $this->createArguments($token->getLine());
953+
}
954+
955+
if (
956+
$node instanceof NameExpression
957+
&&
958+
(
959+
null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))
960+
||
961+
'_self' === $node->getAttribute('name') && $attribute instanceof ConstantExpression
962+
)
963+
) {
964+
return new MacroReferenceExpression(new TemplateVariable($node->getAttribute('name'), $node->getTemplateLine()), 'macro_'.$attribute->getAttribute('value'), $arguments, $node->getTemplateLine());
965+
}
966+
967+
return new GetAttrExpression($node, $attribute, $arguments, $type, $lineno);
968+
}
969+
970+
private function parseSubscriptExpressionArray(Node $node): AbstractExpression
971+
{
972+
$stream = $this->parser->getStream();
973+
$token = $stream->getCurrent();
974+
$lineno = $token->getLine();
975+
$arguments = new ArrayExpression([], $lineno);
976+
977+
// slice?
978+
$slice = false;
979+
if ($stream->test(Token::PUNCTUATION_TYPE, ':')) {
980+
$slice = true;
981+
$attribute = new ConstantExpression(0, $token->getLine());
982+
} else {
983+
$attribute = $this->parseExpression();
984+
}
985+
986+
if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) {
987+
$slice = true;
988+
}
989+
990+
if ($slice) {
991+
if ($stream->test(Token::PUNCTUATION_TYPE, ']')) {
992+
$length = new ConstantExpression(null, $token->getLine());
993+
} else {
994+
$length = $this->parseExpression();
995+
}
996+
997+
$filter = $this->getFilter('slice', $token->getLine());
998+
$arguments = new Nodes([$attribute, $length]);
999+
$filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine());
1000+
1001+
$stream->expect(Token::PUNCTUATION_TYPE, ']');
1002+
1003+
return $filter;
1004+
}
1005+
1006+
$stream->expect(Token::PUNCTUATION_TYPE, ']');
1007+
1008+
return new GetAttrExpression($node, $attribute, $arguments, Template::ARRAY_CALL, $lineno);
1009+
}
10001010
}

src/Extension/CoreExtension.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
use Twig\Node\Expression\Unary\NotUnary;
6767
use Twig\Node\Expression\Unary\PosUnary;
6868
use Twig\Node\Node;
69-
use Twig\NodeVisitor\MacroAutoImportNodeVisitor;
7069
use Twig\OperatorPrecedenceChange;
7170
use Twig\Parser;
7271
use Twig\Source;
@@ -293,7 +292,7 @@ public function getTests(): array
293292

294293
public function getNodeVisitors(): array
295294
{
296-
return [new MacroAutoImportNodeVisitor()];
295+
return [];
297296
}
298297

299298
public function getOperators(): array
@@ -1286,6 +1285,8 @@ public static function capitalize(string $charset, $string): string
12861285

12871286
/**
12881287
* @internal
1288+
*
1289+
* to be removed in 4.0
12891290
*/
12901291
public static function callMacro(Template $template, string $method, array $args, int $lineno, array $context, Source $source)
12911292
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Twig.
5+
*
6+
* (c) Fabien Potencier
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Twig\Node\Expression;
13+
14+
use Twig\Compiler;
15+
use Twig\Node\Expression\Variable\TemplateVariable;
16+
17+
/**
18+
* Represents a macro call node.
19+
*
20+
* @author Fabien Potencier <[email protected]>
21+
*/
22+
class MacroReferenceExpression extends AbstractExpression
23+
{
24+
public function __construct(TemplateVariable $template, string $name, AbstractExpression $arguments, int $lineno)
25+
{
26+
parent::__construct(['template' => $template, 'arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno);
27+
}
28+
29+
public function compile(Compiler $compiler): void
30+
{
31+
if ($this->getAttribute('is_defined_test')) {
32+
$compiler
33+
->subcompile($this->getNode('template'))
34+
->raw('->hasMacro(')
35+
->repr($this->getAttribute('name'))
36+
->raw(', $context')
37+
->raw(')')
38+
;
39+
40+
return;
41+
}
42+
43+
$compiler
44+
->subcompile($this->getNode('template'))
45+
->raw('->getTemplateForMacro(')
46+
->repr($this->getAttribute('name'))
47+
->raw(', $context, ')
48+
->repr($this->getTemplateLine())
49+
->raw(', $this->getSourceContext())')
50+
->raw(\sprintf('->%s', $this->getAttribute('name')))
51+
->raw('(...')
52+
->subcompile($this->getNode('arguments'))
53+
->raw(')')
54+
;
55+
}
56+
}

src/Node/Expression/MethodCallExpression.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class MethodCallExpression extends AbstractExpression
1717
{
1818
public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno)
1919
{
20+
trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MacroReferenceExpression::class);
21+
2022
parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno);
2123

2224
if ($node instanceof NameExpression) {

src/Node/Expression/Test/DefinedTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Twig\Node\Expression\ConstantExpression;
2121
use Twig\Node\Expression\FunctionExpression;
2222
use Twig\Node\Expression\GetAttrExpression;
23+
use Twig\Node\Expression\MacroReferenceExpression;
2324
use Twig\Node\Expression\MethodCallExpression;
2425
use Twig\Node\Expression\NameExpression;
2526
use Twig\Node\Expression\TestExpression;
@@ -55,6 +56,8 @@ public function __construct(Node $node, TwigTest|string $name, ?Node $arguments,
5556
$this->changeIgnoreStrictCheck($node);
5657
} elseif ($node instanceof BlockReferenceExpression) {
5758
$node->setAttribute('is_defined_test', true);
59+
} elseif ($node instanceof MacroReferenceExpression) {
60+
$node->setAttribute('is_defined_test', true);
5861
} elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) {
5962
$node->setAttribute('is_defined_test', true);
6063
} elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Twig.
5+
*
6+
* (c) Fabien Potencier
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Twig\Node\Expression\Variable;
13+
14+
use Twig\Compiler;
15+
use Twig\Node\Expression\NameExpression;
16+
17+
final class TemplateVariable extends NameExpression
18+
{
19+
public function compile(Compiler $compiler): void
20+
{
21+
if ('_self' === $this->getAttribute('name')) {
22+
$compiler->raw('$this');
23+
} else {
24+
$compiler
25+
->raw('$macros[')
26+
->string($this->getAttribute('name'))
27+
->raw(']')
28+
;
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)