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

Using generators to get a list of reflections #1476

Open
wants to merge 2 commits into
base: 6.52.x
Choose a base branch
from

Conversation

shcherbanich
Copy link

Part of PR #1475 related to using generators instead of arrays to get a list of reflections

What has been done?

  1. locateIdentifiersByType now returns a generator to process each individual class sequentially

  2. reflectAllClasses / reflectAllFunctions / reflectAllConstants methods of DefaultReflector class now return generators instead of arrays

  3. FindReflectionOnLine now takes reflections one by one using generators

*/
public function reflectAllClasses(): iterable
public function reflectAllClasses(): Generator
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to change the native type here: iterable is a supertype of Generator, and works fine

@@ -43,11 +44,11 @@ public function reflectClass(string $identifierName): ReflectionClass
/**
* Get all the classes available in the scope specified by the SourceLocator.
*
* @return list<ReflectionClass>
* @return Generator<ReflectionClass>
Copy link
Member

Choose a reason for hiding this comment

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

We should roll back to iterable<int, ReflectionClass> here

Copy link
Member

Choose a reason for hiding this comment

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

This change is the actual BC break, since list<T> is obviously an extremely small type

*/
public function reflectAllFunctions(): iterable
public function reflectAllFunctions(): Generator
Copy link
Member

Choose a reason for hiding this comment

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

Same as comment above: the native type can stay iterable, and the return type can be iterable<int, ReflectionFunction>

Comment on lines +119 to +121
* @return Generator<ReflectionConstant>
*/
public function reflectAllConstants(): iterable
public function reflectAllConstants(): Generator
Copy link
Member

Choose a reason for hiding this comment

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

Same as comment above: the native type can stay iterable, and the return type can be iterable<int, ReflectionConstant>

Comment on lines -24 to +27
* @return list<ReflectionClass>
* @return Generator<ReflectionClass>
*/
public function reflectAllClasses(): iterable;
public function reflectAllClasses(): Generator;
Copy link
Member

Choose a reason for hiding this comment

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

This interface should be rolled back, and the @return types should switch from list to iterable

*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator
Copy link
Member

Choose a reason for hiding this comment

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

The implementation of this class can be rolled back, except for the return type being iterable

* {@inheritDoc}
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator
Copy link
Member

Choose a reason for hiding this comment

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

The implementation of this class can be rolled back, except for the return type being iterable

@@ -83,8 +84,8 @@ public function locateIdentifier(Reflector $reflector, Identifier $identifier):
*
* @throws InvalidFileLocation
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator
Copy link
Member

Choose a reason for hiding this comment

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

The implementation of this class can be rolled back, except for the return type being iterable

Comment on lines +52 to +54
$items = [];
foreach ($this->wrappedSourceLocator->locateIdentifiersByType($reflector, $identifierType) as $item) {
yield $items[] = $item;
Copy link
Member

Choose a reason for hiding this comment

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

The original implementation caches items before returning them: the new implementation caches items after returning them.

I'm unsure about approach here, but it is a subtle change that can lead to uncached results if the consumer of this iterator crashes

Comment on lines +30 to +32
* @return Generator<Reflection>
*/
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array;
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): Generator;
Copy link
Member

Choose a reason for hiding this comment

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

We should move to iterable rather than Generator

@Ocramius Ocramius added this to the 7.0.0 milestone Jan 6, 2025
@Ocramius
Copy link
Member

Ocramius commented Jan 6, 2025

@asgrim @kukulich I think the BC break is worth it here: what do you think? Is it OK to move the source locators and API from list<T> to iterable<int, T>?

@asgrim
Copy link
Member

asgrim commented Jan 6, 2025

@asgrim @kukulich I think the BC break is worth it here: what do you think? Is it OK to move the source locators and API from list<T> to iterable<int, T>?

Reasonable change yes; any impact to our own projects we can handle but definitely keen for @kukulich to check here; depending how phpstan consumes these APIs, may or may not be impactful

@kukulich
Copy link
Collaborator

kukulich commented Jan 6, 2025

@ondrejmirtes will this change be a problem for PHPStan?

@ondrejmirtes
Copy link
Contributor

@kukulich Yes, it's a BC break. For example I have custom source locator wrappers which do this:

	public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
	{
		return $this->sourceLocator->locateIdentifiersByType($reflector, $identifierType);
	}

If these changes really lead to better performance (so that BR does not do work that would be thrown away), it might be a better idea to introduce new methods or interfaces that return generators, and keep the old classes and interfaces there.

After that we can keep the old methods that will just do iterator_to_array on the new methods. Optionally we could deprecated the "old" methods.

Unfortunately if these changes go through and BetterReflection releases a new major, I could not upgrade to it in PHPStan with a clear conscience until 3.0 because these source locators are part of public PHPStan API. I know that for example Rector depends on them and also creates its own implementations.

@shcherbanich
Copy link
Author

@Ocramius @asgrim @kukulich Given all the complexities, let me go back to the first implementation, creating new methods (e.g. iterateIdentifiersByType / iterateAllClasses / ... ) and marking the old ones as deprecated, as noted in the comment above. This won't be difficult, and the change can be released safely as soon as possible.

@Ocramius
Copy link
Member

Ocramius commented Jan 6, 2025

@shcherbanich let it rest a bit before jumping at it: as you can see, discussion is underway, at least :D

I could not upgrade to it in PHPStan with a clear conscience until 3.0 because these source locators are part of public PHPStan API.

This is a real bummer: stuff like source locators in BetterReflection are marked internal for good reason (their API is squishy), so this is unfortunate.

That said, I fully understand the problem, and will think about it today/tomorrow, before we decide whether/how to proceed.

@ondrejmirtes
Copy link
Contributor

Are they really internal? I'm also implementing my own and I'd say it's often useful for projects to write their own. So it might be better to un-internal them.

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

Successfully merging this pull request may close these issues.

5 participants