diff --git a/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php b/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php index a0de3ba5e22..f762a308775 100644 --- a/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php +++ b/src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php @@ -881,7 +881,7 @@ static function (string $parameterName) use ($argsMap, $getArgType): ?Type { static function (Type $type, callable $traverse) use ($templateTypeMap, &$containsUnresolvedTemplate) { if ($type instanceof TemplateType && $type->getScope()->getClassName() !== null) { $resolvedType = $templateTypeMap->getType($type->getName()); - if ($resolvedType === null || $type->getBound()->equals($resolvedType)) { + if ($resolvedType === null || ($type->getScope()->getFunctionName() === null && $type->getBound()->equals($resolvedType))) { $containsUnresolvedTemplate = true; return $type; } diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php index 1cf941dafd9..a8b9db7c3d7 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeStaticMethodCallRuleTest.php @@ -143,6 +143,38 @@ public function testAssertUnresolvedGeneric(): void $this->analyse([__DIR__ . '/data/assert-unresolved-generic.php'], []); } + #[RequiresPhp('>= 8.0.0')] + public function testNoNamedArgumentsAssertIsAlwaysSatisfied(): void + { + $this->treatPhpDocTypesAsCertain = false; + $this->analyse([__DIR__ . '/data/no-named-arguments-assert.php'], [ + [ + 'Call to static method NoNamedArgumentsAssert\QueryBuilder::validateVariadicParameter() with list will always evaluate to true.', + 14, + ], + [ + 'Call to static method NoNamedArgumentsAssert\QueryBuilder::validateVariadicParameter() with list will always evaluate to true.', + 22, + ], + ]); + } + + #[RequiresPhp('>= 8.0.0')] + public function testNoNamedArgumentsAssertIsAlwaysSatisfiedTreatPhpDocTypesAsCertain(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/no-named-arguments-assert.php'], [ + [ + 'Call to static method NoNamedArgumentsAssert\QueryBuilder::validateVariadicParameter() with list will always evaluate to true.', + 14, + ], + [ + 'Call to static method NoNamedArgumentsAssert\QueryBuilder::validateVariadicParameter() with list will always evaluate to true.', + 22, + ], + ]); + } + public static function dataReportAlwaysTrueInLastCondition(): iterable { yield [false, [ diff --git a/tests/PHPStan/Rules/Comparison/data/no-named-arguments-assert.php b/tests/PHPStan/Rules/Comparison/data/no-named-arguments-assert.php new file mode 100644 index 00000000000..1ea5d54ec8d --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/no-named-arguments-assert.php @@ -0,0 +1,39 @@ += 8.0 +declare(strict_types = 1); + +namespace NoNamedArgumentsAssert; + +final class QueryBuilder +{ + + /** + * @no-named-arguments + */ + public function select(mixed ...$select): void + { + self::validateVariadicParameter($select); + } + + /** + * @no-named-arguments + */ + public function groupBy(string ...$groupBy): void + { + self::validateVariadicParameter($groupBy); + } + + /** + * @template TItem + * @param TItem[] $parameter + * @phpstan-assert list $parameter + */ + private static function validateVariadicParameter(array $parameter): void + { + if (array_is_list($parameter)) { + return; + } + + throw new \InvalidArgumentException(); + } + +} diff --git a/turbo-ext/src/DefaultNarrowingHelper.cpp b/turbo-ext/src/DefaultNarrowingHelper.cpp index 85bdbb3038e..30963b7bae8 100644 --- a/turbo-ext/src/DefaultNarrowingHelper.cpp +++ b/turbo-ext/src/DefaultNarrowingHelper.cpp @@ -2027,15 +2027,19 @@ class DefaultNarrowingHelper if (UNEXPECTED(resolvedType.isUndef())) return; bool unresolved = resolvedType.isNull(); if (!unresolved) { - zv::Val bound = pt_type_call(Z_OBJ_P(type), PT_LC("getbound"), 0, NULL); - if (UNEXPECTED(bound.isUndef())) return; - if (UNEXPECTED(Z_TYPE_P(bound.raw()) != IS_OBJECT)) { - (void) callOnNonObject("equals", bound.raw()); - return; + zv::Val functionName = pt_type_call(Z_OBJ_P(templateScope.raw()), PT_LC("getfunctionname"), 0, NULL); + if (UNEXPECTED(functionName.isUndef())) return; + if (functionName.isNull()) { + zv::Val bound = pt_type_call(Z_OBJ_P(type), PT_LC("getbound"), 0, NULL); + if (UNEXPECTED(bound.isUndef())) return; + if (UNEXPECTED(Z_TYPE_P(bound.raw()) != IS_OBJECT)) { + (void) callOnNonObject("equals", bound.raw()); + return; + } + zv::Val equals = pt_type_op(Z_OBJ_P(bound.raw()), PT_OP_EQUALS, 1, resolvedType.raw()); + if (UNEXPECTED(equals.isUndef())) return; + unresolved = zend_is_true(equals.raw()); } - zv::Val equals = pt_type_op(Z_OBJ_P(bound.raw()), PT_OP_EQUALS, 1, resolvedType.raw()); - if (UNEXPECTED(equals.isUndef())) return; - unresolved = zend_is_true(equals.raw()); } if (unresolved) { zval *target = containsUnresolvedTemplate;