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 variable certainty for ArrayDimFetch in falsey isset context #2666

Merged
merged 31 commits into from
Oct 30, 2023

Conversation

staabm
Copy link
Contributor

@staabm staabm commented Oct 8, 2023

triggered by #2657 (comment)


btw: I realized that - across the PHPStan codebase - we are sometimes use falsey and falsy which is something we could fix with PHPStan 2.0

@ondrejmirtes
Copy link
Member

Please reproduce and fix the same problem with Empty_.

@staabm
Copy link
Contributor Author

staabm commented Oct 8, 2023

hmm interessting.. it does not reproduce with Empty_

@staabm
Copy link
Contributor Author

staabm commented Oct 8, 2023

Ohh even more interessting.. it does not reproduce in a NodeScopeResolverTest, but running the same code directly with phpstan in a separate file reproduces the problem for Empty_ -> added a new AnalyserIntegrationTest

@staabm staabm force-pushed the falsey-isset-certainty branch 2 times, most recently from ffe5f0a to e7c2382 Compare October 8, 2023 19:35
@staabm staabm marked this pull request as draft October 8, 2023 19:54
@staabm
Copy link
Contributor Author

staabm commented Oct 9, 2023

after debugging deep in PHPStan internals, I think I am able to pinpoint the underlying issue.

while handling Isset_ or Empty_ there is some expression types storing and re-storing going on.

while this process a variable which was "maybe" before automatically got a "yes"-certainty, because assigning a type to a variable automatically made it certain.

I fixed this mechanism to also remember the previous certainty of the variable, not just the type and native-type.

@staabm
Copy link
Contributor Author

staabm commented Oct 9, 2023

regarding build errors


Carbon:

Error: Variable $date might not be defined.
 ------ -------------------------------------- 
  Line   tests/CarbonPeriod/ToArrayTest.php    
 ------ -------------------------------------- 
  100    Variable $date might not be defined.  
 ------ -------------------------------------- 

https://github.com/briannesbitt/Carbon/blob/0af369fba2435afe17a97cfa9a02a8e84d6c6640/tests/CarbonPeriod/ToArrayTest.php#L95-L100

I think this error could be valid, as we are probably not narrow the type of $date via assertInstanceOfCarbon() - which sounds like a carbon specific unit test utility to me


the efabrica-team/phpstan-latte error look wild.. I wonder whether this are related.


drupal

 ------ --------------------------------------------- 
  Line   core/modules/shortcut/shortcut.module        
 ------ --------------------------------------------- 
  294    Variable $shortcut_id might not be defined.  
  297    Variable $shortcut_id might not be defined.  
 ------ --------------------------------------------- 

https://github.com/drupal/drupal/blob/2700c5afb6c3936041db413872eea82dc0bd4fe4/core/modules/shortcut/shortcut.module#L280-L298

these look valid to me. it seems we are now detecting these mabye undefined while we were missing them before

------ ----------------------------------------------------------------------------------------------------------------------------- 
  Line   core/modules/views/src/Plugin/views/filter/NumericFilter.php                                                                 
 ------ ----------------------------------------------------------------------------------------------------------------------------- 
  426    Ignored error pattern #^Variable \$value might not be defined\.$# in                                                         
         path                                                                                                                         
         /home/runner/work/phpstan-src/phpstan-src/e2e/integration/repo/core/modules/views/src/Plugin/views/filter/NumericFilter.php  
         is expected to occur 2 times, but occurred 3 times.                                                                          
  432    Variable $value might not be defined.                                                                                        
 ------ ----------------------------------------------------------------------------------------------------------------------------- 

https://github.com/drupal/drupal/blob/2700c5afb6c3936041db413872eea82dc0bd4fe4/core/modules/views/src/Plugin/views/filter/NumericFilter.php#L423-L438

also looks valid to me

@staabm staabm marked this pull request as ready for review October 9, 2023 08:16
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

@staabm
Copy link
Contributor Author

staabm commented Oct 9, 2023

I did please see the comment above. I think our comments might be overlapped

@ondrejmirtes
Copy link
Member

Sorry, if there are 21 emails in the thread, I tend to overlook things :)

@ondrejmirtes
Copy link
Member

So if we're currently preserving the certainty, how does PHPStan go from "maybe" to "yes" in this case?

// $foo is Maybe
if (isset($foo)) {
    // $foo is Yes
}

@staabm
Copy link
Contributor Author

staabm commented Oct 9, 2023

excellent question. I had to debug further and look into it even more to answer it ;-).

we get from maybe to yes while we specify non-nullability in

$type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope, $rootExpr);

just pushed a new commit which refactors this code a bit to make it more obvious


another interessting find: it seems we stop specifying types in isset(...) as soon as we encouter the first expression which is undefined

$this->treatPhpDocTypesAsCertain = true;
$this->strictUnnecessaryNullsafePropertyFetch = true;

$this->analyse([__DIR__ . '/../../Analyser/data/bug-3985.php'], [
Copy link
Contributor Author

@staabm staabm Oct 9, 2023

Choose a reason for hiding this comment

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

I decided to add new assertions for this old bug, while in the process of understanding the internals better
(IMO the new assertions better describe what the end user expected)

@ondrejmirtes
Copy link
Member

Yeah, please clean this up as much as possible, and don't forget that both empty and ?? are very similar to isset and might have similar problems.

@ondrejmirtes
Copy link
Member

we get from maybe to yes

I'd still like to get from maybe to yes for other specifiers, like is_string($foo) - if it's a string then we know the certainty is yes.

@staabm
Copy link
Contributor Author

staabm commented Oct 9, 2023

I see. do you talk about new features or something I need to make sure we don't regress?

@ondrejmirtes
Copy link
Member

Not new features, but about this: https://phpstan.org/r/5f6093e0-0f6e-405a-9497-54263c6348b7

I worry that if you preserve certainty, there will be new error on line 12.

@staabm
Copy link
Contributor Author

staabm commented Oct 9, 2023

should we not have the error on line 15 or should we have one on line8?

https://phpstan.org/r/d3ba81f8-188e-47ea-805e-54302006bc7a

@ondrejmirtes
Copy link
Member

We can't go from no to yes and from yes to no.

@ondrejmirtes
Copy link
Member

Also an idea: in the other PR you introduced NotIssetExpr.

I think it should really be IssetExpr and applied in both truthy and falsy contexts.

@staabm
Copy link
Contributor Author

staabm commented Oct 9, 2023

We can't go from no to yes and from yes to no.

lets see if I understood you right ;)

b1981b0

Also an idea: in the other PR you introduced NotIssetExpr.

I think it should really be IssetExpr and applied in both truthy and falsy contexts.

I am fine with it. lets discuss it in the other PR when this one is merged

src/Analyser/NodeScopeResolver.php Outdated Show resolved Hide resolved
src/Analyser/MutatingScope.php Outdated Show resolved Hide resolved
@ondrejmirtes ondrejmirtes merged commit 3de3c85 into phpstan:1.10.x Oct 30, 2023
413 of 417 checks passed
@ondrejmirtes
Copy link
Member

Thank you! Please continue by rebasing and finishing #2657.

@staabm staabm deleted the falsey-isset-certainty branch October 30, 2023 18:10
@ondrejmirtes
Copy link
Member

Hi @staabm this PR caused failures in Drupal and Carbon:

Fortunately it's not released yet. Please look into them otherwise I'll have to revert this. Thanks.

@staabm
Copy link
Contributor Author

staabm commented Nov 3, 2023

I will have a look 👀

@staabm
Copy link
Contributor Author

staabm commented Nov 3, 2023

I had a first look into drupal problem - related source - and reduced it to:

<?php

use function PHPStan\Testing\assertType;
use function PHPStan\Testing\assertVariableCertainty;

function book_node_links_alter():void {
	if (isset($x)) {
		$book_links = 1;
	}

// Expected variable certainty Maybe, actual: No 
	assertVariableCertainty(\PHPStan\TrinaryLogic::createMaybe(), $book_links); 

	if (!empty($book_links)) {
		var_dump($book_links);
	}
}

I wonder why we have a NO certainty here.. is it expected?
its not something which regressed with this PR - its reproducible also with the latest stable release.

@staabm
Copy link
Contributor Author

staabm commented Nov 4, 2023

another finding: in the latest 1.10 release in this snippet we went from No to Yes.

function justEmpty(): void
{
	assertVariableCertainty(TrinaryLogic::createNo(), $foo);
	if (!empty($foo)) {
		assertVariableCertainty(TrinaryLogic::createYes(), $foo);
	}
	assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}

this was changed with this PR here, but regressed the snippet in the above comment.

@staabm
Copy link
Contributor Author

staabm commented Nov 4, 2023

to sum up my investigation of all mentioned failures caused by this PR:

IMO there is only 1 regression, and thats the one reduced above.
I think we need to question whether we are sure about "never go from No to Yes" in certainty, or we need to compensate NO certainty in the rule which emits "Undefined variable: $book_links" when nested in empty() or isset()

all remaining new failures are valid

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