diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 211a5d14e6c3a..bd3f89a245025 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2997,6 +2997,7 @@ ZEND_API void zend_convert_internal_arg_info(zend_arg_info *new_arg_info, const new_arg_info->name = NULL; new_arg_info->default_value = NULL; } + new_arg_info->doc_comment = NULL; new_arg_info->type = arg_info->type; zend_convert_internal_arg_info_type(&new_arg_info->type, persistent); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ee83ee75ff6ea..691445a9f4a58 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8024,6 +8024,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 } else { arg_infos->type = (zend_type) ZEND_TYPE_INIT_CODE(fallback_return_type, 0, 0); } + arg_infos->doc_comment = NULL; arg_infos++; op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE; @@ -8122,6 +8123,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 arg_info->name = zend_string_copy(name); arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(0); arg_info->default_value = NULL; + arg_info->doc_comment = doc_comment_ast ? zend_string_copy(zend_ast_get_str(doc_comment_ast)) : NULL; if (attributes_ast) { zend_compile_attributes( diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 5414467f3f874..abe2a53fe7448 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -515,6 +515,7 @@ typedef struct _zend_arg_info { zend_string *name; zend_type type; zend_string *default_value; + zend_string *doc_comment; } zend_arg_info; /* the following structure repeats the layout of zend_internal_arg_info, diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e2686c7e1c5a8..dbd61319de611 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -817,13 +817,13 @@ optional_cpp_modifiers: parameter: optional_cpp_modifiers optional_type_without_static - is_reference is_variadic T_VARIABLE backup_doc_comment optional_property_hook_list + is_reference is_variadic T_VARIABLE optional_property_hook_list backup_doc_comment { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, NULL, - NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL, $7); } + NULL, $7 ? zend_ast_create_zval_from_str($7) : NULL, $6); } | optional_cpp_modifiers optional_type_without_static - is_reference is_variadic T_VARIABLE backup_doc_comment '=' expr optional_property_hook_list - { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, $8, - NULL, $6 ? zend_ast_create_zval_from_str($6) : NULL, $9); } + is_reference is_variadic T_VARIABLE '=' expr optional_property_hook_list backup_doc_comment + { $$ = zend_ast_create_ex(ZEND_AST_PARAM, $1 | $3 | $4, $2, $5, $7, + NULL, $9 ? zend_ast_create_zval_from_str($9) : NULL, $8); } ; optional_type_without_static: diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 24b480ad71e66..35de02b557298 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -648,6 +648,9 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) if (arg_info[i].name) { zend_string_release_ex(arg_info[i].name, 0); } + if (arg_info[i].doc_comment) { + zend_string_release_ex(arg_info[i].doc_comment, 0); + } zend_type_release(arg_info[i].type, /* persistent */ false); } efree(arg_info); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 9bc2496837ce4..568db085bb2c4 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -652,6 +652,9 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc zend_accel_store_interned_string(arg_info[i].name); } zend_persist_type(&arg_info[i].type); + if (arg_info[i].doc_comment) { + zend_accel_store_interned_string(arg_info[i].doc_comment); + } } if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { arg_info++; diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 0b0ff51d0d4df..657cc03eb3901 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -306,6 +306,9 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) ADD_INTERNED_STRING(arg_info[i].name); } zend_persist_type_calc(&arg_info[i].type); + if (arg_info[i].doc_comment) { + ADD_INTERNED_STRING(arg_info[i].doc_comment); + } } } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index ee49d2d90f507..452313025ee35 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -2649,6 +2649,22 @@ ZEND_METHOD(ReflectionParameter, __toString) /* }}} */ +/* {{{ Returns the doc comment for this parameter */ +ZEND_METHOD(ReflectionParameter, getDocComment) +{ + reflection_object *intern; + parameter_reference *param; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(param); + if (param->arg_info->doc_comment) { + RETURN_STR_COPY(param->arg_info->doc_comment); + } + RETURN_FALSE; +} +/* }}} */ + /* {{{ Returns this parameter's name */ ZEND_METHOD(ReflectionParameter, getName) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index b0273a3174f8e..dd605100f8ba7 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -652,6 +652,8 @@ public function __construct($function, int|string $param) {} public function __toString(): string {} + public function getDocComment(): string|false {} + /** @tentative-return-type */ public function getName(): string {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 66605a22bbd66..65571f38d43c7 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41 + * Stub hash: c80946cc8c8215bb6527e09bb71b3a97a76a6a98 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) @@ -526,6 +526,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionParameter___toString arginfo_class_ReflectionFunction___toString +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_ReflectionParameter_getDocComment, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionParameter_getName arginfo_class_ReflectionFunctionAbstract_getName #define arginfo_class_ReflectionParameter_isPassedByReference arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -721,13 +724,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionConstant_isDeprecated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_ReflectionConstant_getFileName, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) -ZEND_END_ARG_INFO() +#define arginfo_class_ReflectionConstant_getFileName arginfo_class_ReflectionParameter_getDocComment ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionConstant_getExtension, 0, 0, ReflectionExtension, 1) ZEND_END_ARG_INFO() -#define arginfo_class_ReflectionConstant_getExtensionName arginfo_class_ReflectionConstant_getFileName +#define arginfo_class_ReflectionConstant_getExtensionName arginfo_class_ReflectionParameter_getDocComment #define arginfo_class_ReflectionConstant___toString arginfo_class_ReflectionFunction___toString @@ -921,6 +923,7 @@ ZEND_METHOD(ReflectionClassConstant, hasType); ZEND_METHOD(ReflectionClassConstant, getType); ZEND_METHOD(ReflectionParameter, __construct); ZEND_METHOD(ReflectionParameter, __toString); +ZEND_METHOD(ReflectionParameter, getDocComment); ZEND_METHOD(ReflectionParameter, getName); ZEND_METHOD(ReflectionParameter, isPassedByReference); ZEND_METHOD(ReflectionParameter, canBePassedByValue); @@ -1237,6 +1240,7 @@ static const zend_function_entry class_ReflectionParameter_methods[] = { ZEND_RAW_FENTRY("__clone", zim_ReflectionClass___clone, arginfo_class_ReflectionParameter___clone, ZEND_ACC_PRIVATE, NULL, NULL) ZEND_ME(ReflectionParameter, __construct, arginfo_class_ReflectionParameter___construct, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionParameter, __toString, arginfo_class_ReflectionParameter___toString, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionParameter, getDocComment, arginfo_class_ReflectionParameter_getDocComment, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionParameter, getName, arginfo_class_ReflectionParameter_getName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionParameter, isPassedByReference, arginfo_class_ReflectionParameter_isPassedByReference, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionParameter, canBePassedByValue, arginfo_class_ReflectionParameter_canBePassedByValue, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index a5e8affd0beb1..a87e1635419bf 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,12 +1,12 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: 267472e2b726ca5e788eb5cc3e946bc9aa7c9c41 */ + * Stub hash: c80946cc8c8215bb6527e09bb71b3a97a76a6a98 */ -#ifndef ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H -#define ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H +#ifndef ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H +#define ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, ZEND_ENUM_PropertyHookType_Set = 2, } zend_enum_PropertyHookType; -#endif /* ZEND_PHP_REFLECTION_DECL_267472e2b726ca5e788eb5cc3e946bc9aa7c9c41_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H */ diff --git a/ext/reflection/tests/ReflectionParameter_getDocComment_basic.phpt b/ext/reflection/tests/ReflectionParameter_getDocComment_basic.phpt new file mode 100644 index 0000000000000..46e47bf603248 --- /dev/null +++ b/ext/reflection/tests/ReflectionParameter_getDocComment_basic.phpt @@ -0,0 +1,248 @@ +--TEST-- +Test ReflectionParameter::getDocComment() usage. +--INI-- +opcache.save_comments=1 +--FILE-- +property = $value; } +} +} + +function global_function( + /** + * My Doc Comment for $a + * + */ + $a, $b, $c, + /** + * My Doc Comment for $d + */ + $d, + // Not a doc comment + /**Not a doc comment */ + $e, + /** + * Doc comment for $f + */ + $f, + $g /** Doc comment for $g after parameter */, + /** Doc comment for $h */ + $h /** Doc comment for $h after parameter */, +) {} + +$closure = function( + /** + * My Doc Comment for $a + * + */ + $a, $b, $c, + /** + * My Doc Comment for $d + */ + $d, + // Not a doc comment + /**Not a doc comment */ + $e, + /** + * Doc comment for $f + */ + $f, + $g /** Doc comment for $g after parameter */, + /** Doc comment for $h */ + $h /** Doc comment for $h after parameter */, +) {}; + +$arrow_function = fn( + /** + * My Doc Comment for $a + * + */ + $a, $b, $c, + /** + * My Doc Comment for $d + */ + $d, + // Not a doc comment + /**Not a doc comment */ + $e, + /** + * Doc comment for $f + */ + $f, + $g /** Doc comment for $g after parameter */, + /** Doc comment for $h */ + $h /** Doc comment for $h after parameter */, +) => true; + +foreach([ + 'A::method' => (new ReflectionClass('A'))->getMethod('method'), + 'global_function' => new ReflectionFunction('global_function'), + 'closure' => new ReflectionFunction($closure), + 'arrow_function' => new ReflectionFunction($arrow_function), + 'property hook' => (new ReflectionClass('A'))->getProperty('property')->getHook(PropertyHookType::Set), + ] as $function => $rc) { + $rps = $rc->getParameters(); + foreach($rps as $rp) { + echo "\n---> Doc comment for $function parameter $" . $rp->getName() . ":\n"; + var_dump($rp->getDocComment()); + } +} + +?> +--EXPECTF-- +---> Doc comment for A::method parameter $a: +string(%d) "/** + * My Doc Comment for $a + * + */" + +---> Doc comment for A::method parameter $b: +bool(false) + +---> Doc comment for A::method parameter $c: +bool(false) + +---> Doc comment for A::method parameter $d: +string(%d) "/** + * My Doc Comment for $d + */" + +---> Doc comment for A::method parameter $e: +bool(false) + +---> Doc comment for A::method parameter $f: +string(%d) "/** + * Doc comment for $f + */" + +---> Doc comment for A::method parameter $g: +string(%d) "/** Doc comment for $g after parameter */" + +---> Doc comment for A::method parameter $h: +string(%d) "/** Doc comment for $h after parameter */" + +---> Doc comment for global_function parameter $a: +string(%d) "/** + * My Doc Comment for $a + * + */" + +---> Doc comment for global_function parameter $b: +bool(false) + +---> Doc comment for global_function parameter $c: +bool(false) + +---> Doc comment for global_function parameter $d: +string(%d) "/** + * My Doc Comment for $d + */" + +---> Doc comment for global_function parameter $e: +bool(false) + +---> Doc comment for global_function parameter $f: +string(%d) "/** + * Doc comment for $f + */" + +---> Doc comment for global_function parameter $g: +string(%d) "/** Doc comment for $g after parameter */" + +---> Doc comment for global_function parameter $h: +string(%d) "/** Doc comment for $h after parameter */" + +---> Doc comment for closure parameter $a: +string(%d) "/** + * My Doc Comment for $a + * + */" + +---> Doc comment for closure parameter $b: +bool(false) + +---> Doc comment for closure parameter $c: +bool(false) + +---> Doc comment for closure parameter $d: +string(%d) "/** + * My Doc Comment for $d + */" + +---> Doc comment for closure parameter $e: +bool(false) + +---> Doc comment for closure parameter $f: +string(%d) "/** + * Doc comment for $f + */" + +---> Doc comment for closure parameter $g: +string(%d) "/** Doc comment for $g after parameter */" + +---> Doc comment for closure parameter $h: +string(%d) "/** Doc comment for $h after parameter */" + +---> Doc comment for arrow_function parameter $a: +string(%d) "/** + * My Doc Comment for $a + * + */" + +---> Doc comment for arrow_function parameter $b: +bool(false) + +---> Doc comment for arrow_function parameter $c: +bool(false) + +---> Doc comment for arrow_function parameter $d: +string(%d) "/** + * My Doc Comment for $d + */" + +---> Doc comment for arrow_function parameter $e: +bool(false) + +---> Doc comment for arrow_function parameter $f: +string(%d) "/** + * Doc comment for $f + */" + +---> Doc comment for arrow_function parameter $g: +string(%d) "/** Doc comment for $g after parameter */" + +---> Doc comment for arrow_function parameter $h: +string(%d) "/** Doc comment for $h after parameter */" + +---> Doc comment for property hook parameter $value: +string(%d) "/** Doc Comment for property hook parameter $value */" +