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

Dynamic function storage provider #7471

Merged
merged 18 commits into from Jan 28, 2022
Merged

Dynamic function storage provider #7471

merged 18 commits into from Jan 28, 2022

Conversation

klimick
Copy link
Contributor

@klimick klimick commented Jan 23, 2022

Closes #7450
New plugin hook. It allows to create FunctionStorage for hard to infer functions by Psalm.
I've added test for custom_array_map. custom_array_map is similar to native array_map.
Of course plugin hook don't cover all cases that covered by Psalm for array_map. But it possible.
That means if we improve the hook, we can remove the weird code inside Psalm for array_map or array_filter.

With new hook we can create FunctionStorage for pipe or curry or partial and many other functions from functional world.
I can share plugin hooks for pipe and partialRight (without hardcoded templates as in #7470) if new hook is legit.
@veewee @orklah

Copy link
Contributor

@veewee veewee left a comment

Choose a reason for hiding this comment

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

Nice!
This opens up a lot of flexibility for those variadics generic cases!

This pr still contains the preanalysed version of the function params hook. Is it the idea to keep it there since we can now use this new hook?

tests/Config/Plugin/Hook/ArrayMapStorageProvider.php Outdated Show resolved Hide resolved
@klimick
Copy link
Contributor Author

klimick commented Jan 23, 2022

This pr still contains the preanalysed version of the function params hook. Is it the idea to keep it there since we can now use this new hook?

I would suggest to remove old hook in favor new one.
In my opinion old hook is unuseful against new hook. New hook can do the same and more.

@drupol
Copy link
Contributor

drupol commented Jan 23, 2022

Guys, it's amazing to read your comments and the proactivity ! Even if I don't understand anything, I'm pretty sure you're going to make something great that I really want to use ASAP !

@klimick klimick marked this pull request as draft January 24, 2022 14:56
@orklah
Copy link
Collaborator

orklah commented Jan 24, 2022

I would suggest to remove old hook in favor new one.

Would old plugins be easy to migrate? If we do it, it's the right moment, we just removed the old plugins interfaces in Psalm 5, it's possible a lot of plugins didn't migrate already

@veewee
Copy link
Contributor

veewee commented Jan 24, 2022

I would suggest to remove old hook in favor new one.

Would old plugins be easy to migrate? If we do it, it's the right moment, we just removed the old plugins interfaces in Psalm 5, it's possible a lot of plugins didn't migrate already

It depends which ones are gonna be removed.
The function return type hooks are being used quite often in our plugin and still make sense. You don't always want to build up the full function storage.

The one for function parameters I don't think is being used commonly. Because you can't really do that much with it at this moment. But I can only speak for my own plugins :)

Current hooks are compatible with the once for methods as well. There we don't have this new hook.

@klimick
Copy link
Contributor Author

klimick commented Jan 24, 2022

It depends which ones are gonna be removed.

I suggest to remove FunctionParamsProviderInterface and MethodParamsProviderInterface.
As I understand: It provides dynamic signature using raw arg nodes.
With (Function|Method)ParamsProviderInterface + (Function|Method)ReturnTypeProviderInterface we can organize function that return type depends from input args. But it can't provide templates. It can't cover hard to infer functions (native array_map, pipe, curry and other).

On the other side, new hook is very low level. Writing plugin for custom_array_map was not easy as it might seem.
For functions without template params we can leave FunctionParamsProviderInterface and MethodParamsProviderInterface.

@veewee
Copy link
Contributor

veewee commented Jan 25, 2022

It depends which ones are gonna be removed.

I suggest to remove FunctionParamsProviderInterface and MethodParamsProviderInterface. As I understand: It provides dynamic signature using raw arg nodes. With (Function|Method)ParamsProviderInterface + (Function|Method)ReturnTypeProviderInterface we can organize function that return type depends from input args. But it can't provide templates. It can't cover hard to infer functions (native array_map, pipe, curry and other).

On the other side, new hook is very low level. Writing plugin for custom_array_map was not easy as it might seem. For functions without template params we can leave FunctionParamsProviderInterface and MethodParamsProviderInterface.

It might make sense to keep them, for making the upgrade traject easier for maintainers as well.
It gives integrations the flexibility of either taking the more simple plugin approach or have full control with the dynamic one.

@orklah
Copy link
Collaborator

orklah commented Jan 25, 2022

Ok then let's keep other hooks for now.

Before I dive too deep in the code, I have a few questions:

  • does it solves the issue that was created by calling ArgumentsAnalyzer twice?
  • can you add some documentation for the new plugin hook and specify when to use the old ones and the new ones
  • Is ArrayMapStorageProvider just for test purposes? Is it normal it targets custom_array_map only?

@klimick
Copy link
Contributor Author

klimick commented Jan 25, 2022

  • does it solves the issue that was created by calling ArgumentsAnalyzer twice?

I've removed double args analysis just now. This can lead to many problems. If you have not yet understood what the problems are, I can try to describe them.

  • can you add some documentation for the new plugin hook and specify when to use the old ones and the new ones

Of course I can. When PR will ready.
I think we should have hook like this but for class methods. i.e. MethodDynamicStorageProvider. What do you think?

  • Is ArrayMapStorageProvider just for test purposes? Is it normal it targets custom_array_map only?

Currently just for test. I planned to use it as example for documentation.
If you count it too complicated example I will try to write easy one.

@klimick
Copy link
Contributor Author

klimick commented Jan 25, 2022

I removed internal classes usage and tried to make a public api instead.

  • ArgTypeInferer: can use we need to infer argument type
  • DynamicTemplateProvider: template creator. Not all plugin users know about $defining_class and what value it should be. And hook can works with many function that enumerated in getFunctionIds. It's a little bit difficult pass the right value to 'TTemplateParam'. So creator can create well formed TTemplateParam.
  • DynamicFunctionStorage: I've make it because in the FunctionStorage there are useless properties for hook (at my view point)

What do you think? @veewee @orklah

@weirdan
Copy link
Collaborator

weirdan commented Jan 25, 2022

Before I dive too deep in the code, I have a few questions [...]

Also, how does it interact with cache?

@klimick
Copy link
Contributor Author

klimick commented Jan 26, 2022

Also, how does it interact with cache?

I didn't quite understand what you mean.

Dynamically created storage saves only in the FunctionDynamicStorageProvider in $dynamic_storages.
Because there is cases where FunctionDynamicStorageProvider should be called twice for the same expression.
At the second call FunctionDynamicStorageProvider returns already computed storage.

As I know Psalm treats each closure as unique and create for them individual storage. Does Psalm cache it?
Dynamically analyzed function has next id:

$dynamic_storage_id = strtolower($statements_analyzer->getFilePath())
            . ':' . $stmt->getLine()
            . ':' . (int)$stmt->getAttribute('startFilePos')
            . ':dynamic-storage'
            . ':-:' . strtolower($function_id);

Id from FunctionDynamicStorageProvider has similar to closure id.

These two calls will be analyzed individually and each call will have their id:

custom_array_map(
   fn($i) => $i + 1,
   [1, 2, 3]
);
custom_array_map(
   fn($i) => (string) $i,
   fn($i) => $i + 1,
   ['1', '2', '3']
);

There is sense to cache it? And if there is. How?

@weirdan
Copy link
Collaborator

weirdan commented Jan 27, 2022

I didn't quite understand what you mean.

I meant the file cache Psalm uses to speed up things on subsequent runs. Normally FunctionStorage instances are cached as part of FileStorage. Having looked at the proposed changes once again, I think the answer is 'it doesn't interact with cache in any way, as it's used during call analysis'. Also, it appears it would shadow the FunctionStorage Psalm may generate during the scanning phase. It would be nice to have this documented in the source code itself - say, in DynamicFunctionStorageProvider docblock.

Also, would you mind renaming all occurrences of function dynamic storage to dynamic function storage? I believe it would read better that way.

@klimick klimick marked this pull request as ready for review January 28, 2022 13:12
@orklah
Copy link
Collaborator

orklah commented Jan 28, 2022

Thanks!

@orklah orklah merged commit 4609bc4 into vimeo:master Jan 28, 2022
@drupol
Copy link
Contributor

drupol commented Jan 28, 2022

Excellent work, thanks !

@veewee
Copy link
Contributor

veewee commented Jan 28, 2022

Great job!

@klimick klimick changed the title Function dynamic storage provider Dynamic function storage provider Jan 29, 2022
@thomasvargiu
Copy link
Contributor

Will be released in v5?

@orklah
Copy link
Collaborator

orklah commented Apr 26, 2022

Yeah, this has been targeted on master branch, so with V5 release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: Need review release:feature The PR will be included in 'Features' section of the release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants