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