Skip to content

Add @phpstan-self-out support #1799

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

Merged
merged 7 commits into from
Oct 11, 2022
Merged

Conversation

rvanvelzen
Copy link
Contributor

This is a "missing" feature from the assert family which allows changing the $this type after a method call, which is particularly useful for setters.

See https://psalm.dev/docs/annotating_code/adding_assertions/#asserting-return-values-of-methods for the docs for psalm.

Syntax implementation in phpstan/phpdoc-parser#149

@rvanvelzen
Copy link
Contributor Author

Initially I used $thisOutType everywhere, but the generated code for \PHPStan\Reflection\Php\PhpMethodReflectionFactory::create() turned it into $this->containerOutType 🤷‍♂️

@rvanvelzen
Copy link
Contributor Author

This also needs a rule that checks if the out type is a subtype of the class. The type is currently unrestricted (just like in psalm) so generating an error for incompatible types is useful.

@rvanvelzen
Copy link
Contributor Author

I don't understand why CI is failing :/

@ondrejmirtes
Copy link
Member

Looks like Rector fails and doesn't transform the code at all...

@herndlm
Copy link
Contributor

herndlm commented Oct 6, 2022

I had the exact same thing in a PR a couple of days ago where I modified MutatintgScope and its factories

@ondrejmirtes
Copy link
Member

One theory I have is that Rector parallelism isn't really compatible with our caching, so I tried this: c5d186b

Hopefully the build passes now.

@ondrejmirtes
Copy link
Member

So the problem that's happening now is that Rector loads the wrong version of phpdoc-parser, and fails to do its job, because it's calling to an undefined method.

Rector should really preload its own version of phpdoc-parser, like PHPStan does with nikic/php-parser...

@ondrejmirtes
Copy link
Member

Oh it actually does, so the problem is something else still.

@ondrejmirtes
Copy link
Member

Alright, so the problem is that Rector preloads its own version of phpdoc-parser, but it actually uses PHPStan from our current src, where there's a call to a nonexistent Rector's phpdoc-parser method 😂

@herndlm
Copy link
Contributor

herndlm commented Oct 6, 2022

Ah OK, looks like a different error that I had with MutatingScope in #1783 then..

@ondrejmirtes
Copy link
Member

That's gonna be the same category of problem...

@rvanvelzen
Copy link
Contributor Author

Thanks for looking into the rector issue!

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.

And now we have a green build, I can do a proper review :) Thanks!

@@ -216,6 +220,7 @@ public function merge(array $parents, array $parentPhpDocBlocks): self
$result->typeAliasTags = $this->getTypeAliasTags();
$result->typeAliasImportTags = $this->getTypeAliasImportTags();
$result->assertTags = self::mergeAssertTags($this->getAssertTags(), $parents, $parentPhpDocBlocks);
$result->selfOutTypeTag = $this->getThisOutTag();
Copy link
Member

Choose a reason for hiding this comment

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

If the method overrides a parent method, it should use the this-out tag from the parent? That's what all the other tags do here.

Also - selfOutTypeTag vs. getThisOutTag, consistency please :) I like self more.

@@ -297,6 +302,7 @@ public function changeParameterNamesByMapping(array $parameterNameMapping): self
$self->typeAliasTags = $this->typeAliasTags;
$self->typeAliasImportTags = $this->typeAliasImportTags;
$self->assertTags = $assertTags;
$self->selfOutTypeTag = $this->getThisOutTag();
Copy link
Member

Choose a reason for hiding this comment

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

All the other properties here assign properties, so it should be consistent. That's because we don't need to resolve the tag at this point, let false be false.

$classReflection = $method->getDeclaringClass();
$classType = new ObjectType($classReflection->getName(), null, $classReflection);

if (!$classType->isSuperTypeOf($selfOutType)->no()) {
Copy link
Member

Choose a reason for hiding this comment

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

The problem here is that it allows $selfOutType to be wider than $classType. Means it can do @phpstan-self-out self|null for example.

I'd change this condition to if ($classType->isSuperTypeOf($selfOutType)->yes()) {.


return [
RuleErrorBuilder::message(sprintf(
'Out type %s is not compatible with %s.',
Copy link
Member

Choose a reason for hiding this comment

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

Would be nice to have more details here. Like Self-out type %s of method %s::%s() is not subtype of %s. That matches a similar message about PHPDoc vs. native type...

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.

Oh, and InvalidPHPStanDocTagRule isn't updated here.

@rvanvelzen rvanvelzen marked this pull request as ready for review October 8, 2022 14:18
@ondrejmirtes ondrejmirtes mentioned this pull request Oct 8, 2022
5 tasks
@ondrejmirtes ondrejmirtes merged commit f097ca9 into phpstan:1.9.x Oct 11, 2022
@ondrejmirtes
Copy link
Member

Thank you very much!

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.

3 participants