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