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