mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 15:15:33 +00:00
[ty] Create separate FunctionLiteral
and FunctionType
types (#18360)
This updates our representation of functions to more closely match our representation of classes. The new `OverloadLiteral` and `FunctionLiteral` classes represent a function definition in the AST. If a function is generic, this is unspecialized. `FunctionType` has been updated to represent a function type, which is specialized if the function is generic. (These names are chosen to match `ClassLiteral` and `ClassType` on the class side.) This PR does not add a separate `Type` variant for `FunctionLiteral`. Maybe we should? Possibly as a follow-on PR? Part of https://github.com/astral-sh/ty/issues/462 --------- Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
8d98c601d8
commit
2c3b3d3230
12 changed files with 1280 additions and 1021 deletions
108
crates/ty/docs/rules.md
generated
108
crates/ty/docs/rules.md
generated
|
@ -52,7 +52,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `conflicting-argument-forms`
|
## `conflicting-argument-forms`
|
||||||
|
@ -83,7 +83,7 @@ f(int) # error
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `conflicting-declarations`
|
## `conflicting-declarations`
|
||||||
|
@ -113,7 +113,7 @@ a = 1
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `conflicting-metaclass`
|
## `conflicting-metaclass`
|
||||||
|
@ -144,7 +144,7 @@ class C(A, B): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `cyclic-class-definition`
|
## `cyclic-class-definition`
|
||||||
|
@ -175,7 +175,7 @@ class B(A): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `duplicate-base`
|
## `duplicate-base`
|
||||||
|
@ -201,7 +201,7 @@ class B(A, A): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `escape-character-in-forward-annotation`
|
## `escape-character-in-forward-annotation`
|
||||||
|
@ -338,7 +338,7 @@ TypeError: multiple bases have instance lay-out conflict
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20incompatible-slots)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `inconsistent-mro`
|
## `inconsistent-mro`
|
||||||
|
@ -367,7 +367,7 @@ class C(A, B): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `index-out-of-bounds`
|
## `index-out-of-bounds`
|
||||||
|
@ -392,7 +392,7 @@ t[3] # IndexError: tuple index out of range
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-argument-type`
|
## `invalid-argument-type`
|
||||||
|
@ -418,7 +418,7 @@ func("foo") # error: [invalid-argument-type]
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-assignment`
|
## `invalid-assignment`
|
||||||
|
@ -445,7 +445,7 @@ a: int = ''
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-attribute-access`
|
## `invalid-attribute-access`
|
||||||
|
@ -478,7 +478,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-base`
|
## `invalid-base`
|
||||||
|
@ -501,7 +501,7 @@ class A(42): ... # error: [invalid-base]
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-context-manager`
|
## `invalid-context-manager`
|
||||||
|
@ -527,7 +527,7 @@ with 1:
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-declaration`
|
## `invalid-declaration`
|
||||||
|
@ -555,7 +555,7 @@ a: str
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-exception-caught`
|
## `invalid-exception-caught`
|
||||||
|
@ -596,7 +596,7 @@ except ZeroDivisionError:
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-generic-class`
|
## `invalid-generic-class`
|
||||||
|
@ -627,7 +627,7 @@ class C[U](Generic[T]): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-legacy-type-variable`
|
## `invalid-legacy-type-variable`
|
||||||
|
@ -660,7 +660,7 @@ def f(t: TypeVar("U")): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-metaclass`
|
## `invalid-metaclass`
|
||||||
|
@ -692,7 +692,7 @@ class B(metaclass=f): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-overload`
|
## `invalid-overload`
|
||||||
|
@ -740,7 +740,7 @@ def foo(x: int) -> int: ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-parameter-default`
|
## `invalid-parameter-default`
|
||||||
|
@ -765,7 +765,7 @@ def f(a: int = ''): ...
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-protocol`
|
## `invalid-protocol`
|
||||||
|
@ -798,7 +798,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-raise`
|
## `invalid-raise`
|
||||||
|
@ -846,7 +846,7 @@ def g():
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-return-type`
|
## `invalid-return-type`
|
||||||
|
@ -870,7 +870,7 @@ def func() -> int:
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-super-argument`
|
## `invalid-super-argument`
|
||||||
|
@ -914,7 +914,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)`
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-syntax-in-forward-annotation`
|
## `invalid-syntax-in-forward-annotation`
|
||||||
|
@ -954,7 +954,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-checking-constant`
|
## `invalid-type-checking-constant`
|
||||||
|
@ -983,7 +983,7 @@ TYPE_CHECKING = ''
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-form`
|
## `invalid-type-form`
|
||||||
|
@ -1012,7 +1012,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-variable-constraints`
|
## `invalid-type-variable-constraints`
|
||||||
|
@ -1046,7 +1046,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `missing-argument`
|
## `missing-argument`
|
||||||
|
@ -1070,7 +1070,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `no-matching-overload`
|
## `no-matching-overload`
|
||||||
|
@ -1098,7 +1098,7 @@ func("string") # error: [no-matching-overload]
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `non-subscriptable`
|
## `non-subscriptable`
|
||||||
|
@ -1121,7 +1121,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `not-iterable`
|
## `not-iterable`
|
||||||
|
@ -1146,7 +1146,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `parameter-already-assigned`
|
## `parameter-already-assigned`
|
||||||
|
@ -1172,7 +1172,7 @@ f(1, x=2) # Error raised here
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `raw-string-type-annotation`
|
## `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
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `subclass-of-final-class`
|
## `subclass-of-final-class`
|
||||||
|
@ -1259,7 +1259,7 @@ class B(A): ... # Error raised here
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `too-many-positional-arguments`
|
## `too-many-positional-arguments`
|
||||||
|
@ -1285,7 +1285,7 @@ f("foo") # Error raised here
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `type-assertion-failure`
|
## `type-assertion-failure`
|
||||||
|
@ -1312,7 +1312,7 @@ def _(x: int):
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unavailable-implicit-super-arguments`
|
## `unavailable-implicit-super-arguments`
|
||||||
|
@ -1356,7 +1356,7 @@ class A:
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unknown-argument`
|
## `unknown-argument`
|
||||||
|
@ -1382,7 +1382,7 @@ f(x=1, y=2) # Error raised here
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unresolved-attribute`
|
## `unresolved-attribute`
|
||||||
|
@ -1409,7 +1409,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unresolved-import`
|
## `unresolved-import`
|
||||||
|
@ -1433,7 +1433,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unresolved-reference`
|
## `unresolved-reference`
|
||||||
|
@ -1457,7 +1457,7 @@ print(x) # NameError: name 'x' is not defined
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unsupported-bool-conversion`
|
## `unsupported-bool-conversion`
|
||||||
|
@ -1493,7 +1493,7 @@ b1 < b2 < b1 # exception raised here
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unsupported-operator`
|
## `unsupported-operator`
|
||||||
|
@ -1520,7 +1520,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `zero-stepsize-in-slice`
|
## `zero-stepsize-in-slice`
|
||||||
|
@ -1544,7 +1544,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-ignore-comment`
|
## `invalid-ignore-comment`
|
||||||
|
@ -1600,7 +1600,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `possibly-unbound-implicit-call`
|
## `possibly-unbound-implicit-call`
|
||||||
|
@ -1631,7 +1631,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `possibly-unbound-import`
|
## `possibly-unbound-import`
|
||||||
|
@ -1662,7 +1662,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `redundant-cast`
|
## `redundant-cast`
|
||||||
|
@ -1688,7 +1688,7 @@ cast(int, f()) # Redundant
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `undefined-reveal`
|
## `undefined-reveal`
|
||||||
|
@ -1711,7 +1711,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unknown-rule`
|
## `unknown-rule`
|
||||||
|
@ -1779,7 +1779,7 @@ class D(C): ... # error: [unsupported-base]
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `division-by-zero`
|
## `division-by-zero`
|
||||||
|
@ -1802,7 +1802,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `possibly-unresolved-reference`
|
## `possibly-unresolved-reference`
|
||||||
|
@ -1829,7 +1829,7 @@ print(x) # NameError: name 'x' is not defined
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference)
|
* [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)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unused-ignore-comment`
|
## `unused-ignore-comment`
|
||||||
|
|
|
@ -2,7 +2,6 @@ use infer::nearest_enclosing_class;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
|
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use call::{CallDunderError, CallError, CallErrorKind};
|
use call::{CallDunderError, CallError, CallErrorKind};
|
||||||
|
@ -14,7 +13,7 @@ use diagnostic::{
|
||||||
use ruff_db::diagnostic::{
|
use ruff_db::diagnostic::{
|
||||||
Annotation, Severity, Span, SubDiagnostic, create_semantic_syntax_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::name::Name;
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
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_deferred_types, infer_definition_types, infer_expression_type, infer_expression_types,
|
||||||
infer_scope_types,
|
infer_scope_types,
|
||||||
};
|
};
|
||||||
pub(crate) use self::narrow::ClassInfoConstraintFunction;
|
|
||||||
pub(crate) use self::signatures::{CallableSignature, Signature};
|
pub(crate) use self::signatures::{CallableSignature, Signature};
|
||||||
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
||||||
use crate::module_name::ModuleName;
|
use crate::module_name::ModuleName;
|
||||||
use crate::module_resolver::{KnownModule, file_to_module, resolve_module};
|
use crate::module_resolver::{KnownModule, resolve_module};
|
||||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId};
|
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::symbol::ScopeId;
|
use crate::semantic_index::symbol::ScopeId;
|
||||||
use crate::semantic_index::{imported_modules, semantic_index};
|
use crate::semantic_index::{imported_modules, semantic_index};
|
||||||
use crate::suppression::check_suppressions;
|
use crate::suppression::check_suppressions;
|
||||||
use crate::symbol::{
|
use crate::symbol::{Boundness, Symbol, SymbolAndQualifiers, imported_symbol};
|
||||||
Boundness, Symbol, SymbolAndQualifiers, imported_symbol, symbol_from_bindings,
|
|
||||||
};
|
|
||||||
use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding};
|
use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallableBinding};
|
||||||
pub(crate) use crate::types::class_base::ClassBase;
|
pub(crate) use crate::types::class_base::ClassBase;
|
||||||
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
||||||
use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
|
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::generics::{GenericContext, PartialSpecialization, Specialization};
|
||||||
use crate::types::infer::infer_unpack_types;
|
use crate::types::infer::infer_unpack_types;
|
||||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||||
|
@ -64,6 +63,7 @@ mod class_base;
|
||||||
mod context;
|
mod context;
|
||||||
mod diagnostic;
|
mod diagnostic;
|
||||||
mod display;
|
mod display;
|
||||||
|
mod function;
|
||||||
mod generics;
|
mod generics;
|
||||||
mod ide_support;
|
mod ide_support;
|
||||||
mod infer;
|
mod infer;
|
||||||
|
@ -432,26 +432,6 @@ impl From<DataclassTransformerParams> 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.
|
/// Representation of a type: a set of possible values at runtime.
|
||||||
///
|
///
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||||
|
@ -7040,710 +7020,6 @@ impl From<bool> 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<Signature<'db>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<FunctionType<'db>>,
|
|
||||||
/// The implementation of this overloaded function, if any.
|
|
||||||
implementation: Option<FunctionType<'db>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> OverloadedFunction<'db> {
|
|
||||||
/// Returns an iterator over all overloads and the implementation, in that order.
|
|
||||||
fn all(&self) -> impl Iterator<Item = FunctionType<'db>> + '_ {
|
|
||||||
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<KnownFunction>,
|
|
||||||
|
|
||||||
/// 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<DataclassTransformerParams>,
|
|
||||||
|
|
||||||
/// 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<GenericContext<'db>>,
|
|
||||||
|
|
||||||
/// 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<GenericContext<'db>>,
|
|
||||||
) -> 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<TypeVarInstance<'db>>,
|
|
||||||
) {
|
|
||||||
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<OverloadedFunction<'db>> {
|
|
||||||
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<usize>,
|
|
||||||
) -> 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<FunctionSpans> {
|
|
||||||
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<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature_cycle_recover<'db>(
|
|
||||||
_db: &'db dyn Db,
|
|
||||||
_value: &FunctionSignature<'db>,
|
|
||||||
_count: u32,
|
|
||||||
_function: FunctionType<'db>,
|
|
||||||
) -> salsa::CycleRecoveryAction<FunctionSignature<'db>> {
|
|
||||||
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<ClassInfoConstraintFunction> {
|
|
||||||
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<Self> {
|
|
||||||
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
|
/// 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
|
/// 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
|
/// 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 {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db::tests::{TestDbBuilder, setup_db};
|
use crate::db::tests::{TestDbBuilder, setup_db};
|
||||||
use crate::symbol::{
|
use crate::symbol::{global_symbol, typing_extensions_symbol, typing_symbol};
|
||||||
global_symbol, known_module_symbol, typing_extensions_symbol, typing_symbol,
|
|
||||||
};
|
|
||||||
use ruff_db::files::system_path_to_file;
|
use ruff_db::files::system_path_to_file;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_db::system::DbWithWritableSystem as _;
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
use ruff_db::testing::assert_function_query_was_not_run;
|
use ruff_db::testing::assert_function_query_was_not_run;
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
|
/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
|
||||||
|
@ -9364,69 +8637,4 @@ pub(crate) mod tests {
|
||||||
.is_todo()
|
.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}`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,13 +18,13 @@ use crate::types::diagnostic::{
|
||||||
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
|
NO_MATCHING_OVERLOAD, PARAMETER_ALREADY_ASSIGNED, TOO_MANY_POSITIONAL_ARGUMENTS,
|
||||||
UNKNOWN_ARGUMENT,
|
UNKNOWN_ARGUMENT,
|
||||||
};
|
};
|
||||||
|
use crate::types::function::{DataclassTransformerParams, FunctionDecorators, KnownFunction};
|
||||||
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
|
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
|
||||||
use crate::types::signatures::{Parameter, ParameterForm};
|
use crate::types::signatures::{Parameter, ParameterForm};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType,
|
BoundMethodType, DataclassParams, KnownClass, KnownInstanceType, MethodWrapperKind,
|
||||||
KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType,
|
PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType,
|
||||||
SpecialFormType, TupleType, TypeMapping, UnionType, WrapperDescriptorKind, ide_support,
|
WrapperDescriptorKind, ide_support, todo_type,
|
||||||
todo_type,
|
|
||||||
};
|
};
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||||
use ruff_python_ast as ast;
|
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
|
// Ideally, either the implementation, or exactly one of the overloads
|
||||||
// of the function can have the dataclass_transform decorator applied.
|
// of the function can have the dataclass_transform decorator applied.
|
||||||
// However, we do not yet enforce this, and in the case of multiple
|
// However, we do not yet enforce this, and in the case of multiple
|
||||||
// applications of the decorator, we will only consider the last one
|
// applications of the decorator, we will only consider the last one
|
||||||
// for the return value, since the prior ones will be over-written.
|
// for the return value, since the prior ones will be over-written.
|
||||||
if let Some(overloaded) = function_type.to_overloaded(db) {
|
let return_type = function_type
|
||||||
overloaded
|
.iter_overloads_and_implementation(db)
|
||||||
.overloads
|
.filter_map(|function_overload| {
|
||||||
.iter()
|
function_overload.dataclass_transformer_params(db).map(
|
||||||
.for_each(&mut handle_dataclass_transformer_params);
|
|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,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some((kind, function)) = function_type_and_kind {
|
if let Some((kind, function)) = function_type_and_kind {
|
||||||
if let Some(overloaded_function) = function.to_overloaded(context.db()) {
|
let (overloads, implementation) =
|
||||||
if let Some(spans) = overloaded_function
|
function.overloads_and_implementation(context.db());
|
||||||
.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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!(
|
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;
|
if let Some(spans) =
|
||||||
for overload in overloads.iter().take(MAXIMUM_OVERLOADS) {
|
implementation.and_then(|function| function.spans(context.db()))
|
||||||
diag.info(format_args!(" {}", overload.display(context.db())));
|
{
|
||||||
}
|
let mut sub = SubDiagnostic::new(
|
||||||
if overloads.len() > MAXIMUM_OVERLOADS {
|
Severity::Info,
|
||||||
diag.info(format_args!(
|
"Overload implementation defined here",
|
||||||
"... omitted {remaining} overloads",
|
);
|
||||||
remaining = overloads.len() - MAXIMUM_OVERLOADS
|
sub.annotate(Annotation::primary(spans.signature));
|
||||||
));
|
diag.sub(sub);
|
||||||
}
|
|
||||||
|
|
||||||
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(union_diag) = union_diag {
|
if let Some(union_diag) = union_diag {
|
||||||
union_diag.add_union_context(context.db(), &mut diag);
|
union_diag.add_union_context(context.db(), &mut diag);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,17 @@ use std::hash::BuildHasherDefault;
|
||||||
use std::sync::{LazyLock, Mutex};
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
IntersectionBuilder, KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator,
|
IntersectionBuilder, MemberLookupPolicy, Mro, MroError, MroIterator, SpecialFormType,
|
||||||
SpecialFormType, SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase,
|
SubclassOfType, Truthiness, Type, TypeQualifiers, class_base::ClassBase, infer_expression_type,
|
||||||
infer_expression_type, infer_unpack_types,
|
infer_unpack_types,
|
||||||
};
|
};
|
||||||
use crate::semantic_index::DeclarationWithConstraint;
|
use crate::semantic_index::DeclarationWithConstraint;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
|
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization};
|
||||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, TypeMapping,
|
CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeVarInstance,
|
||||||
TypeVarInstance,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
Db, FxOrderSet, KnownModule, Program,
|
Db, FxOrderSet, KnownModule, Program,
|
||||||
|
|
|
@ -11,13 +11,14 @@ use ruff_text_size::{Ranged, TextRange};
|
||||||
use super::{Type, TypeCheckDiagnostics, binding_type};
|
use super::{Type, TypeCheckDiagnostics, binding_type};
|
||||||
|
|
||||||
use crate::lint::LintSource;
|
use crate::lint::LintSource;
|
||||||
|
use crate::semantic_index::semantic_index;
|
||||||
use crate::semantic_index::symbol::ScopeId;
|
use crate::semantic_index::symbol::ScopeId;
|
||||||
|
use crate::types::function::FunctionDecorators;
|
||||||
use crate::{
|
use crate::{
|
||||||
Db,
|
Db,
|
||||||
lint::{LintId, LintMetadata},
|
lint::{LintId, LintMetadata},
|
||||||
suppression::suppressions,
|
suppression::suppressions,
|
||||||
};
|
};
|
||||||
use crate::{semantic_index::semantic_index, types::FunctionDecorators};
|
|
||||||
|
|
||||||
/// Context for inferring the types of a single file.
|
/// Context for inferring the types of a single file.
|
||||||
///
|
///
|
||||||
|
|
|
@ -8,12 +8,13 @@ use super::{
|
||||||
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
||||||
use crate::suppression::FileSuppressionId;
|
use crate::suppression::FileSuppressionId;
|
||||||
use crate::types::LintDiagnosticGuard;
|
use crate::types::LintDiagnosticGuard;
|
||||||
|
use crate::types::function::KnownFunction;
|
||||||
use crate::types::string_annotation::{
|
use crate::types::string_annotation::{
|
||||||
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
||||||
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
||||||
RAW_STRING_TYPE_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 crate::{Db, Module, ModuleName, Program, declare_lint};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||||
|
|
|
@ -7,6 +7,7 @@ use ruff_python_ast::str::{Quote, TripleQuotes};
|
||||||
use ruff_python_literal::escape::AsciiEscape;
|
use ruff_python_literal::escape::AsciiEscape;
|
||||||
|
|
||||||
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
||||||
|
use crate::types::function::{FunctionType, OverloadLiteral};
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization};
|
||||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
|
@ -112,34 +113,7 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
},
|
},
|
||||||
Type::SpecialForm(special_form) => special_form.fmt(f),
|
Type::SpecialForm(special_form) => special_form.fmt(f),
|
||||||
Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f),
|
Type::KnownInstance(known_instance) => known_instance.repr(self.db).fmt(f),
|
||||||
Type::FunctionLiteral(function) => {
|
Type::FunctionLiteral(function) => function.display(self.db).fmt(f),
|
||||||
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::Callable(callable) => callable.display(self.db).fmt(f),
|
Type::Callable(callable) => callable.display(self.db).fmt(f),
|
||||||
Type::BoundMethod(bound_method) => {
|
Type::BoundMethod(bound_method) => {
|
||||||
let function = bound_method.function(self.db);
|
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> {
|
impl<'db> GenericAlias<'db> {
|
||||||
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> {
|
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayGenericAlias<'db> {
|
||||||
DisplayGenericAlias {
|
DisplayGenericAlias {
|
||||||
|
|
996
crates/ty_python_semantic/src/types/function.rs
Normal file
996
crates/ty_python_semantic/src/types/function.rs
Normal file
|
@ -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<Span>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<KnownFunction>,
|
||||||
|
|
||||||
|
/// 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<DataclassTransformerParams>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<FunctionLiteral<'db>> {
|
||||||
|
// 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<GenericContext<'db>>,
|
||||||
|
) -> 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<usize>,
|
||||||
|
) -> 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<FunctionSpans> {
|
||||||
|
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<GenericContext<'db>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<KnownFunction> {
|
||||||
|
// 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<usize>,
|
||||||
|
) -> Option<(Span, Span)> {
|
||||||
|
self.last_definition(db).parameter_span(db, parameter_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spans(self, db: &'db dyn Db) -> Option<FunctionSpans> {
|
||||||
|
self.last_definition(db).spans(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[salsa::tracked(returns(ref))]
|
||||||
|
fn overloads_and_implementation(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
) -> (Box<[OverloadLiteral<'db>]>, Option<OverloadLiteral<'db>>) {
|
||||||
|
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<Item = OverloadLiteral<'db>> + '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<KnownFunction> {
|
||||||
|
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<usize>,
|
||||||
|
) -> 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<FunctionSpans> {
|
||||||
|
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<OverloadLiteral<'db>>) {
|
||||||
|
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<Item = OverloadLiteral<'db>> + '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<TypeVarInstance<'db>>,
|
||||||
|
) {
|
||||||
|
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<CallableSignature<'db>> {
|
||||||
|
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<ClassInfoConstraintFunction> {
|
||||||
|
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<Self> {
|
||||||
|
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}`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,19 +81,21 @@ use crate::types::diagnostic::{
|
||||||
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
||||||
report_invalid_return_type, report_possibly_unbound_attribute,
|
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::generics::GenericContext;
|
||||||
use crate::types::mro::MroErrorKind;
|
use crate::types::mro::MroErrorKind;
|
||||||
use crate::types::signatures::{CallableSignature, Signature};
|
use crate::types::signatures::{CallableSignature, Signature};
|
||||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams,
|
BareTypeAliasType, CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams,
|
||||||
DynamicType, FunctionDecorators, FunctionType, GenericAlias, IntersectionBuilder,
|
DynamicType, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass,
|
||||||
IntersectionType, KnownClass, KnownFunction, KnownInstanceType, MemberLookupPolicy,
|
KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
|
||||||
MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType,
|
ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Symbol,
|
||||||
StringLiteralType, SubclassOfType, Symbol, SymbolAndQualifiers, Truthiness, TupleType, Type,
|
SymbolAndQualifiers, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
|
||||||
TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints,
|
TypeArrayDisplay, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
|
||||||
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type,
|
TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
|
||||||
todo_type,
|
|
||||||
};
|
};
|
||||||
use crate::unpack::{Unpack, UnpackPosition};
|
use crate::unpack::{Unpack, UnpackPosition};
|
||||||
use crate::util::subscript::{PyIndex, PySlice};
|
use crate::util::subscript::{PyIndex, PySlice};
|
||||||
|
@ -1131,12 +1133,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for function in self.called_functions.union(&public_functions) {
|
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;
|
continue;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Check that the overloaded function has at least two overloads
|
// 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());
|
let function_node = function.node(self.db(), self.file());
|
||||||
if let Some(builder) = self
|
if let Some(builder) = self
|
||||||
.context
|
.context
|
||||||
|
@ -1157,7 +1160,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// Check that the overloaded function has an implementation. Overload definitions
|
// Check that the overloaded function has an implementation. Overload definitions
|
||||||
// within stub files, protocols, and on abstract methods within abstract base classes
|
// within stub files, protocols, and on abstract methods within abstract base classes
|
||||||
// are exempt from this check.
|
// 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;
|
let mut implementation_required = true;
|
||||||
|
|
||||||
if let NodeWithScopeKind::Class(class_node_ref) = scope {
|
if let NodeWithScopeKind::Class(class_node_ref) = scope {
|
||||||
|
@ -1169,7 +1172,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
if class.is_protocol(self.db())
|
if class.is_protocol(self.db())
|
||||||
|| (class.is_abstract(self.db())
|
|| (class.is_abstract(self.db())
|
||||||
&& overloaded.overloads.iter().all(|overload| {
|
&& overloads.iter().all(|overload| {
|
||||||
overload.has_known_decorator(
|
overload.has_known_decorator(
|
||||||
self.db(),
|
self.db(),
|
||||||
FunctionDecorators::ABSTRACT_METHOD,
|
FunctionDecorators::ABSTRACT_METHOD,
|
||||||
|
@ -1199,7 +1202,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
let mut decorator_present = false;
|
let mut decorator_present = false;
|
||||||
let mut decorator_missing = vec![];
|
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) {
|
if function.has_known_decorator(self.db(), decorator) {
|
||||||
decorator_present = true;
|
decorator_present = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1240,8 +1243,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
(FunctionDecorators::FINAL, "final"),
|
(FunctionDecorators::FINAL, "final"),
|
||||||
(FunctionDecorators::OVERRIDE, "override"),
|
(FunctionDecorators::OVERRIDE, "override"),
|
||||||
] {
|
] {
|
||||||
if let Some(implementation) = overloaded.implementation.as_ref() {
|
if let Some(implementation) = implementation {
|
||||||
for overload in &overloaded.overloads {
|
for overload in overloads.as_ref() {
|
||||||
if !overload.has_known_decorator(self.db(), decorator) {
|
if !overload.has_known_decorator(self.db(), decorator) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1263,7 +1266,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut overloads = overloaded.overloads.iter();
|
let mut overloads = overloads.iter();
|
||||||
let Some(first_overload) = overloads.next() else {
|
let Some(first_overload) = overloads.next() else {
|
||||||
continue;
|
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);
|
KnownFunction::try_from_definition_and_name(self.db(), definition, name);
|
||||||
|
|
||||||
let body_scope = self
|
let body_scope = self
|
||||||
|
@ -2035,17 +2038,23 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.node_scope(NodeWithScopeRef::Function(function))
|
.node_scope(NodeWithScopeRef::Function(function))
|
||||||
.to_scope_id(self.db(), self.file());
|
.to_scope_id(self.db(), self.file());
|
||||||
|
|
||||||
let inherited_generic_context = None;
|
let overload_literal = OverloadLiteral::new(
|
||||||
let type_mappings = Box::from([]);
|
|
||||||
|
|
||||||
let mut inferred_ty = Type::FunctionLiteral(FunctionType::new(
|
|
||||||
self.db(),
|
self.db(),
|
||||||
&name.id,
|
&name.id,
|
||||||
function_kind,
|
known_function,
|
||||||
body_scope,
|
body_scope,
|
||||||
function_decorators,
|
function_decorators,
|
||||||
dataclass_transformer_params,
|
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,
|
type_mappings,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -2314,14 +2323,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
// overload, or an overload and the implementation both. Nevertheless, this is not
|
// 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
|
// allowed. We do not try to treat the offenders intelligently -- just use the
|
||||||
// params of the last seen usage of `@dataclass_transform`
|
// params of the last seen usage of `@dataclass_transform`
|
||||||
if let Some(overloaded) = f.to_overloaded(self.db()) {
|
let params = f
|
||||||
overloaded.overloads.iter().for_each(|overload| {
|
.iter_overloads_and_implementation(self.db())
|
||||||
if let Some(params) = overload.dataclass_transformer_params(self.db()) {
|
.find_map(|overload| overload.dataclass_transformer_params(self.db()));
|
||||||
dataclass_params = Some(params.into());
|
if let Some(params) = params {
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(params) = f.dataclass_transformer_params(self.db()) {
|
|
||||||
dataclass_params = Some(params.into());
|
dataclass_params = Some(params.into());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::semantic_index::predicate::{
|
||||||
};
|
};
|
||||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||||
use crate::semantic_index::symbol_table;
|
use crate::semantic_index::symbol_table;
|
||||||
|
use crate::types::function::KnownFunction;
|
||||||
use crate::types::infer::infer_same_file_expression_type;
|
use crate::types::infer::infer_same_file_expression_type;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
IntersectionBuilder, KnownClass, SubclassOfType, Truthiness, Type, UnionBuilder,
|
IntersectionBuilder, KnownClass, SubclassOfType, Truthiness, Type, UnionBuilder,
|
||||||
|
@ -20,7 +21,7 @@ use ruff_python_ast::{BoolOp, ExprBoolOp};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::collections::hash_map::Entry;
|
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.
|
/// Return the type constraint that `test` (if true) would place on `symbol`, if any.
|
||||||
///
|
///
|
||||||
|
|
|
@ -7,9 +7,8 @@ use ruff_python_ast::name::Name;
|
||||||
use crate::{
|
use crate::{
|
||||||
semantic_index::{symbol_table, use_def_map},
|
semantic_index::{symbol_table, use_def_map},
|
||||||
symbol::{symbol_from_bindings, symbol_from_declarations},
|
symbol::{symbol_from_bindings, symbol_from_declarations},
|
||||||
types::{
|
types::function::KnownFunction,
|
||||||
ClassBase, ClassLiteral, KnownFunction, Type, TypeMapping, TypeQualifiers, TypeVarInstance,
|
types::{ClassBase, ClassLiteral, Type, TypeMapping, TypeQualifiers, TypeVarInstance},
|
||||||
},
|
|
||||||
{Db, FxOrderSet},
|
{Db, FxOrderSet},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -53,10 +53,6 @@ impl<'db> CallableSignature<'db> {
|
||||||
self.overloads.iter()
|
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 {
|
pub(crate) fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||||
Self::from_overloads(
|
Self::from_overloads(
|
||||||
self.overloads
|
self.overloads
|
||||||
|
@ -1538,7 +1534,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db::tests::{TestDb, setup_db};
|
use crate::db::tests::{TestDb, setup_db};
|
||||||
use crate::symbol::global_symbol;
|
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 _;
|
use ruff_db::system::DbWithWritableSystem as _;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -1559,9 +1556,11 @@ mod tests {
|
||||||
fn empty() {
|
fn empty() {
|
||||||
let mut db = setup_db();
|
let mut db = setup_db();
|
||||||
db.write_dedented("/src/a.py", "def f(): ...").unwrap();
|
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!(sig.return_ty.is_none());
|
||||||
assert_params(&sig, &[]);
|
assert_params(&sig, &[]);
|
||||||
|
@ -1582,9 +1581,11 @@ mod tests {
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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_eq!(sig.return_ty.unwrap().display(&db).to_string(), "bytes");
|
||||||
assert_params(
|
assert_params(
|
||||||
|
@ -1633,9 +1634,11 @@ mod tests {
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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 [
|
let [
|
||||||
Parameter {
|
Parameter {
|
||||||
|
@ -1669,9 +1672,11 @@ mod tests {
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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 [
|
let [
|
||||||
Parameter {
|
Parameter {
|
||||||
|
@ -1705,9 +1710,11 @@ mod tests {
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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 [
|
let [
|
||||||
Parameter {
|
Parameter {
|
||||||
|
@ -1751,9 +1758,11 @@ mod tests {
|
||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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 [
|
let [
|
||||||
Parameter {
|
Parameter {
|
||||||
|
@ -1789,15 +1798,13 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let func = get_function_f(&db, "/src/a.py");
|
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
|
// With no decorators, internal and external signature are the same
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
func.signature(&db),
|
func.signature(&db),
|
||||||
&FunctionSignature {
|
&CallableSignature::single(expected_sig)
|
||||||
overloads: CallableSignature::single(expected_sig),
|
|
||||||
implementation: None
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue