diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 731d08c645..30fe995231 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#L93) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L92) ## `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#L137) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L136) ## `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#L163) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162) ## `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#L188) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L187) ## `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#L214) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L213) ## `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#L258) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L257) ## `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#L279) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278) ## `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#L365) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L364) ## `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#L389) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L388) ## `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#L409) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L408) ## `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#L449) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448) ## `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#L1397) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396) ## `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#L471) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470) ## `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#L522) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L521) ## `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#L543) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542) ## `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#L566) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L565) ## `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#L602) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L601) ## `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#L628) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L627) ## `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#L677) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L676) ## `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#L704) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703) ## `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#L747) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L746) ## `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#L337) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L336) ## `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#L767) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) ## `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#L430) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429) ## `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#L810) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L809) ## `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#L656) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655) ## `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#L849) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L848) ## `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#L873) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872) ## `invalid-type-variable-constraints` @@ -1046,7 +1046,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#L897) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L896) ## `missing-argument` @@ -1070,7 +1070,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#L926) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L925) ## `no-matching-overload` @@ -1098,7 +1098,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#L945) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L944) ## `non-subscriptable` @@ -1121,7 +1121,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#L968) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L967) ## `not-iterable` @@ -1146,7 +1146,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#L986) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L985) ## `parameter-already-assigned` @@ -1172,7 +1172,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#L1037) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036) ## `raw-string-type-annotation` @@ -1231,7 +1231,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#L1373) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372) ## `subclass-of-final-class` @@ -1259,7 +1259,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#L1128) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1127) ## `too-many-positional-arguments` @@ -1285,7 +1285,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#L1173) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172) ## `type-assertion-failure` @@ -1312,7 +1312,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#L1151) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150) ## `unavailable-implicit-super-arguments` @@ -1356,7 +1356,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#L1194) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1193) ## `unknown-argument` @@ -1382,7 +1382,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#L1251) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1250) ## `unresolved-attribute` @@ -1409,7 +1409,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#L1272) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271) ## `unresolved-import` @@ -1433,7 +1433,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#L1294) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293) ## `unresolved-reference` @@ -1457,7 +1457,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#L1313) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) ## `unsupported-bool-conversion` @@ -1493,7 +1493,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#L1006) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1005) ## `unsupported-operator` @@ -1520,7 +1520,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#L1332) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1331) ## `zero-stepsize-in-slice` @@ -1544,7 +1544,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#L1354) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1353) ## `invalid-ignore-comment` @@ -1600,7 +1600,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#L1058) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1057) ## `possibly-unbound-implicit-call` @@ -1631,7 +1631,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#L111) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L110) ## `possibly-unbound-import` @@ -1662,7 +1662,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#L1080) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1079) ## `redundant-cast` @@ -1688,7 +1688,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#L1425) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1424) ## `undefined-reveal` @@ -1711,7 +1711,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#L1233) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1232) ## `unknown-rule` @@ -1779,7 +1779,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#L489) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L488) ## `division-by-zero` @@ -1802,7 +1802,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#L240) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239) ## `possibly-unresolved-reference` @@ -1829,7 +1829,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#L1106) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1105) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/src/symbol.rs b/crates/ty_python_semantic/src/symbol.rs index 8e20a08954..d2b2518e61 100644 --- a/crates/ty_python_semantic/src/symbol.rs +++ b/crates/ty_python_semantic/src/symbol.rs @@ -1131,7 +1131,7 @@ fn widen_type_for_undeclared_public_symbol<'db>( // such. let is_known_instance = inferred .ignore_possibly_unbound() - .is_some_and(|ty| matches!(ty, Type::KnownInstance(_))); + .is_some_and(|ty| matches!(ty, Type::SpecialForm(_) | Type::KnownInstance(_))); if is_considered_non_modifiable || is_known_instance { inferred diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 49bc687557..dfded95182 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -54,8 +54,8 @@ pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic use crate::{Db, FxOrderSet, Module, Program}; pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass}; use instance::Protocol; -pub(crate) use instance::{NominalInstanceType, ProtocolInstanceType}; -pub(crate) use known_instance::KnownInstanceType; +pub use instance::{NominalInstanceType, ProtocolInstanceType}; +pub use special_form::SpecialFormType; mod builder; mod call; @@ -67,12 +67,12 @@ mod display; mod generics; mod infer; mod instance; -mod known_instance; mod mro; mod narrow; mod protocol_class; mod signatures; mod slots; +mod special_form; mod string_annotation; mod subclass_of; mod type_ordering; @@ -511,7 +511,12 @@ pub enum Type<'db> { /// The set of Python objects that conform to the interface described by a given protocol. /// Construct this variant using the `Type::instance` constructor function. ProtocolInstance(ProtocolInstanceType<'db>), - /// A single Python object that requires special treatment in the type system + /// A single Python object that requires special treatment in the type system, + /// and which exists at a location that can be known prior to any analysis by ty. + SpecialForm(SpecialFormType), + /// Singleton types that are heavily special-cased by ty, and which are usually + /// created as a result of some runtime operation (e.g. a type-alias statement, + /// a typevar definition, or `Generic[T]` in a class's bases list). KnownInstance(KnownInstanceType<'db>), /// An instance of `builtins.property` PropertyInstance(PropertyInstanceType<'db>), @@ -664,6 +669,7 @@ impl<'db> Type<'db> { | Self::ModuleLiteral(_) | Self::ClassLiteral(_) | Self::NominalInstance(_) + | Self::SpecialForm(_) | Self::KnownInstance(_) | Self::PropertyInstance(_) | Self::BoundMethod(_) @@ -691,6 +697,7 @@ impl<'db> Type<'db> { | Self::ModuleLiteral(_) | Self::FunctionLiteral(_) | Self::ClassLiteral(_) + | Self::SpecialForm(_) | Self::KnownInstance(_) | Self::StringLiteral(_) | Self::IntLiteral(_) @@ -936,19 +943,6 @@ impl<'db> Type<'db> { .expect("Expected a Type::IntLiteral variant") } - pub const fn into_known_instance(self) -> Option> { - match self { - Type::KnownInstance(known_instance) => Some(known_instance), - _ => None, - } - } - - #[track_caller] - pub fn expect_known_instance(self) -> KnownInstanceType<'db> { - self.into_known_instance() - .expect("Expected a Type::KnownInstance variant") - } - pub const fn into_tuple(self) -> Option> { match self { Type::Tuple(tuple_type) => Some(tuple_type), @@ -1042,6 +1036,7 @@ impl<'db> Type<'db> { | Type::DataclassTransformer(_) | Type::ModuleLiteral(_) | Type::ClassLiteral(_) + | Type::SpecialForm(_) | Type::IntLiteral(_) => self, } } @@ -1385,9 +1380,11 @@ impl<'db> Type<'db> { metaclass_instance_type.is_subtype_of(db, target) }), - // For example: `Type::KnownInstance(KnownInstanceType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, - // because `Type::KnownInstance(KnownInstanceType::Type)` is a set with exactly one runtime value in it + // For example: `Type::SpecialForm(SpecialFormType::Type)` is a subtype of `Type::NominalInstance(_SpecialForm)`, + // because `Type::SpecialForm(SpecialFormType::Type)` is a set with exactly one runtime value in it // (the symbol `typing.Type`), and that symbol is known to be an instance of `typing._SpecialForm` at runtime. + (Type::SpecialForm(left), right) => left.instance_fallback(db).is_subtype_of(db, right), + (Type::KnownInstance(left), right) => { left.instance_fallback(db).is_subtype_of(db, right) } @@ -1884,6 +1881,7 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(..) | Type::ClassLiteral(..) | Type::GenericAlias(..) + | Type::SpecialForm(..) | Type::KnownInstance(..)), right @ (Type::BooleanLiteral(..) | Type::IntLiteral(..) @@ -1896,6 +1894,7 @@ impl<'db> Type<'db> { | Type::ModuleLiteral(..) | Type::ClassLiteral(..) | Type::GenericAlias(..) + | Type::SpecialForm(..) | Type::KnownInstance(..)), ) => left != right, @@ -2028,6 +2027,11 @@ impl<'db> Type<'db> { | Type::IntLiteral(..)), ) => !ty.satisfies_protocol(db, protocol), + (Type::ProtocolInstance(protocol), Type::SpecialForm(special_form)) + | (Type::SpecialForm(special_form), Type::ProtocolInstance(protocol)) => !special_form + .instance_fallback(db) + .satisfies_protocol(db, protocol), + (Type::ProtocolInstance(protocol), Type::KnownInstance(known_instance)) | (Type::KnownInstance(known_instance), Type::ProtocolInstance(protocol)) => { !known_instance @@ -2063,15 +2067,24 @@ impl<'db> Type<'db> { .is_disjoint_from(db, other), }, + (Type::SpecialForm(special_form), Type::NominalInstance(instance)) + | (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { + !special_form.is_instance_of(db, instance.class) + } + (Type::KnownInstance(known_instance), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { !known_instance.is_instance_of(db, instance.class) } - (known_instance_ty @ Type::KnownInstance(_), Type::Tuple(tuple)) - | (Type::Tuple(tuple), known_instance_ty @ Type::KnownInstance(_)) => { - known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)) - } + ( + known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)), + Type::Tuple(tuple), + ) + | ( + Type::Tuple(tuple), + known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)), + ) => known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)), (Type::BooleanLiteral(..), Type::NominalInstance(instance)) | (Type::NominalInstance(instance), Type::BooleanLiteral(..)) => { @@ -2231,6 +2244,7 @@ impl<'db> Type<'db> { | Type::StringLiteral(_) | Type::LiteralString | Type::BytesLiteral(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::AlwaysFalsy | Type::AlwaysTruthy @@ -2345,15 +2359,16 @@ impl<'db> Type<'db> { | Type::ClassLiteral(..) | Type::GenericAlias(..) | Type::ModuleLiteral(..) => true, - Type::KnownInstance(known_instance) => { - // Nearly all `KnownInstance` types are singletons, but if a symbol could validly + Type::SpecialForm(special_form) => { + // Nearly all `SpecialForm` types are singletons, but if a symbol could validly // originate from either `typing` or `typing_extensions` then this is not guaranteed. - // E.g. `typing.Protocol` is equivalent to `typing_extensions.Protocol`, so both are treated - // as inhabiting the type `KnownInstanceType::Protocol` in our model, but they are actually + // E.g. `typing.TypeGuard` is equivalent to `typing_extensions.TypeGuard`, so both are treated + // as inhabiting the type `SpecialFormType::TypeGuard` in our model, but they are actually // distinct symbols at different memory addresses at runtime. - !(known_instance.check_module(KnownModule::Typing) - && known_instance.check_module(KnownModule::TypingExtensions)) + !(special_form.check_module(KnownModule::Typing) + && special_form.check_module(KnownModule::TypingExtensions)) } + Type::KnownInstance(_) => false, Type::Callable(_) => { // A callable type is never a singleton because for any given signature, // there could be any number of distinct objects that are all callable with that @@ -2421,6 +2436,7 @@ impl<'db> Type<'db> { | Type::BooleanLiteral(..) | Type::StringLiteral(..) | Type::BytesLiteral(..) + | Type::SpecialForm(..) | Type::KnownInstance(..) => true, Type::ProtocolInstance(..) => { @@ -2575,6 +2591,7 @@ impl<'db> Type<'db> { | Type::DataclassDecorator(_) | Type::DataclassTransformer(_) | Type::ModuleLiteral(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::AlwaysTruthy | Type::AlwaysFalsy @@ -2699,7 +2716,7 @@ impl<'db> Type<'db> { .to_instance(db) .instance_member(db, name), - Type::KnownInstance(_) => Symbol::Unbound.into(), + Type::SpecialForm(_) | Type::KnownInstance(_) => Symbol::Unbound.into(), Type::PropertyInstance(_) => KnownClass::Property .to_instance(db) @@ -3174,6 +3191,7 @@ impl<'db> Type<'db> { | Type::LiteralString | Type::Tuple(..) | Type::TypeVar(..) + | Type::SpecialForm(..) | Type::KnownInstance(..) | Type::PropertyInstance(..) | Type::FunctionLiteral(..) => { @@ -3455,6 +3473,7 @@ impl<'db> Type<'db> { | Type::PropertyInstance(_) | Type::BoundSuper(_) | Type::KnownInstance(_) + | Type::SpecialForm(_) | Type::AlwaysTruthy => Truthiness::AlwaysTrue, Type::AlwaysFalsy => Truthiness::AlwaysFalse, @@ -4276,7 +4295,7 @@ impl<'db> Type<'db> { .into(), }, - Type::KnownInstance(KnownInstanceType::TypedDict) => { + Type::SpecialForm(SpecialFormType::TypedDict) => { Binding::single( self, Signature::new( @@ -4369,10 +4388,11 @@ impl<'db> Type<'db> { CallableBinding::not_callable(self).into() } - // TODO: some `KnownInstance`s are callable (e.g. TypedDicts) - Type::KnownInstance(_) => CallableBinding::not_callable(self).into(), + // TODO: some `SpecialForm`s are callable (e.g. TypedDicts) + Type::SpecialForm(_) => CallableBinding::not_callable(self).into(), Type::PropertyInstance(_) + | Type::KnownInstance(_) | Type::AlwaysFalsy | Type::AlwaysTruthy | Type::IntLiteral(_) @@ -4861,6 +4881,7 @@ impl<'db> Type<'db> { | Type::DataclassTransformer(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::ModuleLiteral(_) @@ -4942,33 +4963,43 @@ impl<'db> Type<'db> { Type::KnownInstance(known_instance) => match known_instance { KnownInstanceType::TypeAliasType(alias) => Ok(alias.value_type(db)), - KnownInstanceType::Never | KnownInstanceType::NoReturn => Ok(Type::Never), - KnownInstanceType::LiteralString => Ok(Type::LiteralString), - KnownInstanceType::Unknown => Ok(Type::unknown()), - KnownInstanceType::AlwaysTruthy => Ok(Type::AlwaysTruthy), - KnownInstanceType::AlwaysFalsy => Ok(Type::AlwaysFalsy), + KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)), + KnownInstanceType::SubscriptedProtocol(_) => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol], + fallback_type: Type::unknown(), + }), + KnownInstanceType::SubscriptedGeneric(_) => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic], + fallback_type: Type::unknown(), + }), + }, + + Type::SpecialForm(special_form) => match special_form { + SpecialFormType::Never | SpecialFormType::NoReturn => Ok(Type::Never), + SpecialFormType::LiteralString => Ok(Type::LiteralString), + SpecialFormType::Unknown => Ok(Type::unknown()), + SpecialFormType::AlwaysTruthy => Ok(Type::AlwaysTruthy), + SpecialFormType::AlwaysFalsy => Ok(Type::AlwaysFalsy), // We treat `typing.Type` exactly the same as `builtins.type`: - KnownInstanceType::Type => Ok(KnownClass::Type.to_instance(db)), - KnownInstanceType::Tuple => Ok(KnownClass::Tuple.to_instance(db)), + SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)), + SpecialFormType::Tuple => Ok(KnownClass::Tuple.to_instance(db)), // Legacy `typing` aliases - KnownInstanceType::List => Ok(KnownClass::List.to_instance(db)), - KnownInstanceType::Dict => Ok(KnownClass::Dict.to_instance(db)), - KnownInstanceType::Set => Ok(KnownClass::Set.to_instance(db)), - KnownInstanceType::FrozenSet => Ok(KnownClass::FrozenSet.to_instance(db)), - KnownInstanceType::ChainMap => Ok(KnownClass::ChainMap.to_instance(db)), - KnownInstanceType::Counter => Ok(KnownClass::Counter.to_instance(db)), - KnownInstanceType::DefaultDict => Ok(KnownClass::DefaultDict.to_instance(db)), - KnownInstanceType::Deque => Ok(KnownClass::Deque.to_instance(db)), - KnownInstanceType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)), - - KnownInstanceType::TypeVar(typevar) => Ok(Type::TypeVar(*typevar)), + SpecialFormType::List => Ok(KnownClass::List.to_instance(db)), + SpecialFormType::Dict => Ok(KnownClass::Dict.to_instance(db)), + SpecialFormType::Set => Ok(KnownClass::Set.to_instance(db)), + SpecialFormType::FrozenSet => Ok(KnownClass::FrozenSet.to_instance(db)), + SpecialFormType::ChainMap => Ok(KnownClass::ChainMap.to_instance(db)), + SpecialFormType::Counter => Ok(KnownClass::Counter.to_instance(db)), + SpecialFormType::DefaultDict => Ok(KnownClass::DefaultDict.to_instance(db)), + SpecialFormType::Deque => Ok(KnownClass::Deque.to_instance(db)), + SpecialFormType::OrderedDict => Ok(KnownClass::OrderedDict.to_instance(db)), // TODO: Use an opt-in rule for a bare `Callable` - KnownInstanceType::Callable => Ok(CallableType::unknown(db)), + SpecialFormType::Callable => Ok(CallableType::unknown(db)), - KnownInstanceType::TypingSelf => { + SpecialFormType::TypingSelf => { let index = semantic_index(db, scope_id.file(db)); let Some(class) = nearest_enclosing_class(db, index, scope_id) else { return Err(InvalidTypeExpressionError { @@ -4991,41 +5022,41 @@ impl<'db> Type<'db> { TypeVarKind::Legacy, ))) } - KnownInstanceType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")), - KnownInstanceType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")), + SpecialFormType::TypeAlias => Ok(todo_type!("Support for `typing.TypeAlias`")), + SpecialFormType::TypedDict => Ok(todo_type!("Support for `typing.TypedDict`")), - KnownInstanceType::Protocol(_) => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol], - fallback_type: Type::unknown(), - }), - KnownInstanceType::Generic(_) => Err(InvalidTypeExpressionError { - invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic], - fallback_type: Type::unknown(), - }), - - KnownInstanceType::Literal - | KnownInstanceType::Union - | KnownInstanceType::Intersection => Err(InvalidTypeExpressionError { + SpecialFormType::Literal + | SpecialFormType::Union + | SpecialFormType::Intersection => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ InvalidTypeExpression::RequiresArguments(*self) ], fallback_type: Type::unknown(), }), - KnownInstanceType::Optional - | KnownInstanceType::Not - | KnownInstanceType::TypeOf - | KnownInstanceType::TypeIs - | KnownInstanceType::TypeGuard - | KnownInstanceType::Unpack - | KnownInstanceType::CallableTypeOf => Err(InvalidTypeExpressionError { + SpecialFormType::Protocol => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Protocol], + fallback_type: Type::unknown(), + }), + SpecialFormType::Generic => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::Generic], + fallback_type: Type::unknown(), + }), + + SpecialFormType::Optional + | SpecialFormType::Not + | SpecialFormType::TypeOf + | SpecialFormType::TypeIs + | SpecialFormType::TypeGuard + | SpecialFormType::Unpack + | SpecialFormType::CallableTypeOf => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ InvalidTypeExpression::RequiresOneArgument(*self) ], fallback_type: Type::unknown(), }), - KnownInstanceType::Annotated | KnownInstanceType::Concatenate => { + SpecialFormType::Annotated | SpecialFormType::Concatenate => { Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ InvalidTypeExpression::RequiresTwoArguments(*self) @@ -5034,20 +5065,20 @@ impl<'db> Type<'db> { }) } - KnownInstanceType::ClassVar | KnownInstanceType::Final => { + SpecialFormType::ClassVar | SpecialFormType::Final => { Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ - InvalidTypeExpression::TypeQualifier(*known_instance) + InvalidTypeExpression::TypeQualifier(*special_form) ], fallback_type: Type::unknown(), }) } - KnownInstanceType::ReadOnly - | KnownInstanceType::NotRequired - | KnownInstanceType::Required => Err(InvalidTypeExpressionError { + SpecialFormType::ReadOnly + | SpecialFormType::NotRequired + | SpecialFormType::Required => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![ - InvalidTypeExpression::TypeQualifierRequiresOneArgument(*known_instance) + InvalidTypeExpression::TypeQualifierRequiresOneArgument(*special_form) ], fallback_type: Type::unknown(), }), @@ -5158,6 +5189,7 @@ impl<'db> Type<'db> { Type::Never => Type::Never, Type::NominalInstance(instance) => instance.to_meta_type(db), Type::KnownInstance(known_instance) => known_instance.to_meta_type(db), + Type::SpecialForm(special_form) => special_form.to_meta_type(db), Type::PropertyInstance(_) => KnownClass::Property.to_class_literal(db), Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)), Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db), @@ -5359,6 +5391,7 @@ impl<'db> Type<'db> { // some other generic context's specialization is applied to it. | Type::ClassLiteral(_) | Type::BoundSuper(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) => self, } } @@ -5458,6 +5491,7 @@ impl<'db> Type<'db> { | Type::StringLiteral(_) | Type::BytesLiteral(_) | Type::BoundSuper(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) => {} } } @@ -5472,6 +5506,7 @@ impl<'db> Type<'db> { match self { Type::IntLiteral(_) | Type::BooleanLiteral(_) => self.repr(db), Type::StringLiteral(_) | Type::LiteralString => *self, + Type::SpecialForm(special_form) => Type::string_literal(db, special_form.repr()), Type::KnownInstance(known_instance) => Type::StringLiteral(StringLiteralType::new( db, known_instance.repr(db).to_string().into_boxed_str(), @@ -5493,6 +5528,7 @@ impl<'db> Type<'db> { Type::string_literal(db, &format!("'{}'", literal.value(db).escape_default())) } Type::LiteralString => Type::LiteralString, + Type::SpecialForm(special_form) => Type::string_literal(db, special_form.repr()), Type::KnownInstance(known_instance) => Type::StringLiteral(StringLiteralType::new( db, known_instance.repr(db).to_string().into_boxed_str(), @@ -5568,6 +5604,7 @@ impl<'db> Type<'db> { | Self::Never | Self::Callable(_) | Self::AlwaysTruthy + | Self::SpecialForm(_) | Self::AlwaysFalsy => None, } } @@ -5684,6 +5721,112 @@ impl<'db> TypeMapping<'_, 'db> { } } +/// Singleton types that are heavily special-cased by ty. Despite its name, +/// quite a different type to [`NominalInstanceType`]. +/// +/// In many ways, this enum behaves similarly to [`SpecialFormType`]. +/// Unlike instances of that variant, however, `Type::KnownInstance`s do not exist +/// at a location that can be known prior to any analysis by ty, and each variant +/// of `KnownInstanceType` can have multiple instances (as, unlike `SpecialFormType`, +/// `KnownInstanceType` variants can hold associated data). Instances of this type +/// are generally created by operations at runtime in some way, such as a type alias +/// statement, a typevar definition, or an instance of `Generic[T]` in a class's +/// bases list. +/// +/// # Ordering +/// +/// Ordering between variants is stable and should be the same between runs. +/// Ordering within variants is based on the wrapped data's salsa-assigned id and not on its values. +/// The id may change between runs, or when e.g. a `TypeVarInstance` was garbage-collected and recreated. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, salsa::Update, Ord, PartialOrd)] +pub enum KnownInstanceType<'db> { + /// The type of `Protocol[T]`, `Protocol[U, S]`, etc -- usually only found in a class's bases list. + /// + /// Note that unsubscripted `Protocol` is represented by [`SpecialFormType::Protocol`], not this type. + SubscriptedProtocol(GenericContext<'db>), + + /// The type of `Generic[T]`, `Generic[U, S]`, etc -- usually only found in a class's bases list. + /// + /// Note that unsubscripted `Generic` is represented by [`SpecialFormType::Generic`], not this type. + SubscriptedGeneric(GenericContext<'db>), + + /// A single instance of `typing.TypeVar` + TypeVar(TypeVarInstance<'db>), + + /// A single instance of `typing.TypeAliasType` (PEP 695 type alias) + TypeAliasType(TypeAliasType<'db>), +} + +impl<'db> KnownInstanceType<'db> { + fn normalized(self, db: &'db dyn Db) -> Self { + match self { + Self::SubscriptedProtocol(context) => Self::SubscriptedProtocol(context.normalized(db)), + Self::SubscriptedGeneric(context) => Self::SubscriptedGeneric(context.normalized(db)), + Self::TypeVar(typevar) => Self::TypeVar(typevar.normalized(db)), + Self::TypeAliasType(type_alias) => Self::TypeAliasType(type_alias.normalized(db)), + } + } + + const fn class(self) -> KnownClass { + match self { + Self::SubscriptedProtocol(_) | Self::SubscriptedGeneric(_) => KnownClass::SpecialForm, + Self::TypeVar(_) => KnownClass::TypeVar, + Self::TypeAliasType(_) => KnownClass::TypeAliasType, + } + } + + fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> { + self.class().to_class_literal(db) + } + + /// Return the instance type which this type is a subtype of. + /// + /// For example, an alias created using the `type` statement is an instance of + /// `typing.TypeAliasType`, so `KnownInstanceType::TypeAliasType(_).instance_fallback(db)` + /// returns `Type::NominalInstance(NominalInstanceType { class: })`. + fn instance_fallback(self, db: &dyn Db) -> Type { + self.class().to_instance(db) + } + + /// Return `true` if this symbol is an instance of `class`. + fn is_instance_of(self, db: &dyn Db, class: ClassType) -> bool { + self.class().is_subclass_of(db, class) + } + + /// Return the repr of the symbol at runtime + fn repr(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { + struct KnownInstanceRepr<'db> { + known_instance: KnownInstanceType<'db>, + db: &'db dyn Db, + } + + impl std::fmt::Display for KnownInstanceRepr<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.known_instance { + KnownInstanceType::SubscriptedProtocol(generic_context) => { + f.write_str("typing.Protocol")?; + generic_context.display(self.db).fmt(f) + } + KnownInstanceType::SubscriptedGeneric(generic_context) => { + f.write_str("typing.Generic")?; + generic_context.display(self.db).fmt(f) + } + KnownInstanceType::TypeAliasType(_) => f.write_str("typing.TypeAliasType"), + // This is a legacy `TypeVar` _outside_ of any generic class or function, so we render + // it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll + // have a `Type::TypeVar(_)`, which is rendered as the typevar's name. + KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"), + } + } + } + + KnownInstanceRepr { + known_instance: self, + db, + } + } +} + #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum DynamicType { // An explicitly annotated `typing.Any` @@ -5841,10 +5984,10 @@ enum InvalidTypeExpression<'db> { Generic, /// Type qualifiers are always invalid in *type expressions*, /// but these ones are okay with 0 arguments in *annotation expressions* - TypeQualifier(KnownInstanceType<'db>), + TypeQualifier(SpecialFormType), /// Type qualifiers that are invalid in type expressions, /// and which would require exactly one argument even if they appeared in an annotation expression - TypeQualifierRequiresOneArgument(KnownInstanceType<'db>), + TypeQualifierRequiresOneArgument(SpecialFormType), /// Some types are always invalid in type expressions InvalidType(Type<'db>, ScopeId<'db>), } @@ -5882,13 +6025,13 @@ impl<'db> InvalidTypeExpression<'db> { } InvalidTypeExpression::TypeQualifier(qualifier) => write!( f, - "Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions)", - q = qualifier.repr(self.db) + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions)", ), InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier) => write!( f, - "Type qualifier `{q}` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)", - q = qualifier.repr(self.db) + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions, and only with exactly one argument)", ), InvalidTypeExpression::InvalidType(ty, _) => write!( f, @@ -8827,8 +8970,8 @@ impl<'db> SuperOwnerKind<'db> { Type::BytesLiteral(_) => { SuperOwnerKind::try_from_type(db, KnownClass::Bytes.to_instance(db)) } - Type::KnownInstance(known_instance) => { - SuperOwnerKind::try_from_type(db, known_instance.instance_fallback(db)) + Type::SpecialForm(special_form) => { + SuperOwnerKind::try_from_type(db, special_form.instance_fallback(db)) } _ => None, } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 07888335a1..fa52bcd960 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -22,7 +22,7 @@ use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType, KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, - TupleType, TypeMapping, UnionType, WrapperDescriptorKind, todo_type, + SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -933,7 +933,7 @@ impl<'db> Bindings<'db> { _ => {} }, - Type::KnownInstance(KnownInstanceType::TypedDict) => { + Type::SpecialForm(SpecialFormType::TypedDict) => { overload.set_return_type(todo_type!("TypedDict")); } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 48e6705522..50b9e8b682 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3,8 +3,8 @@ use std::sync::{LazyLock, Mutex}; use super::{ IntersectionBuilder, KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator, - SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type, - infer_unpack_types, + SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, + infer_expression_type, infer_unpack_types, }; use crate::semantic_index::DeclarationWithConstraint; use crate::semantic_index::definition::Definition; @@ -729,9 +729,9 @@ impl<'db> ClassLiteral<'db> { pub(crate) fn legacy_generic_context(self, db: &'db dyn Db) -> Option> { self.explicit_bases(db).iter().find_map(|base| match base { Type::KnownInstance( - KnownInstanceType::Generic(generic_context) - | KnownInstanceType::Protocol(generic_context), - ) => *generic_context, + KnownInstanceType::SubscriptedGeneric(generic_context) + | KnownInstanceType::SubscriptedProtocol(generic_context), + ) => Some(*generic_context), _ => None, }) } @@ -879,11 +879,13 @@ impl<'db> ClassLiteral<'db> { // - OR be the last-but-one base (with the final base being `Generic[]` or `object`) // - OR be the last-but-two base (with the penultimate base being `Generic[]` // and the final base being `object`) - self.explicit_bases(db) - .iter() - .rev() - .take(3) - .any(|base| matches!(base, Type::KnownInstance(KnownInstanceType::Protocol(_)))) + self.explicit_bases(db).iter().rev().take(3).any(|base| { + matches!( + base, + Type::SpecialForm(SpecialFormType::Protocol) + | Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(_)) + ) + }) }) } diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index b52c525fa7..cf13ee5b38 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -1,8 +1,8 @@ use crate::Db; use crate::types::generics::Specialization; use crate::types::{ - ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type, - TypeMapping, todo_type, + ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, SpecialFormType, + Type, TypeMapping, todo_type, }; /// Enumeration of the possible kinds of types we allow in class bases. @@ -147,74 +147,82 @@ impl<'db> ClassBase<'db> { | Type::ProtocolInstance(_) | Type::AlwaysFalsy | Type::AlwaysTruthy => None, + Type::KnownInstance(known_instance) => match known_instance { - KnownInstanceType::TypeVar(_) - | KnownInstanceType::TypeAliasType(_) - | KnownInstanceType::Annotated - | KnownInstanceType::Literal - | KnownInstanceType::LiteralString - | KnownInstanceType::Union - | KnownInstanceType::NoReturn - | KnownInstanceType::Never - | KnownInstanceType::Final - | KnownInstanceType::NotRequired - | KnownInstanceType::TypeGuard - | KnownInstanceType::TypeIs - | KnownInstanceType::TypingSelf - | KnownInstanceType::Unpack - | KnownInstanceType::ClassVar - | KnownInstanceType::Concatenate - | KnownInstanceType::Required - | KnownInstanceType::TypeAlias - | KnownInstanceType::ReadOnly - | KnownInstanceType::Optional - | KnownInstanceType::Not - | KnownInstanceType::Intersection - | KnownInstanceType::TypeOf - | KnownInstanceType::CallableTypeOf - | KnownInstanceType::AlwaysTruthy - | KnownInstanceType::AlwaysFalsy => None, - KnownInstanceType::Unknown => Some(Self::unknown()), + KnownInstanceType::SubscriptedGeneric(_) => Some(Self::Generic), + KnownInstanceType::SubscriptedProtocol(_) => Some(Self::Protocol), + KnownInstanceType::TypeAliasType(_) | KnownInstanceType::TypeVar(_) => None, + }, + + Type::SpecialForm(special_form) => match special_form { + SpecialFormType::Annotated + | SpecialFormType::Literal + | SpecialFormType::LiteralString + | SpecialFormType::Union + | SpecialFormType::NoReturn + | SpecialFormType::Never + | SpecialFormType::Final + | SpecialFormType::NotRequired + | SpecialFormType::TypeGuard + | SpecialFormType::TypeIs + | SpecialFormType::TypingSelf + | SpecialFormType::Unpack + | SpecialFormType::ClassVar + | SpecialFormType::Concatenate + | SpecialFormType::Required + | SpecialFormType::TypeAlias + | SpecialFormType::ReadOnly + | SpecialFormType::Optional + | SpecialFormType::Not + | SpecialFormType::Intersection + | SpecialFormType::TypeOf + | SpecialFormType::CallableTypeOf + | SpecialFormType::AlwaysTruthy + | SpecialFormType::AlwaysFalsy => None, + + SpecialFormType::Unknown => Some(Self::unknown()), + + SpecialFormType::Protocol => Some(Self::Protocol), + SpecialFormType::Generic => Some(Self::Generic), + // TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO - KnownInstanceType::Dict => { + SpecialFormType::Dict => { Self::try_from_type(db, KnownClass::Dict.to_class_literal(db)) } - KnownInstanceType::List => { + SpecialFormType::List => { Self::try_from_type(db, KnownClass::List.to_class_literal(db)) } - KnownInstanceType::Type => { + SpecialFormType::Type => { Self::try_from_type(db, KnownClass::Type.to_class_literal(db)) } - KnownInstanceType::Tuple => { + SpecialFormType::Tuple => { Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db)) } - KnownInstanceType::Set => { + SpecialFormType::Set => { Self::try_from_type(db, KnownClass::Set.to_class_literal(db)) } - KnownInstanceType::FrozenSet => { + SpecialFormType::FrozenSet => { Self::try_from_type(db, KnownClass::FrozenSet.to_class_literal(db)) } - KnownInstanceType::ChainMap => { + SpecialFormType::ChainMap => { Self::try_from_type(db, KnownClass::ChainMap.to_class_literal(db)) } - KnownInstanceType::Counter => { + SpecialFormType::Counter => { Self::try_from_type(db, KnownClass::Counter.to_class_literal(db)) } - KnownInstanceType::DefaultDict => { + SpecialFormType::DefaultDict => { Self::try_from_type(db, KnownClass::DefaultDict.to_class_literal(db)) } - KnownInstanceType::Deque => { + SpecialFormType::Deque => { Self::try_from_type(db, KnownClass::Deque.to_class_literal(db)) } - KnownInstanceType::OrderedDict => { + SpecialFormType::OrderedDict => { Self::try_from_type(db, KnownClass::OrderedDict.to_class_literal(db)) } - KnownInstanceType::TypedDict => Self::try_from_type(db, todo_type!("TypedDict")), - KnownInstanceType::Callable => { + SpecialFormType::TypedDict => Self::try_from_type(db, todo_type!("TypedDict")), + SpecialFormType::Callable => { Self::try_from_type(db, todo_type!("Support for Callable as a base class")) } - KnownInstanceType::Protocol(_) => Some(ClassBase::Protocol), - KnownInstanceType::Generic(_) => Some(ClassBase::Generic), }, } } @@ -288,8 +296,8 @@ impl<'db> From> for Type<'db> { match value { ClassBase::Dynamic(dynamic) => Type::Dynamic(dynamic), ClassBase::Class(class) => class.into(), - ClassBase::Protocol => Type::KnownInstance(KnownInstanceType::Protocol(None)), - ClassBase::Generic => Type::KnownInstance(KnownInstanceType::Generic(None)), + ClassBase::Protocol => Type::SpecialForm(SpecialFormType::Protocol), + ClassBase::Generic => Type::SpecialForm(SpecialFormType::Generic), } } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index a4dfbc7f78..7943b53d76 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -5,7 +5,6 @@ use super::{ CallArgumentTypes, CallDunderError, ClassBase, ClassLiteral, KnownClass, add_inferred_python_version_hint_to_diagnostic, }; -use crate::db::Db; use crate::declare_lint; use crate::lint::{Level, LintRegistryBuilder, LintStatus}; use crate::suppression::FileSuppressionId; @@ -15,7 +14,7 @@ use crate::types::string_annotation::{ IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION, RAW_STRING_TYPE_ANNOTATION, }; -use crate::types::{KnownFunction, KnownInstanceType, Type, protocol_class::ProtocolClassLiteral}; +use crate::types::{KnownFunction, SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast::{self as ast, AnyNodeRef}; @@ -1823,7 +1822,6 @@ pub(crate) fn report_base_with_incompatible_slots(context: &InferContext, node: } pub(crate) fn report_invalid_arguments_to_annotated( - db: &dyn Db, context: &InferContext, subscript: &ast::ExprSubscript, ) { @@ -1833,7 +1831,7 @@ pub(crate) fn report_invalid_arguments_to_annotated( builder.into_diagnostic(format_args!( "Special form `{}` expected at least 2 arguments \ (one type and at least one metadata element)", - KnownInstanceType::Annotated.repr(db) + SpecialFormType::Annotated )); } @@ -1873,7 +1871,6 @@ pub(crate) fn report_bad_argument_to_get_protocol_members( } pub(crate) fn report_invalid_arguments_to_callable( - db: &dyn Db, context: &InferContext, subscript: &ast::ExprSubscript, ) { @@ -1882,7 +1879,7 @@ pub(crate) fn report_invalid_arguments_to_callable( }; builder.into_diagnostic(format_args!( "Special form `{}` expected exactly two arguments (parameter types and return type)", - KnownInstanceType::Callable.repr(db) + SpecialFormType::Callable )); } diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 1a12f2291f..6c4dd480d0 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -110,6 +110,7 @@ impl Display for DisplayRepresentation<'_> { SubclassOfInner::Class(class) => write!(f, "type[{}]", class.name(self.db)), SubclassOfInner::Dynamic(dynamic) => write!(f, "type[{dynamic}]"), }, + Type::SpecialForm(special_form) => special_form.fmt(f), Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f), Type::FunctionLiteral(function) => { let signature = function.signature(self.db); diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b60b54a4e5..6187e87ca1 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -89,7 +89,7 @@ use crate::types::{ BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy, - MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, + MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, @@ -850,7 +850,7 @@ impl<'db> TypeInferenceBuilder<'db> { // - If the class is a protocol class: check for inheritance from a non-protocol class for (i, base_class) in class.explicit_bases(self.db()).iter().enumerate() { let base_class = match base_class { - Type::KnownInstance(KnownInstanceType::Generic(None)) => { + Type::SpecialForm(SpecialFormType::Generic) => { if let Some(builder) = self .context .report_lint(&INVALID_BASE, &class_node.bases()[i]) @@ -864,7 +864,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Note that unlike several of the other errors caught in this function, // this does not lead to the class creation failing at runtime, // but it is semantically invalid. - Type::KnownInstance(KnownInstanceType::Protocol(Some(_))) => { + Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(_)) => { if class_node.type_params.is_none() { continue; } @@ -3084,6 +3084,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::BytesLiteral(..) | Type::LiteralString | Type::Tuple(..) + | Type::SpecialForm(..) | Type::KnownInstance(..) | Type::PropertyInstance(..) | Type::FunctionLiteral(..) @@ -3544,10 +3545,10 @@ impl<'db> TypeInferenceBuilder<'db> { } }; - if let Some(known_instance) = target.as_name_expr().and_then(|name| { - KnownInstanceType::try_from_file_and_name(self.db(), self.file(), &name.id) + if let Some(special_form) = target.as_name_expr().and_then(|name| { + SpecialFormType::try_from_file_and_name(self.db(), self.file(), &name.id) }) { - target_ty = Type::KnownInstance(known_instance); + target_ty = Type::SpecialForm(special_form); } self.store_expression_type(target, target_ty); @@ -3631,12 +3632,12 @@ impl<'db> TypeInferenceBuilder<'db> { if let Type::NominalInstance(instance) = declared_ty.inner_type() { if instance.class.is_known(self.db(), KnownClass::SpecialForm) { if let Some(name_expr) = target.as_name_expr() { - if let Some(known_instance) = KnownInstanceType::try_from_file_and_name( + if let Some(special_form) = SpecialFormType::try_from_file_and_name( self.db(), self.file(), &name_expr.id, ) { - declared_ty.inner = Type::KnownInstance(known_instance); + declared_ty.inner = Type::SpecialForm(special_form); } } } @@ -5958,6 +5959,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::SubclassOf(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Union(_) @@ -6287,6 +6289,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::SubclassOf(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Intersection(_) @@ -6312,6 +6315,7 @@ impl<'db> TypeInferenceBuilder<'db> { | Type::SubclassOf(_) | Type::NominalInstance(_) | Type::ProtocolInstance(_) + | Type::SpecialForm(_) | Type::KnownInstance(_) | Type::PropertyInstance(_) | Type::Intersection(_) @@ -7432,48 +7436,49 @@ impl<'db> TypeInferenceBuilder<'db> { value_ty, Type::IntLiteral(i64::from(bool)), ), - (Type::KnownInstance(KnownInstanceType::Protocol(None)), Type::Tuple(typevars), _) => { - self.legacy_generic_class_context( + (Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars), _) => self + .legacy_generic_class_context( value_node, typevars.elements(self.db()), LegacyGenericBase::Protocol, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context)))) - .unwrap_or_else(Type::unknown) - } - (Type::KnownInstance(KnownInstanceType::Protocol(None)), typevar, _) => self + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context))) + .unwrap_or_else(Type::unknown), + (Type::SpecialForm(SpecialFormType::Protocol), typevar, _) => self .legacy_generic_class_context( value_node, std::slice::from_ref(&typevar), LegacyGenericBase::Protocol, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Protocol(Some(context)))) + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context))) .unwrap_or_else(Type::unknown), - (Type::KnownInstance(KnownInstanceType::Protocol(Some(_))), _, _) => { + (Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(_)), _, _) => { // TODO: emit a diagnostic todo_type!("doubly-specialized typing.Protocol") } - (Type::KnownInstance(KnownInstanceType::Generic(None)), Type::Tuple(typevars), _) => { - self.legacy_generic_class_context( + (Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars), _) => self + .legacy_generic_class_context( value_node, typevars.elements(self.db()), LegacyGenericBase::Generic, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context)))) - .unwrap_or_else(Type::unknown) - } - (Type::KnownInstance(KnownInstanceType::Generic(None)), typevar, _) => self + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context))) + .unwrap_or_else(Type::unknown), + (Type::SpecialForm(SpecialFormType::Generic), typevar, _) => self .legacy_generic_class_context( value_node, std::slice::from_ref(&typevar), LegacyGenericBase::Generic, ) - .map(|context| Type::KnownInstance(KnownInstanceType::Generic(Some(context)))) + .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context))) .unwrap_or_else(Type::unknown), - (Type::KnownInstance(KnownInstanceType::Generic(Some(_))), _, _) => { + (Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)), _, _) => { // TODO: emit a diagnostic todo_type!("doubly-specialized typing.Generic") } + (Type::SpecialForm(special_form), _, _) if special_form.class().is_special_form() => { + todo_type!("Inference of subscript on special form") + } (Type::KnownInstance(known_instance), _, _) if known_instance.class().is_special_form() => { @@ -7779,10 +7784,10 @@ impl<'db> TypeInferenceBuilder<'db> { ast::ExprContext::Load => { let name_expr_ty = self.infer_name_expression(name); match name_expr_ty { - Type::KnownInstance(KnownInstanceType::ClassVar) => { + Type::SpecialForm(SpecialFormType::ClassVar) => { TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::CLASS_VAR) } - Type::KnownInstance(KnownInstanceType::Final) => { + Type::SpecialForm(SpecialFormType::Final) => { TypeAndQualifiers::new(Type::unknown(), TypeQualifiers::FINAL) } _ => name_expr_ty @@ -7809,7 +7814,7 @@ impl<'db> TypeInferenceBuilder<'db> { let slice = &**slice; match value_ty { - Type::KnownInstance(KnownInstanceType::Annotated) => { + Type::SpecialForm(SpecialFormType::Annotated) => { // This branch is similar to the corresponding branch in `infer_parameterized_known_instance_type_expression`, but // `Annotated[…]` can appear both in annotation expressions and in type expressions, and needs to be handled slightly // differently in each case (calling either `infer_type_expression_*` or `infer_annotation_expression_*`). @@ -7818,11 +7823,7 @@ impl<'db> TypeInferenceBuilder<'db> { }) = slice { if arguments.len() < 2 { - report_invalid_arguments_to_annotated( - self.db(), - &self.context, - subscript, - ); + report_invalid_arguments_to_annotated(&self.context, subscript); } if let [inner_annotation, metadata @ ..] = &arguments[..] { @@ -7840,16 +7841,12 @@ impl<'db> TypeInferenceBuilder<'db> { TypeAndQualifiers::unknown() } } else { - report_invalid_arguments_to_annotated( - self.db(), - &self.context, - subscript, - ); + report_invalid_arguments_to_annotated(&self.context, subscript); self.infer_annotation_expression_impl(slice) } } - Type::KnownInstance( - known_instance @ (KnownInstanceType::ClassVar | KnownInstanceType::Final), + Type::SpecialForm( + type_qualifier @ (SpecialFormType::ClassVar | SpecialFormType::Final), ) => match slice { ast::Expr::Tuple(..) => { if let Some(builder) = @@ -7858,7 +7855,6 @@ impl<'db> TypeInferenceBuilder<'db> { builder.into_diagnostic(format_args!( "Type qualifier `{type_qualifier}` \ expects exactly one type parameter", - type_qualifier = known_instance.repr(self.db()), )); } Type::unknown().into() @@ -7866,11 +7862,11 @@ impl<'db> TypeInferenceBuilder<'db> { _ => { let mut type_and_qualifiers = self.infer_annotation_expression_impl(slice); - match known_instance { - KnownInstanceType::ClassVar => { + match type_qualifier { + SpecialFormType::ClassVar => { type_and_qualifiers.add_qualifier(TypeQualifiers::CLASS_VAR); } - KnownInstanceType::Final => { + SpecialFormType::Final => { type_and_qualifiers.add_qualifier(TypeQualifiers::FINAL); } _ => unreachable!(), @@ -8322,7 +8318,7 @@ impl<'db> TypeInferenceBuilder<'db> { builder.expression_type(value) }; - value_ty == Type::KnownInstance(KnownInstanceType::Unpack) + value_ty == Type::SpecialForm(SpecialFormType::Unpack) } _ => false, } @@ -8393,7 +8389,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) } } - Type::KnownInstance(KnownInstanceType::Unknown) => { + Type::SpecialForm(SpecialFormType::Unknown) => { SubclassOfType::subclass_of_unknown() } _ => todo_type!("unsupported type[X] special form"), @@ -8424,7 +8420,7 @@ impl<'db> TypeInferenceBuilder<'db> { .. }) => { let parameters_ty = match self.infer_expression(value) { - Type::KnownInstance(KnownInstanceType::Union) => match &**parameters { + Type::SpecialForm(SpecialFormType::Union) => match &**parameters { ast::Expr::Tuple(tuple) => { let ty = UnionType::from_elements( self.db(), @@ -8472,9 +8468,37 @@ impl<'db> TypeInferenceBuilder<'db> { } Type::unknown() } - Type::KnownInstance(known_instance) => { - self.infer_parameterized_known_instance_type_expression(subscript, known_instance) + Type::SpecialForm(special_form) => { + self.infer_parameterized_special_form_type_expression(subscript, special_form) } + Type::KnownInstance(known_instance) => match known_instance { + KnownInstanceType::SubscriptedProtocol(_) => { + self.infer_type_expression(&subscript.slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "`typing.Protocol` is not allowed in type expressions", + )); + } + Type::unknown() + } + KnownInstanceType::SubscriptedGeneric(_) => { + self.infer_type_expression(&subscript.slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "`typing.Generic` is not allowed in type expressions", + )); + } + Type::unknown() + } + KnownInstanceType::TypeVar(_) => { + self.infer_type_expression(&subscript.slice); + todo_type!("TypeVar annotations") + } + KnownInstanceType::TypeAliasType(_) => { + self.infer_type_expression(&subscript.slice); + todo_type!("Generic PEP-695 type alias") + } + }, Type::Dynamic(DynamicType::Todo(_)) => { self.infer_type_expression(slice); value_ty @@ -8509,20 +8533,20 @@ impl<'db> TypeInferenceBuilder<'db> { } } - fn infer_parameterized_known_instance_type_expression( + fn infer_parameterized_special_form_type_expression( &mut self, subscript: &ast::ExprSubscript, - known_instance: KnownInstanceType, + special_form: SpecialFormType, ) -> Type<'db> { let db = self.db(); let arguments_slice = &*subscript.slice; - match known_instance { - KnownInstanceType::Annotated => { + match special_form { + SpecialFormType::Annotated => { let ast::Expr::Tuple(ast::ExprTuple { elts: arguments, .. }) = arguments_slice else { - report_invalid_arguments_to_annotated(self.db(), &self.context, subscript); + report_invalid_arguments_to_annotated(&self.context, subscript); // `Annotated[]` with less than two arguments is an error at runtime. // However, we still treat `Annotated[T]` as `T` here for the purpose of @@ -8532,7 +8556,7 @@ impl<'db> TypeInferenceBuilder<'db> { }; if arguments.len() < 2 { - report_invalid_arguments_to_annotated(self.db(), &self.context, subscript); + report_invalid_arguments_to_annotated(&self.context, subscript); } let [type_expr, metadata @ ..] = &arguments[..] else { @@ -8548,29 +8572,27 @@ impl<'db> TypeInferenceBuilder<'db> { self.store_expression_type(arguments_slice, ty); ty } - KnownInstanceType::Literal => { - match self.infer_literal_parameter_type(arguments_slice) { - Ok(ty) => ty, - Err(nodes) => { - for node in nodes { - if let Some(builder) = - self.context.report_lint(&INVALID_TYPE_FORM, node) - { - builder.into_diagnostic( - "Type arguments for `Literal` must be `None`, \ - a literal value (int, bool, str, or bytes), or an enum value", - ); - } - } - Type::unknown() + SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) { + Ok(ty) => ty, + Err(nodes) => { + for node in nodes { + let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node) + else { + continue; + }; + builder.into_diagnostic( + "Type arguments for `Literal` must be `None`, \ + a literal value (int, bool, str, or bytes), or an enum value", + ); } + Type::unknown() } - } - KnownInstanceType::Optional => { + }, + SpecialFormType::Optional => { let param_type = self.infer_type_expression(arguments_slice); UnionType::from_elements(db, [param_type, Type::none(db)]) } - KnownInstanceType::Union => match arguments_slice { + SpecialFormType::Union => match arguments_slice { ast::Expr::Tuple(t) => { let union_ty = UnionType::from_elements( db, @@ -8581,15 +8603,7 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => self.infer_type_expression(arguments_slice), }, - KnownInstanceType::TypeVar(_) => { - self.infer_type_expression(arguments_slice); - todo_type!("TypeVar annotations") - } - KnownInstanceType::TypeAliasType(_) => { - self.infer_type_expression(arguments_slice); - todo_type!("Generic PEP-695 type alias") - } - KnownInstanceType::Callable => { + SpecialFormType::Callable => { let mut arguments = match arguments_slice { ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()), _ => { @@ -8616,7 +8630,7 @@ impl<'db> TypeInferenceBuilder<'db> { }; if !correct_argument_number { - report_invalid_arguments_to_callable(self.db(), &self.context, subscript); + report_invalid_arguments_to_callable(&self.context, subscript); } let callable_type = if let (Some(parameters), Some(return_type), true) = @@ -8638,12 +8652,11 @@ impl<'db> TypeInferenceBuilder<'db> { } // Type API special forms - KnownInstanceType::Not => match arguments_slice { + SpecialFormType::Not => match arguments_slice { ast::Expr::Tuple(_) => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Special form `{}` expected exactly one type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected exactly one type parameter", )); } Type::unknown() @@ -8653,7 +8666,7 @@ impl<'db> TypeInferenceBuilder<'db> { argument_type.negate(db) } }, - KnownInstanceType::Intersection => { + SpecialFormType::Intersection => { let elements = match arguments_slice { ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()), element => Either::Right(std::iter::once(element)), @@ -8670,12 +8683,11 @@ impl<'db> TypeInferenceBuilder<'db> { } ty } - KnownInstanceType::TypeOf => match arguments_slice { + SpecialFormType::TypeOf => match arguments_slice { ast::Expr::Tuple(_) => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Special form `{}` expected exactly one type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected exactly one type parameter", )); } Type::unknown() @@ -8686,12 +8698,11 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_expression(arguments_slice) } }, - KnownInstanceType::CallableTypeOf => match arguments_slice { + SpecialFormType::CallableTypeOf => match arguments_slice { ast::Expr::Tuple(_) => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Special form `{}` expected exactly one type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected exactly one type parameter", )); } Type::unknown() @@ -8721,11 +8732,10 @@ impl<'db> TypeInferenceBuilder<'db> { .report_lint(&INVALID_TYPE_FORM, arguments_slice) { builder.into_diagnostic(format_args!( - "Expected the first argument to `{}` \ + "Expected the first argument to `{special_form}` \ to be a callable object, \ - but got an object of type `{}`", - known_instance.repr(self.db()), - argument_type.display(db) + but got an object of type `{actual_type}`", + actual_type = argument_type.display(db) )); } return Type::unknown(); @@ -8739,142 +8749,129 @@ impl<'db> TypeInferenceBuilder<'db> { }, // TODO: Generics - KnownInstanceType::ChainMap => { + SpecialFormType::ChainMap => { self.infer_type_expression(arguments_slice); KnownClass::ChainMap.to_instance(db) } - KnownInstanceType::OrderedDict => { + SpecialFormType::OrderedDict => { self.infer_type_expression(arguments_slice); KnownClass::OrderedDict.to_instance(db) } - KnownInstanceType::Dict => { + SpecialFormType::Dict => { self.infer_type_expression(arguments_slice); KnownClass::Dict.to_instance(db) } - KnownInstanceType::List => { + SpecialFormType::List => { self.infer_type_expression(arguments_slice); KnownClass::List.to_instance(db) } - KnownInstanceType::DefaultDict => { + SpecialFormType::DefaultDict => { self.infer_type_expression(arguments_slice); KnownClass::DefaultDict.to_instance(db) } - KnownInstanceType::Counter => { + SpecialFormType::Counter => { self.infer_type_expression(arguments_slice); KnownClass::Counter.to_instance(db) } - KnownInstanceType::Set => { + SpecialFormType::Set => { self.infer_type_expression(arguments_slice); KnownClass::Set.to_instance(db) } - KnownInstanceType::FrozenSet => { + SpecialFormType::FrozenSet => { self.infer_type_expression(arguments_slice); KnownClass::FrozenSet.to_instance(db) } - KnownInstanceType::Deque => { + SpecialFormType::Deque => { self.infer_type_expression(arguments_slice); KnownClass::Deque.to_instance(db) } - KnownInstanceType::ReadOnly => { + SpecialFormType::ReadOnly => { self.infer_type_expression(arguments_slice); todo_type!("`ReadOnly[]` type qualifier") } - KnownInstanceType::NotRequired => { + SpecialFormType::NotRequired => { self.infer_type_expression(arguments_slice); todo_type!("`NotRequired[]` type qualifier") } - KnownInstanceType::ClassVar | KnownInstanceType::Final => { + SpecialFormType::ClassVar | SpecialFormType::Final => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { let diag = builder.into_diagnostic(format_args!( - "Type qualifier `{}` is not allowed in type expressions \ + "Type qualifier `{special_form}` is not allowed in type expressions \ (only in annotation expressions)", - known_instance.repr(self.db()) )); diagnostic::add_type_expression_reference_link(diag); } self.infer_type_expression(arguments_slice) } - KnownInstanceType::Required => { + SpecialFormType::Required => { self.infer_type_expression(arguments_slice); todo_type!("`Required[]` type qualifier") } - KnownInstanceType::TypeIs => { + SpecialFormType::TypeIs => { self.infer_type_expression(arguments_slice); todo_type!("`TypeIs[]` special form") } - KnownInstanceType::TypeGuard => { + SpecialFormType::TypeGuard => { self.infer_type_expression(arguments_slice); todo_type!("`TypeGuard[]` special form") } - KnownInstanceType::Concatenate => { + SpecialFormType::Concatenate => { self.infer_type_expression(arguments_slice); todo_type!("`Concatenate[]` special form") } - KnownInstanceType::Unpack => { + SpecialFormType::Unpack => { self.infer_type_expression(arguments_slice); todo_type!("`Unpack[]` special form") } - KnownInstanceType::Protocol(_) => { - self.infer_type_expression(arguments_slice); - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "`typing.Protocol` is not allowed in type expressions", - )); - } - Type::unknown() - } - KnownInstanceType::Generic(_) => { - self.infer_type_expression(arguments_slice); - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - builder.into_diagnostic(format_args!( - "`typing.Generic` is not allowed in type expressions", - )); - } - Type::unknown() - } - KnownInstanceType::NoReturn - | KnownInstanceType::Never - | KnownInstanceType::AlwaysTruthy - | KnownInstanceType::AlwaysFalsy => { + SpecialFormType::NoReturn + | SpecialFormType::Never + | SpecialFormType::AlwaysTruthy + | SpecialFormType::AlwaysFalsy => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Type `{}` expected no type parameter", - known_instance.repr(self.db()) + "Type `{special_form}` expected no type parameter", )); } Type::unknown() } - KnownInstanceType::TypingSelf - | KnownInstanceType::TypeAlias - | KnownInstanceType::TypedDict - | KnownInstanceType::Unknown => { + SpecialFormType::TypingSelf + | SpecialFormType::TypeAlias + | SpecialFormType::TypedDict + | SpecialFormType::Unknown => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Special form `{}` expected no type parameter", - known_instance.repr(self.db()) + "Special form `{special_form}` expected no type parameter", )); } Type::unknown() } - KnownInstanceType::LiteralString => { + SpecialFormType::LiteralString => { self.infer_type_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { let mut diag = builder.into_diagnostic(format_args!( - "Type `{}` expected no type parameter", - known_instance.repr(self.db()) + "Type `{special_form}` expected no type parameter", )); diag.info("Did you mean to use `Literal[...]` instead?"); } Type::unknown() } - KnownInstanceType::Type => self.infer_subclass_of_type_expression(arguments_slice), - KnownInstanceType::Tuple => self.infer_tuple_type_expression(arguments_slice), + SpecialFormType::Type => self.infer_subclass_of_type_expression(arguments_slice), + SpecialFormType::Tuple => self.infer_tuple_type_expression(arguments_slice), + SpecialFormType::Generic | SpecialFormType::Protocol => { + self.infer_expression(arguments_slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "`{special_form}` is not allowed in type expressions", + )); + } + Type::unknown() + } } } @@ -8886,7 +8883,7 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO handle type aliases ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => { let value_ty = self.infer_expression(value); - if matches!(value_ty, Type::KnownInstance(KnownInstanceType::Literal)) { + if matches!(value_ty, Type::SpecialForm(SpecialFormType::Literal)) { let ty = self.infer_literal_parameter_type(slice)?; // This branch deals with annotations such as `Literal[Literal[1]]`. @@ -9427,7 +9424,7 @@ mod tests { let name_ty = var_ty.member(&db, "__name__").symbol.expect_type(); assert_eq!(name_ty.display(&db).to_string(), expected_name_ty); - let KnownInstanceType::TypeVar(typevar) = var_ty.expect_known_instance() else { + let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = var_ty else { panic!("expected TypeVar"); }; diff --git a/crates/ty_python_semantic/src/types/known_instance.rs b/crates/ty_python_semantic/src/types/known_instance.rs deleted file mode 100644 index 095d1cf752..0000000000 --- a/crates/ty_python_semantic/src/types/known_instance.rs +++ /dev/null @@ -1,413 +0,0 @@ -//! The `KnownInstance` type. -//! -//! Despite its name, this is quite a different type from [`super::NominalInstanceType`]. -//! For the vast majority of instance-types in Python, we cannot say how many possible -//! inhabitants there are or could be of that type at runtime. Each variant of the -//! [`KnownInstanceType`] enum, however, represents a specific runtime symbol -//! that requires heavy special-casing in the type system. Thus any one `KnownInstance` -//! variant can only be inhabited by one or two specific objects at runtime with -//! locations that are known in advance. - -use std::fmt::Display; - -use super::generics::GenericContext; -use super::{ClassType, Type, TypeAliasType, TypeVarInstance, class::KnownClass}; -use crate::db::Db; -use crate::module_resolver::{KnownModule, file_to_module}; -use ruff_db::files::File; - -/// Enumeration of specific runtime symbols that are special enough -/// that they can each be considered to inhabit a unique type. -/// -/// # Ordering -/// -/// Ordering between variants is stable and should be the same between runs. -/// Ordering within variants (for variants that wrap associate data) -/// is based on the known-instance's salsa-assigned id and not on its values. -/// The id may change between runs, or when the type var instance was garbage collected and recreated. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update, PartialOrd, Ord)] -pub enum KnownInstanceType<'db> { - /// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`) - Annotated, - /// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`) - Literal, - /// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`) - LiteralString, - /// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`) - Optional, - /// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`) - Union, - /// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`) - NoReturn, - /// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`) - Never, - /// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`) - Tuple, - /// The symbol `typing.List` (which can also be found as `typing_extensions.List`) - List, - /// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`) - Dict, - /// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`) - Set, - /// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`) - FrozenSet, - /// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`) - ChainMap, - /// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`) - Counter, - /// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`) - DefaultDict, - /// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`) - Deque, - /// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`) - OrderedDict, - /// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`) - Protocol(Option>), - /// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`) - Generic(Option>), - /// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`) - Type, - /// A single instance of `typing.TypeVar` - TypeVar(TypeVarInstance<'db>), - /// A single instance of `typing.TypeAliasType` (PEP 695 type alias) - TypeAliasType(TypeAliasType<'db>), - /// The symbol `ty_extensions.Unknown` - Unknown, - /// The symbol `ty_extensions.AlwaysTruthy` - AlwaysTruthy, - /// The symbol `ty_extensions.AlwaysFalsy` - AlwaysFalsy, - /// The symbol `ty_extensions.Not` - Not, - /// The symbol `ty_extensions.Intersection` - Intersection, - /// The symbol `ty_extensions.TypeOf` - TypeOf, - /// The symbol `ty_extensions.CallableTypeOf` - CallableTypeOf, - /// The symbol `typing.Callable` - /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) - Callable, - /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self` or - /// `_typeshed.Self`) - TypingSelf, - - // Various special forms, special aliases and type qualifiers that we don't yet understand - // (all currently inferred as TODO in most contexts): - Final, - ClassVar, - Concatenate, - Unpack, - Required, - NotRequired, - TypeAlias, - TypeGuard, - TypedDict, - TypeIs, - ReadOnly, - // TODO: fill this enum out with more special forms, etc. -} - -impl<'db> KnownInstanceType<'db> { - pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { - match self { - Self::Annotated - | Self::Literal - | Self::LiteralString - | Self::Optional - | Self::Union - | Self::NoReturn - | Self::Never - | Self::Tuple - | Self::Type - | Self::TypingSelf - | Self::Final - | Self::ClassVar - | Self::Callable - | Self::Concatenate - | Self::Unpack - | Self::Required - | Self::NotRequired - | Self::TypeAlias - | Self::TypeGuard - | Self::TypedDict - | Self::TypeIs - | Self::List - | Self::Dict - | Self::DefaultDict - | Self::Set - | Self::FrozenSet - | Self::Counter - | Self::Deque - | Self::ChainMap - | Self::OrderedDict - | Self::ReadOnly - | Self::Unknown - | Self::AlwaysTruthy - | Self::AlwaysFalsy - | Self::Not - | Self::Intersection - | Self::TypeOf - | Self::CallableTypeOf => self, - Self::TypeVar(tvar) => Self::TypeVar(tvar.normalized(db)), - Self::Protocol(ctx) => Self::Protocol(ctx.map(|ctx| ctx.normalized(db))), - Self::Generic(ctx) => Self::Generic(ctx.map(|ctx| ctx.normalized(db))), - Self::TypeAliasType(alias) => Self::TypeAliasType(alias.normalized(db)), - } - } - - /// Return the repr of the symbol at runtime - pub(crate) fn repr(self, db: &'db dyn Db) -> impl Display + 'db { - KnownInstanceRepr { - known_instance: self, - db, - } - } - - /// Return the [`KnownClass`] which this symbol is an instance of - pub(crate) const fn class(self) -> KnownClass { - match self { - Self::Annotated => KnownClass::SpecialForm, - Self::Literal => KnownClass::SpecialForm, - Self::LiteralString => KnownClass::SpecialForm, - Self::Optional => KnownClass::SpecialForm, - Self::Union => KnownClass::SpecialForm, - Self::NoReturn => KnownClass::SpecialForm, - Self::Never => KnownClass::SpecialForm, - Self::Tuple => KnownClass::SpecialForm, - Self::Type => KnownClass::SpecialForm, - Self::TypingSelf => KnownClass::SpecialForm, - Self::Final => KnownClass::SpecialForm, - Self::ClassVar => KnownClass::SpecialForm, - Self::Callable => KnownClass::SpecialForm, - Self::Concatenate => KnownClass::SpecialForm, - Self::Unpack => KnownClass::SpecialForm, - Self::Required => KnownClass::SpecialForm, - Self::NotRequired => KnownClass::SpecialForm, - Self::TypeAlias => KnownClass::SpecialForm, - Self::TypeGuard => KnownClass::SpecialForm, - Self::TypedDict => KnownClass::SpecialForm, - Self::TypeIs => KnownClass::SpecialForm, - Self::ReadOnly => KnownClass::SpecialForm, - Self::List => KnownClass::StdlibAlias, - Self::Dict => KnownClass::StdlibAlias, - Self::DefaultDict => KnownClass::StdlibAlias, - Self::Set => KnownClass::StdlibAlias, - Self::FrozenSet => KnownClass::StdlibAlias, - Self::Counter => KnownClass::StdlibAlias, - Self::Deque => KnownClass::StdlibAlias, - Self::ChainMap => KnownClass::StdlibAlias, - Self::OrderedDict => KnownClass::StdlibAlias, - Self::Protocol(_) => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says - Self::Generic(_) => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says - Self::TypeVar(_) => KnownClass::TypeVar, - Self::TypeAliasType(_) => KnownClass::TypeAliasType, - Self::TypeOf => KnownClass::SpecialForm, - Self::Not => KnownClass::SpecialForm, - Self::Intersection => KnownClass::SpecialForm, - Self::CallableTypeOf => KnownClass::SpecialForm, - Self::Unknown => KnownClass::Object, - Self::AlwaysTruthy => KnownClass::Object, - Self::AlwaysFalsy => KnownClass::Object, - } - } - - /// Return the instance type which this type is a subtype of. - /// - /// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`, - /// so `KnownInstanceType::Literal.instance_fallback(db)` - /// returns `Type::NominalInstance(NominalInstanceType { class: })`. - pub(super) fn instance_fallback(self, db: &dyn Db) -> Type { - self.class().to_instance(db) - } - - /// Return `true` if this symbol is an instance of `class`. - pub(super) fn is_instance_of(self, db: &'db dyn Db, class: ClassType<'db>) -> bool { - self.class().is_subclass_of(db, class) - } - - pub(super) fn try_from_file_and_name( - db: &'db dyn Db, - file: File, - symbol_name: &str, - ) -> Option { - let candidate = match symbol_name { - "ClassVar" => Self::ClassVar, - "Deque" => Self::Deque, - "List" => Self::List, - "Dict" => Self::Dict, - "DefaultDict" => Self::DefaultDict, - "Set" => Self::Set, - "FrozenSet" => Self::FrozenSet, - "Counter" => Self::Counter, - "ChainMap" => Self::ChainMap, - "OrderedDict" => Self::OrderedDict, - "Generic" => Self::Generic(None), - "Protocol" => Self::Protocol(None), - "Optional" => Self::Optional, - "Union" => Self::Union, - "NoReturn" => Self::NoReturn, - "Tuple" => Self::Tuple, - "Type" => Self::Type, - "Callable" => Self::Callable, - "Annotated" => Self::Annotated, - "Literal" => Self::Literal, - "Never" => Self::Never, - "Self" => Self::TypingSelf, - "Final" => Self::Final, - "Unpack" => Self::Unpack, - "Required" => Self::Required, - "TypeAlias" => Self::TypeAlias, - "TypeGuard" => Self::TypeGuard, - "TypedDict" => Self::TypedDict, - "TypeIs" => Self::TypeIs, - "ReadOnly" => Self::ReadOnly, - "Concatenate" => Self::Concatenate, - "NotRequired" => Self::NotRequired, - "LiteralString" => Self::LiteralString, - "Unknown" => Self::Unknown, - "AlwaysTruthy" => Self::AlwaysTruthy, - "AlwaysFalsy" => Self::AlwaysFalsy, - "Not" => Self::Not, - "Intersection" => Self::Intersection, - "TypeOf" => Self::TypeOf, - "CallableTypeOf" => Self::CallableTypeOf, - _ => return None, - }; - - candidate - .check_module(file_to_module(db, file)?.known()?) - .then_some(candidate) - } - - /// Return `true` if `module` is a module from which this `KnownInstance` variant can validly originate. - /// - /// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`. - /// Some variants could validly be defined in either `typing` or `typing_extensions`, however. - pub(super) fn check_module(self, module: KnownModule) -> bool { - match self { - Self::ClassVar - | Self::Deque - | Self::List - | Self::Dict - | Self::DefaultDict - | Self::Set - | Self::FrozenSet - | Self::Counter - | Self::ChainMap - | Self::OrderedDict - | Self::Optional - | Self::Union - | Self::NoReturn - | Self::Tuple - | Self::Type - | Self::Generic(_) - | Self::Callable => module.is_typing(), - Self::Annotated - | Self::Protocol(_) - | Self::Literal - | Self::LiteralString - | Self::Never - | Self::Final - | Self::Concatenate - | Self::Unpack - | Self::Required - | Self::NotRequired - | Self::TypeAlias - | Self::TypeGuard - | Self::TypedDict - | Self::TypeIs - | Self::ReadOnly - | Self::TypeAliasType(_) - | Self::TypeVar(_) => { - matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) - } - Self::TypingSelf => { - matches!( - module, - KnownModule::Typing | KnownModule::TypingExtensions | KnownModule::Typeshed - ) - } - Self::Unknown - | Self::AlwaysTruthy - | Self::AlwaysFalsy - | Self::Not - | Self::Intersection - | Self::TypeOf - | Self::CallableTypeOf => module.is_ty_extensions(), - } - } - - pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> { - self.class().to_class_literal(db) - } -} - -struct KnownInstanceRepr<'db> { - known_instance: KnownInstanceType<'db>, - db: &'db dyn Db, -} - -impl Display for KnownInstanceRepr<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.known_instance { - KnownInstanceType::Annotated => f.write_str("typing.Annotated"), - KnownInstanceType::Literal => f.write_str("typing.Literal"), - KnownInstanceType::LiteralString => f.write_str("typing.LiteralString"), - KnownInstanceType::Optional => f.write_str("typing.Optional"), - KnownInstanceType::Union => f.write_str("typing.Union"), - KnownInstanceType::NoReturn => f.write_str("typing.NoReturn"), - KnownInstanceType::Never => f.write_str("typing.Never"), - KnownInstanceType::Tuple => f.write_str("typing.Tuple"), - KnownInstanceType::Type => f.write_str("typing.Type"), - KnownInstanceType::TypingSelf => f.write_str("typing.Self"), - KnownInstanceType::Final => f.write_str("typing.Final"), - KnownInstanceType::ClassVar => f.write_str("typing.ClassVar"), - KnownInstanceType::Callable => f.write_str("typing.Callable"), - KnownInstanceType::Concatenate => f.write_str("typing.Concatenate"), - KnownInstanceType::Unpack => f.write_str("typing.Unpack"), - KnownInstanceType::Required => f.write_str("typing.Required"), - KnownInstanceType::NotRequired => f.write_str("typing.NotRequired"), - KnownInstanceType::TypeAlias => f.write_str("typing.TypeAlias"), - KnownInstanceType::TypeGuard => f.write_str("typing.TypeGuard"), - KnownInstanceType::TypedDict => f.write_str("typing.TypedDict"), - KnownInstanceType::TypeIs => f.write_str("typing.TypeIs"), - KnownInstanceType::List => f.write_str("typing.List"), - KnownInstanceType::Dict => f.write_str("typing.Dict"), - KnownInstanceType::DefaultDict => f.write_str("typing.DefaultDict"), - KnownInstanceType::Set => f.write_str("typing.Set"), - KnownInstanceType::FrozenSet => f.write_str("typing.FrozenSet"), - KnownInstanceType::Counter => f.write_str("typing.Counter"), - KnownInstanceType::Deque => f.write_str("typing.Deque"), - KnownInstanceType::ChainMap => f.write_str("typing.ChainMap"), - KnownInstanceType::OrderedDict => f.write_str("typing.OrderedDict"), - KnownInstanceType::Protocol(generic_context) => { - f.write_str("typing.Protocol")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - KnownInstanceType::Generic(generic_context) => { - f.write_str("typing.Generic")?; - if let Some(generic_context) = generic_context { - generic_context.display(self.db).fmt(f)?; - } - Ok(()) - } - KnownInstanceType::ReadOnly => f.write_str("typing.ReadOnly"), - // This is a legacy `TypeVar` _outside_ of any generic class or function, so we render - // it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll - // have a `Type::TypeVar(_)`, which is rendered as the typevar's name. - KnownInstanceType::TypeVar(_) => f.write_str("typing.TypeVar"), - KnownInstanceType::TypeAliasType(_) => f.write_str("typing.TypeAliasType"), - KnownInstanceType::Unknown => f.write_str("ty_extensions.Unknown"), - KnownInstanceType::AlwaysTruthy => f.write_str("ty_extensions.AlwaysTruthy"), - KnownInstanceType::AlwaysFalsy => f.write_str("ty_extensions.AlwaysFalsy"), - KnownInstanceType::Not => f.write_str("ty_extensions.Not"), - KnownInstanceType::Intersection => f.write_str("ty_extensions.Intersection"), - KnownInstanceType::TypeOf => f.write_str("ty_extensions.TypeOf"), - KnownInstanceType::CallableTypeOf => f.write_str("ty_extensions.CallableTypeOf"), - } - } -} diff --git a/crates/ty_python_semantic/src/types/mro.rs b/crates/ty_python_semantic/src/types/mro.rs index 44957fb929..4b45888524 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, KnownInstanceType, Type}; +use crate::types::{ClassLiteral, ClassType, KnownInstanceType, SpecialFormType, Type}; /// The inferred method resolution order of a given class. /// @@ -92,7 +92,7 @@ impl<'db> Mro<'db> { original_bases: &[Type<'db>], remaining_bases: &[Type<'db>], ) { - if original_bases.contains(&Type::KnownInstance(KnownInstanceType::Protocol(None))) { + if original_bases.contains(&Type::SpecialForm(SpecialFormType::Protocol)) { return; } if remaining_bases.iter().any(Type::is_generic_alias) { @@ -146,7 +146,8 @@ impl<'db> Mro<'db> { single_base, Type::GenericAlias(_) | Type::KnownInstance( - KnownInstanceType::Generic(_) | KnownInstanceType::Protocol(_) + KnownInstanceType::SubscriptedGeneric(_) + | KnownInstanceType::SubscriptedProtocol(_) ) ) => { @@ -178,7 +179,7 @@ impl<'db> Mro<'db> { // (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 let Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)) = base { maybe_add_generic( &mut resolved_bases, original_bases, @@ -226,7 +227,11 @@ impl<'db> Mro<'db> { if class.has_pep_695_type_params(db) && original_bases.iter().any(|base| { - matches!(base, Type::KnownInstance(KnownInstanceType::Generic(_))) + matches!( + base, + Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)) + | Type::SpecialForm(SpecialFormType::Generic) + ) }) { return Err(MroErrorKind::Pep695ClassWithGenericInheritance); diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 0850b38745..c419b1c31b 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -1,8 +1,8 @@ use crate::db::tests::TestDb; use crate::symbol::{builtins_symbol, known_module_symbol}; use crate::types::{ - BoundMethodType, CallableType, IntersectionBuilder, KnownClass, KnownInstanceType, Parameter, - Parameters, Signature, SubclassOfType, TupleType, Type, UnionType, + BoundMethodType, CallableType, IntersectionBuilder, KnownClass, Parameter, Parameters, + Signature, SpecialFormType, SubclassOfType, TupleType, Type, UnionType, }; use crate::{Db, KnownModule}; use hashbrown::HashSet; @@ -142,7 +142,7 @@ impl Ty { Ty::AbcClassLiteral(s) => known_module_symbol(db, KnownModule::Abc, s) .symbol .expect_type(), - Ty::TypingLiteral => Type::KnownInstance(KnownInstanceType::Literal), + Ty::TypingLiteral => Type::SpecialForm(SpecialFormType::Literal), Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).symbol.expect_type(), Ty::KnownClassInstance(known_class) => known_class.to_instance(db), Ty::Union(tys) => { diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs new file mode 100644 index 0000000000..73ef8362ad --- /dev/null +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -0,0 +1,305 @@ +//! An enumeration of special forms in the Python type system. +//! Each of these is considered to inhabit a unique type in our model of the type system. + +use super::{ClassType, Type, class::KnownClass}; +use crate::db::Db; +use crate::module_resolver::{KnownModule, file_to_module}; +use ruff_db::files::File; +use std::str::FromStr; + +/// Enumeration of specific runtime symbols that are special enough +/// that they can each be considered to inhabit a unique type. +/// +/// # Ordering +/// +/// Ordering is stable and should be the same between runs. +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + salsa::Update, + PartialOrd, + Ord, + strum_macros::EnumString, +)] +pub enum SpecialFormType { + /// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`) + Annotated, + /// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`) + Literal, + /// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`) + LiteralString, + /// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`) + Optional, + /// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`) + Union, + /// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`) + NoReturn, + /// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`) + Never, + /// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`) + Tuple, + /// The symbol `typing.List` (which can also be found as `typing_extensions.List`) + List, + /// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`) + Dict, + /// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`) + Set, + /// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`) + FrozenSet, + /// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`) + ChainMap, + /// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`) + Counter, + /// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`) + DefaultDict, + /// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`) + Deque, + /// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`) + OrderedDict, + /// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`) + Type, + /// The symbol `ty_extensions.Unknown` + Unknown, + /// The symbol `ty_extensions.AlwaysTruthy` + AlwaysTruthy, + /// The symbol `ty_extensions.AlwaysFalsy` + AlwaysFalsy, + /// The symbol `ty_extensions.Not` + Not, + /// The symbol `ty_extensions.Intersection` + Intersection, + /// The symbol `ty_extensions.TypeOf` + TypeOf, + /// The symbol `ty_extensions.CallableTypeOf` + CallableTypeOf, + /// The symbol `typing.Callable` + /// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`) + Callable, + /// The symbol `typing.Self` (which can also be found as `typing_extensions.Self` or `_typeshed.Self`) + #[strum(serialize = "Self")] + TypingSelf, + /// The symbol `typing.Final` (which can also be found as `typing_extensions.Final`) + Final, + /// The symbol `typing.ClassVar` (which can also be found as `typing_extensions.ClassVar`) + ClassVar, + /// The symbol `typing.Concatenate` (which can also be found as `typing_extensions.Concatenate`) + Concatenate, + /// The symbol `typing.Unpack` (which can also be found as `typing_extensions.Unpack`) + Unpack, + /// The symbol `typing.Required` (which can also be found as `typing_extensions.Required`) + Required, + /// The symbol `typing.NotRequired` (which can also be found as `typing_extensions.NotRequired`) + NotRequired, + /// The symbol `typing.TypeAlias` (which can also be found as `typing_extensions.TypeAlias`) + TypeAlias, + /// The symbol `typing.TypeGuard` (which can also be found as `typing_extensions.TypeGuard`) + TypeGuard, + /// The symbol `typing.TypedDict` (which can also be found as `typing_extensions.TypedDict`) + TypedDict, + /// The symbol `typing.TypeIs` (which can also be found as `typing_extensions.TypeIs`) + TypeIs, + /// The symbol `typing.ReadOnly` (which can also be found as `typing_extensions.ReadOnly`) + ReadOnly, + + /// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`) + /// + /// Note that instances of subscripted `typing.Protocol` are not represented by this type; + /// see also [`super::KnownInstanceType::SubscriptedProtocol`]. + Protocol, + + /// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`). + /// + /// Note that instances of subscripted `typing.Generic` are not represented by this type; + /// see also [`super::KnownInstanceType::SubscriptedGeneric`]. + Generic, +} + +impl SpecialFormType { + /// Return the [`KnownClass`] which this symbol is an instance of + pub(crate) const fn class(self) -> KnownClass { + match self { + Self::Annotated + | Self::Literal + | Self::LiteralString + | Self::Optional + | Self::Union + | Self::NoReturn + | Self::Never + | Self::Tuple + | Self::Type + | Self::TypingSelf + | Self::Final + | Self::ClassVar + | Self::Callable + | Self::Concatenate + | Self::Unpack + | Self::Required + | Self::NotRequired + | Self::TypeAlias + | Self::TypeGuard + | Self::TypedDict + | Self::TypeIs + | Self::TypeOf + | Self::Not + | Self::Intersection + | Self::CallableTypeOf + | Self::Protocol // actually `_ProtocolMeta` at runtime but this is what typeshed says + | Self::Generic // actually `type` at runtime but this is what typeshed says + | Self::ReadOnly => KnownClass::SpecialForm, + + Self::List + | Self::Dict + | Self::DefaultDict + | Self::Set + | Self::FrozenSet + | Self::Counter + | Self::Deque + | Self::ChainMap + | Self::OrderedDict => KnownClass::StdlibAlias, + + Self::Unknown | Self::AlwaysTruthy | Self::AlwaysFalsy => KnownClass::Object, + } + } + + /// Return the instance type which this type is a subtype of. + /// + /// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`, + /// so `SpecialFormType::Literal.instance_fallback(db)` + /// returns `Type::NominalInstance(NominalInstanceType { class: })`. + pub(super) fn instance_fallback(self, db: &dyn Db) -> Type { + self.class().to_instance(db) + } + + /// Return `true` if this symbol is an instance of `class`. + pub(super) fn is_instance_of(self, db: &dyn Db, class: ClassType) -> bool { + self.class().is_subclass_of(db, class) + } + + pub(super) fn try_from_file_and_name( + db: &dyn Db, + file: File, + symbol_name: &str, + ) -> Option { + let candidate = Self::from_str(symbol_name).ok()?; + candidate + .check_module(file_to_module(db, file)?.known()?) + .then_some(candidate) + } + + /// Return `true` if `module` is a module from which this `SpecialFormType` variant can validly originate. + /// + /// Most variants can only exist in one module, which is the same as `self.class().canonical_module(db)`. + /// Some variants could validly be defined in either `typing` or `typing_extensions`, however. + pub(super) fn check_module(self, module: KnownModule) -> bool { + match self { + Self::ClassVar + | Self::Deque + | Self::List + | Self::Dict + | Self::DefaultDict + | Self::Set + | Self::FrozenSet + | Self::Counter + | Self::ChainMap + | Self::OrderedDict + | Self::Optional + | Self::Union + | Self::NoReturn + | Self::Tuple + | Self::Type + | Self::Generic + | Self::Callable => module.is_typing(), + + Self::Annotated + | Self::Literal + | Self::LiteralString + | Self::Never + | Self::Final + | Self::Concatenate + | Self::Unpack + | Self::Required + | Self::NotRequired + | Self::TypeAlias + | Self::TypeGuard + | Self::TypedDict + | Self::TypeIs + | Self::Protocol + | Self::ReadOnly => { + matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) + } + + Self::TypingSelf => matches!( + module, + KnownModule::Typing | KnownModule::TypingExtensions | KnownModule::Typeshed + ), + + Self::Unknown + | Self::AlwaysTruthy + | Self::AlwaysFalsy + | Self::Not + | Self::Intersection + | Self::TypeOf + | Self::CallableTypeOf => module.is_ty_extensions(), + } + } + + pub(super) fn to_meta_type(self, db: &dyn Db) -> Type { + self.class().to_class_literal(db) + } + + /// Return the repr of the symbol at runtime + pub(super) const fn repr(self) -> &'static str { + match self { + SpecialFormType::Annotated => "typing.Annotated", + SpecialFormType::Literal => "typing.Literal", + SpecialFormType::LiteralString => "typing.LiteralString", + SpecialFormType::Optional => "typing.Optional", + SpecialFormType::Union => "typing.Union", + SpecialFormType::NoReturn => "typing.NoReturn", + SpecialFormType::Never => "typing.Never", + SpecialFormType::Tuple => "typing.Tuple", + SpecialFormType::Type => "typing.Type", + SpecialFormType::TypingSelf => "typing.Self", + SpecialFormType::Final => "typing.Final", + SpecialFormType::ClassVar => "typing.ClassVar", + SpecialFormType::Callable => "typing.Callable", + SpecialFormType::Concatenate => "typing.Concatenate", + SpecialFormType::Unpack => "typing.Unpack", + SpecialFormType::Required => "typing.Required", + SpecialFormType::NotRequired => "typing.NotRequired", + SpecialFormType::TypeAlias => "typing.TypeAlias", + SpecialFormType::TypeGuard => "typing.TypeGuard", + SpecialFormType::TypedDict => "typing.TypedDict", + SpecialFormType::TypeIs => "typing.TypeIs", + SpecialFormType::List => "typing.List", + SpecialFormType::Dict => "typing.Dict", + SpecialFormType::DefaultDict => "typing.DefaultDict", + SpecialFormType::Set => "typing.Set", + SpecialFormType::FrozenSet => "typing.FrozenSet", + SpecialFormType::Counter => "typing.Counter", + SpecialFormType::Deque => "typing.Deque", + SpecialFormType::ChainMap => "typing.ChainMap", + SpecialFormType::OrderedDict => "typing.OrderedDict", + SpecialFormType::ReadOnly => "typing.ReadOnly", + SpecialFormType::Unknown => "ty_extensions.Unknown", + SpecialFormType::AlwaysTruthy => "ty_extensions.AlwaysTruthy", + SpecialFormType::AlwaysFalsy => "ty_extensions.AlwaysFalsy", + SpecialFormType::Not => "ty_extensions.Not", + SpecialFormType::Intersection => "ty_extensions.Intersection", + SpecialFormType::TypeOf => "ty_extensions.TypeOf", + SpecialFormType::CallableTypeOf => "ty_extensions.CallableTypeOf", + SpecialFormType::Protocol => "typing.Protocol", + SpecialFormType::Generic => "typing.Generic", + } + } +} + +impl std::fmt::Display for SpecialFormType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.repr()) + } +} diff --git a/crates/ty_python_semantic/src/types/type_ordering.rs b/crates/ty_python_semantic/src/types/type_ordering.rs index cb2b309f63..669d116fd9 100644 --- a/crates/ty_python_semantic/src/types/type_ordering.rs +++ b/crates/ty_python_semantic/src/types/type_ordering.rs @@ -179,10 +179,11 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( (Type::BoundSuper(_), _) => Ordering::Less, (_, Type::BoundSuper(_)) => Ordering::Greater, - (Type::KnownInstance(left_instance), Type::KnownInstance(right_instance)) => { - left_instance.cmp(right_instance) - } + (Type::SpecialForm(left), Type::SpecialForm(right)) => left.cmp(right), + (Type::SpecialForm(_), _) => Ordering::Less, + (_, Type::SpecialForm(_)) => Ordering::Greater, + (Type::KnownInstance(left), Type::KnownInstance(right)) => left.cmp(right), (Type::KnownInstance(_), _) => Ordering::Less, (_, Type::KnownInstance(_)) => Ordering::Greater,