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\Node\NodeScanner;
11: use PHPStan\Reflection\Assertions;
12: use PHPStan\Reflection\AttributeReflection;
13: use PHPStan\Reflection\ExtendedFunctionVariant;
14: use PHPStan\Reflection\ExtendedParameterReflection;
15: use PHPStan\Reflection\ExtendedParametersAcceptor;
16: use PHPStan\Reflection\FunctionReflection;
17: use PHPStan\Reflection\PassedByReference;
18: use PHPStan\ShouldNotHappenException;
19: use PHPStan\TrinaryLogic;
20: use PHPStan\Type\Generic\TemplateTypeMap;
21: use PHPStan\Type\Generic\TemplateTypeVarianceMap;
22: use PHPStan\Type\MixedType;
23: use PHPStan\Type\Type;
24: use PHPStan\Type\TypehintHelper;
25: use function array_map;
26: use function array_reverse;
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: // the yield scan walks the whole body; reflections for the same node are
281: // recreated per ask, so the answer is memoized on the AST node itself
282: // and lives and dies with the parser-cached AST
283: $cached = $this->functionLike->getAttribute('phpstanIsGenerator');
284: if ($cached !== null) {
285: return $cached;
286: }
287:
288: $isGenerator = NodeScanner::nodeIsOrContainsYield($this->functionLike);
289: $this->functionLike->setAttribute('phpstanIsGenerator', $isGenerator);
290:
291: return $isGenerator;
292: }
293:
294: public function acceptsNamedArguments(): TrinaryLogic
295: {
296: return TrinaryLogic::createFromBoolean($this->acceptsNamedArguments);
297: }
298:
299: public function getAsserts(): Assertions
300: {
301: return $this->assertions;
302: }
303:
304: public function getDocComment(): ?string
305: {
306: return $this->phpDocComment;
307: }
308:
309: public function returnsByReference(): TrinaryLogic
310: {
311: return TrinaryLogic::createFromBoolean($this->functionLike->returnsByRef());
312: }
313:
314: public function isPure(): TrinaryLogic
315: {
316: if ($this->isPure === null) {
317: return TrinaryLogic::createMaybe();
318: }
319:
320: return TrinaryLogic::createFromBoolean($this->isPure);
321: }
322:
323: /**
324: * @return array<string, TrinaryLogic>
325: */
326: public function getPureUnlessCallableIsImpureParameters(): array
327: {
328: return array_map(static fn (bool $value): TrinaryLogic => TrinaryLogic::createFromBoolean($value), $this->pureUnlessCallableIsImpureParameters);
329: }
330:
331: public function getAttributes(): array
332: {
333: return $this->attributes;
334: }
335:
336: public function mustUseReturnValue(): TrinaryLogic
337: {
338: foreach ($this->attributes as $attrib) {
339: if (strtolower($attrib->getName()) === 'nodiscard') {
340: return TrinaryLogic::createYes();
341: }
342: }
343: return TrinaryLogic::createNo();
344: }
345:
346: }
347: