From e73fd77c49f28ee2f1da34881d4a1adafee820c3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 9 Aug 2025 13:23:57 +0200 Subject: [PATCH 1/4] Handle default value in phpstan-assert --- src/Analyser/TypeSpecifier.php | 9 ++ tests/PHPStan/Analyser/nsrt/bug-9435.php | 112 +++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-9435.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index e946229f7d..af9497da65 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -21,6 +21,7 @@ use PhpParser\Node\Name; use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Node\Expr\AlwaysRememberedExpr; +use PHPStan\Node\Expr\TypeExpr; use PHPStan\Node\IssetExpr; use PHPStan\Node\Printer\ExprPrinter; use PHPStan\Php\PhpVersion; @@ -1451,6 +1452,14 @@ private function specifyTypesFromAsserts(TypeSpecifierContext $context, Expr\Cal $argsMap[$paramName][] = $arg->value; } + foreach ($parameters as $parameter) { + $name = $parameter->getName(); + $defaultValue = $parameter->getDefaultValue(); + if (isset($argsMap[$name]) || $defaultValue === null) { + continue; + } + $argsMap[$name][] = new TypeExpr($defaultValue); + } if ($call instanceof MethodCall) { $argsMap['this'] = [$call->var]; diff --git a/tests/PHPStan/Analyser/nsrt/bug-9435.php b/tests/PHPStan/Analyser/nsrt/bug-9435.php new file mode 100644 index 0000000000..5a1f821013 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9435.php @@ -0,0 +1,112 @@ + Date: Sat, 9 Aug 2025 15:14:13 +0200 Subject: [PATCH 2/4] More tests --- tests/PHPStan/Analyser/nsrt/bug-9435.php | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-9435.php b/tests/PHPStan/Analyser/nsrt/bug-9435.php index 5a1f821013..3c3256a614 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9435.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9435.php @@ -28,6 +28,10 @@ function trueCheck(mixed $input, bool $allow_null = false): void trueCheck($a, allow_null: false); assertType('string', $a); // correct (string) +$a = x(); +trueCheck(allow_null: false, input: $a); +assertType('string', $a); // correct (string) + $a = x(); trueCheck($a, true); assertType('string|null', $a); // correct (string|null) @@ -36,6 +40,10 @@ function trueCheck(mixed $input, bool $allow_null = false): void trueCheck($a, allow_null: true); assertType('string|null', $a); // correct (string|null) +$a = x(); +trueCheck(allow_null: true, input: $a); +assertType('string|null', $a); // correct (string|null) + /** @phpstan-assert ($allow_null is false ? string : string|null) $input */ function falseCheck(mixed $input, bool $allow_null = false): void { @@ -53,6 +61,10 @@ function falseCheck(mixed $input, bool $allow_null = false): void falseCheck($a, allow_null: false); assertType('string', $a); // correct (string) +$a = x(); +falseCheck(allow_null: false, input: $a); +assertType('string', $a); // correct (string|null) + $a = x(); falseCheck($a, true); assertType('string|null', $a); // correct (string|null) @@ -61,6 +73,10 @@ function falseCheck(mixed $input, bool $allow_null = false): void falseCheck($a, allow_null: true); assertType('string|null', $a); // correct (string|null) +$a = x(); +falseCheck(allow_null: true, input: $a); +assertType('string|null', $a); // correct (string|null) + /** @phpstan-assert ($allow_null is not true ? string : string|null) $input */ function notTrueCheck(mixed $input, bool $allow_null = false): void { @@ -78,6 +94,10 @@ function notTrueCheck(mixed $input, bool $allow_null = false): void notTrueCheck($a, allow_null: false); assertType('string', $a); // correct (string) +$a = x(); +notTrueCheck(allow_null: false, input: $a); +assertType('string', $a); // correct (string|null) + $a = x(); notTrueCheck($a, true); assertType('string|null', $a); // correct (string|null) @@ -86,6 +106,10 @@ function notTrueCheck(mixed $input, bool $allow_null = false): void notTrueCheck($a, allow_null: true); assertType('string|null', $a); // correct (string|null) +$a = x(); +notTrueCheck(allow_null: true, input: $a); +assertType('string|null', $a); // correct (string|null) + /** @phpstan-assert ($allow_null is not false ? string|null : string) $input */ function notFalseCheck(mixed $input, bool $allow_null = false): void { @@ -103,6 +127,10 @@ function notFalseCheck(mixed $input, bool $allow_null = false): void notFalseCheck($a, allow_null: false); assertType('string', $a); // correct (string) +$a = x(); +notFalseCheck(allow_null: false, input: $a); +assertType('string', $a); // correct (string|null) + $a = x(); notFalseCheck($a, true); assertType('string|null', $a); // correct (string|null) @@ -110,3 +138,7 @@ function notFalseCheck(mixed $input, bool $allow_null = false): void $a = x(); notFalseCheck($a, allow_null: true); assertType('string|null', $a); // correct (string|null) + +$a = x(); +notFalseCheck(allow_null: true, input: $a); +assertType('string|null', $a); // correct (string|null) From 0748138436a30c01d070da5c059b7054457afddd Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 9 Aug 2025 15:22:27 +0200 Subject: [PATCH 3/4] Fix php 7.4 error --- tests/PHPStan/Analyser/nsrt/bug-9435.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-9435.php b/tests/PHPStan/Analyser/nsrt/bug-9435.php index 3c3256a614..a684d6808f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9435.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9435.php @@ -1,4 +1,4 @@ -= 8.0 declare(strict_types=1); From d365526c3e5d7dbab412de99c01f77de488b8e85 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 9 Aug 2025 16:09:11 +0200 Subject: [PATCH 4/4] Add test --- tests/PHPStan/Analyser/nsrt/bug-9435.php | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug-9435.php b/tests/PHPStan/Analyser/nsrt/bug-9435.php index a684d6808f..a3196862b1 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-9435.php +++ b/tests/PHPStan/Analyser/nsrt/bug-9435.php @@ -142,3 +142,36 @@ function notFalseCheck(mixed $input, bool $allow_null = false): void $a = x(); notFalseCheck(allow_null: true, input: $a); assertType('string|null', $a); // correct (string|null) + +/** @phpstan-assert ($allow_null is false ? string : string|null) $input */ +function checkWithVariadics(mixed $input, bool $allow_null = false, ...$more): void +{ +} + +$a = x(); +checkWithVariadics($a); +assertType('string', $a); // incorrect: should be string but is string|null + +$a = x(); +checkWithVariadics($a, false); +assertType('string', $a); // correct (string) + +$a = x(); +checkWithVariadics($a, allow_null: false); +assertType('string', $a); // correct (string) + +$a = x(); +checkWithVariadics(allow_null: false, input: $a); +assertType('string', $a); // correct (string) + +$a = x(); +checkWithVariadics($a, true); +assertType('string|null', $a); // correct (string|null) + +$a = x(); +checkWithVariadics($a, allow_null: true); +assertType('string|null', $a); // correct (string|null) + +$a = x(); +checkWithVariadics(allow_null: true, input: $a); +assertType('string|null', $a); // correct (string|null)