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

Recognize constants as constant types #1163

Merged
merged 9 commits into from Apr 8, 2022

Conversation

rvanvelzen
Copy link
Contributor

Closes phpstan/phpstan#6160. This was just a wild idea. Probably not the best way forward, but at least it shows the potential

abstract class Test
{
/**
* @return (PHP_MAJOR_VERSION is 8 ? true : false)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Obviously this breaks on PHP < 8 💥

Copy link
Contributor

@staabm staabm Apr 1, 2022

Choose a reason for hiding this comment

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

you should run this test only on php8, or/and add an php7 alternative test-case with a different expectation.

return new ObjectType($stringName);
}

private function mightBeConstant(string $name): bool
{
return preg_match('(^[A-Z_][A-Z0-9_]*$)', $name) > 0;
Copy link
Contributor

@staabm staabm Apr 1, 2022

Choose a reason for hiding this comment

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

since A and B or similar short-names might be considered a constant in this case, I think tryResolveConstant should at first try whether there is a class defined with this name. Only if not, try whether its actually a constant.

what do you think?

Copy link
Member

Choose a reason for hiding this comment

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

Agreed, constant can be tried only when it's UPPER_CASE and class of that name does not exist.

@staabm
Copy link
Contributor

staabm commented Apr 1, 2022

I love it. this kills 2 birds with one stone.

it implements phpstan/phpstan#6160 and fixes one of the psalm-special-cases within the conditional-type language phpstan/phpstan#3853 (comment)

@rvanvelzen rvanvelzen marked this pull request as ready for review April 2, 2022 06:59
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.

I hate to bring the bad news but:

  1. I like to resolve constants here to solve Support global constants in union types phpstan#6160
  2. But I don't think it makes sense to support this in conditional types. The code here reports int<5, max>: https://phpstan.org/r/853028b5-3d52-4f51-ae64-1655458ef879 So typehinting PHP_MAJOR_VERSION in a PHPDoc also has to resolve to int<5, max> thanks to dynamicConstantNames (https://phpstan.org/config-reference#constants).

Which makes it useless for conditional types. But it's fine for me - having PHP_MAJOR_VERSION conditional in a stub is useful only for built-in PHP functions, and these can be solved by writing code in an extension. I don't want to have a small cosmetic advantage at a cost of doing some horrible hacks to make this work in stubs :)

@ondrejmirtes
Copy link
Member

Also wildcard support would be a great addition, some people expect that to work (phpstan/phpstan#6972). Needs modification in phpdoc-parser too.

@rvanvelzen rvanvelzen marked this pull request as draft April 4, 2022 08:13
rvanvelzen added a commit to rvanvelzen/phpstan-src that referenced this pull request Apr 7, 2022
See phpstan#1163 for discussion. `TypeNodeResolver` needs to be able to resolve constants the same way as `MutatingScope` does
ondrejmirtes pushed a commit that referenced this pull request Apr 7, 2022
See #1163 for discussion. `TypeNodeResolver` needs to be able to resolve constants the same way as `MutatingScope` does
@rvanvelzen
Copy link
Contributor Author

Also wildcard support would be a great addition, some people expect that to work (phpstan/phpstan#6972). Needs modification in phpdoc-parser too.

If you don't mind I'd like to tackle that separately. This PR has become quite large enough as-is 🥲

@ondrejmirtes
Copy link
Member

I agree, we can leave the wildcards for a later time :)

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.

Perfect! Can you please check that NameScope doesn't contain use function uses as they are useless for it?

@ondrejmirtes
Copy link
Member

Checking the code they probably aren't there thanks to TYPE_NORMAL.

@@ -361,9 +366,36 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
return new ErrorType();
}

if (!$this->mightBeConstant($typeNode->name) || $this->getReflectionProvider()->hasClass($stringName)) {
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 this should just be if ($this->getReflectionProvider()->hasClass($stringName)) {.

Because if something looks like a constant FOO_BAR but there's a class FOO_BAR, I want ObjectType of the class, not the constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should still always result in the class taking precedence, but does a slightly cheaper heuristic check.

It's covered in the test as well: https://github.com/phpstan/phpstan-src/pull/1163/files#diff-f60365c259ff216f4d228e0c46729817e679144be2a7bf2e7395ace7fd344284R47

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 it hits that condition:

	private function mightBeConstant(string $name): bool
	{
		return preg_match('((?:^|\\\\)[A-Z_][A-Z0-9_]*$)', $name) > 0;
	}

This just matches UPPER_CASE names, right? But the constant in question has CamelCaps namespace.

Because when I read that code in case of FOO_BAR the first return new ObjectType($stringName); is skipped because mightBeConstant() === true. And then tryResolveConstant is successful because the constant exists. So constant takes precedence over class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The namespace isn't considered, only the last part of $name (so BAR in that case). If the constant took precedence the type in the test would be 10, instead of the class name (ConstantPhpdocType\BAR).

Copy link
Member

Choose a reason for hiding this comment

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

Now it makes sense to me :) I'm ready to merge this, thanks.

@rvanvelzen
Copy link
Contributor Author

Can you please check that NameScope doesn't contain use function uses as they are useless for it?

It shouldn't because only TYPE_NORMAL and TYPE_CONSTANT are considered, and use function is TYPE_FUNCTION.

@rvanvelzen rvanvelzen marked this pull request as ready for review April 8, 2022 08:42
@ondrejmirtes ondrejmirtes merged commit f0bfd4d into phpstan:1.6.x Apr 8, 2022
@ondrejmirtes
Copy link
Member

Thank you! Please send an update to the documentation: https://phpstan.org/writing-php-code/phpdoc-types (add a new section "Global constants").

@rvanvelzen rvanvelzen deleted the constant-reference branch April 8, 2022 11:27
ondrejmirtes pushed a commit to phpstan/phpstan that referenced this pull request Apr 26, 2022
@vudaltsov
Copy link

Did you consider introducing a new constant<MY_CONSTANT> type constructor? That would simplify things a lot and allow to use not only global constants, but any other.

@ondrejmirtes
Copy link
Member

@vudaltsov You're commenting on a two year old PR. You should open a new feature request and describe your use case.

@phpstan phpstan locked and limited conversation to collaborators Apr 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
4 participants