Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@
$impurePoints = [];

$exprType = $scope->getType($expr);
if ($exprType->isObject()->no()) {

Check warning on line 31 in src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $impurePoints = []; $exprType = $scope->getType($expr); - if ($exprType->isObject()->no()) { + if (!$exprType->isObject()->yes()) { return new ExpressionResult( $scope, hasYield: false,

Check warning on line 31 in src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ $impurePoints = []; $exprType = $scope->getType($expr); - if ($exprType->isObject()->no()) { + if (!$exprType->isObject()->yes()) { return new ExpressionResult( $scope, hasYield: false,
return new ExpressionResult(
$scope,
hasYield: false,
isAlwaysTerminating: false,
throwPoints: [],
impurePoints: [],
);
}
$toStringMethod = $scope->getMethodReflection($exprType, '__toString');
if ($toStringMethod === null) {
return new ExpressionResult(
Expand Down
46 changes: 46 additions & 0 deletions src/Type/TypeCombinator.php
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,52 @@ public static function intersect(Type ...$types): Type
continue;
}

if (
$types[$j] instanceof TemplateMixedType
&& !$types[$i] instanceof TemplateType
&& $types[$i]->isScalar()->yes()
&& !$types[$i]->isConstantScalarValue()->yes()
&& $types[$j]->getBound()->isSuperTypeOf($types[$i])->yes()
) {
$narrowed = TemplateTypeFactory::create(
$types[$j]->getScope(),
$types[$j]->getName(),
$types[$i],
$types[$j]->getVariance(),
$types[$j]->getStrategy(),
$types[$j]->getDefault(),
);
if (!$narrowed instanceof TemplateMixedType) {
$types[$j] = $narrowed;
array_splice($types, $i--, 1);
$typesCount--;
continue 2;
}
}

if (
$types[$i] instanceof TemplateMixedType
&& !$types[$j] instanceof TemplateType
&& $types[$j]->isScalar()->yes()
&& !$types[$j]->isConstantScalarValue()->yes()
&& $types[$i]->getBound()->isSuperTypeOf($types[$j])->yes()
) {
$narrowed = TemplateTypeFactory::create(
$types[$i]->getScope(),
$types[$i]->getName(),
$types[$j],
$types[$i]->getVariance(),
$types[$i]->getStrategy(),
$types[$i]->getDefault(),
);
if (!$narrowed instanceof TemplateMixedType) {
$types[$i] = $narrowed;
array_splice($types, $j--, 1);
$typesCount--;
continue;
}
}

if ($types[$i] instanceof IterableType) {
$isSuperTypeB = $types[$i]->isSuperTypeOfMixed($types[$j]);
} else {
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-4498.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function fcn(iterable $iterable): iterable
public function bar(iterable $iterable): iterable
{
if (is_array($iterable)) {
assertType('array<((int&TKey (method Bug4498\Foo::bar(), argument))|(string&TKey (method Bug4498\Foo::bar(), argument))), TValue (method Bug4498\Foo::bar(), argument)>', $iterable);
assertType('array<(TKey of int (method Bug4498\Foo::bar(), argument)|TKey of string (method Bug4498\Foo::bar(), argument)), TValue (method Bug4498\Foo::bar(), argument)>', $iterable);
return $iterable;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,11 +725,11 @@ public function testArrayUdiffCallback(): void
14,
],
[
'Parameter #1 $arr1 of function array_udiff expects array<(int&TK)|(string&TK), string>, null given.',
'Parameter #1 $arr1 of function array_udiff expects array<(TK of int)|TK of string, string>, null given.',
20,
],
[
'Parameter #2 $arr2 of function array_udiff expects array<(int&TK)|(string&TK), string>, null given.',
'Parameter #2 $arr2 of function array_udiff expects array<(TK of int)|TK of string, string>, null given.',
21,
],
[
Expand Down
5 changes: 5 additions & 0 deletions tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,9 @@ public function testBug14504(): void
$this->analyse([__DIR__ . '/data/bug-14504.php'], []);
}

public function testBug14511(): void
{
$this->analyse([__DIR__ . '/data/bug-14511.php'], []);
}

}
6 changes: 6 additions & 0 deletions tests/PHPStan/Rules/Pure/PureMethodRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -383,4 +383,10 @@ public function testBug14504(): void
$this->analyse([__DIR__ . '/data/bug-14504-method.php'], []);
}

public function testBug14511(): void
{
$this->treatPhpDocTypesAsCertain = true;
$this->analyse([__DIR__ . '/data/bug-14511-method.php'], []);
}

}
47 changes: 47 additions & 0 deletions tests/PHPStan/Rules/Pure/data/bug-14511-method.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace Bug14511Method;

class Foo
{

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
public function testStringCast(mixed $val): ?string
{
if (is_int($val)) {
return (string) $val;
}
return null;
}

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
public function testStringConcat(mixed $val): ?string
{
if (is_int($val)) {
return 'value: ' . $val;
}
return null;
}

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
public function testEmptyNonArray(mixed $val): ?string
{
if (empty($val) && !\is_array($val)) {
return (string) $val;
}
return null;
}

}
75 changes: 75 additions & 0 deletions tests/PHPStan/Rules/Pure/data/bug-14511.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php declare(strict_types = 1);

namespace Bug14511;

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
function testStringCast(mixed $val): ?string {
if (is_int($val)) {
return (string) $val;
}
return null;
}

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
function testStringConcat(mixed $val): ?string {
if (is_int($val)) {
return 'value: ' . $val;
}
return null;
}

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
function testFloatCast(mixed $val): ?string {
if (is_float($val)) {
return (string) $val;
}
return null;
}

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
function testBoolCast(mixed $val): ?string {
if (is_bool($val)) {
return (string) $val;
}
return null;
}

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
function testStringVal(mixed $val): ?string {
if (is_string($val)) {
return (string) $val;
}
return null;
}

/**
* @phpstan-pure
* @template T of mixed
* @param T $val
*/
function testEmptyNonArray(mixed $val): ?string {
if (empty($val) && !\is_array($val)) {
return (string) $val;
}
return null;
}
Loading