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

Transform void return to null after call #2778

Merged
merged 7 commits into from Dec 11, 2023

Conversation

herndlm
Copy link
Contributor

@herndlm herndlm commented Nov 26, 2023

Closes phpstan/phpstan#6720

What I meant with question marks yesterday

  • I'm not a 100% sure about this yet, we can see that it turns off many reports and e.g. UsageOfVoidMatchExpressionRule seems to be not doing anything anymore I guess
  • On the other hand it is how PHP behaves
  • I noticed that Generator void returns are not handled

Also thought of: only transform void in union/intersection types, but that does feel inconsistent..

WDYT?

@ondrejmirtes
Copy link
Member

I think the problem with existing rules might not be that hard to fix.

Most of the failures are from the error that's supposed to be reported here:

if (
!$funcCall instanceof Node\Expr\New_
&& !$scope->isInFirstLevelStatement()
&& $scope->getType($funcCall)->isVoid()->yes()
) {
$errors[] = RuleErrorBuilder::message($messages[7])
->identifier(sprintf('%s.void', $nodeType))
->line($funcCall->getLine())
->build();

We could for example clone the node and add an attribute like 'keepVoid' => true that will not transform void into null while resolving the type in MutatingScope.

@herndlm
Copy link
Contributor Author

herndlm commented Nov 27, 2023

I think the problem with existing rules might not be that hard to fix.

Most of the failures are from the error that's supposed to be reported here:

if (
!$funcCall instanceof Node\Expr\New_
&& !$scope->isInFirstLevelStatement()
&& $scope->getType($funcCall)->isVoid()->yes()
) {
$errors[] = RuleErrorBuilder::message($messages[7])
->identifier(sprintf('%s.void', $nodeType))
->line($funcCall->getLine())
->build();

We could for example clone the node and add an attribute like 'keepVoid' => true that will not transform void into null while resolving the type in MutatingScope.

I had a similar thought. Basically, we'd need to either a) keep the information somewhere that this was a void before, either e.g. in the type or the node as you said or b) modify the void type to behave more like null, since it's a special return-only type it always means that it must have came from a return. I guess the "remember in node" approach is the cleanest and one that makes most sense :) I also quickly checked psalm yesterday and looks like it's transforming void to null too basically.

@herndlm
Copy link
Contributor Author

herndlm commented Nov 28, 2023

hmm, this keeps being tricky, I tried to clone the node in rules and add an attribute as you suggested. problem: there were cases where the type was already resolved and it would get it from the internal cache via key again. this could also be circumvented I guess? it's a bit unfortunate hmm.
I'd like to have a wasVoid() method on the type that tells me this, but feels like way too much overhead just for this.. :)

@ondrejmirtes
Copy link
Member

@herndlm Change the cache key based on the attribute in MutatingScope::getNodeKey().

@herndlm herndlm force-pushed the transform-void-return-to-null branch from b51abd9 to 7dbe5a8 Compare November 29, 2023 16:39
@@ -674,6 +674,10 @@ private function getNodeKey(Expr $node): string
$key .= '/*' . $node->getAttribute('startFilePos') . '*/';
}

