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