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