diff --git a/src/Parser.php b/src/Parser.php index 42447dd00..7cfc2058c 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -25,6 +25,7 @@ use Twig\Node\NodeOutputInterface; use Twig\Node\PrintNode; use Twig\Node\TextNode; use Twig\TokenParser\TokenParserInterface; +use Twig\Util\ReflectionCallable; /** * @author Fabien Potencier @@ -156,8 +157,9 @@ class Parser if (null !== $test) { $e = new SyntaxError(\sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); - if (\is_array($test) && isset($test[0]) && $test[0] instanceof TokenParserInterface) { - $e->appendMessage(\sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); + $callable = (new ReflectionCallable($test))->getCallable(); + if (\is_array($callable) && $callable[0] instanceof TokenParserInterface) { + $e->appendMessage(\sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $callable[0]->getTag(), $lineno)); } } else { $e = new SyntaxError(\sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); diff --git a/src/Util/ReflectionCallable.php b/src/Util/ReflectionCallable.php index 312a8c7af..54384e14b 100644 --- a/src/Util/ReflectionCallable.php +++ b/src/Util/ReflectionCallable.php @@ -55,8 +55,10 @@ final class ReflectionCallable $callable = [$object, $r->name]; $this->name = get_debug_type($object).'::'.$r->name; } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { + $callable = [$class->name, $r->name]; $this->name = $class->name.'::'.$r->name; } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { + $callable = [\is_array($callable) ? $callable[0] : $class->name, $r->name]; $this->name = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; } else { $callable = $this->name = $r->name; diff --git a/tests/Node/Expression/FilterTest.php b/tests/Node/Expression/FilterTest.php index 7234ac318..06f1baef4 100644 --- a/tests/Node/Expression/FilterTest.php +++ b/tests/Node/Expression/FilterTest.php @@ -43,6 +43,9 @@ class FilterTest extends NodeTestCase $environment->addFilter(new TwigFilter('bar_closure', \Closure::fromCallable(twig_tests_filter_dummy::class), ['needs_environment' => true])); $environment->addFilter(new TwigFilter('barbar', 'Twig\Tests\Node\Expression\twig_tests_filter_barbar', ['needs_context' => true, 'is_variadic' => true])); $environment->addFilter(new TwigFilter('magic_static', __NAMESPACE__.'\ChildMagicCallStub::magicStaticCall')); + if (\PHP_VERSION_ID >= 80111) { + $environment->addExtension(new FilterTestExtension()); + } $extension = new class() extends AbstractExtension { public function getFilters(): array @@ -121,6 +124,14 @@ class FilterTest extends NodeTestCase $node = $this->createFilter($string, 'barbar', ['arg2' => new ConstantExpression('bar', 1)]); $tests[] = [$node, 'Twig\Tests\Node\Expression\twig_tests_filter_barbar($context, "abc", null, "bar")', $environment]; + if (\PHP_VERSION_ID >= 80111) { + $node = $this->createFilter($string, 'first_class_callable_static'); + $tests[] = [$node, 'Twig\Tests\Node\Expression\FilterTestExtension::staticMethod("abc")', $environment]; + + $node = $this->createFilter($string, 'first_class_callable_object'); + $tests[] = [$node, '$this->extensions[\'Twig\Tests\Node\Expression\FilterTestExtension\']->objectMethod("abc")', $environment]; + } + $node = $this->createFilter($string, 'barbar', [ new ConstantExpression('1', 1), new ConstantExpression('2', 1), diff --git a/tests/Node/Expression/FilterTestExtension.php b/tests/Node/Expression/FilterTestExtension.php new file mode 100644 index 000000000..4a564ae3a --- /dev/null +++ b/tests/Node/Expression/FilterTestExtension.php @@ -0,0 +1,34 @@ +objectMethod(...)), + ]; + } + + public static function staticMethod() + { + } + + public function objectMethod() + { + } +}