Skip to content

Commit a7c4d3e

Browse files
sharkdpmaxmynter
authored andcommitted
[red-knot] Avoid unresolved-reference in unreachable code (astral-sh#17169)
## Summary This PR changes the inferred type for symbols in unreachable sections of code to `Never` (instead of reporting them as unbound), in order to silence false positive diagnostics. See the lengthy comment in the code for further details. ## Test Plan - Updated Markdown tests. - Manually verified a couple of ecosystem diagnostic changes.
1 parent 6cee601 commit a7c4d3e

File tree

3 files changed

+48
-8
lines changed

3 files changed

+48
-8
lines changed

crates/red_knot_python_semantic/resources/mdtest/terminal_statements.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -654,9 +654,7 @@ def f(cond: bool) -> str:
654654
reveal_type(x) # revealed: Literal["before"]
655655
return "a"
656656
x = "after-return"
657-
# TODO: no unresolved-reference error
658-
# error: [unresolved-reference]
659-
reveal_type(x) # revealed: Unknown
657+
reveal_type(x) # revealed: Never
660658
else:
661659
x = "else"
662660
return reveal_type(x) # revealed: Literal["else"]

crates/red_knot_python_semantic/resources/mdtest/unreachable.md

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,6 @@ def f():
211211

212212
print("unreachable")
213213

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

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

230227
def inner():
231-
# TODO: we should not emit an error here
232-
# error: [unresolved-reference]
233228
return x # Name `x` used when not defined
234229
while True:
235230
pass

crates/red_knot_python_semantic/src/symbol.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,53 @@ fn symbol_from_bindings_impl<'db>(
679679
visibility_constraints.evaluate(db, predicates, visibility_constraint);
680680

681681
if static_visibility.is_always_false() {
682+
// We found a binding that we have statically determined to not be visible from
683+
// the use of the symbol that we are investigating. There are three interesting
684+
// cases to consider:
685+
//
686+
// ```py
687+
// def f1():
688+
// if False:
689+
// x = 1
690+
// use(x)
691+
//
692+
// def f2():
693+
// y = 1
694+
// return
695+
// use(y)
696+
//
697+
// def f3(flag: bool):
698+
// z = 1
699+
// if flag:
700+
// z = 2
701+
// return
702+
// use(z)
703+
// ```
704+
//
705+
// In the first case, there is a single binding for `x`, and due to the statically
706+
// known `False` condition, it is not visible at the use of `x`. However, we *can*
707+
// see/reach the start of the scope from `use(x)`. This means that `x` is unbound
708+
// and we should return `None`.
709+
//
710+
// In the second case, `y` is also not visible at the use of `y`, but here, we can
711+
// not see/reach the start of the scope. There is only one path of control flow,
712+
// and it passes through that binding of `y` (which we can not see). This implies
713+
// that we are in an unreachable section of code. We return `Never` in order to
714+
// silence the `unresolve-reference` diagnostic that would otherwise be emitted at
715+
// the use of `y`.
716+
//
717+
// In the third case, we have two bindings for `z`. The first one is visible, so we
718+
// consider the case that we now encounter the second binding `z = 2`, which is not
719+
// visible due to the early return. We *also* can not see the start of the scope
720+
// from `use(z)` because both paths of control flow pass through a binding of `z`.
721+
// The `z = 1` binding is visible, and so we are *not* in an unreachable section of
722+
// code. However, it is still okay to return `Never` in this case, because we will
723+
// union the types of all bindings, and `Never` will be eliminated automatically.
724+
725+
if unbound_visibility.is_always_false() {
726+
// The scope-start is not visible
727+
return Some(Type::Never);
728+
}
682729
return None;
683730
}
684731

0 commit comments

Comments
 (0)