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