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

Improve isset specification in falsy scope #1781

Conversation

herndlm
Copy link
Contributor

@herndlm herndlm commented Oct 1, 2022

Continues specifying the type for a variable in a falsy isset if it exists in the scope.

This affectively leads to a more refined variable type and "fixes" type narrowing with empty() in a truthy scope too (see TypeSpecifierTest changes). The reason for that is that e.g. empty($foo) translates to !isset($foo) || !$foo and without types for $foo on the left side of the BooleanOr SpecifiedTypes::intersectWith() would simply drop the type info for the right side.

@herndlm herndlm changed the base branch from 1.9.x to 1.8.x October 1, 2022 22:30
@herndlm herndlm force-pushed the improve-isset-specification-in-falsy-scope branch from 0d1bc48 to 743db92 Compare October 1, 2022 22:30
@herndlm
Copy link
Contributor Author

herndlm commented Oct 1, 2022

Both the composer and doctrine failure seem legit.

Composer deals with arrays in negated contexts where only booleans are allowed, see https://github.com/composer/composer/blob/9a69c12a0799d5a974f5ec5d2e78be3495b0b06c/src/Composer/Command/DiagnoseCommand.php#L725

Doctrine checks the $className with empty which is unsafe since it can still be an empty string, see https://github.com/doctrine/orm/blob/2.13.1/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php#L3763

@herndlm herndlm marked this pull request as ready for review October 1, 2022 22:49
@herndlm herndlm marked this pull request as draft October 2, 2022 06:04
@herndlm herndlm marked this pull request as ready for review October 2, 2022 07:52
@herndlm herndlm marked this pull request as draft October 2, 2022 09:21
@herndlm herndlm force-pushed the improve-isset-specification-in-falsy-scope branch from 743db92 to 129868e Compare October 3, 2022 22:48
@herndlm
Copy link
Contributor Author

herndlm commented Oct 3, 2022

I figured out how to do it more generically with the help of MutatingScope::issetCheck() for now.

Unfortunately I had to revert a tiny change from my previous #1784. It was not there until a couple hours ago still anyways :P The reason is that it will mess with the falsy scope by not unsetting $moreSpecificTypes for the ArrayDimFetch (which is set to make the expression inside isset() nullable I believe) and then MutatingScope::isSpecified() wrongfully returns true in the wrong context. Related to https://github.com/phpstan/phpstan-src/pull/1307/files#diff-90699c8d7b353d8df3308429a98104b255870f8411c204811b169f528535b022R91 where @rajyan introduced this and hinted it in the description too I think. I think it's fine to keep it like that for now, but this should be checked and cleaned-up
That was also one of the reasons why I did not succeed in MutatingScope / IssetCheck duplication-removal yet :/

But anyways, I think this PR improves falsy isset() and truthy empty() massively :)

@herndlm herndlm marked this pull request as ready for review October 3, 2022 23:00
// the problem is that `empty($config)` translates to `!isset($config) || !$config`
// and before specified types of the left and right side are intersected they are "normalized"
// by removing the sureNotType from $scope->getType() which is `stdClass|array|null` here
assertNativeType('array{}|null', $config);
Copy link
Contributor Author

@herndlm herndlm Oct 3, 2022

Choose a reason for hiding this comment

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

regarding this - here SpecifiedTypes::normalize() wouldn't even be needed and then it would work.. I think I can inline the normalization into SpecifiedTypes::intersectWith() and make it a bit smarter. I'd look at that in a dedicated PR.
ah I remember what the ugly parts here were - the Scope that needs to be passed to intersect then or the other type that needs to be passed to normalize. ugh

then the simplest working thing would be to use the correct type, e.g. the following on MutatingScope to use it inside SpecifiedTypes:

        public function getTypeOrNativeType(Expr $node): Type
	{
		return $this->treatPhpDocTypesAsCertain
			? $this->getNativeType($node)
			: $this->getType($node);
	}

or also pass around $treatPhpDocTypesAsCertain 🤔
but IIIRC there were/are other PRs or issues that touch the same subject

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is right. Native type has to be evaluated alongside the full type. We're always interested in both at the same time. That means that TypeSpecifier and SpecifiedTypes should carry both at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm gonna try further looking into this since it bothers me quite a bit

Copy link
Member

Choose a reason for hiding this comment

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

I think it's a stepping stone to implement my suggestions here: #1627 (comment)

@ondrejmirtes ondrejmirtes merged commit 7cc83da into phpstan:1.8.x Oct 5, 2022
@ondrejmirtes
Copy link
Member

Thank you!

@herndlm herndlm deleted the improve-isset-specification-in-falsy-scope branch October 5, 2022 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants