-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
PLR1702: Visually noisy diagnostic ranges #15570
Comments
PLR1702 is currently marking every line inside this function: def foo():
while a: # should not be marked - not a violation
if b: # should not be marked - not a violation
for c in range(3): # should not be marked - not a violation
if d: # should not be marked - not a violation
while e: # should not be marked - not a violation
if f: # should not be marked - not a violation
for g in z: # not bad to mark this line, but not needed if only marking the max
print(p) # this line should be marked
pass # this line doesn't need to be marked, because the previous line is marked
else: # should not be marked - not a violation
print(q) # should not be marked - not a violation |
Here's an example with multiple diagnostics. def foo():
while a: # should not be marked - not a violation
if b: # should not be marked - not a violation
for c in range(3): # should not be marked - not a violation
if d: # should not be marked - not a violation
while e: # should not be marked - not a violation
if f: # should not be marked - not a violation
for g in z: # not bad to mark this line, but not needed if only marking the max
print(p) # this line should be marked
pass # this line doesn't need to be marked, because the previous line is marked
else:
if h:
print(r)
else: # should not be marked - not a violation
print(q) # should not be marked - not a violation |
In the original example of the first screenshot, there 250 lines all completely highlighted, making it difficult to see other diagnostics. If there are 5 violations, highlighting 5 lines is enough. |
Thank you, that's helpful! I think there's a small design question about how to handle this situation, and we should be conscious of whether we are creating a breaking change by modifying where a suppression comment can go. But I agree that we can do better here. |
I whipped up a draft PR for this. It doesn't quite work yet, but here's how I envision it: | def foo():
| while a: # \
| if b: # |
| for c in range(3): # | These should not be reported,
| if d: # | as they don't exceed the max depth.
| while e: # |
| if f: # /
#
| for g in z: # This statement is the first to exceed the limit.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Report range
| print(p) # Thus, it is reported but not any of its substatements.
| pass #
#
| with y: # The former statement was already reported.
| print(x) # Thus, reporting these is redundant.
| print(u) #
#
| else: # Other blocks of an ancestor statement
| print(q) # are also not reported. | def foo():
| while a: # \
| if b: # |
| for c in range(3): # | These should not be reported,
| if d: # | as they don't exceed the max depth.
| while e: # |
| if f: # /
#
| if x == y: # This statement is the first to exceed the limit.
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Report range
| print(p) # It is therefore reported.
#
| elif y > x: # This block belongs to the same statement,
| print(p) # and so it is not reported on its own. Indentation seems to be the most sensible place to report an overnesting. |
A refined explanation can be found at this test file. I'm torn between this current approach and one that simply reports the first block that exceeds the limit: def foo():
if a:
if b:
if c:
if d:
if e:
if f:
if g: # PLR1702: Too many nested blocks (only n allowed)
if h:
print(i) But that would require some recursion to find the max depth. |
I'm torn on this. Both too-many-branches and complex-structure report the entire function (and I expect so do other too-many-* rules). I also think that highlighting the function itself sort of makes sense, considering:
There's a Ruff helper somewhere (I wasn't able to find it just now) that only extracts the range of the function's name. This would reduce the highlighting range but doesn't point the user to the relevant code. That's where highlighting the first "violating" block would definitely be an improvement. But this is a breaking change (it's still a preview rule so it might be fine). I'm not convinced that highlighting the indent is the right solution. It incorrectly suggests that something is wrong with the indent, which there isn't. Has anyone looked into what the upstream rule odes or what other tools do in this situation? Side note: The rule should probably be updated to support |
(This is a scenario where related diagnostics might help where the main diagnostic will highlight the function name but related diagnostics will also highlight the block which makes Ruff consider this as exceeding the limit.) |
I do think deep indentation is the problem in this case. Not character-wise, no, but semantically. |
Ideally the same approach could be taken for all |
I take it that you are suggesting other rules should be updated as well? If so, here's my take: The reported range is that of the first that exceeds the limit.
This aligns with, say, (Off-topic: |
Reporting the keywords is a solid choice too: if a:
...
if b:
# ^^ PLR1702
...
for d in e:
# ^^^ PLR1702
...
while f < g:
# ^^^^^ PLR1702
...
match h:
# ^^^^^ PLR1702
case I(j=k):
... |
If a function has nested blocks to violate PLR1702, almost the entire function is marked for it, including lines that are not violations.
Even lines that are only nested 2 or 3 deep are marked with squigglies.
Even when there are multiple lines in a block nested 9 deep, I think it would be enough to only mark the first line of that deeply nested block.
The text was updated successfully, but these errors were encountered: