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