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: /** @api */
55: class ParametersAcceptorSelector
56: {
57:
58: /**
59: * @template T of ParametersAcceptor
60: * @param T[] $parametersAcceptors
61: * @return T
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: * @param Node\Arg[] $args
82: * @param ParametersAcceptor[] $parametersAcceptors
83: * @param ParametersAcceptor[]|null $namedArgumentsVariants
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: * @param array<int|string, Type> $types
413: * @param ParametersAcceptor[] $parametersAcceptors
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: * @param ParametersAcceptor[] $acceptors
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: // unknown constant
993: return null;
994: }
995:
996: }
997: