Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 75 additions & 3 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,36 @@
)->setRootExpr($rootExpr));
}

if (
!$context->null()
&& ($exprNode instanceof Expr\Cast\Int_ || $exprNode instanceof Expr\Cast\Double)
) {
$innerExpr = $exprNode->expr;
$castTypes = $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($rootExpr);

$zeroType = $exprNode instanceof Expr\Cast\Int_
? new ConstantIntegerType(0)
: new ConstantFloatType(0.0);
$isZeroValue = $zeroType->isSuperTypeOf($constantType)->yes();

Check warning on line 1617 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $zeroType = $exprNode instanceof Expr\Cast\Int_ ? new ConstantIntegerType(0) : new ConstantFloatType(0.0); - $isZeroValue = $zeroType->isSuperTypeOf($constantType)->yes(); + $isZeroValue = !$zeroType->isSuperTypeOf($constantType)->no(); if ($isZeroValue && $context->false()) { $producesZero = TypeCombinator::union(new NullType(), new ConstantBooleanType(false));

Check warning on line 1617 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ $zeroType = $exprNode instanceof Expr\Cast\Int_ ? new ConstantIntegerType(0) : new ConstantFloatType(0.0); - $isZeroValue = $zeroType->isSuperTypeOf($constantType)->yes(); + $isZeroValue = $constantType->isSuperTypeOf($zeroType)->yes(); if ($isZeroValue && $context->false()) { $producesZero = TypeCombinator::union(new NullType(), new ConstantBooleanType(false));

Check warning on line 1617 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $zeroType = $exprNode instanceof Expr\Cast\Int_ ? new ConstantIntegerType(0) : new ConstantFloatType(0.0); - $isZeroValue = $zeroType->isSuperTypeOf($constantType)->yes(); + $isZeroValue = !$zeroType->isSuperTypeOf($constantType)->no(); if ($isZeroValue && $context->false()) { $producesZero = TypeCombinator::union(new NullType(), new ConstantBooleanType(false));

Check warning on line 1617 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ $zeroType = $exprNode instanceof Expr\Cast\Int_ ? new ConstantIntegerType(0) : new ConstantFloatType(0.0); - $isZeroValue = $zeroType->isSuperTypeOf($constantType)->yes(); + $isZeroValue = $constantType->isSuperTypeOf($zeroType)->yes(); if ($isZeroValue && $context->false()) { $producesZero = TypeCombinator::union(new NullType(), new ConstantBooleanType(false));

if ($isZeroValue && $context->false()) {

Check warning on line 1619 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrueTruthyFalseFalseyTypeSpecifierContextMutator": @@ @@ : new ConstantFloatType(0.0); $isZeroValue = $zeroType->isSuperTypeOf($constantType)->yes(); - if ($isZeroValue && $context->false()) { + if ($isZeroValue && $context->falsey()) { $producesZero = TypeCombinator::union(new NullType(), new ConstantBooleanType(false)); return $castTypes->unionWith( $this->create($innerExpr, $producesZero, $context, $scope)->setRootExpr($rootExpr),

Check warning on line 1619 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrueTruthyFalseFalseyTypeSpecifierContextMutator": @@ @@ : new ConstantFloatType(0.0); $isZeroValue = $zeroType->isSuperTypeOf($constantType)->yes(); - if ($isZeroValue && $context->false()) { + if ($isZeroValue && $context->falsey()) { $producesZero = TypeCombinator::union(new NullType(), new ConstantBooleanType(false)); return $castTypes->unionWith( $this->create($innerExpr, $producesZero, $context, $scope)->setRootExpr($rootExpr),
$producesZero = TypeCombinator::union(new NullType(), new ConstantBooleanType(false));
return $castTypes->unionWith(
$this->create($innerExpr, $producesZero, $context, $scope)->setRootExpr($rootExpr),
);
}

if (!$isZeroValue && $context->true()) {

Check warning on line 1626 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrueTruthyFalseFalseyTypeSpecifierContextMutator": @@ @@ ); } - if (!$isZeroValue && $context->true()) { + if (!$isZeroValue && $context->truthy()) { return $castTypes->unionWith( $this->create($innerExpr, new NullType(), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr), )->unionWith(

Check warning on line 1626 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrueTruthyFalseFalseyTypeSpecifierContextMutator": @@ @@ ); } - if (!$isZeroValue && $context->true()) { + if (!$isZeroValue && $context->truthy()) { return $castTypes->unionWith( $this->create($innerExpr, new NullType(), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr), )->unionWith(
return $castTypes->unionWith(
$this->create($innerExpr, new NullType(), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr),
)->unionWith(
$this->create($innerExpr, new ConstantBooleanType(false), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr),
);
}

return $castTypes;
}

return null;
}

Expand Down Expand Up @@ -1726,6 +1756,32 @@
}
}

if ($exprNode instanceof Expr\Cast\String_) {
$innerExpr = $exprNode->expr;
$castTypes = $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($rootExpr);

if ($constantStringValue === '') {
$producesEmpty = TypeCombinator::union(
new NullType(),
new ConstantBooleanType(false),
new ConstantStringType(''),
);
return $castTypes->unionWith(
$this->create($innerExpr, $producesEmpty, $context, $scope)->setRootExpr($rootExpr),
);
}

if ($context->true()) {

Check warning on line 1774 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrueTruthyFalseFalseyTypeSpecifierContextMutator": @@ @@ ); } - if ($context->true()) { + if ($context->truthy()) { return $castTypes->unionWith( $this->create($innerExpr, new NullType(), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr), )->unionWith(

Check warning on line 1774 in src/Analyser/TypeSpecifier.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrueTruthyFalseFalseyTypeSpecifierContextMutator": @@ @@ ); } - if ($context->true()) { + if ($context->truthy()) { return $castTypes->unionWith( $this->create($innerExpr, new NullType(), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr), )->unionWith(
return $castTypes->unionWith(
$this->create($innerExpr, new NullType(), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr),
)->unionWith(
$this->create($innerExpr, new ConstantBooleanType(false), TypeSpecifierContext::createFalse(), $scope)->setRootExpr($rootExpr),
);
}

return $castTypes;
}

return null;
}

