1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection\Php;
4:
5: use PhpParser\Node;
6: use PhpParser\Node\Expr\Variable;
7: use PhpParser\Node\FunctionLike;
8: use PhpParser\Node\Stmt\ClassMethod;
9: use PhpParser\Node\Stmt\Function_;
10: use PHPStan\Reflection\Assertions;
11: use PHPStan\Reflection\AttributeReflection;
12: use PHPStan\Reflection\ExtendedFunctionVariant;
13: use PHPStan\Reflection\ExtendedParameterReflection;
14: use PHPStan\Reflection\ExtendedParametersAcceptor;
15: use PHPStan\Reflection\FunctionReflection;
16: use PHPStan\Reflection\PassedByReference;
17: use PHPStan\ShouldNotHappenException;
18: use PHPStan\TrinaryLogic;
19: use PHPStan\Type\Generic\TemplateTypeMap;
20: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
21: use PHPStan\Type\MixedType;
22: use PHPStan\Type\Type;
23: use PHPStan\Type\TypehintHelper;
24: use function array_map;
25: use function array_reverse;
26: use function is_array;
27: use function is_string;
28: use function strtolower;
29:
30: /**
31: * @api
32: */
33: class PhpFunctionFromParserNodeReflection implements FunctionReflection, ExtendedParametersAcceptor
34: {
35:
36: /** @var Function_|ClassMethod|Node\PropertyHook */
37: private Node\FunctionLike $functionLike;
38:
39: /** @var list<ExtendedFunctionVariant>|null */
40: private ?array $variants = null;
41:
42: /**
43: * @param Function_|ClassMethod|Node\PropertyHook $functionLike
44: * @param Type[] $realParameterTypes
45: * @param Type[] $phpDocParameterTypes
46: * @param Type[] $realParameterDefaultValues
47: * @param array<string, list<AttributeReflection>> $parameterAttributes
48: * @param Type[] $parameterOutTypes
49: * @param array<string, bool> $immediatelyInvokedCallableParameters
50: * @param array<string, Type> $phpDocClosureThisTypeParameters
51: * @param list<AttributeReflection> $attributes
52: * @param array<string, bool> $pureUnlessCallableIsImpureParameters
53: */
54: public function __construct(
55: FunctionLike $functionLike,
56: private string $fileName,
57: private TemplateTypeMap $templateTypeMap,
58: private array $realParameterTypes,
59: private array $phpDocParameterTypes,
60: private array $realParameterDefaultValues,
61: private array $parameterAttributes,
62: private Type $realReturnType,
63: private ?Type $phpDocReturnType,
64: private ?Type $throwType,
65: private ?string $deprecatedDescription,
66: private bool $isDeprecated,
67: private bool $isInternal,
68: protected ?bool $isPure,
69: private bool $acceptsNamedArguments,
70: private Assertions $assertions,
71: private ?string $phpDocComment,
72: private array $parameterOutTypes,
73: private array $immediatelyInvokedCallableParameters,
74: private array $phpDocClosureThisTypeParameters,
75: private array $attributes,
76: private array $pureUnlessCallableIsImpureParameters,
77: )
78: {
79: $this->functionLike = $functionLike;
80: }
81:
82: /**
83: * @phpstan-assert-if-true PhpMethodFromParserNodeReflection $this
84: */
85: public function isMethodOrPropertyHook(): bool
86: {
87: return $this instanceof PhpMethodFromParserNodeReflection;
88: }
89:
90: protected function getFunctionLike(): FunctionLike
91: {
92: return $this->functionLike;
93: }
94:
95: public function getFileName(): string
96: {
97: return $this->fileName;
98: }
99:
100: public function getName(): string
101: {
102: if ($this->functionLike instanceof ClassMethod) {
103: return $this->functionLike->name->name;
104: }
105:
106: if (!$this->functionLike instanceof Function_) {
107: // PropertyHook is handled in PhpMethodFromParserNodeReflection subclass
108: throw new ShouldNotHappenException();
109: }
110:
111: if ($this->functionLike->namespacedName === null) {
112: throw new ShouldNotHappenException();
113: }
114:
115: return (string) $this->functionLike->namespacedName;
116: }
117:
118: public function getVariants(): array
119: {
120: return $this->variants ??= [
121: new ExtendedFunctionVariant(
122: $this->getTemplateTypeMap(),
123: $this->getResolvedTemplateTypeMap(),
124: $this->getParameters(),
125: $this->isVariadic(),
126: $this->getReturnType(),
127: $this->getPhpDocReturnType(),
128: $this->getNativeReturnType(),
129: ),
130: ];
131: }
132:
133: public function getOnlyVariant(): ExtendedParametersAcceptor
134: {
135: return $this;
136: }
137:
138: public function getNamedArgumentsVariants(): ?array
139: {
140: return null;
141: }
142:
143: public function getTemplateTypeMap(): TemplateTypeMap
144: {
145: return $this->templateTypeMap;
146: }
147:
148: public function getResolvedTemplateTypeMap(): TemplateTypeMap
149: {
150: return TemplateTypeMap::createEmpty();
151: }
152:
153: /**
154: * @return list<ExtendedParameterReflection>
155: */
156: public function getParameters(): array
157: {
158: $parameters = [];
159: $isOptional = true;
160:
161: /** @var Node\Param $parameter */
162: foreach (array_reverse($this->functionLike->getParams()) as $parameter) {
163: if ($parameter->default === null && !$parameter->variadic) {
164: $isOptional = false;
165: }
166:
167: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
168: throw new ShouldNotHappenException();
169: }
170:
171: if (isset($this->immediatelyInvokedCallableParameters[$parameter->var->name])) {
172: $immediatelyInvokedCallable = TrinaryLogic::createFromBoolean($this->immediatelyInvokedCallableParameters[$parameter->var->name]);
173: } else {
174: $immediatelyInvokedCallable = TrinaryLogic::createMaybe();
175: }
176:
177: if (isset($this->phpDocClosureThisTypeParameters[$parameter->var->name])) {
178: $closureThisType = $this->phpDocClosureThisTypeParameters[$parameter->var->name];
179: } else {
180: $closureThisType = null;
181: }
182:
183: $pureUnlessCallableIsImpureParameter = TrinaryLogic::createFromBoolean($this->pureUnlessCallableIsImpureParameters[$parameter->var->name] ?? false);
184:
185: $parameters[] = new PhpParameterFromParserNodeReflection(
186: $parameter->var->name,
187: $isOptional,
188: $this->realParameterTypes[$parameter->var->name],
189: $this->phpDocParameterTypes[$parameter->var->name] ?? null,
190: $parameter->byRef
191: ? PassedByReference::createCreatesNewVariable()
192: : PassedByReference::createNo(),
193: $this->realParameterDefaultValues[$parameter->var->name] ?? null,
194: $parameter->variadic,
195: $this->parameterOutTypes[$parameter->var->name] ?? null,
196: $immediatelyInvokedCallable,
197: $closureThisType,
198: $this->parameterAttributes[$parameter->var->name] ?? [],
199: $pureUnlessCallableIsImpureParameter,
200: );
201: }
202:
203: return array_reverse($parameters);
204: }
205:
206: public function isVariadic(): bool
207: {
208: foreach ($this->functionLike->getParams() as $parameter) {
209: if ($parameter->variadic) {
210: return true;
211: }
212: }
213:
214: return false;
215: }
216:
217: public function getReturnType(): Type
218: {
219: return TypehintHelper::decideType($this->realReturnType, $this->phpDocReturnType);
220: }
221:
222: public function getPhpDocReturnType(): Type
223: {
224: return $this->phpDocReturnType ?? new MixedType();
225: }
226:
227: public function getNativeReturnType(): Type
228: {
229: return $this->realReturnType;
230: }
231:
232: public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
233: {
234: return TemplateTypeVarianceMap::createEmpty();
235: }
236:
237: public function getDeprecatedDescription(): ?string
238: {
239: if ($this->isDeprecated) {
240: return $this->deprecatedDescription;
241: }
242:
243: return null;
244: }
245:
246: public function isDeprecated(): TrinaryLogic
247: {
248: return TrinaryLogic::createFromBoolean($this->isDeprecated);
249: }
250:
251: public function isInternal(): TrinaryLogic
252: {
253: return TrinaryLogic::createFromBoolean($this->isInternal);
254: }
255:
256: public function getThrowType(): ?Type
257: {
258: return $this->throwType;
259: }
260:
261: public function hasSideEffects(): TrinaryLogic
262: {
263: if ($this->getReturnType()->isVoid()->yes()) {
264: return TrinaryLogic::createYes();
265: }
266: if ($this->isPure !== null) {
267: return TrinaryLogic::createFromBoolean(!$this->isPure);
268: }
269:
270: return TrinaryLogic::createMaybe();
271: }
272:
273: public function isBuiltin(): bool
274: {
275: return false;
276: }
277:
278: public function isGenerator(): bool
279: {
280: return $this->nodeIsOrContainsYield($this->functionLike);
281: }
282:
283: public function acceptsNamedArguments(): TrinaryLogic
284: {
285: return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments);
286: }
287:
288: private function nodeIsOrContainsYield(Node $node): bool
289: {
290: if ($node instanceof Node\Expr\Yield_) {
291: return true;
292: }
293:
294: if ($node instanceof Node\Expr\YieldFrom) {
295: return true;
296: }
297:
298: foreach ($node->getSubNodeNames() as $nodeName) {
299: $nodeProperty = $node->$nodeName;
300:
301: if ($nodeProperty instanceof Node && $this->nodeIsOrContainsYield($nodeProperty)) {
302: return true;
303: }
304:
305: if (!is_array($nodeProperty)) {
306: continue;
307: }
308:
309: foreach ($nodeProperty as $nodePropertyArrayItem) {
310: if ($nodePropertyArrayItem instanceof Node && $this->nodeIsOrContainsYield($nodePropertyArrayItem)) {
311: return true;
312: }
313: }
314: }
315:
316: return false;
317: }
318:
319: public function getAsserts(): Assertions
320: {
321: return $this->assertions;
322: }
323:
324: public function getDocComment(): ?string
325: {
326: return $this->phpDocComment;
327: }
328:
329: public function returnsByReference(): TrinaryLogic
330: {
331: return TrinaryLogic::createFromBoolean($this->functionLike->returnsByRef());
332: }
333:
334: public function isPure(): TrinaryLogic
335: {
336: if ($this->isPure === null) {
337: return TrinaryLogic::createMaybe();
338: }
339:
340: return TrinaryLogic::createFromBoolean($this->isPure);
341: }
342:
343: /**
344: * @return array<string, TrinaryLogic>
345: */
346: public function getPureUnlessCallableIsImpureParameters(): array
347: {
348: return array_map(static fn (bool $value): TrinaryLogic => TrinaryLogic::createFromBoolean($value), $this->pureUnlessCallableIsImpureParameters);
349: }
350:
351: public function getAttributes(): array
352: {
353: return $this->attributes;
354: }
355:
356: public function mustUseReturnValue(): TrinaryLogic
357: {
358: foreach ($this->attributes as $attrib) {
359: if (strtolower($attrib->getName()) === 'nodiscard') {
360: return TrinaryLogic::createYes();
361: }
362: }
363: return TrinaryLogic::createNo();
364: }
365:
366: }
367: