1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
6: use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
7: use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8: use PHPStan\Type\Generic\TemplateType;
9: use PHPStan\Type\Generic\TemplateTypeVariance;
10: use PHPStan\Type\Traits\LateResolvableTypeTrait;
11: use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
12: use function count;
13: use function sprintf;
14:
15: /** @api */
16: final class ValueOfType implements CompoundType, LateResolvableType
17: {
18:
19: use LateResolvableTypeTrait;
20: use NonGeneralizableTypeTrait;
21:
22: public function __construct(private Type $type)
23: {
24: }
25:
26: public function getReferencedClasses(): array
27: {
28: return $this->type->getReferencedClasses();
29: }
30:
31: public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
32: {
33: return $this->type->getReferencedTemplateTypes($positionVariance);
34: }
35:
36: public function equals(Type $type): bool
37: {
38: return $type instanceof self
39: && $this->type->equals($type->type);
40: }
41:
42: public function describe(VerbosityLevel $level): string
43: {
44: return sprintf('value-of<%s>', $this->type->describe($level));
45: }
46:
47: public function isResolvable(): bool
48: {
49: return !TypeUtils::containsTemplateType($this->type);
50: }
51:
52: protected function getResult(): Type
53: {
54: if ($this->type->isEnum()->yes()) {
55: $enumCases = $this->type->getEnumCases();
56: if (
57: $enumCases === []
58: && $this->type instanceof TemplateType
59: && (new ObjectType('BackedEnum'))->isSuperTypeOf($this->type->getBound())->yes()
60: ) {
61: return new UnionType([new IntegerType(), new StringType()]);
62: }
63:
64: $valueTypes = [];
65: foreach ($enumCases as $enumCase) {
66: $valueType = $enumCase->getBackingValueType();
67: if ($valueType === null) {
68: continue;
69: }
70:
71: $valueTypes[] = $valueType;
72: }
73:
74: if (count($valueTypes) === 0) {
75: return new NeverType();
76: }
77: if (count($valueTypes) === 1) {
78: return $valueTypes[0];
79: }
80:
81: return new UnionType($valueTypes);
82: }
83:
84: return $this->type->getIterableValueType();
85: }
86:
87: /**
88: * @param callable(Type): Type $cb
89: */
90: public function traverse(callable $cb): Type
91: {
92: $type = $cb($this->type);
93:
94: if ($this->type === $type) {
95: return $this;
96: }
97:
98: return new self($type);
99: }
100:
101: public function traverseSimultaneously(Type $right, callable $cb): Type
102: {
103: if (!$right instanceof self) {
104: return $this;
105: }
106:
107: $type = $cb($this->type, $right->type);
108:
109: if ($this->type === $type) {
110: return $this;
111: }
112:
113: return new self($type);
114: }
115:
116: public function toPhpDocNode(): TypeNode
117: {
118: return new GenericTypeNode(new IdentifierTypeNode('value-of'), [$this->type->toPhpDocNode()]);
119: }
120:
121: }
122: