diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 30fe995231..731d08c645 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#L92) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L93) ## `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#L136) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L137) ## `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#L162) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L163) ## `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#L187) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L188) ## `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#L213) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L214) ## `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#L257) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L258) ## `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#L278) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279) ## `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#L364) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365) ## `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#L388) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L389) ## `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#L408) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L409) ## `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#L448) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449) ## `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#L1396) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397) ## `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#L470) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471) ## `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#L521) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522) ## `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#L542) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L543) ## `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#L565) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566) ## `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#L601) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L602) ## `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#L627) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628) ## `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#L676) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677) ## `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#L703) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L704) ## `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#L746) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L747) ## `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#L336) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L337) ## `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#L766) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L767) ## `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#L429) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L430) ## `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#L809) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L810) ## `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#L655) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L656) ## `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#L848) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L849) ## `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#L872) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L873) ## `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#L896) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L897) ## `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#L925) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L926) ## `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#L944) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L945) ## `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#L967) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L968) ## `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#L985) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L986) ## `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#L1036) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1037) ## `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#L1372) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1373) ## `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#L1127) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1128) ## `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#L1172) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1173) ## `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#L1150) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151) ## `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#L1193) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1194) ## `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#L1250) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1251) ## `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#L1271) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272) ## `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#L1293) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294) ## `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#L1312) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1313) ## `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#L1005) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1006) ## `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#L1331) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1332) ## `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#L1353) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1354) ## `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#L1057) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1058) ## `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#L110) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L111) ## `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#L1079) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1080) ## `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#L1424) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1425) ## `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#L1232) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233) ## `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#L488) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L489) ## `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#L239) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L240) ## `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#L1105) +* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106) ## `unused-ignore-comment` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index dc5450245f..aec03a73a6 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -2,7 +2,6 @@ use infer::nearest_enclosing_class; use itertools::Either; use std::slice::Iter; -use std::str::FromStr; use bitflags::bitflags; use call::{CallDunderError, CallError, CallErrorKind}; @@ -14,7 +13,7 @@ use diagnostic::{ use ruff_db::diagnostic::{ Annotation, Severity, Span, SubDiagnostic, create_semantic_syntax_diagnostic, }; -use ruff_db::files::{File, FileRange}; +use ruff_db::files::File; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, AnyNodeRef}; use ruff_text_size::{Ranged, TextRange}; @@ -28,23 +27,23 @@ pub(crate) use self::infer::{ infer_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types, infer_scope_types, }; -pub(crate) use self::narrow::ClassInfoConstraintFunction; pub(crate) use self::signatures::{CallableSignature, Signature}; pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType}; use crate::module_name::ModuleName; -use crate::module_resolver::{KnownModule, file_to_module, resolve_module}; -use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId}; +use crate::module_resolver::{KnownModule, resolve_module}; +use crate::semantic_index::ast_ids::HasScopedExpressionId; use crate::semantic_index::definition::Definition; use crate::semantic_index::symbol::ScopeId; use crate::semantic_index::{imported_modules, semantic_index}; use crate::suppression::check_suppressions; -use crate::symbol::{ - Boundness, Symbol, SymbolAndQualifiers, imported_symbol, symbol_from_bindings, -}; +use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers, imported_symbol}; use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; +use crate::types::function::{ + DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, +}; use crate::types::generics::{GenericContext, PartialSpecialization, Specialization}; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; @@ -64,6 +63,7 @@ mod class_base; mod context; mod diagnostic; mod display; +mod function; mod generics; mod ide_support; mod infer; @@ -432,26 +432,6 @@ impl From for DataclassParams { } } -bitflags! { - /// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the - /// arguments that were passed in. For the precise meaning of the fields, see [1]. - /// - /// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] - pub struct DataclassTransformerParams: u8 { - const EQ_DEFAULT = 0b0000_0001; - const ORDER_DEFAULT = 0b0000_0010; - const KW_ONLY_DEFAULT = 0b0000_0100; - const FROZEN_DEFAULT = 0b0000_1000; - } -} - -impl Default for DataclassTransformerParams { - fn default() -> Self { - Self::EQ_DEFAULT - } -} - /// Representation of a type: a set of possible values at runtime. /// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] @@ -7040,710 +7020,6 @@ impl From for Truthiness { } } -bitflags! { - #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)] - pub struct FunctionDecorators: u8 { - /// `@classmethod` - const CLASSMETHOD = 1 << 0; - /// `@typing.no_type_check` - const NO_TYPE_CHECK = 1 << 1; - /// `@typing.overload` - const OVERLOAD = 1 << 2; - /// `@abc.abstractmethod` - const ABSTRACT_METHOD = 1 << 3; - /// `@typing.final` - const FINAL = 1 << 4; - /// `@typing.override` - const OVERRIDE = 1 << 6; - } -} - -/// A function signature, which optionally includes an implementation signature if the function is -/// overloaded. -#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] -pub(crate) struct FunctionSignature<'db> { - pub(crate) overloads: CallableSignature<'db>, - pub(crate) implementation: Option>, -} - -impl<'db> FunctionSignature<'db> { - /// Returns the "bottom" signature (subtype of all fully-static signatures.) - pub(crate) fn bottom(db: &'db dyn Db) -> Self { - FunctionSignature { - overloads: CallableSignature::single(Signature::bottom(db)), - implementation: None, - } - } -} - -/// An overloaded function. -/// -/// This is created by the [`to_overloaded`] method on [`FunctionType`]. -/// -/// [`to_overloaded`]: FunctionType::to_overloaded -#[derive(Debug, PartialEq, Eq, salsa::Update)] -struct OverloadedFunction<'db> { - /// The overloads of this function. - overloads: Vec>, - /// The implementation of this overloaded function, if any. - implementation: Option>, -} - -impl<'db> OverloadedFunction<'db> { - /// Returns an iterator over all overloads and the implementation, in that order. - fn all(&self) -> impl Iterator> + '_ { - self.overloads.iter().copied().chain(self.implementation) - } -} - -/// # Ordering -/// Ordering is based on the function type's salsa-assigned id and not on its values. -/// The id may change between runs, or when the function type was garbage collected and recreated. -#[salsa::interned(debug)] -#[derive(PartialOrd, Ord)] -pub struct FunctionType<'db> { - /// Name of the function at definition. - #[returns(ref)] - pub name: ast::name::Name, - - /// Is this a function that we special-case somehow? If so, which one? - known: Option, - - /// The scope that's created by the function, in which the function body is evaluated. - body_scope: ScopeId<'db>, - - /// A set of special decorators that were applied to this function - decorators: FunctionDecorators, - - /// The arguments to `dataclass_transformer`, if this function was annotated - /// with `@dataclass_transformer(...)`. - dataclass_transformer_params: Option, - - /// The inherited generic context, if this function is a class method being used to infer the - /// specialization of its generic class. If the method is itself generic, this is in addition - /// to its own generic context. - inherited_generic_context: Option>, - - /// Type mappings that should be applied to the function's parameter and return types. - type_mappings: Box<[TypeMapping<'db, 'db>]>, -} - -#[salsa::tracked] -impl<'db> FunctionType<'db> { - /// Returns the [`File`] in which this function is defined. - pub(crate) fn file(self, db: &'db dyn Db) -> File { - // NOTE: Do not use `self.definition(db).file(db)` here, as that could create a - // cross-module dependency on the full AST. - self.body_scope(db).file(db) - } - - pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { - self.decorators(db).contains(decorator) - } - - /// Convert the `FunctionType` into a [`Type::Callable`]. - pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { - Type::Callable(CallableType::new( - db, - self.signature(db).overloads.clone(), - false, - )) - } - - /// Convert the `FunctionType` into a [`Type::BoundMethod`]. - pub(crate) fn into_bound_method_type( - self, - db: &'db dyn Db, - self_instance: Type<'db>, - ) -> Type<'db> { - Type::BoundMethod(BoundMethodType::new(db, self, self_instance)) - } - - /// Returns the AST node for this function. - pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { - debug_assert_eq!( - file, - self.file(db), - "FunctionType::node() must be called with the same file as the one where \ - the function is defined." - ); - - self.body_scope(db).node(db).expect_function() - } - - /// Returns the [`FileRange`] of the function's name. - pub fn focus_range(self, db: &dyn Db) -> FileRange { - FileRange::new( - self.file(db), - self.body_scope(db).node(db).expect_function().name.range, - ) - } - - pub fn full_range(self, db: &dyn Db) -> FileRange { - FileRange::new( - self.file(db), - self.body_scope(db).node(db).expect_function().range, - ) - } - - /// Returns the [`Definition`] of this function. - /// - /// ## Warning - /// - /// This uses the semantic index to find the definition of the function. This means that if the - /// calling query is not in the same file as this function is defined in, then this will create - /// a cross-module dependency directly on the full AST which will lead to cache - /// over-invalidation. - pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { - let body_scope = self.body_scope(db); - let index = semantic_index(db, body_scope.file(db)); - index.expect_single_definition(body_scope.node(db).expect_function()) - } - - /// Typed externally-visible signature for this function. - /// - /// This is the signature as seen by external callers, possibly modified by decorators and/or - /// overloaded. - /// - /// ## Why is this a salsa query? - /// - /// This is a salsa query to short-circuit the invalidation - /// when the function's AST node changes. - /// - /// Were this not a salsa query, then the calling query - /// would depend on the function's AST and rerun for every change in that file. - #[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)] - pub(crate) fn signature(self, db: &'db dyn Db) -> FunctionSignature<'db> { - let inherited_generic_context = self.inherited_generic_context(db); - let type_mappings = self.type_mappings(db); - if let Some(overloaded) = self.to_overloaded(db) { - FunctionSignature { - overloads: CallableSignature::from_overloads( - overloaded.overloads.iter().copied().map(|overload| { - type_mappings.iter().fold( - overload.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - ) - }), - ), - implementation: overloaded.implementation.map(|implementation| { - type_mappings.iter().fold( - implementation.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - ) - }), - } - } else { - FunctionSignature { - overloads: CallableSignature::single(type_mappings.iter().fold( - self.internal_signature(db, inherited_generic_context), - |ty, mapping| ty.apply_type_mapping(db, mapping), - )), - implementation: None, - } - } - } - - /// Typed internally-visible signature for this function. - /// - /// This represents the annotations on the function itself, unmodified by decorators and - /// overloads. - /// - /// These are the parameter and return types that should be used for type checking the body of - /// the function. - /// - /// Don't call this when checking any other file; only when type-checking the function body - /// scope. - fn internal_signature( - self, - db: &'db dyn Db, - inherited_generic_context: Option>, - ) -> Signature<'db> { - let scope = self.body_scope(db); - let function_stmt_node = scope.node(db).expect_function(); - let definition = self.definition(db); - let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| { - let index = semantic_index(db, scope.file(db)); - GenericContext::from_type_params(db, index, type_params) - }); - Signature::from_function( - db, - generic_context, - inherited_generic_context, - definition, - function_stmt_node, - ) - } - - pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool { - self.known(db) == Some(known_function) - } - - fn with_dataclass_transformer_params( - self, - db: &'db dyn Db, - params: DataclassTransformerParams, - ) -> Self { - Self::new( - db, - self.name(db).clone(), - self.known(db), - self.body_scope(db), - self.decorators(db), - Some(params), - self.inherited_generic_context(db), - self.type_mappings(db), - ) - } - - fn with_inherited_generic_context( - self, - db: &'db dyn Db, - inherited_generic_context: GenericContext<'db>, - ) -> Self { - // A function cannot inherit more than one generic context from its containing class. - debug_assert!(self.inherited_generic_context(db).is_none()); - Self::new( - db, - self.name(db).clone(), - self.known(db), - self.body_scope(db), - self.decorators(db), - self.dataclass_transformer_params(db), - Some(inherited_generic_context), - self.type_mappings(db), - ) - } - - fn with_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { - let type_mappings: Box<[_]> = self - .type_mappings(db) - .iter() - .cloned() - .chain(std::iter::once(type_mapping.to_owned())) - .collect(); - Self::new( - db, - self.name(db).clone(), - self.known(db), - self.body_scope(db), - self.decorators(db), - self.dataclass_transformer_params(db), - self.inherited_generic_context(db), - type_mappings, - ) - } - - fn find_legacy_typevars( - self, - db: &'db dyn Db, - typevars: &mut FxOrderSet>, - ) { - let signatures = self.signature(db); - for signature in &signatures.overloads { - signature.find_legacy_typevars(db, typevars); - } - } - - /// Returns `self` as [`OverloadedFunction`] if it is overloaded, [`None`] otherwise. - /// - /// ## Note - /// - /// The way this method works only allows us to "see" the overloads that are defined before - /// this function definition. This is because the semantic model records a use for each - /// function on the name node which is used to get the previous function definition with the - /// same name. This means that [`OverloadedFunction`] would only include the functions that - /// comes before this function definition. Consider the following example: - /// - /// ```py - /// from typing import overload - /// - /// @overload - /// def foo() -> None: ... - /// @overload - /// def foo(x: int) -> int: ... - /// def foo(x: int | None) -> int | None: - /// return x - /// ``` - /// - /// Here, when the `to_overloaded` method is invoked on the - /// 1. first `foo` definition, it would only contain a single overload which is itself and no - /// implementation - /// 2. second `foo` definition, it would contain both overloads and still no implementation - /// 3. third `foo` definition, it would contain both overloads and the implementation which is - /// itself - #[salsa::tracked(returns(as_ref))] - fn to_overloaded(self, db: &'db dyn Db) -> Option> { - let mut current = self; - let mut overloads = vec![]; - - loop { - // The semantic model records a use for each function on the name node. This is used - // here to get the previous function definition with the same name. - let scope = current.definition(db).scope(db); - let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db)); - let use_id = current - .body_scope(db) - .node(db) - .expect_function() - .name - .scoped_use_id(db, scope); - - let Symbol::Type(Type::FunctionLiteral(previous), Boundness::Bound) = - symbol_from_bindings(db, use_def.bindings_at_use(use_id)) - else { - break; - }; - - if previous.has_known_decorator(db, FunctionDecorators::OVERLOAD) { - overloads.push(previous); - } else { - break; - } - - current = previous; - } - - // Overloads are inserted in reverse order, from bottom to top. - overloads.reverse(); - - let implementation = if self.has_known_decorator(db, FunctionDecorators::OVERLOAD) { - overloads.push(self); - None - } else { - Some(self) - }; - - if overloads.is_empty() { - None - } else { - Some(OverloadedFunction { - overloads, - implementation, - }) - } - } - - fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { - // A function literal is the subtype of itself, and not of any other function literal. - // However, our representation of a function literal includes any specialization that - // should be applied to the signature. Different specializations of the same function - // literal are only subtypes of each other if they result in subtype signatures. - self.normalized(db) == other.normalized(db) - || (self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_subtype_of(db, other.into_callable_type(db))) - } - - fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { - // A function literal is assignable to itself, and not to any other function literal. - // However, our representation of a function literal includes any specialization that - // should be applied to the signature. Different specializations of the same function - // literal are only assignable to each other if they result in assignable signatures. - self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_assignable_to(db, other.into_callable_type(db)) - } - - fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - self.normalized(db) == other.normalized(db) - || (self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_equivalent_to(db, other.into_callable_type(db))) - } - - fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { - self.body_scope(db) == other.body_scope(db) - && self - .into_callable_type(db) - .is_gradual_equivalent_to(db, other.into_callable_type(db)) - } - - fn normalized(self, db: &'db dyn Db) -> Self { - let context = self - .inherited_generic_context(db) - .map(|ctx| ctx.normalized(db)); - - let mappings: Box<_> = self - .type_mappings(db) - .iter() - .map(|mapping| mapping.normalized(db)) - .collect(); - - Self::new( - db, - self.name(db), - self.known(db), - self.body_scope(db), - self.decorators(db), - self.dataclass_transformer_params(db), - context, - mappings, - ) - } - - /// Returns a tuple of two spans. The first is - /// the span for the identifier of the function - /// definition for `self`. The second is - /// the span for the parameter in the function - /// definition for `self`. - /// - /// If there are no meaningful spans, then this - /// returns `None`. For example, when this type - /// isn't callable. - /// - /// When `parameter_index` is `None`, then the - /// second span returned covers the entire parameter - /// list. - /// - /// # Performance - /// - /// Note that this may introduce cross-module - /// dependencies. This can have an impact on - /// the effectiveness of incremental caching - /// and should therefore be used judiciously. - /// - /// An example of a good use case is to improve - /// a diagnostic. - fn parameter_span( - self, - db: &'db dyn Db, - parameter_index: Option, - ) -> Option<(Span, Span)> { - let function_scope = self.body_scope(db); - let span = Span::from(function_scope.file(db)); - let node = function_scope.node(db); - let func_def = node.as_function()?; - let range = parameter_index - .and_then(|parameter_index| { - func_def - .parameters - .iter() - .nth(parameter_index) - .map(|param| param.range()) - }) - .unwrap_or(func_def.parameters.range); - let name_span = span.clone().with_range(func_def.name.range); - let parameter_span = span.with_range(range); - Some((name_span, parameter_span)) - } - - /// Returns a collection of useful spans for a - /// function signature. These are useful for - /// creating annotations on diagnostics. - /// - /// # Performance - /// - /// Note that this may introduce cross-module - /// dependencies. This can have an impact on - /// the effectiveness of incremental caching - /// and should therefore be used judiciously. - /// - /// An example of a good use case is to improve - /// a diagnostic. - fn spans(self, db: &'db dyn Db) -> Option { - let function_scope = self.body_scope(db); - let span = Span::from(function_scope.file(db)); - let node = function_scope.node(db); - let func_def = node.as_function()?; - let return_type_range = func_def.returns.as_ref().map(|returns| returns.range()); - let mut signature = func_def.name.range.cover(func_def.parameters.range); - if let Some(return_type_range) = return_type_range { - signature = signature.cover(return_type_range); - } - Some(FunctionSpans { - signature: span.clone().with_range(signature), - name: span.clone().with_range(func_def.name.range), - parameters: span.clone().with_range(func_def.parameters.range), - return_type: return_type_range.map(|range| span.clone().with_range(range)), - }) - } -} - -/// A collection of useful spans for annotating functions. -/// -/// This can be retrieved via `FunctionType::spans` or -/// `Type::function_spans`. -struct FunctionSpans { - /// The span of the entire function "signature." This includes - /// the name, parameter list and return type (if present). - signature: Span, - /// The span of the function name. i.e., `foo` in `def foo(): ...`. - name: Span, - /// The span of the parameter list, including the opening and - /// closing parentheses. - #[expect(dead_code)] - parameters: Span, - /// The span of the annotated return type, if present. - return_type: Option, -} - -fn signature_cycle_recover<'db>( - _db: &'db dyn Db, - _value: &FunctionSignature<'db>, - _count: u32, - _function: FunctionType<'db>, -) -> salsa::CycleRecoveryAction> { - salsa::CycleRecoveryAction::Iterate -} - -fn signature_cycle_initial<'db>( - db: &'db dyn Db, - _function: FunctionType<'db>, -) -> FunctionSignature<'db> { - FunctionSignature::bottom(db) -} - -/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might -/// have special behavior. -#[derive( - Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::EnumString, strum_macros::IntoStaticStr, -)] -#[strum(serialize_all = "snake_case")] -#[cfg_attr(test, derive(strum_macros::EnumIter))] -pub enum KnownFunction { - /// `builtins.isinstance` - #[strum(serialize = "isinstance")] - IsInstance, - /// `builtins.issubclass` - #[strum(serialize = "issubclass")] - IsSubclass, - /// `builtins.hasattr` - #[strum(serialize = "hasattr")] - HasAttr, - /// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type` - RevealType, - /// `builtins.len` - Len, - /// `builtins.repr` - Repr, - /// `typing(_extensions).final` - Final, - - /// [`typing(_extensions).no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check) - NoTypeCheck, - - /// `typing(_extensions).assert_type` - AssertType, - /// `typing(_extensions).assert_never` - AssertNever, - /// `typing(_extensions).cast` - Cast, - /// `typing(_extensions).overload` - Overload, - /// `typing(_extensions).override` - Override, - /// `typing(_extensions).is_protocol` - IsProtocol, - /// `typing(_extensions).get_protocol_members` - GetProtocolMembers, - /// `typing(_extensions).runtime_checkable` - RuntimeCheckable, - /// `typing(_extensions).dataclass_transform` - DataclassTransform, - - /// `abc.abstractmethod` - #[strum(serialize = "abstractmethod")] - AbstractMethod, - - /// `dataclasses.dataclass` - Dataclass, - - /// `inspect.getattr_static` - GetattrStatic, - - /// `ty_extensions.static_assert` - StaticAssert, - /// `ty_extensions.is_equivalent_to` - IsEquivalentTo, - /// `ty_extensions.is_subtype_of` - IsSubtypeOf, - /// `ty_extensions.is_assignable_to` - IsAssignableTo, - /// `ty_extensions.is_disjoint_from` - IsDisjointFrom, - /// `ty_extensions.is_gradual_equivalent_to` - IsGradualEquivalentTo, - /// `ty_extensions.is_fully_static` - IsFullyStatic, - /// `ty_extensions.is_singleton` - IsSingleton, - /// `ty_extensions.is_single_valued` - IsSingleValued, - /// `ty_extensions.generic_context` - GenericContext, - /// `ty_extensions.dunder_all_names` - DunderAllNames, - /// `ty_extensions.all_members` - AllMembers, -} - -impl KnownFunction { - pub fn into_classinfo_constraint_function(self) -> Option { - match self { - Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance), - Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass), - _ => None, - } - } - - fn try_from_definition_and_name<'db>( - db: &'db dyn Db, - definition: Definition<'db>, - name: &str, - ) -> Option { - let candidate = Self::from_str(name).ok()?; - candidate - .check_module(file_to_module(db, definition.file(db))?.known()?) - .then_some(candidate) - } - - /// Return `true` if `self` is defined in `module` at runtime. - const fn check_module(self, module: KnownModule) -> bool { - match self { - Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => { - module.is_builtins() - } - Self::AssertType - | Self::AssertNever - | Self::Cast - | Self::Overload - | Self::Override - | Self::RevealType - | Self::Final - | Self::IsProtocol - | Self::GetProtocolMembers - | Self::RuntimeCheckable - | Self::DataclassTransform - | Self::NoTypeCheck => { - matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) - } - Self::AbstractMethod => { - matches!(module, KnownModule::Abc) - } - Self::Dataclass => { - matches!(module, KnownModule::Dataclasses) - } - Self::GetattrStatic => module.is_inspect(), - Self::IsAssignableTo - | Self::IsDisjointFrom - | Self::IsEquivalentTo - | Self::IsGradualEquivalentTo - | Self::IsFullyStatic - | Self::IsSingleValued - | Self::IsSingleton - | Self::IsSubtypeOf - | Self::GenericContext - | Self::DunderAllNames - | Self::StaticAssert - | Self::AllMembers => module.is_ty_extensions(), - } - } -} - /// This type represents bound method objects that are created when a method is accessed /// on an instance of a class. For example, the expression `Path("a.txt").touch` creates /// a bound method object that represents the `Path.touch` method which is bound to the @@ -9213,15 +8489,12 @@ static_assertions::assert_eq_size!(Type, [u8; 16]); pub(crate) mod tests { use super::*; use crate::db::tests::{TestDbBuilder, setup_db}; - use crate::symbol::{ - global_symbol, known_module_symbol, typing_extensions_symbol, typing_symbol, - }; + use crate::symbol::{global_symbol, typing_extensions_symbol, typing_symbol}; use ruff_db::files::system_path_to_file; use ruff_db::parsed::parsed_module; use ruff_db::system::DbWithWritableSystem as _; use ruff_db::testing::assert_function_query_was_not_run; use ruff_python_ast::PythonVersion; - use strum::IntoEnumIterator; use test_case::test_case; /// Explicitly test for Python version <3.13 and >=3.13, to ensure that @@ -9364,69 +8637,4 @@ pub(crate) mod tests { .is_todo() ); } - - #[test] - fn known_function_roundtrip_from_str() { - let db = setup_db(); - - for function in KnownFunction::iter() { - let function_name: &'static str = function.into(); - - let module = match function { - KnownFunction::Len - | KnownFunction::Repr - | KnownFunction::IsInstance - | KnownFunction::HasAttr - | KnownFunction::IsSubclass => KnownModule::Builtins, - - KnownFunction::AbstractMethod => KnownModule::Abc, - - KnownFunction::Dataclass => KnownModule::Dataclasses, - - KnownFunction::GetattrStatic => KnownModule::Inspect, - - KnownFunction::Cast - | KnownFunction::Final - | KnownFunction::Overload - | KnownFunction::Override - | KnownFunction::RevealType - | KnownFunction::AssertType - | KnownFunction::AssertNever - | KnownFunction::IsProtocol - | KnownFunction::GetProtocolMembers - | KnownFunction::RuntimeCheckable - | KnownFunction::DataclassTransform - | KnownFunction::NoTypeCheck => KnownModule::TypingExtensions, - - KnownFunction::IsSingleton - | KnownFunction::IsSubtypeOf - | KnownFunction::GenericContext - | KnownFunction::DunderAllNames - | KnownFunction::StaticAssert - | KnownFunction::IsFullyStatic - | KnownFunction::IsDisjointFrom - | KnownFunction::IsSingleValued - | KnownFunction::IsAssignableTo - | KnownFunction::IsEquivalentTo - | KnownFunction::IsGradualEquivalentTo - | KnownFunction::AllMembers => KnownModule::TyExtensions, - }; - - let function_definition = known_module_symbol(&db, module, function_name) - .symbol - .expect_type() - .expect_function_literal() - .definition(&db); - - assert_eq!( - KnownFunction::try_from_definition_and_name( - &db, - function_definition, - function_name - ), - Some(function), - "The strum `EnumString` implementation appears to be incorrect for `{function_name}`" - ); - } - } } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 51ab005c1b..b49343c12d 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -18,13 +18,13 @@ use crate::types::diagnostic::{ NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS, UNKNOWN_ARGUMENT, }; +use crate::types::function::{DataclassTransformerParams, FunctionDecorators, KnownFunction}; use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError}; use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ - BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType, - KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, - SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, ide_support, - todo_type, + BoundMethodType, DataclassParams, KnownClass, KnownInstanceType, MethodWrapperKind, + PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType, + WrapperDescriptorKind, ide_support, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -871,47 +871,47 @@ impl<'db> Bindings<'db> { } _ => { - let mut handle_dataclass_transformer_params = - |function_type: &FunctionType| { - if let Some(params) = - function_type.dataclass_transformer_params(db) - { - // This is a call to a custom function that was decorated with `@dataclass_transformer`. - // If this function was called with a keyword argument like `order=False`, we extract - // the argument type and overwrite the corresponding flag in `dataclass_params` after - // constructing them from the `dataclass_transformer`-parameter defaults. - - let mut dataclass_params = DataclassParams::from(params); - - if let Some(Some(Type::BooleanLiteral(order))) = overload - .signature - .parameters() - .keyword_by_name("order") - .map(|(idx, _)| idx) - .and_then(|idx| overload.parameter_types().get(idx)) - { - dataclass_params.set(DataclassParams::ORDER, *order); - } - - overload.set_return_type(Type::DataclassDecorator( - dataclass_params, - )); - } - }; - // Ideally, either the implementation, or exactly one of the overloads // of the function can have the dataclass_transform decorator applied. // However, we do not yet enforce this, and in the case of multiple // applications of the decorator, we will only consider the last one // for the return value, since the prior ones will be over-written. - if let Some(overloaded) = function_type.to_overloaded(db) { - overloaded - .overloads - .iter() - .for_each(&mut handle_dataclass_transformer_params); - } + let return_type = function_type + .iter_overloads_and_implementation(db) + .filter_map(|function_overload| { + function_overload.dataclass_transformer_params(db).map( + |params| { + // This is a call to a custom function that was decorated with `@dataclass_transformer`. + // If this function was called with a keyword argument like `order=False`, we extract + // the argument type and overwrite the corresponding flag in `dataclass_params` after + // constructing them from the `dataclass_transformer`-parameter defaults. - handle_dataclass_transformer_params(&function_type); + let mut dataclass_params = + DataclassParams::from(params); + + if let Some(Some(Type::BooleanLiteral(order))) = + overload + .signature + .parameters() + .keyword_by_name("order") + .map(|(idx, _)| idx) + .and_then(|idx| { + overload.parameter_types().get(idx) + }) + { + dataclass_params + .set(DataclassParams::ORDER, *order); + } + + Type::DataclassDecorator(dataclass_params) + }, + ) + }) + .last(); + + if let Some(return_type) = return_type { + overload.set_return_type(return_type); + } } }, @@ -1261,47 +1261,49 @@ impl<'db> CallableBinding<'db> { _ => None, }; if let Some((kind, function)) = function_type_and_kind { - if let Some(overloaded_function) = function.to_overloaded(context.db()) { - if let Some(spans) = overloaded_function - .overloads - .first() - .and_then(|overload| overload.spans(context.db())) - { - let mut sub = - SubDiagnostic::new(Severity::Info, "First overload defined here"); - sub.annotate(Annotation::primary(spans.signature)); - diag.sub(sub); - } + let (overloads, implementation) = + function.overloads_and_implementation(context.db()); + if let Some(spans) = overloads + .first() + .and_then(|overload| overload.spans(context.db())) + { + let mut sub = + SubDiagnostic::new(Severity::Info, "First overload defined here"); + sub.annotate(Annotation::primary(spans.signature)); + diag.sub(sub); + } + + diag.info(format_args!( + "Possible overloads for {kind} `{}`:", + function.name(context.db()) + )); + + for overload in overloads.iter().take(MAXIMUM_OVERLOADS) { diag.info(format_args!( - "Possible overloads for {kind} `{}`:", - function.name(context.db()) + " {}", + overload.signature(context.db(), None).display(context.db()) )); + } + if overloads.len() > MAXIMUM_OVERLOADS { + diag.info(format_args!( + "... omitted {remaining} overloads", + remaining = overloads.len() - MAXIMUM_OVERLOADS + )); + } - let overloads = &function.signature(context.db()).overloads.overloads; - for overload in overloads.iter().take(MAXIMUM_OVERLOADS) { - diag.info(format_args!(" {}", overload.display(context.db()))); - } - if overloads.len() > MAXIMUM_OVERLOADS { - diag.info(format_args!( - "... omitted {remaining} overloads", - remaining = overloads.len() - MAXIMUM_OVERLOADS - )); - } - - if let Some(spans) = overloaded_function - .implementation - .and_then(|function| function.spans(context.db())) - { - let mut sub = SubDiagnostic::new( - Severity::Info, - "Overload implementation defined here", - ); - sub.annotate(Annotation::primary(spans.signature)); - diag.sub(sub); - } + if let Some(spans) = + implementation.and_then(|function| function.spans(context.db())) + { + let mut sub = SubDiagnostic::new( + Severity::Info, + "Overload implementation defined here", + ); + sub.annotate(Annotation::primary(spans.signature)); + diag.sub(sub); } } + if let Some(union_diag) = union_diag { union_diag.add_union_context(context.db(), &mut diag); } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 94158e653c..bcf6c6e1b7 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2,17 +2,17 @@ use std::hash::BuildHasherDefault; use std::sync::{LazyLock, Mutex}; use super::{ - IntersectionBuilder, KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator, - SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, - infer_expression_type, infer_unpack_types, + IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, 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; +use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ - CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping, - TypeVarInstance, + CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeVarInstance, }; use crate::{ Db, FxOrderSet, KnownModule, Program, diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index d8004122cc..f0596b0318 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -11,13 +11,14 @@ use ruff_text_size::{Ranged, TextRange}; use super::{Type, TypeCheckDiagnostics, binding_type}; use crate::lint::LintSource; +use crate::semantic_index::semantic_index; use crate::semantic_index::symbol::ScopeId; +use crate::types::function::FunctionDecorators; use crate::{ Db, lint::{LintId, LintMetadata}, suppression::suppressions, }; -use crate::{semantic_index::semantic_index, types::FunctionDecorators}; /// Context for inferring the types of a single file. /// diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 1a0b13ef21..39277b142b 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -8,12 +8,13 @@ use super::{ use crate::lint::{Level, LintRegistryBuilder, LintStatus}; use crate::suppression::FileSuppressionId; use crate::types::LintDiagnosticGuard; +use crate::types::function::KnownFunction; use crate::types::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION, IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION, RAW_STRING_TYPE_ANNOTATION, }; -use crate::types::{KnownFunction, SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; +use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; use crate::{Db, Module, ModuleName, Program, declare_lint}; use itertools::Itertools; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index 6c4dd480d0..645a102e84 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -7,6 +7,7 @@ use ruff_python_ast::str::{Quote, TripleQuotes}; use ruff_python_literal::escape::AsciiEscape; use crate::types::class::{ClassLiteral, ClassType, GenericAlias}; +use crate::types::function::{FunctionType, OverloadLiteral}; use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; use crate::types::{ @@ -112,34 +113,7 @@ impl Display for DisplayRepresentation<'_> { }, 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); - - // TODO: when generic function types are supported, we should add - // the generic type parameters to the signature, i.e. - // show `def foo[T](x: T) -> T`. - - match signature.overloads.as_slice() { - [signature] => { - write!( - f, - // "def {name}{specialization}{signature}", - "def {name}{signature}", - name = function.name(self.db), - signature = signature.display(self.db) - ) - } - signatures => { - // TODO: How to display overloads? - f.write_str("Overload[")?; - let mut join = f.join(", "); - for signature in signatures { - join.entry(&signature.display(self.db)); - } - f.write_str("]") - } - } - } + Type::FunctionLiteral(function) => function.display(self.db).fmt(f), Type::Callable(callable) => callable.display(self.db).fmt(f), Type::BoundMethod(bound_method) => { let function = bound_method.function(self.db); @@ -241,6 +215,71 @@ impl Display for DisplayRepresentation<'_> { } } +impl<'db> OverloadLiteral<'db> { + // Not currently used, but useful for debugging. + #[expect(dead_code)] + pub(crate) fn display(self, db: &'db dyn Db) -> DisplayOverloadLiteral<'db> { + DisplayOverloadLiteral { literal: self, db } + } +} + +pub(crate) struct DisplayOverloadLiteral<'db> { + literal: OverloadLiteral<'db>, + db: &'db dyn Db, +} + +impl Display for DisplayOverloadLiteral<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let signature = self.literal.signature(self.db, None); + write!( + f, + "def {name}{signature}", + name = self.literal.name(self.db), + signature = signature.display(self.db) + ) + } +} + +impl<'db> FunctionType<'db> { + pub(crate) fn display(self, db: &'db dyn Db) -> DisplayFunctionType<'db> { + DisplayFunctionType { ty: self, db } + } +} + +pub(crate) struct DisplayFunctionType<'db> { + ty: FunctionType<'db>, + db: &'db dyn Db, +} + +impl Display for DisplayFunctionType<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let signature = self.ty.signature(self.db); + + // TODO: We should consider adding the type parameters to the signature of a generic + // function, i.e. `def foo[T](x: T) -> T`. + + match signature.overloads.as_slice() { + [signature] => { + write!( + f, + "def {name}{signature}", + name = self.ty.name(self.db), + signature = signature.display(self.db) + ) + } + signatures => { + // TODO: How to display overloads? + f.write_str("Overload[")?; + let mut join = f.join(", "); + for signature in signatures { + join.entry(&signature.display(self.db)); + } + f.write_str("]") + } + } + } +} + impl<'db> GenericAlias<'db> { pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> { DisplayGenericAlias { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs new file mode 100644 index 0000000000..2911fbb1d6 --- /dev/null +++ b/crates/ty_python_semantic/src/types/function.rs @@ -0,0 +1,996 @@ +//! Contains representations of function literals. There are several complicating factors: +//! +//! - Functions can be generic, and can have specializations applied to them. These are not the +//! same thing! For instance, a method of a generic class might not itself be generic, but it can +//! still have the class's specialization applied to it. +//! +//! - Functions can be overloaded, and each overload can be independently generic or not, with +//! different sets of typevars for different generic overloads. In some cases we need to consider +//! each overload separately; in others we need to consider all of the overloads (and any +//! implementation) as a single collective entity. +//! +//! - Certain “known” functions need special treatment — for instance, inferring a special return +//! type, or raising custom diagnostics. +//! +//! - TODO: Some functions don't correspond to a function definition in the AST, and are instead +//! synthesized as we mimic the behavior of the Python interpreter. Even though they are +//! synthesized, and are “implemented” as Rust code, they are still functions from the POV of the +//! rest of the type system. +//! +//! Given these constraints, we have the following representation: a function is a list of one or +//! more overloads, with zero or more specializations (more specifically, “type mappings”) applied +//! to it. [`FunctionType`] is the outermost type, which is what [`Type::FunctionLiteral`] wraps. +//! It contains the list of type mappings to apply. It wraps a [`FunctionLiteral`], which collects +//! together all of the overloads (and implementation) of an overloaded function. An +//! [`OverloadLiteral`] represents an individual function definition in the AST — that is, each +//! overload (and implementation) of an overloaded function, or the single definition of a +//! non-overloaded function. +//! +//! Technically, each `FunctionLiteral` wraps a particular overload and all _previous_ overloads. +//! So it's only true that it wraps _all_ overloads if you are looking at the last definition. For +//! instance, in +//! +//! ```py +//! @overload +//! def f(x: int) -> None: ... +//! # <-- 1 +//! +//! @overload +//! def f(x: str) -> None: ... +//! # <-- 2 +//! +//! def f(x): pass +//! # <-- 3 +//! ``` +//! +//! resolving `f` at each of the three numbered positions will give you a `FunctionType`, which +//! wraps a `FunctionLiteral`, which contain `OverloadLiteral`s only for the definitions that +//! appear before that position. We rely on the fact that later definitions shadow earlier ones, so +//! the public type of `f` is resolved at position 3, correctly giving you all of the overloads +//! (and the implementation). + +use std::str::FromStr; + +use bitflags::bitflags; +use ruff_db::diagnostic::Span; +use ruff_db::files::{File, FileRange}; +use ruff_python_ast as ast; +use ruff_text_size::Ranged; + +use crate::module_resolver::{KnownModule, file_to_module}; +use crate::semantic_index::ast_ids::HasScopedUseId; +use crate::semantic_index::definition::Definition; +use crate::semantic_index::semantic_index; +use crate::semantic_index::symbol::ScopeId; +use crate::symbol::{Boundness, Symbol, symbol_from_bindings}; +use crate::types::generics::GenericContext; +use crate::types::narrow::ClassInfoConstraintFunction; +use crate::types::signatures::{CallableSignature, Signature}; +use crate::types::{BoundMethodType, CallableType, Type, TypeMapping, TypeVarInstance}; +use crate::{Db, FxOrderSet}; + +/// A collection of useful spans for annotating functions. +/// +/// This can be retrieved via `FunctionType::spans` or +/// `Type::function_spans`. +pub(crate) struct FunctionSpans { + /// The span of the entire function "signature." This includes + /// the name, parameter list and return type (if present). + pub(crate) signature: Span, + /// The span of the function name. i.e., `foo` in `def foo(): ...`. + pub(crate) name: Span, + /// The span of the parameter list, including the opening and + /// closing parentheses. + #[expect(dead_code)] + pub(crate) parameters: Span, + /// The span of the annotated return type, if present. + pub(crate) return_type: Option, +} + +bitflags! { + #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Hash)] + pub struct FunctionDecorators: u8 { + /// `@classmethod` + const CLASSMETHOD = 1 << 0; + /// `@typing.no_type_check` + const NO_TYPE_CHECK = 1 << 1; + /// `@typing.overload` + const OVERLOAD = 1 << 2; + /// `@abc.abstractmethod` + const ABSTRACT_METHOD = 1 << 3; + /// `@typing.final` + const FINAL = 1 << 4; + /// `@typing.override` + const OVERRIDE = 1 << 6; + } +} + +bitflags! { + /// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the + /// arguments that were passed in. For the precise meaning of the fields, see [1]. + /// + /// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] + pub struct DataclassTransformerParams: u8 { + const EQ_DEFAULT = 1 << 0; + const ORDER_DEFAULT = 1 << 1; + const KW_ONLY_DEFAULT = 1 << 2; + const FROZEN_DEFAULT = 1 << 3; + } +} + +impl Default for DataclassTransformerParams { + fn default() -> Self { + Self::EQ_DEFAULT + } +} + +/// Representation of a function definition in the AST: either a non-generic function, or a generic +/// function that has not been specialized. +/// +/// If a function has multiple overloads, each overload is represented by a separate function +/// definition in the AST, and is therefore a separate `OverloadLiteral` instance. +/// +/// # Ordering +/// Ordering is based on the function's id assigned by salsa and not on the function literal's +/// values. The id may change between runs, or when the function literal was garbage collected and +/// recreated. +#[salsa::interned(debug)] +#[derive(PartialOrd, Ord)] +pub struct OverloadLiteral<'db> { + /// Name of the function at definition. + #[returns(ref)] + pub name: ast::name::Name, + + /// Is this a function that we special-case somehow? If so, which one? + pub(crate) known: Option, + + /// The scope that's created by the function, in which the function body is evaluated. + pub(crate) body_scope: ScopeId<'db>, + + /// A set of special decorators that were applied to this function + pub(crate) decorators: FunctionDecorators, + + /// The arguments to `dataclass_transformer`, if this function was annotated + /// with `@dataclass_transformer(...)`. + pub(crate) dataclass_transformer_params: Option, +} + +#[salsa::tracked] +impl<'db> OverloadLiteral<'db> { + fn with_dataclass_transformer_params( + self, + db: &'db dyn Db, + params: DataclassTransformerParams, + ) -> Self { + Self::new( + db, + self.name(db).clone(), + self.known(db), + self.body_scope(db), + self.decorators(db), + Some(params), + ) + } + + fn file(self, db: &'db dyn Db) -> File { + // NOTE: Do not use `self.definition(db).file(db)` here, as that could create a + // cross-module dependency on the full AST. + self.body_scope(db).file(db) + } + + pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { + self.decorators(db).contains(decorator) + } + + pub(crate) fn is_overload(self, db: &dyn Db) -> bool { + self.has_known_decorator(db, FunctionDecorators::OVERLOAD) + } + + fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { + debug_assert_eq!( + file, + self.file(db), + "OverloadLiteral::node() must be called with the same file as the one where \ + the function is defined." + ); + + self.body_scope(db).node(db).expect_function() + } + + /// Returns the [`FileRange`] of the function's name. + pub(crate) fn focus_range(self, db: &dyn Db) -> FileRange { + FileRange::new( + self.file(db), + self.body_scope(db).node(db).expect_function().name.range, + ) + } + + /// Returns the [`Definition`] of this function. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + fn definition(self, db: &'db dyn Db) -> Definition<'db> { + let body_scope = self.body_scope(db); + let index = semantic_index(db, body_scope.file(db)); + index.expect_single_definition(body_scope.node(db).expect_function()) + } + + /// Returns the overload immediately before this one in the AST. Returns `None` if there is no + /// previous overload. + fn previous_overload(self, db: &'db dyn Db) -> Option> { + // The semantic model records a use for each function on the name node. This is used + // here to get the previous function definition with the same name. + let scope = self.definition(db).scope(db); + let use_def = semantic_index(db, scope.file(db)).use_def_map(scope.file_scope_id(db)); + let use_id = self + .body_scope(db) + .node(db) + .expect_function() + .name + .scoped_use_id(db, scope); + + let Symbol::Type(Type::FunctionLiteral(previous_type), Boundness::Bound) = + symbol_from_bindings(db, use_def.bindings_at_use(use_id)) + else { + return None; + }; + + let previous_literal = previous_type.literal(db); + let previous_overload = previous_literal.last_definition(db); + if !previous_overload.is_overload(db) { + return None; + } + + Some(previous_literal) + } + + /// Typed internally-visible signature for this function. + /// + /// This represents the annotations on the function itself, unmodified by decorators and + /// overloads. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + pub(crate) fn signature( + self, + db: &'db dyn Db, + inherited_generic_context: Option>, + ) -> Signature<'db> { + let scope = self.body_scope(db); + let function_stmt_node = scope.node(db).expect_function(); + let definition = self.definition(db); + let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| { + let index = semantic_index(db, scope.file(db)); + GenericContext::from_type_params(db, index, type_params) + }); + Signature::from_function( + db, + generic_context, + inherited_generic_context, + definition, + function_stmt_node, + ) + } + + fn parameter_span( + self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { + let function_scope = self.body_scope(db); + let span = Span::from(function_scope.file(db)); + let node = function_scope.node(db); + let func_def = node.as_function()?; + let range = parameter_index + .and_then(|parameter_index| { + func_def + .parameters + .iter() + .nth(parameter_index) + .map(|param| param.range()) + }) + .unwrap_or(func_def.parameters.range); + let name_span = span.clone().with_range(func_def.name.range); + let parameter_span = span.with_range(range); + Some((name_span, parameter_span)) + } + + pub(crate) fn spans(self, db: &'db dyn Db) -> Option { + let function_scope = self.body_scope(db); + let span = Span::from(function_scope.file(db)); + let node = function_scope.node(db); + let func_def = node.as_function()?; + let return_type_range = func_def.returns.as_ref().map(|returns| returns.range()); + let mut signature = func_def.name.range.cover(func_def.parameters.range); + if let Some(return_type_range) = return_type_range { + signature = signature.cover(return_type_range); + } + Some(FunctionSpans { + signature: span.clone().with_range(signature), + name: span.clone().with_range(func_def.name.range), + parameters: span.clone().with_range(func_def.parameters.range), + return_type: return_type_range.map(|range| span.clone().with_range(range)), + }) + } +} + +/// Representation of a function definition in the AST, along with any previous overloads of the +/// function. Each overload can be separately generic or not, and each generic overload uses +/// distinct typevars. +/// +/// # Ordering +/// Ordering is based on the function's id assigned by salsa and not on the function literal's +/// values. The id may change between runs, or when the function literal was garbage collected and +/// recreated. +#[salsa::interned(debug)] +#[derive(PartialOrd, Ord)] +pub struct FunctionLiteral<'db> { + pub(crate) last_definition: OverloadLiteral<'db>, + + /// The inherited generic context, if this function is a constructor method (`__new__` or + /// `__init__`) being used to infer the specialization of its generic class. If any of the + /// method's overloads are themselves generic, this is in addition to those per-overload + /// generic contexts (which are created lazily in [`OverloadLiteral::signature`]). + /// + /// If the function is not a constructor method, this field will always be `None`. + /// + /// If the function is a constructor method, we will end up creating two `FunctionLiteral` + /// instances for it. The first is created in [`TypeInferenceBuilder`][infer] when we encounter + /// the function definition during type inference. At this point, we don't yet know if the + /// function is a constructor method, so we create a `FunctionLiteral` with `None` for this + /// field. + /// + /// If at some point we encounter a call expression, which invokes the containing class's + /// constructor, as will create a _new_ `FunctionLiteral` instance for the function, with this + /// field [updated][] to contain the containing class's generic context. + /// + /// [infer]: crate::types::infer::TypeInferenceBuilder::infer_function_definition + /// [updated]: crate::types::class::ClassLiteral::own_class_member + inherited_generic_context: Option>, +} + +#[salsa::tracked] +impl<'db> FunctionLiteral<'db> { + fn with_inherited_generic_context( + self, + db: &'db dyn Db, + inherited_generic_context: GenericContext<'db>, + ) -> Self { + // A function cannot inherit more than one generic context from its containing class. + debug_assert!(self.inherited_generic_context(db).is_none()); + Self::new( + db, + self.last_definition(db), + Some(inherited_generic_context), + ) + } + + fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { + // All of the overloads of a function literal should have the same name. + self.last_definition(db).name(db) + } + + fn known(self, db: &'db dyn Db) -> Option { + // Whether a function is known is based on its name (and its containing module's name), so + // all overloads should be known (or not) equivalently. + self.last_definition(db).known(db) + } + + fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { + self.iter_overloads_and_implementation(db) + .any(|overload| overload.decorators(db).contains(decorator)) + } + + fn definition(self, db: &'db dyn Db) -> Definition<'db> { + self.last_definition(db).definition(db) + } + + fn parameter_span( + self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { + self.last_definition(db).parameter_span(db, parameter_index) + } + + fn spans(self, db: &'db dyn Db) -> Option { + self.last_definition(db).spans(db) + } + + #[salsa::tracked(returns(ref))] + fn overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> (Box<[OverloadLiteral<'db>]>, Option>) { + let self_overload = self.last_definition(db); + let mut current = self_overload; + let mut overloads = vec![]; + + while let Some(previous) = current.previous_overload(db) { + let overload = previous.last_definition(db); + overloads.push(overload); + current = overload; + } + + // Overloads are inserted in reverse order, from bottom to top. + overloads.reverse(); + + let implementation = if self_overload.is_overload(db) { + overloads.push(self_overload); + None + } else { + Some(self_overload) + }; + + (overloads.into_boxed_slice(), implementation) + } + + fn iter_overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> impl Iterator> + 'db { + let (implementation, overloads) = self.overloads_and_implementation(db); + overloads.iter().chain(implementation).copied() + } + + /// Typed externally-visible signature for this function. + /// + /// This is the signature as seen by external callers, possibly modified by decorators and/or + /// overloaded. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + fn signature<'a>( + self, + db: &'db dyn Db, + type_mappings: &'a [TypeMapping<'a, 'db>], + ) -> CallableSignature<'db> + where + 'db: 'a, + { + // We only include an implementation (i.e. a definition not decorated with `@overload`) if + // it's the only definition. + let inherited_generic_context = self.inherited_generic_context(db); + let (overloads, implementation) = self.overloads_and_implementation(db); + if let Some(implementation) = implementation { + if overloads.is_empty() { + return CallableSignature::single(type_mappings.iter().fold( + implementation.signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + )); + } + } + + CallableSignature::from_overloads(overloads.iter().map(|overload| { + type_mappings.iter().fold( + overload.signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + ) + })) + } + + fn normalized(self, db: &'db dyn Db) -> Self { + let context = self + .inherited_generic_context(db) + .map(|ctx| ctx.normalized(db)); + Self::new(db, self.last_definition(db), context) + } +} + +/// Represents a function type, which might be a non-generic function, or a specialization of a +/// generic function. +#[salsa::interned(debug)] +#[derive(PartialOrd, Ord)] +pub struct FunctionType<'db> { + pub(crate) literal: FunctionLiteral<'db>, + + /// Type mappings that should be applied to the function's parameter and return types. This + /// might include specializations of enclosing generic contexts (e.g. for non-generic methods + /// of a specialized generic class). + #[returns(deref)] + type_mappings: Box<[TypeMapping<'db, 'db>]>, +} + +#[salsa::tracked] +impl<'db> FunctionType<'db> { + pub(crate) fn with_inherited_generic_context( + self, + db: &'db dyn Db, + inherited_generic_context: GenericContext<'db>, + ) -> Self { + let literal = self + .literal(db) + .with_inherited_generic_context(db, inherited_generic_context); + Self::new(db, literal, self.type_mappings(db)) + } + + pub(crate) fn with_type_mapping<'a>( + self, + db: &'db dyn Db, + type_mapping: &TypeMapping<'a, 'db>, + ) -> Self { + let type_mappings: Box<[_]> = self + .type_mappings(db) + .iter() + .cloned() + .chain(std::iter::once(type_mapping.to_owned())) + .collect(); + Self::new(db, self.literal(db), type_mappings) + } + + pub(crate) fn with_dataclass_transformer_params( + self, + db: &'db dyn Db, + params: DataclassTransformerParams, + ) -> Self { + // A decorator only applies to the specific overload that it is attached to, not to all + // previous overloads. + let literal = self.literal(db); + let last_definition = literal + .last_definition(db) + .with_dataclass_transformer_params(db, params); + let literal = + FunctionLiteral::new(db, last_definition, literal.inherited_generic_context(db)); + Self::new(db, literal, self.type_mappings(db)) + } + + /// Returns the [`File`] in which this function is defined. + pub(crate) fn file(self, db: &'db dyn Db) -> File { + self.literal(db).last_definition(db).file(db) + } + + /// Returns the AST node for this function. + pub(crate) fn node(self, db: &'db dyn Db, file: File) -> &'db ast::StmtFunctionDef { + self.literal(db).last_definition(db).node(db, file) + } + + pub(crate) fn name(self, db: &'db dyn Db) -> &'db ast::name::Name { + self.literal(db).name(db) + } + + pub(crate) fn known(self, db: &'db dyn Db) -> Option { + self.literal(db).known(db) + } + + pub(crate) fn is_known(self, db: &'db dyn Db, known_function: KnownFunction) -> bool { + self.known(db) == Some(known_function) + } + + /// Returns if any of the overloads of this function have a particular decorator. + /// + /// Some decorators are expected to appear on every overload; others are expected to appear + /// only the implementation or first overload. This method does not check either of those + /// conditions. + pub(crate) fn has_known_decorator(self, db: &dyn Db, decorator: FunctionDecorators) -> bool { + self.literal(db).has_known_decorator(db, decorator) + } + + /// Returns the [`Definition`] of the implementation or first overload of this function. + /// + /// ## Warning + /// + /// This uses the semantic index to find the definition of the function. This means that if the + /// calling query is not in the same file as this function is defined in, then this will create + /// a cross-module dependency directly on the full AST which will lead to cache + /// over-invalidation. + pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> { + self.literal(db).definition(db) + } + + /// Returns a tuple of two spans. The first is + /// the span for the identifier of the function + /// definition for `self`. The second is + /// the span for the parameter in the function + /// definition for `self`. + /// + /// If there are no meaningful spans, then this + /// returns `None`. For example, when this type + /// isn't callable. + /// + /// When `parameter_index` is `None`, then the + /// second span returned covers the entire parameter + /// list. + /// + /// # Performance + /// + /// Note that this may introduce cross-module + /// dependencies. This can have an impact on + /// the effectiveness of incremental caching + /// and should therefore be used judiciously. + /// + /// An example of a good use case is to improve + /// a diagnostic. + pub(crate) fn parameter_span( + self, + db: &'db dyn Db, + parameter_index: Option, + ) -> Option<(Span, Span)> { + self.literal(db).parameter_span(db, parameter_index) + } + + /// Returns a collection of useful spans for a + /// function signature. These are useful for + /// creating annotations on diagnostics. + /// + /// # Performance + /// + /// Note that this may introduce cross-module + /// dependencies. This can have an impact on + /// the effectiveness of incremental caching + /// and should therefore be used judiciously. + /// + /// An example of a good use case is to improve + /// a diagnostic. + pub(crate) fn spans(self, db: &'db dyn Db) -> Option { + self.literal(db).spans(db) + } + + /// Returns all of the overload signatures and the implementation definition, if any, of this + /// function. The overload signatures will be in source order. + pub(crate) fn overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> &'db (Box<[OverloadLiteral<'db>]>, Option>) { + self.literal(db).overloads_and_implementation(db) + } + + /// Returns an iterator of all of the definitions of this function, including both overload + /// signatures and any implementation, all in source order. + pub(crate) fn iter_overloads_and_implementation( + self, + db: &'db dyn Db, + ) -> impl Iterator> + 'db { + self.literal(db).iter_overloads_and_implementation(db) + } + + /// Typed externally-visible signature for this function. + /// + /// This is the signature as seen by external callers, possibly modified by decorators and/or + /// overloaded. + /// + /// ## Why is this a salsa query? + /// + /// This is a salsa query to short-circuit the invalidation + /// when the function's AST node changes. + /// + /// Were this not a salsa query, then the calling query + /// would depend on the function's AST and rerun for every change in that file. + #[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)] + pub(crate) fn signature(self, db: &'db dyn Db) -> CallableSignature<'db> { + self.literal(db).signature(db, self.type_mappings(db)) + } + + /// Convert the `FunctionType` into a [`Type::Callable`]. + pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> { + Type::Callable(CallableType::new(db, self.signature(db), false)) + } + + /// Convert the `FunctionType` into a [`Type::BoundMethod`]. + pub(crate) fn into_bound_method_type( + self, + db: &'db dyn Db, + self_instance: Type<'db>, + ) -> Type<'db> { + Type::BoundMethod(BoundMethodType::new(db, self, self_instance)) + } + + pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool { + // A function type is the subtype of itself, and not of any other function type. However, + // our representation of a function type includes any specialization that should be applied + // to the signature. Different specializations of the same function type are only subtypes + // of each other if they result in subtype signatures. + if self.normalized(db) == other.normalized(db) { + return true; + } + if self.literal(db) != other.literal(db) { + return false; + } + let self_signature = self.signature(db); + let other_signature = other.signature(db); + if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) { + return false; + } + self_signature.is_subtype_of(db, other_signature) + } + + pub(crate) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool { + // A function type is assignable to itself, and not to any other function type. However, + // our representation of a function type includes any specialization that should be applied + // to the signature. Different specializations of the same function type are only + // assignable to each other if they result in assignable signatures. + self.literal(db) == other.literal(db) + && self.signature(db).is_assignable_to(db, other.signature(db)) + } + + pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + if self.normalized(db) == other.normalized(db) { + return true; + } + if self.literal(db) != other.literal(db) { + return false; + } + let self_signature = self.signature(db); + let other_signature = other.signature(db); + if !self_signature.is_fully_static(db) || !other_signature.is_fully_static(db) { + return false; + } + self_signature.is_equivalent_to(db, other_signature) + } + + pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { + self.literal(db) == other.literal(db) + && self + .signature(db) + .is_gradual_equivalent_to(db, other.signature(db)) + } + + pub(crate) fn find_legacy_typevars( + self, + db: &'db dyn Db, + typevars: &mut FxOrderSet>, + ) { + let signatures = self.signature(db); + for signature in &signatures.overloads { + signature.find_legacy_typevars(db, typevars); + } + } + + pub(crate) fn normalized(self, db: &'db dyn Db) -> Self { + let mappings: Box<_> = self + .type_mappings(db) + .iter() + .map(|mapping| mapping.normalized(db)) + .collect(); + Self::new(db, self.literal(db).normalized(db), mappings) + } +} + +fn signature_cycle_recover<'db>( + _db: &'db dyn Db, + _value: &CallableSignature<'db>, + _count: u32, + _function: FunctionType<'db>, +) -> salsa::CycleRecoveryAction> { + salsa::CycleRecoveryAction::Iterate +} + +fn signature_cycle_initial<'db>( + db: &'db dyn Db, + _function: FunctionType<'db>, +) -> CallableSignature<'db> { + CallableSignature::single(Signature::bottom(db)) +} + +/// Non-exhaustive enumeration of known functions (e.g. `builtins.reveal_type`, ...) that might +/// have special behavior. +#[derive( + Debug, Copy, Clone, PartialEq, Eq, Hash, strum_macros::EnumString, strum_macros::IntoStaticStr, +)] +#[strum(serialize_all = "snake_case")] +#[cfg_attr(test, derive(strum_macros::EnumIter))] +pub enum KnownFunction { + /// `builtins.isinstance` + #[strum(serialize = "isinstance")] + IsInstance, + /// `builtins.issubclass` + #[strum(serialize = "issubclass")] + IsSubclass, + /// `builtins.hasattr` + #[strum(serialize = "hasattr")] + HasAttr, + /// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type` + RevealType, + /// `builtins.len` + Len, + /// `builtins.repr` + Repr, + /// `typing(_extensions).final` + Final, + + /// [`typing(_extensions).no_type_check`](https://typing.python.org/en/latest/spec/directives.html#no-type-check) + NoTypeCheck, + + /// `typing(_extensions).assert_type` + AssertType, + /// `typing(_extensions).assert_never` + AssertNever, + /// `typing(_extensions).cast` + Cast, + /// `typing(_extensions).overload` + Overload, + /// `typing(_extensions).override` + Override, + /// `typing(_extensions).is_protocol` + IsProtocol, + /// `typing(_extensions).get_protocol_members` + GetProtocolMembers, + /// `typing(_extensions).runtime_checkable` + RuntimeCheckable, + /// `typing(_extensions).dataclass_transform` + DataclassTransform, + + /// `abc.abstractmethod` + #[strum(serialize = "abstractmethod")] + AbstractMethod, + + /// `dataclasses.dataclass` + Dataclass, + + /// `inspect.getattr_static` + GetattrStatic, + + /// `ty_extensions.static_assert` + StaticAssert, + /// `ty_extensions.is_equivalent_to` + IsEquivalentTo, + /// `ty_extensions.is_subtype_of` + IsSubtypeOf, + /// `ty_extensions.is_assignable_to` + IsAssignableTo, + /// `ty_extensions.is_disjoint_from` + IsDisjointFrom, + /// `ty_extensions.is_gradual_equivalent_to` + IsGradualEquivalentTo, + /// `ty_extensions.is_fully_static` + IsFullyStatic, + /// `ty_extensions.is_singleton` + IsSingleton, + /// `ty_extensions.is_single_valued` + IsSingleValued, + /// `ty_extensions.generic_context` + GenericContext, + /// `ty_extensions.dunder_all_names` + DunderAllNames, + /// `ty_extensions.all_members` + AllMembers, +} + +impl KnownFunction { + pub fn into_classinfo_constraint_function(self) -> Option { + match self { + Self::IsInstance => Some(ClassInfoConstraintFunction::IsInstance), + Self::IsSubclass => Some(ClassInfoConstraintFunction::IsSubclass), + _ => None, + } + } + + pub(crate) fn try_from_definition_and_name<'db>( + db: &'db dyn Db, + definition: Definition<'db>, + name: &str, + ) -> Option { + let candidate = Self::from_str(name).ok()?; + candidate + .check_module(file_to_module(db, definition.file(db))?.known()?) + .then_some(candidate) + } + + /// Return `true` if `self` is defined in `module` at runtime. + const fn check_module(self, module: KnownModule) -> bool { + match self { + Self::IsInstance | Self::IsSubclass | Self::HasAttr | Self::Len | Self::Repr => { + module.is_builtins() + } + Self::AssertType + | Self::AssertNever + | Self::Cast + | Self::Overload + | Self::Override + | Self::RevealType + | Self::Final + | Self::IsProtocol + | Self::GetProtocolMembers + | Self::RuntimeCheckable + | Self::DataclassTransform + | Self::NoTypeCheck => { + matches!(module, KnownModule::Typing | KnownModule::TypingExtensions) + } + Self::AbstractMethod => { + matches!(module, KnownModule::Abc) + } + Self::Dataclass => { + matches!(module, KnownModule::Dataclasses) + } + Self::GetattrStatic => module.is_inspect(), + Self::IsAssignableTo + | Self::IsDisjointFrom + | Self::IsEquivalentTo + | Self::IsGradualEquivalentTo + | Self::IsFullyStatic + | Self::IsSingleValued + | Self::IsSingleton + | Self::IsSubtypeOf + | Self::GenericContext + | Self::DunderAllNames + | Self::StaticAssert + | Self::AllMembers => module.is_ty_extensions(), + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use strum::IntoEnumIterator; + + use super::*; + use crate::db::tests::setup_db; + use crate::symbol::known_module_symbol; + + #[test] + fn known_function_roundtrip_from_str() { + let db = setup_db(); + + for function in KnownFunction::iter() { + let function_name: &'static str = function.into(); + + let module = match function { + KnownFunction::Len + | KnownFunction::Repr + | KnownFunction::IsInstance + | KnownFunction::HasAttr + | KnownFunction::IsSubclass => KnownModule::Builtins, + + KnownFunction::AbstractMethod => KnownModule::Abc, + + KnownFunction::Dataclass => KnownModule::Dataclasses, + + KnownFunction::GetattrStatic => KnownModule::Inspect, + + KnownFunction::Cast + | KnownFunction::Final + | KnownFunction::Overload + | KnownFunction::Override + | KnownFunction::RevealType + | KnownFunction::AssertType + | KnownFunction::AssertNever + | KnownFunction::IsProtocol + | KnownFunction::GetProtocolMembers + | KnownFunction::RuntimeCheckable + | KnownFunction::DataclassTransform + | KnownFunction::NoTypeCheck => KnownModule::TypingExtensions, + + KnownFunction::IsSingleton + | KnownFunction::IsSubtypeOf + | KnownFunction::GenericContext + | KnownFunction::DunderAllNames + | KnownFunction::StaticAssert + | KnownFunction::IsFullyStatic + | KnownFunction::IsDisjointFrom + | KnownFunction::IsSingleValued + | KnownFunction::IsAssignableTo + | KnownFunction::IsEquivalentTo + | KnownFunction::IsGradualEquivalentTo + | KnownFunction::AllMembers => KnownModule::TyExtensions, + }; + + let function_definition = known_module_symbol(&db, module, function_name) + .symbol + .expect_type() + .expect_function_literal() + .definition(&db); + + assert_eq!( + KnownFunction::try_from_definition_and_name( + &db, + function_definition, + function_name + ), + Some(function), + "The strum `EnumString` implementation appears to be incorrect for `{function_name}`" + ); + } + } +} diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 44b5d06616..af77abaabc 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -81,19 +81,21 @@ use crate::types::diagnostic::{ report_invalid_attribute_assignment, report_invalid_generator_function_return_type, report_invalid_return_type, report_possibly_unbound_attribute, }; +use crate::types::function::{ + FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, +}; use crate::types::generics::GenericContext; use crate::types::mro::MroErrorKind; use crate::types::signatures::{CallableSignature, Signature}; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, - DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder, - IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy, - 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, - todo_type, + DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass, + KnownInstanceType, MemberLookupPolicy, 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, todo_type, }; use crate::unpack::{Unpack, UnpackPosition}; use crate::util::subscript::{PyIndex, PySlice}; @@ -1131,12 +1133,13 @@ impl<'db> TypeInferenceBuilder<'db> { } for function in self.called_functions.union(&public_functions) { - let Some(overloaded) = function.to_overloaded(self.db()) else { + let (overloads, implementation) = function.overloads_and_implementation(self.db()); + if overloads.is_empty() { continue; - }; + } // Check that the overloaded function has at least two overloads - if let [single_overload] = overloaded.overloads.as_slice() { + if let [single_overload] = overloads.as_ref() { let function_node = function.node(self.db(), self.file()); if let Some(builder) = self .context @@ -1157,7 +1160,7 @@ impl<'db> TypeInferenceBuilder<'db> { // Check that the overloaded function has an implementation. Overload definitions // within stub files, protocols, and on abstract methods within abstract base classes // are exempt from this check. - if overloaded.implementation.is_none() && !self.in_stub() { + if implementation.is_none() && !self.in_stub() { let mut implementation_required = true; if let NodeWithScopeKind::Class(class_node_ref) = scope { @@ -1169,7 +1172,7 @@ impl<'db> TypeInferenceBuilder<'db> { if class.is_protocol(self.db()) || (class.is_abstract(self.db()) - && overloaded.overloads.iter().all(|overload| { + && overloads.iter().all(|overload| { overload.has_known_decorator( self.db(), FunctionDecorators::ABSTRACT_METHOD, @@ -1199,7 +1202,7 @@ impl<'db> TypeInferenceBuilder<'db> { let mut decorator_present = false; let mut decorator_missing = vec![]; - for function in overloaded.all() { + for function in overloads.iter().chain(implementation.as_ref()) { if function.has_known_decorator(self.db(), decorator) { decorator_present = true; } else { @@ -1240,8 +1243,8 @@ impl<'db> TypeInferenceBuilder<'db> { (FunctionDecorators::FINAL, "final"), (FunctionDecorators::OVERRIDE, "override"), ] { - if let Some(implementation) = overloaded.implementation.as_ref() { - for overload in &overloaded.overloads { + if let Some(implementation) = implementation { + for overload in overloads.as_ref() { if !overload.has_known_decorator(self.db(), decorator) { continue; } @@ -1263,7 +1266,7 @@ impl<'db> TypeInferenceBuilder<'db> { ); } } else { - let mut overloads = overloaded.overloads.iter(); + let mut overloads = overloads.iter(); let Some(first_overload) = overloads.next() else { continue; }; @@ -2027,7 +2030,7 @@ impl<'db> TypeInferenceBuilder<'db> { } } - let function_kind = + let known_function = KnownFunction::try_from_definition_and_name(self.db(), definition, name); let body_scope = self @@ -2035,17 +2038,23 @@ impl<'db> TypeInferenceBuilder<'db> { .node_scope(NodeWithScopeRef::Function(function)) .to_scope_id(self.db(), self.file()); - let inherited_generic_context = None; - let type_mappings = Box::from([]); - - let mut inferred_ty = Type::FunctionLiteral(FunctionType::new( + let overload_literal = OverloadLiteral::new( self.db(), &name.id, - function_kind, + known_function, body_scope, function_decorators, dataclass_transformer_params, - inherited_generic_context, + ); + + let inherited_generic_context = None; + let function_literal = + FunctionLiteral::new(self.db(), overload_literal, inherited_generic_context); + + let type_mappings = Box::from([]); + let mut inferred_ty = Type::FunctionLiteral(FunctionType::new( + self.db(), + function_literal, type_mappings, )); @@ -2314,14 +2323,10 @@ impl<'db> TypeInferenceBuilder<'db> { // overload, or an overload and the implementation both. Nevertheless, this is not // allowed. We do not try to treat the offenders intelligently -- just use the // params of the last seen usage of `@dataclass_transform` - if let Some(overloaded) = f.to_overloaded(self.db()) { - overloaded.overloads.iter().for_each(|overload| { - if let Some(params) = overload.dataclass_transformer_params(self.db()) { - dataclass_params = Some(params.into()); - } - }); - } - if let Some(params) = f.dataclass_transformer_params(self.db()) { + let params = f + .iter_overloads_and_implementation(self.db()) + .find_map(|overload| overload.dataclass_transformer_params(self.db())); + if let Some(params) = params { dataclass_params = Some(params.into()); continue; } diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index 303885c320..f8bf6d610b 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -6,6 +6,7 @@ use crate::semantic_index::predicate::{ }; use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable}; use crate::semantic_index::symbol_table; +use crate::types::function::KnownFunction; use crate::types::infer::infer_same_file_expression_type; use crate::types::{ IntersectionBuilder, KnownClass, SubclassOfType, Truthiness, Type, UnionBuilder, @@ -20,7 +21,7 @@ use ruff_python_ast::{BoolOp, ExprBoolOp}; use rustc_hash::FxHashMap; use std::collections::hash_map::Entry; -use super::{KnownFunction, UnionType}; +use super::UnionType; /// Return the type constraint that `test` (if true) would place on `symbol`, if any. /// diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 005ea95765..7efdc84531 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -7,9 +7,8 @@ use ruff_python_ast::name::Name; use crate::{ semantic_index::{symbol_table, use_def_map}, symbol::{symbol_from_bindings, symbol_from_declarations}, - types::{ - ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance, - }, + types::function::KnownFunction, + types::{ClassBase, ClassLiteral, Type, TypeMapping, TypeQualifiers, TypeVarInstance}, {Db, FxOrderSet}, }; diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 24448b661d..201ab0eddf 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -53,10 +53,6 @@ impl<'db> CallableSignature<'db> { self.overloads.iter() } - pub(crate) fn as_slice(&self) -> &[Signature<'db>] { - self.overloads.as_slice() - } - pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self { Self::from_overloads( self.overloads @@ -1538,7 +1534,8 @@ mod tests { use super::*; use crate::db::tests::{TestDb, setup_db}; use crate::symbol::global_symbol; - use crate::types::{FunctionSignature, FunctionType, KnownClass}; + use crate::types::KnownClass; + use crate::types::function::FunctionType; use ruff_db::system::DbWithWritableSystem as _; #[track_caller] @@ -1559,9 +1556,11 @@ mod tests { fn empty() { let mut db = setup_db(); db.write_dedented("/src/a.py", "def f(): ...").unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); assert!(sig.return_ty.is_none()); assert_params(&sig, &[]); @@ -1582,9 +1581,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); assert_eq!(sig.return_ty.unwrap().display(&db).to_string(), "bytes"); assert_params( @@ -1633,9 +1634,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1669,9 +1672,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.pyi"); + let func = get_function_f(&db, "/src/a.pyi") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1705,9 +1710,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.py"); + let func = get_function_f(&db, "/src/a.py") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1751,9 +1758,11 @@ mod tests { ", ) .unwrap(); - let func = get_function_f(&db, "/src/a.pyi"); + let func = get_function_f(&db, "/src/a.pyi") + .literal(&db) + .last_definition(&db); - let sig = func.internal_signature(&db, None); + let sig = func.signature(&db, None); let [ Parameter { @@ -1789,15 +1798,13 @@ mod tests { .unwrap(); let func = get_function_f(&db, "/src/a.py"); - let expected_sig = func.internal_signature(&db, None); + let overload = func.literal(&db).last_definition(&db); + let expected_sig = overload.signature(&db, None); // With no decorators, internal and external signature are the same assert_eq!( func.signature(&db), - &FunctionSignature { - overloads: CallableSignature::single(expected_sig), - implementation: None - }, + &CallableSignature::single(expected_sig) ); } }