diff --git a/src/Rules/Functions/PrintfPlaceholder.php b/src/Rules/Functions/PrintfPlaceholder.php
index 4bdeb156d6..c151395308 100644
--- a/src/Rules/Functions/PrintfPlaceholder.php
+++ b/src/Rules/Functions/PrintfPlaceholder.php
@@ -3,10 +3,14 @@
namespace PHPStan\Rules\Functions;
use PHPStan\ShouldNotHappenException;
+use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
+use PHPStan\Type\IntersectionType;
+use PHPStan\Type\NullType;
use PHPStan\Type\StringAlwaysAcceptingObjectWithToStringType;
+use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
@@ -34,7 +38,11 @@ public function doesArgumentTypeMatchPlaceholder(Type $argumentType, bool $stric
: ! $argumentType->toInteger() instanceof ErrorType;
case 'float':
return $strictPlaceholderTypes
- ? (new FloatType())->accepts($argumentType, true)->yes()
+ ? TypeCombinator::union(
+ new FloatType(),
+ // numeric-string is allowed for consistency with phpstan-strict-rules.
+ new IntersectionType([new StringType(), new AccessoryNumericStringType()]),
+ )->accepts($argumentType, true)->yes()
: ! $argumentType->toFloat() instanceof ErrorType;
case 'string':
case 'mixed':
@@ -46,6 +54,8 @@ public function doesArgumentTypeMatchPlaceholder(Type $argumentType, bool $stric
new StringAlwaysAcceptingObjectWithToStringType(),
// float also accepts int.
new FloatType(),
+ // null is allowed for consistency with phpstan-strict-rules (e.g. $string . $null).
+ new NullType(),
)->accepts($argumentType, true)->yes();
// Without this PHPStan with PHP 7.4 reports "...should return bool but return statement is missing."
// Presumably, because promoted properties are turned into regular properties and the phpdoc isn't applied to the property.
diff --git a/tests/PHPStan/Rules/Functions/PrintfParameterTypeRuleTest.php b/tests/PHPStan/Rules/Functions/PrintfParameterTypeRuleTest.php
index 06e34b998c..835700921c 100644
--- a/tests/PHPStan/Rules/Functions/PrintfParameterTypeRuleTest.php
+++ b/tests/PHPStan/Rules/Functions/PrintfParameterTypeRuleTest.php
@@ -222,10 +222,6 @@ public function testStrict(): void
'Parameter #2 of function printf is expected to be int by placeholder #1 ("%d"), SimpleXMLElement given.',
40,
],
- [
- 'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), string given.',
- 42,
- ],
[
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), null given.',
43,
@@ -238,10 +234,6 @@ public function testStrict(): void
'Parameter #2 of function printf is expected to be float by placeholder #1 ("%f"), SimpleXMLElement given.',
45,
],
- [
- 'Parameter #2 of function printf is expected to be string by placeholder #1 ("%s"), null given.',
- 47,
- ],
[
'Parameter #2 of function printf is expected to be string by placeholder #1 ("%s"), true given.',
48,
diff --git a/tests/PHPStan/Rules/Functions/data/printf-param-types.php b/tests/PHPStan/Rules/Functions/data/printf-param-types.php
index 32b8f7b366..f3c54c77d7 100644
--- a/tests/PHPStan/Rules/Functions/data/printf-param-types.php
+++ b/tests/PHPStan/Rules/Functions/data/printf-param-types.php
@@ -39,12 +39,12 @@ public function __toString(): string
printf('%d', true);
printf('%d', new \SimpleXMLElement('aaa'));
-printf('%f', '1.2345678901234567890123456789013245678901234567989');
+
printf('%f', null);
printf('%f', true);
printf('%f', new \SimpleXMLElement('aaa'));
-printf('%s', null);
+
printf('%s', true);
// Error, but already reported by CallToFunctionParametersRule
@@ -55,6 +55,10 @@ public function __toString(): string
printf('%s');
printf('%s', 1, 2);
+// Arguable
+printf('%f', '1.2345678901234567890123456789013245678901234567989');
+printf('%s', null);
+
// OK
printf('%s', 'a');
printf('%s', new FooStringable());