Skip to content

Fix NumericString to array key #3326

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

Conversation

VincentLanglet
Copy link
Contributor

@VincentLanglet VincentLanglet commented Aug 15, 2024

Closes phpstan/phpstan#4163
Fixes phpstan/phpstan#4671 (already closed with wrong result)
Closes phpstan/phpstan#8592
Closes phpstan/phpstan#11390
Closes phpstan/phpstan#12413

@VincentLanglet VincentLanglet force-pushed the numeric-string-array-key branch from e170a71 to c42d877 Compare August 15, 2024 12:24
@@ -233,10 +233,7 @@ public function testBug4671(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->strictUnnecessaryNullsafePropertyFetch = false;
$this->analyse([__DIR__ . '/data/bug-4671.php'], [[
'Offset numeric-string on array<string, string> in isset() does not exist.',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was wrong

@VincentLanglet VincentLanglet force-pushed the numeric-string-array-key branch 2 times, most recently from 41feb8b to 3ed0134 Compare August 15, 2024 13:10
if ($offsetType instanceof UnionType) {
$results = [];
foreach ($offsetType->getTypes() as $innerType) {
$results[] = $this->hasOffsetValueType($innerType);
$results[] = $this->recursiveHasOffsetValueType($innerType);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

With the previous logic, toArrayKey was again called, creating a new UnionType for numeric string and an infinite loop.

@VincentLanglet VincentLanglet marked this pull request as ready for review August 15, 2024 13:31
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@VincentLanglet VincentLanglet marked this pull request as draft August 15, 2024 13:35
@VincentLanglet VincentLanglet marked this pull request as ready for review August 15, 2024 15:06
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@ondrejmirtes
Copy link
Member

Hey, this area is really tricky. This isn't the right fix, I'd tackle this completely differently. Your change would break too many scenarios.

My idea is: Introduce non-int-string type, and with an optional flag being false by default, I'd interpret array<string, X> as array<non-int-string, X>. Which would make the analysis stricter and correct. That would be a step in the right direction of fixing arrays-with-string-keys issues.

We can't fix numeric-string. Asking is_numeric() does not tell us anything about what's going to happen inside array keys. That's currently broken design. Because only "integer strings" are cast to integers in arrays, but numeric strings also include "float strings"

@ondrejmirtes
Copy link
Member

I'll give these changes a bit more thought, stay tuned and thanks, I appreciate it 😊

@VincentLanglet
Copy link
Contributor Author

Hey, this area is really tricky.

I agree

This isn't the right fix, I'd tackle this completely differently. Your change would break too many scenarios.

Which one ? Looking at tests and issues, it seems to only solve issues without breaking tests.

I was worried too that it would open a rabbit hole, but according to the CI it seems not.

I agree it would be the case if I wanted to change the behavior for string but here it's only numeric-string.
And it seems weird that currently the intersection of string->toArrayKey and numeric-string->toArrayKey is NEVER.

My idea is: Introduce non-int-string type, and with an optional flag being false by default, I'd interpret array<string, X> as array<non-int-string, X>. Which would make the analysis stricter and correct. That would be a step in the right direction of fixing arrays-with-string-keys issues.

I definitely agree on this non-int-string type, but here I think it's different since we're on numeric-string.

Currently string->toArrayKey is considered as string but numeric-string->toArrayKey is considered as int.
It seems better to me to consider that numeric-string->toArrayKey might still be a string,
I especially didn't touch to the string->toArrayKey behavior to avoid the tricky part.

We can't fix numeric-string. Asking is_numeric() does not tell us anything about what's going to happen inside array keys. That's currently broken design. Because only "integer strings" are cast to integers in arrays, but numeric strings also include "float strings"

That especially because numeric-string can include float string that the arrayKey shouldn't be considered as only int.

Imho, 'numeric-string' should be considered as int|numeric-string as array key,
while int-string and non-int-string will be considered respectively as int and string as array key the day a native php is_int_string will be introduced (I just tried an RFC on php internals about such functions to know if it could be introduced in next php version).

I'll give these changes a bit more thought, stay tuned and thanks, I appreciate it 😊

Sure, I'm not in hurry.

@VincentLanglet
Copy link
Contributor Author

Hi, I rebased the branch and really would like to move forward this PR if possible @ondrejmirtes. Can we talk again about it ?

I do agree that one day we will need non-int-string. But I feel like it’s not the same topic.

Currently PHPStan consider that numeric-string is the same than int-string, but it’s not.
Numeric-string should be considered as int-string|float-string and while int-string are casted to int when used as array key, float-string are still float-string when used as array-key.



So NumericString->toArrayKey should be Int|Numeric-string, and not just Int.

At first sight, with all the existing non-regression tests, this change seems to fix a lot of existing issues without introducing a regresion.
Do you prefer this PR to target 2.1.x ?

@VincentLanglet VincentLanglet force-pushed the numeric-string-array-key branch from 205d0c3 to 27a9b38 Compare March 18, 2025 07:55
Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

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

I'm willing to try this again and see what the impact is on real-world projects.

@@ -1029,7 +1029,10 @@ public function toArray(): Type
public function toArrayKey(): Type
{
if ($this->isNumericString()->yes()) {
return new IntegerType();
return new UnionType([
Copy link
Member

Choose a reason for hiding this comment

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

This should be TypeCombinator::union() instead of new UnionType to ensure type normalization.

@ondrejmirtes ondrejmirtes merged commit 36b3925 into phpstan:1.12.x May 5, 2025
136 checks passed
@ondrejmirtes
Copy link
Member

Thank you.

@VincentLanglet
Copy link
Contributor Author

You're welcome, please feel free to ping me after your tests if you need extra work on this and I can help.

@ondrejmirtes
Copy link
Member

This is causing infinite recursion on one of the test projects. I'm investigating details.

@VincentLanglet
Copy link
Contributor Author

This is causing infinite recursion on one of the test projects. I'm investigating details.

I had one infinite loop with PHPStan codebase/tests which I fixed this way https://github.com/phpstan/phpstan-src/pull/3326/files#r1718409129 if it can help for the other one you found.

@ondrejmirtes
Copy link
Member

I'm sorry, it wasn't fault of this PR but a different one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants