diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index f64e915294..6eac8f7408 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -50,7 +50,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#L90) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L91) ## `conflicting-argument-forms` @@ -81,7 +81,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#L134) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L135) ## `conflicting-declarations` @@ -111,7 +111,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#L160) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L161) ## `conflicting-metaclass` @@ -142,7 +142,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#L185) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L186) ## `cyclic-class-definition` @@ -173,7 +173,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#L211) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L212) ## `duplicate-base` @@ -199,7 +199,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#L255) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L256) ## `escape-character-in-forward-annotation` @@ -336,7 +336,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#L276) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L277) ## `inconsistent-mro` @@ -365,7 +365,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#L362) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L363) ## `index-out-of-bounds` @@ -390,7 +390,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#L386) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L387) ## `invalid-argument-type` @@ -416,7 +416,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#L406) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L407) ## `invalid-assignment` @@ -443,7 +443,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#L446) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447) ## `invalid-attribute-access` @@ -476,7 +476,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#L1394) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1395) ## `invalid-base` @@ -499,7 +499,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#L468) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L469) ## `invalid-context-manager` @@ -525,7 +525,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#L519) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520) ## `invalid-declaration` @@ -553,7 +553,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#L540) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541) ## `invalid-exception-caught` @@ -594,7 +594,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#L563) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L564) ## `invalid-generic-class` @@ -625,7 +625,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#L599) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L600) ## `invalid-legacy-type-variable` @@ -658,7 +658,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#L625) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626) ## `invalid-metaclass` @@ -690,7 +690,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#L674) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675) ## `invalid-overload` @@ -738,7 +738,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#L701) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702) ## `invalid-parameter-default` @@ -763,7 +763,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#L744) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L745) ## `invalid-protocol` @@ -796,7 +796,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#L334) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335) ## `invalid-raise` @@ -844,7 +844,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#L764) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765) ## `invalid-return-type` @@ -868,7 +868,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#L427) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L428) ## `invalid-super-argument` @@ -912,7 +912,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#L807) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L808) ## `invalid-syntax-in-forward-annotation` @@ -952,7 +952,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#L653) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654) ## `invalid-type-checking-constant` @@ -981,7 +981,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#L846) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L847) ## `invalid-type-form` @@ -1010,7 +1010,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#L870) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L871) ## `invalid-type-variable-constraints` @@ -1044,7 +1044,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#L894) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L895) ## `missing-argument` @@ -1068,7 +1068,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#L923) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924) ## `no-matching-overload` @@ -1096,7 +1096,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#L942) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L943) ## `non-subscriptable` @@ -1119,7 +1119,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#L965) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L966) ## `not-iterable` @@ -1144,7 +1144,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#L983) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L984) ## `parameter-already-assigned` @@ -1170,7 +1170,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#L1034) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035) ## `raw-string-type-annotation` @@ -1229,7 +1229,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#L1370) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371) ## `subclass-of-final-class` @@ -1257,7 +1257,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#L1125) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126) ## `too-many-positional-arguments` @@ -1283,7 +1283,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#L1170) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171) ## `type-assertion-failure` @@ -1310,7 +1310,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#L1148) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149) ## `unavailable-implicit-super-arguments` @@ -1354,7 +1354,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#L1191) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192) ## `unknown-argument` @@ -1380,7 +1380,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#L1248) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1249) ## `unresolved-attribute` @@ -1407,7 +1407,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#L1269) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1270) ## `unresolved-import` @@ -1431,7 +1431,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#L1291) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1292) ## `unresolved-reference` @@ -1455,7 +1455,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#L1310) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1311) ## `unsupported-bool-conversion` @@ -1491,7 +1491,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#L1003) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1004) ## `unsupported-operator` @@ -1518,7 +1518,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#L1329) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330) ## `zero-stepsize-in-slice` @@ -1542,7 +1542,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#L1351) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352) ## `invalid-ignore-comment` @@ -1598,7 +1598,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#L1055) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1056) ## `possibly-unbound-implicit-call` @@ -1629,7 +1629,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#L108) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L109) ## `possibly-unbound-import` @@ -1660,7 +1660,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#L1077) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1078) ## `redundant-cast` @@ -1686,7 +1686,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#L1422) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423) ## `undefined-reveal` @@ -1709,7 +1709,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#L1230) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231) ## `unknown-rule` @@ -1777,7 +1777,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#L486) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L487) ## `division-by-zero` @@ -1800,7 +1800,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#L237) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L238) ## `possibly-unresolved-reference` @@ -1827,7 +1827,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#L1103) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1104) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md index 94d6a9ec7f..0801b2d0db 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/stdlib_typing_aliases.md @@ -81,23 +81,22 @@ import typing class ListSubclass(typing.List): ... -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(ListSubclass.__mro__) class DictSubclass(typing.Dict): ... -# TODO: should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(DictSubclass.__mro__) class SetSubclass(typing.Set): ... -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(SetSubclass.__mro__) class FrozenSetSubclass(typing.FrozenSet): ... -# revealed: tuple[, , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , typing.Protocol, typing.Generic, ] reveal_type(FrozenSetSubclass.__mro__) #################### @@ -106,30 +105,26 @@ reveal_type(FrozenSetSubclass.__mro__) class ChainMapSubclass(typing.ChainMap): ... -# TODO: should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(ChainMapSubclass.__mro__) class CounterSubclass(typing.Counter): ... -# TODO: Should have one `Generic[]` element, not three(!) -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], typing.Generic[_T], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(CounterSubclass.__mro__) class DefaultDictSubclass(typing.DefaultDict): ... -# TODO: Should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(DefaultDictSubclass.__mro__) class DequeSubclass(typing.Deque): ... -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(DequeSubclass.__mro__) class OrderedDictSubclass(typing.OrderedDict): ... -# TODO: Should not have multiple `Generic[]` elements -# revealed: tuple[, , , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], typing.Generic[_KT, _VT_co], ] +# revealed: tuple[, , , , , , , , typing.Protocol, typing.Generic, ] reveal_type(OrderedDictSubclass.__mro__) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 66f7574243..d952ed9e86 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -24,9 +24,7 @@ class: ```py class Bad(Generic[T], Generic[T]): ... # error: [duplicate-base] - -# TODO: should emit an error (fails at runtime) -class AlsoBad(Generic[T], Generic[S]): ... +class AlsoBad(Generic[T], Generic[S]): ... # error: [duplicate-base] ``` You cannot use the same typevar more than once. diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 810e716688..47d607fcda 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -527,6 +527,45 @@ reveal_type(unknown_object) # revealed: Unknown reveal_type(unknown_object.__mro__) # revealed: Unknown ``` +## MROs of classes that use multiple inheritance with generic aliases and subscripted `Generic` + +```py +from typing import Generic, TypeVar, Iterator + +T = TypeVar("T") + +class peekable(Generic[T], Iterator[T]): ... + +# revealed: tuple[, , , typing.Protocol, typing.Generic, ] +reveal_type(peekable.__mro__) + +class peekable2(Iterator[T], Generic[T]): ... + +# revealed: tuple[, , , typing.Protocol, typing.Generic, ] +reveal_type(peekable2.__mro__) + +class Base: ... +class Intermediate(Base, Generic[T]): ... +class Sub(Intermediate[T], Base): ... + +# revealed: tuple[, , , typing.Generic, ] +reveal_type(Sub.__mro__) +``` + +## Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases + + + +```py +from typing_extensions import Protocol, TypeVar, Generic + +T = TypeVar("T") + +class Foo(Protocol): ... +class Bar(Protocol[T]): ... +class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro] +``` + ## Classes that inherit from themselves These are invalid, but we need to be able to handle them gracefully without panicking. diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 28b12d8d85..f14b838b6a 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -67,12 +67,10 @@ It's an error to include both bare `Protocol` and subscripted `Protocol[]` in th simultaneously: ```py -# TODO: should emit a `[duplicate-bases]` error here: -class DuplicateBases(Protocol, Protocol[T]): +class DuplicateBases(Protocol, Protocol[T]): # error: [duplicate-base] x: T -# TODO: should not have `Protocol` or `Generic` multiple times -# revealed: tuple[, typing.Protocol, typing.Generic, typing.Protocol[T], typing.Generic[T], ] +# revealed: tuple[, Unknown, ] reveal_type(DuplicateBases.__mro__) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_Unresolvable_MROs_in…_(e2b355c09a967862).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_Unresolvable_MROs_in…_(e2b355c09a967862).snap new file mode 100644 index 0000000000..81c10d0361 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or…_-_Unresolvable_MROs_in…_(e2b355c09a967862).snap @@ -0,0 +1,37 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: mro.md - Method Resolution Order tests - Unresolvable MROs involving generics have the original bases reported in the error message, not the resolved bases +mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | from typing_extensions import Protocol, TypeVar, Generic +2 | +3 | T = TypeVar("T") +4 | +5 | class Foo(Protocol): ... +6 | class Bar(Protocol[T]): ... +7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro] +``` + +# Diagnostics + +``` +error[inconsistent-mro]: Cannot create a consistent method resolution order (MRO) for class `Baz` with bases list `[typing.Protocol[T], , ]` + --> src/mdtest_snippet.py:7:1 + | +5 | class Foo(Protocol): ... +6 | class Bar(Protocol[T]): ... +7 | class Baz(Protocol[T], Foo, Bar[T]): ... # error: [inconsistent-mro] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +info: rule `inconsistent-mro` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/stubs/class.md b/crates/ty_python_semantic/resources/mdtest/stubs/class.md index 64fe0bf48d..58355ec62b 100644 --- a/crates/ty_python_semantic/resources/mdtest/stubs/class.md +++ b/crates/ty_python_semantic/resources/mdtest/stubs/class.md @@ -16,7 +16,7 @@ class Foo[T]: ... class Bar(Foo[Bar]): ... reveal_type(Bar) # revealed: -reveal_type(Bar.__mro__) # revealed: tuple[, , ] +reveal_type(Bar.__mro__) # revealed: tuple[, , typing.Generic, ] ``` ## Access to attributes declared in stubs diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md index fbd4434d02..c286b86e02 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/tuple.md @@ -83,7 +83,7 @@ python-version = "3.9" ```py class A(tuple[int, str]): ... -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(A.__mro__) ``` @@ -114,6 +114,6 @@ from typing import Tuple class C(Tuple): ... -# revealed: tuple[, , , , , , , typing.Protocol[_T_co], typing.Generic[_T_co], ] +# revealed: tuple[, , , , , , , typing.Protocol, typing.Generic, ] reveal_type(C.__mro__) ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 8a91eac857..c21144b719 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -598,6 +598,10 @@ impl<'db> Type<'db> { matches!(self, Type::Dynamic(DynamicType::Todo(_))) } + pub const fn is_generic_alias(&self) -> bool { + matches!(self, Type::GenericAlias(_)) + } + /// Replace references to the class `class` with a self-reference marker. This is currently /// used for recursive protocols, but could probably be extended to self-referential type- /// aliases and similar. diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 4278fe0def..83870384ee 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -223,6 +223,10 @@ impl<'db> ClassType<'db> { } } + pub(super) const fn is_generic(self) -> bool { + matches!(self, Self::Generic(_)) + } + /// Returns the class literal and specialization for this class. For a non-generic class, this /// is the class itself. For a generic alias, this is the alias's origin. pub(crate) fn class_literal( @@ -352,7 +356,7 @@ impl<'db> ClassType<'db> { ClassBase::Dynamic(_) => false, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol(_) | ClassBase::Generic(_) => false, + ClassBase::Protocol | ClassBase::Generic => false, ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, @@ -390,7 +394,7 @@ impl<'db> ClassType<'db> { ClassBase::Dynamic(_) => false, // Protocol and Generic are not represented by a ClassType. - ClassBase::Protocol(_) | ClassBase::Generic(_) => false, + ClassBase::Protocol | ClassBase::Generic => false, ClassBase::Class(base) => match (base, other) { (ClassType::NonGeneric(base), ClassType::NonGeneric(other)) => base == other, @@ -602,11 +606,6 @@ impl<'db> ClassLiteral<'db> { ) } - /// Return `true` if this class represents the builtin class `object` - pub(crate) fn is_object(self, db: &'db dyn Db) -> bool { - self.is_known(db, KnownClass::Object) - } - fn file(self, db: &dyn Db) -> File { self.body_scope(db).file(db) } @@ -1068,7 +1067,7 @@ impl<'db> ClassLiteral<'db> { for superclass in mro_iter { match superclass { - ClassBase::Generic(_) | ClassBase::Protocol(_) => { + ClassBase::Generic | ClassBase::Protocol => { // Skip over these very special class bases that aren't really classes. } ClassBase::Dynamic(_) => { @@ -1427,7 +1426,7 @@ impl<'db> ClassLiteral<'db> { for superclass in self.iter_mro(db, specialization) { match superclass { - ClassBase::Generic(_) | ClassBase::Protocol(_) => { + ClassBase::Generic | ClassBase::Protocol => { // Skip over these very special class bases that aren't really classes. } ClassBase::Dynamic(_) => { diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 9ff7fcad60..b52c525fa7 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -1,5 +1,5 @@ use crate::Db; -use crate::types::generics::{GenericContext, Specialization}; +use crate::types::generics::Specialization; use crate::types::{ ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type, TypeMapping, todo_type, @@ -19,11 +19,11 @@ pub enum ClassBase<'db> { Class(ClassType<'db>), /// Although `Protocol` is not a class in typeshed's stubs, it is at runtime, /// and can appear in the MRO of a class. - Protocol(Option>), + Protocol, /// Bare `Generic` cannot be subclassed directly in user code, /// but nonetheless appears in the MRO of classes that inherit from `Generic[T]`, /// `Protocol[T]`, or bare `Protocol`. - Generic(Option>), + Generic, } impl<'db> ClassBase<'db> { @@ -35,60 +35,18 @@ impl<'db> ClassBase<'db> { match self { Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()), Self::Class(class) => Self::Class(class.normalized(db)), - Self::Protocol(generic_context) => { - Self::Protocol(generic_context.map(|context| context.normalized(db))) - } - Self::Generic(generic_context) => { - Self::Generic(generic_context.map(|context| context.normalized(db))) - } + Self::Protocol | Self::Generic => self, } } - pub(crate) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { - struct Display<'db> { - base: ClassBase<'db>, - db: &'db dyn Db, - } - - impl std::fmt::Display for Display<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.base { - ClassBase::Dynamic(dynamic) => dynamic.fmt(f), - ClassBase::Class(class @ ClassType::NonGeneric(_)) => { - write!(f, "", class.name(self.db)) - } - ClassBase::Class(ClassType::Generic(alias)) => { - write!(f, "", alias.display(self.db)) - } - ClassBase::Protocol(generic_context) => { - f.write_str("typing.Protocol")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - ClassBase::Generic(generic_context) => { - f.write_str("typing.Generic")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - } - } - } - - Display { base: self, db } - } - pub(crate) fn name(self, db: &'db dyn Db) -> &'db str { match self { ClassBase::Class(class) => class.name(db), ClassBase::Dynamic(DynamicType::Any) => "Any", ClassBase::Dynamic(DynamicType::Unknown) => "Unknown", ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoPEP695ParamSpec) => "@Todo", - ClassBase::Protocol(_) => "Protocol", - ClassBase::Generic(_) => "Generic", + ClassBase::Protocol => "Protocol", + ClassBase::Generic => "Generic", } } @@ -255,12 +213,8 @@ impl<'db> ClassBase<'db> { KnownInstanceType::Callable => { Self::try_from_type(db, todo_type!("Support for Callable as a base class")) } - KnownInstanceType::Protocol(generic_context) => { - Some(ClassBase::Protocol(generic_context)) - } - KnownInstanceType::Generic(generic_context) => { - Some(ClassBase::Generic(generic_context)) - } + KnownInstanceType::Protocol(_) => Some(ClassBase::Protocol), + KnownInstanceType::Generic(_) => Some(ClassBase::Generic), }, } } @@ -268,14 +222,14 @@ impl<'db> ClassBase<'db> { pub(super) fn into_class(self) -> Option> { match self { Self::Class(class) => Some(class), - Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => None, + Self::Dynamic(_) | Self::Generic | Self::Protocol => None, } } fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { match self { Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)), - Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => self, + Self::Dynamic(_) | Self::Generic | Self::Protocol => self, } } @@ -299,7 +253,7 @@ impl<'db> ClassBase<'db> { .try_mro(db, specialization) .is_err_and(MroError::is_cycle) } - ClassBase::Dynamic(_) | ClassBase::Generic(_) | ClassBase::Protocol(_) => false, + ClassBase::Dynamic(_) | ClassBase::Generic | ClassBase::Protocol => false, } } @@ -310,12 +264,8 @@ impl<'db> ClassBase<'db> { additional_specialization: Option>, ) -> impl Iterator> { match self { - ClassBase::Protocol(context) => { - ClassBaseMroIterator::length_3(db, self, ClassBase::Generic(context)) - } - ClassBase::Dynamic(_) | ClassBase::Generic(_) => { - ClassBaseMroIterator::length_2(db, self) - } + ClassBase::Protocol => ClassBaseMroIterator::length_3(db, self, ClassBase::Generic), + ClassBase::Dynamic(_) | ClassBase::Generic => ClassBaseMroIterator::length_2(db, self), ClassBase::Class(class) => { ClassBaseMroIterator::from_class(db, class, additional_specialization) } @@ -338,12 +288,8 @@ impl<'db> From> for Type<'db> { match value { ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic), ClassBase::Class(class) => class.into(), - ClassBase::Protocol(generic_context) => { - Type::KnownInstance(KnownInstanceType::Protocol(generic_context)) - } - ClassBase::Generic(generic_context) => { - Type::KnownInstance(KnownInstanceType::Generic(generic_context)) - } + ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol(None)), + ClassBase::Generic => Type::KnownInstance(KnownInstanceType::Generic(None)), } } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index d394650d1d..c996be3d0b 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -13,6 +13,7 @@ use crate::types::string_annotation::{ }; use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral}; use crate::{Program, PythonVersionWithSource, declare_lint}; +use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}; use ruff_db::files::system_path_to_file; use ruff_python_ast::{self as ast, AnyNodeRef}; @@ -1698,10 +1699,7 @@ pub(super) fn report_implicit_return_type( let Some(class) = enclosing_class_of_method else { return; }; - if class - .iter_mro(db, None) - .any(|base| matches!(base, ClassBase::Protocol(_))) - { + if class.iter_mro(db, None).contains(&ClassBase::Protocol) { diagnostic.info( "Only functions in stub files, methods on protocol classes, \ or methods with `@abstractmethod` are permitted to have empty bodies", diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 9ad791217f..649f4992ac 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -7529,7 +7529,7 @@ impl<'db> TypeInferenceBuilder<'db> { if !value_ty.into_class_literal().is_some_and(|class| { class .iter_mro(self.db(), None) - .any(|base| matches!(base, ClassBase::Generic(_))) + .contains(&ClassBase::Generic) }) { report_non_subscriptable( &self.context, diff --git a/crates/ty_python_semantic/src/types/mro.rs b/crates/ty_python_semantic/src/types/mro.rs index 151fbf6966..72f3eceafa 100644 --- a/crates/ty_python_semantic/src/types/mro.rs +++ b/crates/ty_python_semantic/src/types/mro.rs @@ -7,7 +7,7 @@ use rustc_hash::FxBuildHasher; use crate::Db; use crate::types::class_base::ClassBase; use crate::types::generics::Specialization; -use crate::types::{ClassLiteral, ClassType, Type}; +use crate::types::{ClassLiteral, ClassType, KnownInstanceType, Type}; /// The inferred method resolution order of a given class. /// @@ -48,12 +48,12 @@ impl<'db> Mro<'db> { /// [`super::infer::TypeInferenceBuilder::infer_region_scope`].) pub(super) fn of_class( db: &'db dyn Db, - class: ClassLiteral<'db>, + class_literal: ClassLiteral<'db>, specialization: Option>, ) -> Result> { - Self::of_class_impl(db, class, specialization).map_err(|err| { - err.into_mro_error(db, class.apply_optional_specialization(db, specialization)) - }) + let class = class_literal.apply_optional_specialization(db, specialization); + Self::of_class_impl(db, class, class_literal.explicit_bases(db), specialization) + .map_err(|err| err.into_mro_error(db, class)) } pub(super) fn from_error(db: &'db dyn Db, class: ClassType<'db>) -> Self { @@ -66,17 +66,16 @@ impl<'db> Mro<'db> { fn of_class_impl( db: &'db dyn Db, - class: ClassLiteral<'db>, + class: ClassType<'db>, + bases: &[Type<'db>], specialization: Option>, ) -> Result> { - let class_type = class.apply_optional_specialization(db, specialization); - - match class.explicit_bases(db) { + match bases { // `builtins.object` is the special case: // the only class in Python that has an MRO with length <2 [] if class.is_object(db) => Ok(Self::from([ // object is not generic, so the default specialization should be a no-op - ClassBase::Class(class_type), + ClassBase::Class(class), ])), // All other classes in Python have an MRO with length >=2. @@ -92,44 +91,82 @@ impl<'db> Mro<'db> { // >>> Foo.__mro__ // (, ) // ``` - [] => Ok(Self::from([ - ClassBase::Class(class_type), - ClassBase::object(db), - ])), + [] => { + // e.g. `class Foo[T]: ...` implicitly has `Generic` inserted into its bases + if class.is_generic() { + Ok(Self::from([ + ClassBase::Class(class), + ClassBase::Generic, + ClassBase::object(db), + ])) + } else { + Ok(Self::from([ClassBase::Class(class), ClassBase::object(db)])) + } + } // Fast path for a class that has only a single explicit base. // // This *could* theoretically be handled by the final branch below, // but it's a common case (i.e., worth optimizing for), // and the `c3_merge` function requires lots of allocations. - [single_base] => ClassBase::try_from_type(db, *single_base).map_or_else( - || Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))), - |single_base| { - if single_base.has_cyclic_mro(db) { - Err(MroErrorKind::InheritanceCycle) - } else { - Ok(std::iter::once(ClassBase::Class( - class.apply_optional_specialization(db, specialization), - )) - .chain(single_base.mro(db, specialization)) - .collect()) - } - }, - ), + [single_base] + if !matches!( + single_base, + Type::GenericAlias(_) + | Type::KnownInstance( + KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_) + ) + ) => + { + ClassBase::try_from_type(db, *single_base).map_or_else( + || Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))), + |single_base| { + if single_base.has_cyclic_mro(db) { + Err(MroErrorKind::InheritanceCycle) + } else { + Ok(std::iter::once(ClassBase::Class(class)) + .chain(single_base.mro(db, specialization)) + .collect()) + } + }, + ) + } // The class has multiple explicit bases. // // We'll fallback to a full implementation of the C3-merge algorithm to determine // what MRO Python will give this class at runtime // (if an MRO is indeed resolvable at all!) - multiple_bases => { - let mut valid_bases = vec![]; + original_bases => { + let mut resolved_bases = vec![]; let mut invalid_bases = vec![]; - for (i, base) in multiple_bases.iter().enumerate() { - match ClassBase::try_from_type(db, *base) { - Some(valid_base) => valid_bases.push(valid_base), - None => invalid_bases.push((i, *base)), + for (i, base) in original_bases.iter().enumerate() { + // This emulates the behavior of `typing._GenericAlias.__mro_entries__` at + // . + // + // Note that emit a diagnostic for inheriting from bare (unsubscripted) `Generic` elsewhere + // (see `infer::TypeInferenceBuilder::check_class_definitions`), + // which is why we only care about `KnownInstanceType::Generic(Some(_))`, + // not `KnownInstanceType::Generic(None)`. + if let Type::KnownInstance(KnownInstanceType::Generic(Some(_))) = base { + if original_bases + .contains(&Type::KnownInstance(KnownInstanceType::Protocol(None))) + { + continue; + } + if original_bases[i + 1..] + .iter() + .any(|b| b.is_generic_alias() && b != base) + { + continue; + } + resolved_bases.push(ClassBase::Generic); + } else { + match ClassBase::try_from_type(db, *base) { + Some(valid_base) => resolved_bases.push(valid_base), + None => invalid_bases.push((i, *base)), + } } } @@ -137,15 +174,15 @@ impl<'db> Mro<'db> { return Err(MroErrorKind::InvalidBases(invalid_bases.into_boxed_slice())); } - let mut seqs = vec![VecDeque::from([ClassBase::Class(class_type)])]; - for base in &valid_bases { + let mut seqs = vec![VecDeque::from([ClassBase::Class(class)])]; + for base in &resolved_bases { if base.has_cyclic_mro(db) { return Err(MroErrorKind::InheritanceCycle); } seqs.push(base.mro(db, specialization).collect()); } seqs.push( - valid_bases + resolved_bases .iter() .map(|base| base.apply_optional_specialization(db, specialization)) .collect(), @@ -161,8 +198,20 @@ impl<'db> Mro<'db> { let mut base_to_indices: IndexMap, Vec, FxBuildHasher> = IndexMap::default(); - for (index, base) in valid_bases.iter().enumerate() { - base_to_indices.entry(*base).or_default().push(index); + // We need to iterate over `original_bases` here rather than `resolved_bases` + // so that we get the correct index of the duplicate bases if there were any + // (`resolved_bases` may be a longer list than `original_bases`!). However, we + // need to use a `ClassBase` rather than a `Type` as the key type for the + // `base_to_indices` map so that a class such as + // `class Foo(Protocol[T], Protocol): ...` correctly causes us to emit a + // `duplicate-base` diagnostic (matching the runtime behaviour) rather than an + // `inconsistent-mro` diagnostic (which would be accurate -- but not nearly as + // precise!). + for (index, base) in original_bases.iter().enumerate() { + let Some(base) = ClassBase::try_from_type(db, *base) else { + continue; + }; + base_to_indices.entry(base).or_default().push(index); } let mut errors = vec![]; @@ -175,9 +224,7 @@ impl<'db> Mro<'db> { continue; } match base { - ClassBase::Class(_) - | ClassBase::Generic(_) - | ClassBase::Protocol(_) => { + ClassBase::Class(_) | ClassBase::Generic | ClassBase::Protocol => { errors.push(DuplicateBaseError { duplicate_base: base, first_index: *first_index, @@ -193,13 +240,10 @@ impl<'db> Mro<'db> { if duplicate_bases.is_empty() { if duplicate_dynamic_bases { - Ok(Mro::from_error( - db, - class.apply_optional_specialization(db, specialization), - )) + Ok(Mro::from_error(db, class)) } else { Err(MroErrorKind::UnresolvableMro { - bases_list: valid_bases.into_boxed_slice(), + bases_list: original_bases.iter().copied().collect(), }) } } else { @@ -378,7 +422,7 @@ pub(super) enum MroErrorKind<'db> { /// The MRO is otherwise unresolvable through the C3-merge algorithm. /// /// See [`c3_merge`] for more details. - UnresolvableMro { bases_list: Box<[ClassBase<'db>]> }, + UnresolvableMro { bases_list: Box<[Type<'db>]> }, } impl<'db> MroErrorKind<'db> { diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index 465fc38900..a52dda24af 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -152,13 +152,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (ClassBase::Class(_), _) => Ordering::Less, (_, ClassBase::Class(_)) => Ordering::Greater, - (ClassBase::Protocol(left), ClassBase::Protocol(right)) => left.cmp(&right), - (ClassBase::Protocol(_), _) => Ordering::Less, - (_, ClassBase::Protocol(_)) => Ordering::Greater, + (ClassBase::Protocol, _) => Ordering::Less, + (_, ClassBase::Protocol) => Ordering::Greater, - (ClassBase::Generic(left), ClassBase::Generic(right)) => left.cmp(&right), - (ClassBase::Generic(_), _) => Ordering::Less, - (_, ClassBase::Generic(_)) => Ordering::Greater, + (ClassBase::Generic, _) => Ordering::Less, + (_, ClassBase::Generic) => Ordering::Greater, (ClassBase::Dynamic(left), ClassBase::Dynamic(right)) => { dynamic_elements_ordering(left, right)