Expand Down Expand Up @@ -2858,7 +2914,20 @@
new ConstantStringType(''),
];
}
return $this->create($exprNode, new UnionType($trueTypes), $context, $scope)->setRootExpr($expr);
$types = $this->create($exprNode, new UnionType($trueTypes), $context, $scope)->setRootExpr($expr);

if ($exprNode instanceof Expr\Cast\String_) {
$producesEmpty = TypeCombinator::union(
new NullType(),
new ConstantBooleanType(false),
new ConstantStringType(''),
);
$types = $types->unionWith(
$this->create($exprNode->expr, $producesEmpty, $context, $scope)->setRootExpr($expr),
);
}

return $types;
}

if (
Expand Down Expand Up @@ -2966,8 +3035,11 @@
$leftExpr = $expr->left;
$rightExpr = $expr->right;

// Normalize to: fn() === expr
if ($rightExpr instanceof FuncCall && !$leftExpr instanceof FuncCall) {
// Normalize to: fn()/cast === expr
if (
($rightExpr instanceof FuncCall || $rightExpr instanceof Expr\Cast)
&& !($leftExpr instanceof FuncCall || $leftExpr instanceof Expr\Cast)
) {
$specifiedTypes = $this->resolveNormalizedIdentical(new Expr\BinaryOp\Identical(
$rightExpr,
$leftExpr,
Expand Down
70 changes: 70 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-8231-analogous.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug8231Analogous;

use function PHPStan\Testing\assertType;

// Analogous case: (int) cast in strict comparison
function testIntCastNotIdenticalZero(int|null $x): void {
if ((int)$x !== 0) {
assertType('int', $x);
} else {
assertType('int|null', $x);
}
}

function testIntCastIdenticalZero(int|null $x): void {
if ((int)$x === 0) {
assertType('int|null', $x);
} else {
assertType('int', $x);
}
}

// Analogous case: (float) cast in strict comparison
function testFloatCastNotIdenticalZero(float|null $x): void {
if ((float)$x !== 0.0) {
assertType('float', $x);
} else {
assertType('float|null', $x);
}
}

// Analogous case: loose comparison with string cast
function testStringCastLooseNotEqual(string|null $x): void {
if ((string)$x != '') {
assertType('non-empty-string', $x);
} else {
assertType("''|null", $x);
}
}

// (bool) cast already works via existing specifyTypesForConstantBinaryExpression
function testBoolCastIdenticalTrue(string|null $x): void {
if ((bool)$x === true) {
assertType('non-falsy-string', $x);
}
}

// Analogous: (int) cast with non-zero constant
function testIntCastIdenticalNonZero(int|null $x): void {
if ((int)$x === 5) {
assertType('int', $x);
}
}

// Analogous: (float) cast with non-zero constant
function testFloatCastIdenticalNonZero(float|null $x): void {
if ((float)$x === 3.14) {
assertType('float', $x);
}
}

// Reversed ordering: 0 !== (int)$x
function testIntCastReversed(int|null $x): void {
if (0 !== (int)$x) {
assertType('int', $x);
}
}
67 changes: 67 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-8231.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug8231;

use function PHPStan\Testing\assertType;

function foo(string $x): void {}

function testStringCastNotIdenticalEmpty(string|null $x): void {
if ((string)$x !== '') {
assertType('non-empty-string', $x);
foo($x);
} else {
assertType("''|null", $x);
}
assertType('string|null', $x);
}

function testStringCastIdenticalEmpty(string|null $x): void {
if ((string)$x === '') {
assertType("''|null", $x);
} else {
assertType('non-empty-string', $x);
}
}

function testStringCastNotIdenticalEmptyReversed(string|null $x): void {
if ('' !== (string)$x) {
assertType('non-empty-string', $x);
} else {
assertType("''|null", $x);
}
}

function testStringCastIntNull(int|null $y): void {
if ((string)$y !== '') {
assertType('int', $y);
} else {
assertType('null', $y);
}
}

/** @param string|false $z */
function testStringCastStringFalse(string|false $z): void {
if ((string)$z !== '') {
assertType('non-empty-string', $z);
} else {
assertType("''|false", $z);
}
}

/** @param bool|null $b */
function testStringCastBoolNull(bool|null $b): void {
if ((string)$b !== '') {
assertType('true', $b);
} else {
assertType('false|null', $b);
}
}

function testStringCastIdenticalNonEmpty(string|null $x): void {
if ((string)$x === 'hello') {
assertType('string', $x);
}
}
Loading