Skip to content

Commit d6e4e48

Browse files
phpstan-botclaude
authored andcommitted
Keep rootExpr for equality assertions, move specifyOnly after rootExpr check
- Revert equality assertions (`@phpstan-assert =`) back to using `rootExpr = $call` instead of `specifyOnly`. The rootExpr mechanism in ImpossibleCheckTypeHelper provides more nuanced detection (constant boolean evaluation via scope) and is the established path for these. `specifyOnly` is reserved for the FAUX replacement cases (str_contains, array_key_exists) where sureTypes are pure side effects. - Move the `specifyOnly` check after the `rootExpr` check in ImpossibleCheckTypeHelper so that rootExpr takes precedence when both flags are set (e.g. via unionWith/intersectWith propagation). - Add duplicate call test cases (str_ends_with, str_contains) to document that nested identical calls are not reported as always-true. This was never detected before — the old FAUX mechanism also returned null for these — and would require a separate mechanism (tracking function call results in scope). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bde2066 commit d6e4e48

3 files changed

Lines changed: 29 additions & 8 deletions

File tree

src/Analyser/TypeSpecifier.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1856,10 +1856,7 @@ static function (Type $type, callable $traverse) use ($templateTypeMap, &$contai
18561856
$assertedType,
18571857
$assert->isNegated() ? TypeSpecifierContext::createFalse() : TypeSpecifierContext::createTrue(),
18581858
$scope,
1859-
)->setRootExpr($containsUnresolvedTemplate ? $call : null);
1860-
if ($assert->isEquality()) {
1861-
$newTypes = $newTypes->setSpecifyOnly();
1862-
}
1859+
)->setRootExpr($containsUnresolvedTemplate || $assert->isEquality() ? $call : null);
18631860
$types = $types !== null ? $types->unionWith($newTypes) : $newTypes;
18641861

18651862
if (!$context->null() || !$assertedType instanceof ConstantBooleanType) {

src/Rules/Comparison/ImpossibleCheckTypeHelper.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,6 @@ public function findSpecifiedType(
273273
return null;
274274
}
275275

276-
if ($specifiedTypes->isSpecifyOnly()) {
277-
return null;
278-
}
279-
280276
$sureTypes = $specifiedTypes->getSureTypes();
281277
$sureNotTypes = $specifiedTypes->getSureNotTypes();
282278

@@ -294,6 +290,10 @@ public function findSpecifiedType(
294290
return null;
295291
}
296292

293+
if ($specifiedTypes->isSpecifyOnly()) {
294+
return null;
295+
}
296+
297297
$results = [];
298298

299299
$assignedInCallVars = [];

tests/PHPStan/Rules/Comparison/data/bug-14705.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,28 @@ public function arrayKeyExistsNonEmpty(array $array, string $key): void
6666
}
6767
}
6868

69+
/**
70+
* @param non-empty-string $needle
71+
*/
72+
public function strEndsWithDuplicate(string $haystack, string $needle): void
73+
{
74+
if (str_ends_with($haystack, $needle)) {
75+
if (str_ends_with($haystack, $needle)) {
76+
77+
}
78+
}
79+
}
80+
81+
/**
82+
* @param non-empty-string $needle
83+
*/
84+
public function strContainsDuplicate(string $haystack, string $needle): void
85+
{
86+
if (str_contains($haystack, $needle)) {
87+
if (str_contains($haystack, $needle)) {
88+
89+
}
90+
}
91+
}
92+
6993
}

0 commit comments

Comments
 (0)