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

Conversation

Girgias
Copy link
Member

@Girgias Girgias commented Jul 26, 2021

There is probably some more clean-up which can be done in zend_execute.c/JIT/OpCache from the handling of iterable, but I am wondering if this the correct way of approaching this.

@nikic
Copy link
Member

nikic commented Jul 27, 2021

Looks basically okay -- one thing I'm unsure about is whether we need to try to convert this back to iterable for reflection purposes to avoid breaking code that uses iterable but does not handle union types.

@nikic
Copy link
Member

nikic commented Aug 2, 2021

A problem here is that existing arginfo for internal functions may have MAY_BE_ITERABLE, in which case it's going to misbehave now. We'd have to either adjust that when registering internal functions, or require arginfo to be updated to not use iterable (in which case we should drop/rename MAY_BE_ITERABLE to cause a build failure).

@Girgias Girgias force-pushed the alias-iterable branch 2 times, most recently from 605a695 to a70db6b Compare August 12, 2021 01:39
@kocsismate
Copy link
Member

@Girgias For gen_stub.php, you'll need something along the lines of:

Index: build/gen_stub.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/build/gen_stub.php b/build/gen_stub.php
--- a/build/gen_stub.php	(revision 524a116cf7399437f69eb1c227d94953cea8b74a)
+++ b/build/gen_stub.php	(date 1631644450337)
@@ -203,7 +203,6 @@
             case "float":
             case "string":
             case "callable":
-            case "iterable":
             case "object":
             case "resource":
             case "mixed":
@@ -278,8 +277,6 @@
                 return "IS_VOID";
             case "callable":
                 return "IS_CALLABLE";
-            case "iterable":
-                return "IS_ITERABLE";
             case "mixed":
                 return "IS_MIXED";
             case "static":
@@ -313,8 +310,6 @@
                 return "MAY_BE_OBJECT";
             case "callable":
                 return "MAY_BE_CALLABLE";
-            case "iterable":
-                return "MAY_BE_ITERABLE";
             case "mixed":
                 return "MAY_BE_ANY";
             case "void":
@@ -427,6 +422,15 @@
             );
         }
 
+        if ($node instanceof Node\Identifier && $node->toLowerString() === "iterable") {
+            return new Type(
+                [
+                    SimpleType::fromString("Traversable"),
+                    ArrayType::createGenericArray(),
+                ]
+            );
+        }
+
         return new Type([SimpleType::fromNode($node)]);
     }

This patch only deals with iterable when it's a simple type, but the union type case (including the nullable one) should also be accounted for. Unfortunately, I couldn't do it elegantly easily, so I'll leave it as an exercise for you. 😅

@Girgias
Copy link
Member Author

Girgias commented Sep 14, 2021

@Girgias For gen_stub.php, you'll need something along the lines of:

Index: build/gen_stub.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/build/gen_stub.php b/build/gen_stub.php
--- a/build/gen_stub.php	(revision 524a116cf7399437f69eb1c227d94953cea8b74a)
+++ b/build/gen_stub.php	(date 1631644450337)
@@ -203,7 +203,6 @@
             case "float":
             case "string":
             case "callable":
-            case "iterable":
             case "object":
             case "resource":
             case "mixed":
@@ -278,8 +277,6 @@
                 return "IS_VOID";
             case "callable":
                 return "IS_CALLABLE";
-            case "iterable":
-                return "IS_ITERABLE";
             case "mixed":
                 return "IS_MIXED";
             case "static":
@@ -313,8 +310,6 @@
                 return "MAY_BE_OBJECT";
             case "callable":
                 return "MAY_BE_CALLABLE";
-            case "iterable":
-                return "MAY_BE_ITERABLE";
             case "mixed":
                 return "MAY_BE_ANY";
             case "void":
@@ -427,6 +422,15 @@
             );
         }
 
+        if ($node instanceof Node\Identifier && $node->toLowerString() === "iterable") {
+            return new Type(
+                [
+                    SimpleType::fromString("Traversable"),
+                    ArrayType::createGenericArray(),
+                ]
+            );
+        }
+
         return new Type([SimpleType::fromNode($node)]);
     }

