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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<Node\Expr\ClassConstFetch>
Expand Down Expand Up @@ -71,7 +72,7 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$referencedClasses = $classTypeResult->getReferencedClasses();
$referencedClasses = TypeCombinator::removeNull($classTypeResult->getType())->getObjectTypeOrClassStringObjectType()->getObjectClassNames();
}

$errors = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<MethodCallableNode>
Expand Down Expand Up @@ -52,7 +54,7 @@ public function processNode(Node $node, Scope $scope): array

$methodName = $node->getName()->name;
$methodCalledOnType = $scope->getType($node->getVar());
$referencedClasses = $methodCalledOnType->getObjectClassNames();
$referencedClasses = TypeCombinator::intersect($methodCalledOnType, new ObjectWithoutClassType())->getObjectClassNames();

$errors = [];

Expand Down
4 changes: 3 additions & 1 deletion src/Rules/RestrictedUsage/RestrictedMethodUsageRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<MethodCall>
Expand Down Expand Up @@ -52,7 +54,7 @@ public function processNode(Node $node, Scope $scope): array

$methodName = $node->name->name;
$methodCalledOnType = $scope->getType($node->var);
$referencedClasses = $methodCalledOnType->getObjectClassNames();
$referencedClasses = TypeCombinator::intersect($methodCalledOnType, new ObjectWithoutClassType())->getObjectClassNames();

$errors = [];

Expand Down
4 changes: 3 additions & 1 deletion src/Rules/RestrictedUsage/RestrictedPropertyUsageRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<Node\Expr\PropertyFetch>
Expand Down Expand Up @@ -51,7 +53,7 @@ public function processNode(Node $node, Scope $scope): array

$propertyName = $node->name->name;
$propertyCalledOnType = $scope->getType($node->var);
$referencedClasses = $propertyCalledOnType->getObjectClassNames();
$referencedClasses = TypeCombinator::intersect($propertyCalledOnType, new ObjectWithoutClassType())->getObjectClassNames();

$errors = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<StaticMethodCallableNode>
Expand Down Expand Up @@ -72,7 +73,7 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$referencedClasses = $classTypeResult->getReferencedClasses();
$referencedClasses = TypeCombinator::removeNull($classTypeResult->getType())->getObjectTypeOrClassStringObjectType()->getObjectClassNames();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can this be simplified to $referencedClasses = TypeCombinator::removeNull($classTypeResult->getType())->getReferencedClasses();?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

no, I was wrong.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I didn't change any code, and I didn't make a new commit or rerun the tests for this round. In the comment on RestrictedStaticMethodCallableUsageRule.php, staabm took back the suggestion to use TypeCombinator::removeNull(...)->getReferencedClasses() ("no, I was wrong"), and has since approved the PR. The code is the same as at commit 2e262ac, which the last round's make tests, make phpstan and make cs passed on.

The suggestion wouldn't have worked anyway. getReferencedClasses() returns every class mentioned anywhere in a type, including generic type arguments, so it's not the same as the class you access a member on. It also doesn't resolve class-string<Foo> to Foo the way getObjectTypeOrClassStringObjectType() does, so $className::doFoo() with a class-string<StaticRequest> would stop being checked.

All four static-access rules still use:

$referencedClasses = TypeCombinator::removeNull($classTypeResult->getType())->getObjectTypeOrClassStringObjectType()->getObjectClassNames();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I kept getObjectTypeOrClassStringObjectType()->getObjectClassNames() and didn't switch to getReferencedClasses(). As you suggested earlier, I added a test case that shows why the current code needs to stay.

Why getReferencedClasses() doesn't work here: it doesn't only return the class whose member is being accessed. It returns every class mentioned anywhere in the type, including generic type arguments. GenericObjectType::getReferencedClasses() adds the referenced classes of each type argument, and GenericClassStringType passes the call through to its inner type. For GenericStaticRequest<StaticRequest>|null, it returns both GenericStaticRequest and StaticRequest, so the rules would also check StaticRequest::doFoo(), a class that isn't being called.

New test case: I added testGeneric() to tests/PHPStan/Rules/RestrictedUsage/data/bug-15681.php. It does static method calls, static callables, static property reads and class constant reads on:

  • GenericStaticRequest<StaticRequest>|null
  • class-string<GenericStaticRequest<StaticRequest>>

StaticRequest also has a doFoo() method, a $foo property and a FOO constant, which makes the difference visible. I added the expected errors to the four static-rule tests.

