1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Analyser;
4:
5: use PhpParser\Node\Expr;
6: use PHPStan\Type\Type;
7: use PHPStan\Type\TypeCombinator;
8:
9: class SpecifiedTypes
10: {
11:
12: /**
13: * @api
14: * @param array<string, array{Expr, Type}> $sureTypes
15: * @param array<string, array{Expr, Type}> $sureNotTypes
16: * @param array<string, ConditionalExpressionHolder[]> $newConditionalExpressionHolders
17: */
18: public function __construct(
19: private array $sureTypes = [],
20: private array $sureNotTypes = [],
21: private bool $overwrite = false,
22: private array $newConditionalExpressionHolders = [],
23: private ?Expr $rootExpr = null,
24: )
25: {
26: }
27:
28: /**
29: * @api
30: * @return array<string, array{Expr, Type}>
31: */
32: public function getSureTypes(): array
33: {
34: return $this->sureTypes;
35: }
36:
37: /**
38: * @api
39: * @return array<string, array{Expr, Type}>
40: */
41: public function getSureNotTypes(): array
42: {
43: return $this->sureNotTypes;
44: }
45:
46: public function shouldOverwrite(): bool
47: {
48: return $this->overwrite;
49: }
50:
51: /**
52: * @return array<string, ConditionalExpressionHolder[]>
53: */
54: public function getNewConditionalExpressionHolders(): array
55: {
56: return $this->newConditionalExpressionHolders;
57: }
58:
59: public function getRootExpr(): ?Expr
60: {
61: return $this->rootExpr;
62: }
63:
64: /** @api */
65: public function intersectWith(SpecifiedTypes $other): self
66: {
67: $sureTypeUnion = [];
68: $sureNotTypeUnion = [];
69: $rootExpr = $this->mergeRootExpr($this->rootExpr, $other->rootExpr);
70:
71: foreach ($this->sureTypes as $exprString => [$exprNode, $type]) {
72: if (!isset($other->sureTypes[$exprString])) {
73: continue;
74: }
75:
76: $sureTypeUnion[$exprString] = [
77: $exprNode,
78: TypeCombinator::union($type, $other->sureTypes[$exprString][1]),
79: ];
80: }
81:
82: foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) {
83: if (!isset($other->sureNotTypes[$exprString])) {
84: continue;
85: }
86:
87: $sureNotTypeUnion[$exprString] = [
88: $exprNode,
89: TypeCombinator::intersect($type, $other->sureNotTypes[$exprString][1]),
90: ];
91: }
92:
93: return new self($sureTypeUnion, $sureNotTypeUnion, $this->overwrite && $other->overwrite, [], $rootExpr);
94: }
95:
96: /** @api */
97: public function unionWith(SpecifiedTypes $other): self
98: {
99: $sureTypeUnion = $this->sureTypes + $other->sureTypes;
100: $sureNotTypeUnion = $this->sureNotTypes + $other->sureNotTypes;
101: $rootExpr = $this->mergeRootExpr($this->rootExpr, $other->rootExpr);
102:
103: foreach ($this->sureTypes as $exprString => [$exprNode, $type]) {
104: if (!isset($other->sureTypes[$exprString])) {
105: continue;
106: }
107:
108: $sureTypeUnion[$exprString] = [
109: $exprNode,
110: TypeCombinator::intersect($type, $other->sureTypes[$exprString][1]),
111: ];
112: }
113:
114: foreach ($this->sureNotTypes as $exprString => [$exprNode, $type]) {
115: if (!isset($other->sureNotTypes[$exprString])) {
116: continue;
117: }
118:
119: $sureNotTypeUnion[$exprString] = [
120: $exprNode,
121: TypeCombinator::union($type, $other->sureNotTypes[$exprString][1]),
122: ];
123: }
124:
125: return new self($sureTypeUnion, $sureNotTypeUnion, $this->overwrite || $other->overwrite, [], $rootExpr);
126: }
127:
128: public function normalize(Scope $scope): self
129: {
130: $sureTypes = $this->sureTypes;
131:
132: foreach ($this->sureNotTypes as $exprString => [$exprNode, $sureNotType]) {
133: if (!isset($sureTypes[$exprString])) {
134: $sureTypes[$exprString] = [$exprNode, TypeCombinator::remove($scope->getType($exprNode), $sureNotType)];
135: continue;
136: }
137:
138: $sureTypes[$exprString][1] = TypeCombinator::remove($sureTypes[$exprString][1], $sureNotType);
139: }
140:
141: return new self($sureTypes, [], $this->overwrite, $this->newConditionalExpressionHolders, $this->rootExpr);
142: }
143:
144: private function mergeRootExpr(?Expr $rootExprA, ?Expr $rootExprB): ?Expr
145: {
146: if ($rootExprA === $rootExprB) {
147: return $rootExprA;
148: }
149:
150: if ($rootExprA === null || $rootExprB === null) {
151: return $rootExprA ?? $rootExprB;
152: }
153:
154: return null;
155: }
156:
157: }
158: