diff --git a/_typos.toml b/_typos.toml index f1cd0c0ef3..24406f10bb 100644 --- a/_typos.toml +++ b/_typos.toml @@ -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] diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 740f5acb08..b36ccae657 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `invalid-protocol` @@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got ### 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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `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) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 0b0f7d5018..53643da8ec 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -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 - - - -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 diff --git a/crates/ty_python_semantic/resources/mdtest/import/basic.md b/crates/ty_python_semantic/resources/mdtest/import/basic.md index 01d8045b97..8e7538190e 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/import/basic.md @@ -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. - - - -`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] -``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi…_(bf7b28ef99f0ec16).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi…_(bf7b28ef99f0ec16).snap deleted file mode 100644 index 350467007b..0000000000 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Suggestions_for_obvi…_(bf7b28ef99f0ec16).snap +++ /dev/null @@ -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 `` 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 `` 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 `` 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 - -``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h…_(3caffc60d8390adf).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h…_(3caffc60d8390adf).snap deleted file mode 100644 index 0b237ac4de..0000000000 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_`from`_import_that_h…_(3caffc60d8390adf).snap +++ /dev/null @@ -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 - -``` diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index e174010fe8..9237e75ee2 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -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> { diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 84462c97d5..4a28fe40c8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -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; diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index d34a12898c..fb41f559af 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -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)), diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index cf7cfbc401..4c729caee9 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -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, diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 454dd24adb..f85d77dcff 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -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"); } diff --git a/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs b/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs deleted file mode 100644 index 11fc59674e..0000000000 --- a/crates/ty_python_semantic/src/types/diagnostic/levenshtein.rs +++ /dev/null @@ -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 { - 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( - options: O, - unresolved_member: &str, - hide_underscored_suggestions: HideUnderscoredSuggestions, -) -> Option -where - O: IntoIterator, - I: ExactSizeIterator, -{ - 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 = - 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, unresolved_member: &str) -> Option { - 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 = string_a.chars().collect(); - let string_b_chars: Vec = 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 - #[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 = 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 - #[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 - #[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 - #[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 - #[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 - #[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 - #[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 - ); - } -} diff --git a/crates/ty_python_semantic/src/types/all_members.rs b/crates/ty_python_semantic/src/types/ide_support.rs similarity index 96% rename from crates/ty_python_semantic/src/types/all_members.rs rename to crates/ty_python_semantic/src/types/ide_support.rs index 6bcdb2d847..1e491e478e 100644 --- a/crates/ty_python_semantic/src/types/all_members.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -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; diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index c192432f57..b2f941da31 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -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> { + fn class_context_of_current_method(&self) -> Option> { 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 + ), + ); + } } }