I ran it both ways:

  • With your suggested change: each new line is reported twice, once for GenericStaticRequest and once for StaticRequest. In RestrictedStaticMethodUsageRuleTest, lines 115 and 120 each showed two Cannot call doFoo errors.
  • With the current code: each line is reported once, which is correct.

make tests passes (23,546 tests, 108 skipped), and make phpstan and make cs are clean. The test is committed as 4914ca9 and not pushed.

}

$errors = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<Node\Expr\StaticCall>
Expand Down Expand Up @@ -71,7 +72,7 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$referencedClasses = $classTypeResult->getReferencedClasses();
$referencedClasses = TypeCombinator::removeNull($classTypeResult->getType())->getObjectTypeOrClassStringObjectType()->getObjectClassNames();
}

$errors = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<Node\Expr\StaticPropertyFetch>
Expand Down Expand Up @@ -71,7 +72,7 @@ public function processNode(Node $node, Scope $scope): array
return [];
}

$referencedClasses = $classTypeResult->getReferencedClasses();
$referencedClasses = TypeCombinator::removeNull($classTypeResult->getType())->getObjectTypeOrClassStringObjectType()->getObjectClassNames();
}

$errors = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\TypeCombinator;

/**
* @implements Rule<Cast\String_>
Expand Down Expand Up @@ -43,7 +45,7 @@ public function processNode(Node $node, Scope $scope): array
}

$exprType = $scope->getType($node->expr);
$referencedClasses = $exprType->getObjectClassNames();
$referencedClasses = TypeCombinator::intersect($exprType, new ObjectWithoutClassType())->getObjectClassNames();

$errors = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\RequiresPhp;

/**
* @extends RuleTestCase<RestrictedClassConstantUsageRule>
Expand Down Expand Up @@ -41,6 +42,33 @@ public function testRule(): void
]);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug15681(): void
{
$this->analyse([__DIR__ . '/data/bug-15681.php'], [
[
'Cannot access FOO',
73,
],
[
'Cannot access FOO',
85,
],
[
'Cannot access FOO',
90,
],
[
'Cannot access FOO',
118,
],
[
'Cannot access FOO',
123,
],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ public function testRule(): void
]);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug15681(): void
{
$this->analyse([__DIR__ . '/data/bug-15681.php'], [
[
'Cannot call doFoo',
64,
],
[
'Cannot call doFoo',
65,
],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPStan\Rules\Rule as TRule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\RequiresPhp;

/**
* @extends RuleTestCase<RestrictedMethodUsageRule>
Expand All @@ -29,6 +30,41 @@ public function testRule(): void
]);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug15681(): void
{
$this->analyse([__DIR__ . '/data/bug-15681.php'], [
[
'Cannot call doFoo',
51,
],
[
'Cannot call doFoo',
52,
],
[
'Cannot call doFoo',
53,
],
[
'Cannot call doFoo',
54,
],
[
'Cannot call doFoo',
55,
],
[
'Cannot call doFoo',
56,
],
[
'Cannot call doFoo',
57,
],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPStan\Rules\Rule as TRule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\RequiresPhp;

/**
* @extends RuleTestCase<RestrictedPropertyUsageRule>
Expand All @@ -29,6 +30,29 @@ public function testRule(): void
]);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug15681(): void
{
$this->analyse([__DIR__ . '/data/bug-15681.php'], [
[
'Cannot access $foo',
59,
],
[
'Cannot access $foo',
60,
],
[
'Cannot access $foo',
61,
],
[
'Cannot access $foo',
62,
],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ public function testBug12951(): void
]);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug15681(): void
{
$this->analyse([__DIR__ . '/data/bug-15681.php'], [
[
'Cannot call doFoo',
71,
],
[
'Cannot call doFoo',
83,
],
[
'Cannot call doFoo',
88,
],
[
'Cannot call doFoo',
116,
],
[
'Cannot call doFoo',
121,
],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,33 @@ public function testBug12951(): void
]);
}

#[RequiresPhp('>= 8.1.0')]
public function testBug15681(): void
{
$this->analyse([__DIR__ . '/data/bug-15681.php'], [
[
'Cannot call doFoo',
70,
],
[
'Cannot call doFoo',
82,
],
[
'Cannot call doFoo',
87,
],
[
'Cannot call doFoo',
115,
],
[
'Cannot call doFoo',
120,
],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
Loading
Loading