Skip to content

Fix PropertyExistsTypeSpecifyingExtension for union types #4153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: 2.1.x
Choose a base branch
from
6 changes: 0 additions & 6 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1647,12 +1647,6 @@ parameters:
count: 1
path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php

-
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#'
identifier: phpstanApi.instanceofType
count: 2
path: src/Type/Php/PropertyExistsTypeSpecifyingExtension.php

-
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#'
identifier: phpstanApi.instanceofType
Expand Down
38 changes: 25 additions & 13 deletions src/Type/Php/PropertyExistsTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use PHPStan\Rules\Properties\PropertyReflectionFinder;
use PHPStan\Type\Accessory\HasPropertyType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\ObjectWithoutClassType;
Expand Down Expand Up @@ -55,8 +54,9 @@ public function specifyTypes(
TypeSpecifierContext $context,
): SpecifiedTypes
{
$propertyNameType = $scope->getType($node->getArgs()[1]->value);
if (!$propertyNameType instanceof ConstantStringType) {
$propertyNameTypes = $scope->getType($node->getArgs()[1]->value)->getConstantStrings();

if ($propertyNameTypes === []) {
return $this->typeSpecifier->create(
new FuncCall(new FullyQualified('property_exists'), $node->getRawArgs()),
new ConstantBooleanType(true),
Expand All @@ -65,24 +65,36 @@ public function specifyTypes(
);
}

if ($propertyNameType->getValue() === '') {
return new SpecifiedTypes([], []);
$hasPropertyTypes = [];
foreach ($propertyNameTypes as $propertyNameType) {
$hasPropertyTypes[] = new HasPropertyType($propertyNameType->getValue());
}

$objectType = $scope->getType($node->getArgs()[0]->value);
if ($objectType instanceof ConstantStringType) {

if (!$objectType->isObject()->yes()) {
return new SpecifiedTypes([], []);
} elseif ($objectType->isObject()->yes()) {
$propertyNode = new PropertyFetch(
}

$propertyNodes = [];

foreach ($propertyNameTypes as $propertyNameType) {
if ($propertyNameType->getValue() === '') {
return new SpecifiedTypes([], []);
}

$propertyNodes[] = new PropertyFetch(
$node->getArgs()[0]->value,
new Identifier($propertyNameType->getValue()),
);
} else {
return new SpecifiedTypes([], []);
}

$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyNode, $scope);
if ($propertyReflection !== null) {
foreach ($propertyNodes as $propertyNode) {
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($propertyNode, $scope);
if ($propertyReflection === null) {
continue;
}

if (!$propertyReflection->isNative()) {
return new SpecifiedTypes([], []);
}
Expand All @@ -92,7 +104,7 @@ public function specifyTypes(
$node->getArgs()[0]->value,
new IntersectionType([
new ObjectWithoutClassType(),
new HasPropertyType($propertyNameType->getValue()),
...$hasPropertyTypes,
]),
$context,
$scope,
Expand Down
16 changes: 16 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-13304.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Bug13304;

use function PHPStan\Testing\assertType;

function foo(object $foo): void
{
foreach (['qux', 'quux'] as $property) {
if (!property_exists($foo, $property)) {
throw new \Exception;
}

assertType("object&hasProperty(quux)&hasProperty(qux)", $foo);
}
}
Loading