Commit d16e6f52a43 for php.net

commit d16e6f52a432cc70fa8d54a3cd0c635ff403ec07
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date:   Tue Feb 3 12:38:04 2026 +0100

    Generate C enums from internal enums, introduce Z_PARAM_ENUM() (#20917)

    Update gen_stubs.php to generate C enums from internal enums, when the stub is annotated with @generate-c-enums. Enum values can be compared to the result of zend_enum_fetch_case_id(zend_object*).

    The generated enums are added to separate files named {$extensionName}_decl.h, so that it's possible to include these from anywhere. _arginfo.h files would generate warnings if we tried to include them in a compilation unit that doesn't call the register_{$class} functions, for instance.

    Introduce Z_PARAM_ENUM().

    * Make ZEND_AST_CONST_ENUM_INIT a 4-children node

    * Store enum case id in ZEND_AST_CONST_ENUM_INIT

    * Store enum case id in instance

    * Expose enum case_id internally

    * Generate C enum for internal enums

    * Introduce Z_PARAM_ENUM()

    * Port extensions

diff --git a/.gitattributes b/.gitattributes
index 8dea3f8bbaf..74fd9f995e8 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -21,6 +21,7 @@

 # Collapse generated files within git and pull request diff.
 **/*_arginfo.h linguist-generated -diff
+**/*_decl.h linguist-generated -diff
 /main/debug_gdb_scripts.c linguist-generated -diff
 /Zend/zend_vm_execute.h linguist-generated -diff
 /Zend/zend_vm_handlers.h linguist-generated -diff
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index e35baa3df56..9e2b3ae4794 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -60,11 +60,20 @@ PHP 8.6 INTERNALS UPGRADE NOTES
     automatically unwrap references when the result of the call is stored in an
     IS_TMP_VAR variable. This may be achieved by calling the
     zend_return_unwrap_ref() function.
+  . The php_math_round_mode_from_enum() function now takes a
+    zend_enum_RoundingMode parameter.
+  . Added Z_PARAM_ENUM().
+  . Added zend_enum_fetch_case_id().

 ========================
 2. Build system changes
 ========================

+  . build/gen_stub.php may now generate a _decl.h file in addition to
+    the _arginfo.h file, if the stub declares enums and is annotated with
+    @generate-c-enums. For each enum the file will contain a C enum. Enum values
+    can be compared to the result of zend_enum_fetch_case_id(zend_object*).
+
 ========================
 3. Module changes
 ========================
diff --git a/Zend/zend_API.h b/Zend/zend_API.h
index c1ccbf13666..d78ee6604e3 100644
--- a/Zend/zend_API.h
+++ b/Zend/zend_API.h
@@ -2009,6 +2009,13 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string
 #define Z_PARAM_OBJ_OF_CLASS_OR_LONG_OR_NULL(dest_obj, _ce, dest_long, is_null) \
 	Z_PARAM_OBJ_OF_CLASS_OR_LONG_EX(dest_obj, _ce, dest_long, is_null, 1)

+#define Z_PARAM_ENUM(dest, _ce) \
+	{ \
+		zend_object *_tmp = NULL; \
+		Z_PARAM_OBJ_OF_CLASS(_tmp, _ce); \
+		dest = zend_enum_fetch_case_id(_tmp); \
+	}
+
 /* old "p" */
 #define Z_PARAM_PATH_EX(dest, dest_len, check_null, deref) \
 		Z_PARAM_PROLOGUE(deref, 0); \
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index 54bf4c0bf2e..a6071d22841 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -995,10 +995,13 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
 			zend_ast *class_name_ast = ast->child[0];
 			zend_string *class_name = zend_ast_get_str(class_name_ast);

-			zend_ast *case_name_ast = ast->child[1];
+			zend_ast *case_id_ast = ast->child[1];
+			int case_id = (int)Z_LVAL_P(zend_ast_get_zval(case_id_ast));
+
+			zend_ast *case_name_ast = ast->child[2];
 			zend_string *case_name = zend_ast_get_str(case_name_ast);

-			zend_ast *case_value_ast = ast->child[2];
+			zend_ast *case_value_ast = ast->child[3];

 			zval case_value_zv;
 			ZVAL_UNDEF(&case_value_zv);
@@ -1009,7 +1012,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
 			}

 			zend_class_entry *ce = zend_lookup_class(class_name);
-			zend_enum_new(result, ce, case_name, case_value_ast != NULL ? &case_value_zv : NULL);
+			zend_enum_new(result, ce, case_id, case_name, case_value_ast != NULL ? &case_value_zv : NULL);
 			zval_ptr_dtor_nogc(&case_value_zv);
 			break;
 		}
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index c212cd8367a..a88efefd85b 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -168,15 +168,15 @@ enum _zend_ast_kind {
 	ZEND_AST_CONST_ELEM,
 	ZEND_AST_CLASS_CONST_GROUP,

-	// Pseudo node for initializing enums
-	ZEND_AST_CONST_ENUM_INIT,
-
 	/* 4 child nodes */
 	ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
 	ZEND_AST_FOREACH,
 	ZEND_AST_ENUM_CASE,
 	ZEND_AST_PROP_ELEM,

+	// Pseudo node for initializing enums
+	ZEND_AST_CONST_ENUM_INIT,
+
 	/* 5 child nodes */

 	/* 6 child nodes */
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 2b2235e5df1..688a50749a6 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -9756,6 +9756,11 @@ static void zend_compile_enum_case(zend_ast *ast)
 	ZVAL_STR_COPY(&class_name_zval, enum_class_name);
 	zend_ast *class_name_ast = zend_ast_create_zval(&class_name_zval);

+	zval case_id_zval;
+	int case_id = zend_enum_next_case_id(enum_class);
+	ZVAL_LONG(&case_id_zval, case_id);
+	zend_ast *case_id_ast = zend_ast_create_zval(&case_id_zval);
+
 	zval case_name_zval;
 	ZVAL_STR_COPY(&case_name_zval, enum_case_name);
 	zend_ast *case_name_ast = zend_ast_create_zval(&case_name_zval);
@@ -9773,7 +9778,8 @@ static void zend_compile_enum_case(zend_ast *ast)
 			ZSTR_VAL(enum_class_name));
 	}

-	zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_ast);
+	zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT,
+			class_name_ast, case_id_ast, case_name_ast, case_value_ast);

 	zval value_zv;
 	zend_const_expr_to_zval(&value_zv, &const_enum_init_ast, /* allow_dynamic */ false);
@@ -12669,7 +12675,7 @@ static void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */
 			zend_eval_const_expr(&ast->child[1]);
 			return;
 		case ZEND_AST_CONST_ENUM_INIT:
-			zend_eval_const_expr(&ast->child[2]);
+			zend_eval_const_expr(&ast->child[3]);
 			return;
 		case ZEND_AST_PROP:
 		case ZEND_AST_NULLSAFE_PROP:
diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c
index 7c62c8a96d0..6e9c68810d4 100644
--- a/Zend/zend_enum.c
+++ b/Zend/zend_enum.c
@@ -40,9 +40,16 @@ static zend_arg_info zarginfo_class_UnitEnum_cases[sizeof(arginfo_class_UnitEnum
 static zend_arg_info zarginfo_class_BackedEnum_from[sizeof(arginfo_class_BackedEnum_from)/sizeof(zend_internal_arg_info)];
 static zend_arg_info zarginfo_class_BackedEnum_tryFrom[sizeof(arginfo_class_BackedEnum_tryFrom)/sizeof(zend_internal_arg_info)];

-zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
+zend_object *zend_enum_new(zval *result, zend_class_entry *ce, int case_id, zend_string *case_name, zval *backing_value_zv)
 {
-	zend_object *zobj = zend_objects_new(ce);
+	zend_enum_obj *intern = zend_object_alloc(sizeof(*intern), ce);
+
+	zend_object_std_init(&intern->std, ce);
+	object_properties_init(&intern->std, ce);
+
+	intern->case_id = case_id;
+
+	zend_object *zobj = &intern->std;
 	GC_ADD_FLAGS(zobj, GC_NOT_COLLECTABLE);
 	ZVAL_OBJ(result, zobj);

@@ -170,6 +177,7 @@ void zend_register_enum_ce(void)
 	zend_ce_backed_enum->interface_gets_implemented = zend_implement_backed_enum;

 	memcpy(&zend_enum_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
+	zend_enum_object_handlers.offset = XtOffsetOf(zend_enum_obj, std);
 	zend_enum_object_handlers.clone_obj = NULL;
 	zend_enum_object_handlers.compare = zend_objects_not_comparable;
 }
@@ -539,16 +547,18 @@ ZEND_API zend_class_entry *zend_register_internal_enum(
 }

 static zend_ast_ref *create_enum_case_ast(
-		zend_string *class_name, zend_string *case_name, zval *value) {
+		zend_string *class_name, int case_id, zend_string *case_name,
+		zval *value) {
 	// TODO: Use custom node type for enum cases?
-	size_t size = sizeof(zend_ast_ref) + zend_ast_size(3)
-		+ (value ? 3 : 2) * sizeof(zend_ast_zval);
+	const size_t num_children = ZEND_AST_CONST_ENUM_INIT >> ZEND_AST_NUM_CHILDREN_SHIFT;
+	size_t size = sizeof(zend_ast_ref) + zend_ast_size(num_children)
+		+ (value ? num_children : num_children-1) * sizeof(zend_ast_zval);
 	char *p = pemalloc(size, 1);
 	zend_ast_ref *ref = (zend_ast_ref *) p; p += sizeof(zend_ast_ref);
 	GC_SET_REFCOUNT(ref, 1);
 	GC_TYPE_INFO(ref) = GC_CONSTANT_AST | GC_PERSISTENT | GC_IMMUTABLE;

-	zend_ast *ast = (zend_ast *) p; p += zend_ast_size(3);
+	zend_ast *ast = (zend_ast *) p; p += zend_ast_size(num_children);
 	ast->kind = ZEND_AST_CONST_ENUM_INIT;
 	ast->attr = 0;
 	ast->lineno = 0;
@@ -563,24 +573,47 @@ static zend_ast_ref *create_enum_case_ast(
 	ast->child[1] = (zend_ast *) p; p += sizeof(zend_ast_zval);
 	ast->child[1]->kind = ZEND_AST_ZVAL;
 	ast->child[1]->attr = 0;
-	ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
-	ZVAL_STR(zend_ast_get_zval(ast->child[1]), case_name);
+	ZVAL_LONG(zend_ast_get_zval(ast->child[1]), case_id);
 	Z_LINENO_P(zend_ast_get_zval(ast->child[1])) = 0;

+	ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
+	ast->child[2]->kind = ZEND_AST_ZVAL;
+	ast->child[2]->attr = 0;
+	ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
+	ZVAL_STR(zend_ast_get_zval(ast->child[2]), case_name);
+	Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;
+
 	if (value) {
-		ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
-		ast->child[2]->kind = ZEND_AST_ZVAL;
-		ast->child[2]->attr = 0;
+		ast->child[3] = (zend_ast *) p; p += sizeof(zend_ast_zval);
+		ast->child[3]->kind = ZEND_AST_ZVAL;
+		ast->child[3]->attr = 0;
 		ZEND_ASSERT(!Z_REFCOUNTED_P(value));
-		ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value);
-		Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;
+		ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[3]), value);
+		Z_LINENO_P(zend_ast_get_zval(ast->child[3])) = 0;
 	} else {
-		ast->child[2] = NULL;
+		ast->child[3] = NULL;
 	}

 	return ref;
 }

+int zend_enum_next_case_id(zend_class_entry *enum_class)
+{
+	ZEND_HASH_REVERSE_FOREACH_VAL(&enum_class->constants_table, zval *zv) {
+		zend_class_constant *c = Z_PTR_P(zv);
+		if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
+			continue;
+		}
+		ZEND_ASSERT(Z_TYPE(c->value) == IS_CONSTANT_AST);
+		zend_ast *ast = Z_ASTVAL(c->value);
+
+		ZEND_ASSERT(ast->kind == ZEND_AST_CONST_ENUM_INIT);
+		return Z_LVAL_P(zend_ast_get_zval(ast->child[1])) + 1;
+	} ZEND_HASH_FOREACH_END();
+
+	return 1;
+}
+
 ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value)
 {
 	if (value) {
@@ -602,9 +635,11 @@ ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, z
 		ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
 	}

+	int case_id = zend_enum_next_case_id(ce);
+
 	zval ast_zv;
 	Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST;
-	Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value);
+	Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_id, case_name, value);
 	zend_class_constant *c = zend_declare_class_constant_ex(
 		ce, case_name, &ast_zv, ZEND_ACC_PUBLIC, NULL);
 	ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
diff --git a/Zend/zend_enum.h b/Zend/zend_enum.h
index 39743c73472..4d0799e4f0a 100644
--- a/Zend/zend_enum.h
+++ b/Zend/zend_enum.h
@@ -30,14 +30,25 @@ extern ZEND_API zend_class_entry *zend_ce_unit_enum;
 extern ZEND_API zend_class_entry *zend_ce_backed_enum;
 extern ZEND_API zend_object_handlers zend_enum_object_handlers;

+typedef struct zend_enum_obj {
+	int         case_id;
+	zend_object std;
+} zend_enum_obj;
+
+static inline zend_enum_obj *zend_enum_obj_from_obj(zend_object *zobj) {
+	ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
+	return (zend_enum_obj*)((char*)(zobj) - XtOffsetOf(zend_enum_obj, std));
+}
+
 void zend_enum_startup(void);
 void zend_register_enum_ce(void);
 void zend_enum_add_interfaces(zend_class_entry *ce);
 zend_result zend_enum_build_backed_enum_table(zend_class_entry *ce);
-zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv);
+zend_object *zend_enum_new(zval *result, zend_class_entry *ce, int case_id, zend_string *case_name, zval *backing_value_zv);
 void zend_verify_enum(const zend_class_entry *ce);
 void zend_enum_register_funcs(zend_class_entry *ce);
 void zend_enum_register_props(zend_class_entry *ce);
+int zend_enum_next_case_id(zend_class_entry *enum_class);

 ZEND_API zend_class_entry *zend_register_internal_enum(
 	const char *name, uint8_t type, const zend_function_entry *functions);
@@ -47,6 +58,12 @@ ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name
 ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name);
 ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from);

