Skip to content

Commit b88557b

Browse files
authored
ForbidFetchOnMixedRule: check even class const fetches (#150)
1 parent e2388a7 commit b88557b

File tree

3 files changed

+59
-9
lines changed

3 files changed

+59
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ implode('', [MyEnum::MyCase]); // denied, would fail on implicit toString conver
464464

465465

466466
### forbidFetchOnMixed
467-
- Denies property fetch on unknown type.
467+
- Denies constant/property fetch on unknown type.
468468
- Any property fetch assumes the caller is an object with such property and therefore, the typehint/phpdoc should be fixed.
469469
- Similar to `forbidMethodCallOnMixed`
470470
- Makes sense only on PHPStan level 8 or below, gets autodisabled on level 9

src/Rule/ForbidFetchOnMixedRule.php

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
use LogicException;
66
use PhpParser\Node;
77
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Expr\ClassConstFetch;
89
use PhpParser\Node\Expr\PropertyFetch;
910
use PhpParser\Node\Expr\StaticPropertyFetch;
1011
use PhpParser\Node\Identifier;
1112
use PhpParser\PrettyPrinter\Standard;
1213
use PHPStan\Analyser\Scope;
1314
use PHPStan\Rules\Rule;
15+
use PHPStan\Type\Type;
1416
use PHPStan\Type\TypeUtils;
1517
use function get_class;
1618
use function sprintf;
@@ -45,15 +47,15 @@ public function processNode(Node $node, Scope $scope): array
4547
return []; // already checked by native PHPStan
4648
}
4749

48-
if ($node instanceof PropertyFetch || $node instanceof StaticPropertyFetch) {
50+
if ($node instanceof PropertyFetch || $node instanceof StaticPropertyFetch || $node instanceof ClassConstFetch) {
4951
return $this->processFetch($node, $scope);
5052
}
5153

5254
return [];
5355
}
5456

5557
/**
56-
* @param PropertyFetch|StaticPropertyFetch $node
58+
* @param PropertyFetch|StaticPropertyFetch|ClassConstFetch $node
5759
* @return list<string>
5860
*/
5961
private function processFetch(Node $node, Scope $scope): array
@@ -68,17 +70,24 @@ private function processFetch(Node $node, Scope $scope): array
6870

6971
$callerType = TypeUtils::toBenevolentUnion($scope->getType($caller));
7072

71-
if ($callerType->getObjectTypeOrClassStringObjectType()->getObjectClassNames() === []) {
73+
if (
74+
$callerType->getObjectTypeOrClassStringObjectType()->getObjectClassNames() === []
75+
&& !$this->isObjectClassFetch($callerType, $node)
76+
) {
7277
$name = $node->name;
73-
$property = $name instanceof Identifier
78+
$propertyOrConstant = $name instanceof Identifier
7479
? $this->printer->prettyPrint([$name])
7580
: $this->printer->prettyPrintExpr($name);
81+
$element = $node instanceof ClassConstFetch
82+
? 'Constant'
83+
: 'Property';
7684

7785
return [
7886
sprintf(
79-
'Property fetch %s%s is prohibited on unknown type (%s)',
87+
'%s fetch %s%s is prohibited on unknown type (%s)',
88+
$element,
8089
$this->getFetchToken($node),
81-
$property,
90+
$propertyOrConstant,
8291
$this->printer->prettyPrintExpr($caller),
8392
),
8493
];
@@ -88,11 +97,12 @@ private function processFetch(Node $node, Scope $scope): array
8897
}
8998

9099
/**
91-
* @param PropertyFetch|StaticPropertyFetch $node
100+
* @param PropertyFetch|StaticPropertyFetch|ClassConstFetch $node
92101
*/
93102
private function getFetchToken(Node $node): string
94103
{
95104
switch (get_class($node)) {
105+
case ClassConstFetch::class:
96106
case StaticPropertyFetch::class:
97107
return '::';
98108

@@ -104,4 +114,28 @@ private function getFetchToken(Node $node): string
104114
}
105115
}
106116

117+
/**
118+
* Detect object::class
119+
*
120+
* @param PropertyFetch|StaticPropertyFetch|ClassConstFetch $node
121+
*/
122+
private function isObjectClassFetch(Type $callerType, Node $node): bool
123+
{
124+
$isObjectWithoutClassName = $callerType->isObject()->yes() && $callerType->getObjectClassNames() === [];
125+
126+
if (!$isObjectWithoutClassName) {
127+
return false;
128+
}
129+
130+
if (!$node instanceof ClassConstFetch) {
131+
return false;
132+
}
133+
134+
if (!$node->name instanceof Identifier) {
135+
return false;
136+
}
137+
138+
return $node->name->name === 'class';
139+
}
140+
107141
}

tests/Rule/data/ForbidFetchOnMixedRuleTest/code.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,50 @@
55
use ReflectionClass;
66

77
class Foo {
8+
public const CONST = 1;
9+
810
public ?int $property = null;
911
public static ?int $staticProperty = null;
1012
}
1113

12-
$fn = function (mixed $mixed, $unknown, string $string, array $array, ReflectionClass $reflection, ?Foo $fooOrNull) {
14+
$fn = function (mixed $mixed, $unknown, string $string, array $array, ReflectionClass $reflection, ?Foo $fooOrNull, object $object) {
1315
(new Foo)->property;
1416
Foo::$staticProperty;
17+
Foo::CONST;
18+
Foo::class;
1519

1620
/** @var class-string $classString */
1721
$classString = '';
1822
$classString::$staticProperty; // error: Property fetch ::$staticProperty is prohibited on unknown type ($classString)
23+
$classString::CONST; // error: Constant fetch ::CONST is prohibited on unknown type ($classString)
1924

2025
/** @var class-string<Foo> $classString2 */
2126
$classString2 = '';
2227
$classString2::$staticProperty;
28+
$classString2::CONST;
2329

2430
$string::$staticProperty; // error: Property fetch ::$staticProperty is prohibited on unknown type ($string)
2531

2632
$mixed->fetch1; // error: Property fetch ->fetch1 is prohibited on unknown type ($mixed)
2733
$mixed::$fetch1; // error: Property fetch ::$fetch1 is prohibited on unknown type ($mixed)
34+
$mixed::CONST; // error: Constant fetch ::CONST is prohibited on unknown type ($mixed)
35+
$mixed::class; // error: Constant fetch ::class is prohibited on unknown type ($mixed)
2836
$unknown->fetch2; // error: Property fetch ->fetch2 is prohibited on unknown type ($unknown)
2937
$unknown::$fetch2; // error: Property fetch ::$fetch2 is prohibited on unknown type ($unknown)
3038
$array[0]->fetch3; // error: Property fetch ->fetch3 is prohibited on unknown type ($array[0])
3139

3240
$fooOrNull->property;
3341
$fooOrNull?->property;
42+
$fooOrNull::$staticProperty;
43+
$fooOrNull::CONST;
44+
$fooOrNull::class;
45+
46+
$object::class;
47+
$object::CONST; // error: Constant fetch ::CONST is prohibited on unknown type ($object)
3448

3549
$reflection->newInstance()->property; // error: Property fetch ->property is prohibited on unknown type ($reflection->newInstance())
50+
$reflection->newInstance()::CONST; // error: Constant fetch ::CONST is prohibited on unknown type ($reflection->newInstance())
3651
/** @var ReflectionClass<Foo> $reflection */
3752
$reflection->newInstance()->property;
53+
$reflection->newInstance()::CONST;
3854
};

0 commit comments

Comments
 (0)