Skip to content

Convert iterable into an internal alias for Traversable|array #7309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5391d70
Convert iterable into an internal alias for Traversable|array
Girgias Jul 26, 2021
ca2d21b
BC support for Reflection
Girgias Jul 28, 2021
842c34f
Fix variance
Girgias Jul 28, 2021
905510b
Use unused type bit for flagging iterable
Girgias Aug 11, 2021
b00a2a6
Fix slightly union types Reflection test
Girgias Aug 11, 2021
fd55485
Point to iterable for incompatible type in intersection
Girgias Aug 12, 2021
e427ef8
Fix iterable type for internal functions
Girgias Sep 14, 2021
3635821
Start of a working stub generation
Girgias Sep 14, 2021
3a9326a
Possibly generic handling of types?
Girgias Sep 14, 2021
280cbf0
Ammend test3
Girgias Apr 20, 2022
1ff4c99
Use iterable instead of array|Traversable when usage of single type
Girgias Apr 20, 2022
fd14604
Handle iterable in complex union types
Girgias Apr 20, 2022
fcecc29
Fix opcache test
Girgias Apr 20, 2022
8b548a2
Address review and drop BC shim for unions
Girgias Apr 23, 2022
569ca2c
Remove shim for iterable BC
Girgias Apr 23, 2022
39b2258
Make invalid types in intersection more robust
Girgias Apr 23, 2022
307b663
Fix opcache test
Girgias Apr 23, 2022
37b7141
Check for legacy iterable arg_info during function registration
Girgias Apr 27, 2022
f29b2c7
Warn also if return type is iterable
Girgias Apr 27, 2022
8b479f0
Do not test statup in effect
Girgias Apr 27, 2022
cc0291c
Do not warn but enable legacy cnbversion test
Girgias May 3, 2022
bbdb793
Reflection iterable BC for named types
Girgias May 20, 2022
be44175
Nit + whitespaces
Girgias May 20, 2022
e0bf71b
Address review comments
Girgias May 27, 2022
78129bd
Whitespace revert + code move revert
Girgias May 30, 2022
ae53b04
Do not memcpy when not needed
Girgias May 30, 2022
d4056a9
Do not use UNION bit for iterable as it is not a type list
Girgias May 30, 2022
9adcadc
last whitespaces reverts
Girgias May 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 7 additions & 0 deletions Zend/tests/return_types/generators001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,18 @@ function test6() : object|callable {
yield 6;
}

function test7() : iterable {
yield 7;
}

var_dump(
test1(),
test2(),
test3(),
test4(),
test5(),
test6(),
test7(),
);
?>
--EXPECTF--
Expand All @@ -48,3 +53,5 @@ object(Generator)#%d (%d) {
}
object(Generator)#%d (%d) {
}
object(Generator)#%d (%d) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 46 additions & 0 deletions Zend/tests/type_declarations/iterable/or_null.phpt
Original file line number Diff line number Diff line change
@@ -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}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
81 changes: 21 additions & 60 deletions Zend/tests/typehints/or_null.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -199,15 +181,6 @@ try {
echo $e, PHP_EOL;
}

function returnMissingIterable(): ?iterable {
}

try {
returnMissingIterable();
} catch (\TypeError $e) {
echo $e, PHP_EOL;
}

function returnMissingInt(): ?int {
}

Expand All @@ -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}
10 changes: 10 additions & 0 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -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*" */
Expand Down Expand Up @@ -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,"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could warn only in ZEND_DEBUG.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't as this is an extension loading warning, so if zend_test is loaded it will emit this warning on every single test

" 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;
}
}
}

Expand Down
Loading