+static zend_always_inline int zend_enum_fetch_case_id(zend_object *zobj)
+{
+	ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
+	return zend_enum_obj_from_obj(zobj)->case_id;
+}
+
 static zend_always_inline zval *zend_enum_fetch_case_name(zend_object *zobj)
 {
 	ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
diff --git a/build/gen_stub.php b/build/gen_stub.php
index 2670e85458d..64273e24bd8 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -82,11 +82,22 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly =
             $stubFilenameWithoutExtension = str_replace(".stub.php", "", $stubFile);
             $arginfoFile = "{$stubFilenameWithoutExtension}_arginfo.h";
             $legacyFile = "{$stubFilenameWithoutExtension}_legacy_arginfo.h";
-
+            $declFile = "{$stubFilenameWithoutExtension}_decl.h";
+
+            /* Check if the stub file changed, by checking that the hash stored
+             * in the generated arginfo.h matches.
+             * Also check that the decl.h file has the same hash. At this point
+             * we don't know if a decl.h file is supposed to exist, so extract
+             * this information (whether a decl file should exist) from the
+             * arginfo.h file. */
             $stubCode = file_get_contents($stubFile);
             $stubHash = sha1(str_replace("\r\n", "\n", $stubCode));
             $oldStubHash = extractStubHash($arginfoFile);
-            if ($stubHash === $oldStubHash && !$context->forceParse) {
+            $hasDeclHeader = extractHasDeclHeader($arginfoFile);
+            $oldStubHashDecl = extractStubHash($declFile);
+            $generatedFilesUpToDate = $stubHash === $oldStubHash
+                    && ($hasDeclHeader ? $stubHash === $oldStubHashDecl : $oldStubHashDecl === null);
+            if ($generatedFilesUpToDate && !$context->forceParse) {
                 /* Stub file did not change, do not regenerate. */
                 return null;
             }
@@ -122,26 +133,31 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly =
             return $fileInfo;
         }

-        $arginfoCode = generateArgInfoCode(
+        [$arginfoCode, $declCode] = generateArgInfoCode(
             basename($stubFilenameWithoutExtension),
             $fileInfo,
             $context->allConstInfos,
             $stubHash
         );
-        if ($context->forceRegeneration || $stubHash !== $oldStubHash) {
+        if ($context->forceRegeneration || !$generatedFilesUpToDate) {
             reportFilePutContents($arginfoFile, $arginfoCode);
+            if ($declCode !== '') {
+                reportFilePutContents($declFile, $declCode);
+            } else if (file_exists($declFile)) {
+                unlink($declFile);
+            }
         }

         if ($fileInfo->shouldGenerateLegacyArginfo()) {
             $legacyFileInfo = $fileInfo->getLegacyVersion();

-            $arginfoCode = generateArgInfoCode(
+            [$arginfoCode] = generateArgInfoCode(
                 basename($stubFilenameWithoutExtension),
                 $legacyFileInfo,
                 $context->allConstInfos,
                 $stubHash
             );
-            if ($context->forceRegeneration || $stubHash !== $oldStubHash) {
+            if ($context->forceRegeneration || !$generatedFilesUpToDate) {
                 reportFilePutContents($legacyFile, $arginfoCode);
             }
         }
@@ -159,13 +175,22 @@ function extractStubHash(string $arginfoFile): ?string {
     }

     $arginfoCode = file_get_contents($arginfoFile);
-    if (!preg_match('/\* Stub hash: ([0-9a-f]+) \*/', $arginfoCode, $matches)) {
+    if (!preg_match('/\* Stub hash: ([0-9a-f]+)/', $arginfoCode, $matches)) {
         return null;
     }

     return $matches[1];
 }

+function extractHasDeclHeader(string $arginfoFile): bool {
+    if (!file_exists($arginfoFile)) {
+        return false;
+    }
+
+    $arginfoCode = file_get_contents($arginfoFile);
+    return str_contains($arginfoCode, '* Has decl header: yes *');
+}
+
 class Context {
     public bool $forceParse = false;
     public bool $forceRegeneration = false;
@@ -3278,7 +3303,7 @@ protected function addModifiersToFieldSynopsis(DOMDocument $doc, DOMElement $fie
 }

 class EnumCaseInfo {
-    private /* readonly */ string $name;
+    public /* readonly */ string $name;
     private /* readonly */ ?Expr $value;

     public function __construct(string $name, ?Expr $value) {
@@ -3661,6 +3686,38 @@ function (Name $item) {
         return $code;
     }

+    public function getCDeclarations(): string
+    {
+        if ($this->type !== "enum") {
+            return '';
+        }
+
+        $code = '';
+
+        if ($this->cond) {
+            $code .= "#if {$this->cond}\n";
+        }
+
+        $cEnumName = 'zend_enum_' . str_replace('\\', '_', $this->name->toString());
+
+        $code .= "typedef enum {$cEnumName} {\n";
+
+        $i = 1;
+        foreach ($this->enumCaseInfos as $case) {
+            $cName = 'ZEND_ENUM_' . str_replace('\\', '_', $this->name->toString()) . '_' . $case->name;
+            $code .= "\t{$cName} = {$i},\n";
+            $i++;
+        }
+
+        $code .= "} {$cEnumName};\n";
+
+        if ($this->cond) {
+            $code .= "#endif\n";
+        }
+
+        return $code;
+    }
+
     private function getFlagsByPhpVersion(): VersionFlags
     {
         $php70Flags = [];
@@ -4192,6 +4249,7 @@ class FileInfo {
     public bool $generateFunctionEntries = false;
     public string $declarationPrefix = "";
     public bool $generateClassEntries = false;
+    public bool $generateCEnums = false;
     private bool $isUndocumentable = false;
     private bool $legacyArginfoGeneration = false;
     private ?int $minimumPhpVersionIdCompatibility = null;
@@ -4217,6 +4275,8 @@ public function __construct(array $fileTags) {
                 $this->declarationPrefix = $tag->value ? $tag->value . " " : "";
             } else if ($tag->name === 'undocumentable') {
                 $this->isUndocumentable = true;
+            } else if ($tag->name === 'generate-c-enums') {
+                $this->generateCEnums = true;
             }
         }

@@ -4515,6 +4575,23 @@ public function generateClassEntryCode(array $allConstInfos): string {

         return $code;
     }
+
+    public function generateCDeclarations(): string {
+        $code = "";
+
+        if (!$this->generateCEnums) {
+            return $code;
+        }
+
+        foreach ($this->classInfos as $class) {
+            $cdecl = $class->getCDeclarations();
+            if ($cdecl !== '') {
+                $code .= "\n" . $cdecl;
+            }
+        }
+
+        return $code;
+    }
 }

 class DocCommentTag {
@@ -5150,15 +5227,15 @@ function generateCodeWithConditions(

 /**
  * @param array<string, ConstInfo> $allConstInfos
+ * @return array{string, string}
  */
 function generateArgInfoCode(
     string $stubFilenameWithoutExtension,
     FileInfo $fileInfo,
     array $allConstInfos,
     string $stubHash
-): string {
-    $code = "/* This is a generated file, edit {$stubFilenameWithoutExtension}.stub.php instead.\n"
-          . " * Stub hash: $stubHash */\n";
+): array {
+    $code = "";

     $generatedFuncInfos = [];

@@ -5250,7 +5327,26 @@ static function (FuncInfo $funcInfo) use ($fileInfo, &$generatedFunctionDeclarat
         $code .= $fileInfo->generateClassEntryCode($allConstInfos);
     }

-    return $code;
+    $hasDeclFile = false;
+    $declCode = $fileInfo->generateCDeclarations();
+    if ($declCode !== '') {
+        $hasDeclFile = true;
+        $headerName = "ZEND_" . strtoupper($stubFilenameWithoutExtension) . "_DECL_{$stubHash}_H";
+        $declCode = "/* This is a generated file, edit {$stubFilenameWithoutExtension}.stub.php instead.\n"
+            . " * Stub hash: $stubHash */\n"
+            . "\n"
+            . "#ifndef {$headerName}\n"
+            . "#define {$headerName}\n"
+            . $declCode . "\n"
+            . "#endif /* {$headerName} */\n";
+    }
+
+    $code = "/* This is a generated file, edit {$stubFilenameWithoutExtension}.stub.php instead.\n"
+          . " * Stub hash: $stubHash"
+          . ($hasDeclFile ? "\n * Has decl header: yes */\n" : " */\n")
+          . $code;
+
+    return [$code, $declCode];
 }

 /** @param FuncInfo[] $funcInfos */
diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c
index df2b96e68a7..539fa9d9cf5 100644
--- a/ext/bcmath/bcmath.c
+++ b/ext/bcmath/bcmath.c
@@ -25,6 +25,7 @@
 #include "php_ini.h"
 #include "zend_exceptions.h"
 #include "zend_interfaces.h"
+#include "zend_enum.h"
 #include "bcmath_arginfo.h"
 #include "ext/standard/info.h"
 #include "php_bcmath.h"
@@ -787,30 +788,25 @@ PHP_FUNCTION(bcround)
 {
 	zend_string *numstr;
 	zend_long precision = 0;
-	zend_long mode = PHP_ROUND_HALF_UP;
-	zend_object *mode_object = NULL;
+	zend_enum_RoundingMode rounding_mode = ZEND_ENUM_RoundingMode_HalfAwayFromZero;
 	bc_num num = NULL, result;

 	ZEND_PARSE_PARAMETERS_START(1, 3)
 		Z_PARAM_STR(numstr)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_LONG(precision)
-		Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce)
+		Z_PARAM_ENUM(rounding_mode, rounding_mode_ce)
 	ZEND_PARSE_PARAMETERS_END();

-	if (mode_object != NULL) {
-		mode = php_math_round_mode_from_enum(mode_object);
-	}
-
-	switch (mode) {
-		case PHP_ROUND_HALF_UP:
-		case PHP_ROUND_HALF_DOWN:
-		case PHP_ROUND_HALF_EVEN:
-		case PHP_ROUND_HALF_ODD:
-		case PHP_ROUND_CEILING:
-		case PHP_ROUND_FLOOR:
-		case PHP_ROUND_TOWARD_ZERO:
-		case PHP_ROUND_AWAY_FROM_ZERO:
+	switch (rounding_mode) {
+		case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
+		case ZEND_ENUM_RoundingMode_HalfTowardsZero:
+		case ZEND_ENUM_RoundingMode_HalfEven:
+		case ZEND_ENUM_RoundingMode_HalfOdd:
+		case ZEND_ENUM_RoundingMode_TowardsZero:
+		case ZEND_ENUM_RoundingMode_AwayFromZero:
+		case ZEND_ENUM_RoundingMode_NegativeInfinity:
+		case ZEND_ENUM_RoundingMode_PositiveInfinity:
 			break;
 		default:
 			/* This is currently unreachable, but might become reachable when new modes are added. */
@@ -827,7 +823,7 @@ PHP_FUNCTION(bcround)
 		goto cleanup;
 	}

-	size_t scale = bc_round(num, precision, mode, &result);
+	size_t scale = bc_round(num, precision, rounding_mode, &result);
 	RETVAL_NEW_STR(bc_num2str_ex(result, scale));

 	cleanup: {
@@ -1796,30 +1792,26 @@ PHP_METHOD(BcMath_Number, ceil)
 PHP_METHOD(BcMath_Number, round)
 {
 	zend_long precision = 0;
-	zend_long rounding_mode = PHP_ROUND_HALF_UP;
-	zend_object *mode_object = NULL;
+	zend_enum_RoundingMode rounding_mode = ZEND_ENUM_RoundingMode_HalfAwayFromZero;

 	ZEND_PARSE_PARAMETERS_START(0, 2)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_LONG(precision);
-		Z_PARAM_OBJ_OF_CLASS(mode_object, rounding_mode_ce);
+		Z_PARAM_ENUM(rounding_mode, rounding_mode_ce);
 	ZEND_PARSE_PARAMETERS_END();

-	if (mode_object != NULL) {
-		rounding_mode = php_math_round_mode_from_enum(mode_object);
-	}
-
 	switch (rounding_mode) {
-		case PHP_ROUND_HALF_UP:
-		case PHP_ROUND_HALF_DOWN:
-		case PHP_ROUND_HALF_EVEN:
-		case PHP_ROUND_HALF_ODD:
-		case PHP_ROUND_CEILING:
-		case PHP_ROUND_FLOOR:
-		case PHP_ROUND_TOWARD_ZERO:
-		case PHP_ROUND_AWAY_FROM_ZERO:
+		case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
+		case ZEND_ENUM_RoundingMode_HalfTowardsZero:
+		case ZEND_ENUM_RoundingMode_HalfEven:
+		case ZEND_ENUM_RoundingMode_HalfOdd:
+		case ZEND_ENUM_RoundingMode_TowardsZero:
+		case ZEND_ENUM_RoundingMode_AwayFromZero:
+		case ZEND_ENUM_RoundingMode_NegativeInfinity:
+		case ZEND_ENUM_RoundingMode_PositiveInfinity:
 			break;
 		default:
+			/* This is currently unreachable, but might become reachable when new modes are added. */
 			zend_argument_value_error(2, "is an unsupported rounding mode");
 			RETURN_THROWS();
 	}
diff --git a/ext/bcmath/libbcmath/src/bcmath.h b/ext/bcmath/libbcmath/src/bcmath.h
index fa335ae4048..970286b8a26 100644
--- a/ext/bcmath/libbcmath/src/bcmath.h
+++ b/ext/bcmath/libbcmath/src/bcmath.h
@@ -155,7 +155,7 @@ bool bc_divmod(bc_num num1, bc_num num2, bc_num *quo, bc_num *rem, size_t scale)

 bc_num bc_floor_or_ceil(bc_num num, bool is_floor);

-size_t bc_round(bc_num num, zend_long places, zend_long mode, bc_num *result);
+size_t bc_round(bc_num num, zend_long places, zend_enum_RoundingMode mode, bc_num *result);

 typedef enum {
 	BC_RAISE_STATUS_OK,
diff --git a/ext/bcmath/libbcmath/src/round.c b/ext/bcmath/libbcmath/src/round.c
index 44df6036cbe..ec0042a9f48 100644
--- a/ext/bcmath/libbcmath/src/round.c
+++ b/ext/bcmath/libbcmath/src/round.c
@@ -19,7 +19,7 @@
 #include <stddef.h>

 /* Returns the scale of the value after rounding. */
-size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
+size_t bc_round(bc_num num, zend_long precision, zend_enum_RoundingMode mode, bc_num *result)
 {
 	/* clear result */
 	bc_free_num(result);
@@ -38,32 +38,30 @@ size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
 	/* e.g. value is 0.1 and precision is -3, ret is 0 or 1000  */
 	if (precision < 0 && num->n_len < (size_t) (-(precision + Z_L(1))) + 1) {
 		switch (mode) {
-			case PHP_ROUND_HALF_UP:
-			case PHP_ROUND_HALF_DOWN:
-			case PHP_ROUND_HALF_EVEN:
-			case PHP_ROUND_HALF_ODD:
-			case PHP_ROUND_TOWARD_ZERO:
+			case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
+			case ZEND_ENUM_RoundingMode_HalfTowardsZero:
+			case ZEND_ENUM_RoundingMode_HalfEven:
+			case ZEND_ENUM_RoundingMode_HalfOdd:
+			case ZEND_ENUM_RoundingMode_TowardsZero:
 				*result = bc_copy_num(BCG(_zero_));
 				return 0;

-			case PHP_ROUND_CEILING:
+			case ZEND_ENUM_RoundingMode_PositiveInfinity:
 				if (num->n_sign == MINUS) {
 					*result = bc_copy_num(BCG(_zero_));
 					return 0;
 				}
 				break;

-			case PHP_ROUND_FLOOR:
+			case ZEND_ENUM_RoundingMode_NegativeInfinity:
 				if (num->n_sign == PLUS) {
 					*result = bc_copy_num(BCG(_zero_));
 					return 0;
 				}
 				break;

-			case PHP_ROUND_AWAY_FROM_ZERO:
+			case ZEND_ENUM_RoundingMode_AwayFromZero:
 				break;
-
-			EMPTY_SWITCH_DEFAULT_CASE()
 		}

 		if (bc_is_zero(num)) {
@@ -117,7 +115,7 @@ size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)

 	/* Check cases that can be determined without looping. */
 	switch (mode) {
-		case PHP_ROUND_HALF_UP:
+		case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
 			if (*nptr >= 5) {
 				goto up;
 			} else if (*nptr < 5) {
@@ -125,9 +123,9 @@ size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
 			}
 			break;

-		case PHP_ROUND_HALF_DOWN:
-		case PHP_ROUND_HALF_EVEN:
-		case PHP_ROUND_HALF_ODD:
+		case ZEND_ENUM_RoundingMode_HalfTowardsZero:
+		case ZEND_ENUM_RoundingMode_HalfEven:
+		case ZEND_ENUM_RoundingMode_HalfOdd:
 			if (*nptr > 5) {
 				goto up;
 			} else if (*nptr < 5) {
@@ -136,7 +134,7 @@ size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
 			/* if *nptr == 5, we need to look-up further digits before making a decision. */
 			break;

-		case PHP_ROUND_CEILING:
+		case ZEND_ENUM_RoundingMode_PositiveInfinity:
 			if (num->n_sign != PLUS) {
 				goto check_zero;
 			} else if (*nptr > 0) {
@@ -145,7 +143,7 @@ size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
 			/* if *nptr == 0, a loop is required for judgment. */
 			break;

-		case PHP_ROUND_FLOOR:
+		case ZEND_ENUM_RoundingMode_NegativeInfinity:
 			if (num->n_sign != MINUS) {
 				goto check_zero;
 			} else if (*nptr > 0) {
@@ -154,17 +152,15 @@ size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
 			/* if *nptr == 0, a loop is required for judgment. */
 			break;

-		case PHP_ROUND_TOWARD_ZERO:
+		case ZEND_ENUM_RoundingMode_TowardsZero:
 			goto check_zero;

-		case PHP_ROUND_AWAY_FROM_ZERO:
+		case ZEND_ENUM_RoundingMode_AwayFromZero:
 			if (*nptr > 0) {
 				goto up;
 			}
 			/* if *nptr == 0, a loop is required for judgment. */
 			break;
-
-		EMPTY_SWITCH_DEFAULT_CASE()
 	}

 	/* Loop through the remaining digits. */
@@ -180,19 +176,19 @@ size_t bc_round(bc_num num, zend_long precision, zend_long mode, bc_num *result)
 	}

 	switch (mode) {
-		case PHP_ROUND_HALF_DOWN:
-		case PHP_ROUND_CEILING:
-		case PHP_ROUND_FLOOR:
-		case PHP_ROUND_AWAY_FROM_ZERO:
+		case ZEND_ENUM_RoundingMode_HalfTowardsZero:
+		case ZEND_ENUM_RoundingMode_PositiveInfinity:
+		case ZEND_ENUM_RoundingMode_NegativeInfinity:
+		case ZEND_ENUM_RoundingMode_AwayFromZero:
 			goto check_zero;

-		case PHP_ROUND_HALF_EVEN:
+		case ZEND_ENUM_RoundingMode_HalfEven:
 			if (rounded_len == 0 || num->n_value[rounded_len - 1] % 2 == 0) {
 				goto check_zero;
 			}
 			break;

-		case PHP_ROUND_HALF_ODD:
+		case ZEND_ENUM_RoundingMode_HalfOdd:
 			if (rounded_len != 0 && num->n_value[rounded_len - 1] % 2 == 1) {
 				goto check_zero;
 			}
diff --git a/ext/bcmath/php_bcmath.h b/ext/bcmath/php_bcmath.h
index 6b6098a2394..83894fd7ca6 100644
--- a/ext/bcmath/php_bcmath.h
+++ b/ext/bcmath/php_bcmath.h
@@ -17,9 +17,9 @@
 #ifndef PHP_BCMATH_H
 #define PHP_BCMATH_H

+#include "ext/standard/php_math_round_mode.h"
 #include "libbcmath/src/bcmath.h"
 #include "zend_API.h"
-#include "ext/standard/php_math_round_mode.h"

 extern zend_module_entry bcmath_module_entry;
 #define phpext_bcmath_ptr &bcmath_module_entry
diff --git a/ext/dom/element.c b/ext/dom/element.c
index 797f215e173..25bd306bcd8 100644
--- a/ext/dom/element.c
+++ b/ext/dom/element.c
@@ -1601,9 +1601,10 @@ PHP_METHOD(DOMElement, replaceChildren)
 #define INSERT_ADJACENT_RES_SYNTAX_FAILED INSERT_ADJACENT_RES_ADOPT_FAILED
 #define INSERT_ADJACENT_RES_PRE_INSERT_FAILED ((void*) -2)

-static xmlNodePtr dom_insert_adjacent(const zend_string *where, xmlNodePtr thisp, dom_object *this_intern, xmlNodePtr otherp)
+static xmlNodePtr dom_insert_adjacent(zend_enum_Dom_AdjacentPosition where, xmlNodePtr thisp, dom_object *this_intern, xmlNodePtr otherp)
 {
-	if (zend_string_equals_literal_ci(where, "beforebegin")) {
+	switch (where) {
+	case ZEND_ENUM_Dom_AdjacentPosition_BeforeBegin:
 		if (thisp->parent == NULL) {
 			return NULL;
 		}
@@ -1613,21 +1614,24 @@ static xmlNodePtr dom_insert_adjacent(const zend_string *where, xmlNodePtr thisp
 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp->parent, thisp)) {
 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
 		}
-	} else if (zend_string_equals_literal_ci(where, "afterbegin")) {
+		break;
+	case ZEND_ENUM_Dom_AdjacentPosition_AfterBegin:
 		if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
 			return INSERT_ADJACENT_RES_ADOPT_FAILED;
 		}
 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp, thisp->children)) {
 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
 		}
-	} else if (zend_string_equals_literal_ci(where, "beforeend")) {
+		break;
+	case ZEND_ENUM_Dom_AdjacentPosition_BeforeEnd:
 		if (!php_dom_adopt_node(otherp, this_intern, thisp->doc)) {
 			return INSERT_ADJACENT_RES_ADOPT_FAILED;
 		}
 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp, NULL)) {
 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
 		}
-	} else if (zend_string_equals_literal_ci(where, "afterend")) {
+		break;
+	case ZEND_ENUM_Dom_AdjacentPosition_AfterEnd:
 		if (thisp->parent == NULL) {
 			return NULL;
 		}
@@ -1637,9 +1641,7 @@ static xmlNodePtr dom_insert_adjacent(const zend_string *where, xmlNodePtr thisp
 		if (!php_dom_pre_insert(this_intern->document, otherp, thisp->parent, thisp->next))  {
 			return INSERT_ADJACENT_RES_PRE_INSERT_FAILED;
 		}
-	} else {
-		php_dom_throw_error(SYNTAX_ERR, dom_get_strict_error(this_intern->document));
-		return INSERT_ADJACENT_RES_SYNTAX_FAILED;
+		break;
 	}
 	return otherp;
 }
@@ -1647,7 +1649,7 @@ static xmlNodePtr dom_insert_adjacent(const zend_string *where, xmlNodePtr thisp
 /* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacentelement
 Since:
 */
-static void dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAMETERS, const zend_string *where, zval *element_zval)
+static void dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAMETERS, zend_enum_Dom_AdjacentPosition where, zval *element_zval)
 {
 	zval *id;
 	xmlNodePtr thisp, otherp;
@@ -1666,12 +1668,41 @@ static void dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAMETERS, co
 	}
 }

+static zend_result dom_adjacent_position_str_to_enum(zend_enum_Dom_AdjacentPosition *value, const zend_string *str)
+{
+	if (zend_string_equals_literal_ci(str, "beforebegin")) {
+		*value = ZEND_ENUM_Dom_AdjacentPosition_BeforeBegin;
+	} else if (zend_string_equals_literal_ci(str, "afterbegin")) {
+		*value = ZEND_ENUM_Dom_AdjacentPosition_AfterBegin;
+	} else if (zend_string_equals_literal_ci(str, "beforeend")) {
+		*value = ZEND_ENUM_Dom_AdjacentPosition_BeforeEnd;
+	} else if (zend_string_equals_literal_ci(str, "afterend")) {
+		*value = ZEND_ENUM_Dom_AdjacentPosition_AfterEnd;
+	} else {
+		return FAILURE;
+	}
+
+	return SUCCESS;
+}
+
 PHP_METHOD(DOMElement, insertAdjacentElement)
 {
-	zend_string *where;
+	zend_string *where_str;
+	zend_enum_Dom_AdjacentPosition where;
 	zval *element_zval;

-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SO", &where, &element_zval, dom_element_class_entry) != SUCCESS) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SO", &where_str, &element_zval, dom_element_class_entry) != SUCCESS) {
+		RETURN_THROWS();
+	}
+
+	if (dom_adjacent_position_str_to_enum(&where, where_str) != SUCCESS) {
+		zval *id;
+		xmlNodePtr p;
+		dom_object *intern;
+		DOM_GET_THIS_OBJ(p, id, xmlNodePtr, intern);
+		(void)p;
+
+		php_dom_throw_error(SYNTAX_ERR, dom_get_strict_error(intern->document));
 		RETURN_THROWS();
 	}

@@ -1680,14 +1711,14 @@ PHP_METHOD(DOMElement, insertAdjacentElement)

 PHP_METHOD(Dom_Element, insertAdjacentElement)
 {
-	zval *element_zval, *where_zv;
+	zend_enum_Dom_AdjacentPosition where;
+	zval *element_zval;

 	ZEND_PARSE_PARAMETERS_START(2, 2)
-		Z_PARAM_OBJECT_OF_CLASS(where_zv, dom_adjacent_position_class_entry)
+		Z_PARAM_ENUM(where, dom_adjacent_position_class_entry)
 		Z_PARAM_OBJECT_OF_CLASS(element_zval, dom_modern_element_class_entry)
 	ZEND_PARSE_PARAMETERS_END();

-	const zend_string *where = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(where_zv)));
 	dom_element_insert_adjacent_element(INTERNAL_FUNCTION_PARAM_PASSTHRU, where, element_zval);
 }
 /* }}} end DOMElement::insertAdjacentElement */
@@ -1695,7 +1726,7 @@ PHP_METHOD(Dom_Element, insertAdjacentElement)
 /* {{{ URL: https://dom.spec.whatwg.org/#dom-element-insertadjacenttext
 Since:
 */
-static void dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAMETERS, const zend_string *where, const zend_string *data)
+static void dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAMETERS, zend_enum_Dom_AdjacentPosition where, const zend_string *data)
 {
 	dom_object *this_intern;
 	zval *id;
@@ -1717,9 +1748,21 @@ static void dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAMETERS, const

 PHP_METHOD(DOMElement, insertAdjacentText)
 {
-	zend_string *where, *data;
+	zend_string *where_str, *data;
+	zend_enum_Dom_AdjacentPosition where;

-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &where, &data) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &where_str, &data) == FAILURE) {
+		RETURN_THROWS();
+	}
+
+	if (dom_adjacent_position_str_to_enum(&where, where_str) != SUCCESS) {
+		zval *id;
+		xmlNodePtr p;
+		dom_object *intern;
+		DOM_GET_THIS_OBJ(p, id, xmlNodePtr, intern);
+		(void)p;
+
+		php_dom_throw_error(SYNTAX_ERR, dom_get_strict_error(intern->document));
 		RETURN_THROWS();
 	}

@@ -1728,15 +1771,14 @@ PHP_METHOD(DOMElement, insertAdjacentText)

 PHP_METHOD(Dom_Element, insertAdjacentText)
 {
-	zval *where_zv;
+	zend_enum_Dom_AdjacentPosition where;
 	zend_string *data;

 	ZEND_PARSE_PARAMETERS_START(2, 2)
-		Z_PARAM_OBJECT_OF_CLASS(where_zv, dom_adjacent_position_class_entry)
+		Z_PARAM_ENUM(where, dom_adjacent_position_class_entry)
 		Z_PARAM_STR(data)
 	ZEND_PARSE_PARAMETERS_END();

-	const zend_string *where = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(where_zv)));
 	dom_element_insert_adjacent_text(INTERNAL_FUNCTION_PARAM_PASSTHRU, where, data);
 }
 /* }}} end DOMElement::insertAdjacentText */
@@ -1744,7 +1786,7 @@ PHP_METHOD(Dom_Element, insertAdjacentText)
 /* https://html.spec.whatwg.org/#dom-element-insertadjacenthtml */
 PHP_METHOD(Dom_Element, insertAdjacentHTML)
 {
-	zval *where_zv;
+	zend_enum_Dom_AdjacentPosition where;
 	zend_string *string;

 	dom_object *this_intern;
@@ -1754,23 +1796,21 @@ PHP_METHOD(Dom_Element, insertAdjacentHTML)
 	bool created_context = false;

 	ZEND_PARSE_PARAMETERS_START(2, 2)
-		Z_PARAM_OBJECT_OF_CLASS(where_zv, dom_adjacent_position_class_entry)
+		Z_PARAM_ENUM(where, dom_adjacent_position_class_entry)
 		Z_PARAM_STR(string)
 	ZEND_PARSE_PARAMETERS_END();

 	DOM_GET_THIS_OBJ(thisp, id, xmlNodePtr, this_intern);

-	const zend_string *where = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(where_zv)));
-
 	/* 1. We don't do injection sinks. */

 	/* 2. Let context be NULL */
 	xmlNodePtr context = NULL;

 	/* 3. Use the first matching item from this list: (...) */
-	switch (ZSTR_LEN(where) + ZSTR_VAL(where)[2]) {
-		case sizeof("BeforeBegin") - 1 + 'f':
-		case sizeof("AfterEnd") - 1 + 't':
+	switch (where) {
+		case ZEND_ENUM_Dom_AdjacentPosition_BeforeBegin:
+		case ZEND_ENUM_Dom_AdjacentPosition_AfterEnd:
 			/* 1. Set context to this's parent. */
 			context = thisp->parent;

@@ -1780,8 +1820,8 @@ PHP_METHOD(Dom_Element, insertAdjacentHTML)
 				RETURN_THROWS();
 			}
 			break;
-		case sizeof("AfterBegin") - 1 + 't':
-		case sizeof("BeforeEnd") - 1 + 'f':
+		case ZEND_ENUM_Dom_AdjacentPosition_AfterBegin:
+		case ZEND_ENUM_Dom_AdjacentPosition_BeforeEnd:
 			/* Set context to this. */
 			context = thisp;
 			break;
@@ -1811,17 +1851,17 @@ PHP_METHOD(Dom_Element, insertAdjacentHTML)
 	php_libxml_invalidate_node_list_cache(this_intern->document);

 	/* 6. Use the first matching item from this list: (...) */
-	switch (ZSTR_LEN(where) + ZSTR_VAL(where)[2]) {
-		case sizeof("BeforeBegin") - 1 + 'f':
+	switch (where) {
+		case ZEND_ENUM_Dom_AdjacentPosition_BeforeBegin:
 			php_dom_pre_insert(this_intern->document, fragment, thisp->parent, thisp);
 			break;
-		case sizeof("AfterEnd") - 1 + 't':
+		case ZEND_ENUM_Dom_AdjacentPosition_AfterEnd:
 			php_dom_pre_insert(this_intern->document, fragment, thisp->parent, thisp->next);
 			break;
-		case sizeof("AfterBegin") - 1 + 't':
+		case ZEND_ENUM_Dom_AdjacentPosition_AfterBegin:
 			php_dom_pre_insert(this_intern->document, fragment, thisp, thisp->children);
 			break;
-		case sizeof("BeforeEnd") - 1 + 'f':
+		case ZEND_ENUM_Dom_AdjacentPosition_BeforeEnd:
 			php_dom_node_append(this_intern->document, fragment, thisp);
 			break;
 		EMPTY_SWITCH_DEFAULT_CASE();
diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h
index e44f74eadeb..e93000044f0 100644
--- a/ext/dom/php_dom.h
+++ b/ext/dom/php_dom.h
@@ -54,6 +54,7 @@ extern zend_module_entry dom_module_entry;
 #include "xpath_callbacks.h"
 #include "zend_exceptions.h"
 #include "dom_ce.h"
+#include "php_dom_decl.h"

 /* DOM API_VERSION, please bump it up, if you change anything in the API
     therefore it's easier for the script-programmers to check, what's working how
diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php
index 37a1bea62b6..71aa5f4ec0f 100644
--- a/ext/dom/php_dom.stub.php
+++ b/ext/dom/php_dom.stub.php
@@ -1,6 +1,9 @@
 <?php

-/** @generate-class-entries */
+/**
+ * @generate-class-entries
+ * @generate-c-enums
+ */

 namespace
 {
diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h
index 7a0ad88d7e5..1c90f920cdd 100644
Binary files a/ext/dom/php_dom_arginfo.h and b/ext/dom/php_dom_arginfo.h differ
diff --git a/ext/dom/php_dom_decl.h b/ext/dom/php_dom_decl.h
new file mode 100644
index 00000000000..f918637ab52
Binary files /dev/null and b/ext/dom/php_dom_decl.h differ
diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c
index 886a292ee29..ab8b638bdbd 100644
--- a/ext/pcntl/pcntl.c
+++ b/ext/pcntl/pcntl.c
@@ -1823,22 +1823,22 @@ PHP_FUNCTION(pcntl_getcpu)
 #endif

 #if defined(HAVE_PTHREAD_SET_QOS_CLASS_SELF_NP)
-static qos_class_t qos_zval_to_lval(const zval *qos_obj)
+static qos_class_t qos_enum_to_pthread(zend_enum_Pcntl_QosClass entry)
 {
-	qos_class_t qos_class = QOS_CLASS_DEFAULT;
-	zend_string *entry = Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(qos_obj)));
-
-	if (zend_string_equals_literal(entry, "UserInteractive")) {
-		qos_class = QOS_CLASS_USER_INTERACTIVE;
-	} else if (zend_string_equals_literal(entry, "UserInitiated")) {
-		qos_class = QOS_CLASS_USER_INITIATED;
-	} else if (zend_string_equals_literal(entry, "Utility")) {
-		qos_class = QOS_CLASS_UTILITY;
-	} else if (zend_string_equals_literal(entry, "Background")) {
-		qos_class = QOS_CLASS_BACKGROUND;
-	}
-
-	return qos_class;
+	switch (entry) {
+	case ZEND_ENUM_Pcntl_QosClass_UserInteractive:
+		return QOS_CLASS_USER_INTERACTIVE;
+	case ZEND_ENUM_Pcntl_QosClass_UserInitiated:
+		return QOS_CLASS_USER_INITIATED;
+	case ZEND_ENUM_Pcntl_QosClass_Utility:
+		return QOS_CLASS_UTILITY;
+	case ZEND_ENUM_Pcntl_QosClass_Background:
+		return QOS_CLASS_BACKGROUND;
+	case ZEND_ENUM_Pcntl_QosClass_Default:
+		return QOS_CLASS_DEFAULT;
+	}
+
+	ZEND_UNREACHABLE();
 }

 static zend_object *qos_lval_to_zval(qos_class_t qos_class)
@@ -1886,13 +1886,13 @@ PHP_FUNCTION(pcntl_getqos_class)

 PHP_FUNCTION(pcntl_setqos_class)
 {
-	zval *qos_obj;
+	zend_enum_Pcntl_QosClass qos;

 	ZEND_PARSE_PARAMETERS_START(1, 1)
-		Z_PARAM_OBJECT_OF_CLASS(qos_obj, QosClass_ce)
+		Z_PARAM_ENUM(qos, QosClass_ce)
 	ZEND_PARSE_PARAMETERS_END();

-	qos_class_t qos_class = qos_zval_to_lval(qos_obj);
+	qos_class_t qos_class = qos_enum_to_pthread(qos);

 	if (UNEXPECTED(pthread_set_qos_class_self_np((qos_class_t)qos_class, 0) != 0))
 	{
diff --git a/ext/pcntl/pcntl.stub.php b/ext/pcntl/pcntl.stub.php
index 3f3800c50ab..2da540fa71e 100644
--- a/ext/pcntl/pcntl.stub.php
+++ b/ext/pcntl/pcntl.stub.php
@@ -1,6 +1,9 @@
 <?php

-/** @generate-class-entries */
+/**
+ * @generate-class-entries
+ * @generate-c-enums
+ */

 /* Wait Constants */

diff --git a/ext/pcntl/pcntl_arginfo.h b/ext/pcntl/pcntl_arginfo.h
index 808632fdabb..d9624a22605 100644
Binary files a/ext/pcntl/pcntl_arginfo.h and b/ext/pcntl/pcntl_arginfo.h differ
diff --git a/ext/pcntl/pcntl_decl.h b/ext/pcntl/pcntl_decl.h
new file mode 100644
index 00000000000..1059485bc93
Binary files /dev/null and b/ext/pcntl/pcntl_decl.h differ
diff --git a/ext/pcntl/php_pcntl.h b/ext/pcntl/php_pcntl.h
index f2cc0d59195..fb151e207b9 100644
--- a/ext/pcntl/php_pcntl.h
+++ b/ext/pcntl/php_pcntl.h
@@ -17,6 +17,8 @@
 #ifndef PHP_PCNTL_H
 #define PHP_PCNTL_H

+#include "pcntl_decl.h"
+
 #if defined(HAVE_DECL_WCONTINUED) && HAVE_DECL_WCONTINUED == 1 && defined(HAVE_WIFCONTINUED) && HAVE_WIFCONTINUED == 1
 #define HAVE_WCONTINUED 1
 #endif
diff --git a/ext/random/php_random.h b/ext/random/php_random.h
index 9db8c8ba190..e4d6b4bdf3d 100644
--- a/ext/random/php_random.h
+++ b/ext/random/php_random.h
@@ -34,6 +34,7 @@
 # include "php.h"
 # include "php_random_csprng.h"
 # include "php_random_uint128.h"
+# include "random_decl.h"

 PHPAPI double php_combined_lcg(void);

diff --git a/ext/random/random.stub.php b/ext/random/random.stub.php
index 2854bdd2c25..57a80bf645b 100644
--- a/ext/random/random.stub.php
+++ b/ext/random/random.stub.php
@@ -1,6 +1,9 @@
 <?php

-/** @generate-class-entries */
+/**
+ * @generate-class-entries
+ * @generate-c-enums
+ */

 namespace {
     /**
diff --git a/ext/random/random_arginfo.h b/ext/random/random_arginfo.h
index fff5f1e9cd8..8332fdc41fd 100644
Binary files a/ext/random/random_arginfo.h and b/ext/random/random_arginfo.h differ
diff --git a/ext/random/random_decl.h b/ext/random/random_decl.h
new file mode 100644
index 00000000000..67b27f19824
Binary files /dev/null and b/ext/random/random_decl.h differ
diff --git a/ext/random/randomizer.c b/ext/random/randomizer.c
index 2daf9866109..a576cd12955 100644
--- a/ext/random/randomizer.c
+++ b/ext/random/randomizer.c
@@ -131,14 +131,13 @@ PHP_METHOD(Random_Randomizer, getFloat)
 {
 	php_random_randomizer *randomizer = Z_RANDOM_RANDOMIZER_P(ZEND_THIS);
 	double min, max;
-	zend_object *bounds = NULL;
-	int bounds_type = 'C' + sizeof("ClosedOpen") - 1;
+	zend_enum_Random_IntervalBoundary bounds_type = ZEND_ENUM_Random_IntervalBoundary_ClosedOpen;

 	ZEND_PARSE_PARAMETERS_START(2, 3)
 		Z_PARAM_DOUBLE(min)
 		Z_PARAM_DOUBLE(max)
 		Z_PARAM_OPTIONAL
-		Z_PARAM_OBJ_OF_CLASS(bounds, random_ce_Random_IntervalBoundary);
+		Z_PARAM_ENUM(bounds_type, random_ce_Random_IntervalBoundary);
 	ZEND_PARSE_PARAMETERS_END();

 	if (!zend_finite(min)) {
@@ -151,36 +150,29 @@ PHP_METHOD(Random_Randomizer, getFloat)
 		RETURN_THROWS();
 	}

-	if (bounds) {
-		zval *case_name = zend_enum_fetch_case_name(bounds);
-		zend_string *bounds_name = Z_STR_P(case_name);
-
-		bounds_type = ZSTR_VAL(bounds_name)[0] + ZSTR_LEN(bounds_name);
-	}
-
 	switch (bounds_type) {
-	case 'C' + sizeof("ClosedOpen") - 1:
+	case ZEND_ENUM_Random_IntervalBoundary_ClosedOpen:
 		if (UNEXPECTED(max <= min)) {
 			zend_argument_value_error(2, "must be greater than argument #1 ($min)");
 			RETURN_THROWS();
 		}

 		RETURN_DOUBLE(php_random_gammasection_closed_open(randomizer->engine, min, max));
-	case 'C' + sizeof("ClosedClosed") - 1:
+	case ZEND_ENUM_Random_IntervalBoundary_ClosedClosed:
 		if (UNEXPECTED(max < min)) {
 			zend_argument_value_error(2, "must be greater than or equal to argument #1 ($min)");
 			RETURN_THROWS();
 		}

 		RETURN_DOUBLE(php_random_gammasection_closed_closed(randomizer->engine, min, max));
-	case 'O' + sizeof("OpenClosed") - 1:
+	case ZEND_ENUM_Random_IntervalBoundary_OpenClosed:
 		if (UNEXPECTED(max <= min)) {
 			zend_argument_value_error(2, "must be greater than argument #1 ($min)");
 			RETURN_THROWS();
 		}

 		RETURN_DOUBLE(php_random_gammasection_open_closed(randomizer->engine, min, max));
-	case 'O' + sizeof("OpenOpen") - 1:
+	case ZEND_ENUM_Random_IntervalBoundary_OpenOpen:
 		if (UNEXPECTED(max <= min)) {
 			zend_argument_value_error(2, "must be greater than argument #1 ($min)");
 			RETURN_THROWS();
@@ -194,8 +186,6 @@ PHP_METHOD(Random_Randomizer, getFloat)
 		}

 		return;
-	default:
-		ZEND_UNREACHABLE();
 	}
 }
 /* }}} */
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index 64e79651913..256468e39a4 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -6547,16 +6547,16 @@ ZEND_METHOD(ReflectionProperty, hasHook)

 	reflection_object *intern;
 	property_reference *ref;
-	zend_object *type;
+	zend_enum_PropertyHookType type;

 	ZEND_PARSE_PARAMETERS_START(1, 1)
-		Z_PARAM_OBJ_OF_CLASS(type, reflection_property_hook_type_ptr)
+		Z_PARAM_ENUM(type, reflection_property_hook_type_ptr)
 	ZEND_PARSE_PARAMETERS_END();

 	GET_REFLECTION_OBJECT_PTR(ref);

 	zend_property_hook_kind kind;
-	if (zend_string_equals_literal(Z_STR_P(zend_enum_fetch_case_name(type)), "Get")) {
+	if (type == ZEND_ENUM_PropertyHookType_Get) {
 		kind = ZEND_PROPERTY_HOOK_GET;
 	} else {
 		kind = ZEND_PROPERTY_HOOK_SET;
@@ -6569,10 +6569,10 @@ ZEND_METHOD(ReflectionProperty, getHook)
 {
 	reflection_object *intern;
 	property_reference *ref;
-	zend_object *type;
+	zend_enum_PropertyHookType type;

 	ZEND_PARSE_PARAMETERS_START(1, 1)
-		Z_PARAM_OBJ_OF_CLASS(type, reflection_property_hook_type_ptr)
+		Z_PARAM_ENUM(type, reflection_property_hook_type_ptr)
 	ZEND_PARSE_PARAMETERS_END();

 	GET_REFLECTION_OBJECT_PTR(ref);
@@ -6583,7 +6583,7 @@ ZEND_METHOD(ReflectionProperty, getHook)
 	}

 	zend_function *hook;
-	if (zend_string_equals_literal(Z_STR_P(zend_enum_fetch_case_name(type)), "Get")) {
+	if (type == ZEND_ENUM_PropertyHookType_Get) {
 		hook = ref->prop->hooks[ZEND_PROPERTY_HOOK_GET];
 	} else {
 		hook = ref->prop->hooks[ZEND_PROPERTY_HOOK_SET];
diff --git a/ext/reflection/php_reflection.h b/ext/reflection/php_reflection.h
index d676597fd0b..dc224073429 100644
--- a/ext/reflection/php_reflection.h
+++ b/ext/reflection/php_reflection.h
@@ -18,6 +18,7 @@
 #define PHP_REFLECTION_H

 #include "php.h"
+#include "php_reflection_decl.h"

 extern zend_module_entry reflection_module_entry;
 #define phpext_reflection_ptr &reflection_module_entry
diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php
index 91c70d6ffdb..147e2f18c9e 100644
--- a/ext/reflection/php_reflection.stub.php
+++ b/ext/reflection/php_reflection.stub.php
@@ -1,6 +1,9 @@
 <?php

-/** @generate-class-entries */
+/**
+ * @generate-class-entries
+ * @generate-c-enums
+ */

 class ReflectionException extends Exception
 {
diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h
index 9e2b8b970eb..5d452451001 100644
Binary files a/ext/reflection/php_reflection_arginfo.h and b/ext/reflection/php_reflection_arginfo.h differ
diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h
new file mode 100644
index 00000000000..7a458bccd1e
Binary files /dev/null and b/ext/reflection/php_reflection_decl.h differ
diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h
index e5b85fbc2d5..004279b9d1a 100644
--- a/ext/standard/basic_functions.h
+++ b/ext/standard/basic_functions.h
@@ -27,6 +27,8 @@

 #include "url_scanner_ex.h"

+#include "basic_functions_decl.h"
+
 #if defined(_WIN32) && !defined(__clang__)
 #include <intrin.h>
 #endif
diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php
index 4f77cf9b7f8..6fa0d47c7bd 100644
--- a/ext/standard/basic_functions.stub.php
+++ b/ext/standard/basic_functions.stub.php
@@ -1,6 +1,9 @@
 <?php

-/** @generate-class-entries */
+/**
+ * @generate-class-entries
+ * @generate-c-enums
+ */

 /* array.c */

diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h
index d63ea5e4e48..e467710f72f 100644
Binary files a/ext/standard/basic_functions_arginfo.h and b/ext/standard/basic_functions_arginfo.h differ
diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h
new file mode 100644
index 00000000000..9e6fb0def44
Binary files /dev/null and b/ext/standard/basic_functions_decl.h differ
diff --git a/ext/standard/math.c b/ext/standard/math.c
index 95384c06588..6418a020f4f 100644
--- a/ext/standard/math.c
+++ b/ext/standard/math.c
@@ -304,30 +304,28 @@ PHP_FUNCTION(floor)
 }
 /* }}} */

-PHPAPI int php_math_round_mode_from_enum(zend_object *mode)
+PHPAPI int php_math_round_mode_from_enum(zend_enum_RoundingMode mode)
 {
-	zval *case_name = zend_enum_fetch_case_name(mode);
-	zend_string *mode_name = Z_STR_P(case_name);
-
-	switch (ZSTR_VAL(mode_name)[0] + ZSTR_VAL(mode_name)[4]) {
-		case 'H' + 'A':
+	switch (mode) {
+		case ZEND_ENUM_RoundingMode_HalfAwayFromZero:
 			return PHP_ROUND_HALF_UP;
-		case 'H' + 'T':
+		case ZEND_ENUM_RoundingMode_HalfTowardsZero:
 			return PHP_ROUND_HALF_DOWN;
-		case 'H' + 'E':
+		case ZEND_ENUM_RoundingMode_HalfEven:
 			return PHP_ROUND_HALF_EVEN;
-		case 'H' + 'O':
+		case ZEND_ENUM_RoundingMode_HalfOdd:
 			return PHP_ROUND_HALF_ODD;
-		case 'T' + 'r':
+		case ZEND_ENUM_RoundingMode_TowardsZero:
 			return PHP_ROUND_TOWARD_ZERO;
-		case 'A' + 'F':
+		case ZEND_ENUM_RoundingMode_AwayFromZero:
 			return PHP_ROUND_AWAY_FROM_ZERO;
-		case 'N' + 't':
+		case ZEND_ENUM_RoundingMode_NegativeInfinity:
 			return PHP_ROUND_FLOOR;
-		case 'P' + 't':
+		case ZEND_ENUM_RoundingMode_PositiveInfinity:
 			return PHP_ROUND_CEILING;
-		EMPTY_SWITCH_DEFAULT_CASE();
 	}
+
+	ZEND_UNREACHABLE();
 }

 /* {{{ Returns the number rounded to specified precision */
@@ -355,7 +353,7 @@ PHP_FUNCTION(round)
 	}

 	if (mode_object != NULL) {
-		mode = php_math_round_mode_from_enum(mode_object);
+		mode = php_math_round_mode_from_enum(zend_enum_fetch_case_id(mode_object));
 	}

 	switch (mode) {
diff --git a/ext/standard/php_math_round_mode.h b/ext/standard/php_math_round_mode.h
index e8cbec64066..9ab02de2c36 100644
--- a/ext/standard/php_math_round_mode.h
+++ b/ext/standard/php_math_round_mode.h
@@ -16,6 +16,7 @@
 */

 #include "php.h"
+#include "basic_functions_decl.h"

 /* Define rounding modes (all are round-to-nearest) */
 #ifndef PHP_ROUND_HALF_UP
@@ -52,4 +53,4 @@

 extern PHPAPI zend_class_entry *rounding_mode_ce;

-PHPAPI int php_math_round_mode_from_enum(zend_object *mode);
+PHPAPI int php_math_round_mode_from_enum(zend_enum_RoundingMode mode);
diff --git a/ext/uri/php_uri.c b/ext/uri/php_uri.c
index 77fc627b6a9..2adece12553 100644
--- a/ext/uri/php_uri.c
+++ b/ext/uri/php_uri.c
@@ -678,7 +678,7 @@ static void throw_cannot_recompose_uri_to_string(php_uri_object *object)
 	zend_throw_exception_ex(php_uri_ce_error, 0, "Cannot recompose %s to a string", ZSTR_VAL(object->std.ce->name));
 }

-static void uri_equals(INTERNAL_FUNCTION_PARAMETERS, php_uri_object *that_object, zend_object *comparison_mode)
+static void uri_equals(INTERNAL_FUNCTION_PARAMETERS, php_uri_object *that_object, zend_enum_Uri_UriComparisonMode comparison_mode)
 {
 	php_uri_object *this_object = Z_URI_OBJECT_P(ZEND_THIS);
 	ZEND_ASSERT(this_object->uri != NULL);
@@ -691,11 +691,7 @@ static void uri_equals(INTERNAL_FUNCTION_PARAMETERS, php_uri_object *that_object
 		RETURN_FALSE;
 	}

-	bool exclude_fragment = true;
-	if (comparison_mode) {
-		zval *case_name = zend_enum_fetch_case_name(comparison_mode);
-		exclude_fragment = zend_string_equals_literal(Z_STR_P(case_name), "ExcludeFragment");
-	}
+	bool exclude_fragment = comparison_mode == ZEND_ENUM_Uri_UriComparisonMode_ExcludeFragment;

 	zend_string *this_str = this_object->parser->to_string(
 		this_object->uri, PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII, exclude_fragment);
@@ -721,12 +717,12 @@ static void uri_equals(INTERNAL_FUNCTION_PARAMETERS, php_uri_object *that_object
 PHP_METHOD(Uri_Rfc3986_Uri, equals)
 {
 	zend_object *that_object;
-	zend_object *comparison_mode = NULL;
+	zend_enum_Uri_UriComparisonMode comparison_mode = ZEND_ENUM_Uri_UriComparisonMode_ExcludeFragment;

 	ZEND_PARSE_PARAMETERS_START(1, 2)
 		Z_PARAM_OBJ_OF_CLASS(that_object, php_uri_ce_rfc3986_uri)
 		Z_PARAM_OPTIONAL
-		Z_PARAM_OBJ_OF_CLASS(comparison_mode, php_uri_ce_comparison_mode)
+		Z_PARAM_ENUM(comparison_mode, php_uri_ce_comparison_mode)
 	ZEND_PARSE_PARAMETERS_END();

 	uri_equals(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_uri_object_from_obj(that_object), comparison_mode);
@@ -917,12 +913,12 @@ PHP_METHOD(Uri_WhatWg_Url, getFragment)
 PHP_METHOD(Uri_WhatWg_Url, equals)
 {
 	zend_object *that_object;
-	zend_object *comparison_mode = NULL;
+	zend_enum_Uri_UriComparisonMode comparison_mode = ZEND_ENUM_Uri_UriComparisonMode_ExcludeFragment;

 	ZEND_PARSE_PARAMETERS_START(1, 2)
 		Z_PARAM_OBJ_OF_CLASS(that_object, php_uri_ce_whatwg_url)
 		Z_PARAM_OPTIONAL
-		Z_PARAM_OBJ_OF_CLASS(comparison_mode, php_uri_ce_comparison_mode)
+		Z_PARAM_ENUM(comparison_mode, php_uri_ce_comparison_mode)
 	ZEND_PARSE_PARAMETERS_END();

 	uri_equals(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_uri_object_from_obj(that_object), comparison_mode);
diff --git a/ext/uri/php_uri.stub.php b/ext/uri/php_uri.stub.php
index 9f12fbb1c07..6d4b2c3517a 100644
--- a/ext/uri/php_uri.stub.php
+++ b/ext/uri/php_uri.stub.php
@@ -1,6 +1,9 @@
 <?php

-/** @generate-class-entries */
+/**
+ * @generate-class-entries
+ * @generate-c-enums
+ */

 namespace Uri {
     /** @strict-properties */
diff --git a/ext/uri/php_uri_arginfo.h b/ext/uri/php_uri_arginfo.h
index 3bb73fb8d94..18d7f4adf78 100644
Binary files a/ext/uri/php_uri_arginfo.h and b/ext/uri/php_uri_arginfo.h differ
diff --git a/ext/uri/php_uri_common.h b/ext/uri/php_uri_common.h
index 109236879ae..2ae76cb2ee4 100644
--- a/ext/uri/php_uri_common.h
+++ b/ext/uri/php_uri_common.h
@@ -17,6 +17,8 @@
 #ifndef PHP_URI_COMMON_H
 #define PHP_URI_COMMON_H

+#include "php_uri_decl.h"
+
 extern zend_class_entry *php_uri_ce_rfc3986_uri;
 extern zend_class_entry *php_uri_ce_whatwg_url;
 extern zend_class_entry *php_uri_ce_comparison_mode;
diff --git a/ext/uri/php_uri_decl.h b/ext/uri/php_uri_decl.h
new file mode 100644
index 00000000000..3c069f3abe6
Binary files /dev/null and b/ext/uri/php_uri_decl.h differ
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
index e93362cebe0..63f9650f7ef 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -2,6 +2,7 @@

 /**
  * @generate-class-entries static
+ * @generate-c-enums
  * @generate-legacy-arginfo 80000
  * @undocumentable
  */
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index e7d09d7da5f..6b5dfe9c256 100644
Binary files a/ext/zend_test/test_arginfo.h and b/ext/zend_test/test_arginfo.h differ
diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h
new file mode 100644
index 00000000000..a6254865a87
Binary files /dev/null and b/ext/zend_test/test_decl.h differ