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