Skip to content

Commit 2ae4e34

Browse files
committed
PropertyExistsTypeSpecifyingExtension: Cleanup instanceof ConstantStringType
1 parent 2132cc0 commit 2ae4e34

File tree

4 files changed

+59
-23
lines changed

4 files changed

+59
-23
lines changed

phpstan-baseline.neon

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,11 +1448,6 @@ parameters:
14481448
count: 1
14491449
path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php
14501450

1451-
-
1452-
message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#"
1453-
count: 2
1454-
path: src/Type/Php/PropertyExistsTypeSpecifyingExtension.php
1455-
14561451
-
14571452
message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#"
14581453
count: 2

src/Type/Php/PropertyExistsTypeSpecifyingExtension.php

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use PHPStan\Reflection\FunctionReflection;
1414
use PHPStan\Rules\Properties\PropertyReflectionFinder;
1515
use PHPStan\Type\Accessory\HasPropertyType;
16-
use PHPStan\Type\Constant\ConstantStringType;
1716
use PHPStan\Type\FunctionTypeSpecifyingExtension;
1817
use PHPStan\Type\IntersectionType;
1918
use PHPStan\Type\ObjectWithoutClassType;
@@ -51,36 +50,36 @@ public function specifyTypes(
5150
TypeSpecifierContext $context,
5251
): SpecifiedTypes
5352
{
54-
$propertyNameType = $scope->getType($node->getArgs()[1]->value);
55-
if (!$propertyNameType instanceof ConstantStringType) {
53+
$propertyNames = $scope->getType($node->getArgs()[1]->value)->getConstantStrings();
54+
if ($propertyNames === []) {
5655
return new SpecifiedTypes([], []);
5756
}
5857

59-
$objectType = $scope->getType($node->getArgs()[0]->value);
60-
if ($objectType instanceof ConstantStringType) {
61-
return new SpecifiedTypes([], []);
62-
} elseif ($objectType->isObject()->yes()) {
58+
$types = [new ObjectWithoutClassType()];
59+
foreach ($propertyNames as $propertyNameType) {
60+
$objectType = $scope->getType($node->getArgs()[0]->value);
61+
if (!$objectType->isObject()->yes()) {
62+
return new SpecifiedTypes([], []);
63+
}
64+
6365
$propertyNode = new PropertyFetch(
6466
$node->getArgs()[0]->value,
6567
new Identifier($propertyNameType->getValue()),
6668
);
67-
} else {
68-
return new SpecifiedTypes([], []);
69-
}
7069

71-
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyNode, $scope);
72-
if ($propertyReflection !== null) {
73-
if (!$propertyReflection->isNative()) {
74-
return new SpecifiedTypes([], []);
70+
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyNode, $scope);
71+
if ($propertyReflection !== null) {
72+
if (!$propertyReflection->isNative()) {
73+
return new SpecifiedTypes([], []);
74+
}
7575
}
76+
77+
$types[] = new HasPropertyType($propertyNameType->getValue());
7678
}
7779

7880
return $this->typeSpecifier->create(
7981
$node->getArgs()[0]->value,
80-
new IntersectionType([
81-
new ObjectWithoutClassType(),
82-
new HasPropertyType($propertyNameType->getValue()),
83-
]),
82+
new IntersectionType($types),
8483
$context,
8584
false,
8685
$scope,

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,14 @@ public function testImpossibleCheckTypeFunctionCall(): void
262262
927,
263263
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
264264
],
265+
[
266+
"Call to function property_exists() with \$this(CheckTypeFunctionCall\\FinalClassWithPropertyExistsOfUnionStrings) and 'foo2Property'|'fooProperty' will always evaluate to true.",
267+
994,
268+
],
269+
[
270+
"Call to function property_exists() with \$this(CheckTypeFunctionCall\\FinalClassWithPropertyExistsOfUnionStrings) and 'barProperty'|'fooProperty' will always evaluate to false.",
271+
1005,
272+
],
265273
],
266274
);
267275
}
@@ -372,6 +380,10 @@ public function testImpossibleCheckTypeFunctionCallWithoutAlwaysTrue(): void
372380
927,
373381
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
374382
],
383+
[
384+
"Call to function property_exists() with \$this(CheckTypeFunctionCall\\FinalClassWithPropertyExistsOfUnionStrings) and 'barProperty'|'fooProperty' will always evaluate to false.",
385+
1005,
386+
],
375387
],
376388
);
377389
}

tests/PHPStan/Rules/Comparison/data/check-type-function-call.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,3 +976,33 @@ function checkClosedResource($resource): void {
976976

977977
}
978978
}
979+
980+
final class FinalClassWithPropertyExistsOfUnionStrings
981+
{
982+
/** @var int */
983+
private $fooProperty;
984+
/** @var int */
985+
private $foo2Property;
986+
987+
public function doFoo()
988+
{
989+
$prop = 'fooProperty';
990+
if (rand(0, 1) === 0) {
991+
$prop = 'foo2Property';
992+
}
993+
994+
if (property_exists($this, $prop)) {
995+
}
996+
}
997+
998+
public function doFooBar()
999+
{
1000+
$prop = 'fooProperty';
1001+
if (rand(0, 1) === 0) {
1002+
$prop = 'barProperty';
1003+
}
1004+
1005+
if (property_exists($this, $prop)) {
1006+
}
1007+
}
1008+
}

0 commit comments

Comments
 (0)