55namespace Webmozart \Assert \Bin ;
66
77use ArrayAccess ;
8- use Closure ;
98use Countable ;
109use ReflectionClass ;
1110use ReflectionException ;
11+ use ReflectionIntersectionType ;
1212use ReflectionMethod ;
13+ use ReflectionType ;
14+ use ReflectionUnionType ;
1315use RuntimeException ;
14- use Throwable ;
1516use Webmozart \Assert \Assert ;
17+
18+ use function array_map ;
19+ use function implode ;
1620use function rtrim ;
1721
1822final class MixinGenerator
@@ -56,6 +60,8 @@ public function generate(): string
5660 <<<'PHP'
5761<?php
5862
63+ declare(strict_types=1);
64+
5965%s
6066PHP
6167 ,
@@ -69,9 +75,7 @@ private function namespace(): string
6975
7076 $ namespace = sprintf ("namespace %s; \n\n" , $ assert ->getNamespaceName ());
7177 $ namespace .= sprintf ("use %s; \n" , ArrayAccess::class);
72- $ namespace .= sprintf ("use %s; \n" , Closure::class);
7378 $ namespace .= sprintf ("use %s; \n" , Countable::class);
74- $ namespace .= sprintf ("use %s; \n" , Throwable::class);
7579 $ namespace .= "\n" ;
7680
7781 $ namespace .= $ this ->trait ($ assert );
@@ -200,6 +204,8 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
200204 $ parameters = [];
201205 /** @psalm-var array<string, scalar|null> $parametersDefaults */
202206 $ parametersDefaults = [];
207+ /** @var array<string, string> $parameterTypes */
208+ $ parameterTypes = [];
203209 $ parametersReflection = $ method ->getParameters ();
204210
205211 foreach ($ parametersReflection as $ parameterReflection ) {
@@ -212,6 +218,18 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
212218
213219 $ parametersDefaults [$ parameterReflection ->name ] = $ defaultValue ;
214220 }
221+
222+ if ($ parameterReflection ->hasType ()) {
223+ if ($ parameterReflection ->name === 'value ' && $ typeTemplate ) {
224+ $ parameterTypes [$ parameterReflection ->name ] = match ($ typeTemplate ) {
225+ '%s|null ' => $ this ->reduceParameterType ($ parameterReflection ->getType ()),
226+ 'iterable<%s> ' => 'iterable ' ,
227+ 'iterable<%s|null> ' => '?iterable ' ,
228+ };
229+ } else {
230+ $ parameterTypes [$ parameterReflection ->name ] = $ this ->reduceParameterType ($ parameterReflection ->getType ());
231+ }
232+ }
215233 }
216234
217235 if (in_array ($ newMethodName , $ this ->skipMethods , true )) {
@@ -278,7 +296,7 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
278296 $ phpdocLines [] = trim ($ comment );
279297 }
280298
281- if ('deprecated ' === $ key || 'psalm-pure ' === $ key ) {
299+ if ('deprecated ' === $ key || 'psalm-pure ' === $ key || ' psalm-assert ' === $ key || ' see ' === $ key ) {
282300 $ phpdocLines [] = '' ;
283301 }
284302 }
@@ -296,7 +314,24 @@ private function assertion(ReflectionMethod $method, string $methodNameTemplate,
296314 $ phpdocLinesDeduplicatedEmptyLines [] = $ line ;
297315 }
298316
299- return $ this ->staticMethod ($ newMethodName , $ parameters , $ parametersDefaults , $ phpdocLinesDeduplicatedEmptyLines , $ indent , $ body );
317+ return $ this ->staticMethod ($ newMethodName , $ parameters , $ parameterTypes , $ parametersDefaults , $ phpdocLinesDeduplicatedEmptyLines , $ indent , $ body );
318+ }
319+
320+ private function reduceParameterType (ReflectionType $ type ): string
321+ {
322+ if ($ type instanceof ReflectionIntersectionType) {
323+ return implode ('& ' , array_map ([$ this , 'reduceParameterType ' ], $ type ->getTypes ()));
324+ }
325+
326+ if ($ type instanceof ReflectionUnionType) {
327+ return implode ('| ' , array_map ([$ this , 'reduceParameterType ' ], $ type ->getTypes ()));
328+ }
329+
330+ if ($ type ->getName () === 'mixed ' ) {
331+ return $ type ->getName ();
332+ }
333+
334+ return ($ type ->allowsNull () ? '? ' : '' ) . $ type ->getName ();
300335 }
301336
302337 private function applyTypeTemplate (string $ type , string $ typeTemplate ): string
@@ -353,26 +388,27 @@ private function findLongestTypeAndName(array $values): array
353388 }
354389
355390 /**
356- * @psalm-param list<string> $parameters
357- * @psalm-param array<string, scalar|null> $defaultValues
358- * @psalm-param list<string> $phpdocLines
391+ * @psalm-param list<string> $parameters
392+ * @psalm-param array<string, scalar|null> $defaults
393+ * @psalm-param list<string> $phpdocLines
359394 * @psalm-param callable(string,string):string $body
360395 *
361- * @param string $name
362- * @param string[] $parameters
363- * @param string[] $defaultValues
364- * @param array $phpdocLines
365- * @param int $indent
366- * @param callable $body
396+ * @param string $name
397+ * @param string[] $parameters
398+ * @param array<string, string> $types
399+ * @param string[] $defaults
400+ * @param array $phpdocLines
401+ * @param int $indent
402+ * @param callable $body
367403 *
368404 * @return string
369405 */
370- private function staticMethod (string $ name , array $ parameters , array $ defaultValues , array $ phpdocLines , int $ indent , callable $ body ): string
406+ private function staticMethod (string $ name , array $ parameters , array $ types , array $ defaults , array $ phpdocLines , int $ indent , callable $ body ): string
371407 {
372408 $ indentation = str_repeat (' ' , $ indent );
373409
374410 $ staticFunction = $ this ->phpdoc ($ phpdocLines , $ indent )."\n" ;
375- $ staticFunction .= $ indentation .'public static function ' .$ name .$ this ->functionParameters ($ parameters , $ defaultValues )."\n"
411+ $ staticFunction .= $ indentation .'public static function ' .$ name .$ this ->functionParameters ($ parameters , $ types , $ defaults ).": void \n"
376412 .$ indentation ."{ \n" ;
377413
378414 $ firstParameter = '$ ' .array_shift ($ parameters );
@@ -387,15 +423,16 @@ private function staticMethod(string $name, array $parameters, array $defaultVal
387423 }
388424
389425 /**
390- * @psalm-param list<string> $parameters
391- * @psalm-param array<string, scalar|null> $defaultValues
426+ * @psalm-param list<string> $parameters
427+ * @psalm-param array<string, scalar|null> $defaults
392428 *
393- * @param string[] $parameters
394- * @param string[] $defaultValues
429+ * @param string[] $parameters
430+ * @param array<string, string> $types
431+ * @param string[] $defaults
395432 *
396433 * @return string
397434 */
398- private function functionParameters (array $ parameters , array $ defaultValues ): string
435+ private function functionParameters (array $ parameters , array $ types , array $ defaults ): string
399436 {
400437 $ result = '' ;
401438
@@ -404,10 +441,12 @@ private function functionParameters(array $parameters, array $defaultValues): st
404441 $ result .= ', ' ;
405442 }
406443
407- $ result .= '$ ' .$ parameter ;
444+ Assert::keyExists ($ types , $ parameter );
445+
446+ $ result .= $ types [$ parameter ].' $ ' .$ parameter ;
408447
409- if (array_key_exists ($ parameter , $ defaultValues )) {
410- $ defaultValue = null === $ defaultValues [$ parameter ] ? 'null ' : var_export ($ defaultValues [$ parameter ], true );
448+ if (array_key_exists ($ parameter , $ defaults )) {
449+ $ defaultValue = null === $ defaults [$ parameter ] ? 'null ' : var_export ($ defaults [$ parameter ], true );
411450
412451 $ result .= ' = ' .$ defaultValue ;
413452 }
0 commit comments