Commit cad6ed2a388 for php.net

commit cad6ed2a388d0d85b913d1b44e298b536b61e1c3
Author: Tim Düsterhus <tim@bastelstu.be>
Date:   Sun Jun 21 21:50:52 2026 +0200

    zend_ast: Surround function by parens when exporting calls to function stored in property (#22376)

    * 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.

    * zend_ast: Avoid needless indirection through `zend_ast_export_ns_name()`

diff --git a/NEWS b/NEWS
index c7645ca27b8..858ca396189 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 f495c4c8e3b..57faedc06f9 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_ex(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 00000000000..8c26f77f490
--- /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--
+<?php
+
+class Foo {
+	public static Closure $sf = strrev(...);
+
+	public function __construct(
+		public Closure $f = strrev(...),
+	) {
+		try {
+			assert(($this->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')