From b3cbcedcedbb9a7fec488fa89296fc5936663f10 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 7 Feb 2026 08:50:23 +0100 Subject: [PATCH] Move to PHPStan level 5 --- phpstan.neon.dist | 2 +- src/Environment.php | 2 +- src/ExpressionParser/Infix/ArgumentsTrait.php | 2 ++ src/ExpressionParser/Infix/FunctionExpressionParser.php | 6 +++++- src/ExpressionParser/Infix/IsExpressionParser.php | 5 ++++- src/Extension/CoreExtension.php | 5 ++++- src/Node/Expression/Filter/DefaultFilter.php | 1 + src/Node/IfNode.php | 2 ++ src/NodeVisitor/EscaperNodeVisitor.php | 4 ++++ src/Util/CallableArgumentsExtractor.php | 7 +++++-- 10 files changed, 29 insertions(+), 7 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d3077dc48..4bab014fc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,7 +2,7 @@ includes: - phpstan-baseline.neon parameters: - level: 4 + level: 5 paths: - src excludePaths: diff --git a/src/Environment.php b/src/Environment.php index 2abb315aa..7069bbfaa 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -495,7 +495,7 @@ class Environment return $this->load($name); } - throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + throw new LoaderError(\sprintf('Unable to find one of the following templates: "%s".', implode('", "', array_map(fn ($name) => $name instanceof TemplateWrapper ? $name->getTemplateName() : $name, $names)))); } /** diff --git a/src/ExpressionParser/Infix/ArgumentsTrait.php b/src/ExpressionParser/Infix/ArgumentsTrait.php index 96bde555d..5c6bd8068 100644 --- a/src/ExpressionParser/Infix/ArgumentsTrait.php +++ b/src/ExpressionParser/Infix/ArgumentsTrait.php @@ -12,6 +12,7 @@ namespace Twig\ExpressionParser\Infix; use Twig\Error\SyntaxError; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\Binary\SetBinary; use Twig\Node\Expression\Unary\SpreadUnary; @@ -27,6 +28,7 @@ trait ArgumentsTrait { $arguments = new ArrayExpression([], $line); foreach ($this->parseNamedArguments($parser, $parseOpenParenthesis) as $k => $n) { + /** @var AbstractExpression $n */ $arguments->addElement($n, new LocalVariable($k, $line)); } diff --git a/src/ExpressionParser/Infix/FunctionExpressionParser.php b/src/ExpressionParser/Infix/FunctionExpressionParser.php index 951246432..242f04126 100644 --- a/src/ExpressionParser/Infix/FunctionExpressionParser.php +++ b/src/ExpressionParser/Infix/FunctionExpressionParser.php @@ -20,6 +20,7 @@ use Twig\Node\EmptyNode; use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\Variable\ContextVariable; +use Twig\Node\Expression\Variable\TemplateVariable; use Twig\Parser; use Twig\Token; @@ -40,7 +41,10 @@ final class FunctionExpressionParser extends AbstractExpressionParser implements $name = $expr->getAttribute('name'); if (null !== $alias = $parser->getImportedSymbol('function', $name)) { - return new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], $this->parseCallableArguments($parser, $line, false), $line); + /** @var TemplateVariable $var */ + $var = $alias['node']->getNode('var'); + + return new MacroReferenceExpression($var, $alias['name'], $this->parseCallableArguments($parser, $line, false), $line); } $args = $this->parseNamedArguments($parser, false); diff --git a/src/ExpressionParser/Infix/IsExpressionParser.php b/src/ExpressionParser/Infix/IsExpressionParser.php index b684ac60f..0a319edfc 100644 --- a/src/ExpressionParser/Infix/IsExpressionParser.php +++ b/src/ExpressionParser/Infix/IsExpressionParser.php @@ -19,6 +19,7 @@ use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\Variable\ContextVariable; +use Twig\Node\Expression\Variable\TemplateVariable; use Twig\Node\Nodes; use Twig\Parser; use Twig\Token; @@ -43,7 +44,9 @@ class IsExpressionParser extends AbstractExpressionParser implements InfixExpres } if ('defined' === $test->getName() && $expr instanceof ContextVariable && null !== $alias = $parser->getImportedSymbol('function', $expr->getAttribute('name'))) { - $expr = new MacroReferenceExpression($alias['node']->getNode('var'), $alias['name'], new ArrayExpression([], $expr->getTemplateLine()), $expr->getTemplateLine()); + /** @var TemplateVariable $var */ + $var = $alias['node']->getNode('var'); + $expr = new MacroReferenceExpression($var, $alias['name'], new ArrayExpression([], $expr->getTemplateLine()), $expr->getTemplateLine()); } return new ($test->getNodeClass())($expr, $test, $arguments, $stream->getCurrent()->getLine()); diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 90ae4b1ae..f426c17e1 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -2041,7 +2041,10 @@ final class CoreExtension extends AbstractExtension $dep->triggerDeprecation($src->getPath() ?: $src->getName(), $line); */ - return new GetAttrExpression($args[0], $args[1], $args[2] ?? null, Template::ANY_CALL, $line); + /** @var ArrayExpression|ContextVariable|null $arguments */ + $arguments = $args[2] ?? null; + + return new GetAttrExpression($args[0], $args[1], $arguments, Template::ANY_CALL, $line); } /** diff --git a/src/Node/Expression/Filter/DefaultFilter.php b/src/Node/Expression/Filter/DefaultFilter.php index 2964f3bb1..b00b16a4f 100644 --- a/src/Node/Expression/Filter/DefaultFilter.php +++ b/src/Node/Expression/Filter/DefaultFilter.php @@ -42,6 +42,7 @@ class DefaultFilter extends FilterExpression if ('default' === $name && ($node instanceof ContextVariable || $node instanceof GetAttrExpression)) { $test = new DefinedTest(clone $node, new TwigTest('defined'), new EmptyNode(), $node->getTemplateLine()); + /** @var AbstractExpression $false */ $false = \count($arguments) ? $arguments->getNode(0) : new ConstantExpression('', $node->getTemplateLine()); $node = new ConditionalTernary($test, $default, $false, $node->getTemplateLine()); diff --git a/src/Node/IfNode.php b/src/Node/IfNode.php index 2c0e2a8e9..8d65b1bb1 100644 --- a/src/Node/IfNode.php +++ b/src/Node/IfNode.php @@ -14,6 +14,7 @@ namespace Twig\Node; use Twig\Attribute\YieldReady; use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ReturnPrimitiveTypeInterface; use Twig\Node\Expression\Test\TrueTest; use Twig\TwigTest; @@ -31,6 +32,7 @@ class IfNode extends Node for ($i = 0, $count = \count($tests); $i < $count; $i += 2) { $test = $tests->getNode((string) $i); if (!$test instanceof ReturnPrimitiveTypeInterface) { + /** @var AbstractExpression $test */ $tests->setNode($i, new TrueTest($test, new TwigTest('true'), null, $test->getTemplateLine())); } } diff --git a/src/NodeVisitor/EscaperNodeVisitor.php b/src/NodeVisitor/EscaperNodeVisitor.php index ee39f5d8e..804fabf22 100644 --- a/src/NodeVisitor/EscaperNodeVisitor.php +++ b/src/NodeVisitor/EscaperNodeVisitor.php @@ -80,8 +80,10 @@ final class EscaperNodeVisitor implements NodeVisitorInterface } elseif ($node instanceof FilterExpression) { return $this->preEscapeFilterNode($node, $env); } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping()) { + /** @var AbstractExpression $expression */ $expression = $node->getNode('expr'); if ($expression instanceof OperatorEscapeInterface) { + /** @var AbstractExpression&OperatorEscapeInterface $expression */ $this->escapeConditional($expression, $env, $type); } else { $node->setNode('expr', $this->escapeExpression($expression, $env, $type)); @@ -102,8 +104,10 @@ final class EscaperNodeVisitor implements NodeVisitorInterface private function escapeConditional(AbstractExpression&OperatorEscapeInterface $expression, Environment $env, string $type): void { foreach ($expression->getOperandNamesToEscape() as $name) { + /** @var AbstractExpression $operand */ $operand = $expression->getNode($name); if ($operand instanceof OperatorEscapeInterface) { + /** @var AbstractExpression&OperatorEscapeInterface $operand */ $this->escapeConditional($operand, $env, $type); } else { $expression->setNode($name, $this->escapeExpression($operand, $env, $type)); diff --git a/src/Util/CallableArgumentsExtractor.php b/src/Util/CallableArgumentsExtractor.php index c129a320a..b14162505 100644 --- a/src/Util/CallableArgumentsExtractor.php +++ b/src/Util/CallableArgumentsExtractor.php @@ -12,6 +12,7 @@ namespace Twig\Util; use Twig\Error\SyntaxError; +use Twig\Node\Expression\AbstractExpression; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\VariadicExpression; @@ -35,15 +36,16 @@ final class CallableArgumentsExtractor } /** - * @return array + * @return array */ public function extractArguments(Node $arguments): array { - /** @var array $extractedArguments */ + /** @var array $extractedArguments */ $extractedArguments = []; $extractedArgumentNameMap = []; $named = false; foreach ($arguments as $name => $node) { + /** @var AbstractExpression $node */ if (!\is_int($name)) { $named = true; } elseif ($named) { @@ -74,6 +76,7 @@ final class CallableArgumentsExtractor } [$callableParameters, $isPhpVariadic] = $this->getCallableParameters(); + /** @var list $arguments */ $arguments = []; $callableParameterNames = []; $missingArguments = [];