Skip to content

Commit

Permalink
[red-knot] Statically known branches
Browse files Browse the repository at this point in the history
  • Loading branch information
sharkdp committed Dec 5, 2024
1 parent bd27bfa commit 66524e7
Show file tree
Hide file tree
Showing 12 changed files with 1,022 additions and 224 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ if (x := 1) and bool_instance():
if True or (x := 1):
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Literal[1]
reveal_type(x) # revealed: Never

if True and (x := 1):
# TODO: infer that the second arm is always executed, do not raise a diagnostic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
# Statically-known branches

## Always false

### If

```py
x = 1

if False:
x = 2

reveal_type(x) # revealed: Literal[1]
```

### Else

```py
x = 1

if True:
pass
else:
x = 2

reveal_type(x) # revealed: Literal[1]
```

## Always true

### If

```py
x = 1

if True:
x = 2

reveal_type(x) # revealed: Literal[2]
```

### Else

```py
x = 1

if False:
pass
else:
x = 2

reveal_type(x) # revealed: Literal[2]
```

## Combination

```py
x = 1

if True:
x = 2
else:
x = 3

reveal_type(x) # revealed: Literal[2]
```

## Nested

```py path=nested_if_true_if_true.py
x = 1

if True:
if True:
x = 2
else:
x = 3
else:
x = 4

reveal_type(x) # revealed: Literal[2]
```

```py path=nested_if_true_if_false.py
x = 1

if True:
if False:
x = 2
else:
x = 3
else:
x = 4

reveal_type(x) # revealed: Literal[3]
```

```py path=nested_if_true_if_bool.py
def flag() -> bool: ...

x = 1

if True:
if flag():
x = 2
else:
x = 3
else:
x = 4

reveal_type(x) # revealed: Literal[2, 3]
```

```py path=nested_if_bool_if_true.py
def flag() -> bool: ...

x = 1

if flag():
if True:
x = 2
else:
x = 3
else:
x = 4

reveal_type(x) # revealed: Literal[2, 4]
```

```py path=nested_else_if_true.py
x = 1

if False:
x = 2
else:
if True:
x = 3
else:
x = 4

reveal_type(x) # revealed: Literal[3]
```

```py path=nested_else_if_false.py
x = 1

if False:
x = 2
else:
if False:
x = 3
else:
x = 4

reveal_type(x) # revealed: Literal[4]
```

```py path=nested_else_if_bool.py
def flag() -> bool: ...

x = 1

if False:
x = 2
else:
if flag():
x = 3
else:
x = 4

reveal_type(x) # revealed: Literal[3, 4]
```

## If-expressions

### Always true

```py
x = 1 if True else 2

reveal_type(x) # revealed: Literal[1]
```

### Always false

```py
x = 1 if False else 2

reveal_type(x) # revealed: Literal[2]
```

## Boolean expressions

### Always true

```py
(x := 1) == 1 or (x := 2)

reveal_type(x) # revealed: Literal[1]
```

### Always false

```py
(x := 1) == 0 or (x := 2)

reveal_type(x) # revealed: Literal[2]
```

## Conditional declarations

```py path=if_false.py
x: str

if False:
x: int

def f() -> None:
reveal_type(x) # revealed: str
```

```py path=if_true_else.py
x: str

if True:
pass
else:
x: int

def f() -> None:
reveal_type(x) # revealed: str
```

```py path=if_true.py
x: str

if True:
x: int

def f() -> None:
reveal_type(x) # revealed: int
```

```py path=if_false_else.py
x: str

if False:
pass
else:
x: int

def f() -> None:
reveal_type(x) # revealed: int
```

```py path=if_bool.py
def flag() -> bool: ...

x: str

if flag():
x: int

def f() -> None:
reveal_type(x) # revealed: str | int
```

## Conditionally defined functions

```py
def f() -> int: ...
def g() -> int: ...

if True:
def f() -> str: ...

else:
def g() -> str: ...

reveal_type(f()) # revealed: str
reveal_type(g()) # revealed: int
```

## Conditionally defined class attributes

```py
class C:
if True:
x: int = 1
else:
x: str = "a"

reveal_type(C.x) # revealed: int
```
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ sometimes not:
```py
import sys

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

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

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

reveal_type(sys.version_info.major >= 3) # revealed: Literal[True]
reveal_type(sys.version_info.minor >= 9) # revealed: Literal[True]
reveal_type(sys.version_info.minor >= 10) # revealed: Literal[False]
reveal_type(sys.version_info.minor >= 12) # revealed: Literal[True]
reveal_type(sys.version_info.minor >= 13) # revealed: Literal[False]
```

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

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

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

reveal_type(sys.version_info[:2] >= (3, 9)) # revealed: Literal[True]
reveal_type(sys.version_info[0:2] >= (3, 10)) # revealed: Literal[False]
reveal_type(sys.version_info[:3] >= (3, 10, 1)) # revealed: Literal[False]
reveal_type(sys.version_info[:2] >= (3, 12)) # revealed: Literal[True]
reveal_type(sys.version_info[0:2] >= (3, 13)) # revealed: Literal[False]
reveal_type(sys.version_info[:3] >= (3, 13, 1)) # revealed: Literal[False]
reveal_type(sys.version_info[3] == "final") # revealed: bool
reveal_type(sys.version_info[3] == "finalllllll") # revealed: Literal[False]
```
28 changes: 28 additions & 0 deletions crates/red_knot_python_semantic/src/semantic_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1229,4 +1229,32 @@ match 1:

assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
}

#[test]
#[ignore]
fn if_statement() {
let TestCase { db, file } = test_case(
"
x = False
if True:
x: bool
",
);

let index = semantic_index(&db, file);
// let global_table = index.symbol_table(FileScopeId::global());

let use_def = index.use_def_map(FileScopeId::global());

// use_def

use_def.print(&db);

panic!();
// let binding = use_def
// .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
// .expect("Expected with item definition for {name}");
// assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
}
}
Loading

0 comments on commit 66524e7

Please sign in to comment.