Skip to content

Commit 089e4f9

Browse files
committed
union types before calling into TypeSpecifier
1 parent a0c7d96 commit 089e4f9

File tree

2 files changed

+35
-33
lines changed

2 files changed

+35
-33
lines changed

src/Type/Php/MethodExistsTypeSpecifyingExtension.php

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
use PHPStan\Type\ClassStringType;
1616
use PHPStan\Type\Constant\ConstantBooleanType;
1717
use PHPStan\Type\FunctionTypeSpecifyingExtension;
18-
use PHPStan\Type\IntersectionType;
1918
use PHPStan\Type\ObjectWithoutClassType;
2019
use PHPStan\Type\TypeCombinator;
21-
use PHPStan\Type\UnionType;
2220
use function count;
2321

2422
#[AutowiredService]
@@ -50,53 +48,57 @@ public function specifyTypes(
5048
TypeSpecifierContext $context,
5149
): SpecifiedTypes
5250
{
51+
$specifiedTypes = $this->typeSpecifier->create(
52+
new FuncCall(new FullyQualified('method_exists'), $node->getRawArgs()),
53+
new ConstantBooleanType(true),
54+
$context,
55+
$scope,
56+
);
57+
5358
$methodNameTypes = $scope->getType($node->getArgs()[1]->value)->getConstantStrings();
5459
if ($methodNameTypes === []) {
55-
return $this->typeSpecifier->create(
56-
new FuncCall(new FullyQualified('method_exists'), $node->getRawArgs()),
57-
new ConstantBooleanType(true),
58-
$context,
59-
$scope,
60-
);
60+
return $specifiedTypes;
6161
}
6262

63-
$specifiedTypes = new SpecifiedTypes([], []);
64-
6563
$objectType = $scope->getType($node->getArgs()[0]->value);
6664
if ($objectType->isString()->yes()) {
6765
if ($objectType->isClassString()->yes()) {
66+
$types = [];
6867
foreach ($methodNameTypes as $methodNameType) {
69-
$specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create(
70-
$node->getArgs()[0]->value,
71-
TypeCombinator::intersect(
72-
$objectType,
73-
new HasMethodType($methodNameType->getValue()),
74-
),
75-
$context,
76-
$scope,
77-
));
68+
$types[] = TypeCombinator::intersect(
69+
$objectType,
70+
new HasMethodType($methodNameType->getValue()),
71+
);
7872
}
73+
74+
return $specifiedTypes->unionWith($this->typeSpecifier->create(
75+
$node->getArgs()[0]->value,
76+
TypeCombinator::union(...$types),
77+
$context,
78+
$scope,
79+
));
7980
}
8081

8182
return $specifiedTypes;
8283
}
8384

85+
$types = [];
8486
foreach ($methodNameTypes as $methodNameType) {
85-
$specifiedTypes = $specifiedTypes->unionWith($this->typeSpecifier->create(
86-
$node->getArgs()[0]->value,
87-
TypeCombinator::union(
88-
TypeCombinator::intersect(
89-
new ObjectWithoutClassType(),
90-
new HasMethodType($methodNameType->getValue()),
91-
),
92-
new ClassStringType(),
87+
$types[] = TypeCombinator::union(
88+
TypeCombinator::intersect(
89+
new ObjectWithoutClassType(),
90+
new HasMethodType($methodNameType->getValue()),
9391
),
94-
$context,
95-
$scope,
96-
));
92+
new ClassStringType(),
93+
);
9794
}
9895

99-
return $specifiedTypes;
96+
return $specifiedTypes->unionWith($this->typeSpecifier->create(
97+
$node->getArgs()[0]->value,
98+
TypeCombinator::union(...$types),
99+
$context,
100+
$scope,
101+
));
100102
}
101103

102104
}

tests/PHPStan/Analyser/nsrt/bug-13272.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function foo(object $bar): void
1414
}
1515

1616
assertType("'quux'|'qux'", $method);
17-
assertType("object&hasMethod(quux)&hasMethod(qux)", $bar);
17+
assertType("(object&hasMethod(quux))|(object&hasMethod(qux))", $bar); // could be object&hasMethod(quux)&hasMethod(qux)
1818
}
1919
}
2020

@@ -28,5 +28,5 @@ function fooBar(object $bar, string $constUnion): void
2828
}
2929

3030
// at this point we don't know whether $constUnion was 'quux' or 'qux'
31-
assertType("object&hasMethod(quux)|object&hasMethod(qux)", $bar);
31+
assertType("(object&hasMethod(quux))|(object&hasMethod(qux))", $bar);
3232
}

0 commit comments

Comments
 (0)