1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Analyser;
4:
5: use PHPStan\Type\Generic\TemplateTypeMap;
6: use PHPStan\Type\Generic\TemplateTypeScope;
7: use PHPStan\Type\Type;
8: use function array_key_exists;
9: use function array_merge;
10: use function array_shift;
11: use function count;
12: use function explode;
13: use function implode;
14: use function ltrim;
15: use function sprintf;
16: use function str_starts_with;
17: use function strtolower;
18:
19: /** @api */
20: class NameScope
21: {
22:
23: private TemplateTypeMap $templateTypeMap;
24:
25: /**
26: * @api
27: * @param non-empty-string|null $namespace
28: * @param array<string, string> $uses alias(string) => fullName(string)
29: * @param array<string, string> $constUses alias(string) => fullName(string)
30: * @param array<string, true> $typeAliasesMap
31: */
32: public function __construct(private ?string $namespace, private array $uses, private ?string $className = null, private ?string $functionName = null, ?TemplateTypeMap $templateTypeMap = null, private array $typeAliasesMap = [], private bool $bypassTypeAliases = false, private array $constUses = [], private ?string $typeAliasClassName = null)
33: {
34: $this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty();
35: }
36:
37: public function getNamespace(): ?string
38: {
39: return $this->namespace;
40: }
41:
42: /**
43: * @return array<string, string>
44: */
45: public function getUses(): array
46: {
47: return $this->uses;
48: }
49:
50: public function hasUseAlias(string $name): bool
51: {
52: return isset($this->uses[strtolower($name)]);
53: }
54:
55: /**
56: * @return array<string, string>
57: */
58: public function getConstUses(): array
59: {
60: return $this->constUses;
61: }
62:
63: public function getClassName(): ?string
64: {
65: return $this->className;
66: }
67:
68: public function getClassNameForTypeAlias(): ?string
69: {
70: return $this->typeAliasClassName ?? $this->className;
71: }
72:
73: public function resolveStringName(string $name): string
74: {
75: if (str_starts_with($name, '\\')) {
76: return ltrim($name, '\\');
77: }
78:
79: $nameParts = explode('\\', $name);
80: $firstNamePart = strtolower($nameParts[0]);
81: if (isset($this->uses[$firstNamePart])) {
82: if (count($nameParts) === 1) {
83: return $this->uses[$firstNamePart];
84: }
85: array_shift($nameParts);
86: return sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts));
87: }
88:
89: if ($this->namespace !== null) {
90: return sprintf('%s\\%s', $this->namespace, $name);
91: }
92:
93: return $name;
94: }
95:
96: /**
97: * @return non-empty-list<string>
98: */
99: public function resolveConstantNames(string $name): array
100: {
101: if (str_starts_with($name, '\\')) {
102: return [ltrim($name, '\\')];
103: }
104:
105: $nameParts = explode('\\', $name);
106: $firstNamePart = strtolower($nameParts[0]);
107:
108: if (count($nameParts) > 1) {
109: if (isset($this->uses[$firstNamePart])) {
110: array_shift($nameParts);
111: return [sprintf('%s\\%s', $this->uses[$firstNamePart], implode('\\', $nameParts))];
112: }
113: } elseif (isset($this->constUses[$firstNamePart])) {
114: return [$this->constUses[$firstNamePart]];
115: }
116:
117: if ($this->namespace !== null) {
118: return [
119: sprintf('%s\\%s', $this->namespace, $name),
120: $name,
121: ];
122: }
123:
124: return [$name];
125: }
126:
127: public function getTemplateTypeScope(): ?TemplateTypeScope
128: {
129: if ($this->className !== null) {
130: if ($this->functionName !== null) {
131: return TemplateTypeScope::createWithMethod($this->className, $this->functionName);
132: }
133:
134: return TemplateTypeScope::createWithClass($this->className);
135: }
136:
137: if ($this->functionName !== null) {
138: return TemplateTypeScope::createWithFunction($this->functionName);
139: }
140:
141: return null;
142: }
143:
144: public function getTemplateTypeMap(): TemplateTypeMap
145: {
146: return $this->templateTypeMap;
147: }
148:
149: public function resolveTemplateTypeName(string $name): ?Type
150: {
151: return $this->templateTypeMap->getType($name);
152: }
153:
154: public function withTemplateTypeMap(TemplateTypeMap $map): self
155: {
156: if ($map->isEmpty() && $this->templateTypeMap->isEmpty()) {
157: return $this;
158: }
159:
160: return new self(
161: $this->namespace,
162: $this->uses,
163: $this->className,
164: $this->functionName,
165: new TemplateTypeMap(array_merge(
166: $this->templateTypeMap->getTypes(),
167: $map->getTypes(),
168: )),
169: $this->typeAliasesMap,
170: $this->bypassTypeAliases,
171: $this->constUses,
172: );
173: }
174:
175: public function withClassName(string $className): self
176: {
177: return new self(
178: $this->namespace,
179: $this->uses,
180: $className,
181: $this->functionName,
182: $this->templateTypeMap,
183: $this->typeAliasesMap,
184: $this->bypassTypeAliases,
185: $this->constUses,
186: );
187: }
188:
189: public function unsetTemplateType(string $name): self
190: {
191: $map = $this->templateTypeMap;
192: if (!$map->hasType($name)) {
193: return $this;
194: }
195:
196: return new self(
197: $this->namespace,
198: $this->uses,
199: $this->className,
200: $this->functionName,
201: $this->templateTypeMap->unsetType($name),
202: $this->typeAliasesMap,
203: $this->bypassTypeAliases,
204: $this->constUses,
205: );
206: }
207:
208: public function bypassTypeAliases(): self
209: {
210: return new self($this->namespace, $this->uses, $this->className, $this->functionName, $this->templateTypeMap, $this->typeAliasesMap, true, $this->constUses);
211: }
212:
213: public function shouldBypassTypeAliases(): bool
214: {
215: return $this->bypassTypeAliases;
216: }
217:
218: public function hasTypeAlias(string $alias): bool
219: {
220: return array_key_exists($alias, $this->typeAliasesMap);
221: }
222:
223: /**
224: * @param mixed[] $properties
225: */
226: public static function __set_state(array $properties): self
227: {
228: return new self(
229: $properties['namespace'],
230: $properties['uses'],
231: $properties['className'],
232: $properties['functionName'],
233: $properties['templateTypeMap'],
234: $properties['typeAliasesMap'],
235: $properties['bypassTypeAliases'],
236: $properties['constUses'],
237: );
238: }
239:
240: }
241: