[php-src] master: Add trait support for internal classes (GH-20976)

Khaled Alam via GitHub <[email protected]> Mon, 20 Jul 2026 21:32:25 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Khaled Alam (khaledalam)
Committer: GitHub (web-flow)
Pusher: iluuu1994
Date: 2026-07-20T23:32:23+02:00

Commit: https://github.com/php/php-src/commit/63e26ab71620a4f636bde1d6c657f9aca5ce987e
Raw diff: https://github.com/php/php-src/commit/63e26ab71620a4f636bde1d6c657f9aca5ce987e.diff

Add trait support for internal classes (GH-20976)

Fixes GH-20974

Co-authored-by: Ilija Tovilo <[email protected]>

Changed paths:
  A  ext/zend_test/tests/internal_class_traits.phpt
  M  UPGRADING.INTERNALS
  M  Zend/zend_API.h
  M  Zend/zend_inheritance.c
  M  Zend/zend_opcode.c
  M  build/gen_stub.php
  M  ext/zend_test/test.c
  M  ext/zend_test/test.stub.php
  M  ext/zend_test/test_arginfo.h
  M  ext/zend_test/test_decl.h
  M  ext/zend_test/test_legacy_arginfo.h


Diff:

diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index ae58e7ba8d5b..ef5155e39db6 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -146,6 +146,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES
     php_stream_copy_to_stream_ex(). The mmap-based copy fallback was removed.
   . Added zend_string_equals_cstr_ci().
   . Added zend_string_ends_with() and related variants.
+  . Added trait support for internal classes.
 
 ========================
 2. Build system changes
diff --git a/Zend/zend_API.h b/Zend/zend_API.h
index bbfe64257744..2c0b892d1941 100644
--- a/Zend/zend_API.h
+++ b/Zend/zend_API.h
@@ -395,6 +395,7 @@ ZEND_API zend_class_entry *zend_register_internal_class_ex(const zend_class_entr
 ZEND_API zend_class_entry *zend_register_internal_class_with_flags(const zend_class_entry *class_entry, zend_class_entry *parent_ce, uint32_t flags);
 ZEND_API zend_class_entry *zend_register_internal_interface(const zend_class_entry *orig_class_entry);
 ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_interfaces, ...);
+ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...);
 
 ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_len, zend_class_entry *ce, bool persistent);
 
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index 583b10d34188..4424c9a1a3ab 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -2403,7 +2403,11 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
 		}
 	}
 
-	if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
+	if (ce->type == ZEND_INTERNAL_CLASS) {
+		ZEND_ASSERT(fn->type == ZEND_INTERNAL_FUNCTION);
+		new_fn = (zend_function*)(uintptr_t)malloc(sizeof(zend_internal_function));
+		memcpy(new_fn, fn, sizeof(zend_internal_function));
+	} else if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
 		new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
 		memcpy(new_fn, fn, sizeof(zend_internal_function));
 		new_fn->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED;
@@ -2833,7 +2837,11 @@ static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_ent
 			if (do_trait_constant_check(ce, constant, constant_name, traits, i)) {
 				zend_class_constant *ct = NULL;
 
-				ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant));
+				if (ce->type == ZEND_INTERNAL_CLASS) {
+					ct = malloc(sizeof(zend_class_constant));
+				} else {
+					ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant));
+				}
 				memcpy(ct, constant, sizeof(zend_class_constant));
 				constant = ct;
 
@@ -3012,6 +3020,49 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
 }
 /* }}} */
 
+ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...)
+{
+	ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED);
+	ZEND_ASSERT(num_traits >= 0);
+
+	if (UNEXPECTED(num_traits == 0)) {
+		return;
+	}
+
+	zend_class_entry **traits = safe_pemalloc(num_traits, sizeof(zend_class_entry *), 0, /* persistent */ true);
+	class_entry->trait_names = safe_pemalloc(num_traits, sizeof(zend_class_name), 0, /* persistent */ true);
+	class_entry->num_traits = num_traits;
+
+	va_list trait_list;
+	va_start(trait_list, num_traits);
+	for (int i = 0; i < num_traits; i++) {
+		zend_class_entry *trait_entry = va_arg(trait_list, zend_class_entry *);
+		class_entry->trait_names[i].name = zend_string_copy(trait_entry->name);
+		class_entry->trait_names[i].lc_name = zend_string_tolower_ex(zend_string_copy(trait_entry->name), /* persistent */ true);
+
+		if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) {
+			free(traits);
+			zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot use %s - it is not a trait",
+				ZSTR_VAL(class_entry->name), ZSTR_VAL(trait_entry->name));
+		}
+		traits[i] = trait_entry;
+	}
+	va_end(trait_list);
+
+	bool contains_abstract_methods = false;
+	zend_do_traits_method_binding(class_entry, traits, NULL, NULL, false, &contains_abstract_methods);
+	zend_do_traits_constant_binding(class_entry, traits);
+	zend_do_traits_property_binding(class_entry, traits);
+
+	ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, zend_function *fn) {
+		zend_fixup_trait_method(fn, class_entry);
+	} ZEND_HASH_FOREACH_END();
+
+	free(traits);
+
+	/* TODO: Verify abstract trait method implementation requirements are enforced. */
+}
+
 #define MAX_ABSTRACT_INFO_CNT 3
 #define MAX_ABSTRACT_INFO_FMT "%s%s%s%s"
 #define DISPLAY_ABSTRACT_FN(idx) \
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index 538eff3ea34d..a25f236c3c20 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -485,7 +485,7 @@ ZEND_API void destroy_zend_class(zval *zv)
 			zend_string_release_ex(ce->name, 1);
 
 			ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) {
-				if (fn->common.scope == ce) {
+				if (fn->common.scope == ce && !(fn->common.fn_flags & ZEND_ACC_TRAIT_CLONE)) {
 					zend_free_internal_arg_info(&fn->internal_function, true);
 
 					if (fn->common.attributes) {
@@ -535,6 +535,13 @@ ZEND_API void destroy_zend_class(zval *zv)
 			if (ce->attributes) {
 				zend_hash_release(ce->attributes);
 			}
+			if (ce->num_traits > 0) {
+				for (uint32_t i = 0; i < ce->num_traits; i++) {
+					zend_string_release(ce->trait_names[i].name);
+					zend_string_release(ce->trait_names[i].lc_name);
+				}
+				free(ce->trait_names);
+			}
 			free(ce);
 			break;
 	}
diff --git a/build/gen_stub.php b/build/gen_stub.php
index 715a47182340..b2f38c978cf3 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -13,6 +13,7 @@
 use PhpParser\Node\Stmt\Enum_;
 use PhpParser\Node\Stmt\Interface_;
 use PhpParser\Node\Stmt\Trait_;
+use PhpParser\Node\Stmt\TraitUse;
 use PhpParser\PrettyPrinter\Standard;
 use PhpParser\PrettyPrinterAbstract;
 
@@ -3383,6 +3384,7 @@ class ClassInfo {
      * @param AttributeInfo[] $attributes
      * @param Name[] $extends
      * @param Name[] $implements
+     * @param Name[] $uses
      * @param ConstInfo[] $constInfos
      * @param PropertyInfo[] $propertyInfos
      * @param FuncInfo[] $funcInfos
@@ -3401,6 +3403,7 @@ public function __construct(
         private bool $isNotSerializable,
         private readonly array $extends,
         private readonly array $implements,
+        private readonly array $uses,
         public /* readonly */ array $constInfos,
         private /* readonly */ array $propertyInfos,
         public array $funcInfos,
@@ -3421,6 +3424,9 @@ public function getRegistration(array $allConstInfos): string
         foreach ($this->implements as $implements) {
             $params[] = "zend_class_entry *class_entry_" . implode("_", $implements->getParts());
         }
+        foreach ($this->uses as $use) {
+            $params[] = "zend_class_entry *class_entry_" . implode("_", $use->getParts());
+        }
 
         $escapedName = implode("_", $this->name->getParts());
 
@@ -3518,6 +3524,17 @@ function (Name $item) {
             $code .= "\tzend_class_implements(class_entry, " . count($implements) . ", " . implode(", ", $implements) . ");\n";
         }
 
+        $traits = array_map(
+            function (Name $item) {
+                return "class_entry_" . implode("_", $item->getParts());
+            },
+            $this->uses
+        );
+
+        if (!empty($traits)) {
+            $code .= "\tzend_class_use_internal_traits(class_entry, " . count($traits) . ", " . implode(", ", $traits) . ");\n";
+        }
+
         if ($this->alias) {
             $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n";
         }
@@ -4408,6 +4425,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
                 $propertyInfos = [];
                 $methodInfos = [];
                 $enumCaseInfos = [];
+                $traitUses = [];
                 foreach ($stmt->stmts as $classStmt) {
                     $cond = self::handlePreprocessorConditions($conds, $classStmt);
                     if ($classStmt instanceof Stmt\Nop) {
@@ -4469,6 +4487,13 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
                             $classStmt->expr,
                             $classStmt->expr ? $prettyPrinter->prettyPrintExpr($classStmt->expr) : null,
                         );
+                    } else if ($classStmt instanceof TraitUse) {
+                        if ($classStmt->adaptations) {
+                            throw new Exception("Trait adaptations are not supported");
+                        }
+                        foreach ($classStmt->traits as $trait) {
+                            $traitUses[] = $trait;
+                        }
                     } else {
                         throw new Exception("Not implemented {$classStmt->getType()}");
                     }
@@ -4481,6 +4506,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
                     $propertyInfos,
                     $methodInfos,
                     $enumCaseInfos,
+                    $traitUses,
                     $cond,
                     $this->getMinimumPhpVersionIdCompatibility(),
                     $this->isUndocumentable
@@ -5167,6 +5193,7 @@ function parseProperty(
  * @param PropertyInfo[] $properties
  * @param FuncInfo[] $methods
  * @param EnumCaseInfo[] $enumCases
+ * @param Name[] $traitUses
  */
 function parseClass(
     Name $name,
@@ -5175,6 +5202,7 @@ function parseClass(
     array $properties,
     array $methods,
     array $enumCases,
+    array $traitUses,
     ?string $cond,
     ?int $minimumPhpVersionIdCompatibility,
     bool $isUndocumentable
@@ -5247,6 +5275,7 @@ function parseClass(
         $isNotSerializable,
         $extends,
         $implements,
+        $traitUses,
         $consts,
         $properties,
         $methods,
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index e8058936b6da..a880c09fc1ff 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -54,6 +54,9 @@ static zend_class_entry *zend_test_child_class;
 static zend_class_entry *zend_test_gen_stub_flag_compatibility_test;
 static zend_class_entry *zend_attribute_test_class;
 static zend_class_entry *zend_test_trait;
+static zend_class_entry *zend_test_trait_for_internal_class;
+static zend_class_entry *zend_test_trait_for_internal_class2;
+static zend_class_entry *zend_test_class_with_traits;
 static zend_class_entry *zend_test_attribute;
 static zend_class_entry *zend_test_repeatable_attribute;
 static zend_class_entry *zend_test_parameter_attribute;
@@ -1305,6 +1308,18 @@ static ZEND_METHOD(_ZendTestTrait, testMethod)
 	RETURN_TRUE;
 }
 
+static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod)
+{
+	ZEND_PARSE_PARAMETERS_NONE();
+	RETURN_LONG(789);
+}
+
+static ZEND_METHOD(_ZendTestTraitForInternalClass2, traitMethod2)
+{
+	ZEND_PARSE_PARAMETERS_NONE();
+	RETURN_LONG(101);
+}
+
 static ZEND_METHOD(ZendTestNS_Foo, method)
 {
 	ZEND_PARSE_PARAMETERS_NONE();
@@ -1607,6 +1622,9 @@ PHP_MINIT_FUNCTION(zend_test)
 	zend_attribute_test_class = register_class_ZendAttributeTest();
 
 	zend_test_trait = register_class__ZendTestTrait();
+	zend_test_trait_for_internal_class = register_class__ZendTestTraitForInternalClass();
+	zend_test_trait_for_internal_class2 = register_class__ZendTestTraitForInternalClass2();
+	zend_test_class_with_traits = register_class__ZendTestClassWithTraits(zend_test_trait_for_internal_class, zend_test_trait_for_internal_class2);
 
 	register_test_symbols(module_number);
 
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
index a4562368735e..7c96ea176a88 100644
--- a/ext/zend_test/test.stub.php
+++ b/ext/zend_test/test.stub.php
@@ -35,6 +35,32 @@ interface _ZendTestInterface
         public const DUMMY = 0;
     }
 
+    trait _ZendTestTraitForInternalClass
+    {
+        /** @var int */
+        public const ZEND_TRAIT_CONST = 123;
+
+        public int $traitProp = 456;
+
+        public function traitMethod(): int {}
+    }
+
+    trait _ZendTestTraitForInternalClass2
+    {
+        /** @var int */
+        public const ZEND_TRAIT_CONST2 = 321;
+
+        public static int $staticTraitProp = 999;
+
+        public function traitMethod2(): int {}
+    }
+
+    class _ZendTestClassWithTraits
+    {
+        use _ZendTestTraitForInternalClass;
+        use _ZendTestTraitForInternalClass2;
+    }
+
     /** @alias _ZendTestClassAlias */
     class _ZendTestClass implements _ZendTestInterface {
         public const mixed TYPED_CLASS_CONST1 = [];
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
index bd6548e7bffa..b2e342382db7 100644
--- a/ext/zend_test/test_arginfo.h
+++ b/ext/zend_test/test_arginfo.h
@@ -1,5 +1,5 @@
 /* This is a generated file, edit test.stub.php instead.
- * Stub hash: 8ca2fc33013d5a1c325bf5f0090cc6416a242297
+ * Stub hash: a221a3df3815679d61fd546ba120fd3a374fe71f
  * Has decl header: yes */
 
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0)
@@ -226,6 +226,10 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_test_void_return
 
+#define arginfo_class__ZendTestTraitForInternalClass_traitMethod arginfo_zend_test_nodiscard
+
+#define arginfo_class__ZendTestTraitForInternalClass2_traitMethod2 arginfo_zend_test_nodiscard
+
 #define arginfo_class__ZendTestClass_is_object arginfo_zend_test_nodiscard
 
 #define arginfo_class__ZendTestClass___toString arginfo_zend_get_current_func_name
@@ -361,6 +365,8 @@ static ZEND_FUNCTION(ZendTestNS2_namespaced_func);
 static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func);
 static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func);
 static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_deprecated_func);
+static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod);
+static ZEND_METHOD(_ZendTestTraitForInternalClass2, traitMethod2);
 static ZEND_METHOD(_ZendTestClass, is_object);
 static ZEND_METHOD(_ZendTestClass, __toString);
 static ZEND_METHOD(_ZendTestClass, returnsStatic);
@@ -542,6 +548,16 @@ static const zend_function_entry ext_functions[] = {
 	ZEND_FE_END
 };
 
+static const zend_function_entry class__ZendTestTraitForInternalClass_methods[] = {
+	ZEND_ME(_ZendTestTraitForInternalClass, traitMethod, arginfo_class__ZendTestTraitForInternalClass_traitMethod, ZEND_ACC_PUBLIC)
+	ZEND_FE_END
+};
+
+static const zend_function_entry class__ZendTestTraitForInternalClass2_methods[] = {
+	ZEND_ME(_ZendTestTraitForInternalClass2, traitMethod2, arginfo_class__ZendTestTraitForInternalClass2_traitMethod2, ZEND_ACC_PUBLIC)
+	ZEND_FE_END
+};
+
 static const zend_function_entry class__ZendTestClass_methods[] = {
 	ZEND_ME(_ZendTestClass, is_object, arginfo_class__ZendTestClass_is_object, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
 	ZEND_ME(_ZendTestClass, __toString, arginfo_class__ZendTestClass___toString, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
@@ -720,6 +736,75 @@ static zend_class_entry *register_class__ZendTestInterface(void)
 	return class_entry;
 }
 
+static zend_class_entry *register_class__ZendTestTraitForInternalClass(void)
+{
+	zend_class_entry ce, *class_entry;
+
+	INIT_CLASS_ENTRY(ce, "_ZendTestTraitForInternalClass", class__ZendTestTraitForInternalClass_methods);
+#if (PHP_VERSION_ID >= 80400)
+	class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_TRAIT);
+#else
+	class_entry = zend_register_internal_class_ex(&ce, NULL);
+	class_entry->ce_flags |= ZEND_ACC_TRAIT;
+#endif
+
+	zval const_ZEND_TRAIT_CONST_value;
+	ZVAL_LONG(&const_ZEND_TRAIT_CONST_value, 123);
+	zend_string *const_ZEND_TRAIT_CONST_name = zend_string_init_interned("ZEND_TRAIT_CONST", sizeof("ZEND_TRAIT_CONST") - 1, true);
+	zend_declare_class_constant_ex(class_entry, const_ZEND_TRAIT_CONST_name, &const_ZEND_TRAIT_CONST_value, ZEND_ACC_PUBLIC, NULL);
+	zend_string_release_ex(const_ZEND_TRAIT_CONST_name, true);
+
+	zval property_traitProp_default_value;
+	ZVAL_LONG(&property_traitProp_default_value, 456);
+	zend_string *property_traitProp_name = zend_string_init("traitProp", sizeof("traitProp") - 1, true);
+	zend_declare_typed_property(class_entry, property_traitProp_name, &property_traitProp_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
+	zend_string_release_ex(property_traitProp_name, true);
+
+	return class_entry;
+}
+
+static zend_class_entry *register_class__ZendTestTraitForInternalClass2(void)
+{
+	zend_class_entry ce, *class_entry;
+
+	INIT_CLASS_ENTRY(ce, "_ZendTestTraitForInternalClass2", class__ZendTestTraitForInternalClass2_methods);
+#if (PHP_VERSION_ID >= 80400)
+	class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_TRAIT);
+#else
+	class_entry = zend_register_internal_class_ex(&ce, NULL);
+	class_entry->ce_flags |= ZEND_ACC_TRAIT;
+#endif
+
+	zval const_ZEND_TRAIT_CONST2_value;
+	ZVAL_LONG(&const_ZEND_TRAIT_CONST2_value, 321);
+	zend_string *const_ZEND_TRAIT_CONST2_name = zend_string_init_interned("ZEND_TRAIT_CONST2", sizeof("ZEND_TRAIT_CONST2") - 1, true);
+	zend_declare_class_constant_ex(class_entry, const_ZEND_TRAIT_CONST2_name, &const_ZEND_TRAIT_CONST2_value, ZEND_ACC_PUBLIC, NULL);
+	zend_string_release_ex(const_ZEND_TRAIT_CONST2_name, true);
+
+	zval property_staticTraitProp_default_value;
+	ZVAL_LONG(&property_staticTraitProp_default_value, 999);
+	zend_string *property_staticTraitProp_name = zend_string_init("staticTraitProp", sizeof("staticTraitProp") - 1, true);
+	zend_declare_typed_property(class_entry, property_staticTraitProp_name, &property_staticTraitProp_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
+	zend_string_release_ex(property_staticTraitProp_name, true);
+
+	return class_entry;
+}
+
+static zend_class_entry *register_class__ZendTestClassWithTraits(zend_class_entry *class_entry__ZendTestTraitForInternalClass, zend_class_entry *class_entry__ZendTestTraitForInternalClass2)
+{
+	zend_class_entry ce, *class_entry;
+
+	INIT_CLASS_ENTRY(ce, "_ZendTestClassWithTraits", NULL);
+#if (PHP_VERSION_ID >= 80400)
+	class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0);
+#else
+	class_entry = zend_register_internal_class_ex(&ce, NULL);
+#endif
+	zend_class_use_internal_traits(class_entry, 2, class_entry__ZendTestTraitForInternalClass, class_entry__ZendTestTraitForInternalClass2);
+
+	return class_entry;
+}
+
 static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_entry__ZendTestInterface)
 {
 	zend_class_entry ce, *class_entry;
diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h
index 2561000f4b60..ea6a2c94fbdf 100644
--- a/ext/zend_test/test_decl.h
+++ b/ext/zend_test/test_decl.h
@@ -1,8 +1,8 @@
 /* This is a generated file, edit test.stub.php instead.
- * Stub hash: 8ca2fc33013d5a1c325bf5f0090cc6416a242297 */
+ * Stub hash: a221a3df3815679d61fd546ba120fd3a374fe71f */
 
-#ifndef ZEND_TEST_DECL_8ca2fc33013d5a1c325bf5f0090cc6416a242297_H
-#define ZEND_TEST_DECL_8ca2fc33013d5a1c325bf5f0090cc6416a242297_H
+#ifndef ZEND_TEST_DECL_a221a3df3815679d61fd546ba120fd3a374fe71f_H
+#define ZEND_TEST_DECL_a221a3df3815679d61fd546ba120fd3a374fe71f_H
 
 typedef enum zend_enum_ZendTestUnitEnum {
 	ZEND_ENUM_ZendTestUnitEnum_Foo = 1,
@@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface {
 	ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2,
 } zend_enum_ZendTestEnumWithInterface;
 
-#endif /* ZEND_TEST_DECL_8ca2fc33013d5a1c325bf5f0090cc6416a242297_H */
+#endif /* ZEND_TEST_DECL_a221a3df3815679d61fd546ba120fd3a374fe71f_H */
diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h
index a254a637e07d..05014c80fd5d 100644
--- a/ext/zend_test/test_legacy_arginfo.h
+++ b/ext/zend_test/test_legacy_arginfo.h
@@ -1,5 +1,5 @@
 /* This is a generated file, edit test.stub.php instead.
- * Stub hash: 8ca2fc33013d5a1c325bf5f0090cc6416a242297
+ * Stub hash: a221a3df3815679d61fd546ba120fd3a374fe71f
  * Has decl header: yes */
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, 0)
@@ -202,6 +202,10 @@ ZEND_END_ARG_INFO()
 
 #define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_trigger_bailout
 
+#define arginfo_class__ZendTestTraitForInternalClass_traitMethod arginfo_zend_trigger_bailout
+
+#define arginfo_class__ZendTestTraitForInternalClass2_traitMethod2 arginfo_zend_trigger_bailout
+
 #define arginfo_class__ZendTestClass_is_object arginfo_zend_trigger_bailout
 
 #define arginfo_class__ZendTestClass___toString arginfo_zend_trigger_bailout
@@ -324,6 +328,8 @@ static ZEND_FUNCTION(ZendTestNS2_namespaced_func);
 static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func);
 static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func);
 static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_deprecated_func);
+static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod);
+static ZEND_METHOD(_ZendTestTraitForInternalClass2, traitMethod2);
 static ZEND_METHOD(_ZendTestClass, is_object);
 static ZEND_METHOD(_ZendTestClass, __toString);
 static ZEND_METHOD(_ZendTestClass, returnsStatic);
