Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/DependencyInjection/LazyContainerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ContextNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\GlobalVariableNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\NameNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\PhpVersionConditionNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\PropertyOrClassConstDefaultNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\StaticVariableNodeVisitor;
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\SymfonyClosureNodeVisitor;
Expand Down Expand Up @@ -239,6 +240,7 @@ final class LazyContainerFactory
*/
private const DECORATING_NODE_VISITOR_CLASSES = [
ArgNodeVisitor::class,
PhpVersionConditionNodeVisitor::class,
AssignedToNodeVisitor::class,
SymfonyClosureNodeVisitor::class,
ByRefReturnNodeVisitor::class,
Expand Down
2 changes: 2 additions & 0 deletions src/NodeTypeResolver/Node/AttributeKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,6 @@ final class AttributeKey
public const IS_INSIDE_BYREF_FUNCTION_LIKE = 'is_inside_byref_function_like';

public const CLASS_CONST_FETCH_NAME = 'class_const_fetch_name';

public const PHP_VERSION_CONDITIONED = 'php_version_conditioned';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Ternary;
use PhpParser\Node\Stmt\If_;
use PhpParser\NodeVisitorAbstract;
use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface;
use Rector\DeadCode\ConditionResolver;
use Rector\DeadCode\ValueObject\VersionCompareCondition;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PhpParser\NodeTraverser\SimpleNodeTraverser;

final class PhpVersionConditionNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface
Copy link
Contributor

@staabm staabm Dec 3, 2025

Choose a reason for hiding this comment

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

instead of this, you might be interessted in $scope->getPhpVersion() which returns a version which can be narrowed by composer.json constraints and/or if(PHP_VERSION_ID > xy) check etc.

see also https://staabm.github.io/2024/11/14/phpstan-php-version-narrowing.html

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll give it a go today, thanks for heads-up 👌

Copy link
Member Author

@TomasVotruba TomasVotruba Dec 3, 2025

Choose a reason for hiding this comment

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

Just tested on the downgrade rule I've introduced this for: https://github.com/rectorphp/rector-downgrade-php/pull/349/files#diff-5f67d22ffde95a027219085ed3266b9ae5277dd85eecf7771c41881372cba9af

And it picks up local version (PHP 8.2), so it never downgrades to PHP 8.1. Not suitable for this kind of operation.

{
public function __construct(
private readonly ConditionResolver $conditionResolver
) {
}

public function enterNode(Node $node): ?Node
{
if (($node instanceof Ternary || $node instanceof If_) && $this->hasVersionCompareCond($node)) {
if ($node instanceof Ternary) {
$nodes = [$node->else];
if ($node->if instanceof \PhpParser\Node) {
$nodes[] = $node->if;
}
} else {
$nodes = $node->stmts;
}

SimpleNodeTraverser::decorateWithAttributeValue($nodes, AttributeKey::PHP_VERSION_CONDITIONED, true);
}

return null;
}

private function hasVersionCompareCond(If_|Ternary $ifOrTernary): bool
{
if (! $ifOrTernary->cond instanceof FuncCall) {
return false;
}

$versionCompare = $this->conditionResolver->resolveFromExpr($ifOrTernary->cond);
return $versionCompare instanceof VersionCompareCondition;
}
}