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