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