This patch only deals with iterable when it's a simple type, but the union type case (including the nullable one) should also be accounted for. Unfortunately, I couldn't do it elegantly easily, so I'll leave it as an exercise for you. sweat_smile

Well, I think I've got something which works, but not sure, might want to build a test suite for it.

@Girgias
Copy link
Member Author

Girgias commented Sep 15, 2021

@nikic I'm wondering if it still makes sense to make Reflection behave properly with iterable as it seems unlikely that PHP 8.2 code won't need to handle intersection/union types.

The fallback for internal functions using it still makes sense though, at least in the short term, but in any cases both things should be documented in their respective UPGRADING docs.

@kocsismate
Copy link
Member

Looks good to me as well

@@ -1189,22 +1189,40 @@ static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scop

zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) {
zend_string *str = NULL;
uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
Copy link
Member

Choose a reason for hiding this comment

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

Something here does not seem to work when iterable appears first in the union.

function test(): iterable|Foo {}
test();

Fatal error: Uncaught TypeError: test(): Return value must be of type Traversable|Foo|array, none returned

The type representation looks like this:

zend_type
	mask: 5505152 (ZEND_TYPE_LIST_BIT | _ZEND_TYPE_UNION_BIT | MAY_BE_ARRAY)
	ptr:
		zend_type_list (2):
			zend_type:
				mask: 16777216 (_ZEND_TYPE_NAME_BIT)
				ptr: Traversable
			zend_type:
				mask: 16777216 (_ZEND_TYPE_NAME_BIT)
				ptr: Foo

Whereas Foo|iterable looks like this:

zend_type
	mask: 5505152 (ZEND_TYPE_LIST_BIT | _ZEND_TYPE_UNION_BIT | MAY_BE_ARRAY)
	ptr:
		zend_type_list (2):
			zend_type:
				mask: 16777216 (_ZEND_TYPE_NAME_BIT)
				ptr: Foo
			zend_type:
				mask: 19136512 (_ZEND_TYPE_NAME_BIT | _ZEND_TYPE_UNION_BIT | _ZEND_TYPE_INTERSECTION_BIT)
				ptr: Traversable

So it looks like the _ZEND_TYPE_ITERABLE_BIT flag is lost. The second one looks fishy too, as the Traversable type has the _ZEND_TYPE_UNION_BIT set when it's not really a union. It's unclear (to me) whether the _ZEND_TYPE_ITERABLE_BIT should be set on the root type.

I wonder if trying to maintain BC is worthwhile at all. For type aliases, I was planning to replace the types at runtime which would also result in the resolved types when printing them. If iterable could be made a type alias at some point the behavior would change again.

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 really don't know if it is worthwile as I didn't bother with it initially, maybe it is better to just have compatibility for Reflection when it is only iterable (or ?iterable) ?

Copy link
Member

Choose a reason for hiding this comment

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

@Girgias IMO it should work for all cases or we should drop it altogether.

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'll go back then to how it was initially, as I would think code running for 8.2 would be adapted to handle union types, but that will need to be put in the migration guide anyway.

Copy link
Member

Choose a reason for hiding this comment

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

@Girgias Maybe keep the code around, just in case we'll need the BC solution anyway. I think with a BC break this will be a bit more questionable and should at least be mentioned on the mailing list.

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'll email internals then

@Girgias Girgias force-pushed the alias-iterable branch 2 times, most recently from ca4a14d to 1ac37d9 Compare April 23, 2022 12:49
@Girgias Girgias force-pushed the alias-iterable branch 3 times, most recently from d217311 to b7a616f Compare May 3, 2022 21:04
@Girgias Girgias requested a review from nikic May 4, 2022 09:45
@Girgias
Copy link
Member Author

Girgias commented May 18, 2022

I would like to merge within a week.

@Girgias Girgias merged commit b40ae80 into php:master Jun 7, 2022
Girgias added a commit that referenced this pull request Jun 21, 2022
Follow-up from #7309 as I didn't change the ZPP TypeError wording.
@Girgias Girgias deleted the alias-iterable branch June 29, 2022 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants