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 !isset() with Variable #2710

Merged
merged 37 commits into from Nov 28, 2023
Merged

Fix !isset() with Variable #2710

merged 37 commits into from Nov 28, 2023

Conversation

staabm
Copy link
Contributor

@staabm staabm commented Nov 2, 2023

Another split out of #2657

implements the

Because for $foo with certainty maybe and not-nullable, the only !isset($foo) is that the variable does not exist, so the certainty needs to be no.

part of #2657 (review)


closes phpstan/phpstan#3632
closes phpstan/phpstan#8190
closes phpstan/phpstan#8366
closes phpstan/phpstan#8659
closes phpstan/phpstan#9580
closes phpstan/phpstan#10064
closes phpstan/phpstan#10088

Comment on lines 732 to 737
$nullType = new NullType();
if (!$nullType->isSuperTypeOf($type)->no()) {
Copy link
Contributor Author

@staabm staabm Nov 2, 2023

Choose a reason for hiding this comment

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

I am not sure about this condition

return $exprType;
}

if ($issetExpr instanceof Expr\Variable && is_string($issetExpr->name)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

hopefully its OK to do the variable stuff in a separate PR.
whats missing is ArrayDimFetch, PropertyFetch, StaticPropertyFetch which could be done in a followup?

@staabm
Copy link
Contributor Author

staabm commented Nov 2, 2023

the carbon error seems valid

the larastan errors seem unrelated, as they also show up in other open PRs

the presta shop error looks fishy

@staabm staabm marked this pull request as ready for review November 2, 2023 20:32
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

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.

Also this is the place to test all the different scenarios like I described in the original PR: #2657 (review)

So if we're doing !isset($a):

  1. If the variable is nullable, it means that after this PR it might be null or it might not exist
  2. If the variable is not nullable, it means that after this PR it does not exist anymore

etc...

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

staabm commented Nov 3, 2023

I have more tests and a small fix open todo

@staabm
Copy link
Contributor Author

staabm commented Nov 3, 2023

one interessting failling drupal case:

https://github.com/drupal/drupal/blob/2700c5afb6c3936041db413872eea82dc0bd4fe4/core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php#L138C84-L154

------ ------------------------------------------------------------------- 
  Line   core/modules/user/tests/src/Unit/UserAccessControlHandlerTest.php  
 ------ ------------------------------------------------------------------- 
  152    Variable $result might not be defined.                             
 ------ ------------------------------------------------------------------- 

reduced it to

function doBar(bool $view, bool $edit): void {
	foreach (['view' => $view, 'edit' => $edit] as $operation => $result) {
		assertVariableCertainty(TrinaryLogic::createYes(), $result);

		$result_text = !isset($result) ? 'null' : ($result ? 'true' : 'false'); // Variable $result in isset() always exists and is not nullable.

		assertVariableCertainty(TrinaryLogic::createYes(), $result); // Expected variable certainty Yes, actual: Maybe
		assertType('bool', $result); // Variable $result might not be defined.
	}
}

-> I think its great that PHPStan realized that since $result can never be null and therefore the isset() is actually dead code
-> since we turn the certainty to NO via !isset() we get a misleading consequential false-positve error

@ondrejmirtes
Copy link
Member

Hi, I'm putting this on hold for a while until this problem is resolved: #2666 (comment)

@ondrejmirtes
Copy link
Member

Alright, one more problem this PR should try to fix is:

Expected No, actual certainty of variable $shortcut_id is Maybe in /Users/ondrej/Development/phpstan/tests/PHPStan/Analyser/data/bug-10088.php on line 22.

Not sure why it does not work now.

@ondrejmirtes
Copy link
Member

The problem in Drupal should be fixed, the code in Carbon should still fail because it was wrong from the start.

@ondrejmirtes
Copy link
Member

Did my commit in forward fashion on 1.10.x, only added more tests here.

@ondrejmirtes
Copy link
Member

@staabm
Copy link
Contributor Author

staabm commented Nov 4, 2023

I guess the problem is related that the variable is only defined via dim fetches

(Maybe I can have a look tonight)

@ondrejmirtes
Copy link
Member

That shouldn't matter, right? Also I found out it already fails on 1.10.x so maybe I broke it with my recent commits.

@staabm
Copy link
Contributor Author

staabm commented Nov 4, 2023

It shouldn't - but its a notable difference of the case at hand vs working unit tests

@staabm
Copy link
Contributor Author

staabm commented Nov 4, 2023

Another notable difference of the drupal case: the dim-fetch based declarations are wrapped in a property-fetch based isset()

@staabm
Copy link
Contributor Author

staabm commented Nov 5, 2023

added a reduced test for the drupal case. looks like a certainty issue of regular variables (which was already contained in 1.10.40)

@ondrejmirtes
Copy link
Member

Hi, my plan for today is to make 1.10.x releasable again by reverting all related changes and picking those commits into this PR. Right now it’s pretty broken and we need to do this properly in order not to endanger the stability. So we’re gonna focus the changes here and make sure the CI is green.

I also need to work on other stuff like PHP 8.3 and PHPStan 1.11.

@staabm
Copy link
Contributor Author

staabm commented Nov 5, 2023

I see thanks. If there is anything I can do to support you in other areas, feel free to note.

@staabm
Copy link
Contributor Author

staabm commented Nov 5, 2023

Results look better but there's something wrong with !empty in Drupal: phpstan/phpstan-src/actions/runs/6756144616/job/18365350671

Source: drupal/drupal@2700c5a/core/modules/book/book.module#L123

I also found the cause for this problem:

function book_node_links_alter(array &$links, NodeInterface $node) {
  \PHPStan\dumpType(isset($node->book['depth'])); // Dumped type: false 
    if (isset($node->book['depth'])) {
          $book_links['book_printer'] = [
          ];
    }
    \PHPStan\Testing\assertVariableCertainty(\PHPStan\TrinaryLogic::createMaybe(), $book_links); // Expected variable certainty Maybe, actual: No

    if (!empty($book_links)) {
      $links['book'] = [
        '#links' => $book_links,
      ];
    }
}

I think everything is working as expected - as far as this PR is a concern. we conclude the isset() expression is always FALSE, therefore we don't take the IF-truethy-scope into the IF-final-scope which means the variable has a certainty of NO after the if (isset($node->book['depth'])) { block

@staabm
Copy link
Contributor Author

staabm commented Nov 5, 2023

Alright, one more problem this PR should try to fix is:

Expected No, actual certainty of variable $shortcut_id is Maybe in /Users/ondrej/Development/phpstan/tests/PHPStan/Analyser/data/bug-10088.php on line 22.

Not sure why it does not work now.

I think the cause for this problem is, that the scope before entering the if ($link_mode === "add") { statement is

$scope->debug();

array (
  '$shortcut_id (Maybe)' => '1',
  '$link_mode (Yes)' => '\'add\'|\'remove\'',
  'native $shortcut_id' => '1',
  'native $link_mode' => '\'add\'|\'remove\'',
  'condition about $shortcut_id #1' => 'if $link_mode=\'remove\' then $shortcut_id is 1 (Yes)',
)

which means we don't know about a conditional type and certainty for the ELSE case of the ternary $link_mode = isset($shortcut_id) ? "remove" : "add";

in the end it feels like a possible future improvement..?

@ondrejmirtes
Copy link
Member

I hope you don't mind the forcepushes. If you have some commits locally and I forcepush the base under your feet, can solve it easily with git rebase --onto.

@staabm
Copy link
Contributor Author

staabm commented Nov 28, 2023

really nice, thank you. I was looking at it for hours and coulnd't make sense of it.

@clxmstaab
Copy link
Contributor

I have a test-case locally for the conditon bug. let me push it into the PR

@ondrejmirtes
Copy link
Member

One part didn't make much sense to me so I changed it:

The change about PHPDoc certainty is probably a bug in merging scopes, I don't care much about that right now.

I will be merging this soon. Thank you!

@staabm
Copy link
Contributor Author

staabm commented Nov 28, 2023

thanks for your support!

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