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: | |
26: | |
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: | |