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