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: $isStaticClosure = TrinaryLogic::createMaybe();
734:
735: foreach ($acceptors as $acceptor) {
736: $returnTypes[] = $acceptor->getReturnType();
737:
738: if ($acceptor instanceof ExtendedParametersAcceptor) {
739: $phpDocReturnTypes[] = $acceptor->getPhpDocReturnType();
740: $nativeReturnTypes[] = $acceptor->getNativeReturnType();
741: }
742: if ($acceptor instanceof CallableParametersAcceptor) {
743: $callableOccurred = true;
744: $throwPoints = array_merge($throwPoints, $acceptor->getThrowPoints());
745: $isPure = $isPure->or($acceptor->isPure());
746: $impurePoints = array_merge($impurePoints, $acceptor->getImpurePoints());
747: $invalidateExpressions = array_merge($invalidateExpressions, $acceptor->getInvalidateExpressions());
748: $usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables());
749: $acceptsNamedArguments = $acceptsNamedArguments->or($acceptor->acceptsNamedArguments());
750: $mustUseReturnValue = $mustUseReturnValue->or($acceptor->mustUseReturnValue());
751: $isStaticClosure = $isStaticClosure->or($acceptor->isStaticClosure());
752: }
753: $isVariadic = $isVariadic || $acceptor->isVariadic();
754:
755: foreach ($acceptor->getParameters() as $i => $parameter) {
756: if (!isset($parameters[$i])) {
757: $parameters[$i] = new ExtendedDummyParameter(
758: $parameter->getName(),
759: $parameter->getType(),
760: $i + 1 > $minimumNumberOfParameters,
761: $parameter->passedByReference(),
762: $parameter->isVariadic(),
763: $parameter->getDefaultValue(),
764: $parameter instanceof ExtendedParameterReflection ? $parameter->getNativeType() : new MixedType(),
765: $parameter instanceof ExtendedParameterReflection ? $parameter->getPhpDocType() : new MixedType(),
766: $parameter instanceof ExtendedParameterReflection ? $parameter->getOutType() : null,
767: $parameter instanceof ExtendedParameterReflection ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(),
768: $parameter instanceof ExtendedParameterReflection ? $parameter->getClosureThisType() : null,
769: $parameter instanceof ExtendedParameterReflection ? $parameter->getAttributes() : [],
770: $parameter instanceof ExtendedParameterReflection ? $parameter->getAllowedConstants() : null,
771: );
772: continue;
773: }
774:
775: $isVariadic = $parameters[$i]->isVariadic() || $parameter->isVariadic();
776: $defaultValueLeft = $parameters[$i]->getDefaultValue();
777: $defaultValueRight = $parameter->getDefaultValue();
778: if ($defaultValueLeft !== null && $defaultValueRight !== null) {
779: $defaultValue = TypeCombinator::union($defaultValueLeft, $defaultValueRight);
780: } else {
781: $defaultValue = null;
782: }
783:
784: $type = TypeCombinator::union($parameters[$i]->getType(), $parameter->getType());
785: $nativeType = $parameters[$i]->getNativeType();
786: $phpDocType = $parameters[$i]->getPhpDocType();
787: $outType = $parameters[$i]->getOutType();
788: $immediatelyInvokedCallable = $parameters[$i]->isImmediatelyInvokedCallable();
789: $closureThisType = $parameters[$i]->getClosureThisType();
790: $attributes = $parameters[$i]->getAttributes();
791: if ($parameter instanceof ExtendedParameterReflection) {
792: $nativeType = TypeCombinator::union($nativeType, $parameter->getNativeType());
793: $phpDocType = TypeCombinator::union($phpDocType, $parameter->getPhpDocType());
794:
795: if ($parameter->getOutType() !== null) {
796: $outType = $outType === null ? null : TypeCombinator::union($outType, $parameter->getOutType());
797: } else {
798: $outType = null;
799: }
800:
801: if ($parameter->getClosureThisType() !== null && $closureThisType !== null) {
802: $closureThisType = TypeCombinator::union($closureThisType, $parameter->getClosureThisType());
803: } else {
804: $closureThisType = null;
805: }
806:
807: $immediatelyInvokedCallable = $parameter->isImmediatelyInvokedCallable()->or($immediatelyInvokedCallable);
808: $attributes = array_merge($attributes, $parameter->getAttributes());
809: } else {
810: $nativeType = new MixedType();
811: $phpDocType = $type;
812: $outType = null;
813: $immediatelyInvokedCallable = TrinaryLogic::createMaybe();
814: $closureThisType = null;
815: }
816:
817: $allowedConstants = $parameters[$i]->getAllowedConstants();
818: if ($allowedConstants !== null) {
819: $otherAllowedConstants = $parameter instanceof ExtendedParameterReflection ? $parameter->getAllowedConstants() : null;
820: if ($otherAllowedConstants === null || !$allowedConstants->equals($otherAllowedConstants)) {
821: $allowedConstants = null;
822: }
823: }
824:
825: $parameters[$i] = new ExtendedDummyParameter(
826: $parameters[$i]->getName() !== $parameter->getName() ? sprintf('%s|%s', $parameters[$i]->getName(), $parameter->getName()) : $parameter->getName(),
827: $type,
828: $i + 1 > $minimumNumberOfParameters,
829: $parameters[$i]->passedByReference()->combine($parameter->passedByReference()),
830: $isVariadic,
831: $defaultValue,
832: $nativeType,
833: $phpDocType,
834: $outType,
835: $immediatelyInvokedCallable,
836: $closureThisType,
837: $attributes,
838: $allowedConstants,
839: );
840:
841: if ($isVariadic) {
842: $parameters = array_slice($parameters, 0, $i + 1);
843: break;
844: }
845: }
846: }
847:
848: $returnType = TypeCombinator::union(...$returnTypes);
849: $phpDocReturnType = $phpDocReturnTypes === [] ? null : TypeCombinator::union(...$phpDocReturnTypes);
850: $nativeReturnType = $nativeReturnTypes === [] ? null : TypeCombinator::union(...$nativeReturnTypes);
851:
852: if ($callableOccurred) {
853: return new ExtendedCallableFunctionVariant(
854: TemplateTypeMap::createEmpty(),
855: null,
856: array_values($parameters),
857: $isVariadic,
858: $returnType,
859: $phpDocReturnType ?? $returnType,
860: $nativeReturnType ?? new MixedType(),
861: null,
862: $throwPoints,
863: $isPure,
864: $impurePoints,
865: $invalidateExpressions,
866: $usedVariables,
867: $acceptsNamedArguments,
868: $mustUseReturnValue,
869: isStatic: $isStaticClosure,
870: );
871: }
872:
873: return new ExtendedFunctionVariant(
874: TemplateTypeMap::createEmpty(),
875: null,
876: array_values($parameters),
877: $isVariadic,
878: $returnType,
879: $phpDocReturnType ?? $returnType,
880: $nativeReturnType ?? new MixedType(),
881: );
882: }
883:
884: private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedParametersAcceptor
885: {
886: if ($acceptor instanceof ExtendedParametersAcceptor) {
887: return $acceptor;
888: }
889:
890: if ($acceptor instanceof CallableParametersAcceptor) {
891: return new ExtendedCallableFunctionVariant(
892: $acceptor->getTemplateTypeMap(),
893: $acceptor->getResolvedTemplateTypeMap(),
894: array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()),
895: $acceptor->isVariadic(),
896: $acceptor->getReturnType(),
897: $acceptor->getReturnType(),
898: new MixedType(),
899: TemplateTypeVarianceMap::createEmpty(),
900: $acceptor->getThrowPoints(),
901: $acceptor->isPure(),
902: $acceptor->getImpurePoints(),
903: $acceptor->getInvalidateExpressions(),
904: $acceptor->getUsedVariables(),
905: $acceptor->acceptsNamedArguments(),
906: $acceptor->mustUseReturnValue(),
907: $acceptor->getAsserts(),
908: $acceptor->isStaticClosure(),
909: );
910: }
911:
912: return new ExtendedFunctionVariant(
913: $acceptor->getTemplateTypeMap(),
914: $acceptor->getResolvedTemplateTypeMap(),
915: array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()),
916: $acceptor->isVariadic(),
917: $acceptor->getReturnType(),
918: $acceptor->getReturnType(),
919: new MixedType(),
920: TemplateTypeVarianceMap::createEmpty(),
921: );
922: }
923:
924: private static function wrapParameter(ParameterReflection $parameter): ExtendedParameterReflection
925: {
926: return $parameter instanceof ExtendedParameterReflection ? $parameter : new ExtendedDummyParameter(
927: $parameter->getName(),
928: $parameter->getType(),
929: $parameter->isOptional(),
930: $parameter->passedByReference(),
931: $parameter->isVariadic(),
932: $parameter->getDefaultValue(),
933: new MixedType(),
934: $parameter->getType(),
935: null,
936: TrinaryLogic::createMaybe(),
937: null,
938: [],
939: null,
940: );
941: }
942:
943: private static function getCurlOptValueType(int $curlOpt): ?Type
944: {
945: if (defined('CURLOPT_SSL_VERIFYHOST') && $curlOpt === CURLOPT_SSL_VERIFYHOST) {
946: return new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(2)]);
947: }
948:
949: $boolConstants = [
950: 'CURLOPT_AUTOREFERER',
951: 'CURLOPT_COOKIESESSION',
952: 'CURLOPT_CERTINFO',
953: 'CURLOPT_CONNECT_ONLY',
954: 'CURLOPT_CRLF',
955: 'CURLOPT_DISALLOW_USERNAME_IN_URL',
956: 'CURLOPT_DNS_SHUFFLE_ADDRESSES',
957: 'CURLOPT_HAPROXYPROTOCOL',
958: 'CURLOPT_SSH_COMPRESSION',
959: 'CURLOPT_DNS_USE_GLOBAL_CACHE',
960: 'CURLOPT_FAILONERROR',
961: 'CURLOPT_SSL_FALSESTART',
962: 'CURLOPT_FILETIME',
963: 'CURLOPT_FOLLOWLOCATION',
964: 'CURLOPT_FORBID_REUSE',
965: 'CURLOPT_FRESH_CONNECT',
966: 'CURLOPT_FTP_USE_EPRT',
967: 'CURLOPT_FTP_USE_EPSV',
968: 'CURLOPT_FTP_CREATE_MISSING_DIRS',
969: 'CURLOPT_FTPAPPEND',
970: 'CURLOPT_TCP_NODELAY',
971: 'CURLOPT_FTPASCII',
972: 'CURLOPT_FTPLISTONLY',
973: 'CURLOPT_HEADER',
974: 'CURLOPT_HTTP09_ALLOWED',
975: 'CURLOPT_HTTPGET',
976: 'CURLOPT_HTTPPROXYTUNNEL',
977: 'CURLOPT_HTTP_CONTENT_DECODING',
978: 'CURLOPT_KEEP_SENDING_ON_ERROR',
979: 'CURLOPT_MUTE',
980: 'CURLOPT_NETRC',
981: 'CURLOPT_NOBODY',
982: 'CURLOPT_NOPROGRESS',
983: 'CURLOPT_NOSIGNAL',
984: 'CURLOPT_PATH_AS_IS',
985: 'CURLOPT_PIPEWAIT',
986: 'CURLOPT_POST',
987: 'CURLOPT_PUT',
988: 'CURLOPT_RETURNTRANSFER',
989: 'CURLOPT_SASL_IR',
990: 'CURLOPT_SSL_ENABLE_ALPN',
991: 'CURLOPT_SSL_ENABLE_NPN',
992: 'CURLOPT_SSL_VERIFYPEER',
993: 'CURLOPT_SSL_VERIFYSTATUS',
994: 'CURLOPT_PROXY_SSL_VERIFYPEER',
995: 'CURLOPT_SUPPRESS_CONNECT_HEADERS',
996: 'CURLOPT_TCP_FASTOPEN',
997: 'CURLOPT_TFTP_NO_OPTIONS',
998: 'CURLOPT_TRANSFERTEXT',
999: 'CURLOPT_UNRESTRICTED_AUTH',
1000: 'CURLOPT_UPLOAD',
1001: 'CURLOPT_VERBOSE',
1002: ];
1003: foreach ($boolConstants as $constName) {
1004: if (defined($constName) && constant($constName) === $curlOpt) {
1005: return new BooleanType();
1006: }
1007: }
1008:
1009: $intConstants = [
1010: 'CURLOPT_BUFFERSIZE',
1011: 'CURLOPT_CONNECTTIMEOUT',
1012: 'CURLOPT_CONNECTTIMEOUT_MS',
1013: 'CURLOPT_DNS_CACHE_TIMEOUT',
1014: 'CURLOPT_EXPECT_100_TIMEOUT_MS',
1015: 'CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS',
1016: 'CURLOPT_FTPSSLAUTH',
1017: 'CURLOPT_HEADEROPT',
1018: 'CURLOPT_HTTP_VERSION',
1019: 'CURLOPT_HTTPAUTH',
1020: 'CURLOPT_INFILESIZE',
1021: 'CURLOPT_LOW_SPEED_LIMIT',
1022: 'CURLOPT_LOW_SPEED_TIME',
1023: 'CURLOPT_MAXCONNECTS',
1024: 'CURLOPT_MAXREDIRS',
1025: 'CURLOPT_PORT',
1026: 'CURLOPT_POSTREDIR',
1027: 'CURLOPT_PROTOCOLS',
1028: 'CURLOPT_PROXYAUTH',
1029: 'CURLOPT_PROXYPORT',
1030: 'CURLOPT_PROXYTYPE',
1031: 'CURLOPT_REDIR_PROTOCOLS',
1032: 'CURLOPT_RESUME_FROM',
1033: 'CURLOPT_SOCKS5_AUTH',
1034: 'CURLOPT_SSL_OPTIONS',
1035: 'CURLOPT_SSL_VERIFYHOST',
1036: 'CURLOPT_SSLVERSION',
1037: 'CURLOPT_PROXY_SSL_OPTIONS',
1038: 'CURLOPT_PROXY_SSL_VERIFYHOST',
1039: 'CURLOPT_PROXY_SSLVERSION',
1040: 'CURLOPT_STREAM_WEIGHT',
1041: 'CURLOPT_TCP_KEEPALIVE',
1042: 'CURLOPT_TCP_KEEPIDLE',
1043: 'CURLOPT_TCP_KEEPINTVL',
1044: 'CURLOPT_TIMECONDITION',
1045: 'CURLOPT_TIMEOUT',
1046: 'CURLOPT_TIMEOUT_MS',
1047: 'CURLOPT_TIMEVALUE',
1048: 'CURLOPT_TIMEVALUE_LARGE',
1049: 'CURLOPT_MAX_RECV_SPEED_LARGE',
1050: 'CURLOPT_SSH_AUTH_TYPES',
1051: 'CURLOPT_IPRESOLVE',
1052: 'CURLOPT_FTP_FILEMETHOD',
1053: ];
1054: foreach ($intConstants as $constName) {
1055: if (defined($constName) && constant($constName) === $curlOpt) {
1056: return new IntegerType();
1057: }
1058: }
1059:
1060: $nullableStringConstants = [
1061: 'CURLOPT_CUSTOMREQUEST',
1062: 'CURLOPT_DNS_INTERFACE',
1063: 'CURLOPT_DNS_LOCAL_IP4',
1064: 'CURLOPT_DNS_LOCAL_IP6',
1065: 'CURLOPT_DOH_URL',
1066: 'CURLOPT_FTP_ACCOUNT',
1067: 'CURLOPT_FTPPORT',
1068: 'CURLOPT_HSTS',
1069: 'CURLOPT_KRBLEVEL',
1070: 'CURLOPT_RANGE',
1071: 'CURLOPT_RTSP_SESSION_ID',
1072: 'CURLOPT_UNIX_SOCKET_PATH',
1073: 'CURLOPT_XOAUTH2_BEARER',
1074: ];
1075: foreach ($nullableStringConstants as $constName) {
1076: if (defined($constName) && constant($constName) === $curlOpt) {
1077: return new UnionType([
1078: new NullType(),
1079: new IntersectionType([
1080: new StringType(),
1081: new AccessoryNonEmptyStringType(),
1082: ]),
1083: ]);
1084: }
1085: }
1086:
1087: $nonEmptyStringConstants = [
1088: 'CURLOPT_ABSTRACT_UNIX_SOCKET',
1089: 'CURLOPT_ALTSVC',
1090: 'CURLOPT_AWS_SIGV4',
1091: 'CURLOPT_CAINFO',
1092: 'CURLOPT_CAPATH',
1093: 'CURLOPT_COOKIE',
1094: 'CURLOPT_COOKIEJAR',
1095: 'CURLOPT_COOKIELIST',
1096: 'CURLOPT_DEFAULT_PROTOCOL',
1097: 'CURLOPT_DNS_SERVERS',
1098: 'CURLOPT_EGDSOCKET',
1099: 'CURLOPT_FTP_ALTERNATIVE_TO_USER',
1100: 'CURLOPT_INTERFACE',
1101: 'CURLOPT_KEYPASSWD',
1102: 'CURLOPT_KRB4LEVEL',
1103: 'CURLOPT_LOGIN_OPTIONS',
1104: 'CURLOPT_MAIL_AUTH',
1105: 'CURLOPT_MAIL_FROM',
1106: 'CURLOPT_NOPROXY',
1107: 'CURLOPT_PASSWORD',
1108: 'CURLOPT_PINNEDPUBLICKEY',
1109: 'CURLOPT_PROTOCOLS_STR',
1110: 'CURLOPT_PROXY_CAINFO',
1111: 'CURLOPT_PROXY_CAPATH',
1112: 'CURLOPT_PROXY_CRLFILE',
1113: 'CURLOPT_PROXY_ISSUERCERT',
1114: 'CURLOPT_PROXY_KEYPASSWD',
1115: 'CURLOPT_PROXY_PINNEDPUBLICKEY',
1116: 'CURLOPT_PROXY_SERVICE_NAME',
1117: 'CURLOPT_PROXY_SSL_CIPHER_LIST',
1118: 'CURLOPT_PROXY_SSLCERT',
1119: 'CURLOPT_PROXY_SSLCERTTYPE',
1120: 'CURLOPT_PROXY_SSLKEY',
1121: 'CURLOPT_PROXY_SSLKEYTYPE',
1122: 'CURLOPT_PROXY_TLS13_CIPHERS',
1123: 'CURLOPT_PROXY_TLSAUTH_PASSWORD',
1124: 'CURLOPT_PROXY_TLSAUTH_TYPE',
1125: 'CURLOPT_PROXY_TLSAUTH_USERNAME',
1126: 'CURLOPT_PROXYPASSWORD',
1127: 'CURLOPT_PROXYUSERNAME',
1128: 'CURLOPT_PROXYUSERPWD',
1129: 'CURLOPT_RANDOM_FILE',
1130: 'CURLOPT_REDIR_PROTOCOLS_STR',
1131: 'CURLOPT_REFERER',
1132: 'CURLOPT_REQUEST_TARGET',
1133: 'CURLOPT_RTSP_STREAM_URI',
1134: 'CURLOPT_RTSP_TRANSPORT',
1135: 'CURLOPT_SASL_AUTHZID',
1136: 'CURLOPT_SERVICE_NAME',
1137: 'CURLOPT_SOCKS5_GSSAPI_SERVICE',
1138: 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5',
1139: 'CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256',
1140: 'CURLOPT_SSH_PRIVATE_KEYFILE',
1141: 'CURLOPT_SSH_PUBLIC_KEYFILE',
1142: 'CURLOPT_SSL_CIPHER_LIST',
1143: 'CURLOPT_SSL_EC_CURVES',
1144: 'CURLOPT_SSLCERT',
1145: 'CURLOPT_SSLCERTPASSWD',
1146: 'CURLOPT_SSLCERTTYPE',
1147: 'CURLOPT_SSLENGINE',
1148: 'CURLOPT_SSLENGINE_DEFAULT',
1149: 'CURLOPT_SSLKEY',
1150: 'CURLOPT_SSLKEYPASSWD',
1151: 'CURLOPT_SSLKEYTYPE',
1152: 'CURLOPT_TLS13_CIPHERS',
1153: 'CURLOPT_TLSAUTH_PASSWORD',
1154: 'CURLOPT_TLSAUTH_TYPE',
1155: 'CURLOPT_TLSAUTH_USERNAME',
1156: 'CURLOPT_TRANSFER_ENCODING',
1157: 'CURLOPT_URL',
1158: 'CURLOPT_USERAGENT',
1159: 'CURLOPT_USERNAME',
1160: 'CURLOPT_USERPWD',
1161: ];
1162: foreach ($nonEmptyStringConstants as $constName) {
1163: if (defined($constName) && constant($constName) === $curlOpt) {
1164: return new IntersectionType([
1165: new StringType(),
1166: new AccessoryNonEmptyStringType(),
1167: ]);
1168: }
1169: }
1170:
1171: $stringConstants = [
1172: 'CURLOPT_COOKIEFILE',
1173: 'CURLOPT_ENCODING', // Alias: CURLOPT_ACCEPT_ENCODING
1174: 'CURLOPT_PRE_PROXY',
1175: 'CURLOPT_PRIVATE',
1176: 'CURLOPT_PROXY',
1177: ];
1178: foreach ($stringConstants as $constName) {
1179: if (defined($constName) && constant($constName) === $curlOpt) {
1180: return new StringType();
1181: }
1182: }
1183:
1184: $intArrayStringKeysConstants = [
1185: 'CURLOPT_HTTPHEADER',
1186: ];
1187: foreach ($intArrayStringKeysConstants as $constName) {
1188: if (defined($constName) && constant($constName) === $curlOpt) {
1189: return new ArrayType(new IntegerType(), new StringType());
1190: }
1191: }
1192:
1193: $arrayConstants = [
1194: 'CURLOPT_CONNECT_TO',
1195: 'CURLOPT_HTTP200ALIASES',
1196: 'CURLOPT_POSTQUOTE',
1197: 'CURLOPT_PROXYHEADER',
1198: 'CURLOPT_QUOTE',
1199: 'CURLOPT_RESOLVE',
1200: ];
1201: foreach ($arrayConstants as $constName) {
1202: if (defined($constName) && constant($constName) === $curlOpt) {
1203: return new ArrayType(new MixedType(), new MixedType());
1204: }
1205: }
1206:
1207: $arrayOrStringConstants = [
1208: 'CURLOPT_POSTFIELDS',
1209: ];
1210: foreach ($arrayOrStringConstants as $constName) {
1211: if (defined($constName) && constant($constName) === $curlOpt) {
1212: return new UnionType([
1213: new StringType(),
1214: new ArrayType(new MixedType(), new MixedType()),
1215: ]);
1216: }
1217: }
1218:
1219: $resourceConstants = [
1220: 'CURLOPT_FILE',
1221: 'CURLOPT_INFILE',
1222: 'CURLOPT_STDERR',
1223: 'CURLOPT_WRITEHEADER',
1224: ];
1225: foreach ($resourceConstants as $constName) {
1226: if (defined($constName) && constant($constName) === $curlOpt) {
1227: return new ResourceType();
1228: }
1229: }
1230:
1231: if (defined('CURLOPT_SHARE') && $curlOpt === CURLOPT_SHARE) {
1232: $phpversion = PhpVersionStaticAccessor::getInstance();
1233:
1234: if ($phpversion->supportsCurlShareHandle()) {
1235: $shareType = new ObjectType('CurlShareHandle');
1236: } else {
1237: $shareType = new ResourceType();
1238: }
1239: if ($phpversion->supportsCurlSharePersistentHandle()) {
1240: $shareType = TypeCombinator::union($shareType, new ObjectType('CurlSharePersistentHandle'));
1241: }
1242:
1243: return $shareType;
1244: }
1245:
1246: // unknown constant
1247: return null;
1248: }
1249:
1250: private static function overrideParameterType(ParameterReflection $original, Type $type, Type $nativeType): ExtendedDummyParameter
1251: {
1252: $wrapped = self::wrapParameter($original);
1253:
1254: return new ExtendedDummyParameter(
1255: $wrapped->getName(),
1256: $type,
1257: $wrapped->isOptional(),
1258: $wrapped->passedByReference(),
1259: $wrapped->isVariadic(),
1260: $wrapped->getDefaultValue(),
1261: $nativeType,
1262: $type,
1263: $wrapped->getOutType(),
1264: $wrapped->isImmediatelyInvokedCallable(),
1265: $wrapped->getClosureThisType(),
1266: $wrapped->getAttributes(),
1267: $wrapped->getAllowedConstants(),
1268: );
1269: }
1270:
1271: /**
1272: * @param list<ParameterReflection> $parameters
1273: */
1274: private static function overrideAcceptorParameters(ParametersAcceptor $acceptor, array $parameters): ParametersAcceptor
1275: {
1276: if ($acceptor instanceof ExtendedParametersAcceptor) {
1277: return new ExtendedFunctionVariant(
1278: $acceptor->getTemplateTypeMap(),
1279: $acceptor->getResolvedTemplateTypeMap(),
1280: array_map(static fn (ParameterReflection $p): ExtendedParameterReflection => $p instanceof ExtendedParameterReflection ? $p : self::wrapParameter($p), $parameters),
1281: $acceptor->isVariadic(),
1282: $acceptor->getReturnType(),
1283: $acceptor->getPhpDocReturnType(),
1284: $acceptor->getNativeReturnType(),
1285: $acceptor->getCallSiteVarianceMap(),
1286: );
1287: }
1288:
1289: return new FunctionVariant(
1290: $acceptor->getTemplateTypeMap(),
1291: $acceptor->getResolvedTemplateTypeMap(),
1292: $parameters,
1293: $acceptor->isVariadic(),
1294: $acceptor->getReturnType(),
1295: TemplateTypeVarianceMap::createEmpty(),
1296: );
1297: }
1298:
1299: }
1300: