Skip to content

Commit 8de9256

Browse files
committed
fix union with other subtype
1 parent d910ce9 commit 8de9256

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed

crates/red_knot_python_semantic/resources/mdtest/call/union.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,29 @@ def _(flag: bool):
175175
# error: [conflicting-argument-forms] "Argument is used as both a value and a type form in call"
176176
reveal_type(f(int)) # revealed: str | Literal[True]
177177
```
178+
179+
## Size limit on unions of literals
180+
181+
Beyond a certain size, large unions of literal types collapse to their nearest super-type (`int`,
182+
`bytes`, `str`).
183+
184+
```py
185+
from typing import Literal
186+
187+
def _(literals_2: Literal[0, 1], b: bool, flag: bool):
188+
literals_4 = 2 * literals_2 + literals_2 # Literal[0, 1, 2, 3]
189+
literals_16 = 4 * literals_4 + literals_4 # Literal[0, 1, .., 15]
190+
literals_64 = 4 * literals_16 + literals_4 # Literal[0, 1, .., 63]
191+
literals_128 = 2 * literals_64 + literals_2 # Literal[0, 1, .., 127]
192+
193+
# Going beyond the MAX_UNION_LITERALS limit (currently 200):
194+
literals_256 = 16 * literals_16 + literals_16
195+
reveal_type(literals_256) # revealed: int
196+
197+
# Going beyond the limit when another type is already part of the union
198+
bool_and_literals_128 = b if flag else literals_128 # bool | Literal[0, 1, ..., 127]
199+
literals_128_shifted = literals_128 + 128 # Literal[128, 129, ..., 255]
200+
201+
# Now union the two:
202+
reveal_type(bool_and_literals_128 if flag else literals_128_shifted) # revealed: int
203+
```

crates/red_knot_python_semantic/src/types/builder.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,14 @@ impl<'db> UnionBuilder<'db> {
9797
// means we shouldn't add it. Otherwise, add a new `UnionElement::StringLiterals`
9898
// containing it.
9999
Type::StringLiteral(literal) => {
100+
let mut too_large = false;
100101
let mut found = false;
101102
for element in &mut self.elements {
102103
match element {
103104
UnionElement::StringLiterals(literals) => {
104105
if literals.len() >= MAX_UNION_LITERALS {
105-
*element = UnionElement::Type(KnownClass::Str.to_instance(self.db));
106-
return self;
106+
too_large = true;
107+
break;
107108
}
108109
literals.insert(literal);
109110
found = true;
@@ -115,6 +116,10 @@ impl<'db> UnionBuilder<'db> {
115116
_ => {}
116117
}
117118
}
119+
if too_large {
120+
let replace_with = KnownClass::Str.to_instance(self.db);
121+
return self.add(replace_with);
122+
}
118123
if !found {
119124
self.elements
120125
.push(UnionElement::StringLiterals(FxOrderSet::from_iter([
@@ -125,13 +130,13 @@ impl<'db> UnionBuilder<'db> {
125130
// Same for bytes literals as for string literals, above.
126131
Type::BytesLiteral(literal) => {
127132
let mut found = false;
133+
let mut too_large = false;
128134
for element in &mut self.elements {
129135
match element {
130136
UnionElement::BytesLiterals(literals) => {
131137
if literals.len() >= MAX_UNION_LITERALS {
132-
*element =
133-
UnionElement::Type(KnownClass::Bytes.to_instance(self.db));
134-
return self;
138+
too_large = true;
139+
break;
135140
}
136141
literals.insert(literal);
137142
found = true;
@@ -143,6 +148,10 @@ impl<'db> UnionBuilder<'db> {
143148
_ => {}
144149
}
145150
}
151+
if too_large {
152+
let replace_with = KnownClass::Bytes.to_instance(self.db);
153+
return self.add(replace_with);
154+
}
146155
if !found {
147156
self.elements
148157
.push(UnionElement::BytesLiterals(FxOrderSet::from_iter([
@@ -153,12 +162,13 @@ impl<'db> UnionBuilder<'db> {
153162
// And same for int literals as well.
154163
Type::IntLiteral(literal) => {
155164
let mut found = false;
165+
let mut too_large = false;
156166
for element in &mut self.elements {
157167
match element {
158168
UnionElement::IntLiterals(literals) => {
159169
if literals.len() >= MAX_UNION_LITERALS {
160-
*element = UnionElement::Type(KnownClass::Int.to_instance(self.db));
161-
return self;
170+
too_large = true;
171+
break;
162172
}
163173
literals.insert(literal);
164174
found = true;
@@ -170,6 +180,10 @@ impl<'db> UnionBuilder<'db> {
170180
_ => {}
171181
}
172182
}
183+
if too_large {
184+
let replace_with = KnownClass::Int.to_instance(self.db);
185+
return self.add(replace_with);
186+
}
173187
if !found {
174188
self.elements
175189
.push(UnionElement::IntLiterals(FxOrderSet::from_iter([literal])));

0 commit comments

Comments
 (0)