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