-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[ty] Suppress diagnostics in statically unreachable nested scopes #23849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -442,6 +442,12 @@ impl<'db, 'ctx> LintDiagnosticGuardBuilder<'db, 'ctx> { | |
| if ctx.is_in_multi_inference() { | ||
| return None; | ||
| } | ||
| // Nested scopes in statically unreachable branches should not produce body diagnostics. | ||
| if !semantic_index(ctx.db, ctx.file) | ||
| .is_scope_reachable(ctx.db, ctx.scope.file_scope_id(ctx.db)) | ||
| { | ||
| return None; | ||
| } | ||
|
Comment on lines
+445
to
+450
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is the actual change that we want.
I looked at those test cases and I would argue that we want a different behavior there. This might have been intended in the past, but we've since come to the conclusion that we may be required to silence all diagnostics in definitely-unreachable sections. So I'll push a change to this PR that removes most of the changes and only leaves this part. Instead, I'm going to modify the test cases.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is in reference to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I was referring to some recent discussions related to python/typing#2210. It's possible that those lines above are too big of a hammer (because they make no such distinction), but I'm less and less convinced that it's worth spending a lot of effort to emit diagnostics for code like if False:
"a" + 1Sure, this line would definitely lead to a runtime error if it was reachable, but is that diagnostic really that useful? No other type checker makes an effort. And it is a reasonable developer/debugging use case to put an early return in a large function to silence diagnostics in "everything that comes below" (apart from the future "this is unreachable" graying out LSP feature / lint rule). I probably shouldn't have said "we've since come to the conclusion" — the statement above was just based on some discussion with @AlexWaygood this morning. What are some concrete scenarios where we would definitely want to emit a diagnostic in definitely-unreachable sections of code?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The only diagnostic which I would really like us to continue to try hard to emit in unreachable code is
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there might actually already be cases where we (accidentally?) suppress some of those diagnostics in unreachable regions, though, IIRC
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This doesn't ring a bell for me -- any further memory of in which cases you recall this happening, or why?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We did have user feedback way back in #15797 (comment) from a couple users that the desired behavior, at least for some kinds of unreachable code, for some developers, was "full diagnostics". I think it's clear that "full diagnostics" is not possible without false positives, but that does at least suggest that creating unreachable code intentionally during development, for other reasons, and still wanting full diagnostics on it, is also a valid developer use case. Overall though, I don't think this matters too much either way and I'm happy for us to do whatever is simplest. (Up until now, I don't think we have gone to extra effort to preserve some diagnostics in unreachable code, I think it was the behavior that fell out naturally, and suppressing required extra work.) If we need to suppress all diagnostics in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think the current state is still accurately described in this mdtest section: https://github.com/astral-sh/ruff/blob/main/crates/ty_python_semantic/resources/mdtest/unreachable.md#no-incorrect-diagnostics-in-unreachable-code: "[…] we do not attempt to provide the full set of diagnostics in unreachable sections. In fact, a large number of diagnostics are suppressed in unreachable code, simply due to the fact that we infer Never for most of the symbols." On top of this, Alex is right in the sense that there are some special diagnostics that are simply "turned off" in unreachable sections (to be more precise, we turn them off if the AST node on which we would emit the diagnostic is unreachable). For example, we turn off
👍
Okay
Yeah, maybe. I think it might have been easier to keep track of reachability constraints for all AST nodes (or for basic blocks, for performance reasons) and to simply suppress all diagnostics in unreachable code. I agree it's not like it was super hard to arrive at the current state, and maybe it's what fell out most naturally, but we did quite a few adjustments on top of "everything will be fine because we infer if sys.version_info >= (3, 11):
import tomllib
# …If we wouldn't track reachability constraints for that So in conclusion, I think it's reasonable to try and make an effort to further patch little things like astral-sh/ty#2891 by dedicated means when they come up. But if we agree that it "doesn't matter too much either way", maybe the easier way would be the big hammer of silencing all diagnostics in unreachable code. This PR is somewhere weirdly in between these two approaches (in that it only swings the big hammer for nested scopes inside unreachable sections), so maybe we should indeed spend a bit more time discussing this before making a decision here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Claude found a repro for me: from typing import reveal_type, assert_type, assert_never
def after_return():
x: int = 1
return
reveal_type(x) # expected: info diagnostic showing type
# actual: nothing
def always_false_branch():
x: int = 1
if False:
reveal_type(x) # expected: info diagnostic
# actual: nothing
def exhaustive_narrowing(x: int | str):
if isinstance(x, int):
return 1
elif isinstance(x, str):
return 2
else:
reveal_type(x) # expected: revealed type `Never`
# actual: nothing
assert_type(x, int) # expected: type-assertion-failure
# actual: nothing
assert_never(x)The issue is that in unreachable regions, the symbols note that |
||
|
|
||
| Some((severity, source)) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change to the code is not really necessary, but think this better reflects how someone would actually write this code. They would use the
if TYPE_CHECKINGbranch to provide a type signature, and then use theelsebranch for the actual implementation (that may or may not have caused some diagnostics, if it were not in a non-TYPE_CHECKINGblock).