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 new *ParameterClosureTypeExtension #3089

Merged
merged 4 commits into from
May 24, 2024

Conversation

canvural
Copy link
Contributor

Hello 👋🏽

This PR adds new ClosureTypeChangingExtension extension that enables changing the closure type on the receiving side when necessary.

There are couple of issues I want solve before merging it though so I'm making a draft first:

  • Naming 😄 Not sure any of the names I picked. Also I see Dynamic prefix in other extensions, not sure if we should use it here too.
  • The extension finding loop stops as soon as it finds a matching extension. That made sense to me. But we can also gather every type and then union them at the end.
  • I think I tested it enough, but not sure if there is any edge case that I should test also.

@canvural canvural force-pushed the closure-type-changing-extension branch from b53f40c to 187e074 Compare May 23, 2024 08:15
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.

Let's solve the names as the last thing, now is time to solve the fundamentals :)


public function isFunctionSupported(FunctionReflection $functionReflection): bool;

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type;
Copy link
Member

Choose a reason for hiding this comment

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

I feel like this method signature is not sufficient because the parameter where the closure is passed to is also significat, right? So we also need to know ParameterReflection here. Also probably add it to the *Supported method too.

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 feel like this method signature is not sufficient because the parameter where the closure is passed to is also significat, right?

hmm, not sure about that. We want to overwrite that parameter by supplying our own. And for my use case I did not need it. The test case I wrote here is basically the use case I needed.

Copy link
Member

Choose a reason for hiding this comment

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

Let's say you have a method call with three parameters. Each parameter is a callable. You want this extension to override each callable parameter with its own type.

Your current design only works for methods with a single callable parameter, not multiple. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ooh yeah, now I understood. We want to be able to select which parameter we want to modify, right? By checking the parameter name or something.

Copy link
Member

Choose a reason for hiding this comment

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

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.

Done ea44946

@@ -3928,6 +3930,49 @@ private function processClosureNode(
?Type $passedToType,
): ProcessClosureResult
{
if ($stmt instanceof Node\Stmt\Expression && $stmt->expr instanceof Expr\CallLike) {
Copy link
Member

Choose a reason for hiding this comment

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

This needs to work for nested expressions to so to eliminate everything that isn't Node\Stmt\Expression is not ideal.

Copy link
Member

Choose a reason for hiding this comment

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

We also need to do the same thing for processArrowFunctionNode.

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 needs to work for nested expressions to so to eliminate everything that isn't Node\Stmt\Expression is not ideal.

How to do that?

Copy link
Member

Choose a reason for hiding this comment

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

I think my other suggestion (to move to processArgs) will take care of this too.

@@ -3928,6 +3930,49 @@ private function processClosureNode(
?Type $passedToType,
): ProcessClosureResult
{
if ($stmt instanceof Node\Stmt\Expression && $stmt->expr instanceof Expr\CallLike) {
if ($stmt->expr instanceof FuncCall && $stmt->expr->name instanceof Name && $this->reflectionProvider->hasFunction($stmt->expr->name, $scope)) {
Copy link
Member

Choose a reason for hiding this comment

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

We could get rid of these if statements if this logic was implemented in processArgs (where processClosureNode is called) it could be simplified a lot. You wouldn't have to look for the right reflection again, because it's available there as $calleeReflection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure. I can try that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ea44946

Yeah it simplified a lot. But the test case with calling static method on a class string doesn't work. And maybe it doesn't need to work 🤷🏽

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'm not surprised by that. $calleeReflecrion is probably null for that case now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed it for now.

Also I forgot to mention, there was no easy way to access the expression in processArgs, so had to refactor a bit so it accepts the expression (CallLike) now and extracts the args inside.

@canvural canvural force-pushed the closure-type-changing-extension branch from 134fb44 to 3b37e60 Compare May 24, 2024 10:45
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.

This is fine by me 👍

Let's think about the name:

  1. I think we can omit "changing". All extensions "change" something.
  2. I think the extension names should include "parameter".

So something like this:

  • (Function|Method|StaticMethod)ParameterClosureTypeExtension (it communicates that it's about a function parameter and its type when you pass in closure)

Maybe we can also prefix the name with "Dynamic" but it might not be necessary.

Let me know your ideas :)

@canvural
Copy link
Contributor Author

I can't find anything better than (Function|Method|StaticMethod)ParameterClosureTypeExtension actually. Dynamic also feels unnecessary cause every extension is dynamic 😄 But we can put it for consistency if you want. So I'll make the change 👍🏽

The extension finding loop stops as soon as it finds a matching extension. That made sense to me. But we can also gather every type and then union them at the end.

Do you have any thoughts for this one also?

@ondrejmirtes
Copy link
Member

Yeah, "dynamic" is not necessary.

I'm fine with the current logic, if someone is going to need adjusting, we can change it in the future.

I'm going to merge it after the rename, thanks :)

@ondrejmirtes
Copy link
Member

I'm on my phone and remembered one more thing - make sure the extension interfaces are marked with @api and also have a description like other interfaces, with a way to register them in .neon. Thanks!

@canvural canvural changed the title Add new ClosureTypeChangingExtension Add new *ParameterClosureTypeExtension May 24, 2024
@ondrejmirtes ondrejmirtes merged commit a57efdf into phpstan:1.11.x May 24, 2024
442 of 445 checks passed
@ondrejmirtes
Copy link
Member

Thank you!

You can now require phpstan/phpstan:^1.11.3 in Larastan. Right now it's going to download a dev version that already contains this PR. Try to create the extensions you have in mind, and if it works for you, I can release PHPStan 1.11.3 and you can release a new Larastan version :)

@canvural
Copy link
Contributor Author

canvural commented May 24, 2024

Yeah I already tried it with downloading the PHAR file from the PR 😄 And tweeted the results. Everything looks good so far. I'll do more testing.

Thanks for the idea and your help! 😊

@canvural canvural deleted the closure-type-changing-extension branch May 24, 2024 16:06
@ondrejmirtes
Copy link
Member

Alright 😊 Since there already has been a release today, I'm going to release 1.11.3 at some point next week (while in Berlin at IPC).

@canvural
Copy link
Contributor Author

Sure. No rush from my side.

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