1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\Reflection; |
4: | |
5: | use Closure; |
6: | use PhpParser\Node; |
7: | use PHPStan\Analyser\ArgumentsNormalizer; |
8: | use PHPStan\Analyser\MutatingScope; |
9: | use PHPStan\Analyser\Scope; |
10: | use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr; |
11: | use PHPStan\Parser\ArrayFilterArgVisitor; |
12: | use PHPStan\Parser\ArrayMapArgVisitor; |
13: | use PHPStan\Parser\ArrayWalkArgVisitor; |
14: | use PHPStan\Parser\ClosureBindArgVisitor; |
15: | use PHPStan\Parser\ClosureBindToVarVisitor; |
16: | use PHPStan\Parser\CurlSetOptArgVisitor; |
17: | use PHPStan\Reflection\Callables\CallableParametersAcceptor; |
18: | use PHPStan\Reflection\Native\NativeParameterReflection; |
19: | use PHPStan\Reflection\Php\DummyParameter; |
20: | use PHPStan\Reflection\Php\ExtendedDummyParameter; |
21: | use PHPStan\ShouldNotHappenException; |
22: | use PHPStan\TrinaryLogic; |
23: | use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; |
24: | use PHPStan\Type\ArrayType; |
25: | use PHPStan\Type\BooleanType; |
26: | use PHPStan\Type\CallableType; |
27: | use PHPStan\Type\Constant\ConstantIntegerType; |
28: | use PHPStan\Type\Generic\TemplateType; |
29: | use PHPStan\Type\Generic\TemplateTypeMap; |
30: | use PHPStan\Type\Generic\TemplateTypeVarianceMap; |
31: | use PHPStan\Type\IntegerType; |
32: | use PHPStan\Type\LateResolvableType; |
33: | use PHPStan\Type\MixedType; |
34: | use PHPStan\Type\NullType; |
35: | use PHPStan\Type\ObjectType; |
36: | use PHPStan\Type\ResourceType; |
37: | use PHPStan\Type\StringType; |
38: | use PHPStan\Type\Type; |
39: | use PHPStan\Type\TypeCombinator; |
40: | use PHPStan\Type\TypeTraverser; |
41: | use PHPStan\Type\UnionType; |
42: | use function array_key_exists; |
43: | use function array_key_last; |
44: | use function array_map; |
45: | use function array_merge; |
46: | use function array_slice; |
47: | use function array_values; |
48: | use function constant; |
49: | use function count; |
50: | use function defined; |
51: | use function is_string; |
52: | use function sprintf; |
53: | use const ARRAY_FILTER_USE_BOTH; |
54: | use const ARRAY_FILTER_USE_KEY; |
55: | use const CURLOPT_SSL_VERIFYHOST; |
56: | |
57: | |
58: | |
59: | |
60: | final class ParametersAcceptorSelector |
61: | { |
62: | |
63: | |
64: | |
65: | |
66: | |
67: | |
68: | public static function selectFromArgs( |
69: | Scope $scope, |
70: | array $args, |
71: | array $parametersAcceptors, |
72: | ?array $namedArgumentsVariants = null, |
73: | ): ParametersAcceptor |
74: | { |
75: | $types = []; |
76: | $unpack = false; |
77: | if ( |
78: | count($args) > 0 |
79: | && count($parametersAcceptors) > 0 |
80: | ) { |
81: | $arrayMapArgs = $args[0]->value->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME); |
82: | if ($arrayMapArgs !== null) { |
83: | $acceptor = $parametersAcceptors[0]; |
84: | $parameters = $acceptor->getParameters(); |
85: | $callbackParameters = []; |
86: | foreach ($arrayMapArgs as $arg) { |
87: | $argType = $scope->getType($arg->value); |
88: | if ($arg->unpack) { |
89: | $constantArrays = $argType->getConstantArrays(); |
90: | if (count($constantArrays) > 0) { |
91: | foreach ($constantArrays as $constantArray) { |
92: | $valueTypes = $constantArray->getValueTypes(); |
93: | foreach ($valueTypes as $valueType) { |
94: | $callbackParameters[] = new DummyParameter('item', $scope->getIterableValueType($valueType), false, PassedByReference::createNo(), false, null); |
95: | } |
96: | } |
97: | } |
98: | } else { |
99: | $callbackParameters[] = new DummyParameter('item', $scope->getIterableValueType($argType), false, PassedByReference::createNo(), false, null); |
100: | } |
101: | } |
102: | $parameters[0] = new NativeParameterReflection( |
103: | $parameters[0]->getName(), |
104: | $parameters[0]->isOptional(), |
105: | new UnionType([ |
106: | new CallableType($callbackParameters, new MixedType(), false), |
107: | new NullType(), |
108: | ]), |
109: | $parameters[0]->passedByReference(), |
110: | $parameters[0]->isVariadic(), |
111: | $parameters[0]->getDefaultValue(), |
112: | ); |
113: | $parametersAcceptors = [ |
114: | new FunctionVariant( |
115: | $acceptor->getTemplateTypeMap(), |
116: | $acceptor->getResolvedTemplateTypeMap(), |
117: | $parameters, |
118: | $acceptor->isVariadic(), |
119: | $acceptor->getReturnType(), |
120: | $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
121: | ), |
122: | ]; |
123: | } |
124: | |
125: | if (count($args) >= 3 && (bool) $args[0]->getAttribute(CurlSetOptArgVisitor::ATTRIBUTE_NAME)) { |
126: | $optType = $scope->getType($args[1]->value); |
127: | if ($optType instanceof ConstantIntegerType) { |
128: | $optValueType = self::getCurlOptValueType($optType->getValue()); |
129: | |
130: | if ($optValueType !== null) { |
131: | $acceptor = $parametersAcceptors[0]; |
132: | $parameters = $acceptor->getParameters(); |
133: | |
134: | $parameters[2] = new NativeParameterReflection( |
135: | $parameters[2]->getName(), |
136: | $parameters[2]->isOptional(), |
137: | $optValueType, |
138: | $parameters[2]->passedByReference(), |
139: | $parameters[2]->isVariadic(), |
140: | $parameters[2]->getDefaultValue(), |
141: | ); |
142: | |
143: | $parametersAcceptors = [ |
144: | new FunctionVariant( |
145: | $acceptor->getTemplateTypeMap(), |
146: | $acceptor->getResolvedTemplateTypeMap(), |
147: | array_values($parameters), |
148: | $acceptor->isVariadic(), |
149: | $acceptor->getReturnType(), |
150: | $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
151: | ), |
152: | ]; |
153: | } |
154: | } |
155: | } |
156: | |
157: | if (isset($args[0]) && (bool) $args[0]->getAttribute(ArrayFilterArgVisitor::ATTRIBUTE_NAME)) { |
158: | if (isset($args[2])) { |
159: | $mode = $scope->getType($args[2]->value); |
160: | if ($mode instanceof ConstantIntegerType) { |
161: | if ($mode->getValue() === ARRAY_FILTER_USE_KEY) { |
162: | $arrayFilterParameters = [ |
163: | new DummyParameter('key', $scope->getIterableKeyType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), |
164: | ]; |
165: | } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) { |
166: | $arrayFilterParameters = [ |
167: | new DummyParameter('item', $scope->getIterableValueType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), |
168: | new DummyParameter('key', $scope->getIterableKeyType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), |
169: | ]; |
170: | } |
171: | } |
172: | } |
173: | |
174: | $acceptor = $parametersAcceptors[0]; |
175: | $parameters = $acceptor->getParameters(); |
176: | $parameters[1] = new NativeParameterReflection( |
177: | $parameters[1]->getName(), |
178: | $parameters[1]->isOptional(), |
179: | new UnionType([ |
180: | new CallableType( |
181: | $arrayFilterParameters ?? [ |
182: | new DummyParameter('item', $scope->getIterableValueType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), |
183: | ], |
184: | new BooleanType(), |
185: | false, |
186: | ), |
187: | new NullType(), |
188: | ]), |
189: | $parameters[1]->passedByReference(), |
190: | $parameters[1]->isVariadic(), |
191: | $parameters[1]->getDefaultValue(), |
192: | ); |
193: | $parametersAcceptors = [ |
194: | new FunctionVariant( |
195: | $acceptor->getTemplateTypeMap(), |
196: | $acceptor->getResolvedTemplateTypeMap(), |
197: | array_values($parameters), |
198: | $acceptor->isVariadic(), |
199: | $acceptor->getReturnType(), |
200: | $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
201: | ), |
202: | ]; |
203: | } |
204: | |
205: | if (isset($args[0]) && (bool) $args[0]->getAttribute(ArrayWalkArgVisitor::ATTRIBUTE_NAME)) { |
206: | $arrayWalkParameters = [ |
207: | new DummyParameter('item', $scope->getIterableValueType($scope->getType($args[0]->value)), false, PassedByReference::createReadsArgument(), false, null), |
208: | new DummyParameter('key', $scope->getIterableKeyType($scope->getType($args[0]->value)), false, PassedByReference::createNo(), false, null), |
209: | ]; |
210: | if (isset($args[2])) { |
211: | $arrayWalkParameters[] = new DummyParameter('arg', $scope->getType($args[2]->value), false, PassedByReference::createNo(), false, null); |
212: | } |
213: | |
214: | $acceptor = $parametersAcceptors[0]; |
215: | $parameters = $acceptor->getParameters(); |
216: | $parameters[1] = new NativeParameterReflection( |
217: | $parameters[1]->getName(), |
218: | $parameters[1]->isOptional(), |
219: | new CallableType($arrayWalkParameters, new MixedType(), false), |
220: | $parameters[1]->passedByReference(), |
221: | $parameters[1]->isVariadic(), |
222: | $parameters[1]->getDefaultValue(), |
223: | ); |
224: | $parametersAcceptors = [ |
225: | new FunctionVariant( |
226: | $acceptor->getTemplateTypeMap(), |
227: | $acceptor->getResolvedTemplateTypeMap(), |
228: | array_values($parameters), |
229: | $acceptor->isVariadic(), |
230: | $acceptor->getReturnType(), |
231: | $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
232: | ), |
233: | ]; |
234: | } |
235: | |
236: | if (isset($args[0])) { |
237: | $closureBindToVar = $args[0]->getAttribute(ClosureBindToVarVisitor::ATTRIBUTE_NAME); |
238: | if ( |
239: | $closureBindToVar !== null |
240: | && $closureBindToVar instanceof Node\Expr\Variable |
241: | && is_string($closureBindToVar->name) |
242: | ) { |
243: | $varType = $scope->getType($closureBindToVar); |
244: | if ((new ObjectType(Closure::class))->isSuperTypeOf($varType)->yes()) { |
245: | $inFunction = $scope->getFunction(); |
246: | if ($inFunction !== null) { |
247: | $closureThisParameters = []; |
248: | foreach ($inFunction->getParameters() as $parameter) { |
249: | if ($parameter->getClosureThisType() === null) { |
250: | continue; |
251: | } |
252: | $closureThisParameters[$parameter->getName()] = $parameter->getClosureThisType(); |
253: | } |
254: | if (array_key_exists($closureBindToVar->name, $closureThisParameters)) { |
255: | if ($scope->hasExpressionType(new ParameterVariableOriginalValueExpr($closureBindToVar->name))->yes()) { |
256: | $acceptor = $parametersAcceptors[0]; |
257: | $parameters = $acceptor->getParameters(); |
258: | $parameters[0] = new NativeParameterReflection( |
259: | $parameters[0]->getName(), |
260: | $parameters[0]->isOptional(), |
261: | $closureThisParameters[$closureBindToVar->name], |
262: | $parameters[0]->passedByReference(), |
263: | $parameters[0]->isVariadic(), |
264: | $parameters[0]->getDefaultValue(), |
265: | ); |
266: | $parametersAcceptors = [ |
267: | new FunctionVariant( |
268: | $acceptor->getTemplateTypeMap(), |
269: | $acceptor->getResolvedTemplateTypeMap(), |
270: | $parameters, |
271: | $acceptor->isVariadic(), |
272: | $acceptor->getReturnType(), |
273: | $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
274: | ), |
275: | ]; |
276: | } |
277: | } |
278: | } |
279: | } |
280: | } |
281: | |
282: | if ( |
283: | $args[0]->getAttribute(ClosureBindArgVisitor::ATTRIBUTE_NAME) !== null |
284: | && $args[0]->value instanceof Node\Expr\Variable |
285: | && is_string($args[0]->value->name) |
286: | ) { |
287: | $closureVarName = $args[0]->value->name; |
288: | $inFunction = $scope->getFunction(); |
289: | if ($inFunction !== null) { |
290: | $closureThisParameters = []; |
291: | foreach ($inFunction->getParameters() as $parameter) { |
292: | if ($parameter->getClosureThisType() === null) { |
293: | continue; |
294: | } |
295: | $closureThisParameters[$parameter->getName()] = $parameter->getClosureThisType(); |
296: | } |
297: | if (array_key_exists($closureVarName, $closureThisParameters)) { |
298: | if ($scope->hasExpressionType(new ParameterVariableOriginalValueExpr($closureVarName))->yes()) { |
299: | $acceptor = $parametersAcceptors[0]; |
300: | $parameters = $acceptor->getParameters(); |
301: | $parameters[1] = new NativeParameterReflection( |
302: | $parameters[1]->getName(), |
303: | $parameters[1]->isOptional(), |
304: | $closureThisParameters[$closureVarName], |
305: | $parameters[1]->passedByReference(), |
306: | $parameters[1]->isVariadic(), |
307: | $parameters[1]->getDefaultValue(), |
308: | ); |
309: | $parametersAcceptors = [ |
310: | new FunctionVariant( |
311: | $acceptor->getTemplateTypeMap(), |
312: | $acceptor->getResolvedTemplateTypeMap(), |
313: | array_values($parameters), |
314: | $acceptor->isVariadic(), |
315: | $acceptor->getReturnType(), |
316: | $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(), |
317: | ), |
318: | ]; |
319: | } |
320: | } |
321: | } |
322: | } |
323: | } |
324: | } |
325: | |
326: | if (count($parametersAcceptors) === 1) { |
327: | $acceptor = $parametersAcceptors[0]; |
328: | if (!self::hasAcceptorTemplateOrLateResolvableType($acceptor)) { |
329: | return $acceptor; |
330: | } |
331: | } |
332: | |
333: | $reorderedArgs = $args; |
334: | $parameters = null; |
335: | $singleParametersAcceptor = null; |
336: | if (count($parametersAcceptors) === 1) { |
337: | $reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptors[0], $args); |
338: | $singleParametersAcceptor = $parametersAcceptors[0]; |
339: | } |
340: | |
341: | $hasName = false; |
342: | foreach ($reorderedArgs ?? $args as $i => $arg) { |
343: | $originalArg = $arg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE) ?? $arg; |
344: | $parameter = null; |
345: | if ($singleParametersAcceptor !== null) { |
346: | $parameters = $singleParametersAcceptor->getParameters(); |
347: | if (isset($parameters[$i])) { |
348: | $parameter = $parameters[$i]; |
349: | } elseif (count($parameters) > 0 && $singleParametersAcceptor->isVariadic()) { |
350: | $parameter = $parameters[count($parameters) - 1]; |
351: | } |
352: | } |
353: | |
354: | if ($parameter !== null && $scope instanceof MutatingScope) { |
355: | $scope = $scope->pushInFunctionCall(null, $parameter); |
356: | } |
357: | |
358: | $type = $scope->getType($originalArg->value); |
359: | |
360: | if ($parameter !== null && $scope instanceof MutatingScope) { |
361: | $scope = $scope->popInFunctionCall(); |
362: | } |
363: | |
364: | if ($originalArg->name !== null) { |
365: | $index = $originalArg->name->toString(); |
366: | $hasName = true; |
367: | } else { |
368: | $index = $i; |
369: | } |
370: | if ($originalArg->unpack) { |
371: | $unpack = true; |
372: | $types[$index] = $type->getIterableValueType(); |
373: | } else { |
374: | $types[$index] = $type; |
375: | } |
376: | } |
377: | |
378: | if ($hasName && $namedArgumentsVariants !== null) { |
379: | return self::selectFromTypes($types, $namedArgumentsVariants, $unpack); |
380: | } |
381: | |
382: | return self::selectFromTypes($types, $parametersAcceptors, $unpack); |
383: | } |
384: | |
385: | private static function hasAcceptorTemplateOrLateResolvableType(ParametersAcceptor $acceptor): bool |
386: | { |
387: | if (self::hasTemplateOrLateResolvableType($acceptor->getReturnType())) { |
388: | return true; |
389: | } |
390: | |
391: | foreach ($acceptor->getParameters() as $parameter) { |
392: | if ( |
393: | $parameter instanceof ExtendedParameterReflection |
394: | && $parameter->getOutType() !== null |
395: | && self::hasTemplateOrLateResolvableType($parameter->getOutType()) |
396: | ) { |
397: | return true; |
398: | } |
399: | |
400: | if ( |
401: | $parameter instanceof ExtendedParameterReflection |
402: | && $parameter->getClosureThisType() !== null |
403: | && self::hasTemplateOrLateResolvableType($parameter->getClosureThisType()) |
404: | ) { |
405: | return true; |
406: | } |
407: | |
408: | if (!self::hasTemplateOrLateResolvableType($parameter->getType())) { |
409: | continue; |
410: | } |
411: | |
412: | return true; |
413: | } |
414: | |
415: | return false; |
416: | } |
417: | |
418: | private static function hasTemplateOrLateResolvableType(Type $type): bool |
419: | { |
420: | $has = false; |
421: | TypeTraverser::map($type, static function (Type $type, callable $traverse) use (&$has): Type { |
422: | if ($type instanceof TemplateType || $type instanceof LateResolvableType) { |
423: | $has = true; |
424: | return $type; |
425: | } |
426: | |
427: | return $traverse($type); |
428: | }); |
429: | |
430: | return $has; |
431: | } |
432: | |
433: | |
434: | |
435: | |
436: | |
437: | public static function selectFromTypes( |
438: | array $types, |
439: | array $parametersAcceptors, |
440: | bool $unpack, |
441: | ): ParametersAcceptor |
442: | { |
443: | if (count($parametersAcceptors) === 1) { |
444: | return GenericParametersAcceptorResolver::resolve($types, $parametersAcceptors[0]); |
445: | } |
446: | |
447: | if (count($parametersAcceptors) === 0) { |
448: | throw new ShouldNotHappenException( |
449: | 'getVariants() must return at least one variant.', |
450: | ); |
451: | } |
452: | |
453: | $typesCount = count($types); |
454: | $acceptableAcceptors = []; |
455: | |
456: | foreach ($parametersAcceptors as $parametersAcceptor) { |
457: | if ($unpack) { |
458: | $acceptableAcceptors[] = $parametersAcceptor; |
459: | continue; |
460: | } |
461: | |
462: | $functionParametersMinCount = 0; |
463: | $functionParametersMaxCount = 0; |
464: | foreach ($parametersAcceptor->getParameters() as $parameter) { |
465: | if (!$parameter->isOptional()) { |
466: | $functionParametersMinCount++; |
467: | } |
468: | |
469: | $functionParametersMaxCount++; |
470: | } |
471: | |
472: | if ($typesCount < $functionParametersMinCount) { |
473: | continue; |
474: | } |
475: | |
476: | if ( |
477: | !$parametersAcceptor->isVariadic() |
478: | && $typesCount > $functionParametersMaxCount |
479: | ) { |
480: | continue; |
481: | } |
482: | |
483: | $acceptableAcceptors[] = $parametersAcceptor; |
484: | } |
485: | |
486: | if (count($acceptableAcceptors) === 0) { |
487: | return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($parametersAcceptors)); |
488: | } |
489: | |
490: | if (count($acceptableAcceptors) === 1) { |
491: | return GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptors[0]); |
492: | } |
493: | |
494: | $winningAcceptors = []; |
495: | $winningCertainty = null; |
496: | foreach ($acceptableAcceptors as $acceptableAcceptor) { |
497: | $isSuperType = TrinaryLogic::createYes(); |
498: | $acceptableAcceptor = GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptor); |
499: | foreach ($acceptableAcceptor->getParameters() as $i => $parameter) { |
500: | if (!isset($types[$i])) { |
501: | if (!$unpack || count($types) <= 0) { |
502: | break; |
503: | } |
504: | |
505: | $type = $types[array_key_last($types)]; |
506: | } else { |
507: | $type = $types[$i]; |
508: | } |
509: | |
510: | if ($parameter->getType() instanceof MixedType) { |
511: | $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe()); |
512: | } else { |
513: | $isSuperType = $isSuperType->and($parameter->getType()->isSuperTypeOf($type)->result); |
514: | } |
515: | } |
516: | |
517: | if ($isSuperType->no()) { |
518: | continue; |
519: | } |
520: | |
521: | if ($winningCertainty === null) { |
522: | $winningAcceptors[] = $acceptableAcceptor; |
523: | $winningCertainty = $isSuperType; |
524: | } else { |
525: | $comparison = $winningCertainty->compareTo($isSuperType); |
526: | if ($comparison === $isSuperType) { |
527: | $winningAcceptors = [$acceptableAcceptor]; |
528: | $winningCertainty = $isSuperType; |
529: | } elseif ($comparison === null) { |
530: | $winningAcceptors[] = $acceptableAcceptor; |
531: | } |
532: | } |
533: | } |
534: | |
535: | if (count($winningAcceptors) === 0) { |
536: | return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($acceptableAcceptors)); |
537: | } |
538: | |
539: | return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($winningAcceptors)); |
540: | } |
541: | |
542: | |
543: | |
544: | |
545: | public static function combineAcceptors(array $acceptors): ExtendedParametersAcceptor |
546: | { |
547: | if (count($acceptors) === 0) { |
548: | throw new ShouldNotHappenException( |
549: | 'getVariants() must return at least one variant.', |
550: | ); |
551: | } |
552: | if (count($acceptors) === 1) { |
553: | return self::wrapAcceptor($acceptors[0]); |
554: | } |
555: | |
556: | $minimumNumberOfParameters = null; |
557: | foreach ($acceptors as $acceptor) { |
558: | $acceptorParametersMinCount = 0; |
559: | foreach ($acceptor->getParameters() as $parameter) { |
560: | if ($parameter->isOptional()) { |
561: | continue; |
562: | } |
563: | |
564: | $acceptorParametersMinCount++; |
565: | } |
566: | |
567: | if ($minimumNumberOfParameters !== null && $minimumNumberOfParameters <= $acceptorParametersMinCount) { |
568: | continue; |
569: | } |
570: | |
571: | $minimumNumberOfParameters = $acceptorParametersMinCount; |
572: | } |
573: | |
574: | $parameters = []; |
575: | $isVariadic = false; |
576: | $returnTypes = []; |
577: | $phpDocReturnTypes = []; |
578: | $nativeReturnTypes = []; |
579: | $callableOccurred = false; |
580: | $throwPoints = []; |
581: | $isPure = TrinaryLogic::createNo(); |
582: | $impurePoints = []; |
583: | $invalidateExpressions = []; |
584: | $usedVariables = []; |
585: | $acceptsNamedArguments = TrinaryLogic::createNo(); |
586: | |
587: | foreach ($acceptors as $acceptor) { |
588: | $returnTypes[] = $acceptor->getReturnType(); |
589: | |
590: | if ($acceptor instanceof ExtendedParametersAcceptor) { |
591: | $phpDocReturnTypes[] = $acceptor->getPhpDocReturnType(); |
592: | $nativeReturnTypes[] = $acceptor->getNativeReturnType(); |
593: | } |
594: | if ($acceptor instanceof CallableParametersAcceptor) { |
595: | $callableOccurred = true; |
596: | $throwPoints = array_merge($throwPoints, $acceptor->getThrowPoints()); |
597: | $isPure = $isPure->or($acceptor->isPure()); |
598: | $impurePoints = array_merge($impurePoints, $acceptor->getImpurePoints()); |
599: | $invalidateExpressions = array_merge($invalidateExpressions, $acceptor->getInvalidateExpressions()); |
600: | $usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables()); |
601: | $acceptsNamedArguments = $acceptsNamedArguments->or($acceptor->acceptsNamedArguments()); |
602: | } |
603: | $isVariadic = $isVariadic || $acceptor->isVariadic(); |
604: | |
605: | foreach ($acceptor->getParameters() as $i => $parameter) { |
606: | if (!isset($parameters[$i])) { |
607: | $parameters[$i] = new ExtendedDummyParameter( |
608: | $parameter->getName(), |
609: | $parameter->getType(), |
610: | $i + 1 > $minimumNumberOfParameters, |
611: | $parameter->passedByReference(), |
612: | $parameter->isVariadic(), |
613: | $parameter->getDefaultValue(), |
614: | $parameter instanceof ExtendedParameterReflection ? $parameter->getNativeType() : new MixedType(), |
615: | $parameter instanceof ExtendedParameterReflection ? $parameter->getPhpDocType() : new MixedType(), |
616: | $parameter instanceof ExtendedParameterReflection ? $parameter->getOutType() : null, |
617: | $parameter instanceof ExtendedParameterReflection ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(), |
618: | $parameter instanceof ExtendedParameterReflection ? $parameter->getClosureThisType() : null, |
619: | ); |
620: | continue; |
621: | } |
622: | |
623: | $isVariadic = $parameters[$i]->isVariadic() || $parameter->isVariadic(); |
624: | $defaultValueLeft = $parameters[$i]->getDefaultValue(); |
625: | $defaultValueRight = $parameter->getDefaultValue(); |
626: | if ($defaultValueLeft !== null && $defaultValueRight !== null) { |
627: | $defaultValue = TypeCombinator::union($defaultValueLeft, $defaultValueRight); |
628: | } else { |
629: | $defaultValue = null; |
630: | } |
631: | |
632: | $type = TypeCombinator::union($parameters[$i]->getType(), $parameter->getType()); |
633: | $nativeType = $parameters[$i]->getNativeType(); |
634: | $phpDocType = $parameters[$i]->getPhpDocType(); |
635: | $outType = $parameters[$i]->getOutType(); |
636: | $immediatelyInvokedCallable = $parameters[$i]->isImmediatelyInvokedCallable(); |
637: | $closureThisType = $parameters[$i]->getClosureThisType(); |
638: | if ($parameter instanceof ExtendedParameterReflection) { |
639: | $nativeType = TypeCombinator::union($nativeType, $parameter->getNativeType()); |
640: | $phpDocType = TypeCombinator::union($phpDocType, $parameter->getPhpDocType()); |
641: | |
642: | if ($parameter->getOutType() !== null) { |
643: | $outType = $outType === null ? null : TypeCombinator::union($outType, $parameter->getOutType()); |
644: | } else { |
645: | $outType = null; |
646: | } |
647: | |
648: | if ($parameter->getClosureThisType() !== null && $closureThisType !== null) { |
649: | $closureThisType = TypeCombinator::union($closureThisType, $parameter->getClosureThisType()); |
650: | } else { |
651: | $closureThisType = null; |
652: | } |
653: | |
654: | $immediatelyInvokedCallable = $parameter->isImmediatelyInvokedCallable()->or($immediatelyInvokedCallable); |
655: | } else { |
656: | $nativeType = new MixedType(); |
657: | $phpDocType = $type; |
658: | $outType = null; |
659: | $immediatelyInvokedCallable = TrinaryLogic::createMaybe(); |
660: | $closureThisType = null; |
661: | } |
662: | |
663: | $parameters[$i] = new ExtendedDummyParameter( |
664: | $parameters[$i]->getName() !== $parameter->getName() ? sprintf('%s|%s', $parameters[$i]->getName(), $parameter->getName()) : $parameter->getName(), |
665: | $type, |
666: | $i + 1 > $minimumNumberOfParameters, |
667: | $parameters[$i]->passedByReference()->combine($parameter->passedByReference()), |
668: | $isVariadic, |
669: | $defaultValue, |
670: | $nativeType, |
671: | $phpDocType, |
672: | $outType, |
673: | $immediatelyInvokedCallable, |
674: | $closureThisType, |
675: | ); |
676: | |
677: | if ($isVariadic) { |
678: | $parameters = array_slice($parameters, 0, $i + 1); |
679: | break; |
680: | } |
681: | } |
682: | } |
683: | |
684: | $returnType = TypeCombinator::union(...$returnTypes); |
685: | $phpDocReturnType = $phpDocReturnTypes === [] ? null : TypeCombinator::union(...$phpDocReturnTypes); |
686: | $nativeReturnType = $nativeReturnTypes === [] ? null : TypeCombinator::union(...$nativeReturnTypes); |
687: | |
688: | if ($callableOccurred) { |
689: | return new ExtendedCallableFunctionVariant( |
690: | TemplateTypeMap::createEmpty(), |
691: | null, |
692: | array_values($parameters), |
693: | $isVariadic, |
694: | $returnType, |
695: | $phpDocReturnType ?? $returnType, |
696: | $nativeReturnType ?? new MixedType(), |
697: | null, |
698: | $throwPoints, |
699: | $isPure, |
700: | $impurePoints, |
701: | $invalidateExpressions, |
702: | $usedVariables, |
703: | $acceptsNamedArguments, |
704: | ); |
705: | } |
706: | |
707: | return new ExtendedFunctionVariant( |
708: | TemplateTypeMap::createEmpty(), |
709: | null, |
710: | array_values($parameters), |
711: | $isVariadic, |
712: | $returnType, |
713: | $phpDocReturnType ?? $returnType, |
714: | $nativeReturnType ?? new MixedType(), |
715: | ); |
716: | } |
717: | |
718: | private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedParametersAcceptor |
719: | { |
720: | if ($acceptor instanceof ExtendedParametersAcceptor) { |
721: | return $acceptor; |
722: | } |
723: | |
724: | if ($acceptor instanceof CallableParametersAcceptor) { |
725: | return new ExtendedCallableFunctionVariant( |
726: | $acceptor->getTemplateTypeMap(), |
727: | $acceptor->getResolvedTemplateTypeMap(), |
728: | array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()), |
729: | $acceptor->isVariadic(), |
730: | $acceptor->getReturnType(), |
731: | $acceptor->getReturnType(), |
732: | new MixedType(), |
733: | TemplateTypeVarianceMap::createEmpty(), |
734: | $acceptor->getThrowPoints(), |
735: | $acceptor->isPure(), |
736: | $acceptor->getImpurePoints(), |
737: | $acceptor->getInvalidateExpressions(), |
738: | $acceptor->getUsedVariables(), |
739: | $acceptor->acceptsNamedArguments(), |
740: | ); |
741: | } |
742: | |
743: | return new ExtendedFunctionVariant( |
744: | $acceptor->getTemplateTypeMap(), |
745: | $acceptor->getResolvedTemplateTypeMap(), |
746: | array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()), |
747: | $acceptor->isVariadic(), |
748: | $acceptor->getReturnType(), |
749: | $acceptor->getReturnType(), |
750: | new MixedType(), |
751: | TemplateTypeVarianceMap::createEmpty(), |
752: | ); |
753: | } |
754: | |
755: | private static function wrapParameter(ParameterReflection $parameter): ExtendedParameterReflection |
756: | { |
757: | return $parameter instanceof ExtendedParameterReflection ? $parameter : new ExtendedDummyParameter( |
758: | $parameter->getName(), |
759: | $parameter->getType(), |
760: | $parameter->isOptional(), |
761: | $parameter->passedByReference(), |
762: | $parameter->isVariadic(), |
763: | $parameter->getDefaultValue(), |
764: | new MixedType(), |
765: | $parameter->getType(), |
766: | null, |
767: | TrinaryLogic::createMaybe(), |
768: | null, |
769: | ); |
770: | } |
771: | |
772: | private static function getCurlOptValueType(int $curlOpt): ?Type |
773: | { |
774: | if (defined('CURLOPT_SSL_VERIFYHOST') && $curlOpt === CURLOPT_SSL_VERIFYHOST) { |
775: | return new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(2)]); |
776: | } |
777: | |
778: | $boolConstants = [ |
779: | 'CURLOPT_AUTOREFERER', |
780: | 'CURLOPT_COOKIESESSION', |
781: | 'CURLOPT_CERTINFO', |
782: | 'CURLOPT_CONNECT_ONLY', |
783: | 'CURLOPT_CRLF', |
784: | 'CURLOPT_DISALLOW_USERNAME_IN_URL', |
785: | 'CURLOPT_DNS_SHUFFLE_ADDRESSES', |
786: | 'CURLOPT_HAPROXYPROTOCOL', |
787: | 'CURLOPT_SSH_COMPRESSION', |
788: | 'CURLOPT_DNS_USE_GLOBAL_CACHE', |
789: | 'CURLOPT_FAILONERROR', |
790: | 'CURLOPT_SSL_FALSESTART', |
791: | 'CURLOPT_FILETIME', |
792: | 'CURLOPT_FOLLOWLOCATION', |
793: | 'CURLOPT_FORBID_REUSE', |
794: | 'CURLOPT_FRESH_CONNECT', |
795: | 'CURLOPT_FTP_USE_EPRT', |
796: | 'CURLOPT_FTP_USE_EPSV', |
797: | 'CURLOPT_FTP_CREATE_MISSING_DIRS', |
798: | 'CURLOPT_FTPAPPEND', |
799: | 'CURLOPT_TCP_NODELAY', |
800: | 'CURLOPT_FTPASCII', |
801: | 'CURLOPT_FTPLISTONLY', |
802: | 'CURLOPT_HEADER', |
803: | 'CURLOPT_HTTP09_ALLOWED', |
804: | 'CURLOPT_HTTPGET', |
805: | 'CURLOPT_HTTPPROXYTUNNEL', |
806: | 'CURLOPT_HTTP_CONTENT_DECODING', |
807: | 'CURLOPT_KEEP_SENDING_ON_ERROR', |
808: | 'CURLOPT_MUTE', |
809: | 'CURLOPT_NETRC', |
810: | 'CURLOPT_NOBODY', |
811: | 'CURLOPT_NOPROGRESS', |
812: | 'CURLOPT_NOSIGNAL', |
813: | 'CURLOPT_PATH_AS_IS', |
814: | 'CURLOPT_PIPEWAIT', |
815: | 'CURLOPT_POST', |
816: | 'CURLOPT_PUT', |
817: | 'CURLOPT_RETURNTRANSFER', |
818: | 'CURLOPT_SASL_IR', |
819: | 'CURLOPT_SSL_ENABLE_ALPN', |
820: | 'CURLOPT_SSL_ENABLE_NPN', |
821: | 'CURLOPT_SSL_VERIFYPEER', |
822: | 'CURLOPT_SSL_VERIFYSTATUS', |
823: | 'CURLOPT_PROXY_SSL_VERIFYPEER', |
824: | 'CURLOPT_SUPPRESS_CONNECT_HEADERS', |
825: | 'CURLOPT_TCP_FASTOPEN', |
826: | 'CURLOPT_TFTP_NO_OPTIONS', |
827: | 'CURLOPT_TRANSFERTEXT', |
828: | 'CURLOPT_UNRESTRICTED_AUTH', |
829: | 'CURLOPT_UPLOAD', |
830: | 'CURLOPT_VERBOSE', |
831: | ]; |
832: | foreach ($boolConstants as $constName) { |
833: | if (defined($constName) && constant($constName) === $curlOpt) { |
834: | return new BooleanType(); |
835: | } |
836: | } |
837: | |
838: | $intConstants = [ |
839: | 'CURLOPT_BUFFERSIZE', |
840: | 'CURLOPT_CONNECTTIMEOUT', |
841: | 'CURLOPT_CONNECTTIMEOUT_MS', |
842: | 'CURLOPT_DNS_CACHE_TIMEOUT', |
843: | 'CURLOPT_EXPECT_100_TIMEOUT_MS', |
844: | 'CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS', |
845: | 'CURLOPT_FTPSSLAUTH', |
846: | 'CURLOPT_HEADEROPT', |
847: | 'CURLOPT_HTTP_VERSION', |
848: | 'CURLOPT_HTTPAUTH', |
849: | 'CURLOPT_INFILESIZE', |
850: | 'CURLOPT_LOW_SPEED_LIMIT', |
851: | 'CURLOPT_LOW_SPEED_TIME', |
852: | 'CURLOPT_MAXCONNECTS', |
853: | 'CURLOPT_MAXREDIRS', |
854: | 'CURLOPT_PORT', |
855: | 'CURLOPT_POSTREDIR', |
856: | 'CURLOPT_PROTOCOLS', |
857: | 'CURLOPT_PROXYAUTH', |
858: | 'CURLOPT_PROXYPORT', |
859: | 'CURLOPT_PROXYTYPE', |
860: | 'CURLOPT_REDIR_PROTOCOLS', |
861: | 'CURLOPT_RESUME_FROM', |
862: | 'CURLOPT_SOCKS5_AUTH', |
863: | 'CURLOPT_SSL_OPTIONS', |
864: | 'CURLOPT_SSL_VERIFYHOST', |
865: | 'CURLOPT_SSLVERSION', |
866: | 'CURLOPT_PROXY_SSL_OPTIONS', |
867: | 'CURLOPT_PROXY_SSL_VERIFYHOST', |
868: | 'CURLOPT_PROXY_SSLVERSION', |
869: | 'CURLOPT_STREAM_WEIGHT', |
870: | 'CURLOPT_TCP_KEEPALIVE', |
871: | 'CURLOPT_TCP_KEEPIDLE', |
872: | 'CURLOPT_TCP_KEEPINTVL', |
873: | 'CURLOPT_TIMECONDITION', |
874: | 'CURLOPT_TIMEOUT', |
875: | 'CURLOPT_TIMEOUT_MS', |
876: | 'CURLOPT_TIMEVALUE', |
877: | 'CURLOPT_TIMEVALUE_LARGE', |
878: | 'CURLOPT_MAX_RECV_SPEED_LARGE', |
879: | 'CURLOPT_SSH_AUTH_TYPES', |
880: | 'CURLOPT_IPRESOLVE', |
881: | 'CURLOPT_FTP_FILEMETHOD', |
882: | ]; |
883: | foreach ($intConstants as $constName) { |
884: | if (defined($constName) && constant($constName) === $curlOpt) { |
885: | return new IntegerType(); |
886: | } |
887: | } |
888: | |
889: | $nonEmptyStringConstants = [ |
890: | 'CURLOPT_ABSTRACT_UNIX_SOCKET', |
891: | 'CURLOPT_CAINFO', |
892: | 'CURLOPT_CAPATH', |
893: | 'CURLOPT_COOKIE', |
894: | 'CURLOPT_COOKIEJAR', |
895: | 'CURLOPT_COOKIELIST', |
896: | 'CURLOPT_CUSTOMREQUEST', |
897: | 'CURLOPT_DEFAULT_PROTOCOL', |
898: | 'CURLOPT_DNS_INTERFACE', |
899: | 'CURLOPT_DNS_LOCAL_IP4', |
900: | 'CURLOPT_DNS_LOCAL_IP6', |
901: | 'CURLOPT_EGDSOCKET', |
902: | 'CURLOPT_FTPPORT', |
903: | 'CURLOPT_INTERFACE', |
904: | 'CURLOPT_KEYPASSWD', |
905: | 'CURLOPT_KRB4LEVEL', |
906: | 'CURLOPT_LOGIN_OPTIONS', |
907: | 'CURLOPT_PINNEDPUBLICKEY', |
908: | 'CURLOPT_PROXY_SERVICE_NAME', |
909: | 'CURLOPT_PROXY_CAINFO', |
910: | 'CURLOPT_PROXY_CAPATH', |
911: | 'CURLOPT_PROXY_CRLFILE', |
912: | 'CURLOPT_PROXY_KEYPASSWD', |
913: | 'CURLOPT_PROXY_PINNEDPUBLICKEY', |
914: | 'CURLOPT_PROXY_SSLCERT', |
915: | 'CURLOPT_PROXY_SSLCERTTYPE', |
916: | 'CURLOPT_PROXY_SSL_CIPHER_LIST', |
917: | 'CURLOPT_PROXY_TLS13_CIPHERS', |
918: | 'CURLOPT_PROXY_SSLKEY', |
919: | 'CURLOPT_PROXY_SSLKEYTYPE', |
920: | 'CURLOPT_PROXY_TLSAUTH_PASSWORD', |
921: | 'CURLOPT_PROXY_TLSAUTH_TYPE', |
922: | 'CURLOPT_PROXY_TLSAUTH_USERNAME', |
923: | 'CURLOPT_PROXYUSERPWD', |
924: | 'CURLOPT_RANDOM_FILE', |
925: | 'CURLOPT_RANGE', |
926: | 'CURLOPT_REFERER', |
927: | 'CURLOPT_SERVICE_NAME', |
928: | 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5', |
929: | 'CURLOPT_SSH_PUBLIC_KEYFILE', |
930: | 'CURLOPT_SSH_PRIVATE_KEYFILE', |
931: | 'CURLOPT_SSL_CIPHER_LIST', |
932: | 'CURLOPT_SSLCERT', |
933: | 'CURLOPT_SSLCERTPASSWD', |
934: | 'CURLOPT_SSLCERTTYPE', |
935: | 'CURLOPT_SSLENGINE', |
936: | 'CURLOPT_SSLENGINE_DEFAULT', |
937: | 'CURLOPT_SSLKEY', |
938: | 'CURLOPT_SSLKEYPASSWD', |
939: | 'CURLOPT_SSLKEYTYPE', |
940: | 'CURLOPT_TLS13_CIPHERS', |
941: | 'CURLOPT_UNIX_SOCKET_PATH', |
942: | 'CURLOPT_URL', |
943: | 'CURLOPT_USERAGENT', |
944: | 'CURLOPT_USERNAME', |
945: | 'CURLOPT_PASSWORD', |
946: | 'CURLOPT_USERPWD', |
947: | 'CURLOPT_XOAUTH2_BEARER', |
948: | ]; |
949: | foreach ($nonEmptyStringConstants as $constName) { |
950: | if (defined($constName) && constant($constName) === $curlOpt) { |
951: | return TypeCombinator::intersect( |
952: | new StringType(), |
953: | new AccessoryNonEmptyStringType(), |
954: | ); |
955: | } |
956: | } |
957: | |
958: | $stringConstants = [ |
959: | 'CURLOPT_COOKIEFILE', |
960: | 'CURLOPT_ENCODING', |
961: | 'CURLOPT_PRE_PROXY', |
962: | 'CURLOPT_PRIVATE', |
963: | 'CURLOPT_PROXY', |
964: | ]; |
965: | foreach ($stringConstants as $constName) { |
966: | if (defined($constName) && constant($constName) === $curlOpt) { |
967: | return new StringType(); |
968: | } |
969: | } |
970: | |
971: | $intArrayStringKeysConstants = [ |
972: | 'CURLOPT_HTTPHEADER', |
973: | ]; |
974: | foreach ($intArrayStringKeysConstants as $constName) { |
975: | if (defined($constName) && constant($constName) === $curlOpt) { |
976: | return new ArrayType(new IntegerType(), new StringType()); |
977: | } |
978: | } |
979: | |
980: | $arrayConstants = [ |
981: | 'CURLOPT_CONNECT_TO', |
982: | 'CURLOPT_HTTP200ALIASES', |
983: | 'CURLOPT_POSTQUOTE', |
984: | 'CURLOPT_PROXYHEADER', |
985: | 'CURLOPT_QUOTE', |
986: | 'CURLOPT_RESOLVE', |
987: | ]; |
988: | foreach ($arrayConstants as $constName) { |
989: | if (defined($constName) && constant($constName) === $curlOpt) { |
990: | return new ArrayType(new MixedType(), new MixedType()); |
991: | } |
992: | } |
993: | |
994: | $arrayOrStringConstants = [ |
995: | 'CURLOPT_POSTFIELDS', |
996: | ]; |
997: | foreach ($arrayOrStringConstants as $constName) { |
998: | if (defined($constName) && constant($constName) === $curlOpt) { |
999: | return new UnionType([ |
1000: | new StringType(), |
1001: | new ArrayType(new MixedType(), new MixedType()), |
1002: | ]); |
1003: | } |
1004: | } |
1005: | |
1006: | $resourceConstants = [ |
1007: | 'CURLOPT_FILE', |
1008: | 'CURLOPT_INFILE', |
1009: | 'CURLOPT_STDERR', |
1010: | 'CURLOPT_WRITEHEADER', |
1011: | ]; |
1012: | foreach ($resourceConstants as $constName) { |
1013: | if (defined($constName) && constant($constName) === $curlOpt) { |
1014: | return new ResourceType(); |
1015: | } |
1016: | } |
1017: | |
1018: | |
1019: | return null; |
1020: | } |
1021: | |
1022: | } |
1023: | |