diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000000..13566b81b018a
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/customTargets.xml b/.idea/customTargets.xml
new file mode 100644
index 0000000000000..d1c7a7505d836
--- /dev/null
+++ b/.idea/customTargets.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/editor.xml b/.idea/editor.xml
new file mode 100644
index 0000000000000..626ca2200383c
--- /dev/null
+++ b/.idea/editor.xml
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000000..e7747caa47a8d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php-src.iml b/.idea/php-src.iml
new file mode 100644
index 0000000000000..bc2cd87409057
--- /dev/null
+++ b/.idea/php-src.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000000..35eb1ddfbbc02
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000..9afeb9e49ddfe
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+FROM ubuntu:noble
+
+RUN apt update && apt install -y pkg-config build-essential autoconf bison re2c \
+ libxml2-dev libsqlite3-dev cmake gdb
+WORKDIR /app
+
+CMD while true; do sleep 3600; done
diff --git a/Dockerfile.php b/Dockerfile.php
new file mode 100644
index 0000000000000..bcccb66b9b83f
--- /dev/null
+++ b/Dockerfile.php
@@ -0,0 +1,8 @@
+FROM ubuntu:noble
+
+RUN apt update && apt install -y pkg-config build-essential autoconf bison re2c \
+ libxml2-dev libsqlite3-dev cmake gdb
+WORKDIR /app
+
+COPY ./sapi/cli/php php
+RUN chmod +x /app/php
\ No newline at end of file
diff --git a/LE.md b/LE.md
new file mode 100644
index 0000000000000..179c1986bd82a
--- /dev/null
+++ b/LE.md
@@ -0,0 +1,154 @@
+# Generics syntax
+
+```
+interface Comparable {
+
+}
+
+interface Equatable {
+
+}
+
+int implements Comparable, Equatable;
+
+class Foo {
+
+ class Bar {
+
+ }
+
+}
+
+enum FooBar {
+
+ case Blum(int bam);
+ case Bam(T boom);
+
+}
+
+```
+
+## Generic classes and interfaces
+
+### Basic case
+```lhp
+class GenericTestClass {
+ // T can be used as type alias inside of 'GenericTestClass'
+ public function __construct(
+ private T $property
+ ) {}
+
+ public function getMyT(): T {
+ return $this->property;
+ }
+
+ public function setMyT(T $newValue) {
+ $this->property = $newValue;
+ }
+}
+```
+
+### Multiple Generics
+```lehp
+class GenericTestClass {
+ // T and U can now be used as type aliases inside of 'GenericTestClass'
+
+ public function __construct(
+ private T $first,
+ private U $second
+ ) {}
+
+ public function getFirst(): T {
+ return $this->first;
+ }
+
+ public function setFirst(T $value) {
+ $this->first = $value;
+ }
+
+ public function getSecond(): U {
+ return $this->second;
+ }
+
+ public function setSecond(U $value) {
+ $this->second = $value;
+ }
+}
+```
+
+### Bounded Gerics
+```lehp
+interface Serializable {
+ public function serialize(): string;
+}
+
+class BoundedGenericClass {
+ public function __construct(
+ private T $serializable
+ ) {}
+
+ public function getSerialized(): string
+ {
+ return $this->serializable->serialize();
+ }
+
+ public function getAsSerializable(): Serializable
+ {
+ return (Serializable) $this->serializable; // cast optional
+ }
+
+ public function getObject(): T
+ {
+ return $this->serializable;
+ }
+}
+```
+
+### A little bit more complex
+```
+interface Serializer {
+ public function serialize(T $object): string;
+}
+
+class RandomAssSerializableContaier> {
+
+ private array $content = [];
+
+ public function __construct(
+ private K serializer
+ ) {}
+
+ public function add(T $toAdd)
+ {
+ $content[] = $toAdd;
+ }
+
+ public function asSerializedList(): array
+ {
+ return array_map(
+ fn ($obj) $this->serializer->serialize($obj),
+ $this->content
+ );
+ }
+}
+```
+## Functions
+### Basic case
+```
+function noOp(T $in): T
+{
+ return $in;
+}
+```
+
+### A bit more complex
+```
+interface Serializer {
+ public function serialize(T $object): string;
+}
+
+function serialize>(T $in, K $serializer): string
+{
+ return $serializer->serialize($in);
+}
+```
\ No newline at end of file
diff --git a/Zend/zend.h b/Zend/zend.h
index 0cf1faeb653fe..dc3d41244363a 100644
--- a/Zend/zend.h
+++ b/Zend/zend.h
@@ -144,8 +144,17 @@ struct _zend_inheritance_cache_entry {
zend_class_entry *traits_and_interfaces[1];
};
+typedef struct _zend_type_parameter {
+ struct _zend_generic_type *elements;
+ size_t element_count;
+ zend_string *name;
+} zend_type_parameter;
+
struct _zend_class_entry {
+ // TYPE HERE
char type;
+ size_t generic_type_count;
+ zend_type_parameter *generic_type;
zend_string *name;
/* class_entry or string depending on ZEND_ACC_LINKED */
union {
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index 9348c35f6cc07..c3b09a7ddb1ac 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -70,6 +70,7 @@ enum _zend_ast_kind {
ZEND_AST_ATTRIBUTE_GROUP,
ZEND_AST_MATCH_ARM_LIST,
ZEND_AST_MODIFIER_LIST,
+ ZEND_AST_GENERIC_TYPE_PARAM_LIST,
/* 0 child nodes */
ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT,
@@ -155,6 +156,8 @@ enum _zend_ast_kind {
ZEND_AST_NAMED_ARG,
ZEND_AST_PARENT_PROPERTY_HOOK_CALL,
+ ZEND_AST_GENERIC_TYPE,
+
/* 3 child nodes */
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_NULLSAFE_METHOD_CALL,
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 8e4221673c4cf..f0a999a30e32b 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -9043,6 +9043,53 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_
zend_type_release(type, 0);
}
+// LEHP-RELEVANT
+// compiles generic class params from the AST to the zend_class_entry
+static void zend_compile_class_generic_params(zend_class_entry *class_entry, zend_ast *ast) {
+ ZEND_ASSERT(ast->kind == ZEND_AST_GENERIC_TYPE_PARAM_LIST);
+ // we know this is a list as it has GENERIC_LIST_TYPE
+ zend_ast_list *generic_param_list = zend_ast_get_list(ast);
+
+ class_entry->generic_type_count = generic_param_list->children;
+ class_entry->generic_type = ecalloc(generic_param_list->children, sizeof(zend_type_parameter));
+ for (uint32_t i = 0; i < generic_param_list->children; i++) {
+ zend_ast *param_ast = generic_param_list->child[i];
+ zend_type_parameter *type_param = &class_entry->generic_type[i];
+
+ if (param_ast->kind == ZEND_AST_ZVAL) {
+ // Simple type parameter T
+ zend_ast_zval *zval_ast = (zend_ast_zval*)param_ast;
+ if (Z_TYPE(zval_ast->val) != IS_STRING) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Generic type parameter name must be a string");
+ }
+ type_param->name = zend_string_copy(Z_STR(zval_ast->val));
+ type_param->element_count = 0;
+ type_param->elements = NULL;
+ zend_resolve_const_class_name_reference(zval_ast, "class name");
+ } else if (param_ast->kind == ZEND_AST_GENERIC_TYPE) {
+ // Type parameter with constraints like T:string
+ if (param_ast->child[0]->kind != ZEND_AST_ZVAL) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Generic type parameter name must be a simple identifier");
+ }
+
+ zend_ast_zval *name_ast = (zend_ast_zval*)param_ast->child[0];
+ if (Z_TYPE(name_ast->val) != IS_STRING) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Generic type parameter name must be a string");
+ }
+
+ type_param->name = zend_string_copy(Z_STR(name_ast->val));
+
+ // LEHP-TODO add constraints to model
+ type_param->element_count = 0;
+ type_param->elements = NULL;
+
+ // TODO: In the future, process constraint list from param_ast->child[1]
+ } else {
+ zend_error_noreturn(E_COMPILE_ERROR, "Invalid generic type parameter");
+ }
+ }
+}
+
static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
{
zend_ast_decl *decl = (zend_ast_decl *) ast;
@@ -9134,7 +9181,12 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
CG(active_class_entry) = ce;
if (decl->child[3]) {
- zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0);
+ if(decl->child[3]->kind == ZEND_AST_GENERIC_TYPE_PARAM_LIST){
+ zend_compile_class_generic_params(ce, decl->child[3]);
+ } else {
+ // LEHP-TODO this should never happen, attributes are never passed as third child to zend_ast_create_decl(ZEND_AST_CLASS, ...)???
+ zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0);
+ }
}
if (implements_ast) {
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 224a68be749cb..ddd71a56751b5 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -483,9 +483,11 @@ typedef struct _zend_internal_arg_info {
} zend_internal_arg_info;
/* arg_info for user functions */
+//LEHP-RELEVANT
typedef struct _zend_arg_info {
zend_string *name;
zend_type type;
+ //LEHP-ASSUMPTION: LEPHP code evaluated at runtime
zend_string *default_value;
} zend_arg_info;
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index 9483a83b4e955..bbdd70f186041 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -237,6 +237,11 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%token T_COALESCE "'??'"
%token T_POW "'**'"
%token T_POW_EQUAL "'**='"
+
+// Generics
+%token T_GENERIC_START "'<'"
+%token T_GENERIC_END "'>'"
+
/* We need to split the & token in two to avoid a shift/reduce conflict. For T1&$v and T1&T2,
* with only one token lookahead, bison does not know whether to reduce T1 as a complete type,
* or shift to continue parsing an intersection type. */
@@ -286,6 +291,16 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type function_name non_empty_member_modifiers
%type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body
%type optional_parameter_list
+%type nested_generic_type_parameter_list
+%type nested_generic_type_parameters
+%type generic_type
+%type non_empty_generic_type_parameters
+%type generic_type_with_constraint
+%type generic_type_parameter_list_with_constraints
+%type class_or_fuction_decl_generic_type_parameters
+%type simple_generic_type
+%type simple_generic_type_parameter_list
+%type non_empty_simple_generic_type_parameters
%type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers
%type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers
@@ -597,11 +612,65 @@ is_variadic:
class_declaration_statement:
class_modifiers T_CLASS { $$ = CG(zend_lineno); }
- T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $3, $7, zend_ast_get_str($4), $5, $6, $9, NULL, NULL); }
+ T_STRING class_or_fuction_decl_generic_type_parameters extends_from implements_list backup_doc_comment '{' class_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $3, $8, zend_ast_get_str($4), $6, $7, $10, $5, NULL); }
| T_CLASS { $$ = CG(zend_lineno); }
- T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $2, $6, zend_ast_get_str($3), $4, $5, $8, NULL, NULL); }
+ T_STRING class_or_fuction_decl_generic_type_parameters extends_from implements_list backup_doc_comment '{' class_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $2, $7, zend_ast_get_str($3), $5, $6, $9, $4, NULL); }
+;
+
+// only support e.g. ""
+non_empty_simple_generic_type_parameters:
+ T_GENERIC_START simple_generic_type_parameter_list T_GENERIC_END { $$ = $2; }
+;
+
+// only support e.g. "SomeType"
+simple_generic_type:
+ T_STRING { $$ = zend_ast_create_zval_from_str(zend_ast_get_str($1)); }
+ | T_STRING non_empty_simple_generic_type_parameters { $$ = zend_ast_create(ZEND_AST_GENERIC_TYPE, zend_ast_create_zval_from_str(zend_ast_get_str($1)), $2); }
+;
+
+// only support e.g. "T, S, A"
+simple_generic_type_parameter_list:
+ T_STRING { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_TYPE_PARAM_LIST, $1); }
+ | generic_type_parameter_list_with_constraints ',' T_STRING { $$ = zend_ast_list_add($1, $3); }
+;
+
+// supports bounded types, e.g. ""
+class_or_fuction_decl_generic_type_parameters:
+ T_GENERIC_START generic_type_parameter_list_with_constraints T_GENERIC_END { $$ = $2; }
+ | %empty { $$ = NULL; }
+;
+
+// supports bounded types, e.g. "T : SomeInterface, S : SomeClass"
+generic_type_parameter_list_with_constraints:
+ generic_type_with_constraint { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_TYPE_PARAM_LIST, $1); }
+ | generic_type_parameter_list_with_constraints ',' generic_type_with_constraint { $$ = zend_ast_list_add($1, $3); }
+;
+
+// supports bounded types, e.g. "T : SomeInterface"
+generic_type_with_constraint:
+ T_STRING { $$ = zend_ast_create_zval_from_str(zend_ast_get_str($1)); }
+ | T_STRING ':' simple_generic_type { $$ = zend_ast_create(ZEND_AST_GENERIC_TYPE, zend_ast_create_zval_from_str(zend_ast_get_str($1)), $3); }
+;
+
+nested_generic_type_parameters:
+ T_GENERIC_START nested_generic_type_parameter_list T_GENERIC_END { $$ = $2; }
+ | %empty { $$ = NULL; }
+;
+
+nested_generic_type_parameter_list:
+ generic_type { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_TYPE_PARAM_LIST, $1); }
+ | nested_generic_type_parameter_list ',' generic_type { $$ = zend_ast_list_add($1, $3); }
+;
+
+non_empty_generic_type_parameters:
+ T_GENERIC_START nested_generic_type_parameter_list T_GENERIC_END { $$ = $2; }
+;
+
+generic_type:
+ T_STRING { $$ = zend_ast_create_zval_from_str(zend_ast_get_str($1)); }
+ | T_STRING non_empty_generic_type_parameters { $$ = zend_ast_create(ZEND_AST_GENERIC_TYPE, zend_ast_create_zval_from_str(zend_ast_get_str($1)), $2); }
;
class_modifiers:
@@ -865,10 +934,11 @@ type_expr_without_static:
| intersection_type_without_static { $$ = $1; }
;
+//TODO add generic type params to AST
type_without_static:
- T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
+ T_ARRAY nested_generic_type_parameters { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
| T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); }
- | name { $$ = $1; }
+ | name nested_generic_type_parameters { $$ = $1; }
;
union_type_without_static_element:
diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l
index 4c883b81c5f7d..aa3381a8a580e 100644
--- a/Zend/zend_language_scanner.l
+++ b/Zend/zend_language_scanner.l
@@ -1395,6 +1395,7 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
}
"function" {
+ yy_push_state(ST_IN_FUNC_DEF);
RETURN_TOKEN_WITH_IDENT(T_FUNCTION);
}
@@ -1537,10 +1538,35 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
RETURN_TOKEN_WITH_IDENT(T_PRINT);
}
+ "<" {
+ yy_push_state(ST_IN_GENERIC);
+ RETURN_TOKEN(T_GENERIC_START);
+}
+
+ ">" {
+ yy_pop_state();
+ RETURN_TOKEN(T_GENERIC_END);
+}
+
+{LABEL} {
+ RETURN_TOKEN_WITH_STR(T_STRING, 0);
+}
+
+
"class" {
+ yy_push_state(ST_IN_CLASS_DEF);
RETURN_TOKEN_WITH_IDENT(T_CLASS);
}
+
+
+"{" {
+ yy_pop_state();
+ yy_push_state(ST_IN_SCRIPTING);
+ enter_nesting('{');
+ RETURN_TOKEN('{');
+}
+
"interface" {
RETURN_TOKEN_WITH_IDENT(T_INTERFACE);
}
@@ -1562,11 +1588,11 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
RETURN_TOKEN_WITH_IDENT(T_ENUM);
}
-"extends" {
+"extends" {
RETURN_TOKEN_WITH_IDENT(T_EXTENDS);
}
-"implements" {
+"implements" {
RETURN_TOKEN_WITH_IDENT(T_IMPLEMENTS);
}
@@ -1580,7 +1606,7 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
RETURN_TOKEN(T_NULLSAFE_OBJECT_OPERATOR);
}
-{WHITESPACE}+ {
+{WHITESPACE}+ {
goto return_whitespace;
}
@@ -1722,9 +1748,14 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
}
"private" {
+ yy_push_state(ST_IN_PROPERTY_TYPE_DEF);
RETURN_TOKEN_WITH_IDENT(T_PRIVATE);
}
+ {
+
+}
+
"protected" {
RETURN_TOKEN_WITH_IDENT(T_PROTECTED);
}
@@ -1908,6 +1939,16 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
RETURN_TOKEN(yytext[0]);
}
+"(" {
+ yy_pop_state();
+ enter_nesting(yytext[0]);
+ RETURN_TOKEN(yytext[0]);
+}
+
+":"|"," {
+ RETURN_TOKEN(yytext[0]);
+}
+
{TOKENS} {
RETURN_TOKEN(yytext[0]);
}
@@ -2394,7 +2435,7 @@ inline_char_handler:
RETURN_TOKEN(T_NS_SEPARATOR);
}
-{LABEL} {
+{LABEL} {
RETURN_TOKEN_WITH_STR(T_STRING, 0);
}
diff --git a/Zend/zend_types.h b/Zend/zend_types.h
index f839cec3b3667..72b814d9f5d88 100644
--- a/Zend/zend_types.h
+++ b/Zend/zend_types.h
@@ -142,6 +142,7 @@ typedef struct {
zend_type types[1];
} zend_type_list;
+//LEHP-RELEVANT new generic type
#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25
#define _ZEND_TYPE_MASK ((1u << 25) - 1)
/* Only one of these bits may be set. */
@@ -553,11 +554,20 @@ typedef struct _HashTableIterator {
uint32_t next_copy; // circular linked list via index into EG(ht_iterators)
} HashTableIterator;
+typedef struct _zend_generic_value {
+ struct _zend_generic_value *elements;
+ size_t element_count;
+ zend_string *name;
+} zend_generic_value;
+
struct _zend_object {
zend_refcounted_h gc;
uint32_t handle; // TODO: may be removed ???
uint32_t extra_flags; /* OBJ_EXTRA_FLAGS() */
zend_class_entry *ce;
+ //LEHP-OPTIMIZATION: COPY THIS FROM _zend_class_entry
+ size_t generic_value_count;
+ zend_generic_value *generic_values;
const zend_object_handlers *handlers;
HashTable *properties;
zval properties_table[1];
diff --git a/dev.sh b/dev.sh
new file mode 100755
index 0000000000000..5d4d2bdd4577f
--- /dev/null
+++ b/dev.sh
@@ -0,0 +1,2 @@
+docker build -t php-dev-setup .
+docker run --volume .:/app php-dev-setup
diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c
index a1e131032bcfb..e8a4d3006aa4f 100644
--- a/ext/tokenizer/tokenizer_data.c
+++ b/ext/tokenizer/tokenizer_data.c
@@ -173,6 +173,8 @@ char *get_token_type_name(int token_type)
case T_COALESCE: return "T_COALESCE";
case T_POW: return "T_POW";
case T_POW_EQUAL: return "T_POW_EQUAL";
+ case T_GENERIC_START: return "T_GENERIC_START";
+ case T_GENERIC_END: return "T_GENERIC_END";
case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG";
case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG";
case T_BAD_CHARACTER: return "T_BAD_CHARACTER";
diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php
index c1e1fd254dfaa..fe7321e9cd40d 100644
--- a/ext/tokenizer/tokenizer_data.stub.php
+++ b/ext/tokenizer/tokenizer_data.stub.php
@@ -742,6 +742,16 @@
* @cvalue T_POW_EQUAL
*/
const T_POW_EQUAL = UNKNOWN;
+/**
+ * @var int
+ * @cvalue T_GENERIC_START
+ */
+const T_GENERIC_START = UNKNOWN;
+/**
+ * @var int
+ * @cvalue T_GENERIC_END
+ */
+const T_GENERIC_END = UNKNOWN;
/**
* @var int
* @cvalue T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h
index 9c488d19f1890..6ac43d4a612b8 100644
--- a/ext/tokenizer/tokenizer_data_arginfo.h
+++ b/ext/tokenizer/tokenizer_data_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 19d25d22098f46283b517352cbb302db962b50fd */
+ * Stub hash: 69f8ad0852d9f99a17d98192dfc54fd8961d3d0a */
static void register_tokenizer_data_symbols(int module_number)
{
@@ -151,6 +151,8 @@ static void register_tokenizer_data_symbols(int module_number)
REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("T_GENERIC_START", T_GENERIC_START, CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("T_GENERIC_END", T_GENERIC_END, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_PERSISTENT);
diff --git a/le-test/simple-class.php b/le-test/simple-class.php
new file mode 100644
index 0000000000000..73423ce0166f2
--- /dev/null
+++ b/le-test/simple-class.php
@@ -0,0 +1,16 @@
+ {
+ // T can be used as type alias inside of 'GenericTestClass'
+ public function __construct(
+ private T $property
+ ) {}
+
+ public function getMyT(): T {
+ return $this->property;
+ }
+
+ public function setMyT(T $newValue) {
+ $this->property = $newValue;
+ }
+}
\ No newline at end of file
diff --git a/le-test/simple-function.php b/le-test/simple-function.php
new file mode 100644
index 0000000000000..307fa2d5f8d03
--- /dev/null
+++ b/le-test/simple-function.php
@@ -0,0 +1,3 @@
+(): void {}
\ No newline at end of file
diff --git a/t.php b/t.php
new file mode 100644
index 0000000000000..93668b01e91a6
--- /dev/null
+++ b/t.php
@@ -0,0 +1,25 @@
+> {
+
+ private array $content = [];
+
+ public function __construct(
+ private K $serializer
+ ) {}
+
+ public function add(T $toAdd)
+ {
+ $content[] = $toAdd;
+ }
+
+ public function asSerializedList(): array
+ {
+ return array_map(
+ fn ($obj) => $this->serializer->serialize($obj),
+ $this->content
+ );
+ }
+}
+
+echo "hi\n";
\ No newline at end of file
diff --git a/t2.php b/t2.php
new file mode 100644
index 0000000000000..520a1c4557105
--- /dev/null
+++ b/t2.php
@@ -0,0 +1,9 @@
+{
+ public function __construct(
+ public K $lol
+){
+ }
+}
+
+echo "hi\n";
diff --git a/tree.php b/tree.php
new file mode 100644
index 0000000000000..934f17b7dd468
--- /dev/null
+++ b/tree.php
@@ -0,0 +1,10 @@
+ {
+ public Serializable $test;
+ function __construct() {}
+}
+');
+
+var_export($classTokens);
\ No newline at end of file