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