-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[ty] Offer "Did you mean...?" suggestions for unresolved from
imports and unresolved attributes
#18705
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
Conversation
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.
This module has been renamed from ide_support
to all_members
because it's no longer just used for autocompletions; it's now also used for diagnostic suggestions.
Mypy_primer is failing because this
Which seems like it is probably a pre-existing bug exposed by this PR (but we obviously won't be able to land this PR without fixing it!). |
76a50bf
to
f7cdb88
Compare
Minimal repro for the primer panic on this branch: class Point:
def orthogonal_direction(self):
self[0].is_zero
def test_point(p2: Point):
p2.y |
And yes, it's easy to trigger the panic on diff --git a/crates/ty_ide/src/completion.rs b/crates/ty_ide/src/completion.rs
index a8b93080b4..9f0a013b31 100644
--- a/crates/ty_ide/src/completion.rs
+++ b/crates/ty_ide/src/completion.rs
@@ -1835,6 +1835,23 @@ f"{f.<CURSOR>
test.assert_completions_include("method");
}
+ #[test]
+ fn no_panic_for_attribute_table_that_contains_subscript() {
+ let test = cursor_test(
+ r#"
+class Point:
+ def orthogonal_direction(self):
+ self[0].is_zero
+
+def test_point(p2: Point):
+ p2.<CURSOR>
+"#,
+ );
+
+ let completions = test.completions();
+ assert_eq!(completions, "orthogonal_direction");
+ }
|
The primer panic should be fixed by #18707, but I won't attempt to rebase this PR branch on top of that as the commit history got somewhat messy here 😆 |
Mypy_primer is now timing out, but it appears that it ran ty successfully on all selected projects. I'm guessing it's taking ages to compute the diff between - error[unresolved-attribute] Type `Foo` has no attribute `bar`
+ error[unresolved-attribute] Type `Foo` has no attribute `bar`: Did you mean `baz`? So this PR is ready for review! |
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.
I reviewed the tests and did a high-level review of the implementation.
This will probably make us look a lot slower on early benchmarks of codebases where we raise a lot of AttributeError false positives, but so it goes; we should eliminate the false positive AttributeErrors.
Very cool feature!
} | ||
|
||
// Filter out the unresolved member itself. | ||
// Otherwise (due to our implementation of implicit instance attributes), |
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.
Not sure I understand this. It sounds like this is saying that we consider an access of self.attribute
(no assignment) sufficient to include attribute
in all_members
? Do we really do that? Why?
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.
It sounds like this is saying that we consider an access of
self.attribute
(no assignment) sufficient to includeattribute
inall_members
? Do we really do that?
That does appear to be the case, yes. If I make this diff to the PR branch:
diff --git a/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs b/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs
index 11fc59674e..ff1fc86a28 100644
--- a/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs
+++ b/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs
@@ -66,18 +66,6 @@ where
return None;
}
- // Filter out the unresolved member itself.
- // Otherwise (due to our implementation of implicit instance attributes),
- // we end up giving bogus suggestions like this:
- //
- // ```python
- // class Foo:
- // _attribute = 42
- // def bar(self):
- // print(self.attribute) # error: unresolved attribute `attribute`; did you mean `attribute`?
- // ```
- let options = options.filter(|name| name != unresolved_member);
-
then we get these changes to the snapshots:
And those seem to be self-evidently worse "Did you mean?" suggestions 😆
Why?
I'm not sure. I agree that it seems a bit suspect. @sharkdp, do you have any ideas off the top of your head for why attribute
might be included in the list returned by all_members
here? If not, I can look into this as a followup.
…ence * main: (71 commits) Bump 0.12.0 (astral-sh#18724) Revert "[ty] Offer "Did you mean...?" suggestions for unresolved `from` imports and unresolved attributes (astral-sh#18705)" (astral-sh#18721) [`flake8-return`] Stabilize only add `return None` at the end when fixing `implicit-return` (`RET503`) (astral-sh#18516) [`pyupgrade`] Stabilize `non-pep695-generic-function` (`UP047`) (astral-sh#18524) [`pyupgrade`] Stabilize `non-pep695-generic-class` (`UP046`) (astral-sh#18519) [`pandas-vet`] Deprecate `pandas-df-variable-name` (`PD901`) (astral-sh#18618) [`flake8-bandit`] Remove `suspicious-xmle-tree-usage` (`S320`) (astral-sh#18617) Stabilize `dataclass-enum` (`RUF049`) (astral-sh#18570) Stabilize `unnecessary-dict-index-lookup` (`PLR1733`) (astral-sh#18571) Remove rust-toolchain.toml from sdist (astral-sh#17925) Stabilize `starmap-zip` (`RUF058`) (astral-sh#18525) [`flake8-logging`] Stabilize `exc-info-outside-except-handler` (`LOG014`) (astral-sh#18517) [`pyupgrade`] Stabilize `non-pep604-annotation-optional` (`UP045`) and preview behavior for `non-pep604-annotation-union` (`UP007`) (astral-sh#18505) Stabilize `pytest-warns-too-broad` (`PT030`) (astral-sh#18568) Stabilize `for-loop-writes` (`FURB122`) (astral-sh#18565) Stabilize `pytest-warns-with-multiple-statements` (`PT031`) (astral-sh#18569) Stabilize `pytest-parameter-with-default-argument` (`PT028`) (astral-sh#18566) Stabilize `nan-comparison` (`PLW0177`) (astral-sh#18559) Stabilize `check-and-remove-from-set` (`FURB132`) (astral-sh#18560) Stabilize `unnecessary-round` (`RUF057`) (astral-sh#18563) ...
Summary
This PR (a collaboration with @ntBre) adds "Did you mean...?" suggestions for unresolved
from
imports and unresolved attributes. For example:The implementation uses the Levenshtein distance algorithm to calculate which possible member of the type is a closest match to the unresolved member that triggered the diagnostic. This specific Levenshtein implementation is an almost exact 1:1 port of CPython's implementation here, and many of the tests in this PR are also ported from CPython's tests. You can see CPython's Levenshtein implementation in action if you access a nonexistent attribute or import in the REPL:
Our motivation for copying CPython's implementation almost exactly is that CPython has had this feature for several Python versions now, and during that time many bug reports have been filed regarding incorrect suggestions, which have since been fixed. This implementation is thus very well "battle-tested" by this point; we can say with a reasonable degree of confidence that it gives good suggestions for unresolved members in the Python context.
Test Plan
levenshtein.rs
Co-authored-by: Brent Westbrook [email protected]