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: */
45: public function __construct(
46: FunctionLike $functionLike,
47: private string $fileName,
48: private TemplateTypeMap $templateTypeMap,
49: private array $realParameterTypes,
50: private array $phpDocParameterTypes,
51: private array $realParameterDefaultValues,
52: private Type $realReturnType,
53: private ?Type $phpDocReturnType,
54: private ?Type $throwType,
55: private ?string $deprecatedDescription,
56: private bool $isDeprecated,
57: private bool $isInternal,
58: private bool $isFinal,
59: private ?bool $isPure,
60: private bool $acceptsNamedArguments,
61: private Assertions $assertions,
62: private ?string $phpDocComment,
63: private array $parameterOutTypes,
64: )
65: {
66: $this->functionLike = $functionLike;
67: }
68:
69: protected function getFunctionLike(): FunctionLike
70: {
71: return $this->functionLike;
72: }
73:
74: public function getFileName(): string
75: {
76: return $this->fileName;
77: }
78:
79: public function getName(): string
80: {
81: if ($this->functionLike instanceof ClassMethod) {
82: return $this->functionLike->name->name;
83: }
84:
85: if ($this->functionLike->namespacedName === null) {
86: throw new ShouldNotHappenException();
87: }
88:
89: return (string) $this->functionLike->namespacedName;
90: }
91:
92: /**
93: * @return ParametersAcceptorWithPhpDocs[]
94: */
95: public function getVariants(): array
96: {
97: if ($this->variants === null) {
98: $this->variants = [
99: new FunctionVariantWithPhpDocs(
100: $this->templateTypeMap,
101: null,
102: $this->getParameters(),
103: $this->isVariadic(),
104: $this->getReturnType(),
105: $this->phpDocReturnType ?? new MixedType(),
106: $this->realReturnType,
107: ),
108: ];
109: }
110:
111: return $this->variants;
112: }
113:
114: /**
115: * @return ParameterReflectionWithPhpDocs[]
116: */
117: private function getParameters(): array
118: {
119: $parameters = [];
120: $isOptional = true;
121:
122: /** @var Node\Param $parameter */
123: foreach (array_reverse($this->functionLike->getParams()) as $parameter) {
124: if ($parameter->default === null && !$parameter->variadic) {
125: $isOptional = false;
126: }
127:
128: if (!$parameter->var instanceof Variable || !is_string($parameter->var->name)) {
129: throw new ShouldNotHappenException();
130: }
131: $parameters[] = new PhpParameterFromParserNodeReflection(
132: $parameter->var->name,
133: $isOptional,
134: $this->realParameterTypes[$parameter->var->name],
135: $this->phpDocParameterTypes[$parameter->var->name] ?? null,
136: $parameter->byRef
137: ? PassedByReference::createCreatesNewVariable()
138: : PassedByReference::createNo(),
139: $this->realParameterDefaultValues[$parameter->var->name] ?? null,
140: $parameter->variadic,
141: $this->parameterOutTypes[$parameter->var->name] ?? null,
142: );
143: }
144:
145: return array_reverse($parameters);
146: }
147:
148: private function isVariadic(): bool
149: {
150: foreach ($this->functionLike->getParams() as $parameter) {
151: if ($parameter->variadic) {
152: return true;
153: }
154: }
155:
156: return false;
157: }
158:
159: private function getReturnType(): Type
160: {
161: return TypehintHelper::decideType($this->realReturnType, $this->phpDocReturnType);
162: }
163:
164: public function getDeprecatedDescription(): ?string
165: {
166: if ($this->isDeprecated) {
167: return $this->deprecatedDescription;
168: }
169:
170: return null;
171: }
172:
173: public function isDeprecated(): TrinaryLogic
174: {
175: return TrinaryLogic::createFromBoolean($this->isDeprecated);
176: }
177:
178: public function isInternal(): TrinaryLogic
179: {
180: return TrinaryLogic::createFromBoolean($this->isInternal);
181: }
182:
183: public function isFinal(): TrinaryLogic
184: {
185: $finalMethod = false;
186: if ($this->functionLike instanceof ClassMethod) {
187: $finalMethod = $this->functionLike->isFinal();
188: }
189: return TrinaryLogic::createFromBoolean($finalMethod || $this->isFinal);
190: }
191:
192: public function getThrowType(): ?Type
193: {
194: return $this->throwType;
195: }
196:
197: public function hasSideEffects(): TrinaryLogic
198: {
199: if ($this->getReturnType()->isVoid()->yes()) {
200: return TrinaryLogic::createYes();
201: }
202: if ($this->isPure !== null) {
203: return TrinaryLogic::createFromBoolean(!$this->isPure);
204: }
205:
206: return TrinaryLogic::createMaybe();
207: }
208:
209: public function isBuiltin(): bool
210: {
211: return false;
212: }
213:
214: public function isGenerator(): bool
215: {
216: return $this->nodeIsOrContainsYield($this->functionLike);
217: }
218:
219: public function acceptsNamedArguments(): bool
220: {
221: return $this->acceptsNamedArguments;
222: }
223:
224: private function nodeIsOrContainsYield(Node $node): bool
225: {
226: if ($node instanceof Node\Expr\Yield_) {
227: return true;
228: }
229:
230: if ($node instanceof Node\Expr\YieldFrom) {
231: return true;
232: }
233:
234: foreach ($node->getSubNodeNames() as $nodeName) {
235: $nodeProperty = $node->$nodeName;
236:
237: if ($nodeProperty instanceof Node && $this->nodeIsOrContainsYield($nodeProperty)) {
238: return true;
239: }
240:
241: if (!is_array($nodeProperty)) {
242: continue;
243: }
244:
245: foreach ($nodeProperty as $nodePropertyArrayItem) {
246: if ($nodePropertyArrayItem instanceof Node && $this->nodeIsOrContainsYield($nodePropertyArrayItem)) {
247: return true;
248: }
249: }
250: }
251:
252: return false;
253: }
254:
255: public function getAsserts(): Assertions
256: {
257: return $this->assertions;
258: }
259:
260: public function getDocComment(): ?string
261: {
262: return $this->phpDocComment;
263: }
264:
265: public function returnsByReference(): TrinaryLogic
266: {
267: return TrinaryLogic::createFromBoolean($this->functionLike->returnsByRef());
268: }
269:
270: }
271: