Skip to content

Commit 66524e7

Browse files
committed
[red-knot] Statically known branches
1 parent bd27bfa commit 66524e7

File tree

12 files changed

+1022
-224
lines changed

12 files changed

+1022
-224
lines changed

crates/red_knot_python_semantic/resources/mdtest/boolean/short_circuit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ if (x := 1) and bool_instance():
3838
if True or (x := 1):
3939
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
4040
# error: [possibly-unresolved-reference]
41-
reveal_type(x) # revealed: Literal[1]
41+
reveal_type(x) # revealed: Never
4242

4343
if True and (x := 1):
4444
# TODO: infer that the second arm is always executed, do not raise a diagnostic
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# Statically-known branches
2+
3+
## Always false
4+
5+
### If
6+
7+
```py
8+
x = 1
9+
10+
if False:
11+
x = 2
12+
13+
reveal_type(x) # revealed: Literal[1]
14+
```
15+
16+
### Else
17+
18+
```py
19+
x = 1
20+
21+
if True:
22+
pass
23+
else:
24+
x = 2
25+
26+
reveal_type(x) # revealed: Literal[1]
27+
```
28+
29+
## Always true
30+
31+
### If
32+
33+
```py
34+
x = 1
35+
36+
if True:
37+
x = 2
38+
39+
reveal_type(x) # revealed: Literal[2]
40+
```
41+
42+
### Else
43+
44+
```py
45+
x = 1
46+
47+
if False:
48+
pass
49+
else:
50+
x = 2
51+
52+
reveal_type(x) # revealed: Literal[2]
53+
```
54+
55+
## Combination
56+
57+
```py
58+
x = 1
59+
60+
if True:
61+
x = 2
62+
else:
63+
x = 3
64+
65+
reveal_type(x) # revealed: Literal[2]
66+
```
67+
68+
## Nested
69+
70+
```py path=nested_if_true_if_true.py
71+
x = 1
72+
73+
if True:
74+
if True:
75+
x = 2
76+
else:
77+
x = 3
78+
else:
79+
x = 4
80+
81+
reveal_type(x) # revealed: Literal[2]
82+
```
83+
84+
```py path=nested_if_true_if_false.py
85+
x = 1
86+
87+
if True:
88+
if False:
89+
x = 2
90+
else:
91+
x = 3
92+
else:
93+
x = 4
94+
95+
reveal_type(x) # revealed: Literal[3]
96+
```
97+
98+
```py path=nested_if_true_if_bool.py
99+
def flag() -> bool: ...
100+
101+
x = 1
102+
103+
if True:
104+
if flag():
105+
x = 2
106+
else:
107+
x = 3
108+
else:
109+
x = 4
110+
111+
reveal_type(x) # revealed: Literal[2, 3]
112+
```
113+
114+
```py path=nested_if_bool_if_true.py
115+
def flag() -> bool: ...
116+
117+
x = 1
118+
119+
if flag():
120+
if True:
121+
x = 2
122+
else:
123+
x = 3
124+
else:
125+
x = 4
126+
127+
reveal_type(x) # revealed: Literal[2, 4]
128+
```
129+
130+
```py path=nested_else_if_true.py
131+
x = 1
132+
133+
if False:
134+
x = 2
135+
else:
136+
if True:
137+
x = 3
138+
else:
139+
x = 4
140+
141+
reveal_type(x) # revealed: Literal[3]
142+
```
143+
144+
```py path=nested_else_if_false.py
145+
x = 1
146+
147+
if False:
148+
x = 2
149+
else:
150+
if False:
151+
x = 3
152+
else:
153+
x = 4
154+
155+
reveal_type(x) # revealed: Literal[4]
156+
```
157+
158+
```py path=nested_else_if_bool.py
159+
def flag() -> bool: ...
160+
161+
x = 1
162+
163+
if False:
164+
x = 2
165+
else:
166+
if flag():
167+
x = 3
168+
else:
169+
x = 4
170+
171+
reveal_type(x) # revealed: Literal[3, 4]
172+
```
173+
174+
## If-expressions
175+
176+
### Always true
177+
178+
```py
179+
x = 1 if True else 2
180+
181+
reveal_type(x) # revealed: Literal[1]
182+
```
183+
184+
### Always false
185+
186+
```py
187+
x = 1 if False else 2
188+
189+
reveal_type(x) # revealed: Literal[2]
190+
```
191+
192+
## Boolean expressions
193+
194+
### Always true
195+
196+
```py
197+
(x := 1) == 1 or (x := 2)
198+
199+
reveal_type(x) # revealed: Literal[1]
200+
```
201+
202+
### Always false
203+
204+
```py
205+
(x := 1) == 0 or (x := 2)
206+
207+
reveal_type(x) # revealed: Literal[2]
208+
```
209+
210+
## Conditional declarations
211+
212+
```py path=if_false.py
213+
x: str
214+
215+
if False:
216+
x: int
217+
218+
def f() -> None:
219+
reveal_type(x) # revealed: str
220+
```
221+
222+
```py path=if_true_else.py
223+
x: str
224+
225+
if True:
226+
pass
227+
else:
228+
x: int
229+
230+
def f() -> None:
231+
reveal_type(x) # revealed: str
232+
```
233+
234+
```py path=if_true.py
235+
x: str
236+
237+
if True:
238+
x: int
239+
240+
def f() -> None:
241+
reveal_type(x) # revealed: int
242+
```
243+
244+
```py path=if_false_else.py
245+
x: str
246+
247+
if False:
248+
pass
249+
else:
250+
x: int
251+
252+
def f() -> None:
253+
reveal_type(x) # revealed: int
254+
```
255+
256+
```py path=if_bool.py
257+
def flag() -> bool: ...
258+
259+
x: str
260+
261+
if flag():
262+
x: int
263+
264+
def f() -> None:
265+
reveal_type(x) # revealed: str | int
266+
```
267+
268+
## Conditionally defined functions
269+
270+
```py
271+
def f() -> int: ...
272+
def g() -> int: ...
273+
274+
if True:
275+
def f() -> str: ...
276+
277+
else:
278+
def g() -> str: ...
279+
280+
reveal_type(f()) # revealed: str
281+
reveal_type(g()) # revealed: int
282+
```
283+
284+
## Conditionally defined class attributes
285+
286+
```py
287+
class C:
288+
if True:
289+
x: int = 1
290+
else:
291+
x: str = "a"
292+
293+
reveal_type(C.x) # revealed: int
294+
```

