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: );
847: continue;
848: }
849:
850: $isParameterVariadic = $parameters[$i]->isVariadic() || $parameter->isVariadic();
851: $defaultValueLeft = $parameters[$i]->getDefaultValue();
852: $defaultValueRight = $parameter->getDefaultValue();
853: if ($defaultValueLeft !== null && $defaultValueRight !== null) {
854: $defaultValue = TypeCombinator::union($defaultValueLeft, $defaultValueRight);
855: } else {
856: $defaultValue = null;
857: }
858:
859: $type = TypeCombinator::union($parameters[$i]->getType(), $parameter->getType());
860: $nativeType = $parameters[$i]->getNativeType();
861: $phpDocType = $parameters[$i]->getPhpDocType();
862: $outType = $parameters[$i]->getOutType();
863: $immediatelyInvokedCallable = $parameters[$i]->isImmediatelyInvokedCallable();
864: $closureThisType = $parameters[$i]->getClosureThisType();
865: $attributes = $parameters[$i]->getAttributes();
866: if ($parameter instanceof ExtendedParameterReflection) {
867: $nativeType = TypeCombinator::union($nativeType, $parameter->getNativeType());
868: $phpDocType = TypeCombinator::union($phpDocType, $parameter->getPhpDocType());
869:
870: if ($parameter->getOutType() !== null) {
871: $outType = $outType === null ? null : TypeCombinator::union($outType, $parameter->getOutType());
872: } else {
873: $outType = null;
874: }
875:
876: if ($parameter->getClosureThisType() !== null && $closureThisType !== null) {
877: $closureThisType = TypeCombinator::union($closureThisType, $parameter->getClosureThisType());
878: } else {
879: $closureThisType = null;
880: }
881:
882: $immediatelyInvokedCallable = $parameter->isImmediatelyInvokedCallable()->or($immediatelyInvokedCallable);
883: $attributes = array_merge($attributes, $parameter->getAttributes());
884: } else {
885: $nativeType = new MixedType();
886: $phpDocType = $type;
887: $outType = null;
888: $immediatelyInvokedCallable = TrinaryLogic::createMaybe();
889: $closureThisType = null;
890: }
891:
892: if ($isParameterVariadic) {
893: // the variadic parameter swallows every parameter behind it,
894: // so their types have to be merged into it before they're dropped
895: $droppedParameters = array_merge(
896: array_slice($parameters, $i + 1),
897: array_slice($acceptor->getParameters(), $i + 1),
898: );
899: foreach ($droppedParameters as $droppedParameter) {
900: $type = TypeCombinator::union($type, $droppedParameter->getType());
901: if ($droppedParameter instanceof ExtendedParameterReflection) {
902: $nativeType = TypeCombinator::union($nativeType, $droppedParameter->getNativeType());
903: $phpDocType = TypeCombinator::union($phpDocType, $droppedParameter->getPhpDocType());
904: continue;
905: }
906:
907: $nativeType = new MixedType();
908: $phpDocType = TypeCombinator::union($phpDocType, $droppedParameter->getType());
909: }
910: }
911:
912: $allowedConstants = $parameters[$i]->getAllowedConstants();
913: if ($allowedConstants !== null) {
914: $otherAllowedConstants = $parameter instanceof ExtendedParameterReflection ? $parameter->getAllowedConstants() : null;
915: if ($otherAllowedConstants === null || !$allowedConstants->equals($otherAllowedConstants)) {
916: $allowedConstants = null;
917: }
918: }
919:
920: $leftPureUnless = $parameters[$i]->isPureUnlessCallableIsImpureParameter();
921: $rightPureUnless = $parameter instanceof ExtendedParameterReflection ? $parameter->isPureUnlessCallableIsImpureParameter() : TrinaryLogic::createNo();
922: $pureUnlessCallableIsImpureParameter = $leftPureUnless->equals($rightPureUnless) ? $leftPureUnless : TrinaryLogic::createMaybe();
923:
924: $parameters[$i] = new ExtendedDummyParameter(
925: $parameters[$i]->getName() !== $parameter->getName() ? sprintf('%s|%s', $parameters[$i]->getName(), $parameter->getName()) : $parameter->getName(),
926: $type,
927: $i + 1 > $minimumNumberOfParameters,
928: $parameters[$i]->passedByReference()->combine($parameter->passedByReference()),
929: $isParameterVariadic,
930: $defaultValue,
931: $nativeType,
932: $phpDocType,
933: $outType,
934: $immediatelyInvokedCallable,
935: $closureThisType,
936: $attributes,
937: $allowedConstants,
938: $pureUnlessCallableIsImpureParameter,
939: );
940:
941: if ($isParameterVariadic) {
942: $parameters = array_slice($parameters, 0, $i + 1);
943: break;
944: }
945: }
946: }
947:
948: $returnType = TypeCombinator::union(...$returnTypes);
949: $phpDocReturnType = $phpDocReturnTypes === [] ? null : TypeCombinator::union(...$phpDocReturnTypes);
950: $nativeReturnType = $nativeReturnTypes === [] ? null : TypeCombinator::union(...$nativeReturnTypes);
951:
952: if ($callableOccurred) {
953: return new ExtendedCallableFunctionVariant(
954: TemplateTypeMap::createEmpty(),
955: null,
956: array_values($parameters),
957: $isVariadic,
958: $returnType,
959: $phpDocReturnType ?? $returnType,
960: $nativeReturnType ?? new MixedType(),
961: null,
962: $throwPoints,
963: $isPure,
964: $impurePoints,
965: $invalidateExpressions,
966: $usedVariables,
967: $acceptsNamedArguments,
968: $mustUseReturnValue,
969: isStatic: $isStaticClosure,
970: );
971: }
972:
973: return new ExtendedFunctionVariant(
974: TemplateTypeMap::createEmpty(),
975: null,
976: array_values($parameters),
977: $isVariadic,
978: $returnType,
979: $phpDocReturnType ?? $returnType,
980: $nativeReturnType ?? new MixedType(),
981: );
982: }
983:
984: private static function wrapAcceptor(ParametersAcceptor $acceptor): ExtendedParametersAcceptor
985: {
986: if ($acceptor instanceof ExtendedParametersAcceptor) {
987: return $acceptor;
988: }
989:
990: if ($acceptor instanceof CallableParametersAcceptor) {
991: return new ExtendedCallableFunctionVariant(
992: $acceptor->getTemplateTypeMap(),
993: $acceptor->getResolvedTemplateTypeMap(),
994: array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()),
995: $acceptor->isVariadic(),
996: $acceptor->getReturnType(),
997: $acceptor->getReturnType(),
998: new MixedType(),
999: TemplateTypeVarianceMap::createEmpty(),
1000: $acceptor->getThrowPoints(),
1001: $acceptor->isPure(),
1002: $acceptor->getImpurePoints(),
1003: $acceptor->getInvalidateExpressions(),
1004: $acceptor->getUsedVariables(),
1005: $acceptor->acceptsNamedArguments(),
1006: $acceptor->mustUseReturnValue(),
1007: $acceptor->getAsserts(),
1008: $acceptor->isStaticClosure(),
1009: );
1010: }
1011:
1012: return new ExtendedFunctionVariant(
1013: $acceptor->getTemplateTypeMap(),
1014: $acceptor->getResolvedTemplateTypeMap(),
1015: array_map(static fn (ParameterReflection $parameter): ExtendedParameterReflection => self::wrapParameter($parameter), $acceptor->getParameters()),
1016: $acceptor->isVariadic(),
1017: $acceptor->getReturnType(),
1018: $acceptor->getReturnType(),
1019: new MixedType(),
1020: TemplateTypeVarianceMap::createEmpty(),
1021: );
1022: }
1023:
1024: private static function wrapParameter(ParameterReflection $parameter): ExtendedParameterReflection
1025: {
1026: return $parameter instanceof ExtendedParameterReflection ? $parameter : new ExtendedDummyParameter(
1027: $parameter->getName(),
1028: $parameter->getType(),
1029: $parameter->isOptional(),
1030: $parameter->passedByReference(),
1031: $parameter->isVariadic(),
1032: $parameter->getDefaultValue(),
1033: new MixedType(),
1034: $parameter->getType(),
1035: null,
1036: TrinaryLogic::createMaybe(),
1037: null,
1038: [],
1039: null,
1040: TrinaryLogic::createNo(),
1041: );
1042: }
1043:
1044: private static function getCurlOptValueType(int $curlOpt): ?Type
1045: {
1046: if (defined('CURLOPT_SSL_VERIFYHOST') && $curlOpt === CURLOPT_SSL_VERIFYHOST) {
1047: return new UnionType([new ConstantIntegerType(0), new ConstantIntegerType(2)]);
1048: }
1049:
1050: $boolConstants = [
1051: 'CURLOPT_AUTOREFERER',
1052: 'CURLOPT_COOKIESESSION',
1053: 'CURLOPT_CERTINFO',
1054: 'CURLOPT_CONNECT_ONLY',
1055: 'CURLOPT_CRLF',
1056: 'CURLOPT_DISALLOW_USERNAME_IN_URL',
1057: 'CURLOPT_DNS_SHUFFLE_ADDRESSES',
1058: 'CURLOPT_HAPROXYPROTOCOL',
1059: 'CURLOPT_SSH_COMPRESSION',
1060: 'CURLOPT_DNS_USE_GLOBAL_CACHE',
1061: 'CURLOPT_FAILONERROR',
1062: 'CURLOPT_SSL_FALSESTART',
1063: 'CURLOPT_FILETIME',
1064: 'CURLOPT_FOLLOWLOCATION',
1065: 'CURLOPT_FORBID_REUSE',
1066: 'CURLOPT_FRESH_CONNECT',
1067: 'CURLOPT_FTP_USE_EPRT',
1068: 'CURLOPT_FTP_USE_EPSV',
1069: 'CURLOPT_FTP_CREATE_MISSING_DIRS',
1070: 'CURLOPT_FTPAPPEND',
1071: 'CURLOPT_TCP_NODELAY',
1072: 'CURLOPT_FTPASCII',
1073: 'CURLOPT_FTPLISTONLY',
1074: 'CURLOPT_HEADER',
1075: 'CURLOPT_HTTP09_ALLOWED',
1076: 'CURLOPT_HTTPGET',
1077: 'CURLOPT_HTTPPROXYTUNNEL',
1078: 'CURLOPT_HTTP_CONTENT_DECODING',
1079: 'CURLOPT_KEEP_SENDING_ON_ERROR',
1080: 'CURLOPT_MUTE',
1081: 'CURLOPT_NETRC',
1082: 'CURLOPT_NOBODY',
1083: 'CURLOPT_NOPROGRESS',
1084: 'CURLOPT_NOSIGNAL',
1085: 'CURLOPT_PATH_AS_IS',
1086: 'CURLOPT_PIPEWAIT',
1087: 'CURLOPT_POST',
1088: 'CURLOPT_PUT',
1089: 'CURLOPT_RETURNTRANSFER',
1090: 'CURLOPT_SASL_IR',
1091: 'CURLOPT_SSL_ENABLE_ALPN',
1092: 'CURLOPT_SSL_ENABLE_NPN',
1093: 'CURLOPT_SSL_VERIFYPEER',
1094: 'CURLOPT_SSL_VERIFYSTATUS',
1095: 'CURLOPT_PROXY_SSL_VERIFYPEER',
1096: 'CURLOPT_SUPPRESS_CONNECT_HEADERS',
1097: 'CURLOPT_TCP_FASTOPEN',
1098: 'CURLOPT_TFTP_NO_OPTIONS',
1099: 'CURLOPT_TRANSFERTEXT',
1100: 'CURLOPT_UNRESTRICTED_AUTH',
1101: 'CURLOPT_UPLOAD',
1102: 'CURLOPT_VERBOSE',
1103: ];
1104: foreach ($boolConstants as $constName) {
1105: if (defined($constName) && constant($constName) === $curlOpt) {
1106: return new BooleanType();
1107: }
1108: }
1109:
1110: $intConstants = [
1111: 'CURLOPT_BUFFERSIZE',
1112: 'CURLOPT_CONNECTTIMEOUT',
1113: 'CURLOPT_CONNECTTIMEOUT_MS',
1114: 'CURLOPT_DNS_CACHE_TIMEOUT',
1115: 'CURLOPT_EXPECT_100_TIMEOUT_MS',
1116: 'CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS',
1117: 'CURLOPT_FTPSSLAUTH',
1118: 'CURLOPT_HEADEROPT',
1119: 'CURLOPT_HTTP_VERSION',
1120: 'CURLOPT_HTTPAUTH',
1121: 'CURLOPT_INFILESIZE',
1122: 'CURLOPT_LOW_SPEED_LIMIT',
1123: 'CURLOPT_LOW_SPEED_TIME',
1124: 'CURLOPT_MAXCONNECTS',
1125: 'CURLOPT_MAXREDIRS',
1126: 'CURLOPT_PORT',
1127: 'CURLOPT_POSTREDIR',
1128: 'CURLOPT_PROTOCOLS',
1129: 'CURLOPT_PROXYAUTH',
1130: 'CURLOPT_PROXYPORT',
1131: 'CURLOPT_PROXYTYPE',
1132: 'CURLOPT_REDIR_PROTOCOLS',
1133: 'CURLOPT_RESUME_FROM',
1134: 'CURLOPT_SOCKS5_AUTH',
1135: 'CURLOPT_SSL_OPTIONS',
1136: 'CURLOPT_SSL_VERIFYHOST',
1137: 'CURLOPT_SSLVERSION',
1138: 'CURLOPT_PROXY_SSL_OPTIONS',
1139: 'CURLOPT_PROXY_SSL_VERIFYHOST',
1140: 'CURLOPT_PROXY_SSLVERSION',
1141: 'CURLOPT_STREAM_WEIGHT',
1142: 'CURLOPT_TCP_KEEPALIVE',
1143: 'CURLOPT_TCP_KEEPIDLE',
1144: 'CURLOPT_TCP_KEEPINTVL',
1145: 'CURLOPT_TIMECONDITION',
1146: 'CURLOPT_TIMEOUT',
1147: 'CURLOPT_TIMEOUT_MS',
1148: 'CURLOPT_TIMEVALUE',
1149: 'CURLOPT_TIMEVALUE_LARGE',
1150: 'CURLOPT_MAX_RECV_SPEED_LARGE',
1151: 'CURLOPT_SSH_AUTH_TYPES',
1152: 'CURLOPT_IPRESOLVE',
1153: 'CURLOPT_FTP_FILEMETHOD',
1154: ];
1155: foreach ($intConstants as $constName) {
1156: if (defined($constName) && constant($constName) === $curlOpt) {
1157: return new IntegerType();
1158: }
1159: }
1160:
1161: $nullableStringConstants = [
1162: 'CURLOPT_CUSTOMREQUEST',
1163: 'CURLOPT_DNS_INTERFACE',
1164: 'CURLOPT_DNS_LOCAL_IP4',
1165: 'CURLOPT_DNS_LOCAL_IP6',
1166: 'CURLOPT_DOH_URL',
1167: 'CURLOPT_FTP_ACCOUNT',
1168: 'CURLOPT_FTPPORT',
1169: 'CURLOPT_HSTS',
1170: 'CURLOPT_KRBLEVEL',
1171: 'CURLOPT_RANGE',
1172: 'CURLOPT_RTSP_SESSION_ID',
1173: 'CURLOPT_UNIX_SOCKET_PATH',
1174: 'CURLOPT_XOAUTH2_BEARER',
1175: ];
1176: foreach ($nullableStringConstants as $constName) {
1177: if (defined($constName) && constant($constName) === $curlOpt) {
1178: return new UnionType([
1179: new NullType(),
1180: new IntersectionType([
1181: new StringType(),
1182: new AccessoryNonEmptyStringType(),
1183: ]),
1184: ]);
1185: }
1186: }
1187:
1188: $nonEmptyStringConstants = [
1189: 'CURLOPT_ABSTRACT_UNIX_SOCKET',
1190: 'CURLOPT_ALTSVC',
1191: 'CURLOPT_AWS_SIGV4',
1192: 'CURLOPT_CAINFO',
1193: 'CURLOPT_CAPATH',
1194: 'CURLOPT_COOKIE',
1195: 'CURLOPT_COOKIEJAR',
1196: 'CURLOPT_COOKIELIST',
1197: 'CURLOPT_DEFAULT_PROTOCOL',
1198: 'CURLOPT_DNS_SERVERS',
1199: 'CURLOPT_EGDSOCKET',
1200: 'CURLOPT_FTP_ALTERNATIVE_TO_USER',
1201: 'CURLOPT_INTERFACE',
1202: 'CURLOPT_KEYPASSWD',
1203: 'CURLOPT_KRB4LEVEL',
1204: 'CURLOPT_LOGIN_OPTIONS',
1205: 'CURLOPT_MAIL_AUTH',
1206: 'CURLOPT_MAIL_FROM',
1207: 'CURLOPT_NOPROXY',
1208: 'CURLOPT_PASSWORD',
1209: 'CURLOPT_PINNEDPUBLICKEY',
1210: 'CURLOPT_PROTOCOLS_STR',
1211: 'CURLOPT_PROXY_CAINFO',
1212: 'CURLOPT_PROXY_CAPATH',
1213: 'CURLOPT_PROXY_CRLFILE',
1214: 'CURLOPT_PROXY_ISSUERCERT',
1215: 'CURLOPT_PROXY_KEYPASSWD',
1216: 'CURLOPT_PROXY_PINNEDPUBLICKEY',
1217: 'CURLOPT_PROXY_SERVICE_NAME',
1218: 'CURLOPT_PROXY_SSL_CIPHER_LIST',
1219: 'CURLOPT_PROXY_SSLCERT',
1220: 'CURLOPT_PROXY_SSLCERTTYPE',
1221: 'CURLOPT_PROXY_SSLKEY',
1222: 'CURLOPT_PROXY_SSLKEYTYPE',
1223: 'CURLOPT_PROXY_TLS13_CIPHERS',
1224: 'CURLOPT_PROXY_TLSAUTH_PASSWORD',
1225: 'CURLOPT_PROXY_TLSAUTH_TYPE',
1226: 'CURLOPT_PROXY_TLSAUTH_USERNAME',
1227: 'CURLOPT_PROXYPASSWORD',
1228: 'CURLOPT_PROXYUSERNAME',
1229: 'CURLOPT_PROXYUSERPWD',
1230: 'CURLOPT_RANDOM_FILE',
1231: 'CURLOPT_REDIR_PROTOCOLS_STR',
1232: 'CURLOPT_REFERER',
1233: 'CURLOPT_REQUEST_TARGET',
1234: 'CURLOPT_RTSP_STREAM_URI',
1235: 'CURLOPT_RTSP_TRANSPORT',
1236: 'CURLOPT_SASL_AUTHZID',
1237: 'CURLOPT_SERVICE_NAME',
1238: 'CURLOPT_SOCKS5_GSSAPI_SERVICE',
1239: 'CURLOPT_SSH_HOST_PUBLIC_KEY_MD5',
1240: 'CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256',
1241: 'CURLOPT_SSH_PRIVATE_KEYFILE',
1242: 'CURLOPT_SSH_PUBLIC_KEYFILE',
1243: 'CURLOPT_SSL_CIPHER_LIST',
1244: 'CURLOPT_SSL_EC_CURVES',
1245: 'CURLOPT_SSLCERT',
1246: 'CURLOPT_SSLCERTPASSWD',
1247: 'CURLOPT_SSLCERTTYPE',
1248: 'CURLOPT_SSLENGINE',
1249: 'CURLOPT_SSLENGINE_DEFAULT',
1250: 'CURLOPT_SSLKEY',
1251: 'CURLOPT_SSLKEYPASSWD',
1252: 'CURLOPT_SSLKEYTYPE',
1253: 'CURLOPT_TLS13_CIPHERS',
1254: 'CURLOPT_TLSAUTH_PASSWORD',
1255: 'CURLOPT_TLSAUTH_TYPE',
1256: 'CURLOPT_TLSAUTH_USERNAME',
1257: 'CURLOPT_TRANSFER_ENCODING',
1258: 'CURLOPT_URL',
1259: 'CURLOPT_USERAGENT',
1260: 'CURLOPT_USERNAME',
1261: 'CURLOPT_USERPWD',
1262: ];
1263: foreach ($nonEmptyStringConstants as $constName) {
1264: if (defined($constName) && constant($constName) === $curlOpt) {
1265: return new IntersectionType([
1266: new StringType(),
1267: new AccessoryNonEmptyStringType(),
1268: ]);
1269: }
1270: }
1271:
1272: $stringConstants = [
1273: 'CURLOPT_COOKIEFILE',
1274: 'CURLOPT_ENCODING', // Alias: CURLOPT_ACCEPT_ENCODING
1275: 'CURLOPT_PRE_PROXY',
1276: 'CURLOPT_PRIVATE',
1277: 'CURLOPT_PROXY',
1278: ];
1279: foreach ($stringConstants as $constName) {
1280: if (defined($constName) && constant($constName) === $curlOpt) {
1281: return new StringType();
1282: }
1283: }
1284:
1285: $intArrayStringKeysConstants = [
1286: 'CURLOPT_HTTPHEADER',
1287: ];
1288: foreach ($intArrayStringKeysConstants as $constName) {
1289: if (defined($constName) && constant($constName) === $curlOpt) {
1290: return new ArrayType(new IntegerType(), new StringType());
1291: }
1292: }
1293:
1294: $arrayConstants = [
1295: 'CURLOPT_CONNECT_TO',
1296: 'CURLOPT_HTTP200ALIASES',
1297: 'CURLOPT_POSTQUOTE',
1298: 'CURLOPT_PROXYHEADER',
1299: 'CURLOPT_QUOTE',
1300: 'CURLOPT_RESOLVE',
1301: ];
1302: foreach ($arrayConstants as $constName) {
1303: if (defined($constName) && constant($constName) === $curlOpt) {
1304: return new ArrayType(new MixedType(), new MixedType());
1305: }
1306: }
1307:
1308: $arrayOrStringConstants = [
1309: 'CURLOPT_POSTFIELDS',
1310: ];
1311: foreach ($arrayOrStringConstants as $constName) {
1312: if (defined($constName) && constant($constName) === $curlOpt) {
1313: return new UnionType([
1314: new StringType(),
1315: new ArrayType(new MixedType(), new MixedType()),
1316: ]);
1317: }
1318: }
1319:
1320: $resourceConstants = [
1321: 'CURLOPT_FILE',
1322: 'CURLOPT_INFILE',
1323: 'CURLOPT_STDERR',
1324: 'CURLOPT_WRITEHEADER',
1325: ];
1326: foreach ($resourceConstants as $constName) {
1327: if (defined($constName) && constant($constName) === $curlOpt) {
1328: return new ResourceType();
1329: }
1330: }
1331:
1332: if (defined('CURLOPT_SHARE') && $curlOpt === CURLOPT_SHARE) {
1333: $phpversion = PhpVersionStaticAccessor::getInstance();
1334:
1335: if ($phpversion->supportsCurlShareHandle()) {
1336: $shareType = new ObjectType('CurlShareHandle');
1337: } else {
1338: $shareType = new ResourceType();
1339: }
1340: if ($phpversion->supportsCurlSharePersistentHandle()) {
1341: $shareType = TypeCombinator::union($shareType, new ObjectType('CurlSharePersistentHandle'));
1342: }
1343:
1344: return $shareType;
1345: }
1346:
1347: $curlHandleType = PhpVersionStaticAccessor::getInstance()->supportsCurlShareHandle()
1348: ? new ObjectType('CurlHandle')
1349: : new ResourceType();
1350:
1351: // callback options: [parameter types passed to the callback (after the handle), expected return type]
1352: $callbackConstants = [
1353: 'CURLOPT_WRITEFUNCTION' => [[new StringType()], new IntegerType()],
1354: 'CURLOPT_HEADERFUNCTION' => [[new StringType()], new IntegerType()],
1355: 'CURLOPT_READFUNCTION' => [[new ResourceType(), new IntegerType()], new StringType()],
1356: 'CURLOPT_PROGRESSFUNCTION' => [[new IntegerType(), new IntegerType(), new IntegerType(), new IntegerType()], new IntegerType()],
1357: 'CURLOPT_XFERINFOFUNCTION' => [[new IntegerType(), new IntegerType(), new IntegerType(), new IntegerType()], new IntegerType()],
1358: 'CURLOPT_PREREQFUNCTION' => [[new StringType(), new StringType(), new IntegerType(), new IntegerType()], new IntegerType()],
1359: ];
1360: foreach ($callbackConstants as $constName => [$paramTypes, $returnType]) {
1361: if (defined($constName) && constant($constName) === $curlOpt) {
1362: $parameters = [
1363: new DummyParameter('handle', $curlHandleType, false, PassedByReference::createNo(), false, null),
1364: ];
1365: foreach ($paramTypes as $i => $paramType) {
1366: $parameters[] = new DummyParameter('param' . $i, $paramType, false, PassedByReference::createNo(), false, null);
1367: }
1368:
1369: return new UnionType([
1370: new CallableType($parameters, $returnType, false),
1371: new NullType(),
1372: ]);
1373: }
1374: }
1375:
1376: // unknown constant
1377: return null;
1378: }
1379:
1380: private static function overrideParameterType(ParameterReflection $original, Type $type, Type $nativeType): ExtendedDummyParameter
1381: {
1382: $wrapped = self::wrapParameter($original);
1383:
1384: return new ExtendedDummyParameter(
1385: $wrapped->getName(),
1386: $type,
1387: $wrapped->isOptional(),
1388: $wrapped->passedByReference(),
1389: $wrapped->isVariadic(),
1390: $wrapped->getDefaultValue(),
1391: $nativeType,
1392: $type,
1393: $wrapped->getOutType(),
1394: $wrapped->isImmediatelyInvokedCallable(),
1395: $wrapped->getClosureThisType(),
1396: $wrapped->getAttributes(),
1397: $wrapped->getAllowedConstants(),
1398: $wrapped->isPureUnlessCallableIsImpureParameter(),
1399: );
1400: }
1401:
1402: /**
1403: * @param list<ParameterReflection> $parameters
1404: */
1405: private static function overrideAcceptorParameters(ParametersAcceptor $acceptor, array $parameters): ParametersAcceptor
1406: {
1407: if ($acceptor instanceof ExtendedParametersAcceptor) {
1408: return new ExtendedFunctionVariant(
1409: $acceptor->getTemplateTypeMap(),
1410: $acceptor->getResolvedTemplateTypeMap(),
1411: array_map(static fn (ParameterReflection $p): ExtendedParameterReflection => $p instanceof ExtendedParameterReflection ? $p : self::wrapParameter($p), $parameters),
1412: $acceptor->isVariadic(),
1413: $acceptor->getReturnType(),
1414: $acceptor->getPhpDocReturnType(),
1415: $acceptor->getNativeReturnType(),
1416: $acceptor->getCallSiteVarianceMap(),
1417: );
1418: }
1419:
1420: return new FunctionVariant(
1421: $acceptor->getTemplateTypeMap(),
1422: $acceptor->getResolvedTemplateTypeMap(),
1423: $parameters,
1424: $acceptor->isVariadic(),
1425: $acceptor->getReturnType(),
1426: TemplateTypeVarianceMap::createEmpty(),
1427: );
1428: }
1429:
1430: }
1431: