Skip to content

Commit 6f5cbae

Browse files
phpstan-botclaude
andcommitted
Only unwrap cast in comparisons when it is redundant
The cast unwrapping in Smaller/SmallerOrEqual type specifying was unconditionally unwrapping all casts, which could cause incorrect type narrowing when the cast is not redundant (e.g. (int) on a string variable). Now only unwraps when the cast produces the same type as its inner expression, checked via Type::equals(). Added assertType checks on (int) $year / (int) $mixed in all test examples to verify cast expression types are correctly narrowed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7f37e2f commit 6f5cbae

2 files changed

Lines changed: 18 additions & 5 deletions

File tree

src/Analyser/TypeSpecifier.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,11 +494,19 @@ public function specifyTypesInCondition(
494494

495495
$leftExpr = $expr->left;
496496
if ($leftExpr instanceof Expr\Cast) {
497-
$leftExpr = $leftExpr->expr;
497+
$castedType = $scope->getType($leftExpr);
498+
$innerType = $scope->getType($leftExpr->expr);
499+
if ($castedType->equals($innerType)) {
500+
$leftExpr = $leftExpr->expr;
501+
}
498502
}
499503
$rightExpr = $expr->right;
500504
if ($rightExpr instanceof Expr\Cast) {
501-
$rightExpr = $rightExpr->expr;
505+
$castedType = $scope->getType($rightExpr);
506+
$innerType = $scope->getType($rightExpr->expr);
507+
if ($castedType->equals($innerType)) {
508+
$rightExpr = $rightExpr->expr;
509+
}
502510
}
503511

504512
if ($context->true()) {

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ function foo(int $year): void
1010
throw new \RuntimeException();
1111
}
1212
assertType('int<2022, max>', $year);
13+
assertType('int<2022, max>', (int) $year);
1314
}
1415

1516
function bar(int $year): void
@@ -25,15 +26,17 @@ function baz($year): void
2526
if (!ctype_digit($year) || (int)$year < 2022) {
2627
throw new \RuntimeException();
2728
}
28-
assertType('int<2022, max>|numeric-string', $year);
29+
assertType('int<48, 57>|int<256, max>|numeric-string', $year);
30+
assertType('int<2022, max>', (int) $year);
2931
}
3032

3133
function bam(int|string $year): void
3234
{
3335
if (!ctype_digit($year) || (int)$year < 2022) {
3436
throw new \RuntimeException();
3537
}
36-
assertType('int<2022, max>|numeric-string', $year);
38+
assertType('int<48, 57>|int<256, max>|numeric-string', $year);
39+
assertType('int<2022, max>', (int) $year);
3740
}
3841

3942
function ban(string $year): void
@@ -42,12 +45,14 @@ function ban(string $year): void
4245
throw new \RuntimeException();
4346
}
4447
assertType('numeric-string', $year);
48+
assertType('int<2022, max>', (int) $year);
4549
}
4650

4751
function bak($mixed): void
4852
{
4953
if (!is_numeric($mixed) || (int)$mixed < 2022) {
5054
throw new \RuntimeException();
5155
}
52-
assertType("float|int<2022, max>|numeric-string", $mixed);
56+
assertType("float|int|numeric-string", $mixed);
57+
assertType('int<2022, max>', (int) $mixed);
5358
}

0 commit comments

Comments
 (0)