Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
34 changes: 30 additions & 4 deletions src/Rules/IssetCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

use PhpParser\Node;
use PhpParser\Node\Expr;
use PHPStan\Analyser\CollectedDataEmitter;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredParameter;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\Expr\PropertyInitializationExpr;
use PHPStan\Rules\Comparison\ConstantConditionInTraitHelper;
use PHPStan\Rules\Properties\PropertyDescriptor;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
use PHPStan\Type\NeverType;
Expand All @@ -27,6 +30,7 @@ final class IssetCheck
public function __construct(
private PropertyDescriptor $propertyDescriptor,
private PropertyReflectionFinder $propertyReflectionFinder,
private ConstantConditionInTraitHelper $constantConditionInTraitHelper,
#[AutowiredParameter]
private bool $checkAdvancedIsset,
#[AutowiredParameter]
Expand All @@ -36,10 +40,11 @@ public function __construct(
}

/**
* @param class-string<Rule<covariant Node>> $ruleName
* @param ErrorIdentifier $identifier
* @param callable(Type): ?string $typeMessageCallback
*/
public function check(Expr $expr, Scope $scope, string $operatorDescription, string $identifier, callable $typeMessageCallback, ?IdentifierRuleError $error = null): ?IdentifierRuleError
public function check(Expr $expr, Scope&NodeCallbackInvoker&CollectedDataEmitter $scope, string $operatorDescription, string $identifier, callable $typeMessageCallback, string $ruleName, Expr $originalExpr, ?IdentifierRuleError $error = null): ?IdentifierRuleError
{
// mirrored in PHPStan\Analyser\MutatingScope::issetCheck()
if ($expr instanceof Node\Expr\Variable && is_string($expr->name)) {
Expand Down Expand Up @@ -114,7 +119,7 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str
), $typeMessageCallback, $identifier, 'offset');

if ($error !== null) {
return $this->check($expr->var, $scope, $operatorDescription, $identifier, $typeMessageCallback, $error);
return $this->check($expr->var, $scope, $operatorDescription, $identifier, $typeMessageCallback, $ruleName, $originalExpr, $error);
}
}

Expand Down Expand Up @@ -220,11 +225,11 @@ static function (Type $type) use ($typeMessageCallback): ?string {

if ($error !== null) {
if ($expr instanceof Node\Expr\PropertyFetch) {
return $this->check($expr->var, $scope, $operatorDescription, $identifier, $typeMessageCallback, $error);
return $this->check($expr->var, $scope, $operatorDescription, $identifier, $typeMessageCallback, $ruleName, $originalExpr, $error);
}

if ($expr->class instanceof Expr) {
return $this->check($expr->class, $scope, $operatorDescription, $identifier, $typeMessageCallback, $error);
return $this->check($expr->class, $scope, $operatorDescription, $identifier, $typeMessageCallback, $ruleName, $originalExpr, $error);
}
}

Expand Down Expand Up @@ -265,6 +270,27 @@ static function (Type $type) use ($typeMessageCallback): ?string {
return null;
}

/**
* @param class-string<Rule<covariant Node>> $ruleName
* @param ErrorIdentifier $identifier
* @param callable(Type): ?string $typeMessageCallback
*/
public function checkWithTraitHandling(Expr $expr, Scope&NodeCallbackInvoker&CollectedDataEmitter $scope, string $operatorDescription, string $identifier, callable $typeMessageCallback, string $ruleName): ?IdentifierRuleError
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are you adding a new method if you can just change the original one?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi @ondrejmirtes thanks for your review. The original check method is very complex, with several return cases and also calls itself. I'm not sure how to manage changing it without breaking it... I proposed a different approach in my new commit so the exposed name does not change and move the logic to a checkInternal, maybe it is more understandable like this...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi @ondrejmirtes any thoughts about this? Thanks!

{
$error = $this->check($expr, $scope, $operatorDescription, $identifier, $typeMessageCallback, $ruleName, $expr);

if ($scope->isInTrait()) {
if ($error !== null) {
$this->constantConditionInTraitHelper->emitError($ruleName, $scope, $expr, true, $error);
return null;
}
$this->constantConditionInTraitHelper->emitNoError($ruleName, $scope, $expr);
return null;
}

return $error;
}

/**
* @param ErrorIdentifier $identifier
*/
Expand Down
8 changes: 5 additions & 3 deletions src/Rules/Variables/EmptyRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace PHPStan\Rules\Variables;

use PhpParser\Node;
use PHPStan\Analyser\CollectedDataEmitter;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Rules\IssetCheck;
Expand All @@ -25,9 +27,9 @@ public function getNodeType(): string
return Node\Expr\Empty_::class;
}

public function processNode(Node $node, Scope $scope): array
public function processNode(Node $node, Scope&NodeCallbackInvoker&CollectedDataEmitter $scope): array
{
$error = $this->issetCheck->check($node->expr, $scope, 'in empty()', 'empty', static function (Type $type): ?string {
$error = $this->issetCheck->checkWithTraitHandling($node->expr, $scope, 'in empty()', 'empty', static function (Type $type): ?string {
$isNull = $type->isNull();
if ($isNull->maybe()) {
return null;
Expand Down Expand Up @@ -57,7 +59,7 @@ public function processNode(Node $node, Scope $scope): array
}

return 'is not nullable';
});
}, self::class);

if ($error === null) {
return [];
Expand Down
8 changes: 5 additions & 3 deletions src/Rules/Variables/IssetRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace PHPStan\Rules\Variables;

use PhpParser\Node;
use PHPStan\Analyser\CollectedDataEmitter;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Rules\IssetCheck;
Expand All @@ -25,11 +27,11 @@ public function getNodeType(): string
return Node\Expr\Isset_::class;
}

public function processNode(Node $node, Scope $scope): array
public function processNode(Node $node, Scope&NodeCallbackInvoker&CollectedDataEmitter $scope): array
{
$messages = [];
foreach ($node->vars as $var) {
$error = $this->issetCheck->check($var, $scope, 'in isset()', 'isset', static function (Type $type): ?string {
$error = $this->issetCheck->checkWithTraitHandling($var, $scope, 'in isset()', 'isset', static function (Type $type): ?string {
$isNull = $type->isNull();
if ($isNull->maybe()) {
return null;
Expand All @@ -40,7 +42,7 @@ public function processNode(Node $node, Scope $scope): array
}

return 'is not nullable';
});
}, self::class);
if ($error === null) {
continue;
}
Expand Down
8 changes: 5 additions & 3 deletions src/Rules/Variables/NullCoalesceRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace PHPStan\Rules\Variables;

use PhpParser\Node;
use PHPStan\Analyser\CollectedDataEmitter;
use PHPStan\Analyser\NodeCallbackInvoker;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\RegisteredRule;
use PHPStan\Rules\IssetCheck;
Expand All @@ -25,7 +27,7 @@ public function getNodeType(): string
return Node\Expr::class;
}

public function processNode(Node $node, Scope $scope): array
public function processNode(Node $node, Scope&NodeCallbackInvoker&CollectedDataEmitter $scope): array
{
$typeMessageCallback = static function (Type $type): ?string {
$isNull = $type->isNull();
Expand All @@ -41,9 +43,9 @@ public function processNode(Node $node, Scope $scope): array
};

if ($node instanceof Node\Expr\BinaryOp\Coalesce) {
$error = $this->issetCheck->check($node->left, $scope, 'on left side of ??', 'nullCoalesce', $typeMessageCallback);
$error = $this->issetCheck->checkWithTraitHandling($node->left, $scope, 'on left side of ??', 'nullCoalesce', $typeMessageCallback, self::class);
} elseif ($node instanceof Node\Expr\AssignOp\Coalesce) {
$error = $this->issetCheck->check($node->var, $scope, 'on left side of ??=', 'nullCoalesce', $typeMessageCallback);
$error = $this->issetCheck->checkWithTraitHandling($node->var, $scope, 'on left side of ??=', 'nullCoalesce', $typeMessageCallback, self::class);
} else {
return [];
}
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Variables/EmptyRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Variables;

use PHPStan\Rules\Comparison\ConstantConditionInTraitHelper;
use PHPStan\Rules\IssetCheck;
use PHPStan\Rules\Properties\PropertyDescriptor;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
Expand All @@ -23,6 +24,7 @@ protected function getRule(): Rule
return new EmptyRule(new IssetCheck(
new PropertyDescriptor(),
new PropertyReflectionFinder(),
self::getContainer()->getByType(ConstantConditionInTraitHelper::class),
true,
$this->treatPhpDocTypesAsCertain,
));
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Variables/IssetRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Variables;

use PHPStan\Rules\Comparison\ConstantConditionInTraitHelper;
use PHPStan\Rules\IssetCheck;
use PHPStan\Rules\Properties\PropertyDescriptor;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
Expand All @@ -22,6 +23,7 @@ protected function getRule(): Rule
return new IssetRule(new IssetCheck(
new PropertyDescriptor(),
new PropertyReflectionFinder(),
self::getContainer()->getByType(ConstantConditionInTraitHelper::class),
true,
$this->treatPhpDocTypesAsCertain,
));
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Rules\Variables;

use PHPStan\Rules\Comparison\ConstantConditionInTraitHelper;
use PHPStan\Rules\IssetCheck;
use PHPStan\Rules\Properties\PropertyDescriptor;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
Expand All @@ -21,6 +22,7 @@ protected function getRule(): Rule
return new NullCoalesceRule(new IssetCheck(
new PropertyDescriptor(),
new PropertyReflectionFinder(),
self::getContainer()->getByType(ConstantConditionInTraitHelper::class),
true,
$this->shouldTreatPhpDocTypesAsCertain(),
));
Expand Down
53 changes: 53 additions & 0 deletions tests/PHPStan/Rules/Variables/data/isset-in-trait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace IssetInTrait;

// Trait where property type differs across classes (should suppress errors)
trait IssetTrait
{
public function doFoo(): void
{
var_dump($this->i ?? -1);
var_dump(isset($this->i));
var_dump(empty($this->i));
}
}

class MyClass
{
use IssetTrait;

public int $i = 10;
}

class MyClassNullable
{
use IssetTrait;

public ?int $i = null;
}

// Trait where property type is the same in all classes (should still report errors)
trait AlwaysNonNullableTrait
{
public function doFoo(): void
{
var_dump($this->j ?? -1);
var_dump(isset($this->j));
var_dump(empty($this->j));
}
}

class ClassA
{
use AlwaysNonNullableTrait;

public int $j = 10;
}

class ClassB
{
use AlwaysNonNullableTrait;

public int $j = 20;
}
Loading