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