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