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