crates/red_knot_python_semantic/resources/mdtest/sys_version_info.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ sometimes not:
4949
```py
5050
import sys
5151

52-
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: bool
53-
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: bool
52+
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: Literal[True]
53+
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: Literal[True]
5454

5555
# TODO: While this won't fail at runtime, the user has probably made a mistake
5656
# if they're comparing a tuple of length >5 with `sys.version_info`
5757
# (`sys.version_info` is a tuple of length 5). It might be worth
5858
# emitting a lint diagnostic of some kind warning them about the probable error?
59-
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: bool
59+
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: Literal[True]
6060

6161
reveal_type(sys.version_info == (3, 8, 1, "finallllll", 0)) # revealed: Literal[False]
6262
```
@@ -102,8 +102,8 @@ The fields of `sys.version_info` can be accessed by name:
102102
import sys
103103

104104
reveal_type(sys.version_info.major >= 3) # revealed: Literal[True]
105-
reveal_type(sys.version_info.minor >= 9) # revealed: Literal[True]
106-
reveal_type(sys.version_info.minor >= 10) # revealed: Literal[False]
105+
reveal_type(sys.version_info.minor >= 12) # revealed: Literal[True]
106+
reveal_type(sys.version_info.minor >= 13) # revealed: Literal[False]
107107
```
108108

109109
But the `micro`, `releaselevel` and `serial` fields are inferred as `@Todo` until we support
@@ -125,14 +125,14 @@ The fields of `sys.version_info` can be accessed by index or by slice:
125125
import sys
126126

127127
reveal_type(sys.version_info[0] < 3) # revealed: Literal[False]
128-
reveal_type(sys.version_info[1] > 9) # revealed: Literal[False]
128+
reveal_type(sys.version_info[1] > 13) # revealed: Literal[False]
129129

130-
# revealed: tuple[Literal[3], Literal[9], int, Literal["alpha", "beta", "candidate", "final"], int]
130+
# revealed: tuple[Literal[3], Literal[12], int, Literal["alpha", "beta", "candidate", "final"], int]
131131
reveal_type(sys.version_info[:5])
132132

133-
reveal_type(sys.version_info[:2] >= (3, 9)) # revealed: Literal[True]
134-
reveal_type(sys.version_info[0:2] >= (3, 10)) # revealed: Literal[False]
135-
reveal_type(sys.version_info[:3] >= (3, 10, 1)) # revealed: Literal[False]
133+
reveal_type(sys.version_info[:2] >= (3, 12)) # revealed: Literal[True]
134+
reveal_type(sys.version_info[0:2] >= (3, 13)) # revealed: Literal[False]
135+
reveal_type(sys.version_info[:3] >= (3, 13, 1)) # revealed: Literal[False]
136136
reveal_type(sys.version_info[3] == "final") # revealed: bool
137137
reveal_type(sys.version_info[3] == "finalllllll") # revealed: Literal[False]
138138
```

crates/red_knot_python_semantic/src/semantic_index.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,4 +1229,32 @@ match 1:
12291229

12301230
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
12311231
}
1232+
1233+
#[test]
1234+
#[ignore]
1235+
fn if_statement() {
1236+
let TestCase { db, file } = test_case(
1237+
"
1238+
x = False
1239+
1240+
if True:
1241+
x: bool
1242+
",
1243+
);
1244+
1245+
let index = semantic_index(&db, file);
1246+
// let global_table = index.symbol_table(FileScopeId::global());
1247+
1248+
let use_def = index.use_def_map(FileScopeId::global());
1249+
1250+
// use_def
1251+
1252+
use_def.print(&db);
1253+
1254+
panic!();
1255+
// let binding = use_def
1256+
// .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
1257+
// .expect("Expected with item definition for {name}");
1258+
// assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
1259+
}
12321260
}

0 commit comments

Comments
 (0)