Skip to content

Commit 7e4d49c

Browse files
phpstan-botclaude
andauthored
Compare constant scalars refined by accessory types against other union members in TypeCombinator::union() (#6863)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent c475e39 commit 7e4d49c

5 files changed

Lines changed: 68 additions & 4 deletions

File tree

‎phpstan-baseline.neon‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1678,7 +1678,7 @@ parameters:
16781678
-
16791679
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
16801680
identifier: phpstanApi.instanceofType
1681-
count: 7
1681+
count: 8
16821682
path: src/Type/TypeCombinator.php
16831683

16841684
-

‎src/Type/TypeCombinator.php‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,10 @@ public static function doUnion(Type ...$types): Type
420420
$enumCaseTypes = [];
421421
$integerRangeTypes = [];
422422
for ($i = 0; $i < $typesCount; $i++) {
423-
if ($types[$i]->isConstantScalarValue()->yes()) {
423+
// a constant scalar refined by an accessory type ('Foo'&hasMethod(bar))
424+
// stays with the other types so it is compared against the plain
425+
// constant scalars and other refinements of the same value
426+
if (!$types[$i] instanceof IntersectionType && $types[$i]->isConstantScalarValue()->yes()) {
424427
$type = $types[$i];
425428
$scalarTypes[get_class($type)][$type->describe(VerbosityLevel::cache())] = $type;
426429
unset($types[$i]);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug15591;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
}
10+
11+
function test(): void
12+
{
13+
$class = Foo::class;
14+
foreach (['m1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm10', 'm11', 'm12'] as $method) {
15+
if (method_exists($class, $method)) {
16+
}
17+
}
18+
assertType("'Bug15591\\\\Foo'", $class);
19+
}
20+
21+
function testStraightLine(): void
22+
{
23+
$class = Foo::class;
24+
if (method_exists($class, 'm1')) {
25+
assertType("'Bug15591\\\\Foo'&hasMethod(m1)", $class);
26+
}
27+
assertType("'Bug15591\\\\Foo'", $class);
28+
if (method_exists($class, 'm2')) {
29+
assertType("'Bug15591\\\\Foo'&hasMethod(m2)", $class);
30+
}
31+
assertType("'Bug15591\\\\Foo'", $class);
32+
}
33+
34+
function testProperty(): void
35+
{
36+
$class = Foo::class;
37+
if (property_exists($class, 'p1')) {
38+
}
39+
if (property_exists($class, 'p2')) {
40+
}
41+
assertType("'Bug15591\\\\Foo'", $class);
42+
}

‎tests/bench/data/bug-15591.php‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace MethodExistsLoopRepro;
4+
5+
class Foo
6+
{
7+
}
8+
9+
function test(): void
10+
{
11+
$class = Foo::class;
12+
foreach (['m1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm10', 'm11', 'm12'] as $method) {
13+
if (method_exists($class, $method)) {
14+
}
15+
}
16+
}

‎turbo-ext/src/TypeCombinator.cpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,8 +1038,11 @@ class TypeCombinator
10381038
Level cacheLevel(PT_LC("cache"));
10391039
for (size_t i = 0; i < typesCount; i++) {
10401040
zval *type = types[i].raw();
1041-
zend_long isConstantScalarValue = callTrinary(type, PT_LC("isconstantscalarvalue"));
1042-
PT_FAIL_IF_NEG(isConstantScalarValue);
1041+
zend_long isConstantScalarValue = PT_TRI_NO;
1042+
if (!isInstance(type, pt_ce_intersection_type)) {
1043+
isConstantScalarValue = callTrinary(type, PT_LC("isconstantscalarvalue"));
1044+
PT_FAIL_IF_NEG(isConstantScalarValue);
1045+
}
10431046
if (isConstantScalarValue == PT_TRI_YES) {
10441047
zv::Val description = describe(type, cacheLevel);
10451048
zend_string *key = describedKey(description);

0 commit comments

Comments
 (0)