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