diff --git a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php index 1b66bc7ba38..e7177fb9b4c 100644 --- a/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php +++ b/src/Analyser/ExprHandler/Helper/ImplicitToStringCallHelper.php @@ -28,6 +28,15 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E $impurePoints = []; $exprType = $scope->getType($expr); + if ($exprType->isObject()->no()) { + return new ExpressionResult( + $scope, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + ); + } $toStringMethod = $scope->getMethodReflection($exprType, '__toString'); if ($toStringMethod === null) { return new ExpressionResult( diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 901c78de7ee..990e6b57263 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -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 { diff --git a/tests/PHPStan/Analyser/nsrt/bug-4498.php b/tests/PHPStan/Analyser/nsrt/bug-4498.php index ad07baa3db4..68e652b159a 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-4498.php +++ b/tests/PHPStan/Analyser/nsrt/bug-4498.php @@ -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; } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index a202c8e271d..e086b2ea02b 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -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, ], [ diff --git a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php index 9fa246f5552..8e172d035a0 100644 --- a/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureFunctionRuleTest.php @@ -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'], []); + } + } diff --git a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php index 2b896c17b10..88bb38c5d40 100644 --- a/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php +++ b/tests/PHPStan/Rules/Pure/PureMethodRuleTest.php @@ -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'], []); + } + } diff --git a/tests/PHPStan/Rules/Pure/data/bug-14511-method.php b/tests/PHPStan/Rules/Pure/data/bug-14511-method.php new file mode 100644 index 00000000000..5b8b15030a5 --- /dev/null +++ b/tests/PHPStan/Rules/Pure/data/bug-14511-method.php @@ -0,0 +1,47 @@ +