Skip to content
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

Ignore NPY201 inside except blocks for compatibility with older numpy versions #12490

Merged
merged 6 commits into from
Jul 24, 2024

Conversation

AlexWaygood
Copy link
Member

Summary

Fixes #12476.

This PR makes it so that deprecated imports and attribute accesses of numpy members are ignored in the following conditions:

  • For attribute accesses (e.g. np.ComplexWarning), we only ignore the violation if it's inside an except AttributeError block, and the member is accessed through its non-deprecated name in the associated try block.
  • For uses of the numpy member where it's simply an ExprName node, we check to see how the numpy member was bound. If it was bound via a from numpy import foo statement, we check to see if that import statement took place inside an except ImportError or except ModuleNotFoundError block. If so, and if the numpy member was imported through its non-deprecated name in the associated try block, we ignore the violation in the same way.

Examples:

import numpy as np

try:
    np.all([True, True])
except AttributError:
    np.alltrue([True, True])  # Okay

try:
    from numpy.exceptions import ComplexWarning
except ImportError:
    from numpy import ComplexWarning

x = ComplexWarning()  # Okay

This was more complex than I expected, since we currently track handled exceptions in the semantic model, but we don't track suspended exceptions. I.e., if we're in a lint rule and we're examining a node inside a try block, we know which exceptions the enclosing StmtTry node handles. But if we're in a lint rule and we're examining a node inside an except block, we don't have that information easily to hand.

Test Plan

Added some fixtures and snapshots.

@AlexWaygood AlexWaygood added the rule Implementing or modifying a lint rule label Jul 24, 2024
Copy link

codspeed-hq bot commented Jul 24, 2024

CodSpeed Performance Report

Merging #12490 will not alter performance

Comparing alex/numpy-depr (9c8c7d3) with alex/numpy-depr (bb14c21)

Summary

✅ 33 untouched benchmarks

Copy link
Contributor

github-actions bot commented Jul 24, 2024

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

@MichaReiser
Copy link
Member

The lexer benchmark is drunk!

@AlexWaygood
Copy link
Member Author

The lexer benchmark is drunk!

Hey, I'll take it!

return false;
};
let suspended_exceptions = Exceptions::from_try_stmt(try_node, semantic);
if !suspended_exceptions.contains(Exceptions::ATTRIBUTE_ERROR) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we check semantic.handled_exceptions instead? Does that work? Then we wouldn't need to find the try statement or anything.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, semantic.handled_exceptions records the exceptions if we're visiting the try block of a try/except statement. But here we're visiting the except block of a try/except statement. Hence "suspended_exceptions" rather than "handled_exceptions".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we still need to find the node for the try statement so that we can check whether it was accessed via the undeprecated call path in the try block

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Maybe give @charliermarsh a grace period of a day to look at the semantic model changes ;)

Comment on lines +904 to +910
/// AST visitor that searches an AST tree for [`ast::StmtImportFrom`] nodes
/// that match a certain [`QualifiedName`].
struct ImportSearcher<'a> {
module: &'a str,
name: &'a str,
found_import: bool,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it matters much because we only run this code in the error case, but it is kind of expensive. Do we have no way of resolving the statement in which a binding is declared and then traverse upwards (by using the parent API?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The situation is that we're visiting an except block in a try/except statement. We find a deprecated numpy access in that except block. We need to find out if there's an undeprecated access in the try block of the try/except statement. The way I do this is by traversing upwards from the node we're linting in the except block (using the parent API) to find the StmtTry node, then from the StmtTry node I then traverse downwards into the try block using the ImportSearcher to find if there are any imports using the undeprecated API.

@@ -1853,6 +1841,10 @@ impl<'a> Checker<'a> {
self.semantic.scope_id
};

if self.semantic.in_exception_handler() {
flags |= BindingFlags::IN_EXCEPT_HANDLER;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idly wondering if we should just store the semantic model flags on the binding, in theory it's tiny.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, get rid of the separate BindingFlags struct altogether, and store the semantic-model flags on Binding instead? Or store the semantic-model flags in addition to the BindingFlags?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store them in addition (and then remove this flag from BindingFlags). But, it's ok, no need to change.

@AlexWaygood AlexWaygood enabled auto-merge (squash) July 24, 2024 20:01
@AlexWaygood AlexWaygood merged commit 928ffd6 into main Jul 24, 2024
17 checks passed
@AlexWaygood AlexWaygood deleted the alex/numpy-depr branch July 24, 2024 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rule Implementing or modifying a lint rule
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NPY201 triggers on code written to support both numpy 2 and legacy numpy
3 participants