From 5992bac191e6e55ad992827852cc8fecdde53e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 21 Jun 2026 12:36:45 +0200 Subject: [PATCH 1/2] zend_ast: Surround function by parens when exporting calls to function stored in property The extra parentheses are needed to disambiguate method calls from calls to a function stored in a property. Fixes php/php-src#22373. --- NEWS | 2 ++ Zend/zend_ast.c | 18 ++++++++----- ext/standard/tests/assert/gh22373.phpt | 36 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 ext/standard/tests/assert/gh22373.phpt diff --git a/NEWS b/NEWS index c7645ca27b8a..858ca396189a 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,8 @@ PHP NEWS invalid variable names). (timwolla) . Fixed bug GH-22291 (AST pretty printing does not correctly handle braces in string interpolation). (timwolla) + . Fixed bug GH-22373 (AST pretty-printing drops meaningful parentheses + surrounding property access). (timwolla) - BCMath: . Added NUL-byte validation to BCMath functions. (jorgsowa) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index f495c4c8e3bb..948209bd7a69 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2535,12 +2535,18 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio break; case ZEND_AST_CALL: { zend_ast *left = ast->child[0]; - if (left->kind == ZEND_AST_ARROW_FUNC || left->kind == ZEND_AST_CLOSURE) { - smart_str_appendc(str, '('); - zend_ast_export_ns_name(str, left, 0, indent); - smart_str_appendc(str, ')'); - } else { - zend_ast_export_ns_name(str, left, 0, indent); + switch (left->kind) { + /* ZEND_AST_ZVAL is a regular function call. */ + case ZEND_AST_ZVAL: + /* ZEND_AST_VAR ($foo()) is unambiguous without parens. */ + case ZEND_AST_VAR: + zend_ast_export_ns_name(str, left, 0, indent); + break; + default: + smart_str_appendc(str, '('); + zend_ast_export_ns_name(str, left, 0, indent); + smart_str_appendc(str, ')'); + break; } smart_str_appendc(str, '('); zend_ast_export_ex(str, ast->child[1], 0, indent); diff --git a/ext/standard/tests/assert/gh22373.phpt b/ext/standard/tests/assert/gh22373.phpt new file mode 100644 index 000000000000..8c26f77f490b --- /dev/null +++ b/ext/standard/tests/assert/gh22373.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-22373: AST pretty-printing drops meaningful parentheses surrounding property access +--FILE-- +f)('abc') !== 'cba'); + } catch (Error $e) { + echo $e->getMessage(), PHP_EOL; + } + try { + assert(($this?->f)('abc') !== 'cba'); + } catch (Error $e) { + echo $e->getMessage(), PHP_EOL; + } + try { + assert((self::$sf)('abc') !== 'cba'); + } catch (Error $e) { + echo $e->getMessage(), PHP_EOL; + } + } +} + +new Foo(); + +?> +--EXPECT-- +assert(($this->f)('abc') !== 'cba') +assert(($this?->f)('abc') !== 'cba') +assert((self::$sf)('abc') !== 'cba') From 65bd57d576ca1272e16ebb87c66715110a0cdb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 21 Jun 2026 19:07:07 +0200 Subject: [PATCH 2/2] zend_ast: Avoid needless indirection through `zend_ast_export_ns_name()` --- Zend/zend_ast.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 948209bd7a69..57faedc06f9b 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2544,7 +2544,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio break; default: smart_str_appendc(str, '('); - zend_ast_export_ns_name(str, left, 0, indent); + zend_ast_export_ex(str, left, 0, indent); smart_str_appendc(str, ')'); break; }