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