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