From 31167bb0ad1efe625566513d5746c15b4e78e1dd Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:32:45 +0100 Subject: [PATCH 01/48] Collection: Add Token --- Zend/zend_language_parser.y | 1 + Zend/zend_language_scanner.l | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 5423d40185766..a9c5b93dac0b4 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -164,6 +164,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_TRAIT "'trait'" %token T_INTERFACE "'interface'" %token T_ENUM "'enum'" +%token T_COLLECTION "'collection'" %token T_EXTENDS "'extends'" %token T_IMPLEMENTS "'implements'" %token T_NAMESPACE "'namespace'" diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 4551d26a17e79..8705e711a4609 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1562,6 +1562,19 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_ENUM); } +/* + * The collection keyword must be followed by whitespace and another identifier. + * This avoids the BC break of using collection in classes, namespaces, functions and constants. + */ +"collection"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { + yyless(10); + RETURN_TOKEN_WITH_STR(T_STRING, 0); +} +"collection"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { + yyless(10); + RETURN_TOKEN_WITH_IDENT(T_COLLECTION); +} + "extends" { RETURN_TOKEN_WITH_IDENT(T_EXTENDS); } From 93c8497b5b1116a1a60aff5347e9bc16db728fc6 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:33:48 +0100 Subject: [PATCH 02/48] Collection: Add Parser Rules --- Zend/zend_compile.h | 3 ++- Zend/zend_language_parser.y | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index c7e31877b5cd2..05622569075d3 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -262,7 +262,7 @@ typedef struct _zend_oparray_context { /* Virtual property without backing storage | | | */ #define ZEND_ACC_VIRTUAL (1 << 9) /* | | X | */ /* | | | */ -/* Class Flags (unused: 30,31) | | | */ +/* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -270,6 +270,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_TRAIT (1 << 1) /* X | | | */ #define ZEND_ACC_ANON_CLASS (1 << 2) /* X | | | */ #define ZEND_ACC_ENUM (1 << 28) /* X | | | */ +#define ZEND_ACC_COLLECTION (1 << 30) /* X | | | */ /* | | | */ /* Class linked with parent, interfaces and traits | | | */ #define ZEND_ACC_LINKED (1 << 3) /* X | | | */ diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index a9c5b93dac0b4..f9308b18e7044 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -280,6 +280,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type attribute_decl attribute attributes attribute_group namespace_declaration_name %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list %type enum_declaration_statement enum_backing_type enum_case enum_case_expr +%type collection_declaration_statement collection_type %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 @@ -388,6 +389,7 @@ attributed_statement: | trait_declaration_statement { $$ = $1; } | interface_declaration_statement { $$ = $1; } | enum_declaration_statement { $$ = $1; } + | collection_declaration_statement { $$ = $1; } ; top_statement: @@ -657,6 +659,15 @@ enum_case_expr: | '=' expr { $$ = $2; } ; +collection_declaration_statement: + T_COLLECTION { $$ = CG(zend_lineno); } + T_STRING '(' collection_type T_DOUBLE_ARROW collection_type ')' backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $9, zend_ast_get_str($3), NULL, NULL, $11, $5, $7); } +; + +collection_type: + type_expr { $$ = $1; } + extends_from: %empty { $$ = NULL; } | T_EXTENDS class_name { $$ = $2; } From 1babb8c249c147dff2abec1524cdde51a1b6f011 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:35:32 +0100 Subject: [PATCH 03/48] Collection: Add AST Code --- Zend/zend.h | 5 ++++- Zend/zend_compile.c | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Zend/zend.h b/Zend/zend.h index 4fe0703d42f69..fbd2e5b3e5ca5 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -218,10 +218,13 @@ struct _zend_class_entry { zend_trait_precedence **trait_precedences; HashTable *attributes; + zend_string *doc_comment; + uint32_t enum_backing_type; HashTable *backed_enum_table; - zend_string *doc_comment; + uint32_t collection_key_type; + zend_type collection_item_type; union { struct { diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9e736ea1b37ad..2a8496d52730d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8946,12 +8946,48 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ zend_type_release(type, 0); } +static void zend_compile_collection_key_type(zend_class_entry *ce, zend_ast *collection_key_type_ast) +{ + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + zend_type type = zend_compile_typename(collection_key_type_ast, 0); + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + if (ZEND_TYPE_IS_COMPLEX(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) { + zend_string *type_string = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Collection key type must be int or string, %s given", + ZSTR_VAL(type_string)); + } + if (type_mask == MAY_BE_LONG) { + ce->collection_key_type = IS_LONG; + } else { + ZEND_ASSERT(type_mask == MAY_BE_STRING); + ce->collection_key_type = IS_STRING; + } + zend_type_release(type, 0); +} + +static void zend_compile_collection_item_type(zend_class_entry *ce, zend_ast *collection_item_type_ast) +{ + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + zend_type type = zend_compile_typename(collection_item_type_ast, 0); + + if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_NEVER|MAY_BE_CALLABLE)) { + zend_string *str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Collection item type cannot have type %s", ZSTR_VAL(str)); + } + + ce->collection_item_type = type; +} + static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *extends_ast = decl->child[0]; zend_ast *implements_ast = decl->child[1]; zend_ast *stmt_ast = decl->child[2]; + zend_ast *collection_key_type_ast = decl->child[3]; + zend_ast *collection_item_type_ast = decl->child[4]; zend_ast *enum_backing_type_ast = decl->child[4]; zend_string *name, *lcname; zend_class_entry *ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry)); @@ -9028,7 +9064,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] && !(ce->ce_flags & ZEND_ACC_COLLECTION)) { zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } @@ -9044,6 +9080,11 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_enum_register_props(ce); } + if (ce->ce_flags & ZEND_ACC_COLLECTION) { + zend_compile_collection_key_type(ce, collection_key_type_ast); + zend_compile_collection_item_type(ce, collection_item_type_ast); + } + zend_compile_stmt(stmt_ast); /* Reset lineno for final opcodes and errors */ From 1e6faf28d55bad3a10401737257af6a344700a73 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:35:50 +0100 Subject: [PATCH 04/48] Collection: Clean Up Class Entry --- Zend/zend_opcode.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0d1d8b6bf528f..85fc5b57ac986 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -439,6 +439,9 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->backed_enum_table) { zend_hash_release(ce->backed_enum_table); } + if (ZEND_TYPE_IS_SET(ce->collection_item_type)) { + zend_type_release(ce->collection_item_type, 0); + } if (ce->default_properties_table) { zval *p = ce->default_properties_table; zval *end = p + ce->default_properties_count; From 4b8fce93409724c336b9628f54fe43e8d68f785d Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:40:21 +0100 Subject: [PATCH 05/48] Collection: Init and Storage --- Zend/zend_collection.c | 41 +++++++++++++++++++++++++++++++++++++++++ Zend/zend_collection.h | 35 +++++++++++++++++++++++++++++++++++ Zend/zend_compile.c | 3 +++ configure.ac | 1 + win32/build/config.w32 | 2 +- 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 Zend/zend_collection.c create mode 100644 Zend/zend_collection.h diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c new file mode 100644 index 0000000000000..eaed0c648f9ed --- /dev/null +++ b/Zend/zend_collection.c @@ -0,0 +1,41 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Derick Rethans | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_API.h" + +ZEND_API zend_object_handlers zend_collection_object_handlers; + +void zend_collection_register_handlers(zend_class_entry *ce) +{ + memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_collection_object_handlers.clone_obj = NULL; + zend_collection_object_handlers.compare = zend_objects_not_comparable; + + ce->default_object_handlers = &zend_collection_object_handlers; +} + +void zend_collection_register_props(zend_class_entry *ce) +{ + ce->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + + zval name_default_value; + ZVAL_UNDEF(&name_default_value); + zend_type name_type = ZEND_TYPE_INIT_CODE(IS_ARRAY, 0, 0); + zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &name_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY, NULL, name_type); +} diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h new file mode 100644 index 0000000000000..9da120285903a --- /dev/null +++ b/Zend/zend_collection.h @@ -0,0 +1,35 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Derick Rethans | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_COLLECTION_H +#define ZEND_COLLECTION_H + +#include "zend.h" + +#include + +BEGIN_EXTERN_C() + +extern ZEND_API zend_object_handlers zend_collection_object_handlers; + +void zend_collection_register_handlers(zend_class_entry *ce); +void zend_collection_register_props(zend_class_entry *ce); + +END_EXTERN_C() + +#endif /* ZEND_COLLECTION_H */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2a8496d52730d..5ecfc0ab4e960 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -34,6 +34,7 @@ #include "zend_inheritance.h" #include "zend_vm.h" #include "zend_enum.h" +#include "zend_collection.h" #include "zend_observer.h" #include "zend_call_stack.h" #include "zend_frameless_function.h" @@ -9083,6 +9084,8 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) if (ce->ce_flags & ZEND_ACC_COLLECTION) { zend_compile_collection_key_type(ce, collection_key_type_ast); zend_compile_collection_item_type(ce, collection_item_type_ast); + zend_collection_register_handlers(ce); + zend_collection_register_props(ce); } zend_compile_stmt(stmt_ast); diff --git a/configure.ac b/configure.ac index 0abaf4a4f185b..701a31b2ab96a 100644 --- a/configure.ac +++ b/configure.ac @@ -1714,6 +1714,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_builtin_functions.c zend_call_stack.c zend_closures.c + zend_collection.c zend_compile.c zend_constants.c zend_cpuinfo.c diff --git a/win32/build/config.w32 b/win32/build/config.w32 index c4a617a2e16ec..4440bf5299363 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -240,7 +240,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ - zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c"); + zend_collection.c zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c"); var PHP_ASSEMBLER = PATH_PROG({ From 982c297244ee55001af8d8925694ed971fa2b2e9 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:40:52 +0100 Subject: [PATCH 06/48] Collection: Add test file --- Zend/tests/collection/collection_1.phpt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Zend/tests/collection/collection_1.phpt diff --git a/Zend/tests/collection/collection_1.phpt b/Zend/tests/collection/collection_1.phpt new file mode 100644 index 0000000000000..1d49928239c6d --- /dev/null +++ b/Zend/tests/collection/collection_1.phpt @@ -0,0 +1,11 @@ +--TEST-- +Collection: Syntax +--FILE-- + Article) +{ +} + +$c = new Articles; + +var_dump($c); From 1c4bb90001983770a3223673658325b11388544a Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:42:43 +0100 Subject: [PATCH 07/48] Collection: Add Interface --- Zend/zend_collection.c | 42 ++++++++++++++++++++++++++++++++++ Zend/zend_collection.h | 4 ++++ Zend/zend_collection.stub.php | 7 ++++++ Zend/zend_collection_arginfo.h | 19 +++++++++++++++ Zend/zend_compile.c | 1 + Zend/zend_default_classes.c | 1 + 6 files changed, 74 insertions(+) create mode 100644 Zend/zend_collection.stub.php create mode 100644 Zend/zend_collection_arginfo.h diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index eaed0c648f9ed..bb4bcadb43f3f 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -18,9 +18,51 @@ #include "zend.h" #include "zend_API.h" +#include "zend_collection_arginfo.h" +#include "zend_execute.h" +ZEND_API zend_class_entry *zend_ce_collection; ZEND_API zend_object_handlers zend_collection_object_handlers; +static int zend_implement_collection(zend_class_entry *interface, zend_class_entry *class_type) +{ + if (class_type->ce_flags & ZEND_ACC_COLLECTION) { + return SUCCESS; + } + + zend_error_noreturn(E_ERROR, "Non-collection class %s cannot implement interface %s", + ZSTR_VAL(class_type->name), + ZSTR_VAL(interface->name)); + + return FAILURE; +} + +void zend_register_collection_ce(void) +{ + zend_ce_collection = register_class_Collection(); + zend_ce_collection->interface_gets_implemented = zend_implement_collection; + + memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_collection_object_handlers.clone_obj = NULL; + zend_collection_object_handlers.compare = zend_objects_not_comparable; +} + +void zend_collection_add_interfaces(zend_class_entry *ce) +{ + uint32_t num_interfaces_before = ce->num_interfaces; + + ce->num_interfaces++; + + ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)); + + ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); + + ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_collection->name); + ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("collection", 0); + + ce->default_object_handlers = &zend_collection_object_handlers; +} + void zend_collection_register_handlers(zend_class_entry *ce) { memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h index 9da120285903a..d0b1fa1087d0d 100644 --- a/Zend/zend_collection.h +++ b/Zend/zend_collection.h @@ -26,6 +26,10 @@ BEGIN_EXTERN_C() extern ZEND_API zend_object_handlers zend_collection_object_handlers; +extern ZEND_API zend_class_entry *zend_ce_collection; + +void zend_register_collection_ce(void); +void zend_collection_add_interfaces(zend_class_entry *ce); void zend_collection_register_handlers(zend_class_entry *ce); void zend_collection_register_props(zend_class_entry *ce); diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php new file mode 100644 index 0000000000000..6468336c25f28 --- /dev/null +++ b/Zend/zend_collection.stub.php @@ -0,0 +1,7 @@ +ce_flags & ZEND_ACC_COLLECTION) { zend_compile_collection_key_type(ce, collection_key_type_ast); zend_compile_collection_item_type(ce, collection_item_type_ast); + zend_collection_add_interfaces(ce); zend_collection_register_handlers(ce); zend_collection_register_props(ce); } diff --git a/Zend/zend_default_classes.c b/Zend/zend_default_classes.c index 7ab9b8325a39a..191b21cb604e9 100644 --- a/Zend/zend_default_classes.c +++ b/Zend/zend_default_classes.c @@ -40,4 +40,5 @@ ZEND_API void zend_register_default_classes(void) zend_register_attribute_ce(); zend_register_enum_ce(); zend_register_fiber_ce(); + zend_register_collection_ce(); } From 84bf65306ffb25352fd462ddbaa1fafd8ac821a2 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:44:58 +0100 Subject: [PATCH 08/48] Collection: Add Write Handler --- Zend/zend_collection.c | 64 +++++++++++++++++++++++++++++++++++++ Zend/zend_collection.h | 2 ++ Zend/zend_object_handlers.c | 3 ++ 3 files changed, 69 insertions(+) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index bb4bcadb43f3f..28f8e21d2ac6d 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -81,3 +81,67 @@ void zend_collection_register_props(zend_class_entry *ce) zend_type name_type = ZEND_TYPE_INIT_CODE(IS_ARRAY, 0, 0); zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &name_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY, NULL, name_type); } + +static void create_array_if_needed(zend_class_entry *ce, zend_object *object) +{ + zval *value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + if (Z_TYPE_P(value_prop) == IS_ARRAY) { + return; + } + + zval new_array; + + array_init(&new_array); + + zend_update_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), &new_array); + + zval_ptr_dtor(&new_array); +} + +void zend_collection_add_item(zend_object *object, zval *offset, zval *value) +{ + zend_class_entry *ce = object->ce; + zval rv; + zval *value_prop; + + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + + if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { + zend_string *type_str = zend_type_to_string(ce->collection_item_type); + zend_type_error( + "Value type %s does not match collection item type %s", + zend_zval_type_name(value), + ZSTR_VAL(type_str) + ); + zend_string_release(type_str); + return; + } + + if (!offset && ce->collection_key_type == IS_LONG) { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + Z_ADDREF_P(value); + add_next_index_zval(value_prop, value); + return; + } else if (offset && ce->collection_key_type == IS_LONG && Z_TYPE_P(offset) == IS_LONG) { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + Z_ADDREF_P(value); + add_index_zval(value_prop, Z_LVAL_P(offset), value); + return; + } else if (offset && ce->collection_key_type == IS_STRING && Z_TYPE_P(offset) == IS_STRING) { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + Z_ADDREF_P(value); + add_assoc_zval_ex(value_prop, Z_STRVAL_P(offset), Z_STRLEN_P(offset), value); + return; + } else { + zend_type_error( + "Key type %s of element does not match collection key type %s", + offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), + zend_get_type_by_const(ce->collection_key_type) + ); + } + +} diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h index d0b1fa1087d0d..f3ad691c14235 100644 --- a/Zend/zend_collection.h +++ b/Zend/zend_collection.h @@ -34,6 +34,8 @@ void zend_collection_add_interfaces(zend_class_entry *ce); void zend_collection_register_handlers(zend_class_entry *ce); void zend_collection_register_props(zend_class_entry *ce); +void zend_collection_add_item(zend_object *object, zval *offset, zval *value); + END_EXTERN_C() #endif /* ZEND_COLLECTION_H */ diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 4c096d26b1b7b..b9e3020bc209e 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -26,6 +26,7 @@ #include "zend_objects_API.h" #include "zend_object_handlers.h" #include "zend_interfaces.h" +#include "zend_collection.h" #include "zend_exceptions.h" #include "zend_closures.h" #include "zend_compile.h" @@ -1205,6 +1206,8 @@ ZEND_API void zend_std_write_dimension(zend_object *object, zval *offset, zval * zend_call_known_instance_method_with_2_params(funcs->zf_offsetset, object, NULL, &tmp_offset, value); OBJ_RELEASE(object); zval_ptr_dtor(&tmp_offset); + } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + zend_collection_add_item(object, offset, value); } else { zend_bad_array_access(ce); } From 56c42232b9a3394184351057de12be55b116d02b Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:45:58 +0100 Subject: [PATCH 09/48] Collection: Make Check Type Public --- Zend/zend_execute.c | 2 +- Zend/zend_execute.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 602bb3b0e79f5..e7a95a6802884 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1190,7 +1190,7 @@ static zend_always_inline bool zend_check_type_slow( * because this case is already checked at compile-time. */ } -static zend_always_inline bool zend_check_type( +zend_always_inline bool zend_check_type( zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal) { diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index fe854f305150c..8a0a40a768338 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -93,6 +93,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_pr ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void); ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg); +ZEND_API bool zend_check_type(zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal); ZEND_API ZEND_COLD void zend_verify_arg_error( const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value); ZEND_API ZEND_COLD void zend_verify_return_error( From f845b94eaa1a74e959485b73775a891c39ed1e43 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:46:56 +0100 Subject: [PATCH 10/48] Collection: Add Read Handler --- Zend/zend_collection.c | 37 +++++++++++++++++++++++++++++++++++++ Zend/zend_collection.h | 1 + Zend/zend_object_handlers.c | 2 ++ 3 files changed, 40 insertions(+) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 28f8e21d2ac6d..d108c552cd72a 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -145,3 +145,40 @@ void zend_collection_add_item(zend_object *object, zval *offset, zval *value) } } + +static int key_type_allowed(zend_class_entry *ce, zval *offset) +{ + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + + if (ce->collection_key_type != Z_TYPE_P(offset)) { + zend_type_error( + "Key type %s of element does not match collection key type %s", + offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), + zend_get_type_by_const(ce->collection_key_type) + ); + return false; + } + + return true; +} + +zval *zend_collection_read_item(zend_object *object, zval *offset) +{ + zval rv; + zval *value_prop, *value; + zend_class_entry *ce = object->ce; + + if (!key_type_allowed(ce, offset)) { + return NULL; + } + + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + + if (Z_TYPE_P(offset) == IS_STRING) { + value = zend_hash_find(HASH_OF(value_prop), Z_STR_P(offset)); + } else { + value = zend_hash_index_find(HASH_OF(value_prop), Z_LVAL_P(offset)); + } + + return value; +} diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h index f3ad691c14235..2328a9294284c 100644 --- a/Zend/zend_collection.h +++ b/Zend/zend_collection.h @@ -35,6 +35,7 @@ void zend_collection_register_handlers(zend_class_entry *ce); void zend_collection_register_props(zend_class_entry *ce); void zend_collection_add_item(zend_object *object, zval *offset, zval *value); +zval *zend_collection_read_item(zend_object *object, zval *offset); END_EXTERN_C() diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index b9e3020bc209e..4ef3f3ff0c0fb 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1183,6 +1183,8 @@ ZEND_API zval *zend_std_read_dimension(zend_object *object, zval *offset, int ty return NULL; } return rv; + } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + return zend_collection_read_item(object, offset); } else { zend_bad_array_access(ce); return NULL; From 91e1ddc80cfe8fb934834b6525a38decdd749776 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:47:21 +0100 Subject: [PATCH 11/48] Collection: Add Has Handler --- Zend/zend_collection.c | 21 +++++++++++++++++++++ Zend/zend_collection.h | 1 + Zend/zend_object_handlers.c | 2 ++ 3 files changed, 24 insertions(+) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index d108c552cd72a..37d3b4d615a8f 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -162,6 +162,27 @@ static int key_type_allowed(zend_class_entry *ce, zval *offset) return true; } +int zend_collection_has_item(zend_object *object, zval *offset) +{ + zval rv; + zval *value_prop; + zend_class_entry *ce = object->ce; + + if (!key_type_allowed(ce, offset)) { + return false; + } + + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + + if (Z_TYPE_P(offset) == IS_STRING) { + return zend_hash_find(HASH_OF(value_prop), Z_STR_P(offset)) != NULL; + } else { + return zend_hash_index_find(HASH_OF(value_prop), Z_LVAL_P(offset)) != NULL; + } + + return false; +} + zval *zend_collection_read_item(zend_object *object, zval *offset) { zval rv; diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h index 2328a9294284c..9abfcfa8e5dcf 100644 --- a/Zend/zend_collection.h +++ b/Zend/zend_collection.h @@ -35,6 +35,7 @@ void zend_collection_register_handlers(zend_class_entry *ce); void zend_collection_register_props(zend_class_entry *ce); void zend_collection_add_item(zend_object *object, zval *offset, zval *value); +int zend_collection_has_item(zend_object *object, zval *offset); zval *zend_collection_read_item(zend_object *object, zval *offset); END_EXTERN_C() diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 4ef3f3ff0c0fb..ee6f96e898183 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1237,6 +1237,8 @@ ZEND_API int zend_std_has_dimension(zend_object *object, zval *offset, int check } OBJ_RELEASE(object); zval_ptr_dtor(&tmp_offset); + } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + return zend_collection_has_item(object, offset); } else { zend_bad_array_access(ce); return 0; From 6d695b14e6ec554bd913e2b42b9f74096c0a9a38 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 12 May 2023 14:47:50 +0100 Subject: [PATCH 12/48] Collection: Add Unset Handler --- Zend/zend_collection.c | 19 +++++++++++++++++++ Zend/zend_collection.h | 1 + Zend/zend_object_handlers.c | 2 ++ 3 files changed, 22 insertions(+) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 37d3b4d615a8f..8c5c6f3926904 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -203,3 +203,22 @@ zval *zend_collection_read_item(zend_object *object, zval *offset) return value; } + +void zend_collection_unset_item(zend_object *object, zval *offset) +{ + zval rv; + zval *value_prop; + zend_class_entry *ce = object->ce; + + if (!key_type_allowed(ce, offset)) { + return; + } + + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + + if (Z_TYPE_P(offset) == IS_STRING) { + zend_hash_del(HASH_OF(value_prop), Z_STR_P(offset)); + } else { + zend_hash_index_del(HASH_OF(value_prop), Z_LVAL_P(offset)); + } +} diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h index 9abfcfa8e5dcf..962115b8aae17 100644 --- a/Zend/zend_collection.h +++ b/Zend/zend_collection.h @@ -37,6 +37,7 @@ void zend_collection_register_props(zend_class_entry *ce); void zend_collection_add_item(zend_object *object, zval *offset, zval *value); int zend_collection_has_item(zend_object *object, zval *offset); zval *zend_collection_read_item(zend_object *object, zval *offset); +void zend_collection_unset_item(zend_object *object, zval *offset); END_EXTERN_C() diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index ee6f96e898183..c306f8d2a1dab 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1425,6 +1425,8 @@ ZEND_API void zend_std_unset_dimension(zend_object *object, zval *offset) /* {{{ zend_call_known_instance_method_with_1_params(funcs->zf_offsetunset, object, NULL, &tmp_offset); OBJ_RELEASE(object); zval_ptr_dtor(&tmp_offset); + } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + zend_collection_unset_item(object, offset); } else { zend_bad_array_access(ce); } From 19c22cea84898fa3d794f02e81f9a7bbe9bb4120 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Thu, 4 May 2023 14:29:12 +0100 Subject: [PATCH 13/48] Collection: Add more tests --- Zend/tests/collection/collection_1.phpt | 6 +++ Zend/tests/collection/collection_2.phpt | 61 +++++++++++++++++++++++++ Zend/tests/collection/collection_3.phpt | 61 +++++++++++++++++++++++++ Zend/tests/collection/collection_4.phpt | 28 ++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 Zend/tests/collection/collection_2.phpt create mode 100644 Zend/tests/collection/collection_3.phpt create mode 100644 Zend/tests/collection/collection_4.phpt diff --git a/Zend/tests/collection/collection_1.phpt b/Zend/tests/collection/collection_1.phpt index 1d49928239c6d..fb7bd4f584193 100644 --- a/Zend/tests/collection/collection_1.phpt +++ b/Zend/tests/collection/collection_1.phpt @@ -9,3 +9,9 @@ collection Articles(int => Article) $c = new Articles; var_dump($c); +?> +--EXPECTF-- +object(Articles)#%d (%d) { + ["value"]=> + uninitialized(array) +} diff --git a/Zend/tests/collection/collection_2.phpt b/Zend/tests/collection/collection_2.phpt new file mode 100644 index 0000000000000..5c589d7228529 --- /dev/null +++ b/Zend/tests/collection/collection_2.phpt @@ -0,0 +1,61 @@ +--TEST-- +Collection: Array access with integer key +--FILE-- + Article) +{ +} + +$c = new Articles; +$c[9] = new Article("First Test"); +$c[] = new Article("Second Test"); + +var_dump($c); + +var_dump(isset($c[8])); +var_dump(isset($c[9])); +var_dump(isset($c[10])); + +var_dump($c[10]); + +unset($c[9]); +var_dump(isset($c[9])); + +try { + var_dump(isset($c["eleven"])); +} catch (Error $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +object(Articles)#%d (%d) { + ["value"]=> + array(2) { + [9]=> + object(Article)#%d (%d) { + ["title"]=> + string(10) "First Test" + } + [10]=> + object(Article)#%d (%d) { + ["title"]=> + string(11) "Second Test" + } + } +} +bool(false) +bool(true) +bool(true) +object(Article)#%d (%d) { + ["title"]=> + string(11) "Second Test" +} +bool(false) +TypeError: Key type string of element does not match collection key type int diff --git a/Zend/tests/collection/collection_3.phpt b/Zend/tests/collection/collection_3.phpt new file mode 100644 index 0000000000000..a1e8cfb852ec4 --- /dev/null +++ b/Zend/tests/collection/collection_3.phpt @@ -0,0 +1,61 @@ +--TEST-- +Collection: Array access with string key +--FILE-- + Article) +{ +} + +$c = new Articles; +$c["nine"] = new Article("First Test"); +$c["ten"] = new Article("Second Test"); + +var_dump($c); + +var_dump(isset($c["eight"])); +var_dump(isset($c["nine"])); +var_dump(isset($c["ten"])); + +var_dump($c["nine"]); + +unset($c["nine"]); +var_dump(isset($c["nine"])); + +try { + var_dump(isset($c[11])); +} catch (Error $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +object(Articles)#%d (%d) { + ["value"]=> + array(2) { + ["nine"]=> + object(Article)#%d (%d) { + ["title"]=> + string(10) "First Test" + } + ["ten"]=> + object(Article)#%d (%d) { + ["title"]=> + string(11) "Second Test" + } + } +} +bool(false) +bool(true) +bool(true) +object(Article)#%d (%d) { + ["title"]=> + string(10) "First Test" +} +bool(false) +TypeError: Key type int of element does not match collection key type string diff --git a/Zend/tests/collection/collection_4.phpt b/Zend/tests/collection/collection_4.phpt new file mode 100644 index 0000000000000..385d2b9491548 --- /dev/null +++ b/Zend/tests/collection/collection_4.phpt @@ -0,0 +1,28 @@ +--TEST-- +Collection: Syntax +--FILE-- + Article) +{ + public function shuffle() + { + return array_shuffle($this->value); + } +} + +class Article +{ + public function __construct(private string $title) {} +} + + +$c = new Articles; +$c[] = new Article("First Test"); + +var_dump($c->shuffle()); +?> +--EXPECTF-- +object(Articles)#%d (%d) { + ["value"]=> + uninitialized(array) +} From 97b0f823bb036f03de8ee7a0e252e9bb1e16a8c9 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 5 May 2023 17:31:23 +0100 Subject: [PATCH 14/48] Add 'collection' name for AST output --- Zend/zend_ast.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 5ecda76870452..2294f66064945 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1878,6 +1878,8 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appends(str, "trait "); } else if (decl->flags & ZEND_ACC_ENUM) { smart_str_appends(str, "enum "); + } else if (decl->flags & ZEND_ACC_COLLECTION) { + smart_str_appends(str, "collection "); } else { if (decl->flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) { smart_str_appends(str, "abstract "); From 546a89f1b23807162f83809ddd1679dc2b3b7324 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Thu, 22 Jun 2023 16:29:02 +0100 Subject: [PATCH 15/48] Split up collection syntax into collection(Seq|Dict) --- Zend/tests/collection/collection_1.phpt | 2 +- Zend/tests/collection/collection_2.phpt | 24 ++++----- Zend/tests/collection/collection_3.phpt | 4 +- Zend/tests/collection/collection_4.phpt | 26 +++++++--- Zend/tests/collection/collection_5.phpt | 10 ++++ Zend/tests/collection/collection_6.phpt | 25 +++++++++ Zend/tests/collection/collection_7.phpt | 10 ++++ Zend/tests/collection/collection_8.phpt | 10 ++++ Zend/tests/collection/collection_9.phpt | 10 ++++ Zend/zend.h | 4 ++ Zend/zend_collection.c | 69 ++++++++++++++++++------- Zend/zend_compile.c | 51 +++++++++++++++--- Zend/zend_language_parser.y | 13 +++-- Zend/zend_language_scanner.l | 11 +--- 14 files changed, 206 insertions(+), 63 deletions(-) create mode 100644 Zend/tests/collection/collection_5.phpt create mode 100644 Zend/tests/collection/collection_6.phpt create mode 100644 Zend/tests/collection/collection_7.phpt create mode 100644 Zend/tests/collection/collection_8.phpt create mode 100644 Zend/tests/collection/collection_9.phpt diff --git a/Zend/tests/collection/collection_1.phpt b/Zend/tests/collection/collection_1.phpt index fb7bd4f584193..eb6eb922e1aec 100644 --- a/Zend/tests/collection/collection_1.phpt +++ b/Zend/tests/collection/collection_1.phpt @@ -2,7 +2,7 @@ Collection: Syntax --FILE-- Article) +collection(Dict) Articles(int => Article) { } diff --git a/Zend/tests/collection/collection_2.phpt b/Zend/tests/collection/collection_2.phpt index 5c589d7228529..53713fcc10a2f 100644 --- a/Zend/tests/collection/collection_2.phpt +++ b/Zend/tests/collection/collection_2.phpt @@ -1,5 +1,5 @@ --TEST-- -Collection: Array access with integer key +Collection: Sequence --FILE-- Article) +collection(Seq) Articles(Article) { } $c = new Articles; -$c[9] = new Article("First Test"); +$c[] = new Article("First Test"); $c[] = new Article("Second Test"); var_dump($c); -var_dump(isset($c[8])); -var_dump(isset($c[9])); -var_dump(isset($c[10])); +var_dump(isset($c[0])); +var_dump(isset($c[1])); +var_dump(isset($c[2])); -var_dump($c[10]); +var_dump($c[1]); -unset($c[9]); -var_dump(isset($c[9])); +unset($c[1]); +var_dump(isset($c[1])); try { var_dump(isset($c["eleven"])); @@ -38,21 +38,21 @@ try { object(Articles)#%d (%d) { ["value"]=> array(2) { - [9]=> + [0]=> object(Article)#%d (%d) { ["title"]=> string(10) "First Test" } - [10]=> + [1]=> object(Article)#%d (%d) { ["title"]=> string(11) "Second Test" } } } -bool(false) bool(true) bool(true) +bool(false) object(Article)#%d (%d) { ["title"]=> string(11) "Second Test" diff --git a/Zend/tests/collection/collection_3.phpt b/Zend/tests/collection/collection_3.phpt index a1e8cfb852ec4..7b611141ddaef 100644 --- a/Zend/tests/collection/collection_3.phpt +++ b/Zend/tests/collection/collection_3.phpt @@ -1,5 +1,5 @@ --TEST-- -Collection: Array access with string key +Collection: Dictionary --FILE-- Article) +collection(Dict) Articles(string => Article) { } diff --git a/Zend/tests/collection/collection_4.phpt b/Zend/tests/collection/collection_4.phpt index 385d2b9491548..c28df949f6c13 100644 --- a/Zend/tests/collection/collection_4.phpt +++ b/Zend/tests/collection/collection_4.phpt @@ -1,15 +1,24 @@ --TEST-- -Collection: Syntax +Collection with Trait --FILE-- Article) +trait Shuffler { public function shuffle() { - return array_shuffle($this->value); + $values = $this->value; + shuffle($values); + + return $values; } } + +collection(Dict) Articles(int => Article) +{ + use Shuffler; +} + class Article { public function __construct(private string $title) {} @@ -17,12 +26,15 @@ class Article $c = new Articles; -$c[] = new Article("First Test"); +$c[0] = new Article("First Test"); var_dump($c->shuffle()); ?> --EXPECTF-- -object(Articles)#%d (%d) { - ["value"]=> - uninitialized(array) +array(1) { + [0]=> + object(Article)#%d (%d) { + ["title":"Article":private]=> + string(10) "First Test" + } } diff --git a/Zend/tests/collection/collection_5.phpt b/Zend/tests/collection/collection_5.phpt new file mode 100644 index 0000000000000..c4f52a3a682b6 --- /dev/null +++ b/Zend/tests/collection/collection_5.phpt @@ -0,0 +1,10 @@ +--TEST-- +Collection with unsupported structure type +--FILE-- + Article) +{ +} +?> +--EXPECTF-- +Fatal error: Collection data structure must be Seq or Dict, Foobar given in %scollection_5.php on line %d diff --git a/Zend/tests/collection/collection_6.phpt b/Zend/tests/collection/collection_6.phpt new file mode 100644 index 0000000000000..65537a0d8de3c --- /dev/null +++ b/Zend/tests/collection/collection_6.phpt @@ -0,0 +1,25 @@ +--TEST-- +Collection: Sequence with errors +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECTF-- +ValueError: Specifying an offset for sequence collections is not allowed diff --git a/Zend/tests/collection/collection_7.phpt b/Zend/tests/collection/collection_7.phpt new file mode 100644 index 0000000000000..96083963e567d --- /dev/null +++ b/Zend/tests/collection/collection_7.phpt @@ -0,0 +1,10 @@ +--TEST-- +Collection: Syntax Errors +--FILE-- + Article => three) +{ +} +?> +--EXPECTF-- +Fatal error: Invalid collection data types signature in %scollection_7.php on line %d diff --git a/Zend/tests/collection/collection_8.phpt b/Zend/tests/collection/collection_8.phpt new file mode 100644 index 0000000000000..dfae765c47376 --- /dev/null +++ b/Zend/tests/collection/collection_8.phpt @@ -0,0 +1,10 @@ +--TEST-- +Collection: Sequence with key type defined +--FILE-- + Article) +{ +} +?> +--EXPECTF-- +Fatal error: Collection sequences may not have a key type defined in %scollection_8.php on line %d diff --git a/Zend/tests/collection/collection_9.phpt b/Zend/tests/collection/collection_9.phpt new file mode 100644 index 0000000000000..0ee36a20306b0 --- /dev/null +++ b/Zend/tests/collection/collection_9.phpt @@ -0,0 +1,10 @@ +--TEST-- +Collection: Dictionary without key type defined +--FILE-- + +--EXPECTF-- +Fatal error: Collection dictionaries must have a key type defined in %scollection_9.php on line %d diff --git a/Zend/zend.h b/Zend/zend.h index fbd2e5b3e5ca5..8016032291dad 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -144,6 +144,9 @@ struct _zend_inheritance_cache_entry { zend_class_entry *traits_and_interfaces[1]; }; +#define ZEND_COLLECTION_SEQ 1 +#define ZEND_COLLECTION_DICT 2 + struct _zend_class_entry { char type; zend_string *name; @@ -223,6 +226,7 @@ struct _zend_class_entry { uint32_t enum_backing_type; HashTable *backed_enum_table; + uint16_t collection_data_structure; uint32_t collection_key_type; zend_type collection_item_type; diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 8c5c6f3926904..0058f43bdf978 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -99,38 +99,31 @@ static void create_array_if_needed(zend_class_entry *ce, zend_object *object) zval_ptr_dtor(&new_array); } -void zend_collection_add_item(zend_object *object, zval *offset, zval *value) +static void seq_add_item(zend_object *object, zval *value) { zend_class_entry *ce = object->ce; zval rv; zval *value_prop; - ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + Z_ADDREF_P(value); + add_next_index_zval(value_prop, value); +} - if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { - zend_string *type_str = zend_type_to_string(ce->collection_item_type); - zend_type_error( - "Value type %s does not match collection item type %s", - zend_zval_type_name(value), - ZSTR_VAL(type_str) - ); - zend_string_release(type_str); - return; - } +static void dict_add_item(zend_object *object, zval *offset, zval *value) +{ + zend_class_entry *ce = object->ce; + zval rv; + zval *value_prop; - if (!offset && ce->collection_key_type == IS_LONG) { - create_array_if_needed(ce, object); - value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); - Z_ADDREF_P(value); - add_next_index_zval(value_prop, value); - return; - } else if (offset && ce->collection_key_type == IS_LONG && Z_TYPE_P(offset) == IS_LONG) { + if (ce->collection_key_type == IS_LONG && Z_TYPE_P(offset) == IS_LONG) { create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); Z_ADDREF_P(value); add_index_zval(value_prop, Z_LVAL_P(offset), value); return; - } else if (offset && ce->collection_key_type == IS_STRING && Z_TYPE_P(offset) == IS_STRING) { + } else if (ce->collection_key_type == IS_STRING && Z_TYPE_P(offset) == IS_STRING) { create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); Z_ADDREF_P(value); @@ -143,7 +136,43 @@ void zend_collection_add_item(zend_object *object, zval *offset, zval *value) zend_get_type_by_const(ce->collection_key_type) ); } +} + +void zend_collection_add_item(zend_object *object, zval *offset, zval *value) +{ + zend_class_entry *ce = object->ce; + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + + if (offset && ce->collection_data_structure == ZEND_COLLECTION_SEQ) { + zend_value_error("Specifying an offset for sequence collections is not allowed"); + return; + } + + if (!offset && ce->collection_data_structure == ZEND_COLLECTION_DICT) { + zend_value_error("Specifying an offset for dictionary collections is required"); + return; + } + + if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { + zend_string *type_str = zend_type_to_string(ce->collection_item_type); + zend_type_error( + "Value type %s does not match collection item type %s", + zend_zval_type_name(value), + ZSTR_VAL(type_str) + ); + zend_string_release(type_str); + return; + } + + switch (ce->collection_data_structure) { + case ZEND_COLLECTION_SEQ: + seq_add_item(object, value); + break; + case ZEND_COLLECTION_DICT: + dict_add_item(object, offset, value); + break; + } } static int key_type_allowed(zend_class_entry *ce, zval *offset) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 55345a6b03f3a..d72a7b490499f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2046,6 +2046,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->attributes = NULL; ce->enum_backing_type = IS_UNDEF; ce->backed_enum_table = NULL; + ce->collection_key_type = IS_UNDEF; if (nullify_handlers) { ce->constructor = NULL; @@ -8947,6 +8948,28 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ zend_type_release(type, 0); } +static void zend_compile_collection_data_structure(zend_class_entry *ce, zend_ast *collection_data_structure_ast) +{ + zend_string *struct_type = zend_ast_get_str(collection_data_structure_ast); + + if (zend_string_equals_literal_ci(struct_type, "seq")) { + if (ce->collection_key_type != IS_UNDEF) { + zend_error_noreturn(E_COMPILE_ERROR, "Collection sequences may not have a key type defined"); + } + ce->collection_key_type = IS_LONG; + ce->collection_data_structure = ZEND_COLLECTION_SEQ; + } else if (zend_string_equals_literal_ci(struct_type, "dict")) { + if (ce->collection_key_type == IS_UNDEF) { + zend_error_noreturn(E_COMPILE_ERROR, "Collection dictionaries must have a key type defined"); + } + ce->collection_data_structure = ZEND_COLLECTION_DICT; + } else { + zend_error_noreturn(E_COMPILE_ERROR, + "Collection data structure must be Seq or Dict, %s given", + ZSTR_VAL(struct_type)); + } +} + static void zend_compile_collection_key_type(zend_class_entry *ce, zend_ast *collection_key_type_ast) { ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); @@ -8986,9 +9009,9 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *extends_ast = decl->child[0]; zend_ast *implements_ast = decl->child[1]; + zend_ast *collection_data_structure_ast = decl->child[1]; zend_ast *stmt_ast = decl->child[2]; - zend_ast *collection_key_type_ast = decl->child[3]; - zend_ast *collection_item_type_ast = decl->child[4]; + zend_ast *collection_types_list_ast = decl->child[4]; zend_ast *enum_backing_type_ast = decl->child[4]; zend_string *name, *lcname; zend_class_entry *ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry)); @@ -9065,11 +9088,11 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) CG(active_class_entry) = ce; - if (decl->child[3] && !(ce->ce_flags & ZEND_ACC_COLLECTION)) { + if (decl->child[3]) { zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } - if (implements_ast) { + if (implements_ast && !(ce->ce_flags & ZEND_ACC_COLLECTION)) { zend_compile_implements(implements_ast); } @@ -9082,8 +9105,24 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) } if (ce->ce_flags & ZEND_ACC_COLLECTION) { - zend_compile_collection_key_type(ce, collection_key_type_ast); - zend_compile_collection_item_type(ce, collection_item_type_ast); + zend_ast_list *list = zend_ast_get_list(collection_types_list_ast); + + switch (list->children) { + case 1: + zend_compile_collection_item_type(ce, list->child[0]); + break; + case 2: + zend_compile_collection_key_type(ce, list->child[0]); + zend_compile_collection_item_type(ce, list->child[1]); + break; + default: + zend_error_noreturn(E_COMPILE_ERROR, "Invalid collection data types signature"); + break; + } + + /* The data structure is compiled after the key/value types so it can do heuristics */ + zend_compile_collection_data_structure(ce, collection_data_structure_ast); + zend_collection_add_interfaces(ce); zend_collection_register_handlers(ce); zend_collection_register_props(ce); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index f9308b18e7044..8fbfccee9e2bc 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -280,7 +280,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type attribute_decl attribute attributes attribute_group namespace_declaration_name %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list %type enum_declaration_statement enum_backing_type enum_case enum_case_expr -%type collection_declaration_statement collection_type +%type collection_declaration_statement collection_type_list %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 @@ -661,12 +661,15 @@ enum_case_expr: collection_declaration_statement: T_COLLECTION { $$ = CG(zend_lineno); } - T_STRING '(' collection_type T_DOUBLE_ARROW collection_type ')' backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $9, zend_ast_get_str($3), NULL, NULL, $11, $5, $7); } + '(' T_STRING ')' + T_STRING '(' collection_type_list ')' backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $10, zend_ast_get_str($6), NULL, $4, $12, NULL, $8); } ; -collection_type: - type_expr { $$ = $1; } +collection_type_list: + collection_type_list T_DOUBLE_ARROW type_expr { $$ = zend_ast_list_add($1, $3); } + | type_expr { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } +; extends_from: %empty { $$ = NULL; } diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 8705e711a4609..529838c436121 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1562,16 +1562,7 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_ENUM); } -/* - * The collection keyword must be followed by whitespace and another identifier. - * This avoids the BC break of using collection in classes, namespaces, functions and constants. - */ -"collection"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { - yyless(10); - RETURN_TOKEN_WITH_STR(T_STRING, 0); -} -"collection"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { - yyless(10); +"collection" { RETURN_TOKEN_WITH_IDENT(T_COLLECTION); } From e7815be308a0d8d6abd659e84bd115106e730104 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Thu, 22 Jun 2023 19:00:40 +0100 Subject: [PATCH 16/48] Separated interfaces into SeqCollection and DictCollection --- Zend/zend_collection.c | 52 +++++++++++++++++++++++++--------- Zend/zend_collection.h | 7 +++-- Zend/zend_collection.stub.php | 8 +++++- Zend/zend_collection_arginfo.h | 23 +++++++++++++-- Zend/zend_object_handlers.c | 16 ++++++++--- 5 files changed, 83 insertions(+), 23 deletions(-) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 0058f43bdf978..5d582ab0f5df2 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -20,9 +20,13 @@ #include "zend_API.h" #include "zend_collection_arginfo.h" #include "zend_execute.h" +#include "zend_extensions.h" +#include "zend_observer.h" -ZEND_API zend_class_entry *zend_ce_collection; -ZEND_API zend_object_handlers zend_collection_object_handlers; +ZEND_API zend_class_entry *zend_ce_seq_collection; +ZEND_API zend_class_entry *zend_ce_dict_collection; +ZEND_API zend_object_handlers zend_seq_collection_object_handlers; +ZEND_API zend_object_handlers zend_dict_collection_object_handlers; static int zend_implement_collection(zend_class_entry *interface, zend_class_entry *class_type) { @@ -39,12 +43,19 @@ static int zend_implement_collection(zend_class_entry *interface, zend_class_ent void zend_register_collection_ce(void) { - zend_ce_collection = register_class_Collection(); - zend_ce_collection->interface_gets_implemented = zend_implement_collection; + zend_ce_seq_collection = register_class_SeqCollection(); + zend_ce_seq_collection->interface_gets_implemented = zend_implement_collection; - memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); - zend_collection_object_handlers.clone_obj = NULL; - zend_collection_object_handlers.compare = zend_objects_not_comparable; + memcpy(&zend_seq_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_seq_collection_object_handlers.clone_obj = NULL; + zend_seq_collection_object_handlers.compare = zend_objects_not_comparable; + + zend_ce_dict_collection = register_class_DictCollection(); + zend_ce_dict_collection->interface_gets_implemented = zend_implement_collection; + + memcpy(&zend_dict_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_dict_collection_object_handlers.clone_obj = NULL; + zend_dict_collection_object_handlers.compare = zend_objects_not_comparable; } void zend_collection_add_interfaces(zend_class_entry *ce) @@ -57,17 +68,32 @@ void zend_collection_add_interfaces(zend_class_entry *ce) ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); - ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_collection->name); - ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("collection", 0); + switch (ce->collection_data_structure) { + case ZEND_COLLECTION_SEQ: + ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_seq_collection->name); + ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("seqcollection", 0); + ce->default_object_handlers = &zend_seq_collection_object_handlers; + break; + case ZEND_COLLECTION_DICT: + ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_dict_collection->name); + ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("dictcollection", 0); + ce->default_object_handlers = &zend_dict_collection_object_handlers; + break; + } - ce->default_object_handlers = &zend_collection_object_handlers; } void zend_collection_register_handlers(zend_class_entry *ce) { - memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); - zend_collection_object_handlers.clone_obj = NULL; - zend_collection_object_handlers.compare = zend_objects_not_comparable; + memcpy(&zend_seq_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_seq_collection_object_handlers.clone_obj = NULL; + zend_seq_collection_object_handlers.compare = zend_objects_not_comparable; + ce->default_object_handlers = &zend_seq_collection_object_handlers; + + memcpy(&zend_dict_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + zend_dict_collection_object_handlers.clone_obj = NULL; + zend_dict_collection_object_handlers.compare = zend_objects_not_comparable; + ce->default_object_handlers = &zend_dict_collection_object_handlers; ce->default_object_handlers = &zend_collection_object_handlers; } diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h index 962115b8aae17..50e03fe226b83 100644 --- a/Zend/zend_collection.h +++ b/Zend/zend_collection.h @@ -25,8 +25,11 @@ BEGIN_EXTERN_C() -extern ZEND_API zend_object_handlers zend_collection_object_handlers; -extern ZEND_API zend_class_entry *zend_ce_collection; +extern ZEND_API zend_class_entry *zend_ce_seq_collection; +extern ZEND_API zend_class_entry *zend_ce_dict_collection; + +extern ZEND_API zend_object_handlers zend_seq_collection_object_handlers; +extern ZEND_API zend_object_handlers zend_dict_collection_object_handlers; void zend_register_collection_ce(void); void zend_collection_add_interfaces(zend_class_entry *ce); diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 6468336c25f28..a142acba64123 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -2,6 +2,12 @@ /** @generate-class-entries */ -interface Collection +interface SeqCollection { + public function add(mixed $value) : void; +} + +interface DictCollection +{ + public function add(mixed $key, mixed $value) : void; } diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 4de507d31d311..416632e333641 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -4,15 +4,32 @@ -static const zend_function_entry class_Collection_methods[] = { + + +static const zend_function_entry class_SeqCollection_methods[] = { ZEND_FE_END }; -static zend_class_entry *register_class_Collection(void) + +static const zend_function_entry class_DictCollection_methods[] = { + ZEND_FE_END +}; + +static zend_class_entry *register_class_SeqCollection(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "SeqCollection", class_SeqCollection_methods); + class_entry = zend_register_internal_interface(&ce); + + return class_entry; +} + +static zend_class_entry *register_class_DictCollection(void) { zend_class_entry ce, *class_entry; - INIT_CLASS_ENTRY(ce, "Collection", class_Collection_methods); + INIT_CLASS_ENTRY(ce, "DictCollection", class_DictCollection_methods); class_entry = zend_register_internal_interface(&ce); return class_entry; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index c306f8d2a1dab..7abd5f1bc9ac7 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1183,7 +1183,9 @@ ZEND_API zval *zend_std_read_dimension(zend_object *object, zval *offset, int ty return NULL; } return rv; - } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + } else if (zend_class_implements_interface(ce, zend_ce_seq_collection)) { + return zend_collection_read_item(object, offset); + } else if (zend_class_implements_interface(ce, zend_ce_dict_collection)) { return zend_collection_read_item(object, offset); } else { zend_bad_array_access(ce); @@ -1208,7 +1210,9 @@ ZEND_API void zend_std_write_dimension(zend_object *object, zval *offset, zval * zend_call_known_instance_method_with_2_params(funcs->zf_offsetset, object, NULL, &tmp_offset, value); OBJ_RELEASE(object); zval_ptr_dtor(&tmp_offset); - } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + } else if (zend_class_implements_interface(ce, zend_ce_seq_collection)) { + zend_collection_add_item(object, offset, value); + } else if (zend_class_implements_interface(ce, zend_ce_dict_collection)) { zend_collection_add_item(object, offset, value); } else { zend_bad_array_access(ce); @@ -1237,7 +1241,9 @@ ZEND_API int zend_std_has_dimension(zend_object *object, zval *offset, int check } OBJ_RELEASE(object); zval_ptr_dtor(&tmp_offset); - } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + } else if (zend_class_implements_interface(ce, zend_ce_seq_collection)) { + return zend_collection_has_item(object, offset); + } else if (zend_class_implements_interface(ce, zend_ce_dict_collection)) { return zend_collection_has_item(object, offset); } else { zend_bad_array_access(ce); @@ -1425,7 +1431,9 @@ ZEND_API void zend_std_unset_dimension(zend_object *object, zval *offset) /* {{{ zend_call_known_instance_method_with_1_params(funcs->zf_offsetunset, object, NULL, &tmp_offset); OBJ_RELEASE(object); zval_ptr_dtor(&tmp_offset); - } else if (zend_class_implements_interface(ce, zend_ce_collection)) { + } else if (zend_class_implements_interface(ce, zend_ce_seq_collection)) { + zend_collection_unset_item(object, offset); + } else if (zend_class_implements_interface(ce, zend_ce_dict_collection)) { zend_collection_unset_item(object, offset); } else { zend_bad_array_access(ce); From 41df61d36da2ce3cb38a1ef1a39d6e981c4dc705 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Thu, 22 Jun 2023 19:01:22 +0100 Subject: [PATCH 17/48] Add Seq::add and Dict:add functions --- Zend/tests/collection/collection_2.phpt | 11 +++- Zend/tests/collection/collection_3.phpt | 8 ++- Zend/zend_collection.c | 73 ++++++++++++++++++++++++- Zend/zend_collection.h | 1 + Zend/zend_collection_arginfo.h | 11 +++- Zend/zend_inheritance.c | 6 ++ Zend/zend_string.h | 1 + 7 files changed, 106 insertions(+), 5 deletions(-) diff --git a/Zend/tests/collection/collection_2.phpt b/Zend/tests/collection/collection_2.phpt index 53713fcc10a2f..3f5c3924607a0 100644 --- a/Zend/tests/collection/collection_2.phpt +++ b/Zend/tests/collection/collection_2.phpt @@ -17,11 +17,13 @@ $c = new Articles; $c[] = new Article("First Test"); $c[] = new Article("Second Test"); +$c->add(new Article("Third Test")); + var_dump($c); var_dump(isset($c[0])); var_dump(isset($c[1])); -var_dump(isset($c[2])); +var_dump(isset($c[3])); var_dump($c[1]); @@ -37,7 +39,7 @@ try { --EXPECTF-- object(Articles)#%d (%d) { ["value"]=> - array(2) { + array(3) { [0]=> object(Article)#%d (%d) { ["title"]=> @@ -48,6 +50,11 @@ object(Articles)#%d (%d) { ["title"]=> string(11) "Second Test" } + [2]=> + object(Article)#%d (%d) { + ["title"]=> + string(10) "Third Test" + } } } bool(true) diff --git a/Zend/tests/collection/collection_3.phpt b/Zend/tests/collection/collection_3.phpt index 7b611141ddaef..4be8bba163c40 100644 --- a/Zend/tests/collection/collection_3.phpt +++ b/Zend/tests/collection/collection_3.phpt @@ -16,6 +16,7 @@ collection(Dict) Articles(string => Article) $c = new Articles; $c["nine"] = new Article("First Test"); $c["ten"] = new Article("Second Test"); +$c->add("seven", new Article("Third Test")); var_dump($c); @@ -37,7 +38,7 @@ try { --EXPECTF-- object(Articles)#%d (%d) { ["value"]=> - array(2) { + array(3) { ["nine"]=> object(Article)#%d (%d) { ["title"]=> @@ -48,6 +49,11 @@ object(Articles)#%d (%d) { ["title"]=> string(11) "Second Test" } + ["seven"]=> + object(Article)#%d (%d) { + ["title"]=> + string(10) "Third Test" + } } } bool(false) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 5d582ab0f5df2..3956d99925074 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -94,8 +94,79 @@ void zend_collection_register_handlers(zend_class_entry *ce) zend_dict_collection_object_handlers.clone_obj = NULL; zend_dict_collection_object_handlers.compare = zend_objects_not_comparable; ce->default_object_handlers = &zend_dict_collection_object_handlers; +} + +static void seq_add_item(zend_object *object, zval *value); +static void dict_add_item(zend_object *object, zval *key, zval *value); + +static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) +{ + zval *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + seq_add_item(Z_OBJ_P(ZEND_THIS), value); +} + +static ZEND_NAMED_FUNCTION(zend_collection_dict_add_func) +{ + zval *key, *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(key) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + dict_add_item(Z_OBJ_P(ZEND_THIS), key, value); +} + +static void zend_collection_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) +{ + zend_string *name = ZSTR_KNOWN(name_id); + zif->type = ZEND_INTERNAL_FUNCTION; + zif->module = EG(current_module); + zif->scope = ce; + zif->T = ZEND_OBSERVER_ENABLED; + if (EG(active)) { // at run-time + ZEND_MAP_PTR_INIT(zif->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size())); + } else { + ZEND_MAP_PTR_NEW(zif->run_time_cache); + } + + if (!zend_hash_add_ptr(&ce->function_table, name, zif)) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } +} + + +void zend_collection_register_funcs(zend_class_entry *ce) +{ + const uint32_t fn_flags = ZEND_ACC_PUBLIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED; - ce->default_object_handlers = &zend_collection_object_handlers; + switch (ce->collection_data_structure) { + case ZEND_COLLECTION_SEQ: + zend_internal_function *seq_add_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1); + seq_add_function->handler = zend_collection_seq_add_func; + seq_add_function->function_name = ZSTR_KNOWN(ZEND_STR_ADD); + seq_add_function->fn_flags = fn_flags; + seq_add_function->num_args = 1; + seq_add_function->required_num_args = 1; + seq_add_function->arg_info = (zend_internal_arg_info *) (arginfo_class_SeqCollection_add + 1); + zend_collection_register_func(ce, ZEND_STR_ADD, seq_add_function); + break; + case ZEND_COLLECTION_DICT: + zend_internal_function *dict_add_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1); + dict_add_function->handler = zend_collection_dict_add_func; + dict_add_function->function_name = ZSTR_KNOWN(ZEND_STR_ADD); + dict_add_function->fn_flags = fn_flags; + dict_add_function->num_args = 2; + dict_add_function->required_num_args = 2; + dict_add_function->arg_info = (zend_internal_arg_info *) (arginfo_class_DictCollection_add + 1); + zend_collection_register_func(ce, ZEND_STR_ADD, dict_add_function); + break; + } } void zend_collection_register_props(zend_class_entry *ce) diff --git a/Zend/zend_collection.h b/Zend/zend_collection.h index 50e03fe226b83..26100fb198ea2 100644 --- a/Zend/zend_collection.h +++ b/Zend/zend_collection.h @@ -36,6 +36,7 @@ void zend_collection_add_interfaces(zend_class_entry *ce); void zend_collection_register_handlers(zend_class_entry *ce); void zend_collection_register_props(zend_class_entry *ce); +void zend_collection_register_funcs(zend_class_entry *ce); void zend_collection_add_item(zend_object *object, zval *offset, zval *value); int zend_collection_has_item(zend_object *object, zval *offset); diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 416632e333641..4968c15b6f825 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,17 +1,26 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: ec5de160dfbacf129b1377ea555ed62442d212ec */ + * Stub hash: d9cf61aa0f398bb1188638856f46286f107e62de */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DictCollection_add, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() static const zend_function_entry class_SeqCollection_methods[] = { + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, add, arginfo_class_SeqCollection_add, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; static const zend_function_entry class_DictCollection_methods[] = { + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, add, arginfo_class_DictCollection_add, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index b43544766b848..69de3abc6adc4 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3508,6 +3508,12 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string zend_enum_register_funcs(ce); } + if (ce->ce_flags & ZEND_ACC_COLLECTION) { + /* Only register builtin collection methods during inheritance to avoid persisting them + * in opcache. */ + zend_collection_register_funcs(ce); + } + if (parent) { if (!(parent->ce_flags & ZEND_ACC_LINKED)) { add_dependency_obligation(ce, parent); diff --git a/Zend/zend_string.h b/Zend/zend_string.h index ad6c5e1ee38bf..a34eed0f6ab26 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -633,6 +633,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_AUTOGLOBAL_REQUEST, "_REQUEST") \ _(ZEND_STR_COUNT, "count") \ _(ZEND_STR_SENSITIVEPARAMETER, "SensitiveParameter") \ + _(ZEND_STR_ADD, "add") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED, "Deprecated") \ _(ZEND_STR_SINCE, "since") \ From c2084d386d9f6be0d26698ec9614b8e6c9ff1f61 Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Fri, 23 Jun 2023 17:23:32 -0500 Subject: [PATCH 18/48] Test for type checking against generic collection types. --- Zend/tests/collection/collection_10.phpt | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Zend/tests/collection/collection_10.phpt diff --git a/Zend/tests/collection/collection_10.phpt b/Zend/tests/collection/collection_10.phpt new file mode 100644 index 0000000000000..fa6869cb3305c --- /dev/null +++ b/Zend/tests/collection/collection_10.phpt @@ -0,0 +1,37 @@ +--TEST-- +Collection: Type checking +--FILE-- + Article) +{ +} + +collection(Seq) ArticleList(Article) +{ +} + +$c = new Articles(); + +$l = new ArticleList(); + +function needsDict(DictCollection $dict) { + var_dump($dict); +} + +function needsSeq(SeqCollection $seq) { + var_dump($seq); +} + +needsDict($c); +needsSeq($l); + +?> +--EXPECTF-- +object(Articles)#%d (%d) { + ["value"]=> + uninitialized(array) +} +object(ArticleList)#%d (%d) { + ["value"]=> + uninitialized(array) +} From 48d5e7168ec4e38747d26f907755fcaa1362ec4e Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Fri, 23 Jun 2023 19:08:09 -0500 Subject: [PATCH 19/48] Add a representative sample of additional operations. --- Zend/zend_collection.stub.php | 57 +++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index a142acba64123..836213b61d78c 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -2,12 +2,69 @@ /** @generate-class-entries */ +// This being "SeqCollection" but the keyword being "Seq" is going to be confusing, I expect. +// This sounds like what namespaces are good for. interface SeqCollection { + // IMO, this method should return $this so it's more conveniently chainable. public function add(mixed $value) : void; + + // Modifies in place. Equivalent of -=. + public function remove(mixed $value) : static; + + public function has(mixed $value) : bool; + + public function get(int $index) : mixed; + + // Returns new object with the value added. Equivalent of +. + public function with(mixed $value) : static; + + // Returns new object with the value removed if it was there. Equivalent of - + public function without(mixed $value) : static; + + // returns self. Throws OutOfBoundsException if $index is not yet defined. + public function set(int $index, mixed $value): static; + + // This should really be static $other, but the language doesn't allow that. + // Equivalent of + static. + public function concat(self $other): static; + + // True if both seqs have the same values in the same order. + public function equals(self $other): bool; + + // $fn is callable(mixed $val) + // The return type is $targetType, but that can't be expressed statically. + public function map(callable $fn, string $targetType): SeqCollection; + } interface DictCollection { public function add(mixed $key, mixed $value) : void; + + public function remove(mixed $key) : static; + + public function has(mixed $key) : bool; + + public function get(mixed $key) : mixed; + + // Returns new object with the value added. + public function with(mixed $key, mixed $value) : static; + + // Returns new object with the value removed if it was there. + public function without(mixed $key) : static; + + // returns self. Overwrites existing value if already set. + public function set(mixed $key, mixed $value): static; + + // This should really be static $other, but the language doesn't allow that. + // Equivalent of + static. + public function concat(self $other): static; + + // True if both dicts have the same key/values in the same order. + public function equals(self $other): bool; + + // $fn is callable(mixed $val, mixed $key) + // The return type is $targetType, but that can't be expressed statically. + public function map(callable $fn, string $targetType): SeqCollection; } From 7958ca15e0bebd9246a2d2e471d243ddd4e08282 Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Fri, 23 Jun 2023 19:08:27 -0500 Subject: [PATCH 20/48] Add tests for the sample operations. These will not pass yet, naturally. --- Zend/tests/collection/collection_11.phpt | 60 ++++++++++++++++++++++++ Zend/tests/collection/collection_12.phpt | 60 ++++++++++++++++++++++++ Zend/tests/collection/collection_13.phpt | 30 ++++++++++++ Zend/tests/collection/collection_14.phpt | 30 ++++++++++++ Zend/tests/collection/collection_15.phpt | 28 +++++++++++ Zend/tests/collection/collection_16.phpt | 27 +++++++++++ Zend/tests/collection/collection_17.phpt | 33 +++++++++++++ Zend/tests/collection/collection_18.phpt | 32 +++++++++++++ 8 files changed, 300 insertions(+) create mode 100644 Zend/tests/collection/collection_11.phpt create mode 100644 Zend/tests/collection/collection_12.phpt create mode 100644 Zend/tests/collection/collection_13.phpt create mode 100644 Zend/tests/collection/collection_14.phpt create mode 100644 Zend/tests/collection/collection_15.phpt create mode 100644 Zend/tests/collection/collection_16.phpt create mode 100644 Zend/tests/collection/collection_17.phpt create mode 100644 Zend/tests/collection/collection_18.phpt diff --git a/Zend/tests/collection/collection_11.phpt b/Zend/tests/collection/collection_11.phpt new file mode 100644 index 0000000000000..dcb661de3d4c6 --- /dev/null +++ b/Zend/tests/collection/collection_11.phpt @@ -0,0 +1,60 @@ +--TEST-- +Modifying a sequence +--FILE-- +add(new Book('Title 1')); +$c->add(new Book('Title 2')); + +// Should have 2 items. +var_dump($c); + +$c->remove(new Book('Title 1')); + +// Should have one item. +var_dump($c); + +// Should be false +var_dump($c->has(new Book('Title 1'))); +var_dump(isset($c[new Book('Title 1')])); + +// Should be true +var_dump($c->has(new Book('Title 2'))); +var_dump(isset($c[new Book('Title 2')])); + +$c2 = $c->with(new Book('Title 3')); + +// Still one item. +var_dump($c); + +// But this has 2 items. +var_dump($c2); + +$c3 = $c2->without(new Book('Title 2')); + +// Still two items. +var_dump($c2); + +// But only one item here. +var_dump($c3); + +$c3->set(0, new Book('Title 4')); + +// Only Title 4 now exists. +var_dump($c3); + +unset($c3[0]); + +// Empty +var_dump($c3); + +?> +--EXPECTF-- diff --git a/Zend/tests/collection/collection_12.phpt b/Zend/tests/collection/collection_12.phpt new file mode 100644 index 0000000000000..96be496c9a325 --- /dev/null +++ b/Zend/tests/collection/collection_12.phpt @@ -0,0 +1,60 @@ +--TEST-- +Modifying a dictionary +--FILE-- + Book) {} + +$c = new Books(); + +$c->add('one', new Book('Title 1')); +$c->add('two', new Book('Title 2')); + +// Should have 2 items. +var_dump($c); + +$c->remove('one'); + +// Should have one item. +var_dump($c); + +// Should be false +var_dump($c->has('one')); +var_dump(isset($c['one'])); + +// Should be true +var_dump($c->has('two')); +var_dump(isset($c['two'])); + +$c2 = $c->with('three', new Book('Title 3')); + +// Still one item. +var_dump($c); + +// But this has 2 items. +var_dump($c2); + +$c3 = $c2->without('two'); + +// Still two items. +var_dump($c2); + +// But only one item here. +var_dump($c3); + +$c3->set('three', new Book('Title 4')); + +// Only Title 4 now exists. +var_dump($c3); + +unset($c3['three']); + +// Empty +var_dump($c3); + +?> +--EXPECTF-- diff --git a/Zend/tests/collection/collection_13.phpt b/Zend/tests/collection/collection_13.phpt new file mode 100644 index 0000000000000..c87526930394e --- /dev/null +++ b/Zend/tests/collection/collection_13.phpt @@ -0,0 +1,30 @@ +--TEST-- +Concatenating a sequence +--FILE-- +add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +$c2->add(new Book('Title 3')); +$c2->add(new Book('Title 4')); + +$c3 = $c->concat($c2); + +// Four items. +var_dump($c3); + +// Still two items. +var_dump($c1); + +?> +--EXPECTF-- diff --git a/Zend/tests/collection/collection_14.phpt b/Zend/tests/collection/collection_14.phpt new file mode 100644 index 0000000000000..315ec3d08ca6f --- /dev/null +++ b/Zend/tests/collection/collection_14.phpt @@ -0,0 +1,30 @@ +--TEST-- +Concatenating a dictionary +--FILE-- + Book) {} + +$c1 = new Books(); +$c2 = new Books(); + +$c1->add('one', new Book('Title 1')); +$c1->add('two', new Book('Title 2')); + +$c2->add('three', new Book('Title 3')); +$c2->add('four', new Book('Title 4')); + +$c3 = $c->concat($c2); + +// Four items. +var_dump($c3); + +// Still two items. +var_dump($c1); + +?> +--EXPECTF-- diff --git a/Zend/tests/collection/collection_15.phpt b/Zend/tests/collection/collection_15.phpt new file mode 100644 index 0000000000000..32d65b9aa27ae --- /dev/null +++ b/Zend/tests/collection/collection_15.phpt @@ -0,0 +1,28 @@ +--TEST-- +Mapping a sequence +--FILE-- +add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +$c2 = $c1->map(fn(Book $b): string => $b->title), Titles::class); + +// True +var_dump($c2 instanceof Titles); + +// List of 2 strings +var_dump($c2); + +?> +--EXPECTF-- diff --git a/Zend/tests/collection/collection_16.phpt b/Zend/tests/collection/collection_16.phpt new file mode 100644 index 0000000000000..9241380a87159 --- /dev/null +++ b/Zend/tests/collection/collection_16.phpt @@ -0,0 +1,27 @@ +--TEST-- +Mapping a dictionary +--FILE-- + Book) {} + +collection(Dict) Titles(string => string) {} + +$c1 = new Books(); + +$c1->add('one', new Book('Title 1')); +$c1->add('two', new Book('Title 2')); + +$c2 = $c1->map(fn(Book $b, string $k) => sprintf('%s: %s', $k, $book->title), Titles::class); + +var_dump($c2 instanceof Titles); + +// Should be a map of string to string, like one => one: Title 1. +var_dump($c2); + +?> +--EXPECTF-- diff --git a/Zend/tests/collection/collection_17.phpt b/Zend/tests/collection/collection_17.phpt new file mode 100644 index 0000000000000..03891b1cedbf1 --- /dev/null +++ b/Zend/tests/collection/collection_17.phpt @@ -0,0 +1,33 @@ +--TEST-- +Sequence equality +--FILE-- +add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +$c2->add(new Book('Title 1')); +$c2->add(new Book('Title 2')); + +// True +var_dump($c1->equals($c2)); +var_dump($c1 == $c2); + +$c2[] = new Book('Title 3'); + +// False +var_dump($c1->equals($c2)); +var_dump($c1 == $c2); + +?> +--EXPECTF-- diff --git a/Zend/tests/collection/collection_18.phpt b/Zend/tests/collection/collection_18.phpt new file mode 100644 index 0000000000000..5a038a26e6544 --- /dev/null +++ b/Zend/tests/collection/collection_18.phpt @@ -0,0 +1,32 @@ +--TEST-- +Dictionary equality +--FILE-- + Book) {} + +$c1 = new Books(); +$c2 = new Books(); + +$c1->add('one', new Book('Title 1')); +$c1->add('two', new Book('Title 2')); + +$c2->add('one', new Book('Title 1')); +$c2->add('two', new Book('Title 2')); + +// True +var_dump($c1->equals($c2)); +var_dump($c1 == $c2); + +$c2['three'] = new Book('Title 3'); + +// False +var_dump($c1->equals($c2)); +var_dump($c1 == $c2); + +?> +--EXPECTF-- From ac6e449dcfe9d052355cec1847aab38d7a9871ec Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 26 Jun 2023 15:00:05 +0100 Subject: [PATCH 21/48] Comment out unimplemented functions --- Zend/zend_collection.stub.php | 21 +++++++++++---------- Zend/zend_collection_arginfo.h | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 836213b61d78c..18d72e0253d5f 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -7,8 +7,8 @@ interface SeqCollection { // IMO, this method should return $this so it's more conveniently chainable. - public function add(mixed $value) : void; - + public function add(mixed $value) : void; +/* // Modifies in place. Equivalent of -=. public function remove(mixed $value) : static; @@ -27,21 +27,21 @@ public function set(int $index, mixed $value): static; // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. - public function concat(self $other): static; + public function concat(SeqCollection $other): static; // True if both seqs have the same values in the same order. - public function equals(self $other): bool; + public function equals(SeqCollection $other): bool; // $fn is callable(mixed $val) // The return type is $targetType, but that can't be expressed statically. public function map(callable $fn, string $targetType): SeqCollection; - +*/ } interface DictCollection { - public function add(mixed $key, mixed $value) : void; - + public function add(mixed $key, mixed $value) : void; +/* public function remove(mixed $key) : static; public function has(mixed $key) : bool; @@ -59,12 +59,13 @@ public function set(mixed $key, mixed $value): static; // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. - public function concat(self $other): static; + public function concat(DictCollection $other): static; // True if both dicts have the same key/values in the same order. - public function equals(self $other): bool; + public function equals(DictCollection $other): bool; // $fn is callable(mixed $val, mixed $key) // The return type is $targetType, but that can't be expressed statically. - public function map(callable $fn, string $targetType): SeqCollection; + public function map(callable $fn, string $targetType): DictCollection; +*/ } diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 4968c15b6f825..1e25134a4aba2 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d9cf61aa0f398bb1188638856f46286f107e62de */ + * Stub hash: a678f92c0c255c3d2842ad1183686c71a3bf98a9 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) From 1493f6b0131fded98178de0fe15ebdd794bdc822 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 26 Jun 2023 15:05:26 +0100 Subject: [PATCH 22/48] Rename test file with logical names --- .../{collection_3.phpt => collection_dict_basic.phpt} | 0 .../{collection_9.phpt => collection_dict_syntax_errors.phpt} | 2 +- .../{collection_4.phpt => collection_dict_with_trait.phpt} | 0 ...{collection_10.phpt => collection_interface_type_check.phpt} | 2 +- .../collection/{collection_2.phpt => collection_seq_basic.phpt} | 0 .../{collection_6.phpt => collection_seq_errors.phpt} | 0 .../{collection_8.phpt => collection_seq_syntax_errors.phpt} | 2 +- .../collection/{collection_1.phpt => collection_syntax.phpt} | 0 .../{collection_7.phpt => collection_syntax_errors.phpt} | 2 +- ...lection_5.phpt => collection_syntax_unknown_collection.phpt} | 2 +- 10 files changed, 5 insertions(+), 5 deletions(-) rename Zend/tests/collection/{collection_3.phpt => collection_dict_basic.phpt} (100%) rename Zend/tests/collection/{collection_9.phpt => collection_dict_syntax_errors.phpt} (84%) rename Zend/tests/collection/{collection_4.phpt => collection_dict_with_trait.phpt} (100%) rename Zend/tests/collection/{collection_10.phpt => collection_interface_type_check.phpt} (91%) rename Zend/tests/collection/{collection_2.phpt => collection_seq_basic.phpt} (100%) rename Zend/tests/collection/{collection_6.phpt => collection_seq_errors.phpt} (100%) rename Zend/tests/collection/{collection_8.phpt => collection_seq_syntax_errors.phpt} (84%) rename Zend/tests/collection/{collection_1.phpt => collection_syntax.phpt} (100%) rename Zend/tests/collection/{collection_7.phpt => collection_syntax_errors.phpt} (58%) rename Zend/tests/collection/{collection_5.phpt => collection_syntax_unknown_collection.phpt} (82%) diff --git a/Zend/tests/collection/collection_3.phpt b/Zend/tests/collection/collection_dict_basic.phpt similarity index 100% rename from Zend/tests/collection/collection_3.phpt rename to Zend/tests/collection/collection_dict_basic.phpt diff --git a/Zend/tests/collection/collection_9.phpt b/Zend/tests/collection/collection_dict_syntax_errors.phpt similarity index 84% rename from Zend/tests/collection/collection_9.phpt rename to Zend/tests/collection/collection_dict_syntax_errors.phpt index 0ee36a20306b0..8ba1a79cf90d8 100644 --- a/Zend/tests/collection/collection_9.phpt +++ b/Zend/tests/collection/collection_dict_syntax_errors.phpt @@ -7,4 +7,4 @@ collection(Dict) Articles(Article) } ?> --EXPECTF-- -Fatal error: Collection dictionaries must have a key type defined in %scollection_9.php on line %d +Fatal error: Collection dictionaries must have a key type defined in %s on line %d diff --git a/Zend/tests/collection/collection_4.phpt b/Zend/tests/collection/collection_dict_with_trait.phpt similarity index 100% rename from Zend/tests/collection/collection_4.phpt rename to Zend/tests/collection/collection_dict_with_trait.phpt diff --git a/Zend/tests/collection/collection_10.phpt b/Zend/tests/collection/collection_interface_type_check.phpt similarity index 91% rename from Zend/tests/collection/collection_10.phpt rename to Zend/tests/collection/collection_interface_type_check.phpt index fa6869cb3305c..4fd5f1224108e 100644 --- a/Zend/tests/collection/collection_10.phpt +++ b/Zend/tests/collection/collection_interface_type_check.phpt @@ -1,5 +1,5 @@ --TEST-- -Collection: Type checking +Collection: Type checking against interface --FILE-- Article) diff --git a/Zend/tests/collection/collection_2.phpt b/Zend/tests/collection/collection_seq_basic.phpt similarity index 100% rename from Zend/tests/collection/collection_2.phpt rename to Zend/tests/collection/collection_seq_basic.phpt diff --git a/Zend/tests/collection/collection_6.phpt b/Zend/tests/collection/collection_seq_errors.phpt similarity index 100% rename from Zend/tests/collection/collection_6.phpt rename to Zend/tests/collection/collection_seq_errors.phpt diff --git a/Zend/tests/collection/collection_8.phpt b/Zend/tests/collection/collection_seq_syntax_errors.phpt similarity index 84% rename from Zend/tests/collection/collection_8.phpt rename to Zend/tests/collection/collection_seq_syntax_errors.phpt index dfae765c47376..e8eff1d5b2f34 100644 --- a/Zend/tests/collection/collection_8.phpt +++ b/Zend/tests/collection/collection_seq_syntax_errors.phpt @@ -7,4 +7,4 @@ collection(Seq) Articles(int => Article) } ?> --EXPECTF-- -Fatal error: Collection sequences may not have a key type defined in %scollection_8.php on line %d +Fatal error: Collection sequences may not have a key type defined in %s on line %d diff --git a/Zend/tests/collection/collection_1.phpt b/Zend/tests/collection/collection_syntax.phpt similarity index 100% rename from Zend/tests/collection/collection_1.phpt rename to Zend/tests/collection/collection_syntax.phpt diff --git a/Zend/tests/collection/collection_7.phpt b/Zend/tests/collection/collection_syntax_errors.phpt similarity index 58% rename from Zend/tests/collection/collection_7.phpt rename to Zend/tests/collection/collection_syntax_errors.phpt index 96083963e567d..57c2464feee4e 100644 --- a/Zend/tests/collection/collection_7.phpt +++ b/Zend/tests/collection/collection_syntax_errors.phpt @@ -7,4 +7,4 @@ collection(Dict) Articles(int => Article => three) } ?> --EXPECTF-- -Fatal error: Invalid collection data types signature in %scollection_7.php on line %d +Fatal error: Invalid collection data types signature in %s on line %d diff --git a/Zend/tests/collection/collection_5.phpt b/Zend/tests/collection/collection_syntax_unknown_collection.phpt similarity index 82% rename from Zend/tests/collection/collection_5.phpt rename to Zend/tests/collection/collection_syntax_unknown_collection.phpt index c4f52a3a682b6..1be49d31a2848 100644 --- a/Zend/tests/collection/collection_5.phpt +++ b/Zend/tests/collection/collection_syntax_unknown_collection.phpt @@ -7,4 +7,4 @@ collection(Foobar) Articles(int => Article) } ?> --EXPECTF-- -Fatal error: Collection data structure must be Seq or Dict, Foobar given in %scollection_5.php on line %d +Fatal error: Collection data structure must be Seq or Dict, Foobar given in %s on line %d From 6c844dce22217de13a10a40306e076a9b6f50491 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 26 Jun 2023 17:11:46 +0100 Subject: [PATCH 23/48] Mark tests as XFAIL --- .../{collection_14.phpt => collection_dict_concat.phpt} | 4 +++- .../{collection_18.phpt => collection_dict_equality.phpt} | 4 +++- .../{collection_16.phpt => collection_dict_map.phpt} | 4 +++- .../{collection_12.phpt => collection_dict_modify.phpt} | 4 +++- .../{collection_13.phpt => collection_seq_concat.phpt} | 4 +++- .../{collection_17.phpt => collection_seq_equality.phpt} | 4 +++- .../{collection_15.phpt => collection_seq_map.phpt} | 4 +++- .../{collection_11.phpt => collection_seq_modify.phpt} | 4 +++- 8 files changed, 24 insertions(+), 8 deletions(-) rename Zend/tests/collection/{collection_14.phpt => collection_dict_concat.phpt} (88%) rename Zend/tests/collection/{collection_18.phpt => collection_dict_equality.phpt} (89%) rename Zend/tests/collection/{collection_16.phpt => collection_dict_map.phpt} (90%) rename Zend/tests/collection/{collection_12.phpt => collection_dict_modify.phpt} (93%) rename Zend/tests/collection/{collection_13.phpt => collection_seq_concat.phpt} (88%) rename Zend/tests/collection/{collection_17.phpt => collection_seq_equality.phpt} (89%) rename Zend/tests/collection/{collection_15.phpt => collection_seq_map.phpt} (89%) rename Zend/tests/collection/{collection_11.phpt => collection_seq_modify.phpt} (94%) diff --git a/Zend/tests/collection/collection_14.phpt b/Zend/tests/collection/collection_dict_concat.phpt similarity index 88% rename from Zend/tests/collection/collection_14.phpt rename to Zend/tests/collection/collection_dict_concat.phpt index 315ec3d08ca6f..62ec0bca96490 100644 --- a/Zend/tests/collection/collection_14.phpt +++ b/Zend/tests/collection/collection_dict_concat.phpt @@ -1,5 +1,7 @@ --TEST-- -Concatenating a dictionary +Collection: Dictionary: Concat +--XFAIL-- +Unimplemented --FILE-- Date: Mon, 26 Jun 2023 17:21:26 +0100 Subject: [PATCH 24/48] Macro-ify function registration --- Zend/zend_collection.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 3956d99925074..2aedc9837b836 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -140,6 +140,18 @@ static void zend_collection_register_func(zend_class_entry *ce, zend_known_strin } } +#define REGISTER_FUNCTION(name, php_handler, arginfo, argc) \ + { \ + zend_internal_function *zif_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1); \ + zif_function->handler = (php_handler); \ + zif_function->function_name = ZSTR_KNOWN((name)); \ + zif_function->fn_flags = fn_flags; \ + zif_function->num_args = (argc); \ + zif_function->required_num_args = (argc); \ + zif_function->arg_info = (zend_internal_arg_info *) ((arginfo) + 1); \ + zend_collection_register_func(ce, (name), zif_function); \ + } + void zend_collection_register_funcs(zend_class_entry *ce) { @@ -147,24 +159,10 @@ void zend_collection_register_funcs(zend_class_entry *ce) switch (ce->collection_data_structure) { case ZEND_COLLECTION_SEQ: - zend_internal_function *seq_add_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1); - seq_add_function->handler = zend_collection_seq_add_func; - seq_add_function->function_name = ZSTR_KNOWN(ZEND_STR_ADD); - seq_add_function->fn_flags = fn_flags; - seq_add_function->num_args = 1; - seq_add_function->required_num_args = 1; - seq_add_function->arg_info = (zend_internal_arg_info *) (arginfo_class_SeqCollection_add + 1); - zend_collection_register_func(ce, ZEND_STR_ADD, seq_add_function); + REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_seq_add_func, arginfo_class_SeqCollection_add, 1); break; case ZEND_COLLECTION_DICT: - zend_internal_function *dict_add_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1); - dict_add_function->handler = zend_collection_dict_add_func; - dict_add_function->function_name = ZSTR_KNOWN(ZEND_STR_ADD); - dict_add_function->fn_flags = fn_flags; - dict_add_function->num_args = 2; - dict_add_function->required_num_args = 2; - dict_add_function->arg_info = (zend_internal_arg_info *) (arginfo_class_DictCollection_add + 1); - zend_collection_register_func(ce, ZEND_STR_ADD, dict_add_function); + REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_dict_add_func, arginfo_class_DictCollection_add, 2); break; } } From b7031cade7ca600ea1e655bff8f64da4ac6e2718 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 27 Jun 2023 15:01:46 +0100 Subject: [PATCH 25/48] Implement Sequence' remove/has/get --- .../collection/collection_seq_modify.phpt | 18 ++++--- Zend/zend_collection.c | 49 +++++++++++++++++++ Zend/zend_collection.stub.php | 14 +++--- Zend/zend_collection_arginfo.h | 31 +++++++++++- Zend/zend_default_classes.c | 1 + Zend/zend_inheritance.c | 1 + Zend/zend_string.h | 6 +++ 7 files changed, 104 insertions(+), 16 deletions(-) diff --git a/Zend/tests/collection/collection_seq_modify.phpt b/Zend/tests/collection/collection_seq_modify.phpt index fc6db9ee0ba20..1d0cfe4b342aa 100644 --- a/Zend/tests/collection/collection_seq_modify.phpt +++ b/Zend/tests/collection/collection_seq_modify.phpt @@ -19,18 +19,18 @@ $c->add(new Book('Title 2')); // Should have 2 items. var_dump($c); -$c->remove(new Book('Title 1')); +$c->remove(0); // Should have one item. var_dump($c); // Should be false -var_dump($c->has(new Book('Title 1'))); -var_dump(isset($c[new Book('Title 1')])); +var_dump($c->has(0)); +var_dump(isset($c[0])); // Should be true -var_dump($c->has(new Book('Title 2'))); -var_dump(isset($c[new Book('Title 2')])); +var_dump($c->has(1)); +var_dump(isset($c[1])); $c2 = $c->with(new Book('Title 3')); @@ -40,7 +40,7 @@ var_dump($c); // But this has 2 items. var_dump($c2); -$c3 = $c2->without(new Book('Title 2')); +$c3 = $c2->without(1); // Still two items. var_dump($c2); @@ -58,5 +58,11 @@ unset($c3[0]); // Empty var_dump($c3); +// Throws OutOfBoundsException +try { + $c3->set(42, new Book('Title 1')); +} catch (OutOfBoundsException $e) { + echo $e->getClass, ': ', $e->getMessage(), "\n"; +} ?> --EXPECTF-- diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 2aedc9837b836..778d266aa8acb 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -98,6 +98,9 @@ void zend_collection_register_handlers(zend_class_entry *ce) static void seq_add_item(zend_object *object, zval *value); static void dict_add_item(zend_object *object, zval *key, zval *value); +int zend_collection_has_item(zend_object *object, zval *offset); +zval *zend_collection_read_item(zend_object *object, zval *offset); +void zend_collection_unset_item(zend_object *object, zval *offset); static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) { @@ -108,8 +111,48 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) ZEND_PARSE_PARAMETERS_END(); seq_add_item(Z_OBJ_P(ZEND_THIS), value); + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } +static ZEND_NAMED_FUNCTION(zend_collection_seq_remove_func) +{ + zval *index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + zend_collection_unset_item(Z_OBJ_P(ZEND_THIS), index); + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); +} + +static ZEND_NAMED_FUNCTION(zend_collection_seq_has_func) +{ + zval *index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_BOOL(zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)); +} + +static ZEND_NAMED_FUNCTION(zend_collection_seq_get_func) +{ + zval *index, *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + value = zend_collection_read_item(Z_OBJ_P(ZEND_THIS), index); + + RETURN_COPY_VALUE(value); +} + + static ZEND_NAMED_FUNCTION(zend_collection_dict_add_func) { zval *key, *value; @@ -160,6 +203,12 @@ void zend_collection_register_funcs(zend_class_entry *ce) switch (ce->collection_data_structure) { case ZEND_COLLECTION_SEQ: REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_seq_add_func, arginfo_class_SeqCollection_add, 1); + REGISTER_FUNCTION(ZEND_STR_REMOVE, zend_collection_seq_remove_func, arginfo_class_SeqCollection_remove, 1); + REGISTER_FUNCTION(ZEND_STR_HAS, zend_collection_seq_has_func, arginfo_class_SeqCollection_has, 1); + REGISTER_FUNCTION(ZEND_STR_GET, zend_collection_seq_get_func, arginfo_class_SeqCollection_get, 1); + REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_seq_with_func, arginfo_class_SeqCollection_with, 1); + REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_seq_without_func, arginfo_class_SeqCollection_without, 1); + REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_set_func, arginfo_class_SeqCollection_set, 2); break; case ZEND_COLLECTION_DICT: REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_dict_add_func, arginfo_class_DictCollection_add, 2); diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 18d72e0253d5f..da86eba9c85da 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -6,13 +6,11 @@ // This sounds like what namespaces are good for. interface SeqCollection { - // IMO, this method should return $this so it's more conveniently chainable. - public function add(mixed $value) : void; -/* - // Modifies in place. Equivalent of -=. - public function remove(mixed $value) : static; + public function add(mixed $value) : static; + + public function remove(int $index) : static; - public function has(mixed $value) : bool; + public function has(int $value) : bool; public function get(int $index) : mixed; @@ -20,11 +18,11 @@ public function get(int $index) : mixed; public function with(mixed $value) : static; // Returns new object with the value removed if it was there. Equivalent of - - public function without(mixed $value) : static; + public function without(int $index) : static; // returns self. Throws OutOfBoundsException if $index is not yet defined. public function set(int $index, mixed $value): static; - +/* // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. public function concat(SeqCollection $other): static; diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 1e25134a4aba2..30f144bab462b 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,7 +1,28 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a678f92c0c255c3d2842ad1183686c71a3bf98a9 */ + * Stub hash: 35953abbd50e7a1021de43de279ec0e3882b6c2c */ -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_remove, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_has, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_get, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_SeqCollection_with arginfo_class_SeqCollection_add + +#define arginfo_class_SeqCollection_without arginfo_class_SeqCollection_remove + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_set, 0, 2, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() @@ -15,6 +36,12 @@ ZEND_END_ARG_INFO() static const zend_function_entry class_SeqCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, add, arginfo_class_SeqCollection_add, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, remove, arginfo_class_SeqCollection_remove, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, has, arginfo_class_SeqCollection_has, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, get, arginfo_class_SeqCollection_get, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, with, arginfo_class_SeqCollection_with, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, without, arginfo_class_SeqCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, set, arginfo_class_SeqCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; diff --git a/Zend/zend_default_classes.c b/Zend/zend_default_classes.c index 191b21cb604e9..31be93ade90c1 100644 --- a/Zend/zend_default_classes.c +++ b/Zend/zend_default_classes.c @@ -28,6 +28,7 @@ #include "zend_weakrefs.h" #include "zend_enum.h" #include "zend_fibers.h" +#include "zend_collection.h" ZEND_API void zend_register_default_classes(void) { diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 69de3abc6adc4..10f3adbc5d60f 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -30,6 +30,7 @@ #include "zend_attributes.h" #include "zend_constants.h" #include "zend_observer.h" +#include "zend_collection.h" ZEND_API zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces) = NULL; ZEND_API zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies) = NULL; diff --git a/Zend/zend_string.h b/Zend/zend_string.h index a34eed0f6ab26..d52b41463b988 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -634,6 +634,12 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_COUNT, "count") \ _(ZEND_STR_SENSITIVEPARAMETER, "SensitiveParameter") \ _(ZEND_STR_ADD, "add") \ + _(ZEND_STR_REMOVE, "remove") \ + _(ZEND_STR_HAS, "has") \ + _(ZEND_STR_GET, "get") \ + _(ZEND_STR_SET, "set") \ + _(ZEND_STR_WITH, "with") \ + _(ZEND_STR_WITHOUT, "without") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED, "Deprecated") \ _(ZEND_STR_SINCE, "since") \ From 1c068268d3a322322339384d4d1ed50d929fa717 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Wed, 28 Jun 2023 16:30:21 +0100 Subject: [PATCH 26/48] Implement sequence 'update/with/without' functions --- .../collection/collection_seq_modify.phpt | 146 ++++++++++++++++-- Zend/zend_collection.c | 121 ++++++++++++++- 2 files changed, 244 insertions(+), 23 deletions(-) diff --git a/Zend/tests/collection/collection_seq_modify.phpt b/Zend/tests/collection/collection_seq_modify.phpt index 1d0cfe4b342aa..ea54aa7167cb2 100644 --- a/Zend/tests/collection/collection_seq_modify.phpt +++ b/Zend/tests/collection/collection_seq_modify.phpt @@ -1,7 +1,5 @@ --TEST-- Collection: Sequence: Modify ---XFAIL-- -Unimplemented --FILE-- add(new Book('Title 1')); $c->add(new Book('Title 2')); -// Should have 2 items. +echo "\nShould have 2 items:\n"; var_dump($c); $c->remove(0); -// Should have one item. +echo "\nShould have one item:\n"; var_dump($c); -// Should be false +echo "\nShould be false:\n"; var_dump($c->has(0)); var_dump(isset($c[0])); -// Should be true +echo "\nShould be true:\n"; var_dump($c->has(1)); var_dump(isset($c[1])); $c2 = $c->with(new Book('Title 3')); -// Still one item. +echo "\nStill one item:\n"; var_dump($c); -// But this has 2 items. +echo "\nBut this has 2 items:\n"; var_dump($c2); $c3 = $c2->without(1); -// Still two items. +echo "\nStill two items:\n"; var_dump($c2); -// But only one item here. +echo "\nBut only one item here:\n"; var_dump($c3); -$c3->set(0, new Book('Title 4')); +$c3->set(2, new Book('Title 4')); -// Only Title 4 now exists. +echo "\nOnly 'Title 4' now exists:\n"; var_dump($c3); -unset($c3[0]); +unset($c3[2]); -// Empty +echo "\nEmpty:\n"; var_dump($c3); -// Throws OutOfBoundsException +echo "\nThrows OutOfBoundsException:\n"; try { $c3->set(42, new Book('Title 1')); } catch (OutOfBoundsException $e) { - echo $e->getClass, ': ', $e->getMessage(), "\n"; + echo get_class($e), ': ', $e->getMessage(), "\n"; } ?> --EXPECTF-- +Should have 2 items: +object(Books)#1 (1) { + ["value"]=> + array(2) { + [0]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 1" + } + [1]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + } +} + +Should have one item: +object(Books)#1 (1) { + ["value"]=> + array(1) { + [1]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + } +} + +Should be false: +bool(false) +bool(false) + +Should be true: +bool(true) +bool(true) + +Still one item: +object(Books)#1 (1) { + ["value"]=> + array(1) { + [1]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + } +} + +But this has 2 items: +object(Books)#4 (1) { + ["value"]=> + array(2) { + [1]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + [2]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 3" + } + } +} + +Still two items: +object(Books)#4 (1) { + ["value"]=> + array(2) { + [1]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + [2]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 3" + } + } +} + +But only one item here: +object(Books)#5 (1) { + ["value"]=> + array(1) { + [2]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 3" + } + } +} + +Only 'Title 4' now exists: +object(Books)#5 (1) { + ["value"]=> + array(1) { + [2]=> + object(Book)#6 (1) { + ["title"]=> + string(7) "Title 4" + } + } +} + +Empty: +object(Books)#5 (1) { + ["value"]=> + array(0) { + } +} + +Throws OutOfBoundsException: +OutOfBoundsException: Index '42' does not exist in the sequence diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 778d266aa8acb..01a30f7b55c0c 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -20,9 +20,12 @@ #include "zend_API.h" #include "zend_collection_arginfo.h" #include "zend_execute.h" +#include "zend_exceptions.h" #include "zend_extensions.h" #include "zend_observer.h" +#include "ext/spl/spl_exceptions.h" + ZEND_API zend_class_entry *zend_ce_seq_collection; ZEND_API zend_class_entry *zend_ce_dict_collection; ZEND_API zend_object_handlers zend_seq_collection_object_handlers; @@ -96,8 +99,8 @@ void zend_collection_register_handlers(zend_class_entry *ce) ce->default_object_handlers = &zend_dict_collection_object_handlers; } -static void seq_add_item(zend_object *object, zval *value); -static void dict_add_item(zend_object *object, zval *key, zval *value); +void zend_collection_add_item(zend_object *object, zval *offset, zval *value); +void zend_collection_update_item(zend_object *object, zval *offset, zval *value); int zend_collection_has_item(zend_object *object, zval *offset); zval *zend_collection_read_item(zend_object *object, zval *offset); void zend_collection_unset_item(zend_object *object, zval *offset); @@ -110,7 +113,7 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END(); - seq_add_item(Z_OBJ_P(ZEND_THIS), value); + zend_collection_add_item(Z_OBJ_P(ZEND_THIS), NULL, value); RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } @@ -152,6 +155,58 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_get_func) RETURN_COPY_VALUE(value); } +static ZEND_NAMED_FUNCTION(zend_collection_seq_with_func) +{ + zval *value; + zend_object *clone; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + clone = zend_objects_clone_obj(Z_OBJ_P(ZEND_THIS)); + + zend_collection_add_item(clone, NULL, value); + + RETURN_OBJ(clone); +} + +static ZEND_NAMED_FUNCTION(zend_collection_seq_without_func) +{ + zval *index; + zend_object *clone; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + clone = zend_objects_clone_obj(Z_OBJ_P(ZEND_THIS)); + + zend_collection_unset_item(clone, index); + + RETURN_OBJ(clone); +} + +static ZEND_NAMED_FUNCTION(zend_collection_seq_update_func) +{ + zval *index; + zval *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(index) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)) { + zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Index '%ld' does not exist in the sequence", Z_LVAL_P(index)); + return; + } + + zend_collection_update_item(Z_OBJ_P(ZEND_THIS), index, value); + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); +} + static ZEND_NAMED_FUNCTION(zend_collection_dict_add_func) { @@ -162,7 +217,7 @@ static ZEND_NAMED_FUNCTION(zend_collection_dict_add_func) Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END(); - dict_add_item(Z_OBJ_P(ZEND_THIS), key, value); + zend_collection_add_item(Z_OBJ_P(ZEND_THIS), key, value); } static void zend_collection_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) @@ -208,7 +263,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_GET, zend_collection_seq_get_func, arginfo_class_SeqCollection_get, 1); REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_seq_with_func, arginfo_class_SeqCollection_with, 1); REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_seq_without_func, arginfo_class_SeqCollection_without, 1); - REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_set_func, arginfo_class_SeqCollection_set, 2); + REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_update_func, arginfo_class_SeqCollection_set, 2); break; case ZEND_COLLECTION_DICT: REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_dict_add_func, arginfo_class_DictCollection_add, 2); @@ -251,11 +306,25 @@ static void seq_add_item(zend_object *object, zval *value) create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + SEPARATE_ARRAY(value_prop); Z_ADDREF_P(value); add_next_index_zval(value_prop, value); } -static void dict_add_item(zend_object *object, zval *offset, zval *value) +static void seq_update_item(zend_object *object, zend_long index, zval *value) +{ + zend_class_entry *ce = object->ce; + zval rv; + zval *value_prop; + + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + SEPARATE_ARRAY(value_prop); + Z_ADDREF_P(value); + add_index_zval(value_prop, index, value); +} + +static void dict_add_or_update_item(zend_object *object, zval *offset, zval *value) { zend_class_entry *ce = object->ce; zval rv; @@ -314,7 +383,44 @@ void zend_collection_add_item(zend_object *object, zval *offset, zval *value) seq_add_item(object, value); break; case ZEND_COLLECTION_DICT: - dict_add_item(object, offset, value); + dict_add_or_update_item(object, offset, value); + break; + } +} + +void zend_collection_update_item(zend_object *object, zval *offset, zval *value) +{ + zend_class_entry *ce = object->ce; + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + + if (Z_TYPE_P(offset) != IS_LONG && ce->collection_data_structure == ZEND_COLLECTION_SEQ) { + zend_value_error("Specifying a non-integer offset for sequence collections is not allowed"); + return; + } + + if (!((Z_TYPE_P(offset) == IS_LONG || Z_TYPE_P(offset) == IS_STRING)) && ce->collection_data_structure == ZEND_COLLECTION_DICT) { + zend_value_error("Specifying a non-integer/non-string offset for sequence collections is not allowed"); + return; + } + + + if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { + zend_string *type_str = zend_type_to_string(ce->collection_item_type); + zend_type_error( + "Value type %s does not match collection item type %s", + zend_zval_type_name(value), + ZSTR_VAL(type_str) + ); + zend_string_release(type_str); + return; + } + + switch (ce->collection_data_structure) { + case ZEND_COLLECTION_SEQ: + seq_update_item(object, Z_LVAL_P(offset), value); + break; + case ZEND_COLLECTION_DICT: + dict_add_or_update_item(object, offset, value); break; } } @@ -388,6 +494,7 @@ void zend_collection_unset_item(zend_object *object, zval *offset) } value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + SEPARATE_ARRAY(value_prop); if (Z_TYPE_P(offset) == IS_STRING) { zend_hash_del(HASH_OF(value_prop), Z_STR_P(offset)); From 1ff66ab92576703ebb7cd317d90eb3346877c387 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Wed, 28 Jun 2023 16:47:22 +0100 Subject: [PATCH 27/48] Implement dict 'remove/has/get/update/with/without' functions --- .../collection/collection_dict_modify.phpt | 144 ++++++++++++++++-- Zend/zend_collection.c | 100 ++++++++++++ Zend/zend_collection.stub.php | 6 +- Zend/zend_collection_arginfo.h | 28 +++- 4 files changed, 261 insertions(+), 17 deletions(-) diff --git a/Zend/tests/collection/collection_dict_modify.phpt b/Zend/tests/collection/collection_dict_modify.phpt index 7fcc321ac3d7e..a0af393dca81c 100644 --- a/Zend/tests/collection/collection_dict_modify.phpt +++ b/Zend/tests/collection/collection_dict_modify.phpt @@ -1,7 +1,5 @@ --TEST-- Collection: Dictionary: Modify ---XFAIL-- -Unimplemented --FILE-- add('one', new Book('Title 1')); $c->add('two', new Book('Title 2')); -// Should have 2 items. +echo "\nShould have 2 items:\n"; var_dump($c); $c->remove('one'); -// Should have one item. +echo "\nShould have one item:\n"; var_dump($c); -// Should be false +echo "\nShould be false:\n"; var_dump($c->has('one')); var_dump(isset($c['one'])); -// Should be true +echo "\nShould be true:\n"; var_dump($c->has('two')); var_dump(isset($c['two'])); $c2 = $c->with('three', new Book('Title 3')); -// Still one item. +echo "\nStill one item:\n"; var_dump($c); -// But this has 2 items. +echo "\nBut this has 2 items:\n"; var_dump($c2); $c3 = $c2->without('two'); -// Still two items. +echo "\nStill two items:\n"; var_dump($c2); -// But only one item here. +echo "\nBut only one item here:\n"; var_dump($c3); $c3->set('three', new Book('Title 4')); -// Only Title 4 now exists. +echo "\nOnly Title 4 now exists:\n"; var_dump($c3); unset($c3['three']); -// Empty +echo "\nEmpty:\n"; var_dump($c3); +echo "\nThrows OutOfBoundsException:\n"; +try { + $c3->set(42, new Book('Title 1')); +} catch (OutOfBoundsException $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} ?> --EXPECTF-- +Should have 2 items: +object(Books)#1 (1) { + ["value"]=> + array(2) { + ["one"]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 1" + } + ["two"]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + } +} + +Should have one item: +object(Books)#1 (1) { + ["value"]=> + array(1) { + ["two"]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + } +} + +Should be false: +bool(false) +bool(false) + +Should be true: +bool(true) +bool(true) + +Still one item: +object(Books)#1 (1) { + ["value"]=> + array(1) { + ["two"]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + } +} + +But this has 2 items: +object(Books)#4 (1) { + ["value"]=> + array(2) { + ["two"]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + ["three"]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 3" + } + } +} + +Still two items: +object(Books)#4 (1) { + ["value"]=> + array(2) { + ["two"]=> + object(Book)#3 (1) { + ["title"]=> + string(7) "Title 2" + } + ["three"]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 3" + } + } +} + +But only one item here: +object(Books)#5 (1) { + ["value"]=> + array(1) { + ["three"]=> + object(Book)#2 (1) { + ["title"]=> + string(7) "Title 3" + } + } +} + +Only Title 4 now exists: +object(Books)#5 (1) { + ["value"]=> + array(1) { + ["three"]=> + object(Book)#6 (1) { + ["title"]=> + string(7) "Title 4" + } + } +} + +Empty: +object(Books)#5 (1) { + ["value"]=> + array(0) { + } +} + +Throws OutOfBoundsException: +OutOfBoundsException: Index '42' does not exist in the sequence diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 01a30f7b55c0c..87d964b83b3d3 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -218,6 +218,98 @@ static ZEND_NAMED_FUNCTION(zend_collection_dict_add_func) ZEND_PARSE_PARAMETERS_END(); zend_collection_add_item(Z_OBJ_P(ZEND_THIS), key, value); + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); +} + +static ZEND_NAMED_FUNCTION(zend_collection_dict_remove_func) +{ + zval *index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + zend_collection_unset_item(Z_OBJ_P(ZEND_THIS), index); + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); +} + +static ZEND_NAMED_FUNCTION(zend_collection_dict_has_func) +{ + zval *index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_BOOL(zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)); +} + +static ZEND_NAMED_FUNCTION(zend_collection_dict_get_func) +{ + zval *index, *value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + value = zend_collection_read_item(Z_OBJ_P(ZEND_THIS), index); + + RETURN_COPY_VALUE(value); +} + +static ZEND_NAMED_FUNCTION(zend_collection_dict_with_func) +{ + zval *index, *value; + zend_object *clone; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(index) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + clone = zend_objects_clone_obj(Z_OBJ_P(ZEND_THIS)); + + zend_collection_add_item(clone, index, value); + + RETURN_OBJ(clone); +} + +static ZEND_NAMED_FUNCTION(zend_collection_dict_without_func) +{ + zval *index; + zend_object *clone; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(index) + ZEND_PARSE_PARAMETERS_END(); + + clone = zend_objects_clone_obj(Z_OBJ_P(ZEND_THIS)); + + zend_collection_unset_item(clone, index); + + RETURN_OBJ(clone); +} + +static ZEND_NAMED_FUNCTION(zend_collection_dict_update_func) +{ + zval *index; + zval *value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(index) + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END(); + + if (!zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)) { + zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Index '%ld' does not exist in the sequence", Z_LVAL_P(index)); + return; + } + + zend_collection_update_item(Z_OBJ_P(ZEND_THIS), index, value); + + RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } static void zend_collection_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) @@ -267,6 +359,12 @@ void zend_collection_register_funcs(zend_class_entry *ce) break; case ZEND_COLLECTION_DICT: REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_dict_add_func, arginfo_class_DictCollection_add, 2); + REGISTER_FUNCTION(ZEND_STR_REMOVE, zend_collection_dict_remove_func, arginfo_class_DictCollection_remove, 1); + REGISTER_FUNCTION(ZEND_STR_HAS, zend_collection_dict_has_func, arginfo_class_DictCollection_has, 1); + REGISTER_FUNCTION(ZEND_STR_GET, zend_collection_dict_get_func, arginfo_class_DictCollection_get, 1); + REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_dict_with_func, arginfo_class_DictCollection_with, 2); + REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_dict_without_func, arginfo_class_DictCollection_without, 1); + REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_dict_update_func, arginfo_class_DictCollection_set, 2); break; } } @@ -333,12 +431,14 @@ static void dict_add_or_update_item(zend_object *object, zval *offset, zval *val if (ce->collection_key_type == IS_LONG && Z_TYPE_P(offset) == IS_LONG) { create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + SEPARATE_ARRAY(value_prop); Z_ADDREF_P(value); add_index_zval(value_prop, Z_LVAL_P(offset), value); return; } else if (ce->collection_key_type == IS_STRING && Z_TYPE_P(offset) == IS_STRING) { create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + SEPARATE_ARRAY(value_prop); Z_ADDREF_P(value); add_assoc_zval_ex(value_prop, Z_STRVAL_P(offset), Z_STRLEN_P(offset), value); return; diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index da86eba9c85da..55ac83f0fdab9 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -38,8 +38,8 @@ public function map(callable $fn, string $targetType): SeqCollection; interface DictCollection { - public function add(mixed $key, mixed $value) : void; -/* + public function add(mixed $key, mixed $value) : static; + public function remove(mixed $key) : static; public function has(mixed $key) : bool; @@ -54,7 +54,7 @@ public function without(mixed $key) : static; // returns self. Overwrites existing value if already set. public function set(mixed $key, mixed $value): static; - +/* // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. public function concat(DictCollection $other): static; diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 30f144bab462b..059a6698d2e3d 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 35953abbd50e7a1021de43de279ec0e3882b6c2c */ + * Stub hash: a3806b60e4a436709ef26623ef4f94afb737b90f */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -26,11 +26,29 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_set, 0, 2, I ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DictCollection_add, 0, 2, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DictCollection_add, 0, 2, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DictCollection_remove, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DictCollection_has, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DictCollection_get, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +#define arginfo_class_DictCollection_with arginfo_class_DictCollection_add + +#define arginfo_class_DictCollection_without arginfo_class_DictCollection_remove + +#define arginfo_class_DictCollection_set arginfo_class_DictCollection_add + @@ -48,6 +66,12 @@ static const zend_function_entry class_SeqCollection_methods[] = { static const zend_function_entry class_DictCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, add, arginfo_class_DictCollection_add, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, remove, arginfo_class_DictCollection_remove, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, has, arginfo_class_DictCollection_has, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, get, arginfo_class_DictCollection_get, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, with, arginfo_class_DictCollection_with, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, without, arginfo_class_DictCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, set, arginfo_class_DictCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; From 5128b487c3316f629b5da90366e108ffc4ac77af Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 30 Jun 2023 11:08:49 +0100 Subject: [PATCH 28/48] Better error messages and more tests for unhappy paths --- .../collection/collection_dict_basic.phpt | 2 +- .../collection/collection_dict_modify.phpt | 4 +- .../collection_dict_modify_errors-001.phpt | 59 +++++++ .../collection_dict_modify_errors-002.phpt | 59 +++++++ .../collection_dict_modify_errors-003.phpt | 63 +++++++ .../collection/collection_seq_basic.phpt | 2 +- .../collection/collection_seq_modify.phpt | 2 +- .../collection_seq_modify_errors-001.phpt | 49 ++++++ Zend/zend_collection.c | 166 ++++++++++++------ Zend/zend_collection.stub.php | 2 +- Zend/zend_collection_arginfo.h | 4 +- 11 files changed, 349 insertions(+), 63 deletions(-) create mode 100644 Zend/tests/collection/collection_dict_modify_errors-001.phpt create mode 100644 Zend/tests/collection/collection_dict_modify_errors-002.phpt create mode 100644 Zend/tests/collection/collection_dict_modify_errors-003.phpt create mode 100644 Zend/tests/collection/collection_seq_modify_errors-001.phpt diff --git a/Zend/tests/collection/collection_dict_basic.phpt b/Zend/tests/collection/collection_dict_basic.phpt index 4be8bba163c40..b769d904fa524 100644 --- a/Zend/tests/collection/collection_dict_basic.phpt +++ b/Zend/tests/collection/collection_dict_basic.phpt @@ -64,4 +64,4 @@ object(Article)#%d (%d) { string(10) "First Test" } bool(false) -TypeError: Key type int of element does not match collection key type string +TypeError: Key type int of element does not match Articles dictionary key type string diff --git a/Zend/tests/collection/collection_dict_modify.phpt b/Zend/tests/collection/collection_dict_modify.phpt index a0af393dca81c..58aaa8d3f3142 100644 --- a/Zend/tests/collection/collection_dict_modify.phpt +++ b/Zend/tests/collection/collection_dict_modify.phpt @@ -58,7 +58,7 @@ var_dump($c3); echo "\nThrows OutOfBoundsException:\n"; try { - $c3->set(42, new Book('Title 1')); + $c3->set('fourtytwo', new Book('Title 1')); } catch (OutOfBoundsException $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; } @@ -179,4 +179,4 @@ object(Books)#5 (1) { } Throws OutOfBoundsException: -OutOfBoundsException: Index '42' does not exist in the sequence +OutOfBoundsException: Index 'fourtytwo' does not exist in the Books dictionary diff --git a/Zend/tests/collection/collection_dict_modify_errors-001.phpt b/Zend/tests/collection/collection_dict_modify_errors-001.phpt new file mode 100644 index 0000000000000..e05dc12ae072b --- /dev/null +++ b/Zend/tests/collection/collection_dict_modify_errors-001.phpt @@ -0,0 +1,59 @@ +--TEST-- +Collection: Dictionary: Modify with wrong key type (string) +--FILE-- + Book) {} + +$c = new Books(); + +$c->add('one', new Book('Title 1')); +$c->add('two', new Book('Title 2')); + +try { + $c->remove(1); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->has(1); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + isset($c[1]); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->with(3, new Book('Title 3')); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->without(2); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->set(3, new Book('Title 4')); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Key type int of element does not match Books dictionary key type string +TypeError: Key type int of element does not match Books dictionary key type string +TypeError: Key type int of element does not match Books dictionary key type string +TypeError: Key type int of element does not match Books dictionary key type string +TypeError: Key type int of element does not match Books dictionary key type string +TypeError: Key type int of element does not match Books dictionary key type string diff --git a/Zend/tests/collection/collection_dict_modify_errors-002.phpt b/Zend/tests/collection/collection_dict_modify_errors-002.phpt new file mode 100644 index 0000000000000..b6856bc6cd573 --- /dev/null +++ b/Zend/tests/collection/collection_dict_modify_errors-002.phpt @@ -0,0 +1,59 @@ +--TEST-- +Collection: Dictionary: Modify with wrong key type (int) +--FILE-- + Book) {} + +$c = new Books(); + +$c->add(1, new Book('Title 1')); +$c->add(2, new Book('Title 2')); + +try { + $c->remove('one'); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->has('one'); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + isset($c['one']); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->with('three', new Book('Title 3')); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->without('two'); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->set('three', new Book('Title 4')); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Key type string of element does not match Books dictionary key type int +TypeError: Key type string of element does not match Books dictionary key type int +TypeError: Key type string of element does not match Books dictionary key type int +TypeError: Key type string of element does not match Books dictionary key type int +TypeError: Key type string of element does not match Books dictionary key type int +TypeError: Key type string of element does not match Books dictionary key type int diff --git a/Zend/tests/collection/collection_dict_modify_errors-003.phpt b/Zend/tests/collection/collection_dict_modify_errors-003.phpt new file mode 100644 index 0000000000000..46c5baa2e388e --- /dev/null +++ b/Zend/tests/collection/collection_dict_modify_errors-003.phpt @@ -0,0 +1,63 @@ +--TEST-- +Collection: Dictionary: Modify with unsupported key types +--FILE-- + Book) {} + +$c = new Books(); + +try { + $c->add(M_PI, new Book('Title 1')); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->remove(M_PI); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->has(new Book("This is not a key")); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + isset($c[M_PI]); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->with(false, new Book("Pie is tasty")); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->without(NULL); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->set(M_PI, new Book("Pai e cho blasta")); +} catch (\TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Key type float of element does not match Books dictionary key type string +TypeError: Key type float of element does not match Books dictionary key type string +TypeError: Key type Book of element does not match Books dictionary key type string +TypeError: Key type float of element does not match Books dictionary key type string +TypeError: Key type bool of element does not match Books dictionary key type string +TypeError: Key type null of element does not match Books dictionary key type string +TypeError: Key type float of element does not match Books dictionary key type string diff --git a/Zend/tests/collection/collection_seq_basic.phpt b/Zend/tests/collection/collection_seq_basic.phpt index 3f5c3924607a0..23850abbcd520 100644 --- a/Zend/tests/collection/collection_seq_basic.phpt +++ b/Zend/tests/collection/collection_seq_basic.phpt @@ -65,4 +65,4 @@ object(Article)#%d (%d) { string(11) "Second Test" } bool(false) -TypeError: Key type string of element does not match collection key type int +TypeError: Key type string of element does not match Articles sequence key type int diff --git a/Zend/tests/collection/collection_seq_modify.phpt b/Zend/tests/collection/collection_seq_modify.phpt index ea54aa7167cb2..c3e2473e57a5f 100644 --- a/Zend/tests/collection/collection_seq_modify.phpt +++ b/Zend/tests/collection/collection_seq_modify.phpt @@ -179,4 +179,4 @@ object(Books)#5 (1) { } Throws OutOfBoundsException: -OutOfBoundsException: Index '42' does not exist in the sequence +OutOfBoundsException: Index '42' does not exist in the Books sequence diff --git a/Zend/tests/collection/collection_seq_modify_errors-001.phpt b/Zend/tests/collection/collection_seq_modify_errors-001.phpt new file mode 100644 index 0000000000000..95865b1b2f3e6 --- /dev/null +++ b/Zend/tests/collection/collection_seq_modify_errors-001.phpt @@ -0,0 +1,49 @@ +--TEST-- +Collection: Sequence: Modify with unsupported key types +--FILE-- +remove('zero'); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->has(new Book("Ne pas un cle")); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->get(false); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->without(M_PI); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c->set(null, new Book("A Random Title")); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Key type string of element does not match Books sequence key type int +TypeError: Key type Book of element does not match Books sequence key type int +TypeError: Key type bool of element does not match Books sequence key type int +TypeError: Key type float of element does not match Books sequence key type int +TypeError: Key type null of element does not match Books sequence key type int diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 87d964b83b3d3..cf292ccf4d091 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -100,11 +100,50 @@ void zend_collection_register_handlers(zend_class_entry *ce) } void zend_collection_add_item(zend_object *object, zval *offset, zval *value); -void zend_collection_update_item(zend_object *object, zval *offset, zval *value); +void zend_collection_set_item(zend_object *object, zval *offset, zval *value); int zend_collection_has_item(zend_object *object, zval *offset); zval *zend_collection_read_item(zend_object *object, zval *offset); void zend_collection_unset_item(zend_object *object, zval *offset); +static const char *get_data_structure_name(zend_class_entry *ce) +{ + if (ce->collection_data_structure == ZEND_COLLECTION_SEQ) { + return "sequence"; + } + if (ce->collection_data_structure == ZEND_COLLECTION_DICT) { + return "dictionary"; + } + return "unknown"; +} + +static int key_type_allowed(zend_class_entry *ce, zval *offset) +{ + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); + + if (Z_TYPE_P(offset) != IS_LONG && Z_TYPE_P(offset) != IS_STRING) { + zend_type_error( + "Key type %s is not allowed for %s %s", + ZSTR_VAL(ce->name), + offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), + get_data_structure_name(ce) + ); + } + + if (ce->collection_key_type != Z_TYPE_P(offset)) { + zend_type_error( + "Key type %s of element does not match %s %s key type %s", + offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), + ZSTR_VAL(ce->name), + get_data_structure_name(ce), + zend_get_type_by_const(ce->collection_key_type) + ); + return false; + } + + return true; +} + + static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) { zval *value; @@ -151,6 +190,9 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_get_func) ZEND_PARSE_PARAMETERS_END(); value = zend_collection_read_item(Z_OBJ_P(ZEND_THIS), index); + if (!value) { + return; + } RETURN_COPY_VALUE(value); } @@ -187,7 +229,7 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_without_func) RETURN_OBJ(clone); } -static ZEND_NAMED_FUNCTION(zend_collection_seq_update_func) +static ZEND_NAMED_FUNCTION(zend_collection_seq_set_func) { zval *index; zval *value; @@ -197,12 +239,19 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_update_func) Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END(); - if (!zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)) { - zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Index '%ld' does not exist in the sequence", Z_LVAL_P(index)); + if (!key_type_allowed(Z_OBJCE_P(ZEND_THIS), index)) { return; } - zend_collection_update_item(Z_OBJ_P(ZEND_THIS), index, value); + if (!zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)) { + zend_throw_exception_ex( + spl_ce_OutOfBoundsException, 0, + "Index '%ld' does not exist in the %s sequence", + Z_LVAL_P(index), ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name) + ); + } + + zend_collection_set_item(Z_OBJ_P(ZEND_THIS), index, value); RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } @@ -292,7 +341,7 @@ static ZEND_NAMED_FUNCTION(zend_collection_dict_without_func) RETURN_OBJ(clone); } -static ZEND_NAMED_FUNCTION(zend_collection_dict_update_func) +static ZEND_NAMED_FUNCTION(zend_collection_dict_set_func) { zval *index; zval *value; @@ -302,12 +351,30 @@ static ZEND_NAMED_FUNCTION(zend_collection_dict_update_func) Z_PARAM_ZVAL(value) ZEND_PARSE_PARAMETERS_END(); - if (!zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)) { - zend_throw_exception_ex(spl_ce_OutOfBoundsException, 0, "Index '%ld' does not exist in the sequence", Z_LVAL_P(index)); + if (!key_type_allowed(Z_OBJCE_P(ZEND_THIS), index)) { return; } - zend_collection_update_item(Z_OBJ_P(ZEND_THIS), index, value); + if (!zend_collection_has_item(Z_OBJ_P(ZEND_THIS), index)) { + switch (Z_TYPE_P(index)) { + case IS_LONG: + zend_throw_exception_ex( + spl_ce_OutOfBoundsException, 0, + "Index '%ld' does not exist in the %s dictionary", + Z_LVAL_P(index), ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name) + ); + return; + case IS_STRING: + zend_throw_exception_ex( + spl_ce_OutOfBoundsException, 0, + "Index '%s' does not exist in the %s dictionary", + Z_STRVAL_P(index), ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name) + ); + return; + } + } + + zend_collection_set_item(Z_OBJ_P(ZEND_THIS), index, value); RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } @@ -355,7 +422,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_GET, zend_collection_seq_get_func, arginfo_class_SeqCollection_get, 1); REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_seq_with_func, arginfo_class_SeqCollection_with, 1); REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_seq_without_func, arginfo_class_SeqCollection_without, 1); - REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_update_func, arginfo_class_SeqCollection_set, 2); + REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_set_func, arginfo_class_SeqCollection_set, 2); break; case ZEND_COLLECTION_DICT: REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_dict_add_func, arginfo_class_DictCollection_add, 2); @@ -364,7 +431,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_GET, zend_collection_dict_get_func, arginfo_class_DictCollection_get, 1); REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_dict_with_func, arginfo_class_DictCollection_with, 2); REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_dict_without_func, arginfo_class_DictCollection_without, 1); - REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_dict_update_func, arginfo_class_DictCollection_set, 2); + REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_dict_set_func, arginfo_class_DictCollection_set, 2); break; } } @@ -396,6 +463,7 @@ static void create_array_if_needed(zend_class_entry *ce, zend_object *object) zval_ptr_dtor(&new_array); } + static void seq_add_item(zend_object *object, zval *value) { zend_class_entry *ce = object->ce; @@ -409,7 +477,7 @@ static void seq_add_item(zend_object *object, zval *value) add_next_index_zval(value_prop, value); } -static void seq_update_item(zend_object *object, zend_long index, zval *value) +static void seq_set_item(zend_object *object, zend_long index, zval *value) { zend_class_entry *ce = object->ce; zval rv; @@ -422,32 +490,33 @@ static void seq_update_item(zend_object *object, zend_long index, zval *value) add_index_zval(value_prop, index, value); } -static void dict_add_or_update_item(zend_object *object, zval *offset, zval *value) +static void dict_add_or_set_item(zend_object *object, zval *offset, zval *value) { zend_class_entry *ce = object->ce; zval rv; zval *value_prop; - if (ce->collection_key_type == IS_LONG && Z_TYPE_P(offset) == IS_LONG) { - create_array_if_needed(ce, object); - value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); - SEPARATE_ARRAY(value_prop); - Z_ADDREF_P(value); - add_index_zval(value_prop, Z_LVAL_P(offset), value); - return; - } else if (ce->collection_key_type == IS_STRING && Z_TYPE_P(offset) == IS_STRING) { - create_array_if_needed(ce, object); - value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); - SEPARATE_ARRAY(value_prop); - Z_ADDREF_P(value); - add_assoc_zval_ex(value_prop, Z_STRVAL_P(offset), Z_STRLEN_P(offset), value); + if (!key_type_allowed(ce, offset)) { return; - } else { - zend_type_error( - "Key type %s of element does not match collection key type %s", - offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), - zend_get_type_by_const(ce->collection_key_type) - ); + } + + switch (ce->collection_key_type) { + case IS_LONG: { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + SEPARATE_ARRAY(value_prop); + Z_ADDREF_P(value); + add_index_zval(value_prop, Z_LVAL_P(offset), value); + break; + } + case IS_STRING: { + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); + SEPARATE_ARRAY(value_prop); + Z_ADDREF_P(value); + add_assoc_zval_ex(value_prop, Z_STRVAL_P(offset), Z_STRLEN_P(offset), value); + break; + } } } @@ -470,7 +539,8 @@ void zend_collection_add_item(zend_object *object, zval *offset, zval *value) if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { zend_string *type_str = zend_type_to_string(ce->collection_item_type); zend_type_error( - "Value type %s does not match collection item type %s", + "Value type %s does not match %s collection item type %s", + ZSTR_VAL(ce->name), zend_zval_type_name(value), ZSTR_VAL(type_str) ); @@ -483,12 +553,12 @@ void zend_collection_add_item(zend_object *object, zval *offset, zval *value) seq_add_item(object, value); break; case ZEND_COLLECTION_DICT: - dict_add_or_update_item(object, offset, value); + dict_add_or_set_item(object, offset, value); break; } } -void zend_collection_update_item(zend_object *object, zval *offset, zval *value) +void zend_collection_set_item(zend_object *object, zval *offset, zval *value) { zend_class_entry *ce = object->ce; ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); @@ -503,11 +573,11 @@ void zend_collection_update_item(zend_object *object, zval *offset, zval *value) return; } - if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { zend_string *type_str = zend_type_to_string(ce->collection_item_type); zend_type_error( - "Value type %s does not match collection item type %s", + "Value type %s does not match %s collection item type %s", + ZSTR_VAL(ce->name), zend_zval_type_name(value), ZSTR_VAL(type_str) ); @@ -515,32 +585,18 @@ void zend_collection_update_item(zend_object *object, zval *offset, zval *value) return; } + + switch (ce->collection_data_structure) { case ZEND_COLLECTION_SEQ: - seq_update_item(object, Z_LVAL_P(offset), value); + seq_set_item(object, Z_LVAL_P(offset), value); break; case ZEND_COLLECTION_DICT: - dict_add_or_update_item(object, offset, value); + dict_add_or_set_item(object, offset, value); break; } } -static int key_type_allowed(zend_class_entry *ce, zval *offset) -{ - ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); - - if (ce->collection_key_type != Z_TYPE_P(offset)) { - zend_type_error( - "Key type %s of element does not match collection key type %s", - offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL), - zend_get_type_by_const(ce->collection_key_type) - ); - return false; - } - - return true; -} - int zend_collection_has_item(zend_object *object, zval *offset) { zval rv; diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 55ac83f0fdab9..ab3acf981d57b 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -10,7 +10,7 @@ public function add(mixed $value) : static; public function remove(int $index) : static; - public function has(int $value) : bool; + public function has(int $index) : bool; public function get(int $index) : mixed; diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 059a6698d2e3d..067425efa1789 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a3806b60e4a436709ef26623ef4f94afb737b90f */ + * Stub hash: cfdc327d33b1231179334dc63c0d5f3aad76c967 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -10,7 +10,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_remove, 0, 1 ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_has, 0, 1, _IS_BOOL, 0) - ZEND_ARG_TYPE_INFO(0, value, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, index, IS_LONG, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_get, 0, 1, IS_MIXED, 0) From ad6cbfc6d3323c94061cae64d32eef9e0069edbc Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 30 Jun 2023 15:19:07 +0100 Subject: [PATCH 29/48] Change syntax to: collection(Variant) name< T>> --- .../tests/collection/collection_dict_basic.phpt | 2 +- .../collection/collection_dict_concat.phpt | 2 +- .../collection/collection_dict_equality.phpt | 2 +- Zend/tests/collection/collection_dict_map.phpt | 4 ++-- .../collection/collection_dict_modify.phpt | 2 +- .../collection_dict_modify_errors-001.phpt | 2 +- .../collection_dict_modify_errors-002.phpt | 2 +- .../collection_dict_modify_errors-003.phpt | 2 +- .../collection_dict_syntax_errors.phpt | 2 +- .../collection/collection_dict_with_trait.phpt | 2 +- .../collection_interface_type_check.phpt | 4 ++-- Zend/tests/collection/collection_seq_basic.phpt | 2 +- .../tests/collection/collection_seq_concat.phpt | 2 +- .../collection/collection_seq_equality.phpt | 2 +- .../tests/collection/collection_seq_errors.phpt | 2 +- Zend/tests/collection/collection_seq_map.phpt | 6 +++--- .../tests/collection/collection_seq_modify.phpt | 2 +- .../collection_seq_modify_errors-001.phpt | 2 +- .../collection_seq_syntax_errors.phpt | 2 +- ...n_syntax.phpt => collection_syntax-001.phpt} | 2 +- .../tests/collection/collection_syntax-002.phpt | 17 +++++++++++++++++ .../collection/collection_syntax_errors.phpt | 2 +- .../collection_syntax_unknown_collection.phpt | 2 +- Zend/zend_language_parser.y | 2 +- 24 files changed, 44 insertions(+), 27 deletions(-) rename Zend/tests/collection/{collection_syntax.phpt => collection_syntax-001.phpt} (78%) create mode 100644 Zend/tests/collection/collection_syntax-002.phpt diff --git a/Zend/tests/collection/collection_dict_basic.phpt b/Zend/tests/collection/collection_dict_basic.phpt index b769d904fa524..55c77d25c1225 100644 --- a/Zend/tests/collection/collection_dict_basic.phpt +++ b/Zend/tests/collection/collection_dict_basic.phpt @@ -9,7 +9,7 @@ class Article } } -collection(Dict) Articles(string => Article) +collection(Dict) Articles< Article>> { } diff --git a/Zend/tests/collection/collection_dict_concat.phpt b/Zend/tests/collection/collection_dict_concat.phpt index 62ec0bca96490..22b933a94fca8 100644 --- a/Zend/tests/collection/collection_dict_concat.phpt +++ b/Zend/tests/collection/collection_dict_concat.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books(string => Book) {} +collection(Dict) Books< Book>> {} $c1 = new Books(); $c2 = new Books(); diff --git a/Zend/tests/collection/collection_dict_equality.phpt b/Zend/tests/collection/collection_dict_equality.phpt index 62f9a72f8a8b2..aec347c478a62 100644 --- a/Zend/tests/collection/collection_dict_equality.phpt +++ b/Zend/tests/collection/collection_dict_equality.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books(string => Book) {} +collection(Dict) Books< Book>> {} $c1 = new Books(); $c2 = new Books(); diff --git a/Zend/tests/collection/collection_dict_map.phpt b/Zend/tests/collection/collection_dict_map.phpt index 9fa801ec768c6..e64f75fd0b3c0 100644 --- a/Zend/tests/collection/collection_dict_map.phpt +++ b/Zend/tests/collection/collection_dict_map.phpt @@ -9,9 +9,9 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books(string => Book) {} +collection(Dict) Books< Book>> {} -collection(Dict) Titles(string => string) {} +collection(Dict) Titles< string>> {} $c1 = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify.phpt b/Zend/tests/collection/collection_dict_modify.phpt index 58aaa8d3f3142..2b17d4459b6d3 100644 --- a/Zend/tests/collection/collection_dict_modify.phpt +++ b/Zend/tests/collection/collection_dict_modify.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books(string => Book) {} +collection(Dict) Books< Book>> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify_errors-001.phpt b/Zend/tests/collection/collection_dict_modify_errors-001.phpt index e05dc12ae072b..3839d739fe0bd 100644 --- a/Zend/tests/collection/collection_dict_modify_errors-001.phpt +++ b/Zend/tests/collection/collection_dict_modify_errors-001.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books(string => Book) {} +collection(Dict) Books< Book>> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify_errors-002.phpt b/Zend/tests/collection/collection_dict_modify_errors-002.phpt index b6856bc6cd573..d4d55a82c4db9 100644 --- a/Zend/tests/collection/collection_dict_modify_errors-002.phpt +++ b/Zend/tests/collection/collection_dict_modify_errors-002.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books(int => Book) {} +collection(Dict) Books< Book>> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify_errors-003.phpt b/Zend/tests/collection/collection_dict_modify_errors-003.phpt index 46c5baa2e388e..8d5181285cbaa 100644 --- a/Zend/tests/collection/collection_dict_modify_errors-003.phpt +++ b/Zend/tests/collection/collection_dict_modify_errors-003.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books(string => Book) {} +collection(Dict) Books< Book>> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_syntax_errors.phpt b/Zend/tests/collection/collection_dict_syntax_errors.phpt index 8ba1a79cf90d8..f7acc8f9a51a0 100644 --- a/Zend/tests/collection/collection_dict_syntax_errors.phpt +++ b/Zend/tests/collection/collection_dict_syntax_errors.phpt @@ -2,7 +2,7 @@ Collection: Dictionary without key type defined --FILE-- > { } ?> diff --git a/Zend/tests/collection/collection_dict_with_trait.phpt b/Zend/tests/collection/collection_dict_with_trait.phpt index c28df949f6c13..1cb3d7bae4166 100644 --- a/Zend/tests/collection/collection_dict_with_trait.phpt +++ b/Zend/tests/collection/collection_dict_with_trait.phpt @@ -14,7 +14,7 @@ trait Shuffler } -collection(Dict) Articles(int => Article) +collection(Dict) Articles< Article>> { use Shuffler; } diff --git a/Zend/tests/collection/collection_interface_type_check.phpt b/Zend/tests/collection/collection_interface_type_check.phpt index 4fd5f1224108e..1e06323a799cd 100644 --- a/Zend/tests/collection/collection_interface_type_check.phpt +++ b/Zend/tests/collection/collection_interface_type_check.phpt @@ -2,11 +2,11 @@ Collection: Type checking against interface --FILE-- Article) +collection(Dict) Articles< Article>> { } -collection(Seq) ArticleList(Article) +collection(Seq) ArticleList<
> { } diff --git a/Zend/tests/collection/collection_seq_basic.phpt b/Zend/tests/collection/collection_seq_basic.phpt index 23850abbcd520..83f534d292723 100644 --- a/Zend/tests/collection/collection_seq_basic.phpt +++ b/Zend/tests/collection/collection_seq_basic.phpt @@ -9,7 +9,7 @@ class Article } } -collection(Seq) Articles(Article) +collection(Seq) Articles<
> { } diff --git a/Zend/tests/collection/collection_seq_concat.phpt b/Zend/tests/collection/collection_seq_concat.phpt index 1bfa4aed50fad..681d33a9f2600 100644 --- a/Zend/tests/collection/collection_seq_concat.phpt +++ b/Zend/tests/collection/collection_seq_concat.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books(Book) {} +collection(Seq) Books<> {} $c1 = new Books(); $c2 = new Books(); diff --git a/Zend/tests/collection/collection_seq_equality.phpt b/Zend/tests/collection/collection_seq_equality.phpt index 2a67f267119fe..14c1e1345fcc5 100644 --- a/Zend/tests/collection/collection_seq_equality.phpt +++ b/Zend/tests/collection/collection_seq_equality.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books(Book) {} +collection(Seq) Books<> {} $c1 = new Books(); diff --git a/Zend/tests/collection/collection_seq_errors.phpt b/Zend/tests/collection/collection_seq_errors.phpt index 65537a0d8de3c..0005d9e8cd082 100644 --- a/Zend/tests/collection/collection_seq_errors.phpt +++ b/Zend/tests/collection/collection_seq_errors.phpt @@ -9,7 +9,7 @@ class Article } } -collection(Seq) Articles(Article) +collection(Seq) Articles<
> { } diff --git a/Zend/tests/collection/collection_seq_map.phpt b/Zend/tests/collection/collection_seq_map.phpt index 7d36138ca1c2b..3b9e54aa76d67 100644 --- a/Zend/tests/collection/collection_seq_map.phpt +++ b/Zend/tests/collection/collection_seq_map.phpt @@ -9,16 +9,16 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books(Book) {} +collection(Seq) Books<> {} -collection(Seq) Titles(string) {} +collection(Seq) Titles<> {} $c1 = new Books(); $c1->add(new Book('Title 1')); $c1->add(new Book('Title 2')); -$c2 = $c1->map(fn(Book $b): string => $b->title), Titles::class); +$c2 = $c1->map(fn(Book $b): string => $b->title, Titles::class); // True var_dump($c2 instanceof Titles); diff --git a/Zend/tests/collection/collection_seq_modify.phpt b/Zend/tests/collection/collection_seq_modify.phpt index c3e2473e57a5f..afc8e9b3ec3a1 100644 --- a/Zend/tests/collection/collection_seq_modify.phpt +++ b/Zend/tests/collection/collection_seq_modify.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books(Book) {} +collection(Seq) Books<> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_seq_modify_errors-001.phpt b/Zend/tests/collection/collection_seq_modify_errors-001.phpt index 95865b1b2f3e6..1dcb03bed0873 100644 --- a/Zend/tests/collection/collection_seq_modify_errors-001.phpt +++ b/Zend/tests/collection/collection_seq_modify_errors-001.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books(Book) {} +collection(Seq) Books<> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_seq_syntax_errors.phpt b/Zend/tests/collection/collection_seq_syntax_errors.phpt index e8eff1d5b2f34..2397f8ad51737 100644 --- a/Zend/tests/collection/collection_seq_syntax_errors.phpt +++ b/Zend/tests/collection/collection_seq_syntax_errors.phpt @@ -2,7 +2,7 @@ Collection: Sequence with key type defined --FILE-- Article) +collection(Seq) Articles< Article>> { } ?> diff --git a/Zend/tests/collection/collection_syntax.phpt b/Zend/tests/collection/collection_syntax-001.phpt similarity index 78% rename from Zend/tests/collection/collection_syntax.phpt rename to Zend/tests/collection/collection_syntax-001.phpt index eb6eb922e1aec..74121895669c1 100644 --- a/Zend/tests/collection/collection_syntax.phpt +++ b/Zend/tests/collection/collection_syntax-001.phpt @@ -2,7 +2,7 @@ Collection: Syntax --FILE-- Article) +collection(Dict) Articles< Article>> { } diff --git a/Zend/tests/collection/collection_syntax-002.phpt b/Zend/tests/collection/collection_syntax-002.phpt new file mode 100644 index 0000000000000..a8c53da0f2a64 --- /dev/null +++ b/Zend/tests/collection/collection_syntax-002.phpt @@ -0,0 +1,17 @@ +--TEST-- +Collection: Sequence syntax with scalars +--FILE-- +> +{ +} + +$c = new Titles; + +var_dump($c); +?> +--EXPECTF-- +object(Titles)#%d (%d) { + ["value"]=> + uninitialized(array) +} diff --git a/Zend/tests/collection/collection_syntax_errors.phpt b/Zend/tests/collection/collection_syntax_errors.phpt index 57c2464feee4e..447b74197c786 100644 --- a/Zend/tests/collection/collection_syntax_errors.phpt +++ b/Zend/tests/collection/collection_syntax_errors.phpt @@ -2,7 +2,7 @@ Collection: Syntax Errors --FILE-- Article => three) +collection(Dict) Articles< Article => three>> { } ?> diff --git a/Zend/tests/collection/collection_syntax_unknown_collection.phpt b/Zend/tests/collection/collection_syntax_unknown_collection.phpt index 1be49d31a2848..4975624497c8c 100644 --- a/Zend/tests/collection/collection_syntax_unknown_collection.phpt +++ b/Zend/tests/collection/collection_syntax_unknown_collection.phpt @@ -2,7 +2,7 @@ Collection with unsupported structure type --FILE-- Article) +collection(Foobar) Articles< Article>> { } ?> diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 8fbfccee9e2bc..eface4c9c504f 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -662,7 +662,7 @@ enum_case_expr: collection_declaration_statement: T_COLLECTION { $$ = CG(zend_lineno); } '(' T_STRING ')' - T_STRING '(' collection_type_list ')' backup_doc_comment '{' class_statement_list '}' + T_STRING T_SL collection_type_list T_SR backup_doc_comment '{' class_statement_list '}' { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $10, zend_ast_get_str($6), NULL, $4, $12, NULL, $8); } ; From 41eabbae01d669c6ef04704b89ec988d5a877006 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 11 Jul 2023 11:38:57 +0100 Subject: [PATCH 30/48] Use two seperate tokens so we can have a 'Collection' class again --- .../collection/collection_syntax-001.phpt | 13 +++++-- .../collection/collection_syntax-003.phpt | 20 +++++++++++ .../collection_syntax_unknown_collection.phpt | 2 +- Zend/zend_compile.c | 35 ++++++++++--------- Zend/zend_language_parser.y | 11 +++--- Zend/zend_language_scanner.l | 27 ++++++++++++-- 6 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 Zend/tests/collection/collection_syntax-003.phpt diff --git a/Zend/tests/collection/collection_syntax-001.phpt b/Zend/tests/collection/collection_syntax-001.phpt index 74121895669c1..05f52627d6324 100644 --- a/Zend/tests/collection/collection_syntax-001.phpt +++ b/Zend/tests/collection/collection_syntax-001.phpt @@ -6,12 +6,21 @@ collection(Dict) Articles< Article>> { } -$c = new Articles; +CoLleCtION(dICT) Books< Book>> +{ +} + +$a = new Articles; +$b = new Books; -var_dump($c); +var_dump($a, $b); ?> --EXPECTF-- object(Articles)#%d (%d) { ["value"]=> uninitialized(array) } +object(Books)#%d (%d) { + ["value"]=> + uninitialized(array) +} diff --git a/Zend/tests/collection/collection_syntax-003.phpt b/Zend/tests/collection/collection_syntax-003.phpt new file mode 100644 index 0000000000000..2fe76b66b2711 --- /dev/null +++ b/Zend/tests/collection/collection_syntax-003.phpt @@ -0,0 +1,20 @@ +--TEST-- +Collection: Syntax with 'Collection' class +--FILE-- +> +{ +} + +$a = new Articles; + +var_dump($a); +?> +--EXPECTF-- +object(Articles)#%d (%d) { + ["value"]=> + uninitialized(array) +} diff --git a/Zend/tests/collection/collection_syntax_unknown_collection.phpt b/Zend/tests/collection/collection_syntax_unknown_collection.phpt index 4975624497c8c..dd9fa2a398d13 100644 --- a/Zend/tests/collection/collection_syntax_unknown_collection.phpt +++ b/Zend/tests/collection/collection_syntax_unknown_collection.phpt @@ -7,4 +7,4 @@ collection(Foobar) Articles< Article>> } ?> --EXPECTF-- -Fatal error: Collection data structure must be Seq or Dict, Foobar given in %s on line %d +Parse error: syntax error, unexpected identifier "Articles" in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d72a7b490499f..1d6e02923e57f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8950,23 +8950,26 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ static void zend_compile_collection_data_structure(zend_class_entry *ce, zend_ast *collection_data_structure_ast) { - zend_string *struct_type = zend_ast_get_str(collection_data_structure_ast); + zval *struct_type = zend_ast_get_zval(collection_data_structure_ast); - if (zend_string_equals_literal_ci(struct_type, "seq")) { - if (ce->collection_key_type != IS_UNDEF) { - zend_error_noreturn(E_COMPILE_ERROR, "Collection sequences may not have a key type defined"); - } - ce->collection_key_type = IS_LONG; - ce->collection_data_structure = ZEND_COLLECTION_SEQ; - } else if (zend_string_equals_literal_ci(struct_type, "dict")) { - if (ce->collection_key_type == IS_UNDEF) { - zend_error_noreturn(E_COMPILE_ERROR, "Collection dictionaries must have a key type defined"); - } - ce->collection_data_structure = ZEND_COLLECTION_DICT; - } else { - zend_error_noreturn(E_COMPILE_ERROR, - "Collection data structure must be Seq or Dict, %s given", - ZSTR_VAL(struct_type)); + ZEND_ASSERT(Z_TYPE_P(struct_type) == IS_LONG); + ZEND_ASSERT(Z_LVAL_P(struct_type) == ZEND_COLLECTION_SEQ || Z_LVAL_P(struct_type) == ZEND_COLLECTION_DICT); + + switch (Z_LVAL_P(struct_type)) { + case ZEND_COLLECTION_SEQ: + if (ce->collection_key_type != IS_UNDEF) { + zend_error_noreturn(E_COMPILE_ERROR, "Collection sequences may not have a key type defined"); + } + ce->collection_key_type = IS_LONG; + ce->collection_data_structure = ZEND_COLLECTION_SEQ; + break; + + case ZEND_COLLECTION_DICT: + if (ce->collection_key_type == IS_UNDEF) { + zend_error_noreturn(E_COMPILE_ERROR, "Collection dictionaries must have a key type defined"); + } + ce->collection_data_structure = ZEND_COLLECTION_DICT; + break; } } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index eface4c9c504f..6981d669ded93 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -164,7 +164,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_TRAIT "'trait'" %token T_INTERFACE "'interface'" %token T_ENUM "'enum'" -%token T_COLLECTION "'collection'" +%token T_COLLECTION_SEQ "'collection(Seq)'" +%token T_COLLECTION_DICT "'collection(Dict)'" %token T_EXTENDS "'extends'" %token T_IMPLEMENTS "'implements'" %token T_NAMESPACE "'namespace'" @@ -660,10 +661,12 @@ enum_case_expr: ; collection_declaration_statement: - T_COLLECTION { $$ = CG(zend_lineno); } - '(' T_STRING ')' + T_COLLECTION_SEQ { $$ = CG(zend_lineno); } T_STRING T_SL collection_type_list T_SR backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $10, zend_ast_get_str($6), NULL, $4, $12, NULL, $8); } + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $7, zend_ast_get_str($3), NULL, zend_ast_create_zval_from_long(ZEND_COLLECTION_SEQ), $9, NULL, $5); } + | T_COLLECTION_DICT { $$ = CG(zend_lineno); } + T_STRING T_SL collection_type_list T_SR backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $7, zend_ast_get_str($3), NULL, zend_ast_create_zval_from_long(ZEND_COLLECTION_DICT), $9, NULL, $5); } ; collection_type_list: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 529838c436121..b751c4a4d8316 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1562,9 +1562,32 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_ENUM); } -"collection" { - RETURN_TOKEN_WITH_IDENT(T_COLLECTION); +/* + * The collection(Seq) keyword must be followed by whitespace and another identifier. + * This avoids the BC break of using 'collection' in classes, namespaces, functions and constants. + */ +"collection(Seq)"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { + yyless(15); + RETURN_TOKEN_WITH_STR(T_STRING, 0); } +"collection(Seq)"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { + yyless(15); + RETURN_TOKEN_WITH_IDENT(T_COLLECTION_SEQ); +} + +/* + * The collection(Dict) keyword must be followed by whitespace and another identifier. + * This avoids the BC break of using 'collection' in classes, namespaces, functions and constants. + */ +"collection(Dict)"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { + yyless(16); + RETURN_TOKEN_WITH_STR(T_STRING, 0); +} +"collection(Dict)"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { + yyless(16); + RETURN_TOKEN_WITH_IDENT(T_COLLECTION_DICT); +} + "extends" { RETURN_TOKEN_WITH_IDENT(T_EXTENDS); From 9ef908ec92b02fb1f6fcdfcf8a2290e29afaae9e Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 17 Jul 2023 16:47:07 +0100 Subject: [PATCH 31/48] Implement Sequence::map() --- Zend/tests/collection/collection_seq_map.phpt | 12 ++- .../collection/collection_seq_map_error.phpt | 46 ++++++++++ Zend/zend_collection.c | 84 +++++++++++++++++-- Zend/zend_collection.stub.php | 5 +- Zend/zend_collection_arginfo.h | 8 +- Zend/zend_string.h | 1 + 6 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 Zend/tests/collection/collection_seq_map_error.phpt diff --git a/Zend/tests/collection/collection_seq_map.phpt b/Zend/tests/collection/collection_seq_map.phpt index 3b9e54aa76d67..2327c464ab6ca 100644 --- a/Zend/tests/collection/collection_seq_map.phpt +++ b/Zend/tests/collection/collection_seq_map.phpt @@ -1,7 +1,5 @@ --TEST-- Collection: Sequence: map ---XFAIL-- -Unimplemented --FILE-- --EXPECTF-- +bool(true) +object(Titles)#5 (1) { + ["value"]=> + array(2) { + [0]=> + string(7) "Title 1" + [1]=> + string(7) "Title 2" + } +} diff --git a/Zend/tests/collection/collection_seq_map_error.phpt b/Zend/tests/collection/collection_seq_map_error.phpt new file mode 100644 index 0000000000000..e1eb5cd7ff8b0 --- /dev/null +++ b/Zend/tests/collection/collection_seq_map_error.phpt @@ -0,0 +1,46 @@ +--TEST-- +Collection: Sequence: map errors +--FILE-- +> {} + +collection(Seq) Books<> {} + +$c1 = new Books(); + +$c1->add(new Book('Title 1', 1978)); +$c1->add(new Book('Title 2', 2023)); + +try { + $c2 = $c1->map(fn(Book $b): string => $b->title, Unknown::class); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c2 = $c1->map(fn(Book $b): string => $b->title, NormalTitle::class); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c2 = $c1->map(fn(Book $b): int => $b->copyright, Titles::class); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Type 'Unknown' can not be fetched +TypeError: Type 'NormalTitle' must implement SeqCollection interface +TypeError: Value type Titles does not match int collection item type string diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index cf292ccf4d091..7fa1ce23d4312 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -99,12 +99,14 @@ void zend_collection_register_handlers(zend_class_entry *ce) ce->default_object_handlers = &zend_dict_collection_object_handlers; } -void zend_collection_add_item(zend_object *object, zval *offset, zval *value); +bool zend_collection_add_item(zend_object *object, zval *offset, zval *value); void zend_collection_set_item(zend_object *object, zval *offset, zval *value); int zend_collection_has_item(zend_object *object, zval *offset); zval *zend_collection_read_item(zend_object *object, zval *offset); void zend_collection_unset_item(zend_object *object, zval *offset); +static void create_array_if_needed(zend_class_entry *ce, zend_object *object); + static const char *get_data_structure_name(zend_class_entry *ce) { if (ce->collection_data_structure == ZEND_COLLECTION_SEQ) { @@ -256,6 +258,69 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_set_func) RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } +static ZEND_NAMED_FUNCTION(zend_collection_seq_map_func) +{ + zend_object *object = Z_OBJ_P(ZEND_THIS); + zend_class_entry *ce = Z_OBJCE_P(ZEND_THIS); + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fci_cache = empty_fcall_info_cache; + zval *value_prop; + zend_string *type; + zval *operand; + zval retval; + zval args[1]; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_FUNC(fci, fci_cache) + Z_PARAM_STR(type) + ZEND_PARSE_PARAMETERS_END(); + + zend_class_entry *return_ce = zend_lookup_class(type); + if (!return_ce) { + zend_type_error( + "Type '%s' can not be fetched", + ZSTR_VAL(type) + ); + return; + } + + if (!instanceof_function(return_ce, zend_ce_seq_collection)) { + zend_type_error( + "Type '%s' must implement SeqCollection interface", + ZSTR_VAL(type) + ); + return; + } + + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + object_init_ex(return_value, return_ce); + Z_OBJCE_P(return_value)->collection_data_structure = return_ce->collection_data_structure; + + fci.retval = &retval; + fci.param_count = 1; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value_prop), operand) { + ZVAL_COPY(&args[0], operand); + + fci.params = args; + + if (zend_call_function(&fci, &fci_cache) == SUCCESS) { + zval_ptr_dtor(&args[0]); + + if (!zend_collection_add_item(Z_OBJ_P(return_value), NULL, &retval)) { + return; + } + } else { + zval_ptr_dtor(&args[0]); + + return; + } + + } ZEND_HASH_FOREACH_END(); +} + static ZEND_NAMED_FUNCTION(zend_collection_dict_add_func) { @@ -423,6 +488,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_seq_with_func, arginfo_class_SeqCollection_with, 1); REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_seq_without_func, arginfo_class_SeqCollection_without, 1); REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_set_func, arginfo_class_SeqCollection_set, 2); + REGISTER_FUNCTION(ZEND_STR_MAP, zend_collection_seq_map_func, arginfo_class_SeqCollection_map, 2); break; case ZEND_COLLECTION_DICT: REGISTER_FUNCTION(ZEND_STR_ADD, zend_collection_dict_add_func, arginfo_class_DictCollection_add, 2); @@ -473,7 +539,7 @@ static void seq_add_item(zend_object *object, zval *value) create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); SEPARATE_ARRAY(value_prop); - Z_ADDREF_P(value); + zval_add_ref(value); add_next_index_zval(value_prop, value); } @@ -486,7 +552,7 @@ static void seq_set_item(zend_object *object, zend_long index, zval *value) create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); SEPARATE_ARRAY(value_prop); - Z_ADDREF_P(value); + zval_add_ref(value); add_index_zval(value_prop, index, value); } @@ -521,22 +587,22 @@ static void dict_add_or_set_item(zend_object *object, zval *offset, zval *value) } -void zend_collection_add_item(zend_object *object, zval *offset, zval *value) +bool zend_collection_add_item(zend_object *object, zval *offset, zval *value) { zend_class_entry *ce = object->ce; ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); if (offset && ce->collection_data_structure == ZEND_COLLECTION_SEQ) { zend_value_error("Specifying an offset for sequence collections is not allowed"); - return; + return false; } if (!offset && ce->collection_data_structure == ZEND_COLLECTION_DICT) { zend_value_error("Specifying an offset for dictionary collections is required"); - return; + return false; } - if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) { + if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, true, true)) { zend_string *type_str = zend_type_to_string(ce->collection_item_type); zend_type_error( "Value type %s does not match %s collection item type %s", @@ -545,7 +611,7 @@ void zend_collection_add_item(zend_object *object, zval *offset, zval *value) ZSTR_VAL(type_str) ); zend_string_release(type_str); - return; + return false; } switch (ce->collection_data_structure) { @@ -556,6 +622,8 @@ void zend_collection_add_item(zend_object *object, zval *offset, zval *value) dict_add_or_set_item(object, offset, value); break; } + + return true; } void zend_collection_set_item(zend_object *object, zval *offset, zval *value) diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index ab3acf981d57b..ecf534fa07d18 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -29,11 +29,10 @@ public function concat(SeqCollection $other): static; // True if both seqs have the same values in the same order. public function equals(SeqCollection $other): bool; - +*/ // $fn is callable(mixed $val) // The return type is $targetType, but that can't be expressed statically. - public function map(callable $fn, string $targetType): SeqCollection; -*/ + public function map(callable $fn, string $targetType): object; } interface DictCollection diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 067425efa1789..f04b974ce5c42 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: cfdc327d33b1231179334dc63c0d5f3aad76c967 */ + * Stub hash: 100f44877eeb98465fc33afb33a69eea82119702 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -26,6 +26,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_set, 0, 2, I ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_map, 0, 2, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, fn, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO(0, targetType, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DictCollection_add, 0, 2, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, key, IS_MIXED, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -60,6 +65,7 @@ static const zend_function_entry class_SeqCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, with, arginfo_class_SeqCollection_with, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, without, arginfo_class_SeqCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, set, arginfo_class_SeqCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, map, arginfo_class_SeqCollection_map, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; diff --git a/Zend/zend_string.h b/Zend/zend_string.h index d52b41463b988..93c8f2aaa6ca0 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -640,6 +640,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_SET, "set") \ _(ZEND_STR_WITH, "with") \ _(ZEND_STR_WITHOUT, "without") \ + _(ZEND_STR_MAP, "map") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED, "Deprecated") \ _(ZEND_STR_SINCE, "since") \ From cf2b4c9c2e00c8c70ce7905e9f8cda148130ed05 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 17 Jul 2023 16:58:37 +0100 Subject: [PATCH 32/48] Change syntax to: collection(Variant) name T> --- Zend/tests/collection/collection_dict_basic.phpt | 2 +- Zend/tests/collection/collection_dict_concat.phpt | 2 +- Zend/tests/collection/collection_dict_equality.phpt | 2 +- Zend/tests/collection/collection_dict_map.phpt | 4 ++-- Zend/tests/collection/collection_dict_modify.phpt | 2 +- Zend/tests/collection/collection_dict_modify_errors-001.phpt | 2 +- Zend/tests/collection/collection_dict_modify_errors-002.phpt | 2 +- Zend/tests/collection/collection_dict_modify_errors-003.phpt | 2 +- Zend/tests/collection/collection_dict_syntax_errors.phpt | 2 +- Zend/tests/collection/collection_dict_with_trait.phpt | 2 +- Zend/tests/collection/collection_interface_type_check.phpt | 4 ++-- Zend/tests/collection/collection_seq_basic.phpt | 2 +- Zend/tests/collection/collection_seq_concat.phpt | 2 +- Zend/tests/collection/collection_seq_equality.phpt | 2 +- Zend/tests/collection/collection_seq_errors.phpt | 2 +- Zend/tests/collection/collection_seq_map.phpt | 4 ++-- Zend/tests/collection/collection_seq_map_error.phpt | 4 ++-- Zend/tests/collection/collection_seq_modify.phpt | 2 +- Zend/tests/collection/collection_seq_modify_errors-001.phpt | 2 +- Zend/tests/collection/collection_seq_syntax_errors.phpt | 2 +- Zend/tests/collection/collection_syntax-001.phpt | 4 ++-- Zend/tests/collection/collection_syntax-002.phpt | 2 +- Zend/tests/collection/collection_syntax-003.phpt | 2 +- Zend/tests/collection/collection_syntax_errors.phpt | 2 +- .../collection/collection_syntax_unknown_collection.phpt | 2 +- Zend/zend_language_parser.y | 4 ++-- 26 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Zend/tests/collection/collection_dict_basic.phpt b/Zend/tests/collection/collection_dict_basic.phpt index 55c77d25c1225..5b7845e1ff833 100644 --- a/Zend/tests/collection/collection_dict_basic.phpt +++ b/Zend/tests/collection/collection_dict_basic.phpt @@ -9,7 +9,7 @@ class Article } } -collection(Dict) Articles< Article>> +collection(Dict) Articles Article> { } diff --git a/Zend/tests/collection/collection_dict_concat.phpt b/Zend/tests/collection/collection_dict_concat.phpt index 22b933a94fca8..4e901778aff37 100644 --- a/Zend/tests/collection/collection_dict_concat.phpt +++ b/Zend/tests/collection/collection_dict_concat.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books< Book>> {} +collection(Dict) Books Book> {} $c1 = new Books(); $c2 = new Books(); diff --git a/Zend/tests/collection/collection_dict_equality.phpt b/Zend/tests/collection/collection_dict_equality.phpt index aec347c478a62..0df9e31a472aa 100644 --- a/Zend/tests/collection/collection_dict_equality.phpt +++ b/Zend/tests/collection/collection_dict_equality.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books< Book>> {} +collection(Dict) Books Book> {} $c1 = new Books(); $c2 = new Books(); diff --git a/Zend/tests/collection/collection_dict_map.phpt b/Zend/tests/collection/collection_dict_map.phpt index e64f75fd0b3c0..82e55b1a49a99 100644 --- a/Zend/tests/collection/collection_dict_map.phpt +++ b/Zend/tests/collection/collection_dict_map.phpt @@ -9,9 +9,9 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books< Book>> {} +collection(Dict) Books Book> {} -collection(Dict) Titles< string>> {} +collection(Dict) Titles string> {} $c1 = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify.phpt b/Zend/tests/collection/collection_dict_modify.phpt index 2b17d4459b6d3..05f1839e0182e 100644 --- a/Zend/tests/collection/collection_dict_modify.phpt +++ b/Zend/tests/collection/collection_dict_modify.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books< Book>> {} +collection(Dict) Books Book> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify_errors-001.phpt b/Zend/tests/collection/collection_dict_modify_errors-001.phpt index 3839d739fe0bd..34e861ccba6b6 100644 --- a/Zend/tests/collection/collection_dict_modify_errors-001.phpt +++ b/Zend/tests/collection/collection_dict_modify_errors-001.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books< Book>> {} +collection(Dict) Books Book> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify_errors-002.phpt b/Zend/tests/collection/collection_dict_modify_errors-002.phpt index d4d55a82c4db9..1cdb7a5cd665e 100644 --- a/Zend/tests/collection/collection_dict_modify_errors-002.phpt +++ b/Zend/tests/collection/collection_dict_modify_errors-002.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books< Book>> {} +collection(Dict) Books Book> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_modify_errors-003.phpt b/Zend/tests/collection/collection_dict_modify_errors-003.phpt index 8d5181285cbaa..f278bb8f52ccc 100644 --- a/Zend/tests/collection/collection_dict_modify_errors-003.phpt +++ b/Zend/tests/collection/collection_dict_modify_errors-003.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Dict) Books< Book>> {} +collection(Dict) Books Book> {} $c = new Books(); diff --git a/Zend/tests/collection/collection_dict_syntax_errors.phpt b/Zend/tests/collection/collection_dict_syntax_errors.phpt index f7acc8f9a51a0..a44c098f2f2af 100644 --- a/Zend/tests/collection/collection_dict_syntax_errors.phpt +++ b/Zend/tests/collection/collection_dict_syntax_errors.phpt @@ -2,7 +2,7 @@ Collection: Dictionary without key type defined --FILE-- > +collection(Dict) Articles
{ } ?> diff --git a/Zend/tests/collection/collection_dict_with_trait.phpt b/Zend/tests/collection/collection_dict_with_trait.phpt index 1cb3d7bae4166..c19a07c864aab 100644 --- a/Zend/tests/collection/collection_dict_with_trait.phpt +++ b/Zend/tests/collection/collection_dict_with_trait.phpt @@ -14,7 +14,7 @@ trait Shuffler } -collection(Dict) Articles< Article>> +collection(Dict) Articles Article> { use Shuffler; } diff --git a/Zend/tests/collection/collection_interface_type_check.phpt b/Zend/tests/collection/collection_interface_type_check.phpt index 1e06323a799cd..a01e8c2964d72 100644 --- a/Zend/tests/collection/collection_interface_type_check.phpt +++ b/Zend/tests/collection/collection_interface_type_check.phpt @@ -2,11 +2,11 @@ Collection: Type checking against interface --FILE-- Article>> +collection(Dict) Articles Article> { } -collection(Seq) ArticleList<
> +collection(Seq) ArticleList
{ } diff --git a/Zend/tests/collection/collection_seq_basic.phpt b/Zend/tests/collection/collection_seq_basic.phpt index 83f534d292723..197a9798e5764 100644 --- a/Zend/tests/collection/collection_seq_basic.phpt +++ b/Zend/tests/collection/collection_seq_basic.phpt @@ -9,7 +9,7 @@ class Article } } -collection(Seq) Articles<
> +collection(Seq) Articles
{ } diff --git a/Zend/tests/collection/collection_seq_concat.phpt b/Zend/tests/collection/collection_seq_concat.phpt index 681d33a9f2600..24500b541b1e0 100644 --- a/Zend/tests/collection/collection_seq_concat.phpt +++ b/Zend/tests/collection/collection_seq_concat.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books<> {} +collection(Seq) Books {} $c1 = new Books(); $c2 = new Books(); diff --git a/Zend/tests/collection/collection_seq_equality.phpt b/Zend/tests/collection/collection_seq_equality.phpt index 14c1e1345fcc5..dc14aef024df7 100644 --- a/Zend/tests/collection/collection_seq_equality.phpt +++ b/Zend/tests/collection/collection_seq_equality.phpt @@ -9,7 +9,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books<> {} +collection(Seq) Books {} $c1 = new Books(); diff --git a/Zend/tests/collection/collection_seq_errors.phpt b/Zend/tests/collection/collection_seq_errors.phpt index 0005d9e8cd082..cb5adc0eb9cd4 100644 --- a/Zend/tests/collection/collection_seq_errors.phpt +++ b/Zend/tests/collection/collection_seq_errors.phpt @@ -9,7 +9,7 @@ class Article } } -collection(Seq) Articles<
> +collection(Seq) Articles
{ } diff --git a/Zend/tests/collection/collection_seq_map.phpt b/Zend/tests/collection/collection_seq_map.phpt index 2327c464ab6ca..265fc1da5792a 100644 --- a/Zend/tests/collection/collection_seq_map.phpt +++ b/Zend/tests/collection/collection_seq_map.phpt @@ -7,9 +7,9 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books<> {} +collection(Seq) Books {} -collection(Seq) Titles<> {} +collection(Seq) Titles {} $c1 = new Books(); diff --git a/Zend/tests/collection/collection_seq_map_error.phpt b/Zend/tests/collection/collection_seq_map_error.phpt index e1eb5cd7ff8b0..3506e8a9c4bdc 100644 --- a/Zend/tests/collection/collection_seq_map_error.phpt +++ b/Zend/tests/collection/collection_seq_map_error.phpt @@ -13,9 +13,9 @@ class Book { class NormalTitle { } -collection(Seq) Titles<> {} +collection(Seq) Titles {} -collection(Seq) Books<> {} +collection(Seq) Books {} $c1 = new Books(); diff --git a/Zend/tests/collection/collection_seq_modify.phpt b/Zend/tests/collection/collection_seq_modify.phpt index afc8e9b3ec3a1..085a1d3b2dde7 100644 --- a/Zend/tests/collection/collection_seq_modify.phpt +++ b/Zend/tests/collection/collection_seq_modify.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books<> {} +collection(Seq) Books {} $c = new Books(); diff --git a/Zend/tests/collection/collection_seq_modify_errors-001.phpt b/Zend/tests/collection/collection_seq_modify_errors-001.phpt index 1dcb03bed0873..d033ed0aa1fd5 100644 --- a/Zend/tests/collection/collection_seq_modify_errors-001.phpt +++ b/Zend/tests/collection/collection_seq_modify_errors-001.phpt @@ -7,7 +7,7 @@ class Book { public function __construct(public string $title) {} } -collection(Seq) Books<> {} +collection(Seq) Books {} $c = new Books(); diff --git a/Zend/tests/collection/collection_seq_syntax_errors.phpt b/Zend/tests/collection/collection_seq_syntax_errors.phpt index 2397f8ad51737..edae30b2cae01 100644 --- a/Zend/tests/collection/collection_seq_syntax_errors.phpt +++ b/Zend/tests/collection/collection_seq_syntax_errors.phpt @@ -2,7 +2,7 @@ Collection: Sequence with key type defined --FILE-- Article>> +collection(Seq) Articles Article> { } ?> diff --git a/Zend/tests/collection/collection_syntax-001.phpt b/Zend/tests/collection/collection_syntax-001.phpt index 05f52627d6324..f7a8603f4538a 100644 --- a/Zend/tests/collection/collection_syntax-001.phpt +++ b/Zend/tests/collection/collection_syntax-001.phpt @@ -2,11 +2,11 @@ Collection: Syntax --FILE-- Article>> +collection(Dict) Articles Article> { } -CoLleCtION(dICT) Books< Book>> +CoLleCtION(dICT) Books Book> { } diff --git a/Zend/tests/collection/collection_syntax-002.phpt b/Zend/tests/collection/collection_syntax-002.phpt index a8c53da0f2a64..61cf09b7d8eec 100644 --- a/Zend/tests/collection/collection_syntax-002.phpt +++ b/Zend/tests/collection/collection_syntax-002.phpt @@ -2,7 +2,7 @@ Collection: Sequence syntax with scalars --FILE-- > +collection(Seq) Titles { } diff --git a/Zend/tests/collection/collection_syntax-003.phpt b/Zend/tests/collection/collection_syntax-003.phpt index 2fe76b66b2711..cb5e10016b036 100644 --- a/Zend/tests/collection/collection_syntax-003.phpt +++ b/Zend/tests/collection/collection_syntax-003.phpt @@ -5,7 +5,7 @@ Collection: Syntax with 'Collection' class class Collection {} class Article extends Collection {} -collection(SEQ) Articles<
> +collection(SEQ) Articles
{ } diff --git a/Zend/tests/collection/collection_syntax_errors.phpt b/Zend/tests/collection/collection_syntax_errors.phpt index 447b74197c786..59757e8fda28f 100644 --- a/Zend/tests/collection/collection_syntax_errors.phpt +++ b/Zend/tests/collection/collection_syntax_errors.phpt @@ -2,7 +2,7 @@ Collection: Syntax Errors --FILE-- Article => three>> +collection(Dict) Articles Article => three> { } ?> diff --git a/Zend/tests/collection/collection_syntax_unknown_collection.phpt b/Zend/tests/collection/collection_syntax_unknown_collection.phpt index dd9fa2a398d13..611a3ada7cf6d 100644 --- a/Zend/tests/collection/collection_syntax_unknown_collection.phpt +++ b/Zend/tests/collection/collection_syntax_unknown_collection.phpt @@ -2,7 +2,7 @@ Collection with unsupported structure type --FILE-- Article>> +collection(Foobar) Articles Article> { } ?> diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 6981d669ded93..5c696f2dbe92f 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -662,10 +662,10 @@ enum_case_expr: collection_declaration_statement: T_COLLECTION_SEQ { $$ = CG(zend_lineno); } - T_STRING T_SL collection_type_list T_SR backup_doc_comment '{' class_statement_list '}' + T_STRING '<' collection_type_list '>' backup_doc_comment '{' class_statement_list '}' { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $7, zend_ast_get_str($3), NULL, zend_ast_create_zval_from_long(ZEND_COLLECTION_SEQ), $9, NULL, $5); } | T_COLLECTION_DICT { $$ = CG(zend_lineno); } - T_STRING T_SL collection_type_list T_SR backup_doc_comment '{' class_statement_list '}' + T_STRING '<' collection_type_list '>' backup_doc_comment '{' class_statement_list '}' { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $2, $7, zend_ast_get_str($3), NULL, zend_ast_create_zval_from_long(ZEND_COLLECTION_DICT), $9, NULL, $5); } ; From 2ea005b250aa6e5fa8b296c3127cbdefa465d282 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 18 Jul 2023 11:22:15 +0100 Subject: [PATCH 33/48] Implement Dictionary::map() --- .../collection/collection_dict_int_map.phpt | 34 ++++++++ .../collection/collection_dict_map_error.phpt | 75 +++++++++++++++++ ...p.phpt => collection_dict_string_map.phpt} | 19 +++-- Zend/zend_collection.c | 80 ++++++++++++++++++- Zend/zend_collection.stub.php | 4 +- Zend/zend_collection_arginfo.h | 5 +- 6 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 Zend/tests/collection/collection_dict_int_map.phpt create mode 100644 Zend/tests/collection/collection_dict_map_error.phpt rename Zend/tests/collection/{collection_dict_map.phpt => collection_dict_string_map.phpt} (64%) diff --git a/Zend/tests/collection/collection_dict_int_map.phpt b/Zend/tests/collection/collection_dict_int_map.phpt new file mode 100644 index 0000000000000..bd15303c71fbf --- /dev/null +++ b/Zend/tests/collection/collection_dict_int_map.phpt @@ -0,0 +1,34 @@ +--TEST-- +Collection: Dictionary: map (int key) +--FILE-- + Book> {} + +collection(Dict) Titles string> {} + +$c1 = new Books(); + +$c1->add(74, new Book('Title 1')); +$c1->add(75, new Book('Title 2')); + +$c2 = $c1->map(fn(Book $b, int $k) => sprintf('%s (%d)', $b->title, $k), Titles::class); +var_dump($c2 instanceof Titles); +var_dump($c2); + +?> +--EXPECTF-- +bool(true) +object(Titles)#%d (%d) { + ["value"]=> + array(2) { + [74]=> + string(12) "Title 1 (74)" + [75]=> + string(12) "Title 2 (75)" + } +} diff --git a/Zend/tests/collection/collection_dict_map_error.phpt b/Zend/tests/collection/collection_dict_map_error.phpt new file mode 100644 index 0000000000000..bc3d58dd5b51a --- /dev/null +++ b/Zend/tests/collection/collection_dict_map_error.phpt @@ -0,0 +1,75 @@ +--TEST-- +Collection: Dictionary: map errors +--FILE-- + string> {} + +collection(Dict) Books Book> {} + +$c1 = new Books(); + +$c1->add('seventy-eight', new Book('Title 1', 1978)); +$c1->add('twenty-three', new Book('Title 2', 2023)); + +try { + $c2 = $c1->map(fn(Book $b, string $k): string => "{$k}: {$b->title}", Unknown::class); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +echo "\n"; + +try { + $c2 = $c1->map(fn(Book $b, string $k): string => "{$k}: {$b->title}", NormalTitle::class); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +echo "\n"; + +try { + $c2 = $c1->map(fn(Book $b, string $k): int => "{$k}: {$b->title}", Titles::class); +} catch (TypeError $e) { + echo get_class($e->getPrevious()), ': ', $e->getPrevious()->getMessage(), "\n"; + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +echo "\n"; + +try { + $c2 = $c1->map(fn(Book $b): string => "{$k}: {$b->title}", Titles::class); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +echo "\n"; + +try { + $c3 = $c1->map(fn(Book $b, int $k) => sprintf('%d: %s', $k, $b->title), Titles::class); +} catch (TypeError $e) { + echo get_class($e->getPrevious()), ': ', $e->getPrevious()->getMessage(), "\n"; + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Type 'Unknown' can not be fetched + +TypeError: Type 'NormalTitle' must implement DictCollection interface + +TypeError: {closure}(): Return value must be of type int, string returned +TypeError: Value type Titles does not match null collection item type string + + +Warning: Undefined variable $k in %scollection_dict_map_error.php on line 45 + +Warning: Undefined variable $k in %scollection_dict_map_error.php on line 45 + +TypeError: {closure}(): Argument #2 ($k) must be of type int, string given +TypeError: Value type Titles does not match null collection item type string diff --git a/Zend/tests/collection/collection_dict_map.phpt b/Zend/tests/collection/collection_dict_string_map.phpt similarity index 64% rename from Zend/tests/collection/collection_dict_map.phpt rename to Zend/tests/collection/collection_dict_string_map.phpt index 82e55b1a49a99..bd0822251d685 100644 --- a/Zend/tests/collection/collection_dict_map.phpt +++ b/Zend/tests/collection/collection_dict_string_map.phpt @@ -1,7 +1,5 @@ --TEST-- -Collection: Dictionary: map ---XFAIL-- -Unimplemented +Collection: Dictionary: map (string key) --FILE-- add('one', new Book('Title 1')); $c1->add('two', new Book('Title 2')); -$c2 = $c1->map(fn(Book $b, string $k) => sprintf('%s: %s', $k, $book->title), Titles::class); - +$c2 = $c1->map(fn(Book $b, string $k) => sprintf('%s: %s', $k, $b->title), Titles::class); var_dump($c2 instanceof Titles); - -// Should be a map of string to string, like one => one: Title 1. var_dump($c2); ?> --EXPECTF-- +bool(true) +object(Titles)#%d (%d) { + ["value"]=> + array(2) { + ["one"]=> + string(12) "one: Title 1" + ["two"]=> + string(12) "two: Title 2" + } +} diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 7fa1ce23d4312..d4a873899d899 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -444,6 +444,81 @@ static ZEND_NAMED_FUNCTION(zend_collection_dict_set_func) RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } +static ZEND_NAMED_FUNCTION(zend_collection_dict_map_func) +{ + zend_object *object = Z_OBJ_P(ZEND_THIS); + zend_class_entry *ce = Z_OBJCE_P(ZEND_THIS); + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fci_cache = empty_fcall_info_cache; + zval *value_prop; + zend_string *type; + zval *operand; + zend_ulong index; + zend_string *key; + zval retval; + zval args[2]; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_FUNC(fci, fci_cache) + Z_PARAM_STR(type) + ZEND_PARSE_PARAMETERS_END(); + + zend_class_entry *return_ce = zend_lookup_class(type); + if (!return_ce) { + zend_type_error( + "Type '%s' can not be fetched", + ZSTR_VAL(type) + ); + return; + } + + if (!instanceof_function(return_ce, zend_ce_dict_collection)) { + zend_type_error( + "Type '%s' must implement DictCollection interface", + ZSTR_VAL(type) + ); + return; + } + + create_array_if_needed(ce, object); + value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + object_init_ex(return_value, return_ce); + Z_OBJCE_P(return_value)->collection_data_structure = return_ce->collection_data_structure; + + fci.retval = &retval; + fci.param_count = 2; + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(value_prop), index, key, operand) { + ZVAL_COPY(&args[0], operand); + if (key != NULL) { + ZVAL_STR_COPY(&args[1], key); + } else { + ZVAL_LONG(&args[1], index); + } + + fci.params = args; + + if (zend_call_function(&fci, &fci_cache) == SUCCESS) { + bool ok = zend_collection_add_item(Z_OBJ_P(return_value), &args[1], &retval); + + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[1]); + + if (!ok) { + return; + } + } else { + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[1]); + + return; + } + + } ZEND_HASH_FOREACH_END(); +} + + static void zend_collection_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) { zend_string *name = ZSTR_KNOWN(name_id); @@ -498,6 +573,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_dict_with_func, arginfo_class_DictCollection_with, 2); REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_dict_without_func, arginfo_class_DictCollection_without, 1); REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_dict_set_func, arginfo_class_DictCollection_set, 2); + REGISTER_FUNCTION(ZEND_STR_MAP, zend_collection_dict_map_func, arginfo_class_DictCollection_map, 2); break; } } @@ -571,7 +647,7 @@ static void dict_add_or_set_item(zend_object *object, zval *offset, zval *value) create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); SEPARATE_ARRAY(value_prop); - Z_ADDREF_P(value); + zval_add_ref(value); add_index_zval(value_prop, Z_LVAL_P(offset), value); break; } @@ -579,7 +655,7 @@ static void dict_add_or_set_item(zend_object *object, zval *offset, zval *value) create_array_if_needed(ce, object); value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); SEPARATE_ARRAY(value_prop); - Z_ADDREF_P(value); + zval_add_ref(value); add_assoc_zval_ex(value_prop, Z_STRVAL_P(offset), Z_STRLEN_P(offset), value); break; } diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index ecf534fa07d18..77c6273bd18e0 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -60,9 +60,9 @@ public function concat(DictCollection $other): static; // True if both dicts have the same key/values in the same order. public function equals(DictCollection $other): bool; +*/ // $fn is callable(mixed $val, mixed $key) // The return type is $targetType, but that can't be expressed statically. - public function map(callable $fn, string $targetType): DictCollection; -*/ + public function map(callable $fn, string $targetType): object; } diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index f04b974ce5c42..2f6ce5574d89d 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 100f44877eeb98465fc33afb33a69eea82119702 */ + * Stub hash: 8dc686206ca76666ef58319de7e5a0504fc99231 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -54,6 +54,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_DictCollection_set arginfo_class_DictCollection_add +#define arginfo_class_DictCollection_map arginfo_class_SeqCollection_map + @@ -78,6 +80,7 @@ static const zend_function_entry class_DictCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, with, arginfo_class_DictCollection_with, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, without, arginfo_class_DictCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, set, arginfo_class_DictCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, map, arginfo_class_DictCollection_map, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; From 3133071b9f1891d516ac7ed58a6511b0604ba1ef Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 28 Jul 2023 10:57:00 +0100 Subject: [PATCH 34/48] Add new tokens to tokenizer extension --- ext/tokenizer/tokenizer_data.c | 2 ++ ext/tokenizer/tokenizer_data.stub.php | 10 ++++++++++ ext/tokenizer/tokenizer_data_arginfo.h | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index cdaaddecd7bfa..988f299ff03f9 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -102,6 +102,8 @@ char *get_token_type_name(int token_type) case T_TRAIT: return "T_TRAIT"; case T_INTERFACE: return "T_INTERFACE"; case T_ENUM: return "T_ENUM"; + case T_COLLECTION_SEQ: return "T_COLLECTION_SEQ"; + case T_COLLECTION_DICT: return "T_COLLECTION_DICT"; case T_EXTENDS: return "T_EXTENDS"; case T_IMPLEMENTS: return "T_IMPLEMENTS"; case T_NAMESPACE: return "T_NAMESPACE"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 81e4e92626f37..07e0b78e42db1 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -387,6 +387,16 @@ * @cvalue T_ENUM */ const T_ENUM = UNKNOWN; +/** + * @var int + * @cvalue T_COLLECTION_SEQ + */ +const T_COLLECTION_SEQ = UNKNOWN; +/** + * @var int + * @cvalue T_COLLECTION_DICT + */ +const T_COLLECTION_DICT = UNKNOWN; /** * @var int * @cvalue T_EXTENDS diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 0fc1f219619dd..ea1bab01cbd68 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: b7a13b3b242bd535f62a7d819eb0df751efb54ed */ + * Stub hash: 59da19fc9926e153c9fc6d99ec0a7c5feb744566 */ static void register_tokenizer_data_symbols(int module_number) { @@ -80,6 +80,8 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_TRAIT", T_TRAIT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INTERFACE", T_INTERFACE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ENUM", T_ENUM, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_COLLECTION_SEQ", T_COLLECTION_SEQ, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_COLLECTION_DICT", T_COLLECTION_DICT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_EXTENDS", T_EXTENDS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IMPLEMENTS", T_IMPLEMENTS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NAMESPACE", T_NAMESPACE, CONST_PERSISTENT); From ec42167ca75d59b6e77ba5727840f54b717eb6c6 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Fri, 28 Jul 2023 12:07:50 +0100 Subject: [PATCH 35/48] Simplify scanner as there can't be a classname with () in it anyway --- Zend/zend_language_scanner.l | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index b751c4a4d8316..303f3511dfd91 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1562,29 +1562,11 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_ENUM); } -/* - * The collection(Seq) keyword must be followed by whitespace and another identifier. - * This avoids the BC break of using 'collection' in classes, namespaces, functions and constants. - */ -"collection(Seq)"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { - yyless(15); - RETURN_TOKEN_WITH_STR(T_STRING, 0); -} -"collection(Seq)"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { - yyless(15); +"collection(Seq)" { RETURN_TOKEN_WITH_IDENT(T_COLLECTION_SEQ); } -/* - * The collection(Dict) keyword must be followed by whitespace and another identifier. - * This avoids the BC break of using 'collection' in classes, namespaces, functions and constants. - */ -"collection(Dict)"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { - yyless(16); - RETURN_TOKEN_WITH_STR(T_STRING, 0); -} -"collection(Dict)"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { - yyless(16); +"collection(Dict)" { RETURN_TOKEN_WITH_IDENT(T_COLLECTION_DICT); } From 0beda3cc3e195bf38d5df791bf5001e1e1a96765 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 31 Jul 2023 16:04:50 +0100 Subject: [PATCH 36/48] Implement Sequence::concat() --- .../collection_seq_concat-errors.phpt | 62 +++++++++++++++++++ .../collection/collection_seq_concat.phpt | 44 ++++++++++++- Zend/zend_collection.c | 26 ++++++++ Zend/zend_collection.stub.php | 6 +- Zend/zend_collection_arginfo.h | 7 ++- Zend/zend_string.h | 1 + 6 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/collection/collection_seq_concat-errors.phpt diff --git a/Zend/tests/collection/collection_seq_concat-errors.phpt b/Zend/tests/collection/collection_seq_concat-errors.phpt new file mode 100644 index 0000000000000..3abd04e1c07c0 --- /dev/null +++ b/Zend/tests/collection/collection_seq_concat-errors.phpt @@ -0,0 +1,62 @@ +--TEST-- +Collection: Sequence: Concat +--FILE-- + {} +collection(Seq) DutchBooks {} +collection(Seq) Articles
{} + +$c1 = new Books(); +$c1->add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +$d1 = new DutchBooks(); +$d1->add(new DutchBook('Titel 1')); +$d1->add(new DutchBook('Titel 2')); + +$a2 = new Articles(); +$a2->add(new Article('Subject 1')); +$a2->add(new Article('Subject 2')); + +try { + $c1->concat(new Book('Title E')); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c1->concat($a2); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $cr = $c1->concat($d1); + echo "Inheritence: OK\n"; +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $cr = $d1->concat($c1); + var_dump($cr); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +TypeError: Books::concat(): Argument #1 ($other) must be of type SeqCollection, Book given +TypeError: Value type Books does not match Article collection item type Book +Inheritence: OK +TypeError: Value type DutchBooks does not match Book collection item type DutchBook diff --git a/Zend/tests/collection/collection_seq_concat.phpt b/Zend/tests/collection/collection_seq_concat.phpt index 24500b541b1e0..231e96233de8a 100644 --- a/Zend/tests/collection/collection_seq_concat.phpt +++ b/Zend/tests/collection/collection_seq_concat.phpt @@ -1,7 +1,5 @@ --TEST-- Collection: Sequence: Concat ---XFAIL-- -Unimplemented --FILE-- add(new Book('Title 2')); $c2->add(new Book('Title 3')); $c2->add(new Book('Title 4')); -$c3 = $c->concat($c2); +$c3 = $c1->concat($c2); // Four items. var_dump($c3); @@ -30,3 +28,43 @@ var_dump($c1); ?> --EXPECTF-- +object(Books)#%d (1) { + ["value"]=> + array(4) { + [0]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [1]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + [2]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 3" + } + [3]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 4" + } + } +} +object(Books)#%d (1) { + ["value"]=> + array(2) { + [0]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [1]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + } +} diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index d4a873899d899..38022a6d1ce55 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -258,6 +258,31 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_set_func) RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } +static ZEND_NAMED_FUNCTION(zend_collection_seq_concat_func) +{ + zval *other; + zend_object *clone; + zend_class_entry *ce = Z_OBJCE_P(ZEND_THIS); + zval *value_prop; + zval *element; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(other, zend_ce_seq_collection); + ZEND_PARSE_PARAMETERS_END(); + + clone = zend_objects_clone_obj(Z_OBJ_P(ZEND_THIS)); + + value_prop = zend_read_property_ex(ce, Z_OBJ_P(other), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value_prop), element) { + if (!zend_collection_add_item(clone, NULL, element)) { + return; + } + } ZEND_HASH_FOREACH_END(); + + RETURN_OBJ(clone); +} + static ZEND_NAMED_FUNCTION(zend_collection_seq_map_func) { zend_object *object = Z_OBJ_P(ZEND_THIS); @@ -563,6 +588,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_seq_with_func, arginfo_class_SeqCollection_with, 1); REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_seq_without_func, arginfo_class_SeqCollection_without, 1); REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_set_func, arginfo_class_SeqCollection_set, 2); + REGISTER_FUNCTION(ZEND_STR_CONCAT, zend_collection_seq_concat_func, arginfo_class_SeqCollection_concat, 1); REGISTER_FUNCTION(ZEND_STR_MAP, zend_collection_seq_map_func, arginfo_class_SeqCollection_map, 2); break; case ZEND_COLLECTION_DICT: diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 77c6273bd18e0..70fbfca956ed9 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -22,11 +22,11 @@ public function without(int $index) : static; // returns self. Throws OutOfBoundsException if $index is not yet defined. public function set(int $index, mixed $value): static; -/* + // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. - public function concat(SeqCollection $other): static; - + public function concat(object $other): static; +/* // True if both seqs have the same values in the same order. public function equals(SeqCollection $other): bool; */ diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 2f6ce5574d89d..0d079144bf558 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 8dc686206ca76666ef58319de7e5a0504fc99231 */ + * Stub hash: 1f93e07aa28af67d65deeb7c9b36e8536c09d7a8 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -26,6 +26,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_set, 0, 2, I ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_concat, 0, 1, IS_STATIC, 0) + ZEND_ARG_TYPE_INFO(0, other, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_map, 0, 2, IS_OBJECT, 0) ZEND_ARG_TYPE_INFO(0, fn, IS_CALLABLE, 0) ZEND_ARG_TYPE_INFO(0, targetType, IS_STRING, 0) @@ -67,6 +71,7 @@ static const zend_function_entry class_SeqCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, with, arginfo_class_SeqCollection_with, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, without, arginfo_class_SeqCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, set, arginfo_class_SeqCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, concat, arginfo_class_SeqCollection_concat, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, map, arginfo_class_SeqCollection_map, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 93c8f2aaa6ca0..edb5a30ce8c5b 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -640,6 +640,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_SET, "set") \ _(ZEND_STR_WITH, "with") \ _(ZEND_STR_WITHOUT, "without") \ + _(ZEND_STR_CONCAT, "concat") \ _(ZEND_STR_MAP, "map") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED, "Deprecated") \ From ad9040800d2ff16d76f01d64d095d30d04fa09c0 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 31 Jul 2023 17:23:53 +0100 Subject: [PATCH 37/48] Implement Dictionary::concat() --- .../collection_dict_concat-errors.phpt | 82 +++++++++++++++++++ .../collection/collection_dict_concat.phpt | 32 -------- .../collection_dict_concat_idx.phpt | 70 ++++++++++++++++ .../collection_dict_concat_key.phpt | 70 ++++++++++++++++ .../collection_seq_concat-errors.phpt | 3 +- Zend/zend_collection.c | 36 ++++++++ Zend/zend_collection.stub.php | 6 +- Zend/zend_collection_arginfo.h | 5 +- 8 files changed, 266 insertions(+), 38 deletions(-) create mode 100644 Zend/tests/collection/collection_dict_concat-errors.phpt delete mode 100644 Zend/tests/collection/collection_dict_concat.phpt create mode 100644 Zend/tests/collection/collection_dict_concat_idx.phpt create mode 100644 Zend/tests/collection/collection_dict_concat_key.phpt diff --git a/Zend/tests/collection/collection_dict_concat-errors.phpt b/Zend/tests/collection/collection_dict_concat-errors.phpt new file mode 100644 index 0000000000000..eaea477075e89 --- /dev/null +++ b/Zend/tests/collection/collection_dict_concat-errors.phpt @@ -0,0 +1,82 @@ +--TEST-- +Collection: Dictionary: Concat Errors +--FILE-- + Book> {} +collection(Dict) YBooks Book> {} +collection(Dict) DutchBooks DutchBook> {} +collection(Dict) Articles Article> {} + +$c1 = new Books(); +$c1->add("one", new Book('Title 1')); +$c1->add("two", new Book('Title 2')); + +$y1 = new YBooks(); +$y1->add(1970, new Book('Title 1')); +$y1->add(1971, new Book('Title 2')); + +$d1 = new DutchBooks(); +$d1->add("één", new DutchBook('Titel 1')); +$d1->add("twee", new DutchBook('Titel 2')); + +$a2 = new Articles(); +$a2->add("three", new Article('Subject 1')); +$a2->add("four", new Article('Subject 2')); + +try { + $c1->concat(new Book('Title E')); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c1->concat($a2); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $cr = $c1->concat($d1); + echo "Inheritence: OK\n"; +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $cr = $d1->concat($c1); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $why = $c1->concat($y1); + var_dump( $why ); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $why = $y1->concat($c1); + var_dump( $why ); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +TypeError: Books::concat(): Argument #1 ($other) must be of type DictCollection, Book given +TypeError: Value type Books does not match Article collection item type Book +Inheritence: OK +TypeError: Value type DutchBooks does not match Book collection item type DutchBook +TypeError: Key type int of element does not match Books dictionary key type string +TypeError: Key type string of element does not match YBooks dictionary key type int diff --git a/Zend/tests/collection/collection_dict_concat.phpt b/Zend/tests/collection/collection_dict_concat.phpt deleted file mode 100644 index 4e901778aff37..0000000000000 --- a/Zend/tests/collection/collection_dict_concat.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -Collection: Dictionary: Concat ---XFAIL-- -Unimplemented ---FILE-- - Book> {} - -$c1 = new Books(); -$c2 = new Books(); - -$c1->add('one', new Book('Title 1')); -$c1->add('two', new Book('Title 2')); - -$c2->add('three', new Book('Title 3')); -$c2->add('four', new Book('Title 4')); - -$c3 = $c->concat($c2); - -// Four items. -var_dump($c3); - -// Still two items. -var_dump($c1); - -?> ---EXPECTF-- diff --git a/Zend/tests/collection/collection_dict_concat_idx.phpt b/Zend/tests/collection/collection_dict_concat_idx.phpt new file mode 100644 index 0000000000000..cc131fa4d57eb --- /dev/null +++ b/Zend/tests/collection/collection_dict_concat_idx.phpt @@ -0,0 +1,70 @@ +--TEST-- +Collection: Dictionary: Concat (int key) +--FILE-- + Book> {} + +$c1 = new Books(); +$c2 = new Books(); + +$c1->add(41, new Book('Title 1')); +$c1->add(42, new Book('Title 2')); + +$c2->add(43, new Book('Title 3')); +$c2->add(44, new Book('Title 4')); + +$c3 = $c1->concat($c2); + +// Four items. +var_dump($c3); + +// Still two items. +var_dump($c1); + +?> +--EXPECTF-- +object(Books)#%d (1) { + ["value"]=> + array(4) { + [41]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [42]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + [43]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 3" + } + [44]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 4" + } + } +} +object(Books)#%d (1) { + ["value"]=> + array(2) { + [41]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [42]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + } +} diff --git a/Zend/tests/collection/collection_dict_concat_key.phpt b/Zend/tests/collection/collection_dict_concat_key.phpt new file mode 100644 index 0000000000000..d574223ee9584 --- /dev/null +++ b/Zend/tests/collection/collection_dict_concat_key.phpt @@ -0,0 +1,70 @@ +--TEST-- +Collection: Dictionary: Concat (string key) +--FILE-- + Book> {} + +$c1 = new Books(); +$c2 = new Books(); + +$c1->add('one', new Book('Title 1')); +$c1->add('two', new Book('Title 2')); + +$c2->add('three', new Book('Title 3')); +$c2->add('four', new Book('Title 4')); + +$c3 = $c1->concat($c2); + +// Four items. +var_dump($c3); + +// Still two items. +var_dump($c1); + +?> +--EXPECTF-- +object(Books)#%d (1) { + ["value"]=> + array(4) { + ["one"]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + ["two"]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + ["three"]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 3" + } + ["four"]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 4" + } + } +} +object(Books)#%d (1) { + ["value"]=> + array(2) { + ["one"]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + ["two"]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + } +} diff --git a/Zend/tests/collection/collection_seq_concat-errors.phpt b/Zend/tests/collection/collection_seq_concat-errors.phpt index 3abd04e1c07c0..c4d78ce0f1c49 100644 --- a/Zend/tests/collection/collection_seq_concat-errors.phpt +++ b/Zend/tests/collection/collection_seq_concat-errors.phpt @@ -1,5 +1,5 @@ --TEST-- -Collection: Sequence: Concat +Collection: Sequence: Concat Errors --FILE-- concat($c1); - var_dump($cr); } catch (TypeError $e) { echo get_class($e), ': ', $e->getMessage(), "\n"; } diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 38022a6d1ce55..a9a7459130a5b 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -469,6 +469,41 @@ static ZEND_NAMED_FUNCTION(zend_collection_dict_set_func) RETURN_OBJ_COPY(Z_OBJ_P(ZEND_THIS)); } +static ZEND_NAMED_FUNCTION(zend_collection_dict_concat_func) +{ + zval *other; + zend_object *clone; + zend_class_entry *ce = Z_OBJCE_P(ZEND_THIS); + zval *value_prop; + zval *element; + zend_ulong int_key; + zend_string *key; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(other, zend_ce_dict_collection); + ZEND_PARSE_PARAMETERS_END(); + + clone = zend_objects_clone_obj(Z_OBJ_P(ZEND_THIS)); + + value_prop = zend_read_property_ex(ce, Z_OBJ_P(other), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(value_prop), int_key, key, element) { + zval zkey; + + if (key) { + ZVAL_STR(&zkey, key); + } else { + ZVAL_LONG(&zkey, int_key); + } + + if (!zend_collection_add_item(clone, &zkey, element)) { + return; + } + } ZEND_HASH_FOREACH_END(); + + RETURN_OBJ(clone); +} + static ZEND_NAMED_FUNCTION(zend_collection_dict_map_func) { zend_object *object = Z_OBJ_P(ZEND_THIS); @@ -599,6 +634,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_WITH, zend_collection_dict_with_func, arginfo_class_DictCollection_with, 2); REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_dict_without_func, arginfo_class_DictCollection_without, 1); REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_dict_set_func, arginfo_class_DictCollection_set, 2); + REGISTER_FUNCTION(ZEND_STR_CONCAT, zend_collection_dict_concat_func, arginfo_class_DictCollection_concat, 1); REGISTER_FUNCTION(ZEND_STR_MAP, zend_collection_dict_map_func, arginfo_class_DictCollection_map, 2); break; } diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 70fbfca956ed9..98faee597b24a 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -53,11 +53,11 @@ public function without(mixed $key) : static; // returns self. Overwrites existing value if already set. public function set(mixed $key, mixed $value): static; -/* + // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. - public function concat(DictCollection $other): static; - + public function concat(object $other): static; +/* // True if both dicts have the same key/values in the same order. public function equals(DictCollection $other): bool; */ diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 0d079144bf558..bb03598de2047 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 1f93e07aa28af67d65deeb7c9b36e8536c09d7a8 */ + * Stub hash: 54aa1ff8b5d7e051b63774a3f371211c28119c45 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -58,6 +58,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_DictCollection_set arginfo_class_DictCollection_add +#define arginfo_class_DictCollection_concat arginfo_class_SeqCollection_concat + #define arginfo_class_DictCollection_map arginfo_class_SeqCollection_map @@ -85,6 +87,7 @@ static const zend_function_entry class_DictCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, with, arginfo_class_DictCollection_with, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, without, arginfo_class_DictCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, set, arginfo_class_DictCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, concat, arginfo_class_DictCollection_concat, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, map, arginfo_class_DictCollection_map, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; From db2616db73dd6a592f3bde09a521922412da9900 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 7 Aug 2023 16:00:34 +0100 Subject: [PATCH 38/48] Remove superfluous and wrong code --- Zend/zend_collection.c | 13 ------------- Zend/zend_compile.c | 1 - 2 files changed, 14 deletions(-) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index a9a7459130a5b..ca3455b87be87 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -86,19 +86,6 @@ void zend_collection_add_interfaces(zend_class_entry *ce) } -void zend_collection_register_handlers(zend_class_entry *ce) -{ - memcpy(&zend_seq_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); - zend_seq_collection_object_handlers.clone_obj = NULL; - zend_seq_collection_object_handlers.compare = zend_objects_not_comparable; - ce->default_object_handlers = &zend_seq_collection_object_handlers; - - memcpy(&zend_dict_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); - zend_dict_collection_object_handlers.clone_obj = NULL; - zend_dict_collection_object_handlers.compare = zend_objects_not_comparable; - ce->default_object_handlers = &zend_dict_collection_object_handlers; -} - bool zend_collection_add_item(zend_object *object, zval *offset, zval *value); void zend_collection_set_item(zend_object *object, zval *offset, zval *value); int zend_collection_has_item(zend_object *object, zval *offset); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1d6e02923e57f..5cb2638721cf9 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9127,7 +9127,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_compile_collection_data_structure(ce, collection_data_structure_ast); zend_collection_add_interfaces(ce); - zend_collection_register_handlers(ce); zend_collection_register_props(ce); } From de78df6bf313827683a37ff55187e16e706e9897 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 7 Aug 2023 16:04:08 +0100 Subject: [PATCH 39/48] Implement Sequence::equals() --- .../collection/collection_seq_equality.phpt | 40 +++++++++++++++---- Zend/zend_collection.c | 27 +++++++++++++ Zend/zend_collection.stub.php | 6 +-- Zend/zend_collection_arginfo.h | 7 +++- Zend/zend_string.h | 1 + 5 files changed, 70 insertions(+), 11 deletions(-) diff --git a/Zend/tests/collection/collection_seq_equality.phpt b/Zend/tests/collection/collection_seq_equality.phpt index dc14aef024df7..55d8e9f675e61 100644 --- a/Zend/tests/collection/collection_seq_equality.phpt +++ b/Zend/tests/collection/collection_seq_equality.phpt @@ -1,7 +1,5 @@ --TEST-- -Collection: Sequence: equality ---XFAIL-- -Unimplemented +Collection: Sequence: Equals --FILE-- {} +collection(Seq) BooksOther {} $c1 = new Books(); -$c2 = new Books(); - $c1->add(new Book('Title 1')); $c1->add(new Book('Title 2')); +$c2 = new Books(); $c2->add(new Book('Title 1')); $c2->add(new Book('Title 2')); +$c3 = new Books(); +$c3->add(new Book('Title 1')); +$c3->add(new Book('Title 2')); +$c3->add(new Book('Title 3')); + +$c4 = new Books(); +$c4->add(new Book('Title 1')); +$c4->add(new Book('Title X')); +$c4->add(new Book('Title 2')); + +$c5 = new BooksOther(); +$c5->add(new Book('Title 1')); +$c5->add(new Book('Title 2')); + // True var_dump($c1->equals($c2)); -var_dump($c1 == $c2); +var_dump($c1->equals($c5)); $c2[] = new Book('Title 3'); // False var_dump($c1->equals($c2)); -var_dump($c1 == $c2); +var_dump($c3->equals($c4)); + +unset($c3[2]); +unset($c4[1]); + +// True +var_dump($c1->equals($c3)); +// False (indexes aren't both ordered without gaps) +var_dump($c1->equals($c4)); ?> --EXPECTF-- +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index ca3455b87be87..a4759011d3e2d 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -132,6 +132,13 @@ static int key_type_allowed(zend_class_entry *ce, zval *offset) return true; } +static int collection_is_equal_function(zval *z1, zval *z2) +{ + zval result; + is_equal_function(&result, z1, z2); + + return Z_TYPE(result) == IS_TRUE ? 0 : 1; +} static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) { @@ -270,6 +277,25 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_concat_func) RETURN_OBJ(clone); } +static ZEND_NAMED_FUNCTION(zend_collection_seq_equals_func) +{ + zval *other; + zval *value_prop_us, *value_prop_other; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(other, zend_ce_seq_collection); + ZEND_PARSE_PARAMETERS_END(); + + value_prop_us = zend_read_property_ex(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + value_prop_other = zend_read_property_ex(Z_OBJCE_P(other), Z_OBJ_P(other), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + if (!value_prop_us || !value_prop_other) { + RETURN_FALSE; + } + + RETURN_BOOL(zend_hash_compare(Z_ARRVAL_P(value_prop_us), Z_ARRVAL_P(value_prop_other), (compare_func_t) collection_is_equal_function, true) == 0); +} + static ZEND_NAMED_FUNCTION(zend_collection_seq_map_func) { zend_object *object = Z_OBJ_P(ZEND_THIS); @@ -611,6 +637,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_seq_without_func, arginfo_class_SeqCollection_without, 1); REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_seq_set_func, arginfo_class_SeqCollection_set, 2); REGISTER_FUNCTION(ZEND_STR_CONCAT, zend_collection_seq_concat_func, arginfo_class_SeqCollection_concat, 1); + REGISTER_FUNCTION(ZEND_STR_EQUALS, zend_collection_seq_equals_func, arginfo_class_SeqCollection_equals, 1); REGISTER_FUNCTION(ZEND_STR_MAP, zend_collection_seq_map_func, arginfo_class_SeqCollection_map, 2); break; case ZEND_COLLECTION_DICT: diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 98faee597b24a..9ddbea8363abc 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -26,10 +26,10 @@ public function set(int $index, mixed $value): static; // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. public function concat(object $other): static; -/* + // True if both seqs have the same values in the same order. - public function equals(SeqCollection $other): bool; -*/ + public function equals(object $other): bool; + // $fn is callable(mixed $val) // The return type is $targetType, but that can't be expressed statically. public function map(callable $fn, string $targetType): object; diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index bb03598de2047..5f5056b7cca3d 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 54aa1ff8b5d7e051b63774a3f371211c28119c45 */ + * Stub hash: 604adda1d78cf3889b92a91dda6ba54223fcd747 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -30,6 +30,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_concat, 0, 1 ZEND_ARG_TYPE_INFO(0, other, IS_OBJECT, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_equals, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, other, IS_OBJECT, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_map, 0, 2, IS_OBJECT, 0) ZEND_ARG_TYPE_INFO(0, fn, IS_CALLABLE, 0) ZEND_ARG_TYPE_INFO(0, targetType, IS_STRING, 0) @@ -74,6 +78,7 @@ static const zend_function_entry class_SeqCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, without, arginfo_class_SeqCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, set, arginfo_class_SeqCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, concat, arginfo_class_SeqCollection_concat, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, equals, arginfo_class_SeqCollection_equals, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(SeqCollection, map, arginfo_class_SeqCollection_map, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; diff --git a/Zend/zend_string.h b/Zend/zend_string.h index edb5a30ce8c5b..aed9a134c94c7 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -641,6 +641,7 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_WITH, "with") \ _(ZEND_STR_WITHOUT, "without") \ _(ZEND_STR_CONCAT, "concat") \ + _(ZEND_STR_EQUALS, "equals") \ _(ZEND_STR_MAP, "map") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED, "Deprecated") \ From 93e95e3b8c4ba263de079941adc35a4efc38d184 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 7 Aug 2023 16:04:36 +0100 Subject: [PATCH 40/48] Implement Sequence's compare_objects handler --- .../collection_seq_equality_handler.phpt | 61 +++++++++++++++++++ Zend/zend_collection.c | 33 +++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/collection/collection_seq_equality_handler.phpt diff --git a/Zend/tests/collection/collection_seq_equality_handler.phpt b/Zend/tests/collection/collection_seq_equality_handler.phpt new file mode 100644 index 0000000000000..cc1ae0088fb48 --- /dev/null +++ b/Zend/tests/collection/collection_seq_equality_handler.phpt @@ -0,0 +1,61 @@ +--TEST-- +Collection: Sequence: Comparison Handler +--FILE-- + {} +collection(Seq) BooksOther {} + + +$c1 = new Books(); +$c1->add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +$c2 = new Books(); +$c2->add(new Book('Title 1')); +$c2->add(new Book('Title 2')); + +$c3 = new Books(); +$c3->add(new Book('Title 1')); +$c3->add(new Book('Title 2')); +$c3->add(new Book('Title 3')); + +$c4 = new Books(); +$c4->add(new Book('Title 1')); +$c4->add(new Book('Title X')); +$c4->add(new Book('Title 2')); + +$c5 = new BooksOther(); +$c5->add(new Book('Title 1')); +$c5->add(new Book('Title 2')); + +// True +var_dump($c1 == $c2); +var_dump($c1 == $c5); + +$c2[] = new Book('Title 3'); + +// False +var_dump($c1 == $c2); +var_dump($c3 == $c4); + +unset($c3[2]); +unset($c4[1]); + +// True +var_dump($c1 == $c3); + +// False (indexes aren't both ordered without gaps) +var_dump($c1 == $c4); +?> +--EXPECTF-- +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index a4759011d3e2d..c87e92195d87f 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -31,6 +31,8 @@ ZEND_API zend_class_entry *zend_ce_dict_collection; ZEND_API zend_object_handlers zend_seq_collection_object_handlers; ZEND_API zend_object_handlers zend_dict_collection_object_handlers; +static int seq_objects_compare(zval *object1, zval *object2); + static int zend_implement_collection(zend_class_entry *interface, zend_class_entry *class_type) { if (class_type->ce_flags & ZEND_ACC_COLLECTION) { @@ -51,7 +53,7 @@ void zend_register_collection_ce(void) memcpy(&zend_seq_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); zend_seq_collection_object_handlers.clone_obj = NULL; - zend_seq_collection_object_handlers.compare = zend_objects_not_comparable; + zend_seq_collection_object_handlers.compare = seq_objects_compare; zend_ce_dict_collection = register_class_DictCollection(); zend_ce_dict_collection->interface_gets_implemented = zend_implement_collection; @@ -140,6 +142,35 @@ static int collection_is_equal_function(zval *z1, zval *z2) return Z_TYPE(result) == IS_TRUE ? 0 : 1; } +static int seq_objects_compare(zval *object1, zval *object2) +{ + zval *value_prop_object1, *value_prop_object2; + + if (!object1 || !object2) { + return ZEND_UNCOMPARABLE; + } + + if (!instanceof_function(Z_OBJCE_P(object1), zend_ce_seq_collection)) { + return 1; + } + if (!instanceof_function(Z_OBJCE_P(object2), zend_ce_seq_collection)) { + return 1; + } + + value_prop_object1 = zend_read_property_ex(Z_OBJCE_P(object1), Z_OBJ_P(object1), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + value_prop_object2 = zend_read_property_ex(Z_OBJCE_P(object2), Z_OBJ_P(object2), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + if (!value_prop_object1 || !value_prop_object2) { + return 1; + } + + return (zend_hash_compare( + Z_ARRVAL_P(value_prop_object1), + Z_ARRVAL_P(value_prop_object2), + (compare_func_t) collection_is_equal_function, true + ) != 0); +} + static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) { zval *value; From 91d6fbce303acb5757db8ac906b819d501d67af4 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 8 Aug 2023 11:05:08 +0100 Subject: [PATCH 41/48] Fixed segfault when value wasn't an object And added negative test cases for Sequence::equals() --- .../collection_seq_equality-errors.phpt | 60 +++++++++++++++++++ Zend/zend_collection.c | 4 +- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/collection/collection_seq_equality-errors.phpt diff --git a/Zend/tests/collection/collection_seq_equality-errors.phpt b/Zend/tests/collection/collection_seq_equality-errors.phpt new file mode 100644 index 0000000000000..9c8fb249473a6 --- /dev/null +++ b/Zend/tests/collection/collection_seq_equality-errors.phpt @@ -0,0 +1,60 @@ +--TEST-- +Collection: Sequence: Equals Errors +--FILE-- + {} +collection(Seq) BooksOther {} + +$c1 = new Books(); +$c1->add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +try { + $c1->equals(new Book('Title 3')); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + $c1->equals(null); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + var_dump($c1 == new Book('Title 3')); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + var_dump(new Book('Title 3') == $c1); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + var_dump($c1 == M_PI); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + var_dump(false == $c1); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Books::equals(): Argument #1 ($other) must be of type SeqCollection, Book given +TypeError: Books::equals(): Argument #1 ($other) must be of type SeqCollection, null given +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index c87e92195d87f..85a3728232629 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -150,10 +150,10 @@ static int seq_objects_compare(zval *object1, zval *object2) return ZEND_UNCOMPARABLE; } - if (!instanceof_function(Z_OBJCE_P(object1), zend_ce_seq_collection)) { + if (Z_TYPE_P(object1) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(object1), zend_ce_seq_collection)) { return 1; } - if (!instanceof_function(Z_OBJCE_P(object2), zend_ce_seq_collection)) { + if (Z_TYPE_P(object2) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(object2), zend_ce_seq_collection)) { return 1; } From 9b6ba6a4a81bea67461343ee1ca5687f829b80b6 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 8 Aug 2023 15:32:44 +0100 Subject: [PATCH 42/48] Implement Dictionary::equals() and comparison handler --- .../collection/collection_dict_equality.phpt | 77 +++++++++++++-- .../collection_dict_equality_handler.phpt | 94 +++++++++++++++++++ Zend/zend_collection.c | 66 ++++++++++++- Zend/zend_collection.stub.php | 5 +- Zend/zend_collection_arginfo.h | 5 +- 5 files changed, 231 insertions(+), 16 deletions(-) create mode 100644 Zend/tests/collection/collection_dict_equality_handler.phpt diff --git a/Zend/tests/collection/collection_dict_equality.phpt b/Zend/tests/collection/collection_dict_equality.phpt index 0df9e31a472aa..1f96771b57dab 100644 --- a/Zend/tests/collection/collection_dict_equality.phpt +++ b/Zend/tests/collection/collection_dict_equality.phpt @@ -1,7 +1,5 @@ --TEST-- -Collection: Dictionary: equality ---XFAIL-- -Unimplemented +Collection: Dictionary: Equals --FILE-- Book> {} +collection(Dict) BooksOther Book> {} +collection(Dict) YearBooks Book> {} $c1 = new Books(); -$c2 = new Books(); - $c1->add('one', new Book('Title 1')); $c1->add('two', new Book('Title 2')); +$c2 = new Books(); $c2->add('one', new Book('Title 1')); $c2->add('two', new Book('Title 2')); +$c3 = new Books(); +$c3->add('one', new Book('Title 1')); +$c3->add('two', new Book('Title 2')); +$c3->add('tri', new Book('Title 3')); + +$c4 = new Books(); +$c4->add('one', new Book('Title 1')); +$c4->add('XXX', new Book('Title X')); +$c4->add('two', new Book('Title 2')); + +$c5 = new Books(); +$c5->add('one', new Book('Title 1')); +$c5->add('two', new Book('Title X')); +$c5->add('tri', new Book('Title 2')); + +$c6 = new Books(); +$c6->add('one', new Book('Title 1')); +$c6->add('XXX', new Book('Title 2')); +$c6->add('tri', new Book('Title 3')); + +$c7 = new BooksOther(); +$c7->add('one', new Book('Title 1')); +$c7->add('two', new Book('Title 2')); + +$c8 = new YearBooks(); +$c8->add(1970, new Book('Title 1')); +$c8->add(1971, new Book('Title 2')); + +$c9 = new Books(); +$c9->add('two', new Book('Title 2')); +$c9->add('one', new Book('Title 1')); + + // True +var_dump($c1->equals($c1)); var_dump($c1->equals($c2)); -var_dump($c1 == $c2); +var_dump($c1->equals($c7)); +var_dump($c1->equals($c9)); -$c2['three'] = new Book('Title 3'); +$c2['tri'] = new Book('Title 3'); + +var_dump($c2->equals($c3)); // False var_dump($c1->equals($c2)); -var_dump($c1 == $c2); +var_dump($c1->equals($c8)); +var_dump($c3->equals($c4)); +var_dump($c3->equals($c5)); +var_dump($c3->equals($c6)); + +unset($c4['XXX']); +// True +var_dump($c1->equals($c4)); + +unset($c6['XXX']); +$c6['two'] = new Book('Title 2'); + +// True +var_dump($c3->equals($c6)); ?> --EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) diff --git a/Zend/tests/collection/collection_dict_equality_handler.phpt b/Zend/tests/collection/collection_dict_equality_handler.phpt new file mode 100644 index 0000000000000..ad27274da3506 --- /dev/null +++ b/Zend/tests/collection/collection_dict_equality_handler.phpt @@ -0,0 +1,94 @@ +--TEST-- +Collection: Dictionary: Comparison Handler +--FILE-- + Book> {} +collection(Dict) BooksOther Book> {} +collection(Dict) YearBooks Book> {} + +$c1 = new Books(); +$c1->add('one', new Book('Title 1')); +$c1->add('two', new Book('Title 2')); + +$c2 = new Books(); +$c2->add('one', new Book('Title 1')); +$c2->add('two', new Book('Title 2')); + +$c3 = new Books(); +$c3->add('one', new Book('Title 1')); +$c3->add('two', new Book('Title 2')); +$c3->add('tri', new Book('Title 3')); + +$c4 = new Books(); +$c4->add('one', new Book('Title 1')); +$c4->add('XXX', new Book('Title X')); +$c4->add('two', new Book('Title 2')); + +$c5 = new Books(); +$c5->add('one', new Book('Title 1')); +$c5->add('two', new Book('Title X')); +$c5->add('tri', new Book('Title 2')); + +$c6 = new Books(); +$c6->add('one', new Book('Title 1')); +$c6->add('XXX', new Book('Title 2')); +$c6->add('tri', new Book('Title 3')); + +$c7 = new BooksOther(); +$c7->add('one', new Book('Title 1')); +$c7->add('two', new Book('Title 2')); + +$c8 = new YearBooks(); +$c8->add(1970, new Book('Title 1')); +$c8->add(1971, new Book('Title 2')); + +$c9 = new Books(); +$c9->add('two', new Book('Title 2')); +$c9->add('one', new Book('Title 1')); + +// True +var_dump($c1 == $c1); +var_dump($c1 == $c2); +var_dump($c1 == $c7); +var_dump($c1 == $c9); + +$c2['tri'] = new Book('Title 3'); + +var_dump($c2 == $c3); + +// False +var_dump($c1 == $c2); +var_dump($c1 == $c8); +var_dump($c3 == $c4); +var_dump($c3 == $c5); +var_dump($c3 == $c6); + +unset($c4['XXX']); + +// True +var_dump($c1 == $c4); + +unset($c6['XXX']); +$c6['two'] = new Book('Title 2'); + +// True +var_dump($c3 == $c6); +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 85a3728232629..79e9ccd2f28f6 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -31,7 +31,8 @@ ZEND_API zend_class_entry *zend_ce_dict_collection; ZEND_API zend_object_handlers zend_seq_collection_object_handlers; ZEND_API zend_object_handlers zend_dict_collection_object_handlers; -static int seq_objects_compare(zval *object1, zval *object2); +static int seq_collection_compare(zval *object1, zval *object2); +static int dict_collection_compare(zval *object1, zval *object2); static int zend_implement_collection(zend_class_entry *interface, zend_class_entry *class_type) { @@ -53,14 +54,14 @@ void zend_register_collection_ce(void) memcpy(&zend_seq_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); zend_seq_collection_object_handlers.clone_obj = NULL; - zend_seq_collection_object_handlers.compare = seq_objects_compare; + zend_seq_collection_object_handlers.compare = seq_collection_compare; zend_ce_dict_collection = register_class_DictCollection(); zend_ce_dict_collection->interface_gets_implemented = zend_implement_collection; memcpy(&zend_dict_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); zend_dict_collection_object_handlers.clone_obj = NULL; - zend_dict_collection_object_handlers.compare = zend_objects_not_comparable; + zend_dict_collection_object_handlers.compare = dict_collection_compare; } void zend_collection_add_interfaces(zend_class_entry *ce) @@ -142,7 +143,7 @@ static int collection_is_equal_function(zval *z1, zval *z2) return Z_TYPE(result) == IS_TRUE ? 0 : 1; } -static int seq_objects_compare(zval *object1, zval *object2) +static int seq_collection_compare(zval *object1, zval *object2) { zval *value_prop_object1, *value_prop_object2; @@ -171,6 +172,39 @@ static int seq_objects_compare(zval *object1, zval *object2) ) != 0); } +static int dict_collection_compare(zval *object1, zval *object2) +{ + zval *value_prop_object1, *value_prop_object2; + + if (!object1 || !object2) { + return ZEND_UNCOMPARABLE; + } + + if (Z_TYPE_P(object1) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(object1), zend_ce_dict_collection)) { + return 1; + } + if (Z_TYPE_P(object2) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(object2), zend_ce_dict_collection)) { + return 1; + } + + if (Z_OBJ_P(object1) == Z_OBJ_P(object2)) { + return 0; + } + + value_prop_object1 = zend_read_property_ex(Z_OBJCE_P(object1), Z_OBJ_P(object1), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + value_prop_object2 = zend_read_property_ex(Z_OBJCE_P(object2), Z_OBJ_P(object2), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + if (!value_prop_object1 || !value_prop_object2) { + return 1; + } + + return (zend_hash_compare( + Z_ARRVAL_P(value_prop_object1), + Z_ARRVAL_P(value_prop_object2), + (compare_func_t) collection_is_equal_function, false + ) != 0); +} + static ZEND_NAMED_FUNCTION(zend_collection_seq_add_func) { zval *value; @@ -548,6 +582,29 @@ static ZEND_NAMED_FUNCTION(zend_collection_dict_concat_func) RETURN_OBJ(clone); } +static ZEND_NAMED_FUNCTION(zend_collection_dict_equals_func) +{ + zval *other; + zval *value_prop_us, *value_prop_other; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(other, zend_ce_dict_collection); + ZEND_PARSE_PARAMETERS_END(); + + if (Z_OBJ_P(ZEND_THIS) == Z_OBJ_P(other)) { + RETURN_TRUE; + } + + value_prop_us = zend_read_property_ex(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + value_prop_other = zend_read_property_ex(Z_OBJCE_P(other), Z_OBJ_P(other), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + if (!value_prop_us || !value_prop_other) { + RETURN_FALSE; + } + + RETURN_BOOL(zend_hash_compare(Z_ARRVAL_P(value_prop_us), Z_ARRVAL_P(value_prop_other), (compare_func_t) collection_is_equal_function, false) == 0); +} + static ZEND_NAMED_FUNCTION(zend_collection_dict_map_func) { zend_object *object = Z_OBJ_P(ZEND_THIS); @@ -680,6 +737,7 @@ void zend_collection_register_funcs(zend_class_entry *ce) REGISTER_FUNCTION(ZEND_STR_WITHOUT, zend_collection_dict_without_func, arginfo_class_DictCollection_without, 1); REGISTER_FUNCTION(ZEND_STR_SET, zend_collection_dict_set_func, arginfo_class_DictCollection_set, 2); REGISTER_FUNCTION(ZEND_STR_CONCAT, zend_collection_dict_concat_func, arginfo_class_DictCollection_concat, 1); + REGISTER_FUNCTION(ZEND_STR_EQUALS, zend_collection_dict_equals_func, arginfo_class_DictCollection_equals, 1); REGISTER_FUNCTION(ZEND_STR_MAP, zend_collection_dict_map_func, arginfo_class_DictCollection_map, 2); break; } diff --git a/Zend/zend_collection.stub.php b/Zend/zend_collection.stub.php index 9ddbea8363abc..28361b0c0a90e 100644 --- a/Zend/zend_collection.stub.php +++ b/Zend/zend_collection.stub.php @@ -57,10 +57,9 @@ public function set(mixed $key, mixed $value): static; // This should really be static $other, but the language doesn't allow that. // Equivalent of + static. public function concat(object $other): static; -/* + // True if both dicts have the same key/values in the same order. - public function equals(DictCollection $other): bool; -*/ + public function equals(object $other): bool; // $fn is callable(mixed $val, mixed $key) // The return type is $targetType, but that can't be expressed statically. diff --git a/Zend/zend_collection_arginfo.h b/Zend/zend_collection_arginfo.h index 5f5056b7cca3d..aed9f597e2344 100644 --- a/Zend/zend_collection_arginfo.h +++ b/Zend/zend_collection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 604adda1d78cf3889b92a91dda6ba54223fcd747 */ + * Stub hash: 81c3150a5c601ea8866a8efe1dd6b6621c1dd194 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_SeqCollection_add, 0, 1, IS_STATIC, 0) ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) @@ -64,6 +64,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_DictCollection_concat arginfo_class_SeqCollection_concat +#define arginfo_class_DictCollection_equals arginfo_class_SeqCollection_equals + #define arginfo_class_DictCollection_map arginfo_class_SeqCollection_map @@ -93,6 +95,7 @@ static const zend_function_entry class_DictCollection_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, without, arginfo_class_DictCollection_without, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, set, arginfo_class_DictCollection_set, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, concat, arginfo_class_DictCollection_concat, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, equals, arginfo_class_DictCollection_equals, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DictCollection, map, arginfo_class_DictCollection_map, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; From 7128fffcca56a47935adc24b39e7deb0add24f27 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 8 Aug 2023 15:33:10 +0100 Subject: [PATCH 43/48] Add short cut for comparisons if both objects are identical --- Zend/tests/collection/collection_seq_equality.phpt | 2 ++ .../tests/collection/collection_seq_equality_handler.phpt | 2 ++ Zend/zend_collection.c | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/Zend/tests/collection/collection_seq_equality.phpt b/Zend/tests/collection/collection_seq_equality.phpt index 55d8e9f675e61..e37c6a60d4c74 100644 --- a/Zend/tests/collection/collection_seq_equality.phpt +++ b/Zend/tests/collection/collection_seq_equality.phpt @@ -34,6 +34,7 @@ $c5->add(new Book('Title 1')); $c5->add(new Book('Title 2')); // True +var_dump($c1->equals($c1)); var_dump($c1->equals($c2)); var_dump($c1->equals($c5)); @@ -55,6 +56,7 @@ var_dump($c1->equals($c4)); --EXPECTF-- bool(true) bool(true) +bool(true) bool(false) bool(false) bool(true) diff --git a/Zend/tests/collection/collection_seq_equality_handler.phpt b/Zend/tests/collection/collection_seq_equality_handler.phpt index cc1ae0088fb48..07b19b2faa012 100644 --- a/Zend/tests/collection/collection_seq_equality_handler.phpt +++ b/Zend/tests/collection/collection_seq_equality_handler.phpt @@ -34,6 +34,7 @@ $c5->add(new Book('Title 1')); $c5->add(new Book('Title 2')); // True +var_dump($c1 == $c1); var_dump($c1 == $c2); var_dump($c1 == $c5); @@ -55,6 +56,7 @@ var_dump($c1 == $c4); --EXPECTF-- bool(true) bool(true) +bool(true) bool(false) bool(false) bool(true) diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index 79e9ccd2f28f6..f2beb71cac1c2 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -158,6 +158,10 @@ static int seq_collection_compare(zval *object1, zval *object2) return 1; } + if (Z_OBJ_P(object1) == Z_OBJ_P(object2)) { + return 0; + } + value_prop_object1 = zend_read_property_ex(Z_OBJCE_P(object1), Z_OBJ_P(object1), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); value_prop_object2 = zend_read_property_ex(Z_OBJCE_P(object2), Z_OBJ_P(object2), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); @@ -351,6 +355,10 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_equals_func) Z_PARAM_OBJECT_OF_CLASS(other, zend_ce_seq_collection); ZEND_PARSE_PARAMETERS_END(); + if (Z_OBJ_P(ZEND_THIS) == Z_OBJ_P(other)) { + RETURN_TRUE; + } + value_prop_us = zend_read_property_ex(Z_OBJCE_P(ZEND_THIS), Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); value_prop_other = zend_read_property_ex(Z_OBJCE_P(other), Z_OBJ_P(other), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); From 3ba4b115278d79c03fc3231b215bf4db8ed2ddf3 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Tue, 8 Aug 2023 15:33:52 +0100 Subject: [PATCH 44/48] Add test case to compare Seq with Dict --- .../collection_dict_int_equality.phpt | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 Zend/tests/collection/collection_dict_int_equality.phpt diff --git a/Zend/tests/collection/collection_dict_int_equality.phpt b/Zend/tests/collection/collection_dict_int_equality.phpt new file mode 100644 index 0000000000000..a2a9dd666e047 --- /dev/null +++ b/Zend/tests/collection/collection_dict_int_equality.phpt @@ -0,0 +1,36 @@ +--TEST-- +Collection: Dictionary/Sequence: Equals +--FILE-- + {} +collection(Dict) YearBooks Book> {} + +$s1 = new Books(); +$s1->add(new Book('Title 1')); +$s1->add(new Book('Title 2')); + +$d1 = new YearBooks(); +$d1->add(0, new Book('Title 1')); +$d1->add(1, new Book('Title 2')); + +// Exception +try { + var_dump($s1->equals($d1)); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} + +try { + var_dump($d1->equals($s1)); +} catch (TypeError $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +?> +--EXPECTF-- +TypeError: Books::equals(): Argument #1 ($other) must be of type SeqCollection, YearBooks given +TypeError: YearBooks::equals(): Argument #1 ($other) must be of type DictCollection, Books given From d5c03b9a8696f51bff6136b677c61247eb15afea Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 14 Aug 2023 11:13:19 +0100 Subject: [PATCH 45/48] Implement Sequence::+ and Sequence::+= operators --- .../collection_seq_operator_concat.phpt | 118 ++++++++++++++++++ Zend/zend_collection.c | 59 +++++++-- 2 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 Zend/tests/collection/collection_seq_operator_concat.phpt diff --git a/Zend/tests/collection/collection_seq_operator_concat.phpt b/Zend/tests/collection/collection_seq_operator_concat.phpt new file mode 100644 index 0000000000000..2da9bd0e5a4a6 --- /dev/null +++ b/Zend/tests/collection/collection_seq_operator_concat.phpt @@ -0,0 +1,118 @@ +--TEST-- +Collection: Sequence: Concat +--FILE-- + {} + +$c1 = new Books(); +$c2 = new Books(); + +$c1->add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +$c2->add(new Book('Title 3')); +$c2->add(new Book('Title 4')); + +$c3 = $c1 + $c2; + +// Four items. +var_dump($c3); + +// Still two items. +var_dump($c1); + +$c1 += $c2; + +// Four items. +var_dump($c1); + +// Still two items. +var_dump($c2); + +?> +--EXPECTF-- +object(Books)#%d (1) { + ["value"]=> + array(4) { + [0]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [1]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + [2]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 3" + } + [3]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 4" + } + } +} +object(Books)#%d (1) { + ["value"]=> + array(2) { + [0]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [1]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + } +} +object(Books)#%d (1) { + ["value"]=> + array(4) { + [0]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [1]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + [2]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 3" + } + [3]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 4" + } + } +} +object(Books)#%d (1) { + ["value"]=> + array(2) { + [0]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 3" + } + [1]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 4" + } + } +} diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index f2beb71cac1c2..e52e524f2cafc 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -34,6 +34,8 @@ ZEND_API zend_object_handlers zend_dict_collection_object_handlers; static int seq_collection_compare(zval *object1, zval *object2); static int dict_collection_compare(zval *object1, zval *object2); +static zend_result seq_collection_do_operation(uint8_t opcode, zval *result, zval *op1, zval *op2); + static int zend_implement_collection(zend_class_entry *interface, zend_class_entry *class_type) { if (class_type->ce_flags & ZEND_ACC_COLLECTION) { @@ -55,6 +57,7 @@ void zend_register_collection_ce(void) memcpy(&zend_seq_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); zend_seq_collection_object_handlers.clone_obj = NULL; zend_seq_collection_object_handlers.compare = seq_collection_compare; + zend_seq_collection_object_handlers.do_operation = seq_collection_do_operation; zend_ce_dict_collection = register_class_DictCollection(); zend_ce_dict_collection->interface_gets_implemented = zend_implement_collection; @@ -176,6 +179,51 @@ static int seq_collection_compare(zval *object1, zval *object2) ) != 0); } +static void seq_copy_add_elements(zend_object *clone, zval *other) +{ + zend_class_entry *ce = clone->ce; + zval *value_prop; + zval *element; + + value_prop = zend_read_property_ex(ce, Z_OBJ_P(other), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value_prop), element) { + if (!zend_collection_add_item(clone, NULL, element)) { + return; + } + } ZEND_HASH_FOREACH_END(); +} + +static zend_result seq_collection_do_operation_add_impl(uint8_t opcode, zval *result, zval *op1, zval *op2) +{ + zend_object *clone; + + clone = zend_objects_clone_obj(Z_OBJ_P(op1)); + + seq_copy_add_elements(clone, op2); + + ZVAL_OBJ(result, clone); + + return SUCCESS; +} + +static zend_result seq_collection_do_operation(uint8_t opcode, zval *result, zval *op1, zval *op2) +{ + if (Z_TYPE_P(op1) != IS_OBJECT || Z_TYPE_P(op2) != IS_OBJECT) { + return FAILURE; + } + + if (!instanceof_function(Z_OBJCE_P(op1), zend_ce_seq_collection) || !instanceof_function(Z_OBJCE_P(op2), zend_ce_seq_collection)) { + return FAILURE; + } + + switch (opcode) { + case ZEND_ADD: + return seq_collection_do_operation_add_impl(opcode, result, op1, op2); + } + return FAILURE; +} + static int dict_collection_compare(zval *object1, zval *object2) { zval *value_prop_object1, *value_prop_object2; @@ -325,9 +373,6 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_concat_func) { zval *other; zend_object *clone; - zend_class_entry *ce = Z_OBJCE_P(ZEND_THIS); - zval *value_prop; - zval *element; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJECT_OF_CLASS(other, zend_ce_seq_collection); @@ -335,13 +380,7 @@ static ZEND_NAMED_FUNCTION(zend_collection_seq_concat_func) clone = zend_objects_clone_obj(Z_OBJ_P(ZEND_THIS)); - value_prop = zend_read_property_ex(ce, Z_OBJ_P(other), ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL); - - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value_prop), element) { - if (!zend_collection_add_item(clone, NULL, element)) { - return; - } - } ZEND_HASH_FOREACH_END(); + seq_copy_add_elements(clone, other); RETURN_OBJ(clone); } From 874d099dd3fed078a882ddc289f1dbf0d5c8b569 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Wed, 9 Aug 2023 00:32:23 +0100 Subject: [PATCH 46/48] Renumber indexes in Sequence collections when an item is removed --- .../collection/collection_seq_basic.phpt | 2 ++ .../collection/collection_seq_equality.phpt | 4 +-- .../collection_seq_equality_handler.phpt | 4 +-- .../collection/collection_seq_modify.phpt | 32 +++++++++---------- Zend/zend_collection.c | 13 ++++++++ 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Zend/tests/collection/collection_seq_basic.phpt b/Zend/tests/collection/collection_seq_basic.phpt index 197a9798e5764..e6bcab88e5c74 100644 --- a/Zend/tests/collection/collection_seq_basic.phpt +++ b/Zend/tests/collection/collection_seq_basic.phpt @@ -29,6 +29,7 @@ var_dump($c[1]); unset($c[1]); var_dump(isset($c[1])); +var_dump(isset($c[2])); try { var_dump(isset($c["eleven"])); @@ -64,5 +65,6 @@ object(Article)#%d (%d) { ["title"]=> string(11) "Second Test" } +bool(true) bool(false) TypeError: Key type string of element does not match Articles sequence key type int diff --git a/Zend/tests/collection/collection_seq_equality.phpt b/Zend/tests/collection/collection_seq_equality.phpt index e37c6a60d4c74..aa93c3a9c081d 100644 --- a/Zend/tests/collection/collection_seq_equality.phpt +++ b/Zend/tests/collection/collection_seq_equality.phpt @@ -50,7 +50,7 @@ unset($c4[1]); // True var_dump($c1->equals($c3)); -// False (indexes aren't both ordered without gaps) +// True (indexes have been automatically renumbered) var_dump($c1->equals($c4)); ?> --EXPECTF-- @@ -60,4 +60,4 @@ bool(true) bool(false) bool(false) bool(true) -bool(false) +bool(true) diff --git a/Zend/tests/collection/collection_seq_equality_handler.phpt b/Zend/tests/collection/collection_seq_equality_handler.phpt index 07b19b2faa012..06f48e7a659a8 100644 --- a/Zend/tests/collection/collection_seq_equality_handler.phpt +++ b/Zend/tests/collection/collection_seq_equality_handler.phpt @@ -50,7 +50,7 @@ unset($c4[1]); // True var_dump($c1 == $c3); -// False (indexes aren't both ordered without gaps) +// True (indexes have been automatically renumbered) var_dump($c1 == $c4); ?> --EXPECTF-- @@ -60,4 +60,4 @@ bool(true) bool(false) bool(false) bool(true) -bool(false) +bool(true) diff --git a/Zend/tests/collection/collection_seq_modify.phpt b/Zend/tests/collection/collection_seq_modify.phpt index 085a1d3b2dde7..17d27867e949e 100644 --- a/Zend/tests/collection/collection_seq_modify.phpt +++ b/Zend/tests/collection/collection_seq_modify.phpt @@ -23,13 +23,13 @@ echo "\nShould have one item:\n"; var_dump($c); echo "\nShould be false:\n"; -var_dump($c->has(0)); -var_dump(isset($c[0])); - -echo "\nShould be true:\n"; var_dump($c->has(1)); var_dump(isset($c[1])); +echo "\nShould be true:\n"; +var_dump($c->has(0)); +var_dump(isset($c[0])); + $c2 = $c->with(new Book('Title 3')); echo "\nStill one item:\n"; @@ -46,12 +46,12 @@ var_dump($c2); echo "\nBut only one item here:\n"; var_dump($c3); -$c3->set(2, new Book('Title 4')); +$c3->set(0, new Book('Title 4')); echo "\nOnly 'Title 4' now exists:\n"; var_dump($c3); -unset($c3[2]); +unset($c3[0]); echo "\nEmpty:\n"; var_dump($c3); @@ -85,7 +85,7 @@ Should have one item: object(Books)#1 (1) { ["value"]=> array(1) { - [1]=> + [0]=> object(Book)#3 (1) { ["title"]=> string(7) "Title 2" @@ -105,7 +105,7 @@ Still one item: object(Books)#1 (1) { ["value"]=> array(1) { - [1]=> + [0]=> object(Book)#3 (1) { ["title"]=> string(7) "Title 2" @@ -117,12 +117,12 @@ But this has 2 items: object(Books)#4 (1) { ["value"]=> array(2) { - [1]=> + [0]=> object(Book)#3 (1) { ["title"]=> string(7) "Title 2" } - [2]=> + [1]=> object(Book)#2 (1) { ["title"]=> string(7) "Title 3" @@ -134,12 +134,12 @@ Still two items: object(Books)#4 (1) { ["value"]=> array(2) { - [1]=> + [0]=> object(Book)#3 (1) { ["title"]=> string(7) "Title 2" } - [2]=> + [1]=> object(Book)#2 (1) { ["title"]=> string(7) "Title 3" @@ -151,10 +151,10 @@ But only one item here: object(Books)#5 (1) { ["value"]=> array(1) { - [2]=> - object(Book)#2 (1) { + [0]=> + object(Book)#3 (1) { ["title"]=> - string(7) "Title 3" + string(7) "Title 2" } } } @@ -163,7 +163,7 @@ Only 'Title 4' now exists: object(Books)#5 (1) { ["value"]=> array(1) { - [2]=> + [0]=> object(Book)#6 (1) { ["title"]=> string(7) "Title 4" diff --git a/Zend/zend_collection.c b/Zend/zend_collection.c index e52e524f2cafc..01607e8777093 100644 --- a/Zend/zend_collection.c +++ b/Zend/zend_collection.c @@ -1008,9 +1008,22 @@ void zend_collection_unset_item(zend_object *object, zval *offset) value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv); SEPARATE_ARRAY(value_prop); + // TODO: Should unset throw when an item with key 'offset' does not exist? + if (Z_TYPE_P(offset) == IS_STRING) { zend_hash_del(HASH_OF(value_prop), Z_STR_P(offset)); } else { + zend_array *new_array; + zval new_zval; + zend_hash_index_del(HASH_OF(value_prop), Z_LVAL_P(offset)); + + if (!HT_IS_WITHOUT_HOLES(HASH_OF(value_prop))) { + new_array = zend_array_to_list(HASH_OF(value_prop)); + ZVAL_ARR(&new_zval, new_array); + Z_PROP_FLAG_P(value_prop) |= IS_PROP_REINITABLE; + zend_update_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), &new_zval); + Z_PROP_FLAG_P(value_prop) &= IS_PROP_REINITABLE; + } } } From 383b3e18d66eb498a36badd2f68d28dcbc4fceac Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 3 Jun 2024 19:06:20 +0100 Subject: [PATCH 47/48] Fixed invocation for zend_compile_typename --- Zend/zend_compile.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5cb2638721cf9..63fe850fc47cc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8976,7 +8976,7 @@ static void zend_compile_collection_data_structure(zend_class_entry *ce, zend_as static void zend_compile_collection_key_type(zend_class_entry *ce, zend_ast *collection_key_type_ast) { ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); - zend_type type = zend_compile_typename(collection_key_type_ast, 0); + zend_type type = zend_compile_typename(collection_key_type_ast); uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); if (ZEND_TYPE_IS_COMPLEX(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) { zend_string *type_string = zend_type_to_string(type); @@ -8996,7 +8996,7 @@ static void zend_compile_collection_key_type(zend_class_entry *ce, zend_ast *col static void zend_compile_collection_item_type(zend_class_entry *ce, zend_ast *collection_item_type_ast) { ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION); - zend_type type = zend_compile_typename(collection_item_type_ast, 0); + zend_type type = zend_compile_typename(collection_item_type_ast); if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_NEVER|MAY_BE_CALLABLE)) { zend_string *str = zend_type_to_string(type); From 4693f3b033cd2c064dc586b9aa88f903eb32abbe Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Mon, 3 Jun 2024 19:06:30 +0100 Subject: [PATCH 48/48] Add extra test file --- .../collection_seq_operator_subtract.phpt | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Zend/tests/collection/collection_seq_operator_subtract.phpt diff --git a/Zend/tests/collection/collection_seq_operator_subtract.phpt b/Zend/tests/collection/collection_seq_operator_subtract.phpt new file mode 100644 index 0000000000000..e3f8d2c1f999f --- /dev/null +++ b/Zend/tests/collection/collection_seq_operator_subtract.phpt @@ -0,0 +1,43 @@ +--TEST-- +Collection: Sequence: Concat +--FILE-- + {} + +$c1 = new Books(); +$c2 = new Books(); + +$c1->add(new Book('Title 1')); +$c1->add(new Book('Title 2')); + +$c2->add(new Book('Title 3')); +$c2->add(new Book('Title 4')); + +$c3 = $c1 + $c2; +$c4 = $c3 - $c2; + +// Two items again +var_dump($c4); + +?> +--EXPECTF-- +object(Books)#%d (1) { + ["value"]=> + array(2) { + [0]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 1" + } + [1]=> + object(Book)#%d (1) { + ["title"]=> + string(7) "Title 2" + } + } +}