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