@@ -469,6 +475,16 @@ static const zend_function_entry ext_functions[] = {
 	ZEND_FE_END
 };
 
+static const zend_function_entry class__ZendTestTraitForInternalClass_methods[] = {
+	ZEND_ME(_ZendTestTraitForInternalClass, traitMethod, arginfo_class__ZendTestTraitForInternalClass_traitMethod, ZEND_ACC_PUBLIC)
+	ZEND_FE_END
+};
+
+static const zend_function_entry class__ZendTestTraitForInternalClass2_methods[] = {
+	ZEND_ME(_ZendTestTraitForInternalClass2, traitMethod2, arginfo_class__ZendTestTraitForInternalClass2_traitMethod2, ZEND_ACC_PUBLIC)
+	ZEND_FE_END
+};
+
 static const zend_function_entry class__ZendTestClass_methods[] = {
 	ZEND_ME(_ZendTestClass, is_object, arginfo_class__ZendTestClass_is_object, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
 	ZEND_ME(_ZendTestClass, __toString, arginfo_class__ZendTestClass___toString, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
@@ -595,6 +611,75 @@ static zend_class_entry *register_class__ZendTestInterface(void)
 	return class_entry;
 }
 
+static zend_class_entry *register_class__ZendTestTraitForInternalClass(void)
+{
+	zend_class_entry ce, *class_entry;
+
+	INIT_CLASS_ENTRY(ce, "_ZendTestTraitForInternalClass", class__ZendTestTraitForInternalClass_methods);
+#if (PHP_VERSION_ID >= 80400)
+	class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_TRAIT);
+#else
+	class_entry = zend_register_internal_class_ex(&ce, NULL);
+	class_entry->ce_flags |= ZEND_ACC_TRAIT;
+#endif
+
+	zval const_ZEND_TRAIT_CONST_value;
+	ZVAL_LONG(&const_ZEND_TRAIT_CONST_value, 123);
+	zend_string *const_ZEND_TRAIT_CONST_name = zend_string_init_interned("ZEND_TRAIT_CONST", sizeof("ZEND_TRAIT_CONST") - 1, true);
+	zend_declare_class_constant_ex(class_entry, const_ZEND_TRAIT_CONST_name, &const_ZEND_TRAIT_CONST_value, ZEND_ACC_PUBLIC, NULL);
+	zend_string_release_ex(const_ZEND_TRAIT_CONST_name, true);
+
+	zval property_traitProp_default_value;
+	ZVAL_LONG(&property_traitProp_default_value, 456);
+	zend_string *property_traitProp_name = zend_string_init("traitProp", sizeof("traitProp") - 1, true);
+	zend_declare_property_ex(class_entry, property_traitProp_name, &property_traitProp_default_value, ZEND_ACC_PUBLIC, NULL);
+	zend_string_release_ex(property_traitProp_name, true);
+
+	return class_entry;
+}
+
+static zend_class_entry *register_class__ZendTestTraitForInternalClass2(void)
+{
+	zend_class_entry ce, *class_entry;
+
+	INIT_CLASS_ENTRY(ce, "_ZendTestTraitForInternalClass2", class__ZendTestTraitForInternalClass2_methods);
+#if (PHP_VERSION_ID >= 80400)
+	class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_TRAIT);
+#else
+	class_entry = zend_register_internal_class_ex(&ce, NULL);
+	class_entry->ce_flags |= ZEND_ACC_TRAIT;
+#endif
+
+	zval const_ZEND_TRAIT_CONST2_value;
+	ZVAL_LONG(&const_ZEND_TRAIT_CONST2_value, 321);
+	zend_string *const_ZEND_TRAIT_CONST2_name = zend_string_init_interned("ZEND_TRAIT_CONST2", sizeof("ZEND_TRAIT_CONST2") - 1, true);
+	zend_declare_class_constant_ex(class_entry, const_ZEND_TRAIT_CONST2_name, &const_ZEND_TRAIT_CONST2_value, ZEND_ACC_PUBLIC, NULL);
+	zend_string_release_ex(const_ZEND_TRAIT_CONST2_name, true);
+
+	zval property_staticTraitProp_default_value;
+	ZVAL_LONG(&property_staticTraitProp_default_value, 999);
+	zend_string *property_staticTraitProp_name = zend_string_init("staticTraitProp", sizeof("staticTraitProp") - 1, true);
+	zend_declare_property_ex(class_entry, property_staticTraitProp_name, &property_staticTraitProp_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, NULL);
+	zend_string_release_ex(property_staticTraitProp_name, true);
+
+	return class_entry;
+}
+
+static zend_class_entry *register_class__ZendTestClassWithTraits(zend_class_entry *class_entry__ZendTestTraitForInternalClass, zend_class_entry *class_entry__ZendTestTraitForInternalClass2)
+{
+	zend_class_entry ce, *class_entry;
+
+	INIT_CLASS_ENTRY(ce, "_ZendTestClassWithTraits", NULL);
+#if (PHP_VERSION_ID >= 80400)
+	class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0);
+#else
+	class_entry = zend_register_internal_class_ex(&ce, NULL);
+#endif
+	zend_class_use_internal_traits(class_entry, 2, class_entry__ZendTestTraitForInternalClass, class_entry__ZendTestTraitForInternalClass2);
+
+	return class_entry;
+}
+
 static zend_class_entry *register_class__ZendTestClass(zend_class_entry *class_entry__ZendTestInterface)
 {
 	zend_class_entry ce, *class_entry;
diff --git a/ext/zend_test/tests/internal_class_traits.phpt b/ext/zend_test/tests/internal_class_traits.phpt
new file mode 100644
index 000000000000..c1a378ee99d4
--- /dev/null
+++ b/ext/zend_test/tests/internal_class_traits.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Test traits in internal classes
+--EXTENSIONS--
+zend_test
+--FILE--
+<?php
+
+// Constants from both traits
+var_dump(_ZendTestClassWithTraits::ZEND_TRAIT_CONST);
+var_dump(_ZendTestClassWithTraits::ZEND_TRAIT_CONST2);
+
+$obj = new _ZendTestClassWithTraits();
+
+// Property from trait 1
+var_dump($obj->traitProp);
+
+// Methods from both traits
+var_dump($obj->traitMethod());
+var_dump($obj->traitMethod2());
+
+// Static property from trait 2
+var_dump(_ZendTestClassWithTraits::$staticTraitProp);
+
+// Reflection should show both traits
+$rc = new ReflectionClass(_ZendTestClassWithTraits::class);
+$traits = $rc->getTraitNames();
+sort($traits);
+var_dump(count($traits));
+var_dump($traits);
+
+echo "Done\n";
+?>
+--EXPECT--
+int(123)
+int(321)
+int(456)
+int(789)
+int(101)
+int(999)
+int(2)
+array(2) {
+  [0]=>
+  string(30) "_ZendTestTraitForInternalClass"
+  [1]=>
+  string(31) "_ZendTestTraitForInternalClass2"
+}
+Done