Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -654,9 +654,7 @@ def f(cond: bool) -> str:
reveal_type(x) # revealed: Literal["before"]
return "a"
x = "after-return"
# TODO: no unresolved-reference error
# error: [unresolved-reference]
reveal_type(x) # revealed: Unknown
reveal_type(x) # revealed: Never
else:
x = "else"
return reveal_type(x) # revealed: Literal["else"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,6 @@ def f():

print("unreachable")

# TODO: we should not emit an error here; we currently do, since there is no control flow path from this
# use of 'x' to any definition of 'x'.
# error: [unresolved-reference]
print(x)
```

Expand All @@ -228,8 +225,6 @@ def outer():
x = 1

def inner():
# TODO: we should not emit an error here
# error: [unresolved-reference]
return x # Name `x` used when not defined
while True:
pass
Expand Down
47 changes: 47 additions & 0 deletions crates/red_knot_python_semantic/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,53 @@ fn symbol_from_bindings_impl<'db>(
visibility_constraints.evaluate(db, predicates, visibility_constraint);

if static_visibility.is_always_false() {
// We found a binding that we have statically determined to not be visible from
// the use of the symbol that we are investigating. There are three interesting
// cases to consider:
//
// ```py
// def f1():
// if False:
// x = 1
// use(x)
//
// def f2():
// y = 1
// return
// use(y)
//
// def f3(flag: bool):
// z = 1
// if flag:
// z = 2
// return
// use(z)
// ```
//
// In the first case, there is a single binding for `x`, and due to the statically
// known `False` condition, it is not visible at the use of `x`. However, we *can*
// see/reach the start of the scope from `use(x)`. This means that `x` is unbound
// and we should return `None`.
//
// In the second case, `y` is also not visible at the use of `y`, but here, we can
// not see/reach the start of the scope. There is only one path of control flow,
// and it passes through that binding of `y` (which we can not see). This implies
// that we are in an unreachable section of code. We return `Never` in order to
// silence the `unresolve-reference` diagnostic that would otherwise be emitted at
// the use of `y`.
//
// In the third case, we have two bindings for `z`. The first one is visible, so we
// consider the case that we now encounter the second binding `z = 2`, which is not
// visible due to the early return. We *also* can not see the start of the scope
// from `use(z)` because both paths of control flow pass through a binding of `z`.
// The `z = 1` binding is visible, and so we are *not* in an unreachable section of
// code. However, it is still okay to return `Never` in this case, because we will
// union the types of all bindings, and `Never` will be eliminated automatically.

if unbound_visibility.is_always_false() {
// The scope-start is not visible
return Some(Type::Never);
}
return None;
}

Expand Down
Loading