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

add callback types for array_uintersect etc #3282

Open
wants to merge 6 commits into
base: 1.12.x
Choose a base branch
from

Conversation

schlndh
Copy link
Contributor

@schlndh schlndh commented Aug 2, 2024

This is a fixed version of #1571 (TK of array-key as you requested).
Fixes phpstan/phpstan#7707

Unfortunately, it doesn't cover all variants due to the limitations of the stubs and the weird interface of the functions. E.g.

var_dump(array_uintersect(
	['a' => 1, 'b' => 2],
	['c' => 1, 'd' => 2],
	['c' => 1, 'd' => 2],
	['c' => 1, 'd' => 2],
	// values comparison
	static function (int $a, int $b): int {
		return $a <=> $b;
	}
));

results in:

Parameter #5 ...$rest of function array_uintersect expects array|(callable(mixed, mixed): int), Closure(int, int): int<-1, 1> given.
    💡 • Type #2 from the union: Type int of parameter #1 $a of passed callable needs to be same or wider than parameter type mixed of accepting callable.
• Type #2 from the union: Type int of parameter #2 $b of passed callable needs to be same or wider than parameter type mixed of accepting callable.

But at least it's a step in the right direction.

@@ -201,8 +201,11 @@ public static function decideType(
}

if (
(!$phpDocType instanceof NeverType || ($type instanceof MixedType && !$type->isExplicitMixed()))
&& $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType))->yes()
($type->isCallable()->yes() && $phpDocType->isCallable()->yes())
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 the problem encountered in the original PR (at least when rebased on the current 1.11.x branch). $type is callable(mixed, mixed): int and TemplateTypeHelper::resolveToBounds($phpDocType) is callable(array-key, array-key): int. $type->isSuperTypeOf(TemplateTypeHelper::resolveToBounds($phpDocType)) returns maybe (IMO it should return no, but I didn't look into that).

As I understand it the goal of decideType is to allow phpdocs to narrow down the native type (or in case of the native PHP code reflection also the signature/stub/jetbrains stuff...). The narrow-down rule probably works fine in most cases, but for callables, chances are that the $type may be incorrectly narrowed down too much.

I'm not sure about the consequences of this change. I'd feel better if it could only be applied to native PHP reflections, not parsed user code.

@ondrejmirtes
Copy link
Member

Please try to open a separate PR with an alternative approach:

  1. Try writing FunctionParameterClosureTypeExtension for one of these functions instead of introducing generics into their stubs (we have PregReplaceCallbackClosureTypeExtension in the repo so you can see how it works)
  2. You can shuffle the signature parameters in ParametersAcceptorSelector so that the number of parameters will match the number of passed arrays and so that the last parameter/argument always has the correct name (so that the FunctionParameterClosureTypeExtension can simply ask about the $value_compare_func parameter and adjust its callable signature.

In theory this should get rid of all false positives. If this alternative PR is not successful then I'll merge this one.

@schlndh
Copy link
Contributor Author

schlndh commented Aug 23, 2024

@ondrejmirtes It turns out that neither approach works consistently out of the box.

It seems that FunctionParameterClosureTypeExtension only narrows down the type inside the closure. It doesn't narrow it down for the purpose of CallFunctionParametersRule: https://phpstan.org/r/8ff9bc8f-4162-4ccf-bf5c-8b3d2061e68d

EDIT: I accidentally tested it without bleeding edge. So the rest of the comment is not true: https://phpstan.org/r/1bff567d-d9a1-4906-9fe0-83705f7854ab

On the other hand, the reflection based approaches (this and V2) don't (sufficiently) narrow down the parameters inside of the closure. E.g.

<?php namespace ParamsArrayIntersectUassoc;

use function PHPStan\Testing\assertType;

var_export(array_uintersect_assoc(
	['a' => 'a', 'b' => 'b'],
	['c' => 'c', 'd' => 'd'],
	function (mixed $a, mixed $b): int {
		assertType("'a'|'b'|'c'|'d'", $a);
		assertType("'a'|'b'|'c'|'d'", $b);

		return 1;
	},
));

Results in:

  9      Expected type 'a'|'b'|'c'|'d', actual: string  
  10     Expected type 'a'|'b'|'c'|'d', actual: string  

So I suppose that should be fixed first. I'll try to have a look (no promises).

@schlndh
Copy link
Contributor Author

schlndh commented Aug 25, 2024

@ondrejmirtes I created a proof-of-concept PR which applies FunctionParameterClosureTypeExtension in FunctionCallParametersCheck. Please let me know whether this is a direction that you're comfortable with, so that I don't get too far down a wrong path again.

@schlndh
Copy link
Contributor Author

schlndh commented Aug 30, 2024

@ondrejmirtes I had another look at this, but I'm not convinced that FunctionParameterClosureTypeExtension is the way to go:

  • selectFromArgs does not know the function name. So it's not possible to modify the signatures of these functions there (without further changes).
  • FunctionParameterClosureTypeExtension doesn't get the argument for which it's being called, so without receiving the hacked ParameterReflection it cannot do anything.

Would you be willing to move forward with the simple stub approach instead? I'd expect 2 array arguments to be the most common usage, so at least it's something (array_udiff is already handled this way).

Or perhaps even the "variadic in the middle" approach. Though you're right that it's very unexpected, so it could definitely lead to random bugs (I also forgot to look for other places which might need handling - e.g. NodeScopeResolver). On the other hand matching arguments to parameters is quite complicated in general, so probably not many people attempt to do that themselves.

@schlndh schlndh marked this pull request as draft August 30, 2024 11:48
mad-briller and others added 6 commits September 13, 2024 12:20
array_diff_uassoc
array_diff_ukey
array_intersect_uassoc
array_intersect_ukey
array_udiff_assoc
array_udiff_uassoc
array_uintersect_assoc
array_uintersect_uassoc
array_uintersect
@schlndh schlndh changed the base branch from 1.11.x to 1.12.x September 13, 2024 10:58
@schlndh schlndh force-pushed the feature-arrayUserCallbackTypes branch from d0f811b to cbc7c87 Compare September 13, 2024 10:58
@schlndh schlndh marked this pull request as ready for review September 13, 2024 11:12
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

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.

4 participants