1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use PHPStan\Reflection\Php\DummyParameterWithPhpDocs;
6: use PHPStan\Type\ConditionalTypeForParameter;
7: use PHPStan\Type\ErrorType;
8: use PHPStan\Type\Generic\TemplateTypeMap;
9: use PHPStan\Type\MixedType;
10: use PHPStan\Type\Type;
11: use PHPStan\Type\TypeCombinator;
12: use function array_key_exists;
13: use function array_map;
14: use function array_merge;
15: use function count;
16: use function is_int;
17:
18: class GenericParametersAcceptorResolver
19: {
20:
21: /**
22: * @api
23: * @param array<int|string, Type> $argTypes
24: */
25: public static function resolve(array $argTypes, ParametersAcceptor $parametersAcceptor): ParametersAcceptorWithPhpDocs
26: {
27: $typeMap = TemplateTypeMap::createEmpty();
28: $passedArgs = [];
29:
30: $parameters = $parametersAcceptor->getParameters();
31: $namedArgTypes = [];
32: foreach ($argTypes as $i => $argType) {
33: if (is_int($i)) {
34: if (isset($parameters[$i])) {
35: $namedArgTypes[$parameters[$i]->getName()] = $argType;
36: continue;
37: }
38: if (count($parameters) > 0) {
39: $lastParameter = $parameters[count($parameters) - 1];
40: if ($lastParameter->isVariadic()) {
41: $parameterName = $lastParameter->getName();
42: if (array_key_exists($parameterName, $namedArgTypes)) {
43: $namedArgTypes[$parameterName] = TypeCombinator::union($namedArgTypes[$parameterName], $argType);
44: continue;
45: }
46: $namedArgTypes[$parameterName] = $argType;
47: }
48: }
49: continue;
50: }
51:
52: $namedArgTypes[$i] = $argType;
53: }
54:
55: foreach ($parametersAcceptor->getParameters() as $param) {
56: if (isset($namedArgTypes[$param->getName()])) {
57: $argType = $namedArgTypes[$param->getName()];
58: } elseif ($param->getDefaultValue() !== null) {
59: $argType = $param->getDefaultValue();
60: } else {
61: continue;
62: }
63:
64: $paramType = $param->getType();
65: $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType));
66: $passedArgs['$' . $param->getName()] = $argType;
67: }
68:
69: $returnType = $parametersAcceptor->getReturnType();
70: if (
71: $returnType instanceof ConditionalTypeForParameter
72: && !$returnType->isNegated()
73: && array_key_exists($returnType->getParameterName(), $passedArgs)
74: ) {
75: $paramType = $returnType->getTarget();
76: $argType = $passedArgs[$returnType->getParameterName()];
77: $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType));
78: }
79:
80: $resolvedTemplateTypeMap = new TemplateTypeMap(array_merge(
81: $parametersAcceptor->getTemplateTypeMap()->map(static fn (string $name, Type $type): Type => new ErrorType())->getTypes(),
82: $typeMap->getTypes(),
83: ));
84:
85: if (!$parametersAcceptor instanceof ParametersAcceptorWithPhpDocs) {
86: $parametersAcceptor = new FunctionVariantWithPhpDocs(
87: $parametersAcceptor->getTemplateTypeMap(),
88: $parametersAcceptor->getResolvedTemplateTypeMap(),
89: array_map(static fn (ParameterReflection $parameter): ParameterReflectionWithPhpDocs => new DummyParameterWithPhpDocs(
90: $parameter->getName(),
91: $parameter->getType(),
92: $parameter->isOptional(),
93: $parameter->passedByReference(),
94: $parameter->isVariadic(),
95: $parameter->getDefaultValue(),
96: new MixedType(),
97: $parameter->getType(),
98: null,
99: ), $parametersAcceptor->getParameters()),
100: $parametersAcceptor->isVariadic(),
101: $parametersAcceptor->getReturnType(),
102: $parametersAcceptor->getReturnType(),
103: new MixedType(),
104: );
105: }
106:
107: return new ResolvedFunctionVariant(
108: $parametersAcceptor,
109: $resolvedTemplateTypeMap,
110: $passedArgs,
111: );
112: }
113:
114: }
115: