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