1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\Analyser\OutOfClassScope;
6: use PHPStan\Reflection\ClassMemberAccessAnswerer;
7: use PHPStan\Reflection\Native\NativeParameterReflection;
8: use PHPStan\Reflection\ParameterReflection;
9: use PHPStan\Reflection\ParametersAcceptor;
10: use PHPStan\ShouldNotHappenException;
11: use PHPStan\TrinaryLogic;
12: use PHPStan\Type\Generic\TemplateType;
13: use PHPStan\Type\Generic\TemplateTypeHelper;
14: use PHPStan\Type\Generic\TemplateTypeMap;
15: use PHPStan\Type\Generic\TemplateTypeVariance;
16: use PHPStan\Type\Traits\MaybeIterableTypeTrait;
17: use PHPStan\Type\Traits\MaybeObjectTypeTrait;
18: use PHPStan\Type\Traits\MaybeOffsetAccessibleTypeTrait;
19: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
20: use PHPStan\Type\Traits\NonRemoveableTypeTrait;
21: use PHPStan\Type\Traits\TruthyBooleanTypeTrait;
22: use PHPStan\Type\Traits\UndecidedComparisonCompoundTypeTrait;
23: use function array_map;
24: use function array_merge;
25: use function implode;
26: use function sprintf;
27:
28: /** @api */
29: class CallableType implements CompoundType, ParametersAcceptor
30: {
31:
32: use MaybeIterableTypeTrait;
33: use MaybeObjectTypeTrait;
34: use MaybeOffsetAccessibleTypeTrait;
35: use TruthyBooleanTypeTrait;
36: use UndecidedComparisonCompoundTypeTrait;
37: use NonRemoveableTypeTrait;
38: use NonGeneralizableTypeTrait;
39:
40: /** @var array<int, ParameterReflection> */
41: private array $parameters;
42:
43: private Type $returnType;
44:
45: private bool $isCommonCallable;
46:
47: /**
48: * @api
49: * @param array<int, ParameterReflection> $parameters
50: */
51: public function __construct(
52: ?array $parameters = null,
53: ?Type $returnType = null,
54: private bool $variadic = true,
55: )
56: {
57: $this->parameters = $parameters ?? [];
58: $this->returnType = $returnType ?? new MixedType();
59: $this->isCommonCallable = $parameters === null && $returnType === null;
60: }
61:
62: /**
63: * @return string[]
64: */
65: public function getReferencedClasses(): array
66: {
67: $classes = [];
68: foreach ($this->parameters as $parameter) {
69: $classes = array_merge($classes, $parameter->getType()->getReferencedClasses());
70: }
71:
72: return array_merge($classes, $this->returnType->getReferencedClasses());
73: }
74:
75: public function accepts(Type $type, bool $strictTypes): TrinaryLogic
76: {
77: if ($type instanceof CompoundType && !$type instanceof self) {
78: return $type->isAcceptedBy($this, $strictTypes);
79: }
80:
81: return $this->isSuperTypeOfInternal($type, true);
82: }
83:
84: public function isSuperTypeOf(Type $type): TrinaryLogic
85: {
86: if ($type instanceof CompoundType && !$type instanceof self) {
87: return $type->isSubTypeOf($this);
88: }
89:
90: return $this->isSuperTypeOfInternal($type, false);
91: }
92:
93: private function isSuperTypeOfInternal(Type $type, bool $treatMixedAsAny): TrinaryLogic
94: {
95: $isCallable = $type->isCallable();
96: if ($isCallable->no() || $this->isCommonCallable) {
97: return $isCallable;
98: }
99:
100: static $scope;
101: if ($scope === null) {
102: $scope = new OutOfClassScope();
103: }
104:
105: $variantsResult = null;
106: foreach ($type->getCallableParametersAcceptors($scope) as $variant) {
107: $isSuperType = CallableTypeHelper::isParametersAcceptorSuperTypeOf($this, $variant, $treatMixedAsAny);
108: if ($variantsResult === null) {
109: $variantsResult = $isSuperType;
110: } else {
111: $variantsResult = $variantsResult->or($isSuperType);
112: }
113: }
114:
115: if ($variantsResult === null) {
116: throw new ShouldNotHappenException();
117: }
118:
119: return $isCallable->and($variantsResult);
120: }
121:
122: public function isSubTypeOf(Type $otherType): TrinaryLogic
123: {
124: if ($otherType instanceof IntersectionType || $otherType instanceof UnionType) {
125: return $otherType->isSuperTypeOf($this);
126: }
127:
128: return $otherType->isCallable()
129: ->and($otherType instanceof self ? TrinaryLogic::createYes() : TrinaryLogic::createMaybe());
130: }
131:
132: public function isAcceptedBy(Type $acceptingType, bool $strictTypes): TrinaryLogic
133: {
134: return $this->isSubTypeOf($acceptingType);
135: }
136:
137: public function equals(Type $type): bool
138: {
139: return $type instanceof self;
140: }
141:
142: public function describe(VerbosityLevel $level): string
143: {
144: return $level->handle(
145: static fn (): string => 'callable',
146: fn (): string => sprintf(
147: 'callable(%s): %s',
148: implode(', ', array_map(
149: static fn (ParameterReflection $param): string => sprintf(
150: '%s%s%s',
151: $param->isVariadic() ? '...' : '',
152: $param->getType()->describe($level),
153: $param->isOptional() && !$param->isVariadic() ? '=' : '',
154: ),
155: $this->getParameters(),
156: )),
157: $this->returnType->describe($level),
158: ),
159: );
160: }
161:
162: public function isCallable(): TrinaryLogic
163: {
164: return TrinaryLogic::createYes();
165: }
166:
167: /**
168: * @return ParametersAcceptor[]
169: */
170: public function getCallableParametersAcceptors(ClassMemberAccessAnswerer $scope): array
171: {
172: return [$this];
173: }
174:
175: public function toNumber(): Type
176: {
177: return new ErrorType();
178: }
179:
180: public function toString(): Type
181: {
182: return new ErrorType();
183: }
184:
185: public function toInteger(): Type
186: {
187: return new ErrorType();
188: }
189:
190: public function toFloat(): Type
191: {
192: return new ErrorType();
193: }
194:
195: public function toArray(): Type
196: {
197: return new ArrayType(new MixedType(), new MixedType());
198: }
199:
200: public function getTemplateTypeMap(): TemplateTypeMap
201: {
202: return TemplateTypeMap::createEmpty();
203: }
204:
205: public function getResolvedTemplateTypeMap(): TemplateTypeMap
206: {
207: return TemplateTypeMap::createEmpty();
208: }
209:
210: /**
211: * @return array<int, ParameterReflection>
212: */
213: public function getParameters(): array
214: {
215: return $this->parameters;
216: }
217:
218: public function isVariadic(): bool
219: {
220: return $this->variadic;
221: }
222:
223: public function getReturnType(): Type
224: {
225: return $this->returnType;
226: }
227:
228: public function inferTemplateTypes(Type $receivedType): TemplateTypeMap
229: {
230: if ($receivedType instanceof UnionType || $receivedType instanceof IntersectionType) {
231: return $receivedType->inferTemplateTypesOn($this);
232: }
233:
234: if (! $receivedType->isCallable()->yes()) {
235: return TemplateTypeMap::createEmpty();
236: }
237:
238: $parametersAcceptors = $receivedType->getCallableParametersAcceptors(new OutOfClassScope());
239:
240: $typeMap = TemplateTypeMap::createEmpty();
241:
242: foreach ($parametersAcceptors as $parametersAcceptor) {
243: $typeMap = $typeMap->union($this->inferTemplateTypesOnParametersAcceptor($parametersAcceptor));
244: }
245:
246: return $typeMap;
247: }
248:
249: private function inferTemplateTypesOnParametersAcceptor(ParametersAcceptor $parametersAcceptor): TemplateTypeMap
250: {
251: $typeMap = TemplateTypeMap::createEmpty();
252: $args = $parametersAcceptor->getParameters();
253: $returnType = $parametersAcceptor->getReturnType();
254:
255: foreach ($this->getParameters() as $i => $param) {
256: $paramType = $param->getType();
257: if (isset($args[$i])) {
258: $argType = $args[$i]->getType();
259: } elseif ($paramType instanceof TemplateType) {
260: $argType = TemplateTypeHelper::resolveToBounds($paramType);
261: } else {
262: $argType = new NeverType();
263: }
264:
265: $typeMap = $typeMap->union($paramType->inferTemplateTypes($argType)->convertToLowerBoundTypes());
266: }
267:
268: return $typeMap->union($this->getReturnType()->inferTemplateTypes($returnType));
269: }
270:
271: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
272: {
273: $references = $this->getReturnType()->getReferencedTemplateTypes(
274: $positionVariance->compose(TemplateTypeVariance::createCovariant()),
275: );
276:
277: $paramVariance = $positionVariance->compose(TemplateTypeVariance::createContravariant());
278:
279: foreach ($this->getParameters() as $param) {
280: foreach ($param->getType()->getReferencedTemplateTypes($paramVariance) as $reference) {
281: $references[] = $reference;
282: }
283: }
284:
285: return $references;
286: }
287:
288: public function traverse(callable $cb): Type
289: {
290: if ($this->isCommonCallable) {
291: return $this;
292: }
293:
294: $parameters = array_map(static function (ParameterReflection $param) use ($cb): NativeParameterReflection {
295: $defaultValue = $param->getDefaultValue();
296: return new NativeParameterReflection(
297: $param->getName(),
298: $param->isOptional(),
299: $cb($param->getType()),
300: $param->passedByReference(),
301: $param->isVariadic(),
302: $defaultValue !== null ? $cb($defaultValue) : null,
303: );
304: }, $this->getParameters());
305:
306: return new self(
307: $parameters,
308: $cb($this->getReturnType()),
309: $this->isVariadic(),
310: );
311: }
312:
313: public function isArray(): TrinaryLogic
314: {
315: return TrinaryLogic::createMaybe();
316: }
317:
318: public function isOversizedArray(): TrinaryLogic
319: {
320: return TrinaryLogic::createNo();
321: }
322:
323: public function isString(): TrinaryLogic
324: {
325: return TrinaryLogic::createMaybe();
326: }
327:
328: public function isNumericString(): TrinaryLogic
329: {
330: return TrinaryLogic::createNo();
331: }
332:
333: public function isNonEmptyString(): TrinaryLogic
334: {
335: return TrinaryLogic::createMaybe();
336: }
337:
338: public function isNonFalsyString(): TrinaryLogic
339: {
340: return TrinaryLogic::createMaybe();
341: }
342:
343: public function isLiteralString(): TrinaryLogic
344: {
345: return TrinaryLogic::createMaybe();
346: }
347:
348: public function isCommonCallable(): bool
349: {
350: return $this->isCommonCallable;
351: }
352:
353: /**
354: * @param mixed[] $properties
355: */
356: public static function __set_state(array $properties): Type
357: {
358: return new self(
359: (bool) $properties['isCommonCallable'] ? null : $properties['parameters'],
360: (bool) $properties['isCommonCallable'] ? null : $properties['returnType'],
361: $properties['variadic'],
362: );
363: }
364:
365: }
366: