Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Indicates whether a variable is global #4245

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

Open
cedric-anne wants to merge 2 commits into phpstan:2.1.x
base: 2.1.x
Choose a base branch
Loading
from cedric-anne:feature/global-variables

Conversation

@cedric-anne
Copy link

@cedric-anne cedric-anne commented Aug 15, 2025
edited
Loading

This will permit to easilly assign types to global variables in an extension. Without this change, it is not possible to distinguish local variables from global variables.

Related to phpstan/phpstan#13243 and following a discussion in #4233.

Here is an exemple:

<?php
namespace MyPhpstanExtension;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Variable;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ObjectType;
use PHPStan\Type\ExpressionTypeResolverExtension;
use PHPStan\Type\Type;
class GlobalExpressionTypeResolverExtension implements ExpressionTypeResolverExtension
{
	public function getType(Expr $expr, Scope $scope): ?Type
	{
		if (!$expr instanceof Variable || !$scope->isGlobalVariable($expr->name)) {
			return null;
		}
		if ($expr->name === 'DB') {
			return new ObjectType(\App\Db::class);
		}
		return null;
	}
}

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.

  1. You're not testing what this should be allowing you. In the original issue you're saying you want to override variable types with ExpressionTypeResolverExtension but you're not testing that here.
  2. Instead of setting an attribute on Variable in assignVariable, I'm thinking we could call assignExpression here
    $scope = $scope->assignVariable($var->name, new MixedType(), new MixedType(), TrinaryLogic::createYes());
    with new virtual node GlobalVariableExpr. The Scope::isGlobalVariable could check the type of the expr node for that.

Existing virtual expr nodes are defined here: https://github.com/phpstan/phpstan-src/tree/2.1.x/src/Node/Expr

Additionally, they have to be handled here https://github.com/phpstan/phpstan-src/blob/2.1.x/src/Node/Printer/Printer.php and here

if ($node instanceof GetIterableKeyTypeExpr) {
return $this->getIterableKeyType($this->getType($node->getExpr()));
}
if ($node instanceof GetIterableValueTypeExpr) {
return $this->getIterableValueType($this->getType($node->getExpr()));
}
if ($node instanceof GetOffsetValueTypeExpr) {
return $this->getType($node->getVar())->getOffsetValueType($this->getType($node->getDim()));
}
if ($node instanceof ExistingArrayDimFetch) {
return $this->getType(new Expr\ArrayDimFetch($node->getVar(), $node->getDim()));
}
if ($node instanceof UnsetOffsetExpr) {
return $this->getType($node->getVar())->unsetOffset($this->getType($node->getDim()));
}
.

cedric-anne reacted with thumbs up emoji
Copy link
Author

  1. You're not testing what this should be allowing you. In the original issue you're saying you want to override variable types with ExpressionTypeResolverExtension but you're not testing that here.

I added a test for this use case.

  1. Instead of setting an attribute on Variable in assignVariable, I'm thinking we could call assignExpression [...] with new virtual node GlobalVariableExpr. The Scope::isGlobalVariable could check the type of the expr node for that.

I struggle to find how to make it work, probably because I am not familiar with the PHPStan internal logic, so maybe I missed something.

I finally found a solution with an early call to the ExpressionTypeResolverExtensionRegistry here:

if ($expr instanceof GlobalVariableExpr) {
foreach ($this->expressionTypeResolverExtensionRegistry->getExtensions() as $extension) {
$typeFromExtension = $extension->getType($expr, $this);
if ($typeFromExtension !== null) {
$type = $typeFromExtension;
break;
}
}
$scope = $scope->specifyExpressionType($expr->getVar(), $type, $nativeType, $certainty);
}

This is the only solution I found to be able to assign the type during the Global_ statement node parsing.

Copy link
Contributor

I think you need to rebase/fix some conflict @cedric-anne

Copy link
Author

cedric-anne commented Sep 14, 2025
edited
Loading

I think you need to rebase/fix some conflict @cedric-anne

Done. Conflicts were only related to use statements.

I am not entirely satisfied with what I have proposed, but I was waiting for a review from someone who knows more about the internal logic of PHPStan to find out if I had missed something obvious, or even if I was heading in the right direction.
Rather than using the ExpressionTypeResolverExtension extensions, I think it might be better to declare a new extension interface GlobalVariableTypeSpecifyingExtension with a public function getGlobalVariableType(GlobalVariableExpr $globalVariable, Scope $scope): ?Type method.

Also, maybe this should be splitted into two distinct PRs. Indeed, being able to know whether a variable is global and being able to define its type using an extension are two distinct features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@ondrejmirtes ondrejmirtes Awaiting requested review from ondrejmirtes

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /