Commit 9c900b6e348 for php.net

commit 9c900b6e3485d49b4fc5d677ff3d139926f61644
Author: Tim Düsterhus <tim@tideways-gmbh.com>
Date:   Mon Feb 16 14:19:29 2026 +0100

    zend_compile: Bundle function type constants into an `zend_function_type` enum (#21208)

    * zend_compile: Bundle function type constants into an `zend_function_type` enum

    This clarifies the relationship between these constants and improves type
    safety a little.

    * Add C23_ENUM() helper macro

    * zend_portability: Rename `size` to `underlying_type` in `C23_ENUM()`

    * zend_portability: Include the leading `enum` in the `C23_ENUM` macro

    * Fix comment for C23_ENUM()

diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 06c1d528262..0c8e10e17f0 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -72,6 +72,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES
     zend_string* parameter.
   . EG(in_autoload) was renamed to EG(autoload_current_classnames) and no
     longer is a pointer, but a directly embedded HashTable struct.
+  . Added a C23_ENUM() helper macro to define forward-compatible fixed-size
+    enums.

 ========================
 2. Build system changes
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 587ae485ec8..41bbdde5a38 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -497,6 +497,12 @@ typedef struct _zend_class_constant {

 #define ZEND_CLASS_CONST_FLAGS(c) Z_CONSTANT_FLAGS((c)->value)

+C23_ENUM(zend_function_type, uint8_t) {
+	ZEND_INTERNAL_FUNCTION = 1,
+	ZEND_USER_FUNCTION = 2,
+	ZEND_EVAL_CODE = 4,
+};
+
 /* arg_info for internal functions */
 typedef struct _zend_internal_arg_info {
 	const char *name;
@@ -524,7 +530,7 @@ typedef struct _zend_internal_function_info {

 struct _zend_op_array {
 	/* Common elements */
-	uint8_t type;
+	zend_function_type type;
 	uint8_t arg_flags[3]; /* bitset of arg_info.pass_by_reference */
 	uint32_t fn_flags;
 	zend_string *function_name;
@@ -584,7 +590,7 @@ typedef void (ZEND_FASTCALL *zif_handler)(INTERNAL_FUNCTION_PARAMETERS);

 typedef struct _zend_internal_function {
 	/* Common elements */
-	uint8_t type;
+	zend_function_type type;
 	uint8_t arg_flags[3]; /* bitset of arg_info.pass_by_reference */
 	uint32_t fn_flags;
 	zend_string* function_name;
@@ -610,11 +616,11 @@ typedef struct _zend_internal_function {
 #define ZEND_FN_SCOPE_NAME(function)  ((function) && (function)->common.scope ? ZSTR_VAL((function)->common.scope->name) : "")

 union _zend_function {
-	uint8_t type;	/* MUST be the first element of this struct! */
+	zend_function_type type;	/* MUST be the first element of this struct! */
 	uint32_t   quick_arg_flags;

 	struct {
-		uint8_t type;  /* never used */
+		zend_function_type type;  /* never used */
 		uint8_t arg_flags[3]; /* bitset of arg_info.pass_by_reference */
 		uint32_t fn_flags;
 		zend_string *function_name;
@@ -956,7 +962,7 @@ ZEND_API zend_ast *zend_compile_string_to_ast(
 ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count, ...);
 ZEND_API zend_result zend_execute_script(int type, zval *retval, zend_file_handle *file_handle);
 ZEND_API zend_result open_file_for_scanning(zend_file_handle *file_handle);
-ZEND_API void init_op_array(zend_op_array *op_array, uint8_t type, int initial_ops_size);
+ZEND_API void init_op_array(zend_op_array *op_array, zend_function_type type, int initial_ops_size);
 ZEND_API void destroy_op_array(zend_op_array *op_array);
 ZEND_API void zend_destroy_static_vars(zend_op_array *op_array);
 ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle);
@@ -1071,10 +1077,6 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
 #define BP_VAR_FUNC_ARG		4
 #define BP_VAR_UNSET		5

-#define ZEND_INTERNAL_FUNCTION		1
-#define ZEND_USER_FUNCTION			2
-#define ZEND_EVAL_CODE				4
-
 #define ZEND_USER_CODE(type)		((type) != ZEND_INTERNAL_FUNCTION)

 #define ZEND_INTERNAL_CLASS         1
diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l
index 5a8a78cc3bd..1c985189fd3 100644
--- a/Zend/zend_language_scanner.l
+++ b/Zend/zend_language_scanner.l
@@ -591,7 +591,7 @@ ZEND_API zend_result open_file_for_scanning(zend_file_handle *file_handle)
 	return SUCCESS;
 }

-static zend_op_array *zend_compile(int type)
+static zend_op_array *zend_compile(zend_function_type type)
 {
 	zend_op_array *op_array = NULL;
 	bool original_in_compilation = CG(in_compilation);
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index d874f566dc8..24b480ad71e 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -45,7 +45,7 @@ static void zend_extension_op_array_dtor_handler(zend_extension *extension, zend
 	}
 }

-void init_op_array(zend_op_array *op_array, uint8_t type, int initial_ops_size)
+void init_op_array(zend_op_array *op_array, zend_function_type type, int initial_ops_size)
 {
 	op_array->type = type;
 	op_array->arg_flags[0] = 0;
diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h
index 6546ebfb5b7..c8a6dfa871b 100644
--- a/Zend/zend_portability.h
+++ b/Zend/zend_portability.h
@@ -146,6 +146,19 @@

 #define zend_quiet_write(...) ZEND_IGNORE_VALUE(write(__VA_ARGS__))

+/* Define an enum with a fixed underlying type as C23_ENUM(name, underlying_type) { }. */
+#if __STDC_VERSION__ >= 202311L || defined(__cplusplus)
+# define C23_ENUM(name, underlying_type) \
+    enum name: underlying_type; \
+    typedef enum name name; \
+    enum name: underlying_type
+#else
+# define C23_ENUM(name, underlying_type) \
+    enum name; \
+    typedef underlying_type name; \
+    enum name
+#endif
+
 /* all HAVE_XXX test have to be after the include of zend_config above */

 #if defined(HAVE_LIBDL) && !defined(ZEND_WIN32)