if ($node->hasAttribute('keepVoid')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

would be great if we could put this string into a class-constant somewhere.

that way it would ease navigation with the IDE and make dependent parts obvious

Copy link
Contributor Author

@herndlm herndlm Nov 29, 2023

Choose a reason for hiding this comment

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

yeah, I wanted to check first if all is fine and then ask about how to properly refactor this thing ☺️ I hate that it is sprinkled x times around the code base now..

Copy link
Member

Choose a reason for hiding this comment

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

Could be a new method on Scope, just don't start it with getType, I'd hate to confuse autocompletion with this niche use case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

still need to do this refactor, missed the comment. currently working on it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

looks like I'm prone to commenting race conditions today.. :) did something, not sure about the name of course. this is now in Scope after all and should be stable. WDYT?

@herndlm
Copy link
Contributor Author

herndlm commented Nov 29, 2023

got it working, this is looking not bad, did only quickly go through the failures, the ones that worry me

  • Method call return value that should be used, but is not from simplify/simplify, no clue what that is yet.
  • the Parameter #2 .. of class .. expects Closure(): void, Closure(): null given. of PMMP, could be a sign that more adaptions are needed hmm

@ondrejmirtes
Copy link
Member

Closure(): void, Closure(): null given.

Stop early in the callback:

if ($type instanceof CallableType || $type instanceof ClosureType) {
    return $type;
}

About simplify - don't worry about it, they'll have to fix their code.

@staabm
Copy link
Contributor

staabm commented Nov 29, 2023

symplify/symplify is abandoned. parts of it moved to symplify/phpstan-rules

@ondrejmirtes
Copy link
Member

I've update the tests in phpstan/phpstan to symplify/phpstan-rules, you can try again :)

@herndlm
Copy link
Contributor Author

herndlm commented Nov 30, 2023

sorry, still trying to recreate one of the failures of PMMP to properly test the fix..
currently e.g.

class Bar
{

	/** @phpstan-param \Closure(): void $handler */
	public function doFoo(\Closure $handler): void
	{
		\PHPStan\dumpType($handler()); // void
		\PHPStan\dumpType($this->handler()); // null
	}

	public function doBar(): void
	{
		$handler = $this->handler(...);
		\PHPStan\dumpType($handler); // Closure(): void
		$this->doFoo($handler);
	}

	private function handler(): void
	{
	}

}

is working just fine in an AnalyserIntegrationTest which I don't understand. I tried to replicate the CI failure I saw via https://github.com/pmmp/PocketMine-MP/blob/0984aa670d5c04e4714853dbf738f51bfa4f39e1/src/network/mcpe/handler/SessionStartPacketHandler.php#L39 and https://github.com/pmmp/PocketMine-MP/blob/0984aa670d5c04e4714853dbf738f51bfa4f39e1/src/network/mcpe/NetworkSession.php#L201

but I can of course just try to adapt the traverse callback 😅

@ondrejmirtes
Copy link
Member

This passes on 1.10.x but fails on your PR: https://phpstan.org/r/f8d2decc-2d58-4ce4-a2b3-d811700abe9f

@ondrejmirtes
Copy link
Member

I'd say we need to do the same keepVoid thing for inferring the return type of arrow function in MutatingScope around here:

$returnType = $arrowScope->getType($node->expr);
if ($node->returnType !== null) {
$returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType);
}

@herndlm
Copy link
Contributor Author

herndlm commented Nov 30, 2023

bingo! thx again :)

what do you think about the previous code example I pasted, it looks like those closure returns are not transformed to null yet. I guess that could be fixed?

@ondrejmirtes
Copy link
Member

it looks like those closure returns are not transformed to null

That's okay and actually correct behaviour. We don't want to transform void to null in a return type there.

Your TypeTransformer callback only transforms standalone void + void in union and intersection 👍

@herndlm herndlm force-pushed the transform-void-return-to-null branch 4 times, most recently from 42d4d9f to edf2ead Compare November 30, 2023 14:17
@herndlm
Copy link
Contributor Author

herndlm commented Nov 30, 2023

sorry for the many force pushes, should be good now. do you not think we should refactor it somehow? e.g. put the 'keepVoid' in some constant, or maybe even have some static helper methods to add / preserve it or so.

@herndlm herndlm marked this pull request as ready for review November 30, 2023 15:00
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@ondrejmirtes
Copy link
Member

Please see #2778 (comment)

src/Analyser/MutatingScope.php Show resolved Hide resolved
src/Analyser/MutatingScope.php Show resolved Hide resolved
@staabm
Copy link
Contributor

staabm commented Dec 11, 2023

@herndlm this PR here will render the PR at #2494 obsolete, right?

@ondrejmirtes ondrejmirtes merged commit 1b4b134 into phpstan:1.10.x Dec 11, 2023
420 of 424 checks passed
@ondrejmirtes
Copy link
Member

Thank you!

@ondrejmirtes
Copy link
Member

@herndlm Can you please look at this bug? https://github.com/WordPress/WordPress-Coding-Standards/pull/2416/files

We should allow returning null from void method when it's typed in a PHPDoc. Here's the code: https://github.com/WordPress/WordPress-Coding-Standards/blob/develop/WordPress/Sniffs/WhiteSpace/ObjectOperatorSpacingSniff.php

@herndlm
Copy link
Contributor Author

herndlm commented Dec 12, 2023

I dislike those WP void unions.. but sure. https://phpstan.org/r/12a90bd8-64db-4620-855c-23579d0085f6 seems to be a simple reproducer too.

@@ -1236,7 +1242,7 @@ private function resolveType(string $exprString, Expr $node): Type
new VoidType(),
]);
} else {
$returnType = $arrowScope->getType($node->expr);
$returnType = $arrowScope->getKeepVoidType($node->expr);
Copy link
Contributor

Choose a reason for hiding this comment

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

does it make sense to handle a "void" return type of a arrow function? can arrow functions have a void return type?

Copy link
Member

Choose a reason for hiding this comment

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

Try to change it and see what fails. I think it's valid, at least for a throw expr or calling never function.

Copy link
Contributor

Choose a reason for hiding this comment

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

just found #2778 (comment) which answers this question, I guess

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm locally I did not get a test failure after changing it. lets see #2873

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