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