From a44e0d791e8f6ed86036e08442ecd97d756eb511 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 19 Oct 2025 12:05:09 +0200 Subject: [PATCH] Use the same scalar limit for sprintf and concat --- .../InitializerExprTypeResolver.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-13378.php | 29 +++++++++++++++++ .../Analyser/nsrt/constant-string-unions.php | 31 ++++++++++++------- .../CallToFunctionParametersRuleTest.php | 2 +- 4 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-13378.php diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 8b38c5a383..16446a29d7 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -457,7 +457,7 @@ public function resolveConcatType(Type $left, Type $right): Type $combinedConstantStringsCount = count($leftConstantStrings) * count($rightConstantStrings); // we limit the number of union-types for performance reasons - if ($combinedConstantStringsCount > 0 && $combinedConstantStringsCount <= 16) { + if ($combinedConstantStringsCount > 0 && $combinedConstantStringsCount <= self::CALCULATE_SCALARS_LIMIT) { $strings = []; foreach ($leftConstantStrings as $leftConstantString) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-13378.php b/tests/PHPStan/Analyser/nsrt/bug-13378.php new file mode 100644 index 0000000000..916a248a8a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13378.php @@ -0,0 +1,29 @@ +format('n'); + $prefix = $short ? 'SHORT_' : ''; + + $formatted = $prefix . 'MONTH_' . $month; + + assertType("'MONTH_1'|'MONTH_10'|'MONTH_11'|'MONTH_12'|'MONTH_2'|'MONTH_3'|'MONTH_4'|'MONTH_5'|'MONTH_6'|'MONTH_7'|'MONTH_8'|'MONTH_9'|'SHORT_MONTH_1'|'SHORT_MONTH_10'|'SHORT_MONTH_11'|'SHORT_MONTH_12'|'SHORT_MONTH_2'|'SHORT_MONTH_3'|'SHORT_MONTH_4'|'SHORT_MONTH_5'|'SHORT_MONTH_6'|'SHORT_MONTH_7'|'SHORT_MONTH_8'|'SHORT_MONTH_9'", $formatted); + + return $formatted; +} + +function formatMonthSprintf(\DateTimeInterface $date, bool $short = false): string +{ + $month = $date->format('n'); + $prefix = $short ? 'SHORT_' : ''; + + $formatted = sprintf('%sMONTH_%s', $prefix, $month); + + assertType("'MONTH_1'|'MONTH_10'|'MONTH_11'|'MONTH_12'|'MONTH_2'|'MONTH_3'|'MONTH_4'|'MONTH_5'|'MONTH_6'|'MONTH_7'|'MONTH_8'|'MONTH_9'|'SHORT_MONTH_1'|'SHORT_MONTH_10'|'SHORT_MONTH_11'|'SHORT_MONTH_12'|'SHORT_MONTH_2'|'SHORT_MONTH_3'|'SHORT_MONTH_4'|'SHORT_MONTH_5'|'SHORT_MONTH_6'|'SHORT_MONTH_7'|'SHORT_MONTH_8'|'SHORT_MONTH_9'", $formatted); + + return $formatted; +} diff --git a/tests/PHPStan/Analyser/nsrt/constant-string-unions.php b/tests/PHPStan/Analyser/nsrt/constant-string-unions.php index b97b117179..ee236b553e 100644 --- a/tests/PHPStan/Analyser/nsrt/constant-string-unions.php +++ b/tests/PHPStan/Analyser/nsrt/constant-string-unions.php @@ -59,26 +59,32 @@ public function encapsedString():void * @param '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'10'|'11'|'12'|'13'|'14'|'15' $s15 * @param '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'10'|'11'|'12'|'13'|'14'|'15'|'16' $s16 * @param '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'10'|'11'|'12'|'13'|'14'|'15'|'16'|'17' $s17 + * @param 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h' $suffix */ - public function testLimit(string $s15, string $s16, string $s17) { + public function testLimit(string $s15, string $s16, string $s17, string $suffix) { if (rand(0,1)) { - // doubles the number of elements - $s15 .= 'a'; - $s16 .= 'a'; - $s17 .= 'a'; + // multiply the number of elements by 8 + $s15 .= $suffix; + $s16 .= $suffix; + $s17 .= $suffix; } - // union should contain 30 elements - assertType("'1'|'10'|'10a'|'11'|'11a'|'12'|'12a'|'13'|'13a'|'14'|'14a'|'15'|'15a'|'1a'|'2'|'2a'|'3'|'3a'|'4'|'4a'|'5'|'5a'|'6'|'6a'|'7'|'7a'|'8'|'8a'|'9'|'9a'", $s15); - // union should contain 32 elements - assertType("'1'|'10'|'10a'|'11'|'11a'|'12'|'12a'|'13'|'13a'|'14'|'14a'|'15'|'15a'|'16'|'16a'|'1a'|'2'|'2a'|'3'|'3a'|'4'|'4a'|'5'|'5a'|'6'|'6a'|'7'|'7a'|'8'|'8a'|'9'|'9a'", $s16); + + // union should contain 120 elements + assertType("'1'|'10'|'10a'|'10b'|'10c'|'10d'|'10e'|'10f'|'10g'|'10h'|'11'|'11a'|'11b'|'11c'|'11d'|'11e'|'11f'|'11g'|'11h'|'12'|'12a'|'12b'|'12c'|'12d'|'12e'|'12f'|'12g'|'12h'|'13'|'13a'|'13b'|'13c'|'13d'|'13e'|'13f'|'13g'|'13h'|'14'|'14a'|'14b'|'14c'|'14d'|'14e'|'14f'|'14g'|'14h'|'15'|'15a'|'15b'|'15c'|'15d'|'15e'|'15f'|'15g'|'15h'|'1a'|'1b'|'1c'|'1d'|'1e'|'1f'|'1g'|'1h'|'2'|'2a'|'2b'|'2c'|'2d'|'2e'|'2f'|'2g'|'2h'|'3'|'3a'|'3b'|'3c'|'3d'|'3e'|'3f'|'3g'|'3h'|'4'|'4a'|'4b'|'4c'|'4d'|'4e'|'4f'|'4g'|'4h'|'5'|'5a'|'5b'|'5c'|'5d'|'5e'|'5f'|'5g'|'5h'|'6'|'6a'|'6b'|'6c'|'6d'|'6e'|'6f'|'6g'|'6h'|'7'|'7a'|'7b'|'7c'|'7d'|'7e'|'7f'|'7g'|'7h'|'8'|'8a'|'8b'|'8c'|'8d'|'8e'|'8f'|'8g'|'8h'|'9'|'9a'|'9b'|'9c'|'9d'|'9e'|'9f'|'9g'|'9h'", $s15); + // union should contain 128 elements + assertType("'1'|'10'|'10a'|'10b'|'10c'|'10d'|'10e'|'10f'|'10g'|'10h'|'11'|'11a'|'11b'|'11c'|'11d'|'11e'|'11f'|'11g'|'11h'|'12'|'12a'|'12b'|'12c'|'12d'|'12e'|'12f'|'12g'|'12h'|'13'|'13a'|'13b'|'13c'|'13d'|'13e'|'13f'|'13g'|'13h'|'14'|'14a'|'14b'|'14c'|'14d'|'14e'|'14f'|'14g'|'14h'|'15'|'15a'|'15b'|'15c'|'15d'|'15e'|'15f'|'15g'|'15h'|'16'|'16a'|'16b'|'16c'|'16d'|'16e'|'16f'|'16g'|'16h'|'1a'|'1b'|'1c'|'1d'|'1e'|'1f'|'1g'|'1h'|'2'|'2a'|'2b'|'2c'|'2d'|'2e'|'2f'|'2g'|'2h'|'3'|'3a'|'3b'|'3c'|'3d'|'3e'|'3f'|'3g'|'3h'|'4'|'4a'|'4b'|'4c'|'4d'|'4e'|'4f'|'4g'|'4h'|'5'|'5a'|'5b'|'5c'|'5d'|'5e'|'5f'|'5g'|'5h'|'6'|'6a'|'6b'|'6c'|'6d'|'6e'|'6f'|'6g'|'6h'|'7'|'7a'|'7b'|'7c'|'7d'|'7e'|'7f'|'7g'|'7h'|'8'|'8a'|'8b'|'8c'|'8d'|'8e'|'8f'|'8g'|'8h'|'9'|'9a'|'9b'|'9c'|'9d'|'9e'|'9f'|'9g'|'9h'", $s16); // fallback to the more general form assertType("literal-string&lowercase-string&non-falsy-string", $s17); + $left = rand() ? 'a' : 'b'; $right = rand() ? 'x' : 'y'; $left .= $right; $left .= $right; $left .= $right; - assertType("'axxx'|'axxy'|'axyx'|'axyy'|'ayxx'|'ayxy'|'ayyx'|'ayyy'|'bxxx'|'bxxy'|'bxyx'|'bxyy'|'byxx'|'byxy'|'byyx'|'byyy'", $left); + $left .= $right; + $left .= $right; + $left .= $right; + assertType("'axxxxxx'|'axxxxxy'|'axxxxyx'|'axxxxyy'|'axxxyxx'|'axxxyxy'|'axxxyyx'|'axxxyyy'|'axxyxxx'|'axxyxxy'|'axxyxyx'|'axxyxyy'|'axxyyxx'|'axxyyxy'|'axxyyyx'|'axxyyyy'|'axyxxxx'|'axyxxxy'|'axyxxyx'|'axyxxyy'|'axyxyxx'|'axyxyxy'|'axyxyyx'|'axyxyyy'|'axyyxxx'|'axyyxxy'|'axyyxyx'|'axyyxyy'|'axyyyxx'|'axyyyxy'|'axyyyyx'|'axyyyyy'|'ayxxxxx'|'ayxxxxy'|'ayxxxyx'|'ayxxxyy'|'ayxxyxx'|'ayxxyxy'|'ayxxyyx'|'ayxxyyy'|'ayxyxxx'|'ayxyxxy'|'ayxyxyx'|'ayxyxyy'|'ayxyyxx'|'ayxyyxy'|'ayxyyyx'|'ayxyyyy'|'ayyxxxx'|'ayyxxxy'|'ayyxxyx'|'ayyxxyy'|'ayyxyxx'|'ayyxyxy'|'ayyxyyx'|'ayyxyyy'|'ayyyxxx'|'ayyyxxy'|'ayyyxyx'|'ayyyxyy'|'ayyyyxx'|'ayyyyxy'|'ayyyyyx'|'ayyyyyy'|'bxxxxxx'|'bxxxxxy'|'bxxxxyx'|'bxxxxyy'|'bxxxyxx'|'bxxxyxy'|'bxxxyyx'|'bxxxyyy'|'bxxyxxx'|'bxxyxxy'|'bxxyxyx'|'bxxyxyy'|'bxxyyxx'|'bxxyyxy'|'bxxyyyx'|'bxxyyyy'|'bxyxxxx'|'bxyxxxy'|'bxyxxyx'|'bxyxxyy'|'bxyxyxx'|'bxyxyxy'|'bxyxyyx'|'bxyxyyy'|'bxyyxxx'|'bxyyxxy'|'bxyyxyx'|'bxyyxyy'|'bxyyyxx'|'bxyyyxy'|'bxyyyyx'|'bxyyyyy'|'byxxxxx'|'byxxxxy'|'byxxxyx'|'byxxxyy'|'byxxyxx'|'byxxyxy'|'byxxyyx'|'byxxyyy'|'byxyxxx'|'byxyxxy'|'byxyxyx'|'byxyxyy'|'byxyyxx'|'byxyyxy'|'byxyyyx'|'byxyyyy'|'byyxxxx'|'byyxxxy'|'byyxxyx'|'byyxxyy'|'byyxyxx'|'byyxyxy'|'byyxyyx'|'byyxyyy'|'byyyxxx'|'byyyxxy'|'byyyxyx'|'byyyxyy'|'byyyyxx'|'byyyyxy'|'byyyyyx'|'byyyyyy'", $left); $left .= $right; assertType("literal-string&lowercase-string&non-falsy-string", $left); @@ -87,7 +93,10 @@ public function testLimit(string $s15, string $s16, string $s17) { $left = "{$left}{$right}"; $left = "{$left}{$right}"; $left = "{$left}{$right}"; - assertType("'axxx'|'axxy'|'axyx'|'axyy'|'ayxx'|'ayxy'|'ayyx'|'ayyy'|'bxxx'|'bxxy'|'bxyx'|'bxyy'|'byxx'|'byxy'|'byyx'|'byyy'", $left); + $left = "{$left}{$right}"; + $left = "{$left}{$right}"; + $left = "{$left}{$right}"; + assertType("'axxxxxx'|'axxxxxy'|'axxxxyx'|'axxxxyy'|'axxxyxx'|'axxxyxy'|'axxxyyx'|'axxxyyy'|'axxyxxx'|'axxyxxy'|'axxyxyx'|'axxyxyy'|'axxyyxx'|'axxyyxy'|'axxyyyx'|'axxyyyy'|'axyxxxx'|'axyxxxy'|'axyxxyx'|'axyxxyy'|'axyxyxx'|'axyxyxy'|'axyxyyx'|'axyxyyy'|'axyyxxx'|'axyyxxy'|'axyyxyx'|'axyyxyy'|'axyyyxx'|'axyyyxy'|'axyyyyx'|'axyyyyy'|'ayxxxxx'|'ayxxxxy'|'ayxxxyx'|'ayxxxyy'|'ayxxyxx'|'ayxxyxy'|'ayxxyyx'|'ayxxyyy'|'ayxyxxx'|'ayxyxxy'|'ayxyxyx'|'ayxyxyy'|'ayxyyxx'|'ayxyyxy'|'ayxyyyx'|'ayxyyyy'|'ayyxxxx'|'ayyxxxy'|'ayyxxyx'|'ayyxxyy'|'ayyxyxx'|'ayyxyxy'|'ayyxyyx'|'ayyxyyy'|'ayyyxxx'|'ayyyxxy'|'ayyyxyx'|'ayyyxyy'|'ayyyyxx'|'ayyyyxy'|'ayyyyyx'|'ayyyyyy'|'bxxxxxx'|'bxxxxxy'|'bxxxxyx'|'bxxxxyy'|'bxxxyxx'|'bxxxyxy'|'bxxxyyx'|'bxxxyyy'|'bxxyxxx'|'bxxyxxy'|'bxxyxyx'|'bxxyxyy'|'bxxyyxx'|'bxxyyxy'|'bxxyyyx'|'bxxyyyy'|'bxyxxxx'|'bxyxxxy'|'bxyxxyx'|'bxyxxyy'|'bxyxyxx'|'bxyxyxy'|'bxyxyyx'|'bxyxyyy'|'bxyyxxx'|'bxyyxxy'|'bxyyxyx'|'bxyyxyy'|'bxyyyxx'|'bxyyyxy'|'bxyyyyx'|'bxyyyyy'|'byxxxxx'|'byxxxxy'|'byxxxyx'|'byxxxyy'|'byxxyxx'|'byxxyxy'|'byxxyyx'|'byxxyyy'|'byxyxxx'|'byxyxxy'|'byxyxyx'|'byxyxyy'|'byxyyxx'|'byxyyxy'|'byxyyyx'|'byxyyyy'|'byyxxxx'|'byyxxxy'|'byyxxyx'|'byyxxyy'|'byyxyxx'|'byyxyxy'|'byyxyyx'|'byyxyyy'|'byyyxxx'|'byyyxxy'|'byyyxyx'|'byyyxyy'|'byyyyxx'|'byyyyxy'|'byyyyyx'|'byyyyyy'", $left); $left = "{$left}{$right}"; assertType("literal-string&lowercase-string&non-falsy-string", $left); } diff --git a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php index c8deff26b8..bf3600834d 100644 --- a/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php @@ -663,7 +663,7 @@ public function testArrayUdiffCallback(): void 6, ], [ - 'Parameter #3 $data_comp_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(int, int): (literal-string&lowercase-string&non-falsy-string&numeric-string&uppercase-string) given.', + "Parameter #3 \$data_comp_func of function array_udiff expects callable(1|2|3|4|5|6, 1|2|3|4|5|6): int, Closure(int, int): ('11'|'12'|'13'|'14'|'15'|'16'|'21'|'22'|'23'|'24'|'25'|'26'|'31'|'32'|'33'|'34'|'35'|'36'|'41'|'42'|'43'|'44'|'45'|'46'|'51'|'52'|'53'|'54'|'55'|'56'|'61'|'62'|'63'|'64'|'65'|'66') given.", 14, ], [