1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use PHPStan\Analyser\OutOfClassScope;
6: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
7: use PHPStan\Reflection\Php\ExtendedDummyParameter;
8: use PHPStan\TrinaryLogic;
9: use PHPStan\Type\CallableAssertionsHelper;
10: use PHPStan\Type\ConditionalTypeForParameter;
11: use PHPStan\Type\ErrorType;
12: use PHPStan\Type\Generic\TemplateTypeHelper;
13: use PHPStan\Type\Generic\TemplateTypeMap;
14: use PHPStan\Type\Generic\TemplateTypeVariance;
15: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
16: use PHPStan\Type\MixedType;
17: use PHPStan\Type\NeverType;
18: use PHPStan\Type\Type;
19: use PHPStan\Type\TypeCombinator;
20: use PHPStan\Type\UnionType;
21: use function array_key_exists;
22: use function array_last;
23: use function array_map;
24: use function array_merge;
25: use function count;
26: use function is_int;
27:
28: final class GenericParametersAcceptorResolver
29: {
30:
31: /**
32: * @api
33: * @param array<int|string, Type> $argTypes
34: */
35: public static function resolve(array $argTypes, ParametersAcceptor $parametersAcceptor): ExtendedParametersAcceptor
36: {
37: $typeMap = TemplateTypeMap::createEmpty();
38: $passedArgs = [];
39:
40: $parameters = $parametersAcceptor->getParameters();
41: $namedArgTypes = [];
42: foreach ($argTypes as $i => $argType) {
43: if (is_int($i)) {
44: if (isset($parameters[$i])) {
45: $namedArgTypes[$parameters[$i]->getName()] = $argType;
46: continue;
47: }
48: if (count($parameters) > 0) {
49: $lastParameter = array_last($parameters);
50: if ($lastParameter->isVariadic()) {
51: $parameterName = $lastParameter->getName();
52: if (array_key_exists($parameterName, $namedArgTypes)) {
53: $namedArgTypes[$parameterName] = TypeCombinator::union($namedArgTypes[$parameterName], $argType);
54: continue;
55: }
56: $namedArgTypes[$parameterName] = $argType;
57: }
58: }
59: continue;
60: }
61:
62: $namedArgTypes[$i] = $argType;
63: }
64:
65: // type predicates of passed callables determine their template types exactly,
66: // so they are inferred first and substituted into the parameter types —
67: // the remaining template types are then inferred from what is left over,
68: // e.g. in partition(iterable<T0|T1> $values, callable(T0|T1): ($value is T0 ? true : false) $predicate)
69: // called with iterable<int|string> and is_int(...), T0 becomes int and T1 string
70: $predicateTypeMap = TemplateTypeMap::createEmpty();
71: foreach ($parameters as $param) {
72: if (!isset($namedArgTypes[$param->getName()])) {
73: continue;
74: }
75:
76: $predicateTypeMap = $predicateTypeMap->union(
77: self::inferPredicateTemplateTypes($param->getType(), $namedArgTypes[$param->getName()]),
78: );
79: }
80:
81: foreach ($parameters as $param) {
82: if (isset($namedArgTypes[$param->getName()])) {
83: $argType = $namedArgTypes[$param->getName()];
84: } elseif ($param->getDefaultValue() !== null) {
85: $argType = $param->getDefaultValue();
86: } elseif ($param->isVariadic()) {
87: $argType = new NeverType(true);
88: } else {
89: continue;
90: }
91:
92: $paramType = self::resolvePredicateTemplateTypes($param->getType(), $predicateTypeMap);
93: $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType));
94: $passedArgs['$' . $param->getName()] = $argType;
95: }
96:
97: $typeMap = $typeMap->union($predicateTypeMap);
98:
99: $returnType = $parametersAcceptor->getReturnType();
100: if (
101: $returnType instanceof ConditionalTypeForParameter
102: && !$returnType->isNegated()
103: && array_key_exists($returnType->getParameterName(), $passedArgs)
104: ) {
105: $paramType = self::resolvePredicateTemplateTypes($returnType->getTarget(), $predicateTypeMap);
106: $argType = $passedArgs[$returnType->getParameterName()];
107: $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType));
108: }
109:
110: $resolvedTemplateTypeMap = new TemplateTypeMap(array_merge(
111: $parametersAcceptor->getTemplateTypeMap()->map(static fn (string $name, Type $type): Type => new ErrorType())->getTypes(),
112: $typeMap->getTypes(),
113: ));
114:
115: $originalParametersAcceptor = $parametersAcceptor;
116:
117: if (!$parametersAcceptor instanceof ExtendedParametersAcceptor) {
118: $parametersAcceptor = new ExtendedFunctionVariant(
119: $parametersAcceptor->getTemplateTypeMap(),
120: $parametersAcceptor->getResolvedTemplateTypeMap(),
121: array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter(
122: $parameter->getName(),
123: $parameter->getType(),
124: $parameter->isOptional(),
125: $parameter->passedByReference(),
126: $parameter->isVariadic(),
127: $parameter->getDefaultValue(),
128: new MixedType(),
129: $parameter->getType(),
130: null,
131: TrinaryLogic::createMaybe(),
132: null,
133: [],
134: null,
135: TrinaryLogic::createNo(),
136: ), $parameters),
137: $parametersAcceptor->isVariadic(),
138: $returnType,
139: $returnType,
140: new MixedType(),
141: TemplateTypeVarianceMap::createEmpty(),
142: );
143: }
144:
145: $result = new ResolvedFunctionVariantWithOriginal(
146: $parametersAcceptor,
147: $resolvedTemplateTypeMap,
148: $parametersAcceptor->getCallSiteVarianceMap(),
149: $passedArgs,
150: );
151: if ($originalParametersAcceptor instanceof CallableParametersAcceptor) {
152: return new ResolvedFunctionVariantWithCallable(
153: $result,
154: $originalParametersAcceptor->getThrowPoints(),
155: $originalParametersAcceptor->isPure(),
156: $originalParametersAcceptor->getImpurePoints(),
157: $originalParametersAcceptor->getInvalidateExpressions(),
158: $originalParametersAcceptor->getUsedVariables(),
159: $originalParametersAcceptor->acceptsNamedArguments(),
160: $originalParametersAcceptor->mustUseReturnValue(),
161: $originalParametersAcceptor->getAsserts(),
162: $originalParametersAcceptor->isStaticClosure(),
163: );
164: }
165:
166: return $result;
167: }
168:
169: private static function inferPredicateTemplateTypes(Type $paramType, Type $argType): TemplateTypeMap
170: {
171: $typeMap = TemplateTypeMap::createEmpty();
172: if (!$argType->isCallable()->yes()) {
173: return $typeMap;
174: }
175:
176: foreach ($paramType instanceof UnionType ? $paramType->getTypes() : [$paramType] as $innerType) {
177: if (!$innerType instanceof CallableParametersAcceptor) {
178: continue;
179: }
180: if ($innerType->getAsserts()->getAll() === []) {
181: continue;
182: }
183:
184: foreach ($argType->getCallableParametersAcceptors(new OutOfClassScope()) as $receivedAcceptor) {
185: $typeMap = $typeMap->union(CallableAssertionsHelper::inferTemplateTypesOnAsserts($innerType, $receivedAcceptor));
186: }
187: }
188:
189: return $typeMap;
190: }
191:
192: private static function resolvePredicateTemplateTypes(Type $type, TemplateTypeMap $predicateTypeMap): Type
193: {
194: if ($predicateTypeMap->isEmpty()) {
195: return $type;
196: }
197:
198: return TemplateTypeHelper::resolveTemplateTypes(
199: $type,
200: $predicateTypeMap,
201: TemplateTypeVarianceMap::createEmpty(),
202: TemplateTypeVariance::createInvariant(),
203: );
204: }
205:
206: }
207: