mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-07 13:15:19 +00:00
Revert "[ty] Offer "Did you mean...?" suggestions for unresolved from
imports and unresolved attributes (#18705)" (#18721)
This commit is contained in:
parent
a93992fa30
commit
685eac10e5
14 changed files with 96 additions and 794 deletions
|
@ -8,8 +8,6 @@ extend-exclude = [
|
|||
# words naturally. It's annoying to have to make all
|
||||
# of them actually words. So just ignore typos here.
|
||||
"crates/ty_ide/src/completion.rs",
|
||||
# Same for "Did you mean...?" levenshtein tests.
|
||||
"crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs",
|
||||
]
|
||||
|
||||
[default.extend-words]
|
||||
|
|
112
crates/ty/docs/rules.md
generated
112
crates/ty/docs/rules.md
generated
|
@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L94)
|
||||
</details>
|
||||
|
||||
## `conflicting-argument-forms`
|
||||
|
@ -83,7 +83,7 @@ f(int) # error
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L138)
|
||||
</details>
|
||||
|
||||
## `conflicting-declarations`
|
||||
|
@ -113,7 +113,7 @@ a = 1
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L164)
|
||||
</details>
|
||||
|
||||
## `conflicting-metaclass`
|
||||
|
@ -144,7 +144,7 @@ class C(A, B): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L189)
|
||||
</details>
|
||||
|
||||
## `cyclic-class-definition`
|
||||
|
@ -175,7 +175,7 @@ class B(A): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L215)
|
||||
</details>
|
||||
|
||||
## `duplicate-base`
|
||||
|
@ -201,7 +201,7 @@ class B(A, A): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L259)
|
||||
</details>
|
||||
|
||||
## `escape-character-in-forward-annotation`
|
||||
|
@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L280)
|
||||
</details>
|
||||
|
||||
## `inconsistent-mro`
|
||||
|
@ -367,7 +367,7 @@ class C(A, B): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L371)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L366)
|
||||
</details>
|
||||
|
||||
## `index-out-of-bounds`
|
||||
|
@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L395)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L390)
|
||||
</details>
|
||||
|
||||
## `invalid-argument-type`
|
||||
|
@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type]
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L415)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L410)
|
||||
</details>
|
||||
|
||||
## `invalid-assignment`
|
||||
|
@ -445,7 +445,7 @@ a: int = ''
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L455)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450)
|
||||
</details>
|
||||
|
||||
## `invalid-attribute-access`
|
||||
|
@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1459)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1454)
|
||||
</details>
|
||||
|
||||
## `invalid-base`
|
||||
|
@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base]
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L477)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472)
|
||||
</details>
|
||||
|
||||
## `invalid-context-manager`
|
||||
|
@ -527,7 +527,7 @@ with 1:
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L528)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523)
|
||||
</details>
|
||||
|
||||
## `invalid-declaration`
|
||||
|
@ -555,7 +555,7 @@ a: str
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L549)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L544)
|
||||
</details>
|
||||
|
||||
## `invalid-exception-caught`
|
||||
|
@ -596,7 +596,7 @@ except ZeroDivisionError:
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L572)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
|
||||
</details>
|
||||
|
||||
## `invalid-generic-class`
|
||||
|
@ -627,7 +627,7 @@ class C[U](Generic[T]): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L608)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L603)
|
||||
</details>
|
||||
|
||||
## `invalid-legacy-type-variable`
|
||||
|
@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L634)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L629)
|
||||
</details>
|
||||
|
||||
## `invalid-metaclass`
|
||||
|
@ -692,7 +692,7 @@ class B(metaclass=f): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L683)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L678)
|
||||
</details>
|
||||
|
||||
## `invalid-overload`
|
||||
|
@ -740,7 +740,7 @@ def foo(x: int) -> int: ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L710)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L705)
|
||||
</details>
|
||||
|
||||
## `invalid-parameter-default`
|
||||
|
@ -765,7 +765,7 @@ def f(a: int = ''): ...
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L753)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L748)
|
||||
</details>
|
||||
|
||||
## `invalid-protocol`
|
||||
|
@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L343)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L338)
|
||||
</details>
|
||||
|
||||
## `invalid-raise`
|
||||
|
@ -846,7 +846,7 @@ def g():
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L773)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L768)
|
||||
</details>
|
||||
|
||||
## `invalid-return-type`
|
||||
|
@ -870,7 +870,7 @@ def func() -> int:
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L436)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431)
|
||||
</details>
|
||||
|
||||
## `invalid-super-argument`
|
||||
|
@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)`
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L816)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811)
|
||||
</details>
|
||||
|
||||
## `invalid-syntax-in-forward-annotation`
|
||||
|
@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L662)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L657)
|
||||
</details>
|
||||
|
||||
## `invalid-type-checking-constant`
|
||||
|
@ -983,7 +983,7 @@ TYPE_CHECKING = ''
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L855)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L850)
|
||||
</details>
|
||||
|
||||
## `invalid-type-form`
|
||||
|
@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L879)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L874)
|
||||
</details>
|
||||
|
||||
## `invalid-type-guard-call`
|
||||
|
@ -1045,7 +1045,7 @@ f(10) # Error
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L931)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926)
|
||||
</details>
|
||||
|
||||
## `invalid-type-guard-definition`
|
||||
|
@ -1078,7 +1078,7 @@ class C:
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L903)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L898)
|
||||
</details>
|
||||
|
||||
## `invalid-type-variable-constraints`
|
||||
|
@ -1112,7 +1112,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L954)
|
||||
</details>
|
||||
|
||||
## `missing-argument`
|
||||
|
@ -1136,7 +1136,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L988)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983)
|
||||
</details>
|
||||
|
||||
## `no-matching-overload`
|
||||
|
@ -1164,7 +1164,7 @@ func("string") # error: [no-matching-overload]
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1007)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002)
|
||||
</details>
|
||||
|
||||
## `non-subscriptable`
|
||||
|
@ -1187,7 +1187,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1030)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1025)
|
||||
</details>
|
||||
|
||||
## `not-iterable`
|
||||
|
@ -1212,7 +1212,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1048)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1043)
|
||||
</details>
|
||||
|
||||
## `parameter-already-assigned`
|
||||
|
@ -1238,7 +1238,7 @@ f(1, x=2) # Error raised here
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1099)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1094)
|
||||
</details>
|
||||
|
||||
## `raw-string-type-annotation`
|
||||
|
@ -1297,7 +1297,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1435)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430)
|
||||
</details>
|
||||
|
||||
## `subclass-of-final-class`
|
||||
|
@ -1325,7 +1325,7 @@ class B(A): ... # Error raised here
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1190)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1185)
|
||||
</details>
|
||||
|
||||
## `too-many-positional-arguments`
|
||||
|
@ -1351,7 +1351,7 @@ f("foo") # Error raised here
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1235)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230)
|
||||
</details>
|
||||
|
||||
## `type-assertion-failure`
|
||||
|
@ -1378,7 +1378,7 @@ def _(x: int):
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208)
|
||||
</details>
|
||||
|
||||
## `unavailable-implicit-super-arguments`
|
||||
|
@ -1422,7 +1422,7 @@ class A:
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251)
|
||||
</details>
|
||||
|
||||
## `unknown-argument`
|
||||
|
@ -1448,7 +1448,7 @@ f(x=1, y=2) # Error raised here
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1308)
|
||||
</details>
|
||||
|
||||
## `unresolved-attribute`
|
||||
|
@ -1475,7 +1475,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1334)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1329)
|
||||
</details>
|
||||
|
||||
## `unresolved-import`
|
||||
|
@ -1499,7 +1499,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1356)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1351)
|
||||
</details>
|
||||
|
||||
## `unresolved-reference`
|
||||
|
@ -1523,7 +1523,7 @@ print(x) # NameError: name 'x' is not defined
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1375)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370)
|
||||
</details>
|
||||
|
||||
## `unsupported-bool-conversion`
|
||||
|
@ -1559,7 +1559,7 @@ b1 < b2 < b1 # exception raised here
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1068)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063)
|
||||
</details>
|
||||
|
||||
## `unsupported-operator`
|
||||
|
@ -1586,7 +1586,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1394)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1389)
|
||||
</details>
|
||||
|
||||
## `zero-stepsize-in-slice`
|
||||
|
@ -1610,7 +1610,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1416)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411)
|
||||
</details>
|
||||
|
||||
## `invalid-ignore-comment`
|
||||
|
@ -1666,7 +1666,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1120)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1115)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-implicit-call`
|
||||
|
@ -1697,7 +1697,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L112)
|
||||
</details>
|
||||
|
||||
## `possibly-unbound-import`
|
||||
|
@ -1728,7 +1728,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1142)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1137)
|
||||
</details>
|
||||
|
||||
## `redundant-cast`
|
||||
|
@ -1754,7 +1754,7 @@ cast(int, f()) # Redundant
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1487)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1482)
|
||||
</details>
|
||||
|
||||
## `undefined-reveal`
|
||||
|
@ -1777,7 +1777,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1295)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290)
|
||||
</details>
|
||||
|
||||
## `unknown-rule`
|
||||
|
@ -1845,7 +1845,7 @@ class D(C): ... # error: [unsupported-base]
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L495)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L490)
|
||||
</details>
|
||||
|
||||
## `division-by-zero`
|
||||
|
@ -1868,7 +1868,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L241)
|
||||
</details>
|
||||
|
||||
## `possibly-unresolved-reference`
|
||||
|
@ -1895,7 +1895,7 @@ print(x) # NameError: name 'x' is not defined
|
|||
|
||||
### Links
|
||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1168)
|
||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1163)
|
||||
</details>
|
||||
|
||||
## `unused-ignore-comment`
|
||||
|
|
|
@ -2167,57 +2167,6 @@ reveal_type(Foo.BAR.value) # revealed: @Todo(Attribute access on enum classes)
|
|||
reveal_type(Foo.__members__) # revealed: @Todo(Attribute access on enum classes)
|
||||
```
|
||||
|
||||
## Suggestions for obvious typos
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
For obvious typos, we add a "Did you mean...?" suggestion to the diagnostic.
|
||||
|
||||
```py
|
||||
import collections
|
||||
|
||||
print(collections.dequee) # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
But the suggestion is suppressed if the only close matches start with a leading underscore:
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
_bar = 42
|
||||
|
||||
print(Foo.bar) # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
The suggestion is not suppressed if the typo itself starts with a leading underscore, however:
|
||||
|
||||
```py
|
||||
print(Foo._barr) # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
And in method contexts, the suggestion is never suppressed if accessing an attribute on an instance
|
||||
of the method's enclosing class:
|
||||
|
||||
```py
|
||||
class Bar:
|
||||
_attribute = 42
|
||||
|
||||
def f(self, x: "Bar"):
|
||||
# TODO: we should emit `[unresolved-attribute]` here, should have the same behaviour as `x.attribute` below
|
||||
print(self.attribute)
|
||||
|
||||
# We give a suggestion here, even though the only good candidates start with underscores and the typo does not,
|
||||
# because we're in a method context and `x` is an instance of the enclosing class.
|
||||
print(x.attribute) # error: [unresolved-attribute]
|
||||
|
||||
class Baz:
|
||||
def f(self, x: Bar):
|
||||
# No suggestion is given here, because:
|
||||
# - the good suggestions all start with underscores
|
||||
# - the typo does not start with an underscore
|
||||
# - We *are* in a method context, but `x` is not an instance of the enclosing class
|
||||
print(x.attribute) # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
Some of the tests in the *Class and instance variables* section draw inspiration from
|
||||
|
|
|
@ -205,39 +205,3 @@ python-version = "3.13"
|
|||
import aifc # error: [unresolved-import]
|
||||
from distutils import sysconfig # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## `from` import that has a typo
|
||||
|
||||
We offer a "Did you mean?" subdiagnostic suggestion if there's a name in the module that's
|
||||
reasonably similar to the unresolved member.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`foo.py`:
|
||||
|
||||
```py
|
||||
from collections import dequee # error: [unresolved-import]
|
||||
```
|
||||
|
||||
However, we suppress the suggestion if the only close matches in the module start with a leading
|
||||
underscore:
|
||||
|
||||
`bar.py`:
|
||||
|
||||
```py
|
||||
from baz import foo # error: [unresolved-import]
|
||||
```
|
||||
|
||||
`baz.py`:
|
||||
|
||||
```py
|
||||
_foo = 42
|
||||
```
|
||||
|
||||
The suggestion is never suppressed if the typo itself starts with a leading underscore, however:
|
||||
|
||||
`eggs.py`:
|
||||
|
||||
```py
|
||||
from baz import _fooo # error: [unresolved-import]
|
||||
```
|
||||
|
|
|
@ -1,115 +0,0 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attributes.md - Attributes - Suggestions for obvious typos
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | import collections
|
||||
2 |
|
||||
3 | print(collections.dequee) # error: [unresolved-attribute]
|
||||
4 | class Foo:
|
||||
5 | _bar = 42
|
||||
6 |
|
||||
7 | print(Foo.bar) # error: [unresolved-attribute]
|
||||
8 | print(Foo._barr) # error: [unresolved-attribute]
|
||||
9 | class Bar:
|
||||
10 | _attribute = 42
|
||||
11 |
|
||||
12 | def f(self, x: "Bar"):
|
||||
13 | # TODO: we should emit `[unresolved-attribute]` here, should have the same behaviour as `x.attribute` below
|
||||
14 | print(self.attribute)
|
||||
15 |
|
||||
16 | # We give a suggestion here, even though the only good candidates start with underscores and the typo does not,
|
||||
17 | # because we're in a method context and `x` is an instance of the enclosing class.
|
||||
18 | print(x.attribute) # error: [unresolved-attribute]
|
||||
19 |
|
||||
20 | class Baz:
|
||||
21 | def f(self, x: Bar):
|
||||
22 | # No suggestion is given here, because:
|
||||
23 | # - the good suggestions all start with underscores
|
||||
24 | # - the typo does not start with an underscore
|
||||
25 | # - We *are* in a method context, but `x` is not an instance of the enclosing class
|
||||
26 | print(x.attribute) # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<module 'collections'>` has no attribute `dequee`
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | import collections
|
||||
2 |
|
||||
3 | print(collections.dequee) # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^^^^ Did you mean `deque`?
|
||||
4 | class Foo:
|
||||
5 | _bar = 42
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<class 'Foo'>` has no attribute `bar`
|
||||
--> src/mdtest_snippet.py:7:7
|
||||
|
|
||||
5 | _bar = 42
|
||||
6 |
|
||||
7 | print(Foo.bar) # error: [unresolved-attribute]
|
||||
| ^^^^^^^
|
||||
8 | print(Foo._barr) # error: [unresolved-attribute]
|
||||
9 | class Bar:
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<class 'Foo'>` has no attribute `_barr`
|
||||
--> src/mdtest_snippet.py:8:7
|
||||
|
|
||||
7 | print(Foo.bar) # error: [unresolved-attribute]
|
||||
8 | print(Foo._barr) # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^ Did you mean `_bar`?
|
||||
9 | class Bar:
|
||||
10 | _attribute = 42
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `Bar` has no attribute `attribute`
|
||||
--> src/mdtest_snippet.py:18:15
|
||||
|
|
||||
16 | # We give a suggestion here, even though the only good candidates start with underscores and the typo does not,
|
||||
17 | # because we're in a method context and `x` is an instance of the enclosing class.
|
||||
18 | print(x.attribute) # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^ Did you mean `_attribute`?
|
||||
19 |
|
||||
20 | class Baz:
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `Bar` has no attribute `attribute`
|
||||
--> src/mdtest_snippet.py:26:15
|
||||
|
|
||||
24 | # - the typo does not start with an underscore
|
||||
25 | # - We *are* in a method context, but `x` is not an instance of the enclosing class
|
||||
26 | print(x.attribute) # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
|
@ -1,69 +0,0 @@
|
|||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: basic.md - Structures - `from` import that has a typo
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## foo.py
|
||||
|
||||
```
|
||||
1 | from collections import dequee # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## bar.py
|
||||
|
||||
```
|
||||
1 | from baz import foo # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## baz.py
|
||||
|
||||
```
|
||||
1 | _foo = 42
|
||||
```
|
||||
|
||||
## eggs.py
|
||||
|
||||
```
|
||||
1 | from baz import _fooo # error: [unresolved-import]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-import]: Module `collections` has no member `dequee`
|
||||
--> src/foo.py:1:25
|
||||
|
|
||||
1 | from collections import dequee # error: [unresolved-import]
|
||||
| ^^^^^^ Did you mean `deque`?
|
||||
|
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-import]: Module `baz` has no member `foo`
|
||||
--> src/bar.py:1:17
|
||||
|
|
||||
1 | from baz import foo # error: [unresolved-import]
|
||||
| ^^^
|
||||
|
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-import]: Module `baz` has no member `_fooo`
|
||||
--> src/eggs.py:1:17
|
||||
|
|
||||
1 | from baz import _fooo # error: [unresolved-import]
|
||||
| ^^^^^ Did you mean `_foo`?
|
||||
|
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
|
@ -10,7 +10,7 @@ use crate::module_resolver::{Module, resolve_module};
|
|||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::place::FileScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::types::all_members::all_declarations_and_bindings;
|
||||
use crate::types::ide_support::all_declarations_and_bindings;
|
||||
use crate::types::{Type, binding_type, infer_scope_types};
|
||||
|
||||
pub struct SemanticModel<'db> {
|
||||
|
|
|
@ -38,7 +38,6 @@ use crate::semantic_index::definition::Definition;
|
|||
use crate::semantic_index::place::{ScopeId, ScopedPlaceId};
|
||||
use crate::semantic_index::{imported_modules, place_table, semantic_index};
|
||||
use crate::suppression::check_suppressions;
|
||||
pub use crate::types::all_members::all_members;
|
||||
use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding};
|
||||
pub(crate) use crate::types::class_base::ClassBase;
|
||||
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
||||
|
@ -47,6 +46,7 @@ use crate::types::function::{
|
|||
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
|
||||
};
|
||||
use crate::types::generics::{GenericContext, PartialSpecialization, Specialization};
|
||||
pub use crate::types::ide_support::all_members;
|
||||
use crate::types::infer::infer_unpack_types;
|
||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
|
@ -58,7 +58,6 @@ use instance::Protocol;
|
|||
pub use instance::{NominalInstanceType, ProtocolInstanceType};
|
||||
pub use special_form::SpecialFormType;
|
||||
|
||||
pub(crate) mod all_members;
|
||||
mod builder;
|
||||
mod call;
|
||||
mod class;
|
||||
|
@ -68,6 +67,7 @@ mod diagnostic;
|
|||
mod display;
|
||||
mod function;
|
||||
mod generics;
|
||||
pub(crate) mod ide_support;
|
||||
mod infer;
|
||||
mod instance;
|
||||
mod mro;
|
||||
|
|
|
@ -27,7 +27,7 @@ use crate::types::signatures::{Parameter, ParameterForm};
|
|||
use crate::types::{
|
||||
BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType,
|
||||
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType,
|
||||
WrapperDescriptorKind, all_members, todo_type,
|
||||
WrapperDescriptorKind, ide_support, todo_type,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
use ruff_python_ast as ast;
|
||||
|
@ -669,7 +669,7 @@ impl<'db> Bindings<'db> {
|
|||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(TupleType::from_elements(
|
||||
db,
|
||||
all_members::all_members(db, *ty)
|
||||
ide_support::all_members(db, *ty)
|
||||
.into_iter()
|
||||
.sorted()
|
||||
.map(|member| Type::string_literal(db, &member)),
|
||||
|
|
|
@ -225,10 +225,6 @@ pub enum ClassType<'db> {
|
|||
|
||||
#[salsa::tracked]
|
||||
impl<'db> ClassType<'db> {
|
||||
pub(super) fn is_protocol(self, db: &'db dyn Db) -> bool {
|
||||
self.class_literal(db).0.is_protocol(db)
|
||||
}
|
||||
|
||||
pub(super) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
match self {
|
||||
Self::NonGeneric(_) => self,
|
||||
|
|
|
@ -17,17 +17,12 @@ use crate::types::string_annotation::{
|
|||
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
|
||||
use crate::{Db, Module, ModuleName, Program, declare_lint};
|
||||
use itertools::Itertools;
|
||||
pub(crate) use levenshtein::{
|
||||
HideUnderscoredSuggestions, find_best_suggestion_for_unresolved_member,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
mod levenshtein;
|
||||
|
||||
/// Registers all known type check lints.
|
||||
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&CALL_NON_CALLABLE);
|
||||
|
@ -2217,7 +2212,7 @@ fn report_invalid_base<'ctx, 'db>(
|
|||
/// misconfigured their Python version.
|
||||
pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
db: &dyn Db,
|
||||
diagnostic: &mut LintDiagnosticGuard,
|
||||
mut diagnostic: LintDiagnosticGuard,
|
||||
full_submodule_name: &ModuleName,
|
||||
parent_module: &Module,
|
||||
) {
|
||||
|
@ -2252,5 +2247,5 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
|
|||
version_range = version_range.diagnostic_display(),
|
||||
));
|
||||
|
||||
add_inferred_python_version_hint_to_diagnostic(db, diagnostic, "resolving modules");
|
||||
add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving modules");
|
||||
}
|
||||
|
|
|
@ -1,378 +0,0 @@
|
|||
//! Infrastructure for providing "Did you mean..?" suggestions to attach to diagnostics.
|
||||
//!
|
||||
//! This is a Levenshtein implementation that is mainly ported from the implementation
|
||||
//! CPython uses to provide suggestions in its own exception messages.
|
||||
//! The tests similarly owe much to CPython's test suite.
|
||||
//! Many thanks to Pablo Galindo Salgado and others for implementing the original
|
||||
//! feature in CPython!
|
||||
|
||||
use crate::Db;
|
||||
use crate::types::{Type, all_members};
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use ruff_python_ast::name::Name;
|
||||
|
||||
/// Given a type and an unresolved member name, find the best suggestion for a member name
|
||||
/// that is similar to the unresolved member name.
|
||||
///
|
||||
/// This function is used to provide suggestions for subdiagnostics attached to
|
||||
/// `unresolved-attribute`, `unresolved-import`, and `unresolved-reference` diagnostics.
|
||||
pub(crate) fn find_best_suggestion_for_unresolved_member<'db>(
|
||||
db: &'db dyn Db,
|
||||
obj: Type<'db>,
|
||||
unresolved_member: &str,
|
||||
hide_underscored_suggestions: HideUnderscoredSuggestions,
|
||||
) -> Option<Name> {
|
||||
find_best_suggestion(
|
||||
all_members(db, obj),
|
||||
unresolved_member,
|
||||
hide_underscored_suggestions,
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether to hide suggestions that start with an underscore.
|
||||
///
|
||||
/// If the typo itself starts with an underscore, this policy is ignored.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum HideUnderscoredSuggestions {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
impl HideUnderscoredSuggestions {
|
||||
const fn is_no(self) -> bool {
|
||||
matches!(self, HideUnderscoredSuggestions::No)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_best_suggestion<O, I>(
|
||||
options: O,
|
||||
unresolved_member: &str,
|
||||
hide_underscored_suggestions: HideUnderscoredSuggestions,
|
||||
) -> Option<Name>
|
||||
where
|
||||
O: IntoIterator<IntoIter = I>,
|
||||
I: ExactSizeIterator<Item = Name>,
|
||||
{
|
||||
if unresolved_member.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let options = options.into_iter();
|
||||
|
||||
// Don't spend a *huge* amount of time computing suggestions if there are many candidates.
|
||||
// This limit is fairly arbitrary and can be adjusted as needed.
|
||||
if options.len() > 4096 {
|
||||
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);
|
||||
|
||||
let mut options: IndexSet<Name> =
|
||||
if hide_underscored_suggestions.is_no() || unresolved_member.starts_with('_') {
|
||||
options.collect()
|
||||
} else {
|
||||
options.filter(|name| !name.starts_with('_')).collect()
|
||||
};
|
||||
options.sort_unstable();
|
||||
find_best_suggestion_impl(options, unresolved_member)
|
||||
}
|
||||
|
||||
fn find_best_suggestion_impl(options: IndexSet<Name>, unresolved_member: &str) -> Option<Name> {
|
||||
let mut best_suggestion = None;
|
||||
|
||||
for member in options {
|
||||
let mut max_distance =
|
||||
(member.chars().count() + unresolved_member.chars().count() + 3) * MOVE_COST / 6;
|
||||
|
||||
if let Some((_, best_distance)) = best_suggestion {
|
||||
if best_distance > 0 {
|
||||
max_distance = max_distance.min(best_distance - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let current_distance = levenshtein_distance(unresolved_member, &member, max_distance);
|
||||
if current_distance > max_distance {
|
||||
continue;
|
||||
}
|
||||
|
||||
if best_suggestion
|
||||
.as_ref()
|
||||
.is_none_or(|(_, best_score)| ¤t_distance < best_score)
|
||||
{
|
||||
best_suggestion = Some((member, current_distance));
|
||||
}
|
||||
}
|
||||
|
||||
best_suggestion.map(|(suggestion, _)| suggestion)
|
||||
}
|
||||
|
||||
/// Determine the "cost" of converting `string_a` to `string_b`.
|
||||
fn substitution_cost(char_a: char, char_b: char) -> CharacterMatch {
|
||||
if char_a == char_b {
|
||||
return CharacterMatch::Exact;
|
||||
}
|
||||
|
||||
let char_a_lowercase = char_a.to_lowercase();
|
||||
let char_b_lowercase = char_b.to_lowercase();
|
||||
|
||||
if char_a_lowercase.len() == char_b_lowercase.len()
|
||||
&& char_a_lowercase.zip(char_b_lowercase).all(|(a, b)| a == b)
|
||||
{
|
||||
return CharacterMatch::CaseInsensitive;
|
||||
}
|
||||
|
||||
CharacterMatch::None
|
||||
}
|
||||
|
||||
/// The result of comparing two characters.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum CharacterMatch {
|
||||
Exact,
|
||||
CaseInsensitive,
|
||||
None,
|
||||
}
|
||||
|
||||
/// The cost of a Levenshtein insertion, deletion, or substitution.
|
||||
/// It should be the same as `CharacterMatch::None` cast to a `usize`.
|
||||
///
|
||||
/// This is used instead of the conventional unit cost to give these differences a higher cost than
|
||||
/// casing differences, which CPython assigns a cost of 1.
|
||||
const MOVE_COST: usize = CharacterMatch::None as usize;
|
||||
|
||||
/// Returns the [Levenshtein edit distance] between strings `string_a` and `string_b`.
|
||||
/// Uses the [Wagner-Fischer algorithm] to speed up the calculation.
|
||||
///
|
||||
/// [Levenshtein edit distance]: https://en.wikipedia.org/wiki/Levenshtein_distance
|
||||
/// [Wagner-Fischer algorithm]: https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm
|
||||
fn levenshtein_distance(string_a: &str, string_b: &str, max_cost: usize) -> usize {
|
||||
if string_a == string_b {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let string_a_chars: Vec<char> = string_a.chars().collect();
|
||||
let string_b_chars: Vec<char> = string_b.chars().collect();
|
||||
|
||||
// Trim away common affixes
|
||||
let pre = string_a_chars
|
||||
.iter()
|
||||
.zip(string_b_chars.iter())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.count();
|
||||
let string_a_chars = &string_a_chars[pre..];
|
||||
let string_b_chars = &string_b_chars[pre..];
|
||||
|
||||
// Trim away common suffixes
|
||||
let post = string_a_chars
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(string_b_chars.iter().rev())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.count();
|
||||
let mut string_a_chars = &string_a_chars[..string_a_chars.len() - post];
|
||||
let mut string_b_chars = &string_b_chars[..string_b_chars.len() - post];
|
||||
|
||||
let mut string_a_len = string_a_chars.len();
|
||||
let mut string_b_len = string_b_chars.len();
|
||||
|
||||
// Short-circuit if either string is empty after trimming affixes/suffixes
|
||||
if string_a_len == 0 || string_b_len == 0 {
|
||||
return MOVE_COST * (string_a_len + string_b_len);
|
||||
}
|
||||
|
||||
// `string_a` should refer to the shorter of the two strings.
|
||||
// This enables us to create a smaller buffer in the main loop below.
|
||||
if string_b_chars.len() < string_a_chars.len() {
|
||||
std::mem::swap(&mut string_a_chars, &mut string_b_chars);
|
||||
std::mem::swap(&mut string_a_len, &mut string_b_len);
|
||||
}
|
||||
|
||||
// Quick fail if a match is impossible.
|
||||
if (string_b_len - string_a_len) * MOVE_COST > max_cost {
|
||||
return max_cost + 1;
|
||||
}
|
||||
|
||||
let mut row = vec![0; string_a_len];
|
||||
for (i, v) in (MOVE_COST..MOVE_COST * (string_a_len + 1))
|
||||
.step_by(MOVE_COST)
|
||||
.enumerate()
|
||||
{
|
||||
row[i] = v;
|
||||
}
|
||||
|
||||
let mut result = 0;
|
||||
|
||||
for (b_index, b_char) in string_b_chars
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.take(string_b_len)
|
||||
{
|
||||
result = b_index * MOVE_COST;
|
||||
let mut distance = result;
|
||||
let mut minimum = usize::MAX;
|
||||
for index in 0..string_a_len {
|
||||
let substitute = distance + substitution_cost(b_char, string_a_chars[index]) as usize;
|
||||
distance = row[index];
|
||||
let insert_delete = result.min(distance) + MOVE_COST;
|
||||
result = insert_delete.min(substitute);
|
||||
|
||||
row[index] = result;
|
||||
if result < minimum {
|
||||
minimum = result;
|
||||
}
|
||||
}
|
||||
|
||||
if minimum > max_cost {
|
||||
return max_cost + 1;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use test_case::test_case;
|
||||
|
||||
/// Given a list of candidates, this test asserts that the best suggestion
|
||||
/// for the typo `bluch` is what we'd expect.
|
||||
///
|
||||
/// This test is ported from <https://github.com/python/cpython/blob/6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41/Lib/test/test_traceback.py#L4037-L4078>
|
||||
#[test_case(&["noise", "more_noise", "a", "bc", "bluchin"], "bluchin"; "test for additional characters")]
|
||||
#[test_case(&["noise", "more_noise", "a", "bc", "blech"], "blech"; "test for substituted characters")]
|
||||
#[test_case(&["noise", "more_noise", "a", "bc", "blch"], "blch"; "test for eliminated characters")]
|
||||
#[test_case(&["blach", "bluc"], "blach"; "substitutions are preferred over eliminations")]
|
||||
#[test_case(&["blach", "bluchi"], "blach"; "substitutions are preferred over additions")]
|
||||
#[test_case(&["blucha", "bluc"], "bluc"; "eliminations are preferred over additions")]
|
||||
#[test_case(&["Luch", "fluch", "BLuch"], "BLuch"; "case changes are preferred over substitutions")]
|
||||
fn test_good_suggestions(candidate_list: &[&str], expected_suggestion: &str) {
|
||||
let candidates: Vec<Name> = candidate_list.iter().copied().map(Name::from).collect();
|
||||
let suggestion = find_best_suggestion(candidates, "bluch", HideUnderscoredSuggestions::No);
|
||||
assert_eq!(suggestion.as_deref(), Some(expected_suggestion));
|
||||
}
|
||||
|
||||
/// Test ported from <https://github.com/python/cpython/blob/6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41/Lib/test/test_traceback.py#L4080-L4099>
|
||||
#[test]
|
||||
fn underscored_names_not_suggested_if_hide_policy_set_to_yes() {
|
||||
let suggestion = find_best_suggestion(
|
||||
[Name::from("_bluch")],
|
||||
"bluch",
|
||||
HideUnderscoredSuggestions::Yes,
|
||||
);
|
||||
if let Some(suggestion) = suggestion {
|
||||
panic!(
|
||||
"Expected no suggestions for `bluch` due to `HideUnderscoredSuggestions::Yes` but `{suggestion}` was suggested"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Test ported from <https://github.com/python/cpython/blob/6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41/Lib/test/test_traceback.py#L4080-L4099>
|
||||
#[test_case("_blach")]
|
||||
#[test_case("_luch")]
|
||||
fn underscored_names_are_suggested_if_hide_policy_set_to_yes_when_typo_is_underscored(
|
||||
typo: &str,
|
||||
) {
|
||||
let suggestion = find_best_suggestion(
|
||||
[Name::from("_bluch")],
|
||||
typo,
|
||||
HideUnderscoredSuggestions::Yes,
|
||||
);
|
||||
assert_eq!(suggestion.as_deref(), Some("_bluch"));
|
||||
}
|
||||
|
||||
/// Test ported from <https://github.com/python/cpython/blob/6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41/Lib/test/test_traceback.py#L4080-L4099>
|
||||
#[test_case("_luch")]
|
||||
#[test_case("_bluch")]
|
||||
fn non_underscored_names_always_suggested_even_if_typo_underscored(typo: &str) {
|
||||
let suggestion =
|
||||
find_best_suggestion([Name::from("bluch")], typo, HideUnderscoredSuggestions::Yes);
|
||||
assert_eq!(suggestion.as_deref(), Some("bluch"));
|
||||
}
|
||||
|
||||
/// This asserts that we do not offer silly suggestions for very small names.
|
||||
/// The test is ported from <https://github.com/python/cpython/blob/6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41/Lib/test/test_traceback.py#L4108-L4120>
|
||||
#[test_case("b")]
|
||||
#[test_case("v")]
|
||||
#[test_case("m")]
|
||||
#[test_case("py")]
|
||||
fn test_bad_suggestions_do_not_trigger_for_small_names(typo: &str) {
|
||||
let candidates = ["vvv", "mom", "w", "id", "pytho"].map(Name::from);
|
||||
let suggestion = find_best_suggestion(candidates, typo, HideUnderscoredSuggestions::No);
|
||||
if let Some(suggestion) = suggestion {
|
||||
panic!("Expected no suggestions for `{typo}` but `{suggestion}` was suggested");
|
||||
}
|
||||
}
|
||||
|
||||
/// Test ported from <https://github.com/python/cpython/blob/6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41/Lib/test/test_traceback.py#L4101-L4106>
|
||||
#[test]
|
||||
fn test_no_suggestion_for_very_different_attribute() {
|
||||
assert_eq!(
|
||||
find_best_suggestion(
|
||||
[Name::from("blech")],
|
||||
"somethingverywrong",
|
||||
HideUnderscoredSuggestions::No
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
/// These tests are from the Levenshtein Wikipedia article, updated to match CPython's
|
||||
/// implementation (just doubling the score to accommodate the MOVE_COST)
|
||||
#[test_case("kitten", "sitting", 6)]
|
||||
#[test_case("uninformed", "uniformed", 2)]
|
||||
#[test_case("flaw", "lawn", 4)]
|
||||
fn test_levenshtein_distance_calculation_wikipedia_examples(
|
||||
string_a: &str,
|
||||
string_b: &str,
|
||||
expected_distance: usize,
|
||||
) {
|
||||
assert_eq!(
|
||||
levenshtein_distance(string_a, string_b, usize::MAX),
|
||||
expected_distance
|
||||
);
|
||||
}
|
||||
|
||||
/// Test ported from <https://github.com/python/cpython/blob/6eb6c5dbfb528bd07d77b60fd71fd05d81d45c41/Lib/test/test_traceback.py#L4670-L4697>
|
||||
#[test_case("", "", 0)]
|
||||
#[test_case("", "a", 2)]
|
||||
#[test_case("a", "A", 1)]
|
||||
#[test_case("Apple", "Aple", 2)]
|
||||
#[test_case("Banana", "B@n@n@", 6)]
|
||||
#[test_case("Cherry", "Cherry!", 2)]
|
||||
#[test_case("---0---", "------", 2)]
|
||||
#[test_case("abc", "y", 6)]
|
||||
#[test_case("aa", "bb", 4)]
|
||||
#[test_case("aaaaa", "AAAAA", 5)]
|
||||
#[test_case("wxyz", "wXyZ", 2)]
|
||||
#[test_case("wxyz", "wXyZ123", 8)]
|
||||
#[test_case("Python", "Java", 12)]
|
||||
#[test_case("Java", "C#", 8)]
|
||||
#[test_case("AbstractFoobarManager", "abstract_foobar_manager", 3+2*2)]
|
||||
#[test_case("CPython", "PyPy", 10)]
|
||||
#[test_case("CPython", "pypy", 11)]
|
||||
#[test_case("AttributeError", "AttributeErrop", 2)]
|
||||
#[test_case("AttributeError", "AttributeErrorTests", 10)]
|
||||
#[test_case("ABA", "AAB", 4)]
|
||||
fn test_levenshtein_distance_calculation_cpython_examples(
|
||||
string_a: &str,
|
||||
string_b: &str,
|
||||
expected_distance: usize,
|
||||
) {
|
||||
assert_eq!(
|
||||
levenshtein_distance(string_a, string_b, 4044),
|
||||
expected_distance
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,3 @@
|
|||
//! Routines to gather all members of a type.
|
||||
//!
|
||||
//! This is used in autocompletion logic from the `ty_ide` crate,
|
||||
//! but it is also used in the `ty_python_semantic` crate to provide
|
||||
//! "Did you mean...?" suggestions in diagnostics.
|
||||
|
||||
use crate::Db;
|
||||
use crate::place::{imported_symbol, place_from_bindings, place_from_declarations};
|
||||
use crate::semantic_index::place::ScopeId;
|
|
@ -77,18 +77,17 @@ use crate::types::call::{
|
|||
use crate::types::class::{MetaclassErrorKind, SliceLiteral};
|
||||
use crate::types::diagnostic::{
|
||||
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
||||
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, HideUnderscoredSuggestions, INCONSISTENT_MRO,
|
||||
INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
|
||||
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE,
|
||||
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
|
||||
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE,
|
||||
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
|
||||
INVALID_GENERIC_CLASS, INVALID_LEGACY_TYPE_VARIABLE, INVALID_PARAMETER_DEFAULT,
|
||||
INVALID_TYPE_ALIAS_TYPE, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT,
|
||||
TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT,
|
||||
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, find_best_suggestion_for_unresolved_member,
|
||||
report_implicit_return_type, report_invalid_argument_number_to_special_form,
|
||||
report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable,
|
||||
report_invalid_assignment, report_invalid_attribute_assignment,
|
||||
report_invalid_generator_function_return_type, report_invalid_return_type,
|
||||
report_possibly_unbound_attribute,
|
||||
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type,
|
||||
report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated,
|
||||
report_invalid_arguments_to_callable, report_invalid_assignment,
|
||||
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
||||
report_invalid_return_type, report_possibly_unbound_attribute,
|
||||
};
|
||||
use crate::types::function::{
|
||||
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
|
||||
|
@ -1855,7 +1854,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
/// is a class scope OR the immediate parent scope is an annotation scope
|
||||
/// and the grandparent scope is a class scope. This means it has different
|
||||
/// behaviour to the [`nearest_enclosing_class`] function.
|
||||
fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
|
||||
fn class_context_of_current_method(&self) -> Option<ClassLiteral<'db>> {
|
||||
let current_scope_id = self.scope().file_scope_id(self.db());
|
||||
let current_scope = self.index.scope(current_scope_id);
|
||||
if current_scope.kind() != ScopeKind::Function {
|
||||
|
@ -1880,7 +1879,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
let class_stmt = class_scope.node().as_class(self.module())?;
|
||||
let class_definition = self.index.expect_single_definition(class_stmt);
|
||||
binding_type(self.db(), class_definition).to_class_type(self.db())
|
||||
binding_type(self.db(), class_definition).into_class_literal()
|
||||
}
|
||||
|
||||
/// Returns `true` if the current scope is the function body scope of a function overload (that
|
||||
|
@ -2040,7 +2039,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
returns.range(),
|
||||
declared_ty,
|
||||
has_empty_body,
|
||||
enclosing_class_context.map(|class| class.class_literal(self.db()).0),
|
||||
enclosing_class_context,
|
||||
no_return,
|
||||
);
|
||||
}
|
||||
|
@ -4416,11 +4415,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
return;
|
||||
}
|
||||
|
||||
// Now we know the import cannot be resolved. Several things remain to do:
|
||||
// - Add `Unknown` as the stored type for the definition.
|
||||
// - Maybe: add a diagnostic.
|
||||
// - If emitting a diagnostic: see if we can add helpful subdiagnostics.
|
||||
|
||||
self.add_unknown_declaration_with_binding(alias.into(), definition);
|
||||
|
||||
if &alias.name == "*" {
|
||||
|
@ -4438,27 +4432,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
return;
|
||||
};
|
||||
|
||||
let mut diagnostic = builder.into_diagnostic(format_args!(
|
||||
let diagnostic = builder.into_diagnostic(format_args!(
|
||||
"Module `{module_name}` has no member `{name}`"
|
||||
));
|
||||
|
||||
if let Some(full_submodule_name) = full_submodule_name {
|
||||
hint_if_stdlib_submodule_exists_on_other_versions(
|
||||
self.db(),
|
||||
&mut diagnostic,
|
||||
diagnostic,
|
||||
&full_submodule_name,
|
||||
&module,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(suggestion) = find_best_suggestion_for_unresolved_member(
|
||||
self.db(),
|
||||
module_ty,
|
||||
name,
|
||||
HideUnderscoredSuggestions::Yes,
|
||||
) {
|
||||
diagnostic.set_primary_message(format_args!("Did you mean `{suggestion}`?",));
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_return_statement(&mut self, ret: &ast::StmtReturn) {
|
||||
|
@ -6415,7 +6400,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let attribute_exists = self
|
||||
.class_context_of_current_method()
|
||||
.and_then(|class| {
|
||||
Type::instance(self.db(), class)
|
||||
Type::instance(self.db(), class.default_specialization(self.db()))
|
||||
.member(self.db(), id)
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
|
@ -6509,41 +6494,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.context
|
||||
.report_lint(&UNRESOLVED_ATTRIBUTE, attribute)
|
||||
{
|
||||
let mut diagnostic = if bound_on_instance {
|
||||
builder.into_diagnostic(
|
||||
format_args!(
|
||||
"Attribute `{}` can only be accessed on instances, \
|
||||
not on the class object `{}` itself.",
|
||||
attr.id,
|
||||
value_type.display(db)
|
||||
),
|
||||
)
|
||||
} else {
|
||||
builder.into_diagnostic(
|
||||
format_args!(
|
||||
"Type `{}` has no attribute `{}`",
|
||||
value_type.display(db),
|
||||
attr.id
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let underscore_policy = if self
|
||||
.class_context_of_current_method()
|
||||
.is_some_and(|class|value_type.is_subtype_of(db, Type::instance(db, class)))
|
||||
{
|
||||
HideUnderscoredSuggestions::No
|
||||
} else {
|
||||
HideUnderscoredSuggestions::Yes
|
||||
};
|
||||
|
||||
if let Some(suggestion) =
|
||||
find_best_suggestion_for_unresolved_member(db, value_type, &attr.id, underscore_policy)
|
||||
{
|
||||
diagnostic.set_primary_message(format_args!(
|
||||
"Did you mean `{suggestion}`?",
|
||||
));
|
||||
}
|
||||
if bound_on_instance {
|
||||
builder.into_diagnostic(
|
||||
format_args!(
|
||||
"Attribute `{}` can only be accessed on instances, \
|
||||
not on the class object `{}` itself.",
|
||||
attr.id,
|
||||
value_type.display(db)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
builder.into_diagnostic(
|
||||
format_args!(
|
||||
"Type `{}` has no attribute `{}`",
|
||||
value_type.display(db),
|
||||
attr.id
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue