1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use LogicException;
8: use PhpParser\Node\Identifier;
9: use PhpParser\Node\Name;
10: use ReflectionClass as CoreReflectionClass;
11: use PHPStan\BetterReflection\Reflector\Reflector;
12:
13: use function array_key_exists;
14: use function assert;
15: use function sprintf;
16: use function strtolower;
17:
18: /** @psalm-immutable */
19: class ReflectionNamedType extends ReflectionType
20: {
21: private Reflector $reflector;
22: /**
23: * @var \PHPStan\BetterReflection\Reflection\ReflectionParameter|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionEnum|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant
24: */
25: private $owner;
26: /**
27: * @var mixed[]
28: */
29: private const BUILT_IN_TYPES = [
30: 'int' => null,
31: 'float' => null,
32: 'string' => null,
33: 'bool' => null,
34: 'callable' => null,
35: 'self' => null,
36: 'parent' => null,
37: 'array' => null,
38: 'iterable' => null,
39: 'object' => null,
40: 'void' => null,
41: 'mixed' => null,
42: 'static' => null,
43: 'null' => null,
44: 'never' => null,
45: 'false' => null,
46: 'true' => null,
47: ];
48:
49: /** @var non-empty-string */
50: private string $name;
51:
52: private bool $isIdentifier;
53:
54: /** @internal
55: * @param \PHPStan\BetterReflection\Reflection\ReflectionParameter|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionEnum|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant $owner
56: * @param \PhpParser\Node\Identifier|\PhpParser\Node\Name $type */
57: public function __construct(
58: Reflector $reflector,
59: $owner,
60: $type
61: ) {
62: $this->reflector = $reflector;
63: $this->owner = $owner;
64: $this->name = $type->toString();
65: $this->isIdentifier = $type instanceof Identifier;
66: }
67:
68: /**
69: * @return array<string, mixed>
70: */
71: public function exportToCache(): array
72: {
73: return [
74: 'name' => $this->name,
75: 'isIdentifier' => $this->isIdentifier,
76: ];
77: }
78:
79: /**
80: * @param array<string, mixed> $data
81: * @param ReflectionParameter|ReflectionMethod|ReflectionFunction|ReflectionEnum|ReflectionProperty|ReflectionClassConstant $owner
82: */
83: public static function importFromCache(Reflector $reflector, array $data, $owner): self
84: {
85: $reflection = new CoreReflectionClass(self::class);
86: /** @var self $ref */
87: $ref = $reflection->newInstanceWithoutConstructor();
88: $ref->reflector = $reflector;
89: $ref->owner = $owner;
90: $ref->name = $data['name'];
91: $ref->isIdentifier = $data['isIdentifier'];
92:
93: return $ref;
94: }
95:
96: /** @internal
97: * @param \PHPStan\BetterReflection\Reflection\ReflectionParameter|\PHPStan\BetterReflection\Reflection\ReflectionMethod|\PHPStan\BetterReflection\Reflection\ReflectionFunction|\PHPStan\BetterReflection\Reflection\ReflectionEnum|\PHPStan\BetterReflection\Reflection\ReflectionProperty|\PHPStan\BetterReflection\Reflection\ReflectionClassConstant $owner
98: * @return static */
99: public function withOwner($owner)
100: {
101: // The owner is only ever consulted when resolving self/parent/static
102: // to a class - any other name reads the same through any owner, so
103: // re-owning would only duplicate the instance: one fresh 'void' per
104: // inherited-method clone was ~45k duplicate instances in a PHPStan
105: // worker process.
106: switch (strtolower($this->name)) {
107: case 'self':
108: case 'parent':
109: case 'static':
110: $clone = clone $this;
111: $clone->owner = $owner;
112:
113: return $clone;
114: }
115:
116: return $this;
117: }
118:
119: /** @return non-empty-string */
120: public function getName(): string
121: {
122: return $this->name;
123: }
124:
125: /**
126: * Checks if it is a built-in type (i.e., it's not an object...)
127: *
128: * @see https://php.net/manual/en/reflectiontype.isbuiltin.php
129: */
130: public function isBuiltin(): bool
131: {
132: return array_key_exists(strtolower($this->name), self::BUILT_IN_TYPES);
133: }
134:
135: public function getClass(): ReflectionClass
136: {
137: if (! $this->isBuiltin()) {
138: return $this->reflector->reflectClass($this->name);
139: }
140:
141: if (
142: $this->owner instanceof ReflectionEnum
143: || $this->owner instanceof ReflectionFunction
144: || ($this->owner instanceof ReflectionParameter && $this->owner->getDeclaringFunction() instanceof ReflectionFunction)
145: ) {
146: throw new LogicException(sprintf('The type %s cannot be resolved to class', $this->name));
147: }
148:
149: $lowercaseName = strtolower($this->name);
150:
151: if ($lowercaseName === 'self') {
152: $class = $this->owner->getImplementingClass();
153: assert($class instanceof ReflectionClass);
154:
155: return $class;
156: }
157:
158: if ($lowercaseName === 'parent') {
159: $class = $this->owner->getDeclaringClass();
160: assert($class instanceof ReflectionClass);
161: $parentClass = $class->getParentClass();
162: assert($parentClass instanceof ReflectionClass);
163:
164: return $parentClass;
165: }
166:
167: if (
168: $this->owner instanceof ReflectionMethod
169: && $lowercaseName === 'static'
170: ) {
171: return $this->owner->getCurrentClass();
172: }
173:
174: throw new LogicException(sprintf('The type %s cannot be resolved to class', $this->name));
175: }
176:
177: public function allowsNull(): bool
178: {
179: switch (strtolower($this->name)) {
180: case 'mixed':
181: return true;
182: case 'null':
183: return true;
184: default:
185: return false;
186: }
187: }
188:
189: public function isIdentifier(): bool
190: {
191: return $this->isIdentifier;
192: }
193:
194: /** @return non-empty-string */
195: public function __toString(): string
196: {
197: return $this->getName();
198: }
199: }
200: