Commit f6dba91e621 for php.net

commit f6dba91e6219ecb77018569f9800eca6edebb57f
Author: Weilin Du <weilindu@php.net>
Date:   Wed Aug 12 22:04:57 2026 +0800

    ext/readline: Fix class constant completion in readline interactive shell (#23218)

    Reference: https://github.com/php/php-src/pull/22994#discussion_r3708633591_

    php -a class constant completion passed the full ClassName::PREFIX string to
    the constant completion generator. And textlen had already been shortened to
    the part after "::". This commit fixes this bug.

    Co-authored-by: Ilia Alshanetsky <ilia@ilia.ws>

diff --git a/NEWS b/NEWS
index ffefe0d338e..0d9e4279207 100644
--- a/NEWS
+++ b/NEWS
@@ -60,6 +60,9 @@ PHP                                                                        NEWS
   . Fixed segfault in ReflectionMethod::createFromMethodName() on an
     uninstantiable subclass. (iliaal)

+- Readline:
+  . Fixed class constant completion in the interactive shell. (Weilin Du)
+
 - Session:
   . Fix corruption in mod_mm. (ndossche)
   . Fixed bug GH-23043 (broken session id code can cause zend_mm_heap
diff --git a/ext/readline/readline_cli.c b/ext/readline/readline_cli.c
index ca4e8eb4fe9..ff5caee9eb7 100644
--- a/ext/readline/readline_cli.c
+++ b/ext/readline/readline_cli.c
@@ -531,6 +531,7 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */
 	} else {
 		char *lc_text;
 		const char *class_name_end;
+		const char *constant_text = text;
 		zend_string *class_name = NULL;
 		zend_class_entry *ce = NULL;

@@ -543,6 +544,7 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */
 				zend_string_release_ex(class_name, 0);
 				return NULL;
 			}
+			constant_text = class_name_end + 2;
 			lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len);
 			textlen -= (class_name_len + 2);
 		} else {
@@ -559,7 +561,7 @@ static char *cli_completion_generator(const char *text, int index) /* {{{ */
 				ZEND_FALLTHROUGH;
 			case 2:
 			case 3:
-				retval = cli_completion_generator_define(text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants));
+				retval = cli_completion_generator_define(constant_text, textlen, &cli_completion_state, ce ? &ce->constants_table : EG(zend_constants));
 				if (retval || ce) {
 					break;
 				}
diff --git a/ext/readline/tests/readline_cli_completion_class_constant.phpt b/ext/readline/tests/readline_cli_completion_class_constant.phpt
new file mode 100644
index 00000000000..1d4e6228df0
--- /dev/null
+++ b/ext/readline/tests/readline_cli_completion_class_constant.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Interactive shell: class constant completion
+--EXTENSIONS--
+readline
+--SKIPIF--
+<?php
+if (READLINE_LIB !== "readline") die('skip readline only');
+if (!function_exists('shell_exec')) die('skip shell_exec() not available');
+?>
+--FILE--
+<?php
+$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
+$ini = getenv('TEST_PHP_EXTRA_ARGS');
+
+putenv('TERM=VT100');
+putenv('INPUTRC=/dev/null');
+
+$code = <<<'PHP'
+class Foo {
+    public const FooBar = "wrong_constant\n";
+    public const Zed = "zed_ok\n";
+}
+echo strtoupper(Foo::Ze	);
+exit
+PHP;
+
+echo shell_exec("echo " . escapeshellarg($code) . " | $php $ini -a");
+?>
+--EXPECTF--
+%AInteractive shell%AZED_OK%A