mirror of
https://github.com/php/php-src.git
synced 2024-11-23 18:04:36 +08:00
Convert iterable into an internal alias for Traversable|array (#7309)
This does a compile time transformation of ``iterable`` into ``Traversable|array`` which simplifies some of the LSP variance handling. The arginfo generation script from stubs is updated to produce a union type when it encounters the type ``iterable`` Extension functions which do not regenerate the arginfo, or write them manually are still supported by mimicking the compile time transformation while registering the function. Type Reflection is preserved for single ``iterable`` (and ``?iterable``) to produce a ReflectionNamedType with name ``iterable``, however usage of ``iterable`` in union types will be converted to ``array|Traversable``
This commit is contained in:
parent
94183d3e8b
commit
b40ae80804
@ -2114,9 +2114,6 @@ static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) {
|
||||
if (type_mask & MAY_BE_CALLABLE) {
|
||||
result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
|
||||
}
|
||||
if (type_mask & MAY_BE_ITERABLE) {
|
||||
result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
|
||||
}
|
||||
if (type_mask & MAY_BE_STATIC) {
|
||||
result_mask |= MAY_BE_OBJECT;
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ function test6() : object|callable {
|
||||
yield 6;
|
||||
}
|
||||
|
||||
function test7() : iterable {
|
||||
yield 7;
|
||||
}
|
||||
|
||||
var_dump(
|
||||
test1(),
|
||||
test2(),
|
||||
@ -33,6 +37,7 @@ var_dump(
|
||||
test4(),
|
||||
test5(),
|
||||
test6(),
|
||||
test7(),
|
||||
);
|
||||
?>
|
||||
--EXPECTF--
|
||||
@ -48,3 +53,5 @@ object(Generator)#%d (%d) {
|
||||
}
|
||||
object(Generator)#%d (%d) {
|
||||
}
|
||||
object(Generator)#%d (%d) {
|
||||
}
|
||||
|
@ -7,4 +7,4 @@ function foo(): iterable&Iterator {}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type iterable cannot be part of an intersection type in %s on line %d
|
||||
Fatal error: Type Traversable|array cannot be part of an intersection type in %s on line %d
|
||||
|
@ -15,4 +15,4 @@ class Test2 extends Test {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): iterable in %s on line %d
|
||||
Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): Traversable|array in %s on line %d
|
||||
|
@ -45,4 +45,4 @@ object(ArrayIterator)#1 (1) {
|
||||
int(3)
|
||||
}
|
||||
}
|
||||
test(): Argument #1 ($iterable) must be of type iterable, int given, called in %s on line %d
|
||||
test(): Argument #1 ($iterable) must be of type Traversable|array, int given, called in %s on line %d
|
@ -17,4 +17,4 @@ function baz(iterable $iterable = 1) {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Cannot use int as default value for parameter $iterable of type iterable in %s on line %d
|
||||
Fatal error: Cannot use int as default value for parameter $iterable of type Traversable|array in %s on line %d
|
@ -29,4 +29,4 @@ array(0) {
|
||||
}
|
||||
object(Generator)#2 (0) {
|
||||
}
|
||||
baz(): Return value must be of type iterable, int returned
|
||||
baz(): Return value must be of type Traversable|array, int returned
|
@ -21,4 +21,4 @@ class Bar extends Foo {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of Bar::testScalar(iterable $iterable) must be compatible with Foo::testScalar(int $int) in %s on line %d
|
||||
Fatal error: Declaration of Bar::testScalar(Traversable|array $iterable) must be compatible with Foo::testScalar(int $int) in %s on line %d
|
@ -29,4 +29,4 @@ class TestScalar extends Test {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): iterable in %s on line %d
|
||||
Fatal error: Declaration of TestScalar::method(): int must be compatible with Test::method(): Traversable|array in %s on line %d
|
46
Zend/tests/type_declarations/iterable/or_null.phpt
Normal file
46
Zend/tests/type_declarations/iterable/or_null.phpt
Normal file
@ -0,0 +1,46 @@
|
||||
--TEST--
|
||||
Test "or null"/"or be null" in type-checking errors for userland functions with iterable
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
// This should test every branch in zend_execute.c's `zend_verify_arg_type`, `zend_verify_return_type` and `zend_verify_missing_return_type` functions which produces an "or null"/"or be null" part in its error message
|
||||
|
||||
function iterableF(?iterable $param) {}
|
||||
try {
|
||||
iterableF(1);
|
||||
} catch (\TypeError $e) {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function returnIterable(): ?iterable {
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
returnIterable();
|
||||
} catch (\TypeError $e) {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function returnMissingIterable(): ?iterable {
|
||||
}
|
||||
|
||||
try {
|
||||
returnMissingIterable();
|
||||
} catch (\TypeError $e) {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
?>
|
||||
--EXPECTF--
|
||||
TypeError: iterableF(): Argument #1 ($param) must be of type Traversable|array|null, int given, called in %s on line %d and defined in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(%d): iterableF(1)
|
||||
#1 {main}
|
||||
TypeError: returnIterable(): Return value must be of type Traversable|array|null, int returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(%d): returnIterable()
|
||||
#1 {main}
|
||||
TypeError: returnMissingIterable(): Return value must be of type Traversable|array|null, none returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(%d): returnMissingIterable()
|
||||
#1 {main}
|
@ -8,4 +8,4 @@ function test(): iterable|Traversable {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type Traversable|iterable contains both iterable and Traversable, which is redundant in %s on line %d
|
||||
Fatal error: Duplicate type Traversable is redundant in %s on line %d
|
||||
|
@ -8,4 +8,4 @@ function test(): iterable|Traversable|ArrayAccess {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type Traversable|ArrayAccess|iterable contains both iterable and Traversable, which is redundant in %s on line %d
|
||||
Fatal error: Duplicate type Traversable is redundant in %s on line %d
|
||||
|
@ -8,4 +8,4 @@ function test(): iterable|array {
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Type iterable|array contains both iterable and array, which is redundant in %s on line %d
|
||||
Fatal error: Duplicate type array is redundant in %s on line %d
|
||||
|
@ -57,14 +57,6 @@ try {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function iterableF(?iterable $param) {}
|
||||
|
||||
try {
|
||||
iterableF(1);
|
||||
} catch (\TypeError $e) {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function intF(?int $param) {}
|
||||
|
||||
try {
|
||||
@ -143,16 +135,6 @@ try {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function returnIterable(): ?iterable {
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
returnIterable();
|
||||
} catch (\TypeError $e) {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function returnInt(): ?int {
|
||||
return new \StdClass;
|
||||
}
|
||||
@ -199,15 +181,6 @@ try {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function returnMissingIterable(): ?iterable {
|
||||
}
|
||||
|
||||
try {
|
||||
returnMissingIterable();
|
||||
} catch (\TypeError $e) {
|
||||
echo $e, PHP_EOL;
|
||||
}
|
||||
|
||||
function returnMissingInt(): ?int {
|
||||
}
|
||||
|
||||
@ -221,97 +194,85 @@ try {
|
||||
--EXPECTF--
|
||||
TypeError: unloadedClass(): Argument #1 ($param) must be of type ?I\Dont\Exist, stdClass given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(8): unloadedClass(Object(stdClass))
|
||||
#0 %s(%d): unloadedClass(Object(stdClass))
|
||||
#1 {main}
|
||||
TypeError: loadedClass(): Argument #1 ($param) must be of type ?RealClass, stdClass given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(20): loadedClass(Object(stdClass))
|
||||
#0 %s(%d): loadedClass(Object(stdClass))
|
||||
#1 {main}
|
||||
TypeError: loadedInterface(): Argument #1 ($param) must be of type ?RealInterface, stdClass given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(26): loadedInterface(Object(stdClass))
|
||||
#0 %s(%d): loadedInterface(Object(stdClass))
|
||||
#1 {main}
|
||||
TypeError: unloadedClass(): Argument #1 ($param) must be of type ?I\Dont\Exist, int given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(32): unloadedClass(1)
|
||||
#0 %s(%d): unloadedClass(1)
|
||||
#1 {main}
|
||||
TypeError: loadedClass(): Argument #1 ($param) must be of type ?RealClass, int given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(38): loadedClass(1)
|
||||
#0 %s(%d): loadedClass(1)
|
||||
#1 {main}
|
||||
TypeError: loadedInterface(): Argument #1 ($param) must be of type ?RealInterface, int given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(44): loadedInterface(1)
|
||||
#0 %s(%d): loadedInterface(1)
|
||||
#1 {main}
|
||||
TypeError: callableF(): Argument #1 ($param) must be of type ?callable, int given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(52): callableF(1)
|
||||
#1 {main}
|
||||
TypeError: iterableF(): Argument #1 ($param) must be of type ?iterable, int given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(60): iterableF(1)
|
||||
#0 %s(%d): callableF(1)
|
||||
#1 {main}
|
||||
TypeError: intF(): Argument #1 ($param) must be of type ?int, stdClass given, called in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(68): intF(Object(stdClass))
|
||||
#0 %s(%d): intF(Object(stdClass))
|
||||
#1 {main}
|
||||
TypeError: returnUnloadedClass(): Return value must be of type ?I\Dont\Exist, stdClass returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(78): returnUnloadedClass()
|
||||
#0 %s(%d): returnUnloadedClass()
|
||||
#1 {main}
|
||||
TypeError: returnLoadedClass(): Return value must be of type ?RealClass, stdClass returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(88): returnLoadedClass()
|
||||
#0 %s(%d): returnLoadedClass()
|
||||
#1 {main}
|
||||
TypeError: returnLoadedInterface(): Return value must be of type ?RealInterface, stdClass returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(98): returnLoadedInterface()
|
||||
#0 %s(%d): returnLoadedInterface()
|
||||
#1 {main}
|
||||
TypeError: returnUnloadedClassScalar(): Return value must be of type ?I\Dont\Exist, int returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(108): returnUnloadedClassScalar()
|
||||
#0 %s(%d): returnUnloadedClassScalar()
|
||||
#1 {main}
|
||||
TypeError: returnLoadedClassScalar(): Return value must be of type ?RealClass, int returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(118): returnLoadedClassScalar()
|
||||
#0 %s(%d): returnLoadedClassScalar()
|
||||
#1 {main}
|
||||
TypeError: returnLoadedInterfaceScalar(): Return value must be of type ?RealInterface, int returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(128): returnLoadedInterfaceScalar()
|
||||
#0 %s(%d): returnLoadedInterfaceScalar()
|
||||
#1 {main}
|
||||
TypeError: returnCallable(): Return value must be of type ?callable, int returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(138): returnCallable()
|
||||
#1 {main}
|
||||
TypeError: returnIterable(): Return value must be of type ?iterable, int returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(148): returnIterable()
|
||||
#0 %s(%d): returnCallable()
|
||||
#1 {main}
|
||||
TypeError: returnInt(): Return value must be of type ?int, stdClass returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(158): returnInt()
|
||||
#0 %s(%d): returnInt()
|
||||
#1 {main}
|
||||
TypeError: returnMissingUnloadedClass(): Return value must be of type ?I\Dont\Exist, none returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(167): returnMissingUnloadedClass()
|
||||
#0 %s(%d): returnMissingUnloadedClass()
|
||||
#1 {main}
|
||||
TypeError: returnMissingLoadedClass(): Return value must be of type ?RealClass, none returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(176): returnMissingLoadedClass()
|
||||
#0 %s(%d): returnMissingLoadedClass()
|
||||
#1 {main}
|
||||
TypeError: returnMissingLoadedInterface(): Return value must be of type ?RealInterface, none returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(185): returnMissingLoadedInterface()
|
||||
#0 %s(%d): returnMissingLoadedInterface()
|
||||
#1 {main}
|
||||
TypeError: returnMissingCallable(): Return value must be of type ?callable, none returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(194): returnMissingCallable()
|
||||
#1 {main}
|
||||
TypeError: returnMissingIterable(): Return value must be of type ?iterable, none returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(203): returnMissingIterable()
|
||||
#0 %s(%d): returnMissingCallable()
|
||||
#1 {main}
|
||||
TypeError: returnMissingInt(): Return value must be of type ?int, none returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(212): returnMissingInt()
|
||||
#0 %s(%d): returnMissingInt()
|
||||
#1 {main}
|
||||
|
@ -2805,6 +2805,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
|
||||
}
|
||||
}
|
||||
|
||||
/* Rebuild arginfos if parameter/property types and/or a return type are used */
|
||||
if (reg_function->common.arg_info &&
|
||||
(reg_function->common.fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS))) {
|
||||
/* convert "const char*" class type names into "zend_string*" */
|
||||
@ -2856,6 +2857,15 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(new_arg_info[i].type)) {
|
||||
/* Warning generated an extension load warning which is emitted for every test
|
||||
zend_error(E_CORE_WARNING, "iterable type is now a compile time alias for array|Traversable,"
|
||||
" regenerate the argument info via the php-src gen_stub build script");
|
||||
*/
|
||||
zend_type legacy_iterable = ZEND_TYPE_INIT_CLASS_CONST_MASK(ZSTR_KNOWN(ZEND_STR_TRAVERSABLE),
|
||||
(new_arg_info[i].type.type_mask|MAY_BE_ARRAY));
|
||||
new_arg_info[i].type = legacy_iterable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1227,9 +1227,6 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop
|
||||
if (type_mask & MAY_BE_CALLABLE) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_ITERABLE) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE), /* is_intersection */ false);
|
||||
}
|
||||
if (type_mask & MAY_BE_OBJECT) {
|
||||
str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT), /* is_intersection */ false);
|
||||
}
|
||||
@ -1289,7 +1286,7 @@ static void zend_mark_function_as_generator(void) /* {{{ */
|
||||
|
||||
if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
|
||||
zend_type return_type = CG(active_op_array)->arg_info[-1].type;
|
||||
bool valid_type = (ZEND_TYPE_FULL_MASK(return_type) & (MAY_BE_ITERABLE | MAY_BE_OBJECT)) != 0;
|
||||
bool valid_type = (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_OBJECT) != 0;
|
||||
if (!valid_type) {
|
||||
zend_type *single_type;
|
||||
ZEND_TYPE_FOREACH(return_type, single_type) {
|
||||
@ -6134,6 +6131,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Cannot use \"static\" when no class scope is active");
|
||||
}
|
||||
|
||||
return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0);
|
||||
} else {
|
||||
zend_string *class_name = zend_ast_get_str(ast);
|
||||
@ -6145,6 +6143,15 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
|
||||
"Type declaration '%s' must be unqualified",
|
||||
ZSTR_VAL(zend_string_tolower(class_name)));
|
||||
}
|
||||
|
||||
/* Transform iterable into a type union alias */
|
||||
if (type_code == IS_ITERABLE) {
|
||||
/* Set iterable bit for BC compat during Reflection and string representation of type */
|
||||
zend_type iterable = (zend_type) ZEND_TYPE_INIT_CLASS_CONST_MASK(ZSTR_KNOWN(ZEND_STR_TRAVERSABLE),
|
||||
(MAY_BE_ARRAY|_ZEND_TYPE_ITERABLE_BIT));
|
||||
return iterable;
|
||||
}
|
||||
|
||||
return (zend_type) ZEND_TYPE_INIT_CODE(type_code, 0, 0);
|
||||
} else {
|
||||
const char *correct_name;
|
||||
@ -6184,19 +6191,6 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
|
||||
}
|
||||
}
|
||||
|
||||
static bool zend_type_contains_traversable(zend_type type) {
|
||||
zend_type *single_type;
|
||||
ZEND_TYPE_FOREACH(type, single_type) {
|
||||
if (ZEND_TYPE_HAS_NAME(*single_type)
|
||||
&& zend_string_equals_literal_ci(ZEND_TYPE_NAME(*single_type), "Traversable")) {
|
||||
return 1;
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially
|
||||
// treat it as a built-in type alias.
|
||||
static zend_type zend_compile_typename(
|
||||
zend_ast *ast, bool force_allow_null) /* {{{ */
|
||||
{
|
||||
@ -6290,6 +6284,14 @@ static zend_type zend_compile_typename(
|
||||
zend_ast *type_ast = list->child[i];
|
||||
zend_type single_type = zend_compile_single_typename(type_ast);
|
||||
|
||||
/* An intersection of union types cannot exist so invalidate it
|
||||
* Currently only can happen with iterable getting canonicalized to Traversable|array */
|
||||
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(single_type)) {
|
||||
zend_string *standard_type_str = zend_type_to_string(single_type);
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str));
|
||||
zend_string_release_ex(standard_type_str, false);
|
||||
}
|
||||
/* An intersection of standard types cannot exist so invalidate it */
|
||||
if (ZEND_TYPE_IS_ONLY_MASK(single_type)) {
|
||||
zend_string *standard_type_str = zend_type_to_string(single_type);
|
||||
@ -6329,18 +6331,6 @@ static zend_type zend_compile_typename(
|
||||
}
|
||||
|
||||
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
|
||||
if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) {
|
||||
zend_string *type_str = zend_type_to_string(type);
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str));
|
||||
}
|
||||
|
||||
if ((type_mask & MAY_BE_ITERABLE) && zend_type_contains_traversable(type)) {
|
||||
zend_string *type_str = zend_type_to_string(type);
|
||||
zend_error_noreturn(E_COMPILE_ERROR,
|
||||
"Type %s contains both iterable and Traversable, which is redundant",
|
||||
ZSTR_VAL(type_str));
|
||||
}
|
||||
|
||||
if (type_mask == MAY_BE_ANY && is_marked_nullable) {
|
||||
zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null");
|
||||
@ -6387,9 +6377,6 @@ static bool zend_is_valid_default_value(zend_type type, zval *value)
|
||||
convert_to_double(value);
|
||||
return 1;
|
||||
}
|
||||
if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_ITERABLE) && Z_TYPE_P(value) == IS_ARRAY) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -909,9 +909,6 @@ static zend_always_inline bool i_zend_check_property_type(zend_property_info *in
|
||||
|
||||
uint32_t type_mask = ZEND_TYPE_FULL_MASK(info->type);
|
||||
ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
|
||||
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(property)) {
|
||||
return 1;
|
||||
}
|
||||
return zend_verify_scalar_type_hint(type_mask, property, strict, 0);
|
||||
}
|
||||
|
||||
@ -1048,9 +1045,6 @@ static zend_always_inline bool zend_check_type_slow(
|
||||
if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) {
|
||||
return 1;
|
||||
}
|
||||
if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) {
|
||||
return 1;
|
||||
}
|
||||
if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) {
|
||||
return 1;
|
||||
}
|
||||
@ -2929,7 +2923,7 @@ static zend_always_inline bool check_type_array_assignable(zend_type type) {
|
||||
if (!ZEND_TYPE_IS_SET(type)) {
|
||||
return 1;
|
||||
}
|
||||
return (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0;
|
||||
return (ZEND_TYPE_FULL_MASK(type) & MAY_BE_ARRAY) != 0;
|
||||
}
|
||||
|
||||
/* Checks whether an array can be assigned to the reference. Throws error if not assignable. */
|
||||
@ -3378,9 +3372,6 @@ static zend_always_inline int i_zend_verify_type_assignable_zval(
|
||||
|
||||
type_mask = ZEND_TYPE_FULL_MASK(type);
|
||||
ZEND_ASSERT(!(type_mask & (MAY_BE_CALLABLE|MAY_BE_STATIC)));
|
||||
if (type_mask & MAY_BE_ITERABLE) {
|
||||
return zend_is_iterable(zv);
|
||||
}
|
||||
|
||||
/* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
|
||||
if (strict) {
|
||||
|
@ -328,21 +328,6 @@ static bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool zend_type_contains_traversable(zend_type type) {
|
||||
zend_type *single_type;
|
||||
if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ZEND_TYPE_FOREACH(type, single_type) {
|
||||
if (ZEND_TYPE_HAS_NAME(*single_type)
|
||||
&& zend_string_equals_literal_ci(ZEND_TYPE_NAME(*single_type), "Traversable")) {
|
||||
return 1;
|
||||
}
|
||||
} ZEND_TYPE_FOREACH_END();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool zend_type_permits_self(
|
||||
zend_type type, zend_class_entry *scope, zend_class_entry *self) {
|
||||
if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) {
|
||||
@ -473,15 +458,6 @@ static inheritance_status zend_is_class_subtype_of_type(
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
}
|
||||
if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) {
|
||||
if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
if (!fe_ce) {
|
||||
have_unresolved = 1;
|
||||
} else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) {
|
||||
track_class_dependency(fe_ce, fe_class_name);
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
zend_type *single_type;
|
||||
|
||||
@ -567,18 +543,6 @@ static inheritance_status zend_perform_covariant_type_check(
|
||||
uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type);
|
||||
uint32_t added_types = fe_type_mask & ~proto_type_mask;
|
||||
if (added_types) {
|
||||
// TODO: Make "iterable" an alias of "array|Traversable" instead,
|
||||
// so these special cases will be handled automatically.
|
||||
if ((added_types & MAY_BE_ITERABLE)
|
||||
&& (proto_type_mask & MAY_BE_ARRAY)
|
||||
&& zend_type_contains_traversable(proto_type)) {
|
||||
/* Replacing array|Traversable with iterable is okay */
|
||||
added_types &= ~MAY_BE_ITERABLE;
|
||||
}
|
||||
if ((added_types & MAY_BE_ARRAY) && (proto_type_mask & MAY_BE_ITERABLE)) {
|
||||
/* Replacing iterable with array is okay */
|
||||
added_types &= ~MAY_BE_ARRAY;
|
||||
}
|
||||
if ((added_types & MAY_BE_STATIC)
|
||||
&& zend_type_permits_self(proto_type, proto_scope, fe_scope)) {
|
||||
/* Replacing type that accepts self with static is okay */
|
||||
@ -605,8 +569,7 @@ static inheritance_status zend_perform_covariant_type_check(
|
||||
* We still perform a class lookup for forward-compatibility reasons,
|
||||
* as we may have named types in the future that are not classes
|
||||
* (such as typedefs). */
|
||||
if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) {
|
||||
bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0;
|
||||
if (proto_type_mask & MAY_BE_OBJECT) {
|
||||
ZEND_TYPE_FOREACH(fe_type, single_type) {
|
||||
zend_string *fe_class_name = get_class_from_type(fe_scope, *single_type);
|
||||
if (!fe_class_name) {
|
||||
@ -614,10 +577,8 @@ static inheritance_status zend_perform_covariant_type_check(
|
||||
}
|
||||
zend_class_entry *fe_ce = lookup_class(fe_scope, fe_class_name);
|
||||
if (fe_ce) {
|
||||
if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) {
|
||||
track_class_dependency(fe_ce, fe_class_name);
|
||||
return INHERITANCE_SUCCESS;
|
||||
}
|
||||
track_class_dependency(fe_ce, fe_class_name);
|
||||
return INHERITANCE_SUCCESS;
|
||||
} else {
|
||||
have_unresolved = true;
|
||||
}
|
||||
|
@ -580,6 +580,7 @@ EMPTY_SWITCH_DEFAULT_CASE()
|
||||
_(ZEND_STR_FALSE, "false") \
|
||||
_(ZEND_STR_NULL_LOWERCASE, "null") \
|
||||
_(ZEND_STR_MIXED, "mixed") \
|
||||
_(ZEND_STR_TRAVERSABLE, "Traversable") \
|
||||
_(ZEND_STR_SLEEP, "__sleep") \
|
||||
_(ZEND_STR_WAKEUP, "__wakeup") \
|
||||
_(ZEND_STR_CASES, "cases") \
|
||||
|
@ -38,7 +38,6 @@
|
||||
/* These are used in zend_type, but not for type inference.
|
||||
* They are allowed to overlap with types used during inference. */
|
||||
#define MAY_BE_CALLABLE (1 << IS_CALLABLE)
|
||||
#define MAY_BE_ITERABLE (1 << IS_ITERABLE)
|
||||
#define MAY_BE_VOID (1 << IS_VOID)
|
||||
#define MAY_BE_NEVER (1 << IS_NEVER)
|
||||
#define MAY_BE_STATIC (1 << IS_STATIC)
|
||||
|
@ -144,7 +144,8 @@ typedef struct {
|
||||
#define _ZEND_TYPE_NAME_BIT (1u << 24)
|
||||
#define _ZEND_TYPE_LIST_BIT (1u << 22)
|
||||
#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT)
|
||||
/* TODO: bit 21 is not used */
|
||||
/* For BC behaviour with iterable type */
|
||||
#define _ZEND_TYPE_ITERABLE_BIT (1u << 21)
|
||||
/* Whether the type list is arena allocated */
|
||||
#define _ZEND_TYPE_ARENA_BIT (1u << 20)
|
||||
/* Whether the type list is an intersection type */
|
||||
@ -170,6 +171,9 @@ typedef struct {
|
||||
#define ZEND_TYPE_HAS_LIST(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0)
|
||||
|
||||
#define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0)
|
||||
|
||||
#define ZEND_TYPE_IS_INTERSECTION(t) \
|
||||
((((t).type_mask) & _ZEND_TYPE_INTERSECTION_BIT) != 0)
|
||||
|
||||
@ -263,7 +267,7 @@ typedef struct {
|
||||
{ NULL, (_type_mask) }
|
||||
|
||||
#define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \
|
||||
ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code)))) \
|
||||
ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ( (code) == IS_ITERABLE ? _ZEND_TYPE_ITERABLE_BIT : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code))))) \
|
||||
| ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags))
|
||||
|
||||
#define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \
|
||||
|
@ -219,7 +219,6 @@ class SimpleType {
|
||||
case "float":
|
||||
case "string":
|
||||
case "callable":
|
||||
case "iterable":
|
||||
case "object":
|
||||
case "resource":
|
||||
case "mixed":
|
||||
@ -231,6 +230,8 @@ class SimpleType {
|
||||
return ArrayType::createGenericArray();
|
||||
case "self":
|
||||
throw new Exception('The exact class name must be used instead of "self"');
|
||||
case "iterable":
|
||||
throw new Exception('This should not happen');
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
@ -369,8 +370,6 @@ class SimpleType {
|
||||
return "IS_VOID";
|
||||
case "callable":
|
||||
return "IS_CALLABLE";
|
||||
case "iterable":
|
||||
return "IS_ITERABLE";
|
||||
case "mixed":
|
||||
return "IS_MIXED";
|
||||
case "static":
|
||||
@ -408,8 +407,6 @@ class SimpleType {
|
||||
return "MAY_BE_OBJECT";
|
||||
case "callable":
|
||||
return "MAY_BE_CALLABLE";
|
||||
case "iterable":
|
||||
return "MAY_BE_ITERABLE";
|
||||
case "mixed":
|
||||
return "MAY_BE_ANY";
|
||||
case "void":
|
||||
@ -509,18 +506,32 @@ class Type {
|
||||
|
||||
public static function fromNode(Node $node): Type {
|
||||
if ($node instanceof Node\UnionType) {
|
||||
return new Type(array_map(['SimpleType', 'fromNode'], $node->types));
|
||||
$nestedTypeObjects = array_map(['Type', 'fromNode'], $node->types);
|
||||
$types = [];
|
||||
foreach ($nestedTypeObjects as $typeObject) {
|
||||
array_push($types, ...$typeObject->types);
|
||||
}
|
||||
return new Type($types);
|
||||
}
|
||||
|
||||
if ($node instanceof Node\NullableType) {
|
||||
return new Type(
|
||||
[
|
||||
SimpleType::fromNode($node->type),
|
||||
...Type::fromNode($node->type)->types,
|
||||
SimpleType::null(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ($node instanceof Node\Identifier && $node->toLowerString() === "iterable") {
|
||||
return new Type(
|
||||
[
|
||||
SimpleType::fromString("Traversable"),
|
||||
ArrayType::createGenericArray(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return new Type([SimpleType::fromNode($node)]);
|
||||
}
|
||||
|
||||
|
@ -12232,7 +12232,7 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
|
||||
|
||||
if (flags == ZEND_FETCH_DIM_WRITE) {
|
||||
if ((ZEND_TYPE_FULL_MASK(prop_info->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) {
|
||||
if ((ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_ARRAY) == 0) {
|
||||
if (!type_loaded) {
|
||||
type_loaded = 1;
|
||||
| MEM_ACCESS_32_WITH_UOFFSET ldr, REG2w, FCARG1x, (prop_info->offset + offsetof(zval,u1.type_info)), TMP1
|
||||
|
@ -1949,7 +1949,7 @@ static zend_always_inline bool check_type_array_assignable(zend_type type) {
|
||||
if (!ZEND_TYPE_IS_SET(type)) {
|
||||
return 1;
|
||||
}
|
||||
return (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0;
|
||||
return (ZEND_TYPE_FULL_MASK(type) & MAY_BE_ARRAY) != 0;
|
||||
}
|
||||
|
||||
static zend_property_info *zend_object_fetch_property_type_info(
|
||||
@ -2094,7 +2094,7 @@ static void ZEND_FASTCALL zend_jit_check_array_promotion(zval *val, zend_propert
|
||||
if ((Z_TYPE_P(val) <= IS_FALSE
|
||||
|| (Z_ISREF_P(val) && Z_TYPE_P(Z_REFVAL_P(val)) <= IS_FALSE))
|
||||
&& ZEND_TYPE_IS_SET(prop->type)
|
||||
&& (ZEND_TYPE_FULL_MASK(prop->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) {
|
||||
&& (ZEND_TYPE_FULL_MASK(prop->type) & MAY_BE_ARRAY) == 0) {
|
||||
zend_string *type_str = zend_type_to_string(prop->type);
|
||||
zend_type_error(
|
||||
"Cannot auto-initialize an array inside property %s::$%s of type %s",
|
||||
|
@ -12962,7 +12962,7 @@ static int zend_jit_fetch_obj(dasm_State **Dst,
|
||||
uint32_t flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
|
||||
|
||||
if (flags == ZEND_FETCH_DIM_WRITE) {
|
||||
if ((ZEND_TYPE_FULL_MASK(prop_info->type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) == 0) {
|
||||
if ((ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_ARRAY) == 0) {
|
||||
if (!type_loaded) {
|
||||
type_loaded = 1;
|
||||
| mov edx, dword [FCARG1a + prop_info->offset + 8]
|
||||
|
@ -12,7 +12,7 @@ test(new stdClass);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Fatal error: Uncaught TypeError: test(): Return value must be of type iterable, stdClass returned in %s:%d
|
||||
Fatal error: Uncaught TypeError: test(): Return value must be of type Traversable|array, stdClass returned in %s:%d
|
||||
Stack trace:
|
||||
#0 %s(%d): test(Object(stdClass))
|
||||
#1 {main}
|
||||
|
@ -1330,6 +1330,7 @@ typedef enum {
|
||||
} reflection_type_kind;
|
||||
|
||||
/* For backwards compatibility reasons, we need to return T|null style unions
|
||||
* and transformation from iterable to Traversable|array
|
||||
* as a ReflectionNamedType. Here we determine what counts as a union type and
|
||||
* what doesn't. */
|
||||
static reflection_type_kind get_type_kind(zend_type type) {
|
||||
@ -1344,6 +1345,10 @@ static reflection_type_kind get_type_kind(zend_type type) {
|
||||
}
|
||||
|
||||
if (ZEND_TYPE_IS_COMPLEX(type)) {
|
||||
/* BC support for 'iterable' type */
|
||||
if (UNEXPECTED(ZEND_TYPE_IS_ITERABLE_FALLBACK(type))) {
|
||||
return NAMED_TYPE;
|
||||
}
|
||||
if (type_mask_without_null != 0) {
|
||||
return UNION_TYPE;
|
||||
}
|
||||
@ -2726,6 +2731,11 @@ ZEND_METHOD(ReflectionParameter, isArray)
|
||||
}
|
||||
GET_REFLECTION_OBJECT_PTR(param);
|
||||
|
||||
/* BC For iterable */
|
||||
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(param->arg_info->type)) {
|
||||
RETURN_FALSE;
|
||||
}
|
||||
|
||||
type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(param->arg_info->type);
|
||||
RETVAL_BOOL(type_mask == MAY_BE_ARRAY);
|
||||
}
|
||||
@ -3006,9 +3016,21 @@ ZEND_METHOD(ReflectionType, allowsNull)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* For BC with iterable for named types */
|
||||
static zend_string *zend_named_reflection_type_to_string(zend_type type) {
|
||||
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(type)) {
|
||||
zend_string *iterable = ZSTR_KNOWN(ZEND_STR_ITERABLE);
|
||||
if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_NULL) {
|
||||
return zend_string_concat2("?", strlen("?"), ZSTR_VAL(iterable), ZSTR_LEN(iterable));
|
||||
}
|
||||
return iterable;
|
||||
}
|
||||
return zend_type_to_string(type);
|
||||
}
|
||||
|
||||
static zend_string *zend_type_to_string_without_null(zend_type type) {
|
||||
ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL;
|
||||
return zend_type_to_string(type);
|
||||
return zend_named_reflection_type_to_string(type);
|
||||
}
|
||||
|
||||
/* {{{ Return the text of the type hint */
|
||||
@ -3022,7 +3044,7 @@ ZEND_METHOD(ReflectionType, __toString)
|
||||
}
|
||||
GET_REFLECTION_OBJECT_PTR(param);
|
||||
|
||||
RETURN_STR(zend_type_to_string(param->type));
|
||||
RETURN_STR(zend_named_reflection_type_to_string(param->type));
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@ -3040,7 +3062,7 @@ ZEND_METHOD(ReflectionNamedType, getName)
|
||||
if (param->legacy_behavior) {
|
||||
RETURN_STR(zend_type_to_string_without_null(param->type));
|
||||
}
|
||||
RETURN_STR(zend_type_to_string(param->type));
|
||||
RETURN_STR(zend_named_reflection_type_to_string(param->type));
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@ -3055,6 +3077,10 @@ ZEND_METHOD(ReflectionNamedType, isBuiltin)
|
||||
}
|
||||
GET_REFLECTION_OBJECT_PTR(param);
|
||||
|
||||
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(param->type)) {
|
||||
RETURN_TRUE;
|
||||
}
|
||||
|
||||
/* Treat "static" as a class type for the purposes of reflection. */
|
||||
RETVAL_BOOL(ZEND_TYPE_IS_ONLY_MASK(param->type)
|
||||
&& !(ZEND_TYPE_FULL_MASK(param->type) & MAY_BE_STATIC));
|
||||
@ -3063,6 +3089,11 @@ ZEND_METHOD(ReflectionNamedType, isBuiltin)
|
||||
|
||||
static void append_type(zval *return_value, zend_type type) {
|
||||
zval reflection_type;
|
||||
/* Drop iterable BC bit for type list */
|
||||
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(type)) {
|
||||
ZEND_TYPE_FULL_MASK(type) &= ~_ZEND_TYPE_ITERABLE_BIT;
|
||||
}
|
||||
|
||||
reflection_type_factory(type, &reflection_type, 0);
|
||||
zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type);
|
||||
}
|
||||
@ -3103,9 +3134,6 @@ ZEND_METHOD(ReflectionUnionType, getTypes)
|
||||
if (type_mask & MAY_BE_CALLABLE) {
|
||||
append_type_mask(return_value, MAY_BE_CALLABLE);
|
||||
}
|
||||
if (type_mask & MAY_BE_ITERABLE) {
|
||||
append_type_mask(return_value, MAY_BE_ITERABLE);
|
||||
}
|
||||
if (type_mask & MAY_BE_OBJECT) {
|
||||
append_type_mask(return_value, MAY_BE_OBJECT);
|
||||
}
|
||||
|
@ -80,7 +80,6 @@ class PropTypeTest {
|
||||
public int $int;
|
||||
public string $string;
|
||||
public array $arr;
|
||||
public iterable $iterable;
|
||||
public stdClass $std;
|
||||
public OtherThing $other;
|
||||
public $mixed;
|
||||
@ -216,7 +215,6 @@ string(4) "Test"
|
||||
public int $int;
|
||||
public string $string;
|
||||
public array $arr;
|
||||
public iterable $iterable;
|
||||
public stdClass $std;
|
||||
public OtherThing $other;
|
||||
public $mixed;
|
||||
|
@ -11,7 +11,6 @@ $functions = [
|
||||
function(): bool {},
|
||||
function(): array {},
|
||||
function(): callable {},
|
||||
function(): iterable {},
|
||||
function(): null {},
|
||||
function(): false {},
|
||||
function(): StdClass {}
|
||||
@ -31,7 +30,6 @@ string(6) "string"
|
||||
string(4) "bool"
|
||||
string(5) "array"
|
||||
string(8) "callable"
|
||||
string(8) "iterable"
|
||||
string(4) "null"
|
||||
string(5) "false"
|
||||
string(8) "StdClass"
|
||||
|
@ -1,10 +0,0 @@
|
||||
--TEST--
|
||||
Bug #72661 (ReflectionType::__toString crashes with iterable)
|
||||
--FILE--
|
||||
<?php
|
||||
function test(iterable $arg) { }
|
||||
|
||||
var_dump((string)(new ReflectionParameter("test", 0))->getType());
|
||||
?>
|
||||
--EXPECT--
|
||||
string(8) "iterable"
|
98
ext/reflection/tests/iterable_Reflection.phpt
Normal file
98
ext/reflection/tests/iterable_Reflection.phpt
Normal file
@ -0,0 +1,98 @@
|
||||
--TEST--
|
||||
iterable Type in Reflection
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$function = function(iterable $arg): iterable {};
|
||||
|
||||
$paramType = (new ReflectionParameter($function, 0))->getType();
|
||||
var_dump($paramType::class);
|
||||
var_dump($paramType);
|
||||
var_dump($paramType->getName());
|
||||
var_dump((string) $paramType);
|
||||
var_dump($paramType->isBuiltin());
|
||||
|
||||
$reflectionFunc = new ReflectionFunction($function);
|
||||
$returnType = $reflectionFunc->getReturnType();
|
||||
var_dump($returnType::class);
|
||||
var_dump($returnType);
|
||||
var_dump($returnType->getName());
|
||||
var_dump((string) $returnType);
|
||||
var_dump($returnType->isBuiltin());
|
||||
|
||||
class PropIterableTypeTest {
|
||||
public iterable $iterable;
|
||||
public ?iterable $nullableIterable;
|
||||
public array $control;
|
||||
public ?array $nullableControl;
|
||||
}
|
||||
|
||||
$reflector = new ReflectionClass(PropIterableTypeTest::class);
|
||||
|
||||
[$property, $nullable, $control, $nullableControl] = $reflector->getProperties();
|
||||
$iterableType = $property->getType();
|
||||
var_dump($iterableType::class);
|
||||
var_dump($iterableType);
|
||||
var_dump($iterableType->getName());
|
||||
var_dump((string) $iterableType);
|
||||
var_dump($iterableType->isBuiltin());
|
||||
|
||||
$nullableIterableType = $nullable->getType();
|
||||
var_dump($nullableIterableType::class);
|
||||
var_dump($nullableIterableType);
|
||||
var_dump($nullableIterableType->getName());
|
||||
var_dump((string) $nullableIterableType);
|
||||
var_dump($nullableIterableType->isBuiltin());
|
||||
|
||||
$controlType = $control->getType();
|
||||
var_dump($controlType::class);
|
||||
var_dump($controlType);
|
||||
var_dump($controlType->getName());
|
||||
var_dump((string) $controlType);
|
||||
var_dump($controlType->isBuiltin());
|
||||
|
||||
$nullableControlType = $nullableControl->getType();
|
||||
var_dump($nullableControlType::class);
|
||||
var_dump($nullableControlType);
|
||||
var_dump($nullableControlType->getName());
|
||||
var_dump((string) $nullableControlType);
|
||||
var_dump($nullableControlType->isBuiltin());
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
string(19) "ReflectionNamedType"
|
||||
object(ReflectionNamedType)#%d (0) {
|
||||
}
|
||||
string(8) "iterable"
|
||||
string(8) "iterable"
|
||||
bool(true)
|
||||
string(19) "ReflectionNamedType"
|
||||
object(ReflectionNamedType)#%d (0) {
|
||||
}
|
||||
string(8) "iterable"
|
||||
string(8) "iterable"
|
||||
bool(true)
|
||||
string(19) "ReflectionNamedType"
|
||||
object(ReflectionNamedType)#%d (0) {
|
||||
}
|
||||
string(8) "iterable"
|
||||
string(8) "iterable"
|
||||
bool(true)
|
||||
string(19) "ReflectionNamedType"
|
||||
object(ReflectionNamedType)#%d (0) {
|
||||
}
|
||||
string(8) "iterable"
|
||||
string(9) "?iterable"
|
||||
bool(true)
|
||||
string(19) "ReflectionNamedType"
|
||||
object(ReflectionNamedType)#%d (0) {
|
||||
}
|
||||
string(5) "array"
|
||||
string(5) "array"
|
||||
bool(true)
|
||||
string(19) "ReflectionNamedType"
|
||||
object(ReflectionNamedType)#%d (0) {
|
||||
}
|
||||
string(5) "array"
|
||||
string(6) "?array"
|
||||
bool(true)
|
@ -75,13 +75,16 @@ Allows null: true
|
||||
Name: null
|
||||
String: null
|
||||
Allows Null: true
|
||||
Type X|iterable|bool:
|
||||
Type X|Traversable|array|bool:
|
||||
Allows null: false
|
||||
Name: X
|
||||
String: X
|
||||
Allows Null: false
|
||||
Name: iterable
|
||||
String: iterable
|
||||
Name: Traversable
|
||||
String: Traversable
|
||||
Allows Null: false
|
||||
Name: array
|
||||
String: array
|
||||
Allows Null: false
|
||||
Name: bool
|
||||
String: bool
|
||||
|
@ -314,6 +314,29 @@ static ZEND_FUNCTION(zend_iterable)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
}
|
||||
|
||||
static ZEND_FUNCTION(zend_iterable_legacy)
|
||||
{
|
||||
zval *arg1, *arg2;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, 2)
|
||||
Z_PARAM_ITERABLE(arg1)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_ITERABLE_OR_NULL(arg2)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
RETURN_COPY(arg1);
|
||||
}
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_iterable_legacy, 0, 1, IS_ITERABLE, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, arg1, IS_ITERABLE, 0)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_ITERABLE, 1, "null")
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
static const zend_function_entry ext_function_legacy[] = {
|
||||
ZEND_FE(zend_iterable_legacy, arginfo_zend_iterable_legacy)
|
||||
ZEND_FE_END
|
||||
};
|
||||
|
||||
/* Call a method on a class or object using zend_call_method() */
|
||||
static ZEND_FUNCTION(zend_call_method)
|
||||
{
|
||||
@ -628,6 +651,8 @@ PHP_MINIT_FUNCTION(zend_test)
|
||||
zend_test_string_enum = register_class_ZendTestStringEnum();
|
||||
zend_test_int_enum = register_class_ZendTestIntEnum();
|
||||
|
||||
zend_register_functions(NULL, ext_function_legacy, NULL, EG(current_module)->type);
|
||||
|
||||
// Loading via dl() not supported with the observer API
|
||||
if (type != MODULE_TEMPORARY) {
|
||||
REGISTER_INI_ENTRIES();
|
||||
|
4
ext/zend_test/test_arginfo.h
generated
4
ext/zend_test/test_arginfo.h
generated
@ -53,8 +53,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_zend_string_or_stdclass_or_n
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_iterable, 0, 1, IS_VOID, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, arg1, IS_ITERABLE, 0)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_ITERABLE, 1, "null")
|
||||
ZEND_ARG_OBJ_TYPE_MASK(0, arg1, Traversable, MAY_BE_ARRAY, NULL)
|
||||
ZEND_ARG_OBJ_TYPE_MASK(0, arg2, Traversable, MAY_BE_ARRAY|MAY_BE_NULL, "null")
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_weakmap_attach, 0, 2, _IS_BOOL, 0)
|
||||
|
28
ext/zend_test/tests/zend_legacy_iterable.phpt
Normal file
28
ext/zend_test/tests/zend_legacy_iterable.phpt
Normal file
@ -0,0 +1,28 @@
|
||||
--TEST--
|
||||
Test that legacy IS_ITERABLE arg info type generates a notice
|
||||
--EXTENSIONS--
|
||||
zend_test
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
function gen() {
|
||||
yield 1;
|
||||
};
|
||||
|
||||
var_dump(zend_iterable_legacy([], null));
|
||||
var_dump(zend_iterable_legacy([], []));
|
||||
var_dump(zend_iterable_legacy(gen(), null));
|
||||
var_dump(zend_iterable_legacy(gen(), gen()));
|
||||
|
||||
?>
|
||||
==DONE==
|
||||
--EXPECT--
|
||||
array(0) {
|
||||
}
|
||||
array(0) {
|
||||
}
|
||||
object(Generator)#1 (0) {
|
||||
}
|
||||
object(Generator)#1 (0) {
|
||||
}
|
||||
==DONE==
|
@ -51,4 +51,4 @@ array(1) {
|
||||
[%s]=>
|
||||
object(stdClass)#2 (0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user