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/Zend/zend_ast.h b/Zend/zend_ast.h index 9348c35f6cc07..77ad0aa141692 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, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8e4221673c4cf..75047fc029c71 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9133,7 +9133,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) CG(active_class_entry) = ce; - if (decl->child[3]) { + if (decl->child[3] && decl->child[3]->kind != ZEND_AST_GENERIC_TYPE_PARAM_LIST) { zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 9483a83b4e955..0866c45752b3f 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,8 @@ 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 generic_type_parameter_list +%type 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 +604,21 @@ 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 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 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); } +; + +generic_type_parameters: + T_GENERIC_START generic_type_parameter_list possible_comma T_GENERIC_END { $$ = $2; } + | %empty { $$ = NULL; } +; + +generic_type_parameter_list: + T_STRING { $$ = zend_ast_create_list(1, ZEND_AST_GENERIC_TYPE_PARAM_LIST, $1); } + | generic_type_parameter_list ',' T_STRING { $$ = zend_ast_list_add($1, $3); } ; class_modifiers: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 4c883b81c5f7d..7a25ea7adf5bc 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1537,6 +1537,30 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_PRINT); } +"<"{LABEL}">" { + yyless(1); + yy_push_state(ST_GENERIC); + RETURN_TOKEN(T_GENERIC_START); +} + +{LABEL} { + RETURN_TOKEN_WITH_STR(T_STRING, 0); +} + +"," { + RETURN_TOKEN(','); +} + +">" { + yy_pop_state(); + RETURN_TOKEN(T_GENERIC_END); +} + +{WHITESPACE} { + HANDLE_NEWLINES(yytext, yyleng); + RETURN_TOKEN(T_WHITESPACE); +} + "class" { RETURN_TOKEN_WITH_IDENT(T_CLASS); } 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);