Skip to content
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

Improve in_array extension to get rid of related impossible check adaptions #1514

Closed
wants to merge 6 commits into from

Conversation

herndlm
Copy link
Contributor

@herndlm herndlm commented Jul 13, 2022

Closes phpstan/phpstan#6705

Almost all of the problems were solved by adding the $offsetValueType information to HasOffset. This way it is now possible to e.g. specify a type non-empty-array<int, int>&hasOffset(int, 17) which tells us that the generic array consists of ints AND has at least one entry with the value 17. (but if the type is dumped/printed the hasOffset is not showing the offset value to e.g. not mess up baselines..).

@herndlm herndlm force-pushed the in-array-impossible-checks branch from 31c2a6c to e994f69 Compare July 13, 2022 18:26
src/Type/Accessory/HasOffsetType.php Outdated Show resolved Hide resolved
src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php Outdated Show resolved Hide resolved
@@ -26,7 +28,7 @@ public function doFoo(
return;
}

if (in_array($r, $strings, true)) {
if (in_array($r, $moreStrings, true)) {
Copy link
Contributor Author

@herndlm herndlm Jul 13, 2022

Choose a reason for hiding this comment

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

I had to do that because $strings was already type-specified from previous conditionals in the same scope which messed the result up here since types are specified better now.

Comment on lines 69 to +71
return $type->isOffsetAccessible()
->and($type->hasOffsetValueType($this->offsetType));
->and($type->hasOffsetValueType($this->offsetType))
->and($this->offsetValueType->isSuperTypeOf($type->getOffsetValueType($this->offsetType)));
Copy link
Contributor Author

@herndlm herndlm Jul 13, 2022

Choose a reason for hiding this comment

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

not sure if isSuperTypeOf and friends are really correct, e.g. I did not touch isSubTypeOf or accept at all

@herndlm herndlm force-pushed the in-array-impossible-checks branch from b51b928 to e994f69 Compare July 13, 2022 19:26
@@ -14,15 +14,15 @@ class HelloWorld
public function sayHello(array $array): void
{
if(in_array("thing", $array, true)){
assertType('non-empty-array<int, string>', $array);
assertType('non-empty-array<int, string>&hasOffset(mixed)', $array);
Copy link
Contributor Author

@herndlm herndlm Jul 13, 2022

Choose a reason for hiding this comment

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

this is obviously weird. I'd prefer to rather have an hasOffsetValue('thing') I guess. But on the other hand, the HasOffset with both the offset and offset value might be useful somewhere else?
Also, mixed is potentially confusing here, isn't it? :/

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe print offset and offsetValue type within the bracket, like
&hasOffset(mixed, 'thing') ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that would be easy to do and makes sense, yeah. I did not do it yet because I was scared that it will lead to too many changes in e.g. baselines. but we can't think always about that, right? I'll check it out :)

@herndlm
Copy link
Contributor Author

herndlm commented Jul 14, 2022

This is obviously not ready yet. I'll keep thinking about and come back to it. E.g. generic array usage should be fixed with hasoffset only, while constant array most likely needs special handling to e.g. make optional entries non-optional for example.

If you ask me if this PR is going to be finished soon, I'd answer maybe. Feels a bit like https://xkcd.com/612/ right now :D

Comment on lines +553 to +556
[
'Call to function in_array() with arguments \'abstract methods\'|\'constructor\'|\'destructor\'|\'final methods\'|\'magic methods\'|\'private constants\'|\'private methods\'|\'private properties\'|\'private static…\'|\'private static…\'|\'protected abstract…\'|\'protected constants\'|\'protected final…\'|\'protected methods\'|\'protected properties\'|\'protected static…\'|\'protected static…\'|\'protected static…\'|\'protected static…\'|\'public abstract…\'|\'public constants\'|\'public final methods\'|\'public methods\'|\'public properties\'|\'public static…\'|\'public static final…\'|\'public static…\'|\'public static…\'|\'static constructors\'|\'static methods\'|\'static properties\', array{0: \'final methods\'|\'private static…\'|\'protected final…\'|\'public abstract…\'|\'public constants\'|\'public final methods\'|\'public static…\'|\'static constructors\'|\'static properties\', 1: \'abstract methods\'|\'private methods\'|\'protected abstract…\'|\'protected constants\'|\'protected final…\'|\'protected static…\'|\'protected static…\'|\'public properties\'|\'public static final…\', 2?: \'private constants\'|\'private static…\'|\'protected abstract…\'|\'protected properties\'|\'protected static…\'|\'public abstract…\'|\'public static…\'|\'public static final…\'|\'static methods\', 3?: \'constructor\'|\'private properties\'|\'protected static…\'|\'protected static…\'|\'public static…\', 4?: \'destructor\'|\'protected static…\'|\'protected static…\'|\'public static…\', 5?: \'protected methods\'|\'public methods\'|\'public static…\', 6?: \'protected methods\'|\'protected static…\', 7?: \'private methods\'|\'private static…\', ...} and true will always evaluate to true.',
132,
],
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 is showing up because in_array_key is specifying constant types I think which it shouldn't. I created phpstan/phpstan#7621 for that

Comment on lines +63 to +71
if (
$arrayType instanceof ConstantArrayType && !$arrayType->isEmpty()
&& count(TypeUtils::getConstantScalars($needleType)) === 0 && $arrayValueType->isSuperTypeOf($needleType)->yes()
) {
// Avoid false-positives with e.g. a string needle and array{'self', string} as haystack
// For such cases there seems to be nothing more that we can specify unfortunately
return $specifiedTypes;
}

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 is an edge case I'd really like to not have here. But I didn't find a way to specify anything here, that doesn't get normalized away (e.g. $needle is 'self'|string which is string and the array must have a string value for sure, which it already has). There was initially no test case for this, but it was showing up as error in slevomat-cs.
any other ideas?

Comment on lines +87 to +92
// If e.g. needle is 'a' and haystack non-empty-array<int, 'a'> we can be sure that this always evaluates to true
// Belows HasOffset::isSuperTypeOf cannot deal with that since it calls ArrayType::hasOffsetValueType and that returns maybe at max
if ($needleType instanceof ConstantScalarType && $arrayType->isIterableAtLeastOnce()->yes() && $arrayValueType->equals($needleType)) {
return $specifiedTypes;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also dislike this edge case, maybe it is possible to adapt HasOffset::isSuperTypeOf somehow to figure this out.. Any ideas?

@ondrejmirtes
Copy link
Member

I'm gonna stop you right there - you're trying to do too many things here - improve in_array(), and introduce value type into HasOffsetType.

These should be two separate PRs. Please send HasOffsetType separately as that's more difficult and going to have many implications we need to discuss.

@herndlm
Copy link
Contributor Author

herndlm commented Jul 14, 2022

I totally agree, I'm quite unsure about HasOffsetType tbh, I was even thinking about a HasOffsetValueType, but not sure if that helps either.
If I start with HasOffsetType - do you have already ideas how and where roughly? And the most important thing - how to test it? UPDATE: It looks at least like I'm not using that for constant arrays and that could be improved first here maybe hmm

@ondrejmirtes
Copy link
Member

There are some issues where I mention it and that should fix some things. If you can't think of any scenarios it'd fix then it's not probably worth it 😊

@herndlm herndlm closed this Jul 14, 2022
@herndlm
Copy link
Contributor Author

herndlm commented Jul 14, 2022

thx for the explanations. I found at least one issue where it might help, yeah. well, I'm gonna step back and re-think it :) I was kinda forcing it I guess

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