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