1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use Closure;
6: use PhpParser\Node;
7: use PhpParser\Node\Expr;
8: use PHPStan\Analyser\ArgumentsNormalizer;
9: use PHPStan\Analyser\MutatingScope;
10: use PHPStan\Analyser\Scope;
11: use PHPStan\Node\Expr\ParameterVariableOriginalValueExpr;
12: use PHPStan\Parser\ArrayFilterArgVisitor;
13: use PHPStan\Parser\ArrayFindArgVisitor;
14: use PHPStan\Parser\ArrayMapArgVisitor;
15: use PHPStan\Parser\ArrayWalkArgVisitor;
16: use PHPStan\Parser\ClosureBindArgVisitor;
17: use PHPStan\Parser\ClosureBindToVarVisitor;
18: use PHPStan\Parser\CurlSetOptArgVisitor;
19: use PHPStan\Parser\CurlSetOptArrayArgVisitor;
20: use PHPStan\Parser\ImplodeArgVisitor;
21: use PHPStan\Reflection\Callables\CallableParametersAcceptor;
22: use PHPStan\Reflection\Native\NativeParameterReflection;
23: use PHPStan\Reflection\Php\DummyParameter;
24: use PHPStan\Reflection\Php\ExtendedDummyParameter;
25: use PHPStan\ShouldNotHappenException;
26: use PHPStan\TrinaryLogic;
27: use PHPStan\Turbo\ShadowedByTurboExtension;
28: use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
29: use PHPStan\Type\ArrayType;
30: use PHPStan\Type\BooleanType;
31: use PHPStan\Type\CallableType;
32: use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
33: use PHPStan\Type\Constant\ConstantIntegerType;
34: use PHPStan\Type\Generic\TemplateTypeMap;
35: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
36: use PHPStan\Type\IntegerType;
37: use PHPStan\Type\IntersectionType;
38: use PHPStan\Type\MixedType;
39: use PHPStan\Type\NullType;
40: use PHPStan\Type\ObjectType;
41: use PHPStan\Type\ResourceType;
42: use PHPStan\Type\StringType;
43: use PHPStan\Type\Type;
44: use PHPStan\Type\TypeCombinator;
45: use PHPStan\Type\UnionType;
46: use function array_is_list;
47: use function array_key_exists;
48: use function array_key_last;
49: use function array_last;
50: use function array_map;
51: use function array_merge;
52: use function array_slice;
53: use function array_values;
54: use function constant;
55: use function count;
56: use function defined;
57: use function is_int;
58: use function is_string;
59: use function sprintf;
60: use const ARRAY_FILTER_USE_BOTH;
61: use const ARRAY_FILTER_USE_KEY;
62: use const CURLOPT_SHARE;
63: use const CURLOPT_SSL_VERIFYHOST;
64:
65: /**
66: * @api
67: */
68: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/ParametersAcceptorSelector.cpp')]
69: final class ParametersAcceptorSelector
70: {
71:
72: /**
73: * @param Node\Arg[] $args
74: * @param ParametersAcceptor[] $parametersAcceptors
75: * @param ParametersAcceptor[]|null $namedArgumentsVariants
76: */
77: public static function selectFromArgs(
78: Scope $scope,
79: array $args,
80: array $parametersAcceptors,
81: ?array $namedArgumentsVariants = null,
82: ): ParametersAcceptor
83: {
84: $types = [];
85: $unpack = false;
86: $parametersAcceptors = self::applyIntrinsicArgOverrides(
87: $args,
88: $parametersAcceptors,
89: $namedArgumentsVariants,
90: $scope,
91: static fn (Expr $e): Type => $scope->getType($e),
92: static fn (Expr $e): Type => $scope->getNativeType($e),
93: static fn (Type $t): Type => $scope->getIterableValueType($t),
94: static fn (Type $t): Type => $scope->getIterableKeyType($t),
95: );
96:
97: if (count($parametersAcceptors) === 1) {
98: $acceptor = $parametersAcceptors[0];
99: if (!self::hasAcceptorTemplateOrLateResolvableType($acceptor)) {
100: return $acceptor;
101: }
102: }
103:
104: $reorderedArgs = $args;
105: $singleParametersAcceptor = null;
106: if (count($parametersAcceptors) === 1) {
107: if (!array_is_list($args)) {
108: // actually $args parameter should be typed to list but we can't atm,
109: // because its a BC break.
110: $args = array_values($args);
111: }
112: $reorderedArgs = ArgumentsNormalizer::reorderArgs($parametersAcceptors[0], $args);
113: $singleParametersAcceptor = $parametersAcceptors[0];
114: }
115:
116: $hasName = false;
117: foreach ($reorderedArgs ?? $args as $i => $arg) {
118: $originalArg = $arg->getAttribute(ArgumentsNormalizer::ORIGINAL_ARG_ATTRIBUTE) ?? $arg;
119: $parameter = null;
120: if ($singleParametersAcceptor !== null) {
121: $parameters = $singleParametersAcceptor->getParameters();
122: if (isset($parameters[$i])) {
123: $parameter = $parameters[$i];
124: } elseif (count($parameters) > 0 && $singleParametersAcceptor->isVariadic()) {
125: $parameter = array_last($parameters);
126: }
127: }
128:
129: if ($parameter !== null && $scope instanceof MutatingScope) {
130: $rememberTypes = !$originalArg->value instanceof Node\Expr\Closure && !$originalArg->value instanceof Node\Expr\ArrowFunction;
131: $scope = $scope->pushInFunctionCall(null, $parameter, $rememberTypes);
132: }
133:
134: $type = $scope->getType($originalArg->value);
135:
136: if ($parameter !== null && $scope instanceof MutatingScope) {
137: $scope = $scope->popInFunctionCall();
138: }
139:
140: if ($originalArg->name !== null) {
141: $index = $originalArg->name->toString();
142: $hasName = true;
143: } else {
144: $index = $i;
145: }
146: if ($originalArg->unpack) {
147: $unpack = true;
148: $constantArrays = $type->getConstantArrays();
149: if (count($constantArrays) > 0) {
150: foreach ($constantArrays as $constantArray) {
151: $values = $constantArray->getValueTypes();
152: foreach ($constantArray->getKeyTypes() as $j => $keyType) {
153: $valueType = $values[$j];
154: $valueIndex = $keyType->getValue();
155: if (is_string($valueIndex)) {
156: $hasName = true;
157: } else {
158: $valueIndex = $i + $j;
159: }
160:
161: $types[$valueIndex] = isset($types[$valueIndex])
162: ? TypeCombinator::union($types[$valueIndex], $valueType)
163: : $valueType;
164: }
165: }
166: } else {
167: $types[$index] = $type->getIterableValueType();
168: }
169: } else {
170: $types[$index] = $type;
171: }
172: }
173:
174: if ($hasName && $namedArgumentsVariants !== null) {
175: return self::selectFromTypes($types, $namedArgumentsVariants, $unpack);
176: }
177:
178: return self::selectFromTypes($types, $parametersAcceptors, $unpack);
179: }
180:
181: /**
182: * @internal
183: * @param Node\Arg[] $args
184: * @param ParametersAcceptor[] $parametersAcceptors
185: * @param ParametersAcceptor[]|null $namedArgumentsVariants
186: * @param Closure(Expr): Type $typeGetter
187: * @param Closure(Expr): Type $nativeTypeGetter
188: * @param Closure(Type): Type $iterableValueTypeGetter
189: * @param Closure(Type): Type $iterableKeyTypeGetter
190: * @return ParametersAcceptor[]
191: */
192: public static function applyIntrinsicArgOverrides(
193: array $args,
194: array $parametersAcceptors,
195: ?array $namedArgumentsVariants,
196: Scope $scope,
197: Closure $typeGetter,
198: Closure $nativeTypeGetter,
199: Closure $iterableValueTypeGetter,
200: Closure $iterableKeyTypeGetter,
201: ): array
202: {
203: if (
204: count($args) > 0
205: && count($parametersAcceptors) > 0
206: ) {
207: $arrayMapArgs = $args[0]->value->getAttribute(ArrayMapArgVisitor::ATTRIBUTE_NAME);
208: if ($arrayMapArgs !== null) {
209: $callbackParameters = [];
210: $nativeCallbackParameters = [];
211: foreach ($arrayMapArgs as $arg) {
212: $argType = ($typeGetter)($arg->value);
213: $nativeArgType = ($nativeTypeGetter)($arg->value);
214: if ($arg->unpack) {
215: $constantArrays = $argType->getConstantArrays();
216: if (count($constantArrays) > 0) {
217: foreach ($constantArrays as $constantArray) {
218: $valueTypes = $constantArray->getValueTypes();
219: foreach ($valueTypes as $valueType) {
220: $callbackParameters[] = new DummyParameter('item', ($iterableValueTypeGetter)($valueType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
221: }
222: }
223: }
224: $nativeConstantArrays = $nativeArgType->getConstantArrays();
225: if (count($nativeConstantArrays) > 0) {
226: foreach ($nativeConstantArrays as $constantArray) {
227: $valueTypes = $constantArray->getValueTypes();
228: foreach ($valueTypes as $valueType) {
229: $nativeCallbackParameters[] = new DummyParameter('item', ($iterableValueTypeGetter)($valueType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
230: }
231: }
232: }
233: } else {
234: $callbackParameters[] = new DummyParameter('item', ($iterableValueTypeGetter)($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
235: $nativeCallbackParameters[] = new DummyParameter('item', ($iterableValueTypeGetter)($nativeArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
236: }
237: }
238:
239: $acceptor = $parametersAcceptors[0];
240: $parameters = $acceptor->getParameters();
241: if (isset($parameters[0])) {
242: $callableType = new UnionType([
243: new CallableType($callbackParameters, new MixedType(), false),
244: new NullType(),
245: ]);
246: $nativeCallableType = new UnionType([
247: new CallableType($nativeCallbackParameters, new MixedType(), false),
248: new NullType(),
249: ]);
250: $parameters[0] = self::overrideParameterType($parameters[0], $callableType, $nativeCallableType);
251: $parametersAcceptors = [self::overrideAcceptorParameters($acceptor, $parameters)];
252: }
253: }
254:
255: if (count($args) >= 3 && (bool) $args[0]->getAttribute(CurlSetOptArgVisitor::ATTRIBUTE_NAME)) {
256: $optType = ($typeGetter)($args[1]->value);
257:
258: $valueTypes = [];
259: foreach ($optType->getConstantScalarValues() as $scalarValue) {
260: if (!is_int($scalarValue)) {
261: $valueTypes = [];
262: break;
263: }
264:
265: $valueType = self::getCurlOptValueType($scalarValue);
266: if ($valueType === null) {
267: $valueTypes = [];
268: break;
269: }
270:
271: $valueTypes[] = $valueType;
272: }
273:
274: $acceptor = $parametersAcceptors[0];
275: $parameters = $acceptor->getParameters();
276: if (count($valueTypes) !== 0 && isset($parameters[2])) {
277: $parameters[2] = new NativeParameterReflection(
278: $parameters[2]->getName(),
279: $parameters[2]->isOptional(),
280: TypeCombinator::union(...$valueTypes),
281: $parameters[2]->passedByReference(),
282: $parameters[2]->isVariadic(),
283: $parameters[2]->getDefaultValue(),
284: );
285:
286: $parametersAcceptors = [
287: new FunctionVariant(
288: $acceptor->getTemplateTypeMap(),
289: $acceptor->getResolvedTemplateTypeMap(),
290: $parameters,
291: $acceptor->isVariadic(),
292: $acceptor->getReturnType(),
293: $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
294: ),
295: ];
296: }
297: }
298:
299: if (count($args) >= 2 && (bool) $args[1]->getAttribute(CurlSetOptArrayArgVisitor::ATTRIBUTE_NAME)) {
300: $optArrayType = ($typeGetter)($args[1]->value);
301:
302: $hasTypes = false;
303: $builder = ConstantArrayTypeBuilder::createEmpty();
304: foreach ($optArrayType->getIterableKeyType()->getConstantScalarTypes() as $optType) {
305: $optValue = $optType->getValue();
306:
307: if (!is_int($optValue)) {
308: $hasTypes = false;
309: break;
310: }
311:
312: $optValueType = self::getCurlOptValueType($optValue);
313: if ($optValueType === null) {
314: $hasTypes = false;
315: break;
316: }
317:
318: $hasTypes = true;
319: $builder->setOffsetValueType(
320: new ConstantIntegerType($optValue),
321: $optValueType,
322: !$optArrayType->hasOffsetValueType($optType)->yes(),
323: );
324: }
325:
326: $acceptor = $parametersAcceptors[0];
327: $parameters = $acceptor->getParameters();
328: if ($hasTypes && isset($parameters[1])) {
329: $parameters[1] = new NativeParameterReflection(
330: $parameters[1]->getName(),
331: $parameters[1]->isOptional(),
332: $builder->getArray(),
333: $parameters[1]->passedByReference(),
334: $parameters[1]->isVariadic(),
335: $parameters[1]->getDefaultValue(),
336: );
337:
338: $parametersAcceptors = [
339: new FunctionVariant(
340: $acceptor->getTemplateTypeMap(),
341: $acceptor->getResolvedTemplateTypeMap(),
342: $parameters,
343: $acceptor->isVariadic(),
344: $acceptor->getReturnType(),
345: $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
346: ),
347: ];
348: }
349: }
350:
351: if ((bool) $args[0]->getAttribute(ArrayFilterArgVisitor::ATTRIBUTE_NAME)) {
352: $arrayFilterParameters = null;
353: $nativeArrayFilterParameters = null;
354: if (isset($args[2])) {
355: $mode = ($typeGetter)($args[2]->value);
356: if ($mode instanceof ConstantIntegerType) {
357: if ($mode->getValue() === ARRAY_FILTER_USE_KEY) {
358: $arrayFilterParameters = [
359: new DummyParameter('key', ($iterableKeyTypeGetter)(($typeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
360: ];
361: $nativeArrayFilterParameters = [
362: new DummyParameter('key', ($iterableKeyTypeGetter)(($nativeTypeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
363: ];
364: } elseif ($mode->getValue() === ARRAY_FILTER_USE_BOTH) {
365: $arrayFilterParameters = [
366: new DummyParameter('item', ($iterableValueTypeGetter)(($typeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
367: new DummyParameter('key', ($iterableKeyTypeGetter)(($typeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
368: ];
369: $nativeArrayFilterParameters = [
370: new DummyParameter('item', ($iterableValueTypeGetter)(($nativeTypeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
371: new DummyParameter('key', ($iterableKeyTypeGetter)(($nativeTypeGetter)($args[0]->value)), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
372: ];
373: }
374: }
375: }
376:
377: $acceptor = $parametersAcceptors[0];
378: $parameters = $acceptor->getParameters();
379: if (isset($parameters[1])) {
380: $arrayArgType = ($typeGetter)($args[0]->value);
381: $callableType = new UnionType([
382: new CallableType(
383: $arrayFilterParameters ?? [
384: new DummyParameter('item', ($iterableValueTypeGetter)($arrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
385: ],
386: new BooleanType(),
387: false,
388: ),
389: new NullType(),
390: ]);
391: $nativeArrayArgType = ($nativeTypeGetter)($args[0]->value);
392: $nativeCallableType = new UnionType([
393: new CallableType(
394: $nativeArrayFilterParameters ?? [
395: new DummyParameter('item', ($iterableValueTypeGetter)($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
396: ],
397: new BooleanType(),
398: false,
399: ),
400: new NullType(),
401: ]);
402: $parameters[1] = self::overrideParameterType($parameters[1], $callableType, $nativeCallableType);
403: $parametersAcceptors = [self::overrideAcceptorParameters($acceptor, $parameters)];
404: }
405: }
406:
407: if (count($args) <= 2 && (bool) $args[0]->getAttribute(ImplodeArgVisitor::ATTRIBUTE_NAME)) {
408: $acceptor = $namedArgumentsVariants[0] ?? $parametersAcceptors[0];
409: $parameters = $acceptor->getParameters();
410: if (
411: (isset($args[1]) || ($args[0]->name !== null && $args[0]->name->name === 'array'))
412: && isset($parameters[0]) && isset($parameters[1])
413: ) {
414: $parameters = [
415: new NativeParameterReflection($parameters[0]->getName(), optional: false, type: new StringType(), passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
416: new NativeParameterReflection($parameters[1]->getName(), optional: false, type: new ArrayType(new MixedType(), new MixedType()), passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
417: ];
418: } elseif (isset($parameters[0])) {
419: $parameters = [
420: new NativeParameterReflection($parameters[0]->getName(), optional: false, type: new ArrayType(new MixedType(), new MixedType()), passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
421: ];
422: }
423:
424: $parametersAcceptors = [
425: new FunctionVariant(
426: $acceptor->getTemplateTypeMap(),
427: $acceptor->getResolvedTemplateTypeMap(),
428: $parameters,
429: $acceptor->isVariadic(),
430: $acceptor->getReturnType(),
431: $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
432: ),
433: ];
434: }
435:
436: if ((bool) $args[0]->getAttribute(ArrayWalkArgVisitor::ATTRIBUTE_NAME)) {
437: $arrayArgType = ($typeGetter)($args[0]->value);
438: $nativeArrayArgType = ($nativeTypeGetter)($args[0]->value);
439: $arrayWalkParameters = [
440: new DummyParameter('item', ($iterableValueTypeGetter)($arrayArgType), optional: false, passedByReference: PassedByReference::createReadsArgument(), variadic: false, defaultValue: null),
441: new DummyParameter('key', ($iterableKeyTypeGetter)($arrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
442: ];
443: $nativeArrayWalkParameters = [
444: new DummyParameter('item', ($iterableValueTypeGetter)($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createReadsArgument(), variadic: false, defaultValue: null),
445: new DummyParameter('key', ($iterableKeyTypeGetter)($nativeArrayArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
446: ];
447: if (isset($args[2])) {
448: $arrayWalkParameters[] = new DummyParameter('arg', ($typeGetter)($args[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
449: $nativeArrayWalkParameters[] = new DummyParameter('arg', ($nativeTypeGetter)($args[2]->value), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null);
450: }
451:
452: $acceptor = $parametersAcceptors[0];
453: $parameters = $acceptor->getParameters();
454: if (isset($parameters[1])) {
455: $callableType = new CallableType($arrayWalkParameters, new MixedType(), false);
456: $nativeCallableType = new CallableType($nativeArrayWalkParameters, new MixedType(), false);
457: $parameters[1] = self::overrideParameterType($parameters[1], $callableType, $nativeCallableType);
458: $parametersAcceptors = [self::overrideAcceptorParameters($acceptor, $parameters)];
459: }
460: }
461:
462: if ((bool) $args[0]->getAttribute(ArrayFindArgVisitor::ATTRIBUTE_NAME)) {
463: $acceptor = $parametersAcceptors[0];
464: $parameters = $acceptor->getParameters();
465: if (isset($parameters[1])) {
466: $argType = ($typeGetter)($args[0]->value);
467: $callableType = new CallableType(
468: [
469: new DummyParameter('value', ($iterableValueTypeGetter)($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
470: new DummyParameter('key', ($iterableKeyTypeGetter)($argType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
471: ],
472: new BooleanType(),
473: false,
474: );
475: $nativeArgType = ($nativeTypeGetter)($args[0]->value);
476: $nativeCallableType = new CallableType(
477: [
478: new DummyParameter('value', ($iterableValueTypeGetter)($nativeArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
479: new DummyParameter('key', ($iterableKeyTypeGetter)($nativeArgType), optional: false, passedByReference: PassedByReference::createNo(), variadic: false, defaultValue: null),
480: ],
481: new BooleanType(),
482: false,
483: );
484: $parameters[1] = self::overrideParameterType($parameters[1], $callableType, $nativeCallableType);
485: $parametersAcceptors = [self::overrideAcceptorParameters($acceptor, $parameters)];
486: }
487: }
488:
489: $closureBindToVar = $args[0]->getAttribute(ClosureBindToVarVisitor::ATTRIBUTE_NAME);
490: if (
491: $closureBindToVar instanceof Node\Expr\Variable
492: && is_string($closureBindToVar->name)
493: ) {
494: $varType = ($typeGetter)($closureBindToVar);
495: if ((new ObjectType(Closure::class))->isSuperTypeOf($varType)->yes()) {
496: $inFunction = $scope->getFunction();
497: if ($inFunction !== null) {
498: $closureThisParameters = [];
499: foreach ($inFunction->getParameters() as $parameter) {
500: if ($parameter->getClosureThisType() === null) {
501: continue;
502: }
503: $closureThisParameters[$parameter->getName()] = $parameter->getClosureThisType();
504: }
505: if (array_key_exists($closureBindToVar->name, $closureThisParameters)) {
506: if ($scope->hasExpressionType(new ParameterVariableOriginalValueExpr($closureBindToVar->name))->yes()) {
507: $acceptor = $parametersAcceptors[0];
508: $parameters = $acceptor->getParameters();
509: if (isset($parameters[0])) {
510: $parameters[0] = new NativeParameterReflection(
511: $parameters[0]->getName(),
512: $parameters[0]->isOptional(),
513: $closureThisParameters[$closureBindToVar->name],
514: $parameters[0]->passedByReference(),
515: $parameters[0]->isVariadic(),
516: $parameters[0]->getDefaultValue(),
517: );
518: $parametersAcceptors = [
519: new FunctionVariant(
520: $acceptor->getTemplateTypeMap(),
521: $acceptor->getResolvedTemplateTypeMap(),
522: $parameters,
523: $acceptor->isVariadic(),
524: $acceptor->getReturnType(),
525: $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
526: ),
527: ];
528: }
529: }
530: }
531: }
532: }
533: }
534:
535: if (
536: $args[0]->getAttribute(ClosureBindArgVisitor::ATTRIBUTE_NAME) !== null
537: && $args[0]->value instanceof Node\Expr\Variable
538: && is_string($args[0]->value->name)
539: ) {
540: $closureVarName = $args[0]->value->name;
541: $inFunction = $scope->getFunction();
542: if ($inFunction !== null) {
543: $closureThisParameters = [];
544: foreach ($inFunction->getParameters() as $parameter) {
545: if ($parameter->getClosureThisType() === null) {
546: continue;
547: }
548: $closureThisParameters[$parameter->getName()] = $parameter->getClosureThisType();
549: }
550: if (array_key_exists($closureVarName, $closureThisParameters)) {
551: if ($scope->hasExpressionType(new ParameterVariableOriginalValueExpr($closureVarName))->yes()) {
552: $acceptor = $parametersAcceptors[0];
553: $parameters = $acceptor->getParameters();
554:
555: if (isset($parameters[1])) {
556: $parameters[1] = new NativeParameterReflection(
557: $parameters[1]->getName(),
558: $parameters[1]->isOptional(),
559: $closureThisParameters[$closureVarName],
560: $parameters[1]->passedByReference(),
561: $parameters[1]->isVariadic(),
562: $parameters[1]->getDefaultValue(),
563: );
564: $parametersAcceptors = [
565: new FunctionVariant(
566: $acceptor->getTemplateTypeMap(),
567: $acceptor->getResolvedTemplateTypeMap(),
568: $parameters,
569: $acceptor->isVariadic(),
570: $acceptor->getReturnType(),
571: $acceptor instanceof ExtendedParametersAcceptor ? $acceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
572: ),
573: ];
574: }
575: }
576: }
577: }
578: }
579: }
580:
581: return $parametersAcceptors;
582: }
583:
584: /**
585: * @internal
586: */
587: public static function hasAcceptorTemplateOrLateResolvableType(ParametersAcceptor $acceptor): bool
588: {
589: if ($acceptor->getReturnType()->hasTemplateOrLateResolvableType()) {
590: return true;
591: }
592:
593: return self::hasAcceptorTemplateOrLateResolvableParameterType($acceptor);
594: }
595:
596: public static function hasAcceptorTemplateOrLateResolvableParameterType(ParametersAcceptor $acceptor): bool
597: {
598: foreach ($acceptor->getParameters() as $parameter) {
599: if (
600: $parameter instanceof ExtendedParameterReflection
601: && $parameter->getOutType() !== null
602: && $parameter->getOutType()->hasTemplateOrLateResolvableType()
603: ) {
604: return true;
605: }
606:
607: if (
608: $parameter instanceof ExtendedParameterReflection
609: && $parameter->getClosureThisType() !== null
610: && $parameter->getClosureThisType()->hasTemplateOrLateResolvableType()
611: ) {
612: return true;
613: }
614:
615: if (!$parameter->getType()->hasTemplateOrLateResolvableType()) {
616: continue;
617: }
618:
619: return true;
620: }
621:
622: return false;
623: }
624:
625: /**
626: * @param array<int|string, Type> $types
627: * @param ParametersAcceptor[] $parametersAcceptors
628: */
629: public static function selectFromTypes(
630: array $types,
631: array $parametersAcceptors,
632: bool $unpack,
633: ): ParametersAcceptor
634: {
635: if (count($parametersAcceptors) === 1) {
636: return GenericParametersAcceptorResolver::resolve($types, $parametersAcceptors[0]);
637: }
638:
639: if (count($parametersAcceptors) === 0) {
640: throw new ShouldNotHappenException(
641: 'getVariants() must return at least one variant.',
642: );
643: }
644:
645: $typesCount = count($types);
646: $acceptableAcceptors = [];
647:
648: foreach ($parametersAcceptors as $parametersAcceptor) {
649: if ($unpack) {
650: $acceptableAcceptors[] = $parametersAcceptor;
651: continue;
652: }
653:
654: $functionParametersMinCount = 0;
655: $functionParametersMaxCount = 0;
656: foreach ($parametersAcceptor->getParameters() as $parameter) {
657: if (!$parameter->isOptional()) {
658: $functionParametersMinCount++;
659: }
660:
661: $functionParametersMaxCount++;
662: }
663:
664: if ($typesCount < $functionParametersMinCount) {
665: continue;
666: }
667:
668: if (
669: !$parametersAcceptor->isVariadic()
670: && $typesCount > $functionParametersMaxCount
671: ) {
672: continue;
673: }
674:
675: $acceptableAcceptors[] = $parametersAcceptor;
676: }
677:
678: if (count($acceptableAcceptors) === 0) {
679: return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($parametersAcceptors));
680: }
681:
682: if (count($acceptableAcceptors) === 1) {
683: return GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptors[0]);
684: }
685:
686: $winningAcceptors = [];
687: $winningCertainty = null;
688: foreach ($acceptableAcceptors as $acceptableAcceptor) {
689: $isSuperType = TrinaryLogic::createYes();
690: $acceptableAcceptor = GenericParametersAcceptorResolver::resolve($types, $acceptableAcceptor);
691: foreach ($acceptableAcceptor->getParameters() as $i => $parameter) {
692: if (!isset($types[$i])) {
693: if (!$unpack || count($types) <= 0) {
694: break;
695: }
696:
697: $type = $types[array_key_last($types)];
698: } else {
699: $type = $types[$i];
700: }
701:
702: if ($parameter->getType() instanceof MixedType) {
703: $isSuperType = $isSuperType->and(TrinaryLogic::createMaybe());
704: } else {
705: $isSuperType = $isSuperType->and($parameter->getType()->isSuperTypeOf($type)->result);
706: }
707: }
708:
709: if ($isSuperType->no()) {
710: continue;
711: }
712:
713: if ($winningCertainty === null) {
714: $winningAcceptors[] = $acceptableAcceptor;
715: $winningCertainty = $isSuperType;
716: } else {
717: $comparison = $winningCertainty->compareTo($isSuperType);
718: if ($comparison === $isSuperType) {
719: $winningAcceptors = [$acceptableAcceptor];
720: $winningCertainty = $isSuperType;
721: } elseif ($comparison === null) {
722: $winningAcceptors[] = $acceptableAcceptor;
723: }
724: }
725: }
726:
727: if (count($winningAcceptors) === 0) {
728: return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($acceptableAcceptors));
729: }
730:
731: return GenericParametersAcceptorResolver::resolve($types, self::combineAcceptors($winningAcceptors));
732: }
733:
734: /**
735: * Picks the structural ParametersAcceptor (parameter names/positions/variadic
736: * only) that drives argument normalization / reordering. Unlike selectFromArgs()
737: * it never reads argument types from a Scope, so it is safe to call before the
738: * arguments have been processed - generics are resolved separately, type-driven.
739: *
740: * @internal
741: * @param Node\Arg[] $args
742: * @param ParametersAcceptor[] $variants
743: * @param ParametersAcceptor[]|null $namedArgumentsVariants
744: */
745: public static function combineVariantsForNormalization(array $args, array $variants, ?array $namedArgumentsVariants): ParametersAcceptor
746: {
747: $hasName = false;
748: foreach ($args as $arg) {
749: if ($arg->name !== null) {
750: $hasName = true;
751: break;
752: }
753: }
754:
755: $selectedVariants = $hasName && $namedArgumentsVariants !== null ? $namedArgumentsVariants : $variants;
756:
757: return count($selectedVariants) === 1
758: ? $selectedVariants[0]
759: : self::combineAcceptors($selectedVariants);
760: }
761:
762: /**
763: * @param ParametersAcceptor[] $acceptors
764: */
765: public static function combineAcceptors(array $acceptors): ExtendedParametersAcceptor
766: {
767: if (count($acceptors) === 0) {
768: throw new ShouldNotHappenException(
769: 'getVariants() must return at least one variant.',
770: );
771: }
772: if (count($acceptors) === 1) {
773: return self::wrapAcceptor($acceptors[0]);
774: }
775:
776: $minimumNumberOfParameters = null;
777: foreach ($acceptors as $acceptor) {
778: $acceptorParametersMinCount = 0;
779: foreach ($acceptor->getParameters() as $parameter) {
780: if ($parameter->isOptional()) {
781: continue;
782: }
783:
784: $acceptorParametersMinCount++;
785: }
786:
787: if ($minimumNumberOfParameters !== null && $minimumNumberOfParameters <= $acceptorParametersMinCount) {
788: continue;
789: }
790:
791: $minimumNumberOfParameters = $acceptorParametersMinCount;
792: }
793:
794: $parameters = [];
795: $isVariadic = false;
796: $returnTypes = [];
797: $phpDocReturnTypes = [];
798: $nativeReturnTypes = [];
799: $callableOccurred = false;
800: $throwPoints = [];
801: $isPure = TrinaryLogic::createNo();
802: $impurePoints = [];
803: $invalidateExpressions = [];
804: $usedVariables = [];
805: $acceptsNamedArguments = TrinaryLogic::createNo();
806: $mustUseReturnValue = TrinaryLogic::createMaybe();
807: $isStaticClosure = TrinaryLogic::createMaybe();
808:
809: foreach ($acceptors as $acceptor) {
810: $returnTypes[] = $acceptor->getReturnType();
811:
812: if ($acceptor instanceof ExtendedParametersAcceptor) {
813: $phpDocReturnTypes[] = $acceptor->getPhpDocReturnType();
814: $nativeReturnTypes[] = $acceptor->getNativeReturnType();
815: }
816: if ($acceptor instanceof CallableParametersAcceptor) {
817: $callableOccurred = true;
818: $throwPoints = array_merge($throwPoints, $acceptor->getThrowPoints());
819: $isPure = $isPure->or($acceptor->isPure());
820: $impurePoints = array_merge($impurePoints, $acceptor->getImpurePoints());
821: $invalidateExpressions = array_merge($invalidateExpressions, $acceptor->getInvalidateExpressions());
822: $usedVariables = array_merge($usedVariables, $acceptor->getUsedVariables());
823: $acceptsNamedArguments = $acceptsNamedArguments->or($acceptor->acceptsNamedArguments());
824: $mustUseReturnValue = $mustUseReturnValue->or($acceptor->mustUseReturnValue());
825: $isStaticClosure = $isStaticClosure->or($acceptor->isStaticClosure());
826: }
827: $isVariadic = $isVariadic || $acceptor->isVariadic();
828:
829: foreach ($acceptor->getParameters() as $i => $parameter) {
830: if (!isset($parameters[$i])) {
831: $parameters[$i] = new ExtendedDummyParameter(
832: $parameter->getName(),
833: $parameter->getType(),
834: $i + 1 > $minimumNumberOfParameters,
835: $parameter->passedByReference(),
836: $parameter->isVariadic(),
837: $parameter->getDefaultValue(),
838: $parameter instanceof ExtendedParameterReflection ? $parameter->getNativeType() : new MixedType(),
839: $parameter instanceof ExtendedParameterReflection ? $parameter->getPhpDocType() : new MixedType(),
840: $parameter instanceof ExtendedParameterReflection ? $parameter->getOutType() : null,
841: $parameter instanceof ExtendedParameterReflection ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(),
842: $parameter instanceof ExtendedParameterReflection ? $parameter->getClosureThisType() : null,
843: $parameter instanceof ExtendedParameterReflection ? $parameter->getAttributes() : [],
844: $parameter instanceof ExtendedParameterReflection ? $parameter->getAllowedConstants() : null,
845: $parameter instanceof ExtendedParameterReflection ? $parameter->isPureUnlessCallableIsImpureParameter() : TrinaryLogic::createNo(),
846: $parameter instanceof ExtendedParameterReflection ? $parameter->isPureUnlessParameterPassedParameter() : TrinaryLogic::createNo(),
847: );
848: continue;
849: }
850:
851: $isParameterVariadic = $parameters[$i]->isVariadic() || $parameter->isVariadic();
852: $defaultValueLeft = $parameters[$i]->getDefaultValue();
853: $defaultValueRight = $parameter->getDefaultValue();
854: if ($defaultValueLeft !== null && $defaultValueRight !== null) {
855: $defaultValue = TypeCombinator::union($defaultValueLeft, $defaultValueRight);
856: } else {
857: $defaultValue = null;
858: }
859:
860: $type = TypeCombinator::union($parameters[$i]->getType(), $parameter->getType());
861: $nativeType = $parameters[$i]->getNativeType();
862: $phpDocType = $parameters[$i]->getPhpDocType();
863: $outType = $parameters[$i]->getOutType();
864: $immediatelyInvokedCallable = $parameters[$i]->isImmediatelyInvokedCallable();
865: $closureThisType = $parameters[$i]->getClosureThisType();
866: $attributes = $parameters[$i]->getAttributes();
867: if ($parameter instanceof ExtendedParameterReflection) {
868: $nativeType = TypeCombinator::union($nativeType, $parameter->getNativeType());
869: $phpDocType = TypeCombinator::union($phpDocType, $parameter->getPhpDocType());
870:
871: if ($parameter->getOutType() !== null) {
872: $outType = $outType === null ? null : TypeCombinator::union($outType, $parameter->getOutType());
873: } else {
874: $outType = null;
875: }
876:
877: if ($parameter->getClosureThisType() !== null && $closureThisType !== null) {
878: $closureThisType = TypeCombinator::union($closureThisType, $parameter->getClosureThisType());
879: } else {
880: $closureThisType = null;
881: }
882:
883: $immediatelyInvokedCallable = $parameter->isImmediatelyInvokedCallable()->or($immediatelyInvokedCallable);
884: $attributes = array_merge($attributes, $parameter->getAttributes());
885: } else {
886: $nativeType = new MixedType();
887: $phpDocType = $type;
888: $outType = null;
889: $immediatelyInvokedCallable = TrinaryLogic::createMaybe();
890: $closureThisType = null;
891: }
892:
893: if ($isParameterVariadic) {
894: // the variadic parameter swallows every parameter behind it,
895: // so their types have to be merged into it before they're dropped
896: $droppedParameters = array_merge(
897: array_slice($parameters, $i + 1),
898: array_slice($acceptor->getParameters(), $i + 1),
899: );
900: foreach ($droppedParameters as $droppedParameter) {
901: $type = TypeCombinator::union($type, $droppedParameter->getType());
902: if ($droppedParameter instanceof ExtendedParameterReflection) {
903: $nativeType = TypeCombinator::union($nativeType, $droppedParameter->getNativeType());
904: $phpDocType = TypeCombinator::union($phpDocType, $droppedParameter->getPhpDocType());
905: continue;
906: }
907:
908: $nativeType = new MixedType();
909: $phpDocType = TypeCombinator::union($phpDocType, $droppedParameter->getType());
910: }
911: }
912:
913: $allowedConstants = $parameters[$i]->getAllowedConstants();
914: if ($allowedConstants !== null) {
915: $otherAllowedConstants = $parameter instanceof ExtendedParameterReflection ? $parameter->getAllowedConstants() : null;
916: if ($otherAllowedConstants === null || !$allowedConstants->equals($otherAllowedConstants)) {
917: $allowedConstants = null;
918: }
919: }
920:
921: $leftPureUnless = $parameters[$i]->isPureUnlessCallableIsImpureParameter();
922: $rightPureUnless = $parameter instanceof ExtendedParameterReflection ? $parameter->isPureUnlessCallableIsImpureParameter() : TrinaryLogic::createNo();
923: $pureUnlessCallableIsImpureParameter = $leftPureUnless->equals($rightPureUnless) ? $leftPureUnless : TrinaryLogic::createMaybe();
924:
925: $leftPureUnlessParameterPassed = $parameters[$i]->isPureUnlessParameterPassedParameter();
926: $rightPureUnlessParameterPassed = $parameter instanceof ExtendedParameterReflection ? $parameter->isPureUnlessParameterPassedParameter() : TrinaryLogic::createNo();
927: $pureUnlessParameterPassedParameter = $leftPureUnlessParameterPassed->equals($rightPureUnlessParameterPassed) ? $leftPureUnlessParameterPassed : TrinaryLogic::createMaybe();
928:
929: $parameters[$i] = new ExtendedDummyParameter(
930: $parameters[$i]->getName() !== $parameter->getName() ? sprintf('%s|%s', $parameters[$i]->getName(), $parameter->getName()) : $parameter->getName(),
931: $type,
932: $i + 1 > $minimumNumberOfParameters,
933: $parameters[$i]->passedByReference()->combine($parameter->passedByReference()),
934: $isParameterVariadic,
935: $defaultValue,
936: $nativeType,
937: $phpDocType,
938: $outType,
939: $immediatelyInvokedCallable,
940: $closureThisType,
941: $attributes,
942: $allowedConstants,
943: $pureUnlessCallableIsImpureParameter,
944: $pureUnlessParameterPassedParameter,
945: );
946:
947: if ($isParameterVariadic) {
948: $parameters = array_slice($parameters, 0, $i + 1);
949: break;
950: }
951: }
952: }
953:
954: $returnType = TypeCombinator::union(...$returnTypes);
955: $phpDocReturnType = $phpDocReturnTypes === [] ? null : TypeCombinator::union(...$phpDocReturnTypes);
956: $nativeReturnType = $nativeReturnTypes === [] ? null : TypeCombinator::union(...$nativeReturnTypes);
957:
958: if ($callableOccurred) {
959: return new ExtendedCallableFunctionVariant(
960: TemplateTypeMap::createEmpty(),
961: null,
962: array_values($parameters),
963: $isVariadic,
964: $returnType,
965: $phpDocReturnType ?? $returnType,
966: $nativeReturnType ?? new MixedType(),
967: null,
968: $throwPoints,
969: $isPure,
970: $impurePoints,
971: $invalidateExpressions,
972: $usedVariables,
973: $acceptsNamedArguments,
974: $mustUseReturnValue,
975: isStatic: $isStaticClosure,
976: );
977: }
978:
979: return new ExtendedFunctionVariant(
980: TemplateTypeMap::createEmpty(),
981: null,
982: array_values($parameters),
983: $isVariadic,
984: $returnType,
985: $phpDocReturnType ?? $returnType,
986: $nativeReturnType ?? new MixedType(),
987: );
988: }
989:
990: private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedParametersAcceptor
991: {
992: if ($acceptor instanceof ExtendedParametersAcceptor) {
993: return $acceptor;
994: }
995:
996: if ($acceptor instanceof CallableParametersAcceptor) {
997: return new ExtendedCallableFunctionVariant(
998: $acceptor->getTemplateTypeMap(),
999: $acceptor->getResolvedTemplateTypeMap(),
1000: array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()),
1001: $acceptor->isVariadic(),
1002: $acceptor->getReturnType(),
1003: $acceptor->getReturnType(),
1004: new MixedType(),
1005: TemplateTypeVarianceMap::createEmpty(),
1006: $acceptor->getThrowPoints(),
1007: $acceptor->isPure(),
1008: $acceptor->getImpurePoints(),
1009: $acceptor->getInvalidateExpressions(),
1010: $acceptor->getUsedVariables(),
1011: $acceptor->acceptsNamedArguments(),
1012: $acceptor->mustUseReturnValue(),
1013: $acceptor->getAsserts(),
1014: $acceptor->isStaticClosure(),
1015: );
1016: }
1017:
1018: return new ExtendedFunctionVariant(
1019: $acceptor->getTemplateTypeMap(),
1020: $acceptor->getResolvedTemplateTypeMap(),
1021: array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()),
1022: $acceptor->isVariadic(),
1023: $acceptor->getReturnType(),
1024: $acceptor->getReturnType(),
1025: new MixedType(),
1026: TemplateTypeVarianceMap::createEmpty(),
1027: );
1028: }
1029:
1030: private static function wrapParameter(ParameterReflection $parameter): ExtendedParameterReflection
1031: {
1032: return $parameter instanceof ExtendedParameterReflection ? $parameter : new ExtendedDummyParameter(
1033: $parameter->getName(),
1034: $parameter->getType(),
1035: $parameter->isOptional(),
1036: $parameter->passedByReference(),
1037: $parameter->isVariadic(),
1038: $parameter->getDefaultValue(),
1039: new MixedType(),
1040: $parameter->getType(),
1041: null,
1042: TrinaryLogic::createMaybe(),
1043: null,
1044: [],
1045: null,
1046: TrinaryLogic::createNo(),
1047: TrinaryLogic::createNo(),
1048: );
1049: }
1050:
1051: private static function getCurlOptValueType(int $curlOpt): ?Type
1052: {
1053: if (defined('CURLOPT_SSL_VERIFYHOST') && $curlOpt === CURLOPT_SSL_VERIFYHOST) {
1054: return new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(2)]);
1055: }
1056:
1057: $boolConstants = [
1058: 'CURLOPT_AUTOREFERER',
1059: 'CURLOPT_COOKIESESSION',
1060: 'CURLOPT_CERTINFO',
1061: 'CURLOPT_CONNECT_ONLY',
1062: 'CURLOPT_CRLF',
1063: 'CURLOPT_DISALLOW_USERNAME_IN_URL',
1064: 'CURLOPT_DNS_SHUFFLE_ADDRESSES',
1065: 'CURLOPT_HAPROXYPROTOCOL',
1066: 'CURLOPT_SSH_COMPRESSION',
1067: 'CURLOPT_DNS_USE_GLOBAL_CACHE',
1068: 'CURLOPT_FAILONERROR',
1069: 'CURLOPT_SSL_FALSESTART',
1070: 'CURLOPT_FILETIME',
1071: 'CURLOPT_FOLLOWLOCATION',
1072: 'CURLOPT_FORBID_REUSE',
1073: 'CURLOPT_FRESH_CONNECT',
1074: 'CURLOPT_FTP_USE_EPRT',
1075: 'CURLOPT_FTP_USE_EPSV',
1076: 'CURLOPT_FTP_CREATE_MISSING_DIRS',
1077: 'CURLOPT_FTPAPPEND',
1078: 'CURLOPT_TCP_NODELAY',
1079: 'CURLOPT_FTPASCII',
1080: 'CURLOPT_FTPLISTONLY',
1081: 'CURLOPT_HEADER',
1082: 'CURLOPT_HTTP09_ALLOWED',
1083: 'CURLOPT_HTTPGET',
1084: 'CURLOPT_HTTPPROXYTUNNEL',
1085: 'CURLOPT_HTTP_CONTENT_DECODING',
1086: 'CURLOPT_KEEP_SENDING_ON_ERROR',
1087: 'CURLOPT_MUTE',
1088: 'CURLOPT_NETRC',
1089: 'CURLOPT_NOBODY',
1090: 'CURLOPT_NOPROGRESS',
1091: 'CURLOPT_NOSIGNAL',
1092: 'CURLOPT_PATH_AS_IS',
1093: 'CURLOPT_PIPEWAIT',
1094: 'CURLOPT_POST',
1095: 'CURLOPT_PUT',
1096: 'CURLOPT_RETURNTRANSFER',
1097: 'CURLOPT_SASL_IR',
1098: 'CURLOPT_SSL_ENABLE_ALPN',
1099: 'CURLOPT_SSL_ENABLE_NPN',
1100: 'CURLOPT_SSL_VERIFYPEER',
1101: 'CURLOPT_SSL_VERIFYSTATUS',
1102: 'CURLOPT_PROXY_SSL_VERIFYPEER',
1103: 'CURLOPT_SUPPRESS_CONNECT_HEADERS',
1104: 'CURLOPT_TCP_FASTOPEN',
1105: 'CURLOPT_TFTP_NO_OPTIONS',
1106: 'CURLOPT_TRANSFERTEXT',
1107: 'CURLOPT_UNRESTRICTED_AUTH',
1108: 'CURLOPT_UPLOAD',
1109: 'CURLOPT_VERBOSE',
1110: ];
1111: foreach ($boolConstants as $constName) {
1112: if (defined($constName) && constant($constName) === $curlOpt) {
1113: return new BooleanType();
1114: }
1115: }
1116:
1117: $intConstants = [
1118: 'CURLOPT_BUFFERSIZE',
1119: 'CURLOPT_CONNECTTIMEOUT',
1120: 'CURLOPT_CONNECTTIMEOUT_MS',
1121: 'CURLOPT_DNS_CACHE_TIMEOUT',
1122: 'CURLOPT_EXPECT_100_TIMEOUT_MS',
1123: 'CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS',
1124: 'CURLOPT_FTPSSLAUTH',
1125: 'CURLOPT_HEADEROPT',
1126: 'CURLOPT_HTTP_VERSION',
1127: 'CURLOPT_HTTPAUTH',
1128: 'CURLOPT_INFILESIZE',
1129: 'CURLOPT_LOW_SPEED_LIMIT',
1130: 'CURLOPT_LOW_SPEED_TIME',
1131: 'CURLOPT_MAXCONNECTS',
1132: 'CURLOPT_MAXREDIRS',
1133: 'CURLOPT_PORT',
1134: 'CURLOPT_POSTREDIR',
1135: 'CURLOPT_PROTOCOLS',
1136: 'CURLOPT_PROXYAUTH',
1137: 'CURLOPT_PROXYPORT',
1138: 'CURLOPT_PROXYTYPE',
1139: 'CURLOPT_REDIR_PROTOCOLS',
1140: 'CURLOPT_RESUME_FROM',
1141: 'CURLOPT_SOCKS5_AUTH',
1142: 'CURLOPT_SSL_OPTIONS',
1143: 'CURLOPT_SSL_VERIFYHOST',
1144: 'CURLOPT_SSLVERSION',
1145: 'CURLOPT_PROXY_SSL_OPTIONS',
1146: 'CURLOPT_PROXY_SSL_VERIFYHOST',
1147: 'CURLOPT_PROXY_SSLVERSION',
1148: 'CURLOPT_STREAM_WEIGHT',
1149: 'CURLOPT_TCP_KEEPALIVE',
1150: 'CURLOPT_TCP_KEEPIDLE',
1151: 'CURLOPT_TCP_KEEPINTVL',
1152: 'CURLOPT_TIMECONDITION',
1153: 'CURLOPT_TIMEOUT',
1154: 'CURLOPT_TIMEOUT_MS',
1155: 'CURLOPT_TIMEVALUE',
1156: 'CURLOPT_TIMEVALUE_LARGE',
1157: 'CURLOPT_MAX_RECV_SPEED_LARGE',
1158: 'CURLOPT_SSH_AUTH_TYPES',
1159: 'CURLOPT_IPRESOLVE',
1160: 'CURLOPT_FTP_FILEMETHOD',
1161: ];
1162: foreach ($intConstants as $constName) {
1163: if (defined($constName) && constant($constName) === $curlOpt) {
1164: return new IntegerType();
1165: }
1166: }
1167:
1168: $nullableStringConstants = [
1169: 'CURLOPT_CUSTOMREQUEST',
1170: 'CURLOPT_DNS_INTERFACE',
1171: 'CURLOPT_DNS_LOCAL_IP4',
1172: 'CURLOPT_DNS_LOCAL_IP6',
1173: 'CURLOPT_DOH_URL',
1174: 'CURLOPT_FTP_ACCOUNT',
1175: 'CURLOPT_FTPPORT',
1176: 'CURLOPT_HSTS',
1177: 'CURLOPT_KRBLEVEL',
1178: 'CURLOPT_RANGE',
1179: 'CURLOPT_RTSP_SESSION_ID',
1180: 'CURLOPT_UNIX_SOCKET_PATH',
1181: 'CURLOPT_XOAUTH2_BEARER',
1182: ];
1183: foreach ($nullableStringConstants as $constName) {
1184: if (defined($constName) && constant($constName) === $curlOpt) {
1185: return new UnionType([
1186: new NullType(),
1187: new IntersectionType([
1188: new StringType(),
1189: new AccessoryNonEmptyStringType(),
1190: ]),
1191: ]);
1192: }
1193: }
1194:
1195: $nonEmptyStringConstants = [
1196: 'CURLOPT_ABSTRACT_UNIX_SOCKET',
1197: 'CURLOPT_ALTSVC',
1198: 'CURLOPT_AWS_SIGV4',
1199: 'CURLOPT_CAINFO',
1200: 'CURLOPT_CAPATH',
1201: 'CURLOPT_COOKIE',
1202: 'CURLOPT_COOKIEJAR',
1203: 'CURLOPT_COOKIELIST',
1204: 'CURLOPT_DEFAULT_PROTOCOL',
1205: 'CURLOPT_DNS_SERVERS',
1206: 'CURLOPT_EGDSOCKET',
1207: 'CURLOPT_FTP_ALTERNATIVE_TO_USER',
1208: 'CURLOPT_INTERFACE',
1209: 'CURLOPT_KEYPASSWD',
1210: 'CURLOPT_KRB4LEVEL',
1211: 'CURLOPT_LOGIN_OPTIONS',
1212: 'CURLOPT_MAIL_AUTH',
1213: 'CURLOPT_MAIL_FROM',
1214: 'CURLOPT_NOPROXY',
1215: 'CURLOPT_PASSWORD',
1216: 'CURLOPT_PINNEDPUBLICKEY',
1217: 'CURLOPT_PROTOCOLS_STR',
1218: 'CURLOPT_PROXY_CAINFO',
1219: 'CURLOPT_PROXY_CAPATH',
1220: 'CURLOPT_PROXY_CRLFILE',
1221: 'CURLOPT_PROXY_ISSUERCERT',
1222: 'CURLOPT_PROXY_KEYPASSWD',
1223: 'CURLOPT_PROXY_PINNEDPUBLICKEY',
1224: 'CURLOPT_PROXY_SERVICE_NAME',
1225: 'CURLOPT_PROXY_SSL_CIPHER_LIST',
1226: 'CURLOPT_PROXY_SSLCERT',
1227: 'CURLOPT_PROXY_SSLCERTTYPE',
1228: 'CURLOPT_PROXY_SSLKEY',
1229: 'CURLOPT_PROXY_SSLKEYTYPE',
1230: 'CURLOPT_PROXY_TLS13_CIPHERS',
1231: 'CURLOPT_PROXY_TLSAUTH_PASSWORD',
1232: 'CURLOPT_PROXY_TLSAUTH_TYPE',
1233: 'CURLOPT_PROXY_TLSAUTH_USERNAME',
1234: 'CURLOPT_PROXYPASSWORD',
1235: 'CURLOPT_PROXYUSERNAME',
1236: 'CURLOPT_PROXYUSERPWD',
1237: 'CURLOPT_RANDOM_FILE',
1238: 'CURLOPT_REDIR_PROTOCOLS_STR',
1239: 'CURLOPT_REFERER',
1240: 'CURLOPT_REQUEST_TARGET',
1241: 'CURLOPT_RTSP_STREAM_URI',
1242: 'CURLOPT_RTSP_TRANSPORT',
1243: 'CURLOPT_SASL_AUTHZID',
1244: 'CURLOPT_SERVICE_NAME',
1245: 'CURLOPT_SOCKS5_GSSAPI_SERVICE',
1246: 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5',
1247: 'CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256',
1248: 'CURLOPT_SSH_PRIVATE_KEYFILE',
1249: 'CURLOPT_SSH_PUBLIC_KEYFILE',
1250: 'CURLOPT_SSL_CIPHER_LIST',
1251: 'CURLOPT_SSL_EC_CURVES',
1252: 'CURLOPT_SSLCERT',
1253: 'CURLOPT_SSLCERTPASSWD',
1254: 'CURLOPT_SSLCERTTYPE',
1255: 'CURLOPT_SSLENGINE',
1256: 'CURLOPT_SSLENGINE_DEFAULT',
1257: 'CURLOPT_SSLKEY',
1258: 'CURLOPT_SSLKEYPASSWD',
1259: 'CURLOPT_SSLKEYTYPE',
1260: 'CURLOPT_TLS13_CIPHERS',
1261: 'CURLOPT_TLSAUTH_PASSWORD',
1262: 'CURLOPT_TLSAUTH_TYPE',
1263: 'CURLOPT_TLSAUTH_USERNAME',
1264: 'CURLOPT_TRANSFER_ENCODING',
1265: 'CURLOPT_URL',
1266: 'CURLOPT_USERAGENT',
1267: 'CURLOPT_USERNAME',
1268: 'CURLOPT_USERPWD',
1269: ];
1270: foreach ($nonEmptyStringConstants as $constName) {
1271: if (defined($constName) && constant($constName) === $curlOpt) {
1272: return new IntersectionType([
1273: new StringType(),
1274: new AccessoryNonEmptyStringType(),
1275: ]);
1276: }
1277: }
1278:
1279: $stringConstants = [
1280: 'CURLOPT_COOKIEFILE',
1281: 'CURLOPT_ENCODING', // Alias: CURLOPT_ACCEPT_ENCODING
1282: 'CURLOPT_PRE_PROXY',
1283: 'CURLOPT_PRIVATE',
1284: 'CURLOPT_PROXY',
1285: ];
1286: foreach ($stringConstants as $constName) {
1287: if (defined($constName) && constant($constName) === $curlOpt) {
1288: return new StringType();
1289: }
1290: }
1291:
1292: $intArrayStringKeysConstants = [
1293: 'CURLOPT_HTTPHEADER',
1294: ];
1295: foreach ($intArrayStringKeysConstants as $constName) {
1296: if (defined($constName) && constant($constName) === $curlOpt) {
1297: return new ArrayType(new IntegerType(), new StringType());
1298: }
1299: }
1300:
1301: $arrayConstants = [
1302: 'CURLOPT_CONNECT_TO',
1303: 'CURLOPT_HTTP200ALIASES',
1304: 'CURLOPT_POSTQUOTE',
1305: 'CURLOPT_PROXYHEADER',
1306: 'CURLOPT_QUOTE',
1307: 'CURLOPT_RESOLVE',
1308: ];
1309: foreach ($arrayConstants as $constName) {
1310: if (defined($constName) && constant($constName) === $curlOpt) {
1311: return new ArrayType(new MixedType(), new MixedType());
1312: }
1313: }
1314:
1315: $arrayOrStringConstants = [
1316: 'CURLOPT_POSTFIELDS',
1317: ];
1318: foreach ($arrayOrStringConstants as $constName) {
1319: if (defined($constName) && constant($constName) === $curlOpt) {
1320: return new UnionType([
1321: new StringType(),
1322: new ArrayType(new MixedType(), new MixedType()),
1323: ]);
1324: }
1325: }
1326:
1327: $resourceConstants = [
1328: 'CURLOPT_FILE',
1329: 'CURLOPT_INFILE',
1330: 'CURLOPT_STDERR',
1331: 'CURLOPT_WRITEHEADER',
1332: ];
1333: foreach ($resourceConstants as $constName) {
1334: if (defined($constName) && constant($constName) === $curlOpt) {
1335: return new ResourceType();
1336: }
1337: }
1338:
1339: if (defined('CURLOPT_SHARE') && $curlOpt === CURLOPT_SHARE) {
1340: $phpversion = PhpVersionStaticAccessor::getInstance();
1341:
1342: if ($phpversion->supportsCurlShareHandle()) {
1343: $shareType = new ObjectType('CurlShareHandle');
1344: } else {
1345: $shareType = new ResourceType();
1346: }
1347: if ($phpversion->supportsCurlSharePersistentHandle()) {
1348: $shareType = TypeCombinator::union($shareType, new ObjectType('CurlSharePersistentHandle'));
1349: }
1350:
1351: return $shareType;
1352: }
1353:
1354: $curlHandleType = PhpVersionStaticAccessor::getInstance()->supportsCurlShareHandle()
1355: ? new ObjectType('CurlHandle')
1356: : new ResourceType();
1357:
1358: // callback options: [parameter types passed to the callback (after the handle), expected return type]
1359: $callbackConstants = [
1360: 'CURLOPT_WRITEFUNCTION' => [[new StringType()], new IntegerType()],
1361: 'CURLOPT_HEADERFUNCTION' => [[new StringType()], new IntegerType()],
1362: 'CURLOPT_READFUNCTION' => [[new ResourceType(), new IntegerType()], new StringType()],
1363: 'CURLOPT_PROGRESSFUNCTION' => [[new IntegerType(), new IntegerType(), new IntegerType(), new IntegerType()], new IntegerType()],
1364: 'CURLOPT_XFERINFOFUNCTION' => [[new IntegerType(), new IntegerType(), new IntegerType(), new IntegerType()], new IntegerType()],
1365: 'CURLOPT_PREREQFUNCTION' => [[new StringType(), new StringType(), new IntegerType(), new IntegerType()], new IntegerType()],
1366: ];
1367: foreach ($callbackConstants as $constName => [$paramTypes, $returnType]) {
1368: if (defined($constName) && constant($constName) === $curlOpt) {
1369: $parameters = [
1370: new DummyParameter('handle', $curlHandleType, false, PassedByReference::createNo(), false, null),
1371: ];
1372: foreach ($paramTypes as $i => $paramType) {
1373: $parameters[] = new DummyParameter('param' . $i, $paramType, false, PassedByReference::createNo(), false, null);
1374: }
1375:
1376: return new UnionType([
1377: new CallableType($parameters, $returnType, false),
1378: new NullType(),
1379: ]);
1380: }
1381: }
1382:
1383: // unknown constant
1384: return null;
1385: }
1386:
1387: private static function overrideParameterType(ParameterReflection $original, Type $type, Type $nativeType): ExtendedDummyParameter
1388: {
1389: $wrapped = self::wrapParameter($original);
1390:
1391: return new ExtendedDummyParameter(
1392: $wrapped->getName(),
1393: $type,
1394: $wrapped->isOptional(),
1395: $wrapped->passedByReference(),
1396: $wrapped->isVariadic(),
1397: $wrapped->getDefaultValue(),
1398: $nativeType,
1399: $type,
1400: $wrapped->getOutType(),
1401: $wrapped->isImmediatelyInvokedCallable(),
1402: $wrapped->getClosureThisType(),
1403: $wrapped->getAttributes(),
1404: $wrapped->getAllowedConstants(),
1405: $wrapped->isPureUnlessCallableIsImpureParameter(),
1406: $wrapped->isPureUnlessParameterPassedParameter(),
1407: );
1408: }
1409:
1410: /**
1411: * @param list<ParameterReflection> $parameters
1412: */
1413: private static function overrideAcceptorParameters(ParametersAcceptor $acceptor, array $parameters): ParametersAcceptor
1414: {
1415: if ($acceptor instanceof ExtendedParametersAcceptor) {
1416: return new ExtendedFunctionVariant(
1417: $acceptor->getTemplateTypeMap(),
1418: $acceptor->getResolvedTemplateTypeMap(),
1419: array_map(static fn (ParameterReflection $p): ExtendedParameterReflection => $p instanceof ExtendedParameterReflection ? $p : self::wrapParameter($p), $parameters),
1420: $acceptor->isVariadic(),
1421: $acceptor->getReturnType(),
1422: $acceptor->getPhpDocReturnType(),
1423: $acceptor->getNativeReturnType(),
1424: $acceptor->getCallSiteVarianceMap(),
1425: );
1426: }
1427:
1428: return new FunctionVariant(
1429: $acceptor->getTemplateTypeMap(),
1430: $acceptor->getResolvedTemplateTypeMap(),
1431: $parameters,
1432: $acceptor->isVariadic(),
1433: $acceptor->getReturnType(),
1434: TemplateTypeVarianceMap::createEmpty(),
1435: );
1436: }
1437:
1438: }
1439: