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