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