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