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

Fix bug #9630 (?) #3353

Draft
wants to merge 4 commits into
base: 1.12.x
Choose a base branch
from

Conversation

FoxCie
Copy link

@FoxCie FoxCie commented Aug 26, 2024

Hello, I am trying to make use of generics work when using traits from traits (see phpstan/phpstan#9630). It works totally fine when using a single file, but not when separating classes and traits in different files.

The main reason, from what I have seen, is that caching is made using file names, and when using traits, the file name used as the cache key is the one of the class that is using the traits, which is not the filename of the direct user of the trait if some traits are used in traits.

For example, if a class C uses trait T1 that uses trait T2, the cache key when computing T2 in the context of C would be computed with the file in which T1 is defined, when it is expected to be the one in which C is defined.

That is my comprehension of what is done there, but I may be completely mistaken. I tried to handle the case of the wrong cache key by searching in the cache with the filename of the class using the traits if the search fails with original parameters. I am not sure my PR fixes everything correctly, because I was unable to build the PHAR package locally to test it on my real project in which the bug happens. It seemed to work on an example I made, but I may have failed reproducing the behaviour correctly.

This PR is work in progress, I tried to see if I could download the PHAR package from the artifacts of the CI to test.

@ondrejmirtes
Copy link
Member

Please write a failing test here in the test suite, that way we can verify if your fix works.

…and another one that shows a bug that may have appeared when fixing the other one.
@FoxCie
Copy link
Author

FoxCie commented Aug 28, 2024

Ok, I wrote a test that should pass here but not on the main branch, but doing so I noticed another bug that has appeared in my fix.

The bug is similar to what happened in bug phpstan/phpstan#4557 that has been fixed : PHPStan gets confused about templates with same names from different contexts.
I am not sure why this happens, but from what I understand, traits use are "flattened" when processing them, meaning they all belong to the class that uses the traits, without hierarchical structure. This means that if class C uses trait T1 and trait T1 uses trait T2, traits T1 and T2 end up being related to class C without information about T1 using T2, and that may lose the context of template names?

From what I have seen while debugging, templates are in maps that map template names to their actual type, with some context, but either this context is lost somehow, or it is not used to resolve template names.

EDIT: when I wrote that the context is lost somehow, I suspect it is due to the fact that we actually use the same file name as key in the cache; if this key is also used to map template types, then that might cause conflicts.

@FoxCie FoxCie marked this pull request as draft August 28, 2024 09:44
@FoxCie
Copy link
Author

FoxCie commented Aug 29, 2024

So I tracked down the issue with conflicting template names to this line https://github.com/phpstan/phpstan-src/blob/1.12.x/src/Type/FileTypeMapper.php#L676 where the template type maps are merged when handling traits use, which comes down to an array_merge on the types here : https://github.com/phpstan/phpstan-src/blob/1.12.x/src/Analyser/NameScope.php#L168. So template types are merged in a single map, with their names used as keys without any context, meaning that if two traits are used that define templates with the same name, one will replace the other in the final map.

However, I don't really know how to fix this. Maybe we should add the trait name in front of the template name ? Like T1::T and T2::T. But then, it should probably be done everywhere to get the correct name resolution when resolving the types.
If the search is recursive, we could try to merge maps instead of types ? I am not used to dealing with this code, since I have only started to read it to fix this issue a week ago.

What do you think @ondrejmirtes? Do you have any idea on how this could be handled properly?

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.

2 participants