| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
… robustly and efficiently, and avoid multiple `revealed type` notes for the same line. This change is an improvement over 9685171. Besides fixing the regression reported in python#18606 and removing the duplicates reported in python#18511, it should significantly reduce the performance regression reported in python#18991. At least running `Measure-command {python runtests.py self}` on my computer (with removed cache) is 10 % faster.
for more information, see https://pre-commit.ci
|
@JukkaL: I thought that later responses to reveal_type are always better than earlier ones. However, this is not the case for testNewRedefineTryStatement test case. Maybe the most general approach would be to collect all the union items of the individual responses and to combine them in the final note. I could try this, if you think it's a good way to go. Alternatively, I could just remove the reveal_type part of this pull request, as it seems less important than the other two addressed issues. |
Sorry, something went wrong.
|
I implemented a collection mechanism for the types revealed in different iteration steps. In the case of unions, the items are sorted alphabetically without differentiating upper and lower case letters. At first glance, the reason for the Mypy primer diff (https://github.com/pydata/xarray) is not clear to me. I asked for a review of those who opened the mentioned issues. Thanks in advance! |
Sorry, something went wrong.
|
xarray is #19121, we have it on all current PRs |
Sorry, something went wrong.
|
@sterliakov: Thanks, good to know. (Anybody already working on it?) |
Sorry, something went wrong.
|
@tyralla I'm a bit swamped, there are no related PRs open - so probably not yet? It is likely related to #18976, but I have no idea why it's inconsistent - any MRE extracted from that fails every time, not randomly. https://mypy-play.net/?mypy=master&python=3.12&flags=strict&gist=50f4ae811a650f9e90c6f52269f59d29 |
Sorry, something went wrong.
There was a problem hiding this comment.
I'm not immediately convinced this is robust!
Sorry, something went wrong.
| # We collect errors that indicate unreachability or redundant expressions | ||
| # in the first iteration and remove them in subsequent iterations if the | ||
| # related statement becomes reachable or non-redundant due to changes in | ||
| # partial types: |
There was a problem hiding this comment.
What if an unreachable statement is within another unreachable statement?
Sorry, something went wrong.
There was a problem hiding this comment.
I am not exactly sure what you are asking for.
Is this an example that fits your question?
[case testAvoidUnreachableCodeInUnreachableCodeWithPartialType]
# flags: --warn-unreachable --python-version 3.11
x = None
y = None
while x is None and y is None:
reveal_type(x) # N: Revealed type is "None"
reveal_type(y) # N: Revealed type is "None"
if x is None and y is not None:
x = 1 # E: Statement is unreachable
if y is None:
y = 1
[builtins fixtures/bool.pyi]In this case, the results are the same for 1.15 and the current state of this PR.
Sorry, something went wrong.
There was a problem hiding this comment.
something like this:
x = None
y = None
while True:
if x is not None:
print("a")
if y is not None:
print("b") # this is unreachable
x = 1(I haven't run the PR on this so I'm not sure the result, but it should warn about unreachablity. mypy 1.15 does. I'm worried this change won't)
Sorry, something went wrong.
There was a problem hiding this comment.
This example should print b. Is there a typo?
Sorry, something went wrong.
There was a problem hiding this comment.
I edited it afterwards, it shouldn't anymore.
Sorry, something went wrong.
There was a problem hiding this comment.
Yes, you were totally right. The large temporarily unreachable code section shadowed the persistent unreachable code section. I added your test case and extended the current logic a bit. It's late now, so I'll postpone double-checking until tomorrow.
Can you see any more potential pitfalls?
Sorry, something went wrong.
There was a problem hiding this comment.
I again looked at it. To me, the logic appears a little special, but clear and straightforward. Of course, I cannot exclude that there are more special cases my solution does not cover. So I wait for the next review.
Sorry, something went wrong.
What kind of test case could convince you? |
Sorry, something went wrong.
|
Also BTW I notice your "restart CI" commit -- you could use git commit --allow-empty to avoid having to push a dummy change + a revert. (just a recommendation in case you didn't know) |
Sorry, something went wrong.
I didn't know. That's helpful, thanks! |
Sorry, something went wrong.
…UnreachableLines` provided by @A5rocks and extend the current logic to fix it.
There was a problem hiding this comment.
Sorry, something went wrong.
| for candidate in set(itertools.chain(*uselessness_errors)): | ||
| if all( | ||
| (candidate in errors) or (candidate[2] in lines) | ||
| for errors, lines in zip(uselessness_errors, unreachable_lines) |
There was a problem hiding this comment.
Why can't this just use the last round of unreachability warnings?
Sorry, something went wrong.
There was a problem hiding this comment.
That is a good question. I initially implemented it so in 229e4a6. However, in the last function definition of test case testNewRedefineTryStatement, the first response to reveal_type does provide complete information, but the second (and final) response does not:
def f4() -> None:
while int():
try:
x = 1
if int():
x = ""
break
if int():
while int():
if int():
x = None
break
finally:
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str, None]" \
# N: Revealed type is "Union[builtins.int, None]"I have no experience with the new --allow-redefinition-new option so far, and have not tried to investigate why this happens. I just extended my approach in c24c950.
Sorry, something went wrong.
There was a problem hiding this comment.
Wouldn't that case also allow something not allowed for str? Eg does that error for:
if x is not None:
print(x + 5)Above the reveal_type
Sorry, something went wrong.
There was a problem hiding this comment.
Oh, after re-reading, it seems that I have not hit the point of your question. Another try: There might be some scope for optimisations. However, I am unsure what can possibly happen when running Mypy with different configurations (like in the f4 example discussed above), so I preferred to implement this part of the approach as robustly as possible.
Sorry, something went wrong.
There was a problem hiding this comment.
On the other hand it's possible to be too robust: mypy only prints the errors for the last round right? So it's possible (through some bug elsewhere, like forgetting to propagate line numbers maybe?) to have unreachable code in the last run that isn't marked -- and so have no signal that the code there isn't checked.
On the other other hand, just using the last iteration's unreachability errors might be wrong in presence of bugs like in f4, but it's simpler (and implementation is trivial to get right) and could never mislead the user about what code could get errors.
Basically I think it's better to match the semantics of how error reporting happens (ie during the last round of checking) then to have only one type of error be correct in face of bugs.
Edit: actually I might be wrong about my assumptions here, let me play with your PR a bit and I'll update this.
Sorry, something went wrong.
There was a problem hiding this comment.
My bad; the -warn-unreachable flag was missing in the test case. Now I get both Statement is unreachable and Revealed type is "builtins.str". However, the same happens when I check the modified test with Mypy-Master. Therefore, I don't know if this actually points to a flaw in my current approach. I will investigate tomorrow....
Sorry, something went wrong.
There was a problem hiding this comment.
...I had no time for a detailed investigation yet, but I tend to think the problem with the currently discussed test case has nothing to do with this PR, because the response to reveal_type and the unreachable error for the same line happen at the same iteration step.
Maybe the first hint on the cause for this discrepancy: expr.node.type = Union[builtins.int, builtins.str, None] but TypeChecker.lookup_type(expr) = Union[builtins.int, None].
Sorry, something went wrong.
There was a problem hiding this comment.
@JukkaL do you think types getting narrower between iterations (see previous comments) should be allowed?
If not, then could this PR simply suppress all output and only print out the last iteration?
Sorry, something went wrong.
There was a problem hiding this comment.
Maybe I misunderstand, but that sounds like reverting the "fix part" of this PR.
This is the current behaviour with the proposed changes to Mypy:
y = None
while y is None:
reveal_type(y) # None
if y is None:
y = []
y.append(1)
reveal_type(y) # list[int]The responses to reveal_type are the result of the first iteration step. In the second iteration step, the whole loop's body is unreachable.
Sorry, something went wrong.
There was a problem hiding this comment.
I think @tyralla is right, and just printing out the last iteration is not the right thing to do. I'll still need to experiment with this however.
Sorry, something went wrong.
test case from @auntsaninja (slightly simplified)
|
According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅ |
Sorry, something went wrong.
|
I updated this PR to also fix #19170. (Or, at least the test case provided by Shantanu.) I have edited the PR's title and initial description accordingly. |
Sorry, something went wrong.
|
Thanks for working on the performance regression! It would be great to measure the impact using the misc/perf_compare.py script. I can do this but possibly not until next week. |
Sorry, something went wrong.
|
I did not work with misc/perf_compare.py so far, but using it does not seem very complicated. I will try it later. |
Sorry, something went wrong.
|
I just executed python .\misc\perf_compare.py master fix/reachability_regression. The results: master 10.851s (0.0%) | stdev 0.373s fix/reachability_regression 10.044s (-7.4%) | stdev 0.423s Total time taken by the whole benchmarking program (including any setup): 11 minutes, 32 seconds So, according to this measurement, this PR achieves a speed increase of 7.4 %, while #18433 resulted in a slowdown of 8.7 %. I am not familiar with the involved tools, but this intuitively makes sense to me and aligns roughly with my initial estimate using Measure-command {python runtests.py self} (which is already somewhat outdated because some details of the implementation changed). |
Sorry, something went wrong.
This looks good. The exact numbers depend on the specifics of hardware, Python version, C compiler, etc. |
Sorry, something went wrong.
|
I still think this is a bug: #19118 (comment) But it's probably a bug with some other things rather than specifically the logic in this PR (mainly because the logic looks right to me). (though, what's the point of more robust logic if it cannot prevent that) |
Sorry, something went wrong.
|
I appreciate your carefulness. Still, I rethought the problem and am now very sure it is beyond the scope of this PR. This PR only tries to correctly aggregate information that Mypy collects stepwise when analysing loops. The bug you discovered, however, can be triggered without loops: # Mypy 1.16.0
# --allow-redefinition-new --local-partial-types --warn-unreachable
def f() -> None:
try:
x = 1
if int():
x = ""
return
if int():
x = None
return
finally:
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str, None]" \
# N: Revealed type is "builtins.int"
if isinstance(x, str): # E: Subclass of "int" and "str" cannot exist: would have incompatible method signatures
reveal_type(x) # E: Statement is unreachable \
# E: Revealed type is "builtins.str"
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str, None]" \
# N: Revealed type is "builtins.int"This suggests that try-finally statements are also handled iteratively and may require similar treatment to the one we are discussing for loops in this PR. The corresponding code and code comments suggest the same: Lines 4954 to 4985 in b025bda Would you like to open a separate issue? I think I could work on it next week. |
Sorry, something went wrong.
…ally clauses. (#19270) Fixes #19269 This PR refactors the logic implemented in #19118 (which only targeted repeatedly checked loops) and applies it to repeatedly checked finally clauses. I moved nearly all relevant code to the class `LoopErrorWatcher`, which now has the more general name `IterationErrorWatcher`, to avoid code duplication. However, one duplication is left, which concerns error reporting. It would be nice and easy to move this functionality to `IterationErrorWatcher`, too, but this would result in import cycles, and I am unsure if working with `TYPE_CHECKING` and postponed importing is acceptable in such cases (both for Mypy and Mypyc). After the refactoring, it should not be much effort to apply the logic to other cases where code sections are analysed iteratively. However, the only thing that comes to my mind is the repeated checking of functions with arguments that contain constrained type variables. I will check it. If anyone finds a similar case and the solution is as simple as expected, we could add the fix to this PR, of course.
…st manner. (#19324) This PR fixes a regression introduced in #19118 and discussed in #19270. The combination of the revealed types of individual iteration steps now relies on collecting the original type objects instead of parts of preliminary `revealed_type` notes. As @JukkaL suspected, this approach is much more straightforward than introducing a sufficiently complete `revealed_type` note parser. Please note that I appended a commit that refactors already existing code. It is mainly code-moving, so I hope it does not complicate the review of this PR.
…ally clauses. (#19270) Fixes #19269 This PR refactors the logic implemented in #19118 (which only targeted repeatedly checked loops) and applies it to repeatedly checked finally clauses. I moved nearly all relevant code to the class `LoopErrorWatcher`, which now has the more general name `IterationErrorWatcher`, to avoid code duplication. However, one duplication is left, which concerns error reporting. It would be nice and easy to move this functionality to `IterationErrorWatcher`, too, but this would result in import cycles, and I am unsure if working with `TYPE_CHECKING` and postponed importing is acceptable in such cases (both for Mypy and Mypyc). After the refactoring, it should not be much effort to apply the logic to other cases where code sections are analysed iteratively. However, the only thing that comes to my mind is the repeated checking of functions with arguments that contain constrained type variables. I will check it. If anyone finds a similar case and the solution is as simple as expected, we could add the fix to this PR, of course.
…st manner. (#19324) This PR fixes a regression introduced in #19118 and discussed in #19270. The combination of the revealed types of individual iteration steps now relies on collecting the original type objects instead of parts of preliminary `revealed_type` notes. As @JukkaL suspected, this approach is much more straightforward than introducing a sufficiently complete `revealed_type` note parser. Please note that I appended a commit that refactors already existing code. It is mainly code-moving, so I hope it does not complicate the review of this PR.
| Back | FazBrowse Home | New Git URL |
Fixes #18606
Closes #18511
Improves #18991
Fixes #19170
This change is an improvement over 9685171. Besides fixing the regressions reported in #18606 and #19170 and removing the duplicates reported in #18511, it should significantly reduce the performance regression reported in #18991. At least running Measure-command {python runtests.py self} on my computer (with removed cache) is 10 % faster.