mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ty] Homogeneous and mixed tuples (#18600)
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / cargo fuzz build (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
We already had support for homogeneous tuples (`tuple[int, ...]`). This PR extends this to also support mixed tuples (`tuple[str, str, *tuple[int, ...], str str]`). A mixed tuple consists of a fixed-length (possibly empty) prefix and suffix, and a variable-length portion in the middle. Every element of the variable-length portion must be of the same type. A homogeneous tuple is then just a mixed tuple with an empty prefix and suffix. The new data representation uses different Rust types for a fixed-length (aka heterogeneous) tuple. Another option would have been to use the `VariableLengthTuple` representation for all tuples, and to wrap the "variable + suffix" portion in an `Option`. I don't think that would simplify the method implementations much, though, since we would still have a 2×2 case analysis for most of them. One wrinkle is that the definition of the `tuple` class in the typeshed has a single typevar, and canonically represents a homogeneous tuple. When getting the class of a tuple instance, that means that we have to summarize our detailed mixed tuple type information into its "homogeneous supertype". (We were already doing this for heterogeneous types.) A similar thing happens when concatenating two mixed tuples: the variable-length portion and suffix of the LHS, and the prefix and variable-length portion of the RHS, all get unioned into the variable-length portion of the result. The LHS prefix and RHS suffix carry through unchanged. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
d9266284df
commit
ea812d0813
32 changed files with 2432 additions and 758 deletions
114
crates/ty/docs/rules.md
generated
114
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#L95)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L96)
|
||||||
</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#L139)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L140)
|
||||||
</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#L165)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L166)
|
||||||
</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#L190)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L191)
|
||||||
</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#L216)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L217)
|
||||||
</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#L260)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L261)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `duplicate-kw-only`
|
## `duplicate-kw-only`
|
||||||
|
@ -238,7 +238,7 @@ class A: # Crash at runtime
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only)
|
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only)
|
||||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L281)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L282)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `escape-character-in-forward-annotation`
|
## `escape-character-in-forward-annotation`
|
||||||
|
@ -375,7 +375,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#L313)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L314)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `inconsistent-mro`
|
## `inconsistent-mro`
|
||||||
|
@ -404,7 +404,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#L399)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L400)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `index-out-of-bounds`
|
## `index-out-of-bounds`
|
||||||
|
@ -429,7 +429,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#L423)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L424)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-argument-type`
|
## `invalid-argument-type`
|
||||||
|
@ -455,7 +455,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#L443)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L444)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-assignment`
|
## `invalid-assignment`
|
||||||
|
@ -482,7 +482,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#L483)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L484)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-attribute-access`
|
## `invalid-attribute-access`
|
||||||
|
@ -515,7 +515,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#L1487)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1488)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-base`
|
## `invalid-base`
|
||||||
|
@ -538,7 +538,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#L505)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L506)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-context-manager`
|
## `invalid-context-manager`
|
||||||
|
@ -564,7 +564,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#L556)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L557)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-declaration`
|
## `invalid-declaration`
|
||||||
|
@ -592,7 +592,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#L577)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L578)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-exception-caught`
|
## `invalid-exception-caught`
|
||||||
|
@ -633,7 +633,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#L600)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L601)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-generic-class`
|
## `invalid-generic-class`
|
||||||
|
@ -664,7 +664,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#L636)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L637)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-legacy-type-variable`
|
## `invalid-legacy-type-variable`
|
||||||
|
@ -697,7 +697,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#L662)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-metaclass`
|
## `invalid-metaclass`
|
||||||
|
@ -729,7 +729,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#L711)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L712)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-overload`
|
## `invalid-overload`
|
||||||
|
@ -777,7 +777,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#L738)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-parameter-default`
|
## `invalid-parameter-default`
|
||||||
|
@ -802,7 +802,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#L781)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L782)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-protocol`
|
## `invalid-protocol`
|
||||||
|
@ -835,7 +835,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#L371)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L372)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-raise`
|
## `invalid-raise`
|
||||||
|
@ -883,7 +883,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#L801)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L802)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-return-type`
|
## `invalid-return-type`
|
||||||
|
@ -907,7 +907,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#L464)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L465)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-super-argument`
|
## `invalid-super-argument`
|
||||||
|
@ -951,7 +951,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#L844)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-syntax-in-forward-annotation`
|
## `invalid-syntax-in-forward-annotation`
|
||||||
|
@ -991,7 +991,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#L690)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L691)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-checking-constant`
|
## `invalid-type-checking-constant`
|
||||||
|
@ -1020,7 +1020,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#L883)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L884)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-form`
|
## `invalid-type-form`
|
||||||
|
@ -1049,7 +1049,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#L907)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L908)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-guard-call`
|
## `invalid-type-guard-call`
|
||||||
|
@ -1082,7 +1082,7 @@ f(10) # Error
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call)
|
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call)
|
||||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L960)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-guard-definition`
|
## `invalid-type-guard-definition`
|
||||||
|
@ -1115,7 +1115,7 @@ class C:
|
||||||
|
|
||||||
### Links
|
### Links
|
||||||
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition)
|
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition)
|
||||||
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L931)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L932)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-type-variable-constraints`
|
## `invalid-type-variable-constraints`
|
||||||
|
@ -1149,7 +1149,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#L987)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L988)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `missing-argument`
|
## `missing-argument`
|
||||||
|
@ -1173,7 +1173,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#L1016)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1017)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `no-matching-overload`
|
## `no-matching-overload`
|
||||||
|
@ -1201,7 +1201,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#L1035)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `non-subscriptable`
|
## `non-subscriptable`
|
||||||
|
@ -1224,7 +1224,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#L1058)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1059)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `not-iterable`
|
## `not-iterable`
|
||||||
|
@ -1249,7 +1249,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#L1076)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1077)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `parameter-already-assigned`
|
## `parameter-already-assigned`
|
||||||
|
@ -1275,7 +1275,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#L1127)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1128)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `raw-string-type-annotation`
|
## `raw-string-type-annotation`
|
||||||
|
@ -1334,7 +1334,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#L1463)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1464)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `subclass-of-final-class`
|
## `subclass-of-final-class`
|
||||||
|
@ -1362,7 +1362,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#L1218)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1219)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `too-many-positional-arguments`
|
## `too-many-positional-arguments`
|
||||||
|
@ -1388,7 +1388,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#L1263)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1264)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `type-assertion-failure`
|
## `type-assertion-failure`
|
||||||
|
@ -1415,7 +1415,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#L1241)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1242)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unavailable-implicit-super-arguments`
|
## `unavailable-implicit-super-arguments`
|
||||||
|
@ -1459,7 +1459,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#L1284)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1285)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unknown-argument`
|
## `unknown-argument`
|
||||||
|
@ -1485,7 +1485,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#L1341)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1342)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unresolved-attribute`
|
## `unresolved-attribute`
|
||||||
|
@ -1512,7 +1512,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#L1362)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1363)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unresolved-import`
|
## `unresolved-import`
|
||||||
|
@ -1536,7 +1536,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#L1384)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1385)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unresolved-reference`
|
## `unresolved-reference`
|
||||||
|
@ -1560,7 +1560,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#L1403)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1404)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unsupported-bool-conversion`
|
## `unsupported-bool-conversion`
|
||||||
|
@ -1596,7 +1596,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#L1096)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1097)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unsupported-operator`
|
## `unsupported-operator`
|
||||||
|
@ -1623,7 +1623,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#L1422)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1423)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `zero-stepsize-in-slice`
|
## `zero-stepsize-in-slice`
|
||||||
|
@ -1647,7 +1647,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#L1444)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1445)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `invalid-ignore-comment`
|
## `invalid-ignore-comment`
|
||||||
|
@ -1703,7 +1703,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#L1148)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1149)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `possibly-unbound-implicit-call`
|
## `possibly-unbound-implicit-call`
|
||||||
|
@ -1734,7 +1734,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#L113)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L114)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `possibly-unbound-import`
|
## `possibly-unbound-import`
|
||||||
|
@ -1765,7 +1765,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#L1170)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `redundant-cast`
|
## `redundant-cast`
|
||||||
|
@ -1791,7 +1791,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#L1515)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1516)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `undefined-reveal`
|
## `undefined-reveal`
|
||||||
|
@ -1814,7 +1814,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#L1323)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1324)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unknown-rule`
|
## `unknown-rule`
|
||||||
|
@ -1882,7 +1882,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#L523)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L524)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `division-by-zero`
|
## `division-by-zero`
|
||||||
|
@ -1905,7 +1905,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#L242)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L243)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `possibly-unresolved-reference`
|
## `possibly-unresolved-reference`
|
||||||
|
@ -1932,7 +1932,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#L1196)
|
* [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1197)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## `unused-ignore-comment`
|
## `unused-ignore-comment`
|
||||||
|
|
|
@ -58,7 +58,7 @@ reveal_type(c) # revealed: tuple[str, int]
|
||||||
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
|
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
|
||||||
reveal_type(e) # revealed: tuple[str, ...]
|
reveal_type(e) # revealed: tuple[str, ...]
|
||||||
|
|
||||||
reveal_type(f) # revealed: @Todo(PEP 646)
|
reveal_type(f) # revealed: tuple[str, *tuple[int, ...], bytes]
|
||||||
reveal_type(g) # revealed: @Todo(PEP 646)
|
reveal_type(g) # revealed: @Todo(PEP 646)
|
||||||
|
|
||||||
reveal_type(h) # revealed: tuple[list[int], list[int]]
|
reveal_type(h) # revealed: tuple[list[int], list[int]]
|
||||||
|
|
|
@ -1722,7 +1722,7 @@ d = True
|
||||||
reveal_type(d.__class__) # revealed: <class 'bool'>
|
reveal_type(d.__class__) # revealed: <class 'bool'>
|
||||||
|
|
||||||
e = (42, 42)
|
e = (42, 42)
|
||||||
reveal_type(e.__class__) # revealed: <class 'tuple'>
|
reveal_type(e.__class__) # revealed: <class 'tuple[Literal[42], Literal[42]]'>
|
||||||
|
|
||||||
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
|
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
|
||||||
reveal_type(a.__class__) # revealed: type[int]
|
reveal_type(a.__class__) # revealed: type[int]
|
||||||
|
|
|
@ -17,6 +17,32 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def _(x: tuple[int, ...], y: tuple[str, ...]):
|
def _(x: tuple[int, ...], y: tuple[str, ...]):
|
||||||
|
reveal_type(x + x) # revealed: tuple[int, ...]
|
||||||
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||||
reveal_type(x + (1, 2)) # revealed: tuple[int, ...]
|
reveal_type((1, 2) + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...]]
|
||||||
|
reveal_type(x + (3, 4)) # revealed: tuple[*tuple[int, ...], Literal[3], Literal[4]]
|
||||||
|
reveal_type((1, 2) + x + (3, 4)) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[3], Literal[4]]
|
||||||
|
reveal_type((1, 2) + y + (3, 4) + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int | str, ...]]
|
||||||
|
```
|
||||||
|
|
||||||
|
We get the same results even when we use a legacy type alias, even though this involves first
|
||||||
|
inferring the `tuple[...]` expression as a value form. (Doing so gives a generic alias of the
|
||||||
|
`tuple` type, but as a special case, we include the full detailed tuple element specification in
|
||||||
|
specializations of `tuple`.)
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
OneTwo = tuple[Literal[1], Literal[2]]
|
||||||
|
ThreeFour = tuple[Literal[3], Literal[4]]
|
||||||
|
IntTuple = tuple[int, ...]
|
||||||
|
StrTuple = tuple[str, ...]
|
||||||
|
|
||||||
|
def _(one_two: OneTwo, x: IntTuple, y: StrTuple, three_four: ThreeFour):
|
||||||
|
reveal_type(x + x) # revealed: tuple[int, ...]
|
||||||
|
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||||
|
reveal_type(one_two + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...]]
|
||||||
|
reveal_type(x + three_four) # revealed: tuple[*tuple[int, ...], Literal[3], Literal[4]]
|
||||||
|
reveal_type(one_two + x + three_four) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[3], Literal[4]]
|
||||||
|
reveal_type(one_two + y + three_four + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int | str, ...]]
|
||||||
```
|
```
|
||||||
|
|
|
@ -130,6 +130,44 @@ reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int
|
||||||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Inferring tuple parameter types
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
def takes_mixed_tuple_suffix(x: tuple[int, bytes, *tuple[str, ...], T, int]) -> T:
|
||||||
|
return x[-2]
|
||||||
|
|
||||||
|
# TODO: revealed: Literal[True]
|
||||||
|
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||||
|
|
||||||
|
def takes_mixed_tuple_prefix(x: tuple[int, T, *tuple[str, ...], bool, int]) -> T:
|
||||||
|
return x[1]
|
||||||
|
|
||||||
|
# TODO: revealed: Literal[b"foo"]
|
||||||
|
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||||
|
|
||||||
|
def takes_fixed_tuple(x: tuple[T, int]) -> T:
|
||||||
|
return x[0]
|
||||||
|
|
||||||
|
reveal_type(takes_fixed_tuple((True, 42))) # revealed: Literal[True]
|
||||||
|
|
||||||
|
def takes_homogeneous_tuple(x: tuple[T, ...]) -> T:
|
||||||
|
return x[0]
|
||||||
|
|
||||||
|
# TODO: revealed: Literal[42]
|
||||||
|
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Unknown
|
||||||
|
# TODO: revealed: Literal[42, 43]
|
||||||
|
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
||||||
## Inferring a bound typevar
|
## Inferring a bound typevar
|
||||||
|
|
||||||
<!-- snapshot-diagnostics -->
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
|
@ -125,6 +125,35 @@ reveal_type(takes_in_protocol(ExplicitSub())) # revealed: int
|
||||||
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Inferring tuple parameter types
|
||||||
|
|
||||||
|
```py
|
||||||
|
def takes_mixed_tuple_suffix[T](x: tuple[int, bytes, *tuple[str, ...], T, int]) -> T:
|
||||||
|
return x[-2]
|
||||||
|
|
||||||
|
# TODO: revealed: Literal[True]
|
||||||
|
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||||
|
|
||||||
|
def takes_mixed_tuple_prefix[T](x: tuple[int, T, *tuple[str, ...], bool, int]) -> T:
|
||||||
|
return x[1]
|
||||||
|
|
||||||
|
# TODO: revealed: Literal[b"foo"]
|
||||||
|
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||||
|
|
||||||
|
def takes_fixed_tuple[T](x: tuple[T, int]) -> T:
|
||||||
|
return x[0]
|
||||||
|
|
||||||
|
reveal_type(takes_fixed_tuple((True, 42))) # revealed: Literal[True]
|
||||||
|
|
||||||
|
def takes_homogeneous_tuple[T](x: tuple[T, ...]) -> T:
|
||||||
|
return x[0]
|
||||||
|
|
||||||
|
# TODO: revealed: Literal[42]
|
||||||
|
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Unknown
|
||||||
|
# TODO: revealed: Literal[42, 43]
|
||||||
|
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Unknown
|
||||||
|
```
|
||||||
|
|
||||||
## Inferring a bound typevar
|
## Inferring a bound typevar
|
||||||
|
|
||||||
<!-- snapshot-diagnostics -->
|
<!-- snapshot-diagnostics -->
|
||||||
|
|
|
@ -192,10 +192,9 @@ def _(t1: tuple[int | None, int | None], t2: tuple[int, int] | tuple[None, None]
|
||||||
reveal_type(t1[1]) # revealed: int | None
|
reveal_type(t1[1]) # revealed: int | None
|
||||||
|
|
||||||
if t2[0] is not None:
|
if t2[0] is not None:
|
||||||
|
reveal_type(t2[0]) # revealed: int
|
||||||
# TODO: should be int
|
# TODO: should be int
|
||||||
reveal_type(t2[0]) # revealed: Unknown & ~None
|
reveal_type(t2[1]) # revealed: int | None
|
||||||
# TODO: should be int
|
|
||||||
reveal_type(t2[1]) # revealed: Unknown
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### String subscript
|
### String subscript
|
||||||
|
|
|
@ -215,12 +215,12 @@ def _(a: tuple[str, int] | tuple[int, str], c: C[Any]):
|
||||||
# TODO: Should be `tuple[int, str]`
|
# TODO: Should be `tuple[int, str]`
|
||||||
reveal_type(a) # revealed: tuple[str, int] | tuple[int, str]
|
reveal_type(a) # revealed: tuple[str, int] | tuple[int, str]
|
||||||
# TODO: Should be `str`
|
# TODO: Should be `str`
|
||||||
reveal_type(a[1]) # revealed: Unknown
|
reveal_type(a[1]) # revealed: str | int
|
||||||
|
|
||||||
if reveal_type(is_int(a[0])): # revealed: TypeIs[int @ a[0]]
|
if reveal_type(is_int(a[0])): # revealed: TypeIs[int @ a[0]]
|
||||||
# TODO: Should be `tuple[int, str]`
|
# TODO: Should be `tuple[int, str]`
|
||||||
reveal_type(a) # revealed: tuple[str, int] | tuple[int, str]
|
reveal_type(a) # revealed: tuple[str, int] | tuple[int, str]
|
||||||
reveal_type(a[0]) # revealed: Unknown & int
|
reveal_type(a[0]) # revealed: int
|
||||||
|
|
||||||
# TODO: Should be `TypeGuard[str @ c.v]`
|
# TODO: Should be `TypeGuard[str @ c.v]`
|
||||||
if reveal_type(guard_str(c.v)): # revealed: @Todo(`TypeGuard[]` special form)
|
if reveal_type(guard_str(c.v)): # revealed: @Todo(`TypeGuard[]` special form)
|
||||||
|
|
|
@ -69,8 +69,64 @@ def _(m: int, n: int):
|
||||||
t[::0] # error: [zero-stepsize-in-slice]
|
t[::0] # error: [zero-stepsize-in-slice]
|
||||||
|
|
||||||
tuple_slice = t[m:n]
|
tuple_slice = t[m:n]
|
||||||
# TODO: Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
|
reveal_type(tuple_slice) # revealed: tuple[Literal[1, "a", b"b"] | None, ...]
|
||||||
reveal_type(tuple_slice) # revealed: tuple[Unknown, ...]
|
```
|
||||||
|
|
||||||
|
## Slices of homogeneous and mixed tuples
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.11"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
def homogeneous(t: tuple[str, ...]) -> None:
|
||||||
|
reveal_type(t[0]) # revealed: str
|
||||||
|
reveal_type(t[1]) # revealed: str
|
||||||
|
reveal_type(t[2]) # revealed: str
|
||||||
|
reveal_type(t[3]) # revealed: str
|
||||||
|
|
||||||
|
reveal_type(t[-1]) # revealed: str
|
||||||
|
reveal_type(t[-2]) # revealed: str
|
||||||
|
reveal_type(t[-3]) # revealed: str
|
||||||
|
reveal_type(t[-4]) # revealed: str
|
||||||
|
|
||||||
|
def mixed(s: tuple[str, ...]) -> None:
|
||||||
|
t = (1, 2, 3) + s + (8, 9, 10)
|
||||||
|
|
||||||
|
reveal_type(t[0]) # revealed: Literal[1]
|
||||||
|
reveal_type(t[1]) # revealed: Literal[2]
|
||||||
|
reveal_type(t[2]) # revealed: Literal[3]
|
||||||
|
reveal_type(t[3]) # revealed: str | Literal[8]
|
||||||
|
reveal_type(t[4]) # revealed: str | Literal[8, 9]
|
||||||
|
reveal_type(t[5]) # revealed: str | Literal[8, 9, 10]
|
||||||
|
|
||||||
|
reveal_type(t[-1]) # revealed: Literal[10]
|
||||||
|
reveal_type(t[-2]) # revealed: Literal[9]
|
||||||
|
reveal_type(t[-3]) # revealed: Literal[8]
|
||||||
|
reveal_type(t[-4]) # revealed: Literal[3] | str
|
||||||
|
reveal_type(t[-5]) # revealed: Literal[2, 3] | str
|
||||||
|
reveal_type(t[-6]) # revealed: Literal[1, 2, 3] | str
|
||||||
|
```
|
||||||
|
|
||||||
|
## `tuple` as generic alias
|
||||||
|
|
||||||
|
For tuple instances, we can track more detailed information about the length and element types of
|
||||||
|
the tuple. This information carries over to the generic alias that the tuple is an instance of.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def _(a: tuple, b: tuple[int], c: tuple[int, str], d: tuple[int, ...]) -> None:
|
||||||
|
reveal_type(a) # revealed: tuple[Unknown, ...]
|
||||||
|
reveal_type(b) # revealed: tuple[int]
|
||||||
|
reveal_type(c) # revealed: tuple[int, str]
|
||||||
|
reveal_type(d) # revealed: tuple[int, ...]
|
||||||
|
|
||||||
|
reveal_type(tuple) # revealed: <class 'tuple'>
|
||||||
|
reveal_type(tuple[int]) # revealed: <class 'tuple[int]'>
|
||||||
|
reveal_type(tuple[int, str]) # revealed: <class 'tuple[int, str]'>
|
||||||
|
reveal_type(tuple[int, ...]) # revealed: <class 'tuple[int, ...]'>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Inheritance
|
## Inheritance
|
||||||
|
@ -83,8 +139,13 @@ python-version = "3.9"
|
||||||
```py
|
```py
|
||||||
class A(tuple[int, str]): ...
|
class A(tuple[int, str]): ...
|
||||||
|
|
||||||
# revealed: tuple[<class 'A'>, <class 'tuple[@Todo(Generic tuple specializations), ...]'>, <class 'Sequence[@Todo(Generic tuple specializations)]'>, <class 'Reversible[@Todo(Generic tuple specializations)]'>, <class 'Collection[@Todo(Generic tuple specializations)]'>, <class 'Iterable[@Todo(Generic tuple specializations)]'>, <class 'Container[@Todo(Generic tuple specializations)]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||||
reveal_type(A.__mro__)
|
reveal_type(A.__mro__)
|
||||||
|
|
||||||
|
class C(tuple): ...
|
||||||
|
|
||||||
|
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||||
|
reveal_type(C.__mro__)
|
||||||
```
|
```
|
||||||
|
|
||||||
## `typing.Tuple`
|
## `typing.Tuple`
|
||||||
|
@ -109,9 +170,19 @@ def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
|
||||||
Inheriting from `Tuple` results in a MRO with `builtins.tuple` and `typing.Generic`. `Tuple` itself
|
Inheriting from `Tuple` results in a MRO with `builtins.tuple` and `typing.Generic`. `Tuple` itself
|
||||||
is not a class.
|
is not a class.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.9"
|
||||||
|
```
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
|
class A(Tuple[int, str]): ...
|
||||||
|
|
||||||
|
# revealed: tuple[<class 'A'>, <class 'tuple[int, str]'>, <class 'Sequence[int | str]'>, <class 'Reversible[int | str]'>, <class 'Collection[int | str]'>, <class 'Iterable[int | str]'>, <class 'Container[int | str]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||||
|
reveal_type(A.__mro__)
|
||||||
|
|
||||||
class C(Tuple): ...
|
class C(Tuple): ...
|
||||||
|
|
||||||
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
# revealed: tuple[<class 'C'>, <class 'tuple[Unknown, ...]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>]
|
||||||
|
|
|
@ -16,6 +16,28 @@ def _(p: P, q: Q):
|
||||||
assert_type((p, q), tuple[P, Q])
|
assert_type((p, q), tuple[P, Q])
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Instantiating tuples
|
||||||
|
|
||||||
|
Like all classes, tuples can be instantiated by invoking the `tuple` class. When instantiating a
|
||||||
|
specialization of `tuple` we (TODO: should) check that the values passed in match the element types
|
||||||
|
defined in the specialization.
|
||||||
|
|
||||||
|
```py
|
||||||
|
# TODO: revealed: tuple[()]
|
||||||
|
reveal_type(tuple()) # revealed: tuple[Unknown, ...]
|
||||||
|
# TODO: revealed: tuple[Literal[1]]
|
||||||
|
reveal_type(tuple([1])) # revealed: tuple[Unknown, ...]
|
||||||
|
reveal_type(tuple[int]([1])) # revealed: tuple[int]
|
||||||
|
# TODO: error for invalid arguments
|
||||||
|
reveal_type(tuple[int, str]([1])) # revealed: tuple[int, str]
|
||||||
|
|
||||||
|
reveal_type(().__class__()) # revealed: tuple[()]
|
||||||
|
# TODO: error for invalid arguments
|
||||||
|
reveal_type((1,).__class__()) # revealed: tuple[Literal[1]]
|
||||||
|
# TODO: error for invalid arguments
|
||||||
|
reveal_type((1, 2).__class__()) # revealed: tuple[Literal[1], Literal[2]]
|
||||||
|
```
|
||||||
|
|
||||||
## Subtyping relationships
|
## Subtyping relationships
|
||||||
|
|
||||||
The type `tuple[S1, S2]` is a subtype of `tuple[T1, T2]` if and only if `S1` is a subtype of `T1`
|
The type `tuple[S1, S2]` is a subtype of `tuple[T1, T2]` if and only if `S1` is a subtype of `T1`
|
||||||
|
@ -60,10 +82,7 @@ class AnotherEmptyTuple(tuple[()]): ...
|
||||||
|
|
||||||
static_assert(not is_equivalent_to(AnotherEmptyTuple, tuple[()]))
|
static_assert(not is_equivalent_to(AnotherEmptyTuple, tuple[()]))
|
||||||
|
|
||||||
# TODO: These should not be errors
|
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(is_subtype_of(AnotherEmptyTuple, tuple[()]))
|
static_assert(is_subtype_of(AnotherEmptyTuple, tuple[()]))
|
||||||
# error: [static-assert-error]
|
|
||||||
static_assert(is_assignable_to(AnotherEmptyTuple, tuple[()]))
|
static_assert(is_assignable_to(AnotherEmptyTuple, tuple[()]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -158,8 +177,6 @@ class NotAlwaysTruthyTuple(tuple[int]):
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# TODO: This assignment should be allowed
|
|
||||||
# error: [invalid-assignment]
|
|
||||||
t: tuple[int] = NotAlwaysTruthyTuple((1,))
|
t: tuple[int] = NotAlwaysTruthyTuple((1,))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -303,6 +303,11 @@ static_assert(not is_assignable_to(tuple[Any, Literal[2]], tuple[int, str]))
|
||||||
|
|
||||||
## Assignability of heterogeneous tuple types to homogeneous tuple types
|
## Assignability of heterogeneous tuple types to homogeneous tuple types
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
While a homogeneous tuple type is not assignable to any heterogeneous tuple types, a heterogeneous
|
While a homogeneous tuple type is not assignable to any heterogeneous tuple types, a heterogeneous
|
||||||
tuple type can be assignable to a homogeneous tuple type, and homogeneous tuple types can be
|
tuple type can be assignable to a homogeneous tuple type, and homogeneous tuple types can be
|
||||||
assignable to `Sequence`:
|
assignable to `Sequence`:
|
||||||
|
@ -312,6 +317,11 @@ from typing import Literal, Any, Sequence
|
||||||
from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy
|
from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy
|
||||||
|
|
||||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
|
||||||
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[Literal[2], ...]]))
|
||||||
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[*tuple[Literal[1], ...], Literal[2]]))
|
||||||
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[str, ...], Literal[2]]))
|
||||||
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[2], *tuple[str, ...]]))
|
||||||
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[*tuple[str, ...], Literal[1], Literal[2]]))
|
||||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int, ...]))
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int, ...]))
|
||||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
|
||||||
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Any, ...]))
|
static_assert(is_assignable_to(tuple[Literal[1], Literal[2]], tuple[Any, ...]))
|
||||||
|
@ -330,6 +340,218 @@ static_assert(is_assignable_to(tuple[()], Sequence[int]))
|
||||||
static_assert(not is_assignable_to(tuple[int, int], tuple[str, ...]))
|
static_assert(not is_assignable_to(tuple[int, int], tuple[str, ...]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Assignability of two mixed tuple types
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal, Any, Sequence
|
||||||
|
from ty_extensions import static_assert, is_assignable_to, Not, AlwaysFalsy
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], *tuple[int, ...]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[*tuple[int, ...], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[*tuple[int, ...]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[Literal[1], *tuple[int, ...]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[*tuple[int, ...], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_assignable_to(
|
||||||
|
tuple[*tuple[int, ...]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Assignability of the gradual tuple
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
As a [special case][gradual tuple], `tuple[Any, ...]` is a [gradual][gradual form] tuple type, which
|
||||||
|
is assignable to every tuple of any length.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Any
|
||||||
|
from ty_extensions import static_assert, is_assignable_to
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(tuple[Any, ...], tuple[Any, ...]))
|
||||||
|
static_assert(is_assignable_to(tuple[Any, ...], tuple[Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[Any, ...], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[Any, ...], tuple[int, ...]))
|
||||||
|
static_assert(is_assignable_to(tuple[Any, ...], tuple[int]))
|
||||||
|
static_assert(is_assignable_to(tuple[Any, ...], tuple[int, int]))
|
||||||
|
```
|
||||||
|
|
||||||
|
This also applies when `tuple[Any, ...]` is unpacked into a mixed tuple.
|
||||||
|
|
||||||
|
```py
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[Any, ...]]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[Any, ...]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[int, ...]]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, ...]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...]], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[*tuple[Any, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[*tuple[int, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[int]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[Any, ...], int], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[Any, ...], int], tuple[int, int]))
|
||||||
|
```
|
||||||
|
|
||||||
|
The same is not true of fully static tuple types, since an unbounded homogeneous tuple is defined to
|
||||||
|
be the _union_ of all tuple lengths, not the _gradual choice_ of them.
|
||||||
|
|
||||||
|
```py
|
||||||
|
static_assert(is_assignable_to(tuple[int, ...], tuple[Any, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, ...], tuple[Any]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, ...], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, ...], tuple[int, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, ...], tuple[int]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, ...], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, *tuple[Any, ...]]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[Any, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[Any]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, *tuple[int, ...]]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...]], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[*tuple[Any, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[Any]))
|
||||||
|
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[*tuple[int, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[*tuple[int, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[int]))
|
||||||
|
static_assert(not is_assignable_to(tuple[*tuple[int, ...], int], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[Any]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||||
|
static_assert(is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int]))
|
||||||
|
static_assert(not is_assignable_to(tuple[int, *tuple[int, ...], int], tuple[int, int]))
|
||||||
|
```
|
||||||
|
|
||||||
## Union types
|
## Union types
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
@ -828,8 +1050,8 @@ sets of possible materializations -- if they represent the same sets of possible
|
||||||
sets of sets of possible runtime objects). By this principle `int | Any` is gradually equivalent to
|
sets of sets of possible runtime objects). By this principle `int | Any` is gradually equivalent to
|
||||||
`Unknown | int`, since they have exactly the same sets of posisble materializations. But
|
`Unknown | int`, since they have exactly the same sets of posisble materializations. But
|
||||||
`bool | Any` is not equivalent to `int`, since there are many possible materializations of
|
`bool | Any` is not equivalent to `int`, since there are many possible materializations of
|
||||||
`bool | Any` that are not assignable to `int`. It is therefore *not* necessary for `X` to be
|
`bool | Any` that are not assignable to `int`. It is therefore _not_ necessary for `X` to be
|
||||||
gradually equivalent to `Y` in order for `Foo[X]` to be assignable to `Foo[Y]`; it is *only*
|
gradually equivalent to `Y` in order for `Foo[X]` to be assignable to `Foo[Y]`; it is _only_
|
||||||
necessary for `X` and `Y` to be mutually assignable.
|
necessary for `X` and `Y` to be mutually assignable.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
@ -887,4 +1109,6 @@ static_assert(not is_assignable_to(TypeGuard[Unknown], str)) # error: [static-a
|
||||||
static_assert(not is_assignable_to(TypeIs[Any], str))
|
static_assert(not is_assignable_to(TypeIs[Any], str))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form
|
||||||
|
[gradual tuple]: https://typing.python.org/en/latest/spec/tuples.html#tuple-type-form
|
||||||
[typing documentation]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
|
[typing documentation]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
|
||||||
|
|
|
@ -48,8 +48,7 @@ static_assert(not is_fully_static(Any | str))
|
||||||
static_assert(not is_fully_static(str | Unknown))
|
static_assert(not is_fully_static(str | Unknown))
|
||||||
static_assert(not is_fully_static(Intersection[Any, Not[LiteralString]]))
|
static_assert(not is_fully_static(Intersection[Any, Not[LiteralString]]))
|
||||||
|
|
||||||
# TODO: should pass
|
static_assert(not is_fully_static(tuple[Any, ...]))
|
||||||
static_assert(not is_fully_static(tuple[Any, ...])) # error: [static-assert-error]
|
|
||||||
|
|
||||||
static_assert(not is_fully_static(tuple[int, Any]))
|
static_assert(not is_fully_static(tuple[int, Any]))
|
||||||
static_assert(not is_fully_static(type[Any]))
|
static_assert(not is_fully_static(type[Any]))
|
||||||
|
|
|
@ -159,6 +159,11 @@ from typing import Literal, Any, Sequence
|
||||||
from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy
|
from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy
|
||||||
|
|
||||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1, 2], ...]))
|
||||||
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[Literal[2], ...]]))
|
||||||
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[*tuple[Literal[1], ...], Literal[2]]))
|
||||||
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1], *tuple[str, ...], Literal[2]]))
|
||||||
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Literal[1], Literal[2], *tuple[str, ...]]))
|
||||||
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[*tuple[str, ...], Literal[1], Literal[2]]))
|
||||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int, ...]))
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int, ...]))
|
||||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[int | str, ...]))
|
||||||
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...]))
|
static_assert(is_subtype_of(tuple[Literal[1], Literal[2]], tuple[Not[AlwaysFalsy], ...]))
|
||||||
|
@ -177,6 +182,215 @@ static_assert(not is_subtype_of(tuple[int, ...], Sequence[Any]))
|
||||||
static_assert(not is_subtype_of(tuple[Any, ...], Sequence[int]))
|
static_assert(not is_subtype_of(tuple[Any, ...], Sequence[int]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Subtyping of two mixed tuple types
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal, Any, Sequence
|
||||||
|
from ty_extensions import static_assert, is_subtype_of, Not, AlwaysFalsy
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], *tuple[int, ...]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[*tuple[int, ...], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[*tuple[int, ...]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[Literal[1], *tuple[int, ...], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[Literal[1], *tuple[int, ...]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[*tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[*tuple[int, ...], Literal[10]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
static_assert(
|
||||||
|
not is_subtype_of(
|
||||||
|
tuple[*tuple[int, ...]],
|
||||||
|
tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[9], Literal[10]],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subtyping of the gradual tuple
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
As a [special case][gradual tuple], `tuple[Any, ...]` is a [gradual][gradual form] tuple type.
|
||||||
|
However, the special-case behavior of assignability does not also apply to subtyping, since gradual
|
||||||
|
types to not participate in subtyping.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Any
|
||||||
|
from ty_extensions import static_assert, is_subtype_of
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any, Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[Any, ...], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[Any, ...], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[Any, ...], tuple[int, int]))
|
||||||
|
```
|
||||||
|
|
||||||
|
Subtyping also does not apply when `tuple[Any, ...]` is unpacked into a mixed tuple.
|
||||||
|
|
||||||
|
```py
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[Any, ...]]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any, Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[int, ...]]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[*tuple[Any, ...], int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[*tuple[int, ...], int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, int]))
|
||||||
|
```
|
||||||
|
|
||||||
|
Subtyping does apply to unbounded homogeneous tuples of a fully static type. However, such tuples
|
||||||
|
are defined to be the _union_ of all tuple lengths, not the _gradual choice_ of them, so no
|
||||||
|
variable-length tuples are a subtyping of _any_ fixed-length tuple.
|
||||||
|
|
||||||
|
```py
|
||||||
|
static_assert(not is_subtype_of(tuple[int, ...], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, ...], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, ...], tuple[Any, Any]))
|
||||||
|
static_assert(is_subtype_of(tuple[int, ...], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, ...], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, ...], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, *tuple[Any, ...]]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[Any, Any]))
|
||||||
|
static_assert(is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, *tuple[int, ...]]))
|
||||||
|
static_assert(is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...]], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[*tuple[Any, ...], int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(is_subtype_of(tuple[*tuple[int, ...], int], tuple[*tuple[int, ...], int]))
|
||||||
|
static_assert(is_subtype_of(tuple[*tuple[int, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[*tuple[int, ...], int], tuple[int, int]))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[Any, ...], int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[Any, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[Any]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[Any, Any]))
|
||||||
|
static_assert(is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, *tuple[int, ...], int]))
|
||||||
|
static_assert(is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, ...]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int]))
|
||||||
|
static_assert(not is_subtype_of(tuple[int, *tuple[int, ...], int], tuple[int, int]))
|
||||||
|
```
|
||||||
|
|
||||||
## Union types
|
## Union types
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
@ -1639,5 +1853,7 @@ static_assert(is_subtype_of(CallableTypeOf[overload_ab], CallableTypeOf[overload
|
||||||
static_assert(is_subtype_of(CallableTypeOf[overload_ba], CallableTypeOf[overload_ab]))
|
static_assert(is_subtype_of(CallableTypeOf[overload_ba], CallableTypeOf[overload_ab]))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form
|
||||||
|
[gradual tuple]: https://typing.python.org/en/latest/spec/tuples.html#tuple-type-form
|
||||||
[special case for float and complex]: https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
[special case for float and complex]: https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||||
[typing documentation]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
[typing documentation]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
|
||||||
|
|
|
@ -7,6 +7,7 @@ cpython # too many cycle iterations
|
||||||
hydpy # too many iterations
|
hydpy # too many iterations
|
||||||
ibis # too many iterations
|
ibis # too many iterations
|
||||||
jax # too many iterations
|
jax # too many iterations
|
||||||
|
mypy # too many iterations (self-recursive type alias)
|
||||||
packaging # too many iterations
|
packaging # too many iterations
|
||||||
pandas # slow (9s)
|
pandas # slow (9s)
|
||||||
pandera # stack overflow
|
pandera # stack overflow
|
||||||
|
|
|
@ -60,7 +60,6 @@ mkdocs
|
||||||
mkosi
|
mkosi
|
||||||
mongo-python-driver
|
mongo-python-driver
|
||||||
more-itertools
|
more-itertools
|
||||||
mypy
|
|
||||||
mypy-protobuf
|
mypy-protobuf
|
||||||
mypy_primer
|
mypy_primer
|
||||||
nionutils
|
nionutils
|
||||||
|
|
|
@ -51,6 +51,7 @@ use crate::types::infer::infer_unpack_types;
|
||||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
||||||
|
use crate::types::tuple::{TupleSpec, TupleType};
|
||||||
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||||
use crate::{Db, FxOrderSet, Module, Program};
|
use crate::{Db, FxOrderSet, Module, Program};
|
||||||
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
|
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
|
||||||
|
@ -78,6 +79,7 @@ mod slots;
|
||||||
mod special_form;
|
mod special_form;
|
||||||
mod string_annotation;
|
mod string_annotation;
|
||||||
mod subclass_of;
|
mod subclass_of;
|
||||||
|
mod tuple;
|
||||||
mod type_ordering;
|
mod type_ordering;
|
||||||
mod unpacker;
|
mod unpacker;
|
||||||
|
|
||||||
|
@ -543,8 +545,10 @@ pub enum Type<'db> {
|
||||||
LiteralString,
|
LiteralString,
|
||||||
/// A bytes literal
|
/// A bytes literal
|
||||||
BytesLiteral(BytesLiteralType<'db>),
|
BytesLiteral(BytesLiteralType<'db>),
|
||||||
/// A heterogeneous tuple type, with elements of the given types in source order.
|
/// An instance of the builtin `tuple` class.
|
||||||
// TODO: Support variable length homogeneous tuple type like `tuple[int, ...]`.
|
/// TODO: Consider removing this in favor of `NominalInstance`. This is currently stored as a
|
||||||
|
/// separate variant partly for historical reasons, and partly to allow us to easily
|
||||||
|
/// distinguish tuples since they occur so often.
|
||||||
Tuple(TupleType<'db>),
|
Tuple(TupleType<'db>),
|
||||||
/// An instance of a typevar in a generic class or function. When the generic class or function
|
/// An instance of a typevar in a generic class or function. When the generic class or function
|
||||||
/// is specialized, we will replace this typevar with its specialization.
|
/// is specialized, we will replace this typevar with its specialization.
|
||||||
|
@ -720,13 +724,7 @@ impl<'db> Type<'db> {
|
||||||
.map(|ty| ty.materialize(db, variance.flip())),
|
.map(|ty| ty.materialize(db, variance.flip())),
|
||||||
)
|
)
|
||||||
.build(),
|
.build(),
|
||||||
Type::Tuple(tuple_type) => TupleType::from_elements(
|
Type::Tuple(tuple_type) => Type::tuple(db, tuple_type.materialize(db, variance)),
|
||||||
db,
|
|
||||||
tuple_type
|
|
||||||
.elements(db)
|
|
||||||
.iter()
|
|
||||||
.map(|ty| ty.materialize(db, variance)),
|
|
||||||
),
|
|
||||||
Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)),
|
Type::TypeVar(type_var) => Type::TypeVar(type_var.materialize(db, variance)),
|
||||||
Type::TypeIs(type_is) => {
|
Type::TypeIs(type_is) => {
|
||||||
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
|
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
|
||||||
|
@ -770,8 +768,8 @@ impl<'db> Type<'db> {
|
||||||
Self::Tuple(tuple) => TupleType::from_elements(
|
Self::Tuple(tuple) => TupleType::from_elements(
|
||||||
db,
|
db,
|
||||||
tuple
|
tuple
|
||||||
.elements(db)
|
.tuple(db)
|
||||||
.iter()
|
.all_elements()
|
||||||
.map(|ty| ty.replace_self_reference(db, class)),
|
.map(|ty| ty.replace_self_reference(db, class)),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -881,8 +879,8 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Tuple(tuple) => tuple
|
Self::Tuple(tuple) => tuple
|
||||||
.elements(db)
|
.tuple(db)
|
||||||
.iter()
|
.all_elements()
|
||||||
.any(|ty| ty.any_over_type(db, type_fn)),
|
.any(|ty| ty.any_over_type(db, type_fn)),
|
||||||
|
|
||||||
Self::Union(union) => union
|
Self::Union(union) => union
|
||||||
|
@ -1076,13 +1074,6 @@ impl<'db> Type<'db> {
|
||||||
.expect("Expected a Type::IntLiteral variant")
|
.expect("Expected a Type::IntLiteral variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn into_tuple(self) -> Option<TupleType<'db>> {
|
|
||||||
match self {
|
|
||||||
Type::Tuple(tuple_type) => Some(tuple_type),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn is_boolean_literal(&self) -> bool {
|
pub const fn is_boolean_literal(&self) -> bool {
|
||||||
matches!(self, Type::BooleanLiteral(..))
|
matches!(self, Type::BooleanLiteral(..))
|
||||||
}
|
}
|
||||||
|
@ -1141,7 +1132,7 @@ impl<'db> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
Type::Union(union) => Type::Union(union.normalized(db)),
|
Type::Union(union) => Type::Union(union.normalized(db)),
|
||||||
Type::Intersection(intersection) => Type::Intersection(intersection.normalized(db)),
|
Type::Intersection(intersection) => Type::Intersection(intersection.normalized(db)),
|
||||||
Type::Tuple(tuple) => Type::Tuple(tuple.normalized(db)),
|
Type::Tuple(tuple) => Type::tuple(db, tuple.normalized(db)),
|
||||||
Type::Callable(callable) => Type::Callable(callable.normalized(db)),
|
Type::Callable(callable) => Type::Callable(callable.normalized(db)),
|
||||||
Type::ProtocolInstance(protocol) => protocol.normalized(db),
|
Type::ProtocolInstance(protocol) => protocol.normalized(db),
|
||||||
Type::NominalInstance(instance) => Type::NominalInstance(instance.normalized(db)),
|
Type::NominalInstance(instance) => Type::NominalInstance(instance.normalized(db)),
|
||||||
|
@ -1441,27 +1432,23 @@ impl<'db> Type<'db> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
// A fully static heterogeneous tuple type `A` is a subtype of a fully static heterogeneous tuple type `B`
|
|
||||||
// iff the two tuple types have the same number of elements and each element-type in `A` is a subtype
|
|
||||||
// of the element-type at the same index in `B`. (Now say that 5 times fast.)
|
|
||||||
//
|
|
||||||
// For example: `tuple[bool, bool]` is a subtype of `tuple[int, int]`,
|
|
||||||
// but `tuple[bool, bool, bool]` is not a subtype of `tuple[int, int]`
|
|
||||||
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
|
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
|
||||||
let self_elements = self_tuple.elements(db);
|
self_tuple.has_relation_to(db, target_tuple, relation)
|
||||||
let target_elements = target_tuple.elements(db);
|
|
||||||
self_elements.len() == target_elements.len()
|
|
||||||
&& self_elements.iter().zip(target_elements).all(
|
|
||||||
|(self_element, target_element)| {
|
|
||||||
self_element.has_relation_to(db, *target_element, relation)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// `tuple[A, B, C]` is a subtype of `tuple[A | B | C, ...]`
|
(Type::Tuple(self_tuple), Type::NominalInstance(target_instance)) => {
|
||||||
(Type::Tuple(tuple), _) => tuple
|
self_tuple.to_class_type(db).is_some_and(|self_class| {
|
||||||
.homogeneous_supertype(db)
|
self_class.has_relation_to(db, target_instance.class, relation)
|
||||||
.has_relation_to(db, target, relation),
|
})
|
||||||
|
}
|
||||||
|
(Type::NominalInstance(self_instance), Type::Tuple(target_tuple)) => {
|
||||||
|
target_tuple.to_class_type(db).is_some_and(|target_class| {
|
||||||
|
self_instance
|
||||||
|
.class
|
||||||
|
.has_relation_to(db, target_class, relation)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(Type::Tuple(_), _) => false,
|
||||||
|
|
||||||
(Type::BoundSuper(_), Type::BoundSuper(_)) => relation.are_equivalent(db, self, target),
|
(Type::BoundSuper(_), Type::BoundSuper(_)) => relation.are_equivalent(db, self, target),
|
||||||
(Type::BoundSuper(_), _) => KnownClass::Super
|
(Type::BoundSuper(_), _) => KnownClass::Super
|
||||||
|
@ -1961,14 +1948,15 @@ impl<'db> Type<'db> {
|
||||||
!known_instance.is_instance_of(db, instance.class)
|
!known_instance.is_instance_of(db, instance.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(Type::SpecialForm(special_form), Type::Tuple(tuple))
|
||||||
known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)),
|
| (Type::Tuple(tuple), Type::SpecialForm(special_form)) => tuple
|
||||||
Type::Tuple(tuple),
|
.to_class_type(db)
|
||||||
)
|
.is_some_and(|tuple_class| !special_form.is_instance_of(db, tuple_class)),
|
||||||
| (
|
|
||||||
Type::Tuple(tuple),
|
(Type::KnownInstance(known_instance), Type::Tuple(tuple))
|
||||||
known_instance_ty @ (Type::SpecialForm(_) | Type::KnownInstance(_)),
|
| (Type::Tuple(tuple), Type::KnownInstance(known_instance)) => tuple
|
||||||
) => known_instance_ty.is_disjoint_from(db, tuple.homogeneous_supertype(db)),
|
.to_class_type(db)
|
||||||
|
.is_some_and(|tuple_class| !known_instance.is_instance_of(db, tuple_class)),
|
||||||
|
|
||||||
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
|
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
|
||||||
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
|
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
|
||||||
|
@ -2113,18 +2101,14 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
|
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
|
||||||
let self_elements = tuple.elements(db);
|
tuple.is_disjoint_from(db, other_tuple)
|
||||||
let other_elements = other_tuple.elements(db);
|
|
||||||
self_elements.len() != other_elements.len()
|
|
||||||
|| self_elements
|
|
||||||
.iter()
|
|
||||||
.zip(other_elements)
|
|
||||||
.any(|(e1, e2)| e1.is_disjoint_from(db, *e2))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::Tuple(tuple), instance @ Type::NominalInstance(_))
|
(Type::Tuple(tuple), Type::NominalInstance(instance))
|
||||||
| (instance @ Type::NominalInstance(_), Type::Tuple(tuple)) => {
|
| (Type::NominalInstance(instance), Type::Tuple(tuple)) => {
|
||||||
instance.is_disjoint_from(db, tuple.homogeneous_supertype(db))
|
tuple.to_class_type(db).is_some_and(|tuple_class| {
|
||||||
|
instance.is_disjoint_from_nominal_instance_of_class(db, tuple_class)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
|
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
|
||||||
|
@ -2203,10 +2187,7 @@ impl<'db> Type<'db> {
|
||||||
// containing gradual forms such as `tuple[Any, ...]`.
|
// containing gradual forms such as `tuple[Any, ...]`.
|
||||||
// Conversely, make sure to return `true` for homogeneous tuples such as
|
// Conversely, make sure to return `true` for homogeneous tuples such as
|
||||||
// `tuple[int, ...]`, once we add support for them.
|
// `tuple[int, ...]`, once we add support for them.
|
||||||
Type::Tuple(tuple) => tuple
|
Type::Tuple(tuple) => tuple.is_fully_static(db),
|
||||||
.elements(db)
|
|
||||||
.iter()
|
|
||||||
.all(|elem| elem.is_fully_static(db)),
|
|
||||||
Type::Callable(callable) => callable.is_fully_static(db),
|
Type::Callable(callable) => callable.is_fully_static(db),
|
||||||
Type::TypeIs(type_is) => type_is.return_type(db).is_fully_static(db),
|
Type::TypeIs(type_is) => type_is.return_type(db).is_fully_static(db),
|
||||||
}
|
}
|
||||||
|
@ -2379,11 +2360,7 @@ impl<'db> Type<'db> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::Tuple(tuple) => tuple
|
Type::Tuple(tuple) => tuple.is_single_valued(db),
|
||||||
.elements(db)
|
|
||||||
.iter()
|
|
||||||
.all(|elem| elem.is_single_valued(db)),
|
|
||||||
|
|
||||||
Type::NominalInstance(instance) => instance.is_single_valued(db),
|
Type::NominalInstance(instance) => instance.is_single_valued(db),
|
||||||
|
|
||||||
Type::BoundSuper(_) => {
|
Type::BoundSuper(_) => {
|
||||||
|
@ -2629,7 +2606,10 @@ impl<'db> Type<'db> {
|
||||||
KnownClass::Str.to_instance(db).instance_member(db, name)
|
KnownClass::Str.to_instance(db).instance_member(db, name)
|
||||||
}
|
}
|
||||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db).instance_member(db, name),
|
Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db).instance_member(db, name),
|
||||||
Type::Tuple(tuple) => tuple.homogeneous_supertype(db).instance_member(db, name),
|
Type::Tuple(tuple) => tuple
|
||||||
|
.to_class_type(db)
|
||||||
|
.map(|class| class.instance_member(db, name))
|
||||||
|
.unwrap_or(Place::Unbound.into()),
|
||||||
|
|
||||||
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name),
|
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name),
|
||||||
Type::ModuleLiteral(_) => KnownClass::ModuleType
|
Type::ModuleLiteral(_) => KnownClass::ModuleType
|
||||||
|
@ -3474,7 +3454,7 @@ impl<'db> Type<'db> {
|
||||||
Type::BooleanLiteral(bool) => Truthiness::from(*bool),
|
Type::BooleanLiteral(bool) => Truthiness::from(*bool),
|
||||||
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
||||||
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
||||||
Type::Tuple(items) => Truthiness::from(!items.elements(db).is_empty()),
|
Type::Tuple(tuple) => Truthiness::from(!tuple.tuple(db).is_empty()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(truthiness)
|
Ok(truthiness)
|
||||||
|
@ -3505,7 +3485,11 @@ impl<'db> Type<'db> {
|
||||||
let usize_len = match self {
|
let usize_len = match self {
|
||||||
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
|
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
|
||||||
Type::StringLiteral(string) => Some(string.python_len(db)),
|
Type::StringLiteral(string) => Some(string.python_len(db)),
|
||||||
Type::Tuple(tuple) => Some(tuple.len(db)),
|
Type::Tuple(tuple) => match tuple.tuple(db) {
|
||||||
|
TupleSpec::Fixed(tuple) => Some(tuple.len()),
|
||||||
|
TupleSpec::Variable(_) => None,
|
||||||
|
},
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3705,10 +3689,7 @@ impl<'db> Type<'db> {
|
||||||
db,
|
db,
|
||||||
[
|
[
|
||||||
KnownClass::Str.to_instance(db),
|
KnownClass::Str.to_instance(db),
|
||||||
KnownClass::Tuple.to_specialized_instance(
|
TupleType::homogeneous(db, KnownClass::Str.to_instance(db)),
|
||||||
db,
|
|
||||||
[KnownClass::Str.to_instance(db)],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
Parameter::positional_only(Some(Name::new_static("start")))
|
Parameter::positional_only(Some(Name::new_static("start")))
|
||||||
|
@ -4022,10 +4003,10 @@ impl<'db> Type<'db> {
|
||||||
Parameter::positional_only(Some(Name::new_static("name")))
|
Parameter::positional_only(Some(Name::new_static("name")))
|
||||||
.with_annotated_type(str_instance),
|
.with_annotated_type(str_instance),
|
||||||
Parameter::positional_only(Some(Name::new_static("bases")))
|
Parameter::positional_only(Some(Name::new_static("bases")))
|
||||||
.with_annotated_type(
|
.with_annotated_type(TupleType::homogeneous(
|
||||||
KnownClass::Tuple
|
db,
|
||||||
.to_specialized_instance(db, [type_instance]),
|
type_instance,
|
||||||
),
|
)),
|
||||||
Parameter::positional_only(Some(Name::new_static("dict")))
|
Parameter::positional_only(Some(Name::new_static("dict")))
|
||||||
.with_annotated_type(
|
.with_annotated_type(
|
||||||
KnownClass::Dict.to_specialized_instance(
|
KnownClass::Dict.to_specialized_instance(
|
||||||
|
@ -4173,16 +4154,16 @@ impl<'db> Type<'db> {
|
||||||
.with_annotated_type(Type::any())
|
.with_annotated_type(Type::any())
|
||||||
.type_form(),
|
.type_form(),
|
||||||
Parameter::keyword_only(Name::new_static("type_params"))
|
Parameter::keyword_only(Name::new_static("type_params"))
|
||||||
.with_annotated_type(KnownClass::Tuple.to_specialized_instance(
|
.with_annotated_type(TupleType::homogeneous(
|
||||||
db,
|
db,
|
||||||
[UnionType::from_elements(
|
UnionType::from_elements(
|
||||||
db,
|
db,
|
||||||
[
|
[
|
||||||
KnownClass::TypeVar.to_instance(db),
|
KnownClass::TypeVar.to_instance(db),
|
||||||
KnownClass::ParamSpec.to_instance(db),
|
KnownClass::ParamSpec.to_instance(db),
|
||||||
KnownClass::TypeVarTuple.to_instance(db),
|
KnownClass::TypeVarTuple.to_instance(db),
|
||||||
],
|
],
|
||||||
)],
|
),
|
||||||
))
|
))
|
||||||
.with_default_type(TupleType::empty(db)),
|
.with_default_type(TupleType::empty(db)),
|
||||||
]),
|
]),
|
||||||
|
@ -4476,7 +4457,10 @@ impl<'db> Type<'db> {
|
||||||
/// ```
|
/// ```
|
||||||
fn try_iterate(self, db: &'db dyn Db) -> Result<Type<'db>, IterationError<'db>> {
|
fn try_iterate(self, db: &'db dyn Db) -> Result<Type<'db>, IterationError<'db>> {
|
||||||
if let Type::Tuple(tuple_type) = self {
|
if let Type::Tuple(tuple_type) = self {
|
||||||
return Ok(UnionType::from_elements(db, tuple_type.elements(db)));
|
return Ok(UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
tuple_type.tuple(db).all_elements(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Type::GenericAlias(alias) = self {
|
if let Type::GenericAlias(alias) = self {
|
||||||
|
@ -4639,20 +4623,20 @@ impl<'db> Type<'db> {
|
||||||
// have the class's typevars still in the method signature when we attempt to call it. To
|
// have the class's typevars still in the method signature when we attempt to call it. To
|
||||||
// do this, we instead use the _identity_ specialization, which maps each of the class's
|
// do this, we instead use the _identity_ specialization, which maps each of the class's
|
||||||
// generic typevars to itself.
|
// generic typevars to itself.
|
||||||
let (generic_origin, generic_context, self_type) = match self {
|
let (generic_origin, generic_context, self_type) =
|
||||||
Type::ClassLiteral(class) => match class.generic_context(db) {
|
match self {
|
||||||
Some(generic_context) => {
|
Type::ClassLiteral(class) => match class.generic_context(db) {
|
||||||
let specialization = generic_context.identity_specialization(db);
|
Some(generic_context) => (
|
||||||
(
|
|
||||||
Some(class),
|
Some(class),
|
||||||
Some(generic_context),
|
Some(generic_context),
|
||||||
Type::GenericAlias(GenericAlias::new(db, class, specialization)),
|
Type::from(class.apply_specialization(db, |_| {
|
||||||
)
|
generic_context.identity_specialization(db)
|
||||||
}
|
})),
|
||||||
|
),
|
||||||
|
_ => (None, None, self),
|
||||||
|
},
|
||||||
_ => (None, None, self),
|
_ => (None, None, self),
|
||||||
},
|
};
|
||||||
_ => (None, None, self),
|
|
||||||
};
|
|
||||||
|
|
||||||
// As of now we do not model custom `__call__` on meta-classes, so the code below
|
// As of now we do not model custom `__call__` on meta-classes, so the code below
|
||||||
// only deals with interplay between `__new__` and `__init__` methods.
|
// only deals with interplay between `__new__` and `__init__` methods.
|
||||||
|
@ -4775,11 +4759,7 @@ impl<'db> Type<'db> {
|
||||||
.map(|specialization| {
|
.map(|specialization| {
|
||||||
Type::instance(
|
Type::instance(
|
||||||
db,
|
db,
|
||||||
ClassType::Generic(GenericAlias::new(
|
generic_origin.apply_specialization(db, |_| specialization),
|
||||||
db,
|
|
||||||
generic_origin,
|
|
||||||
specialization,
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or(instance_ty);
|
.unwrap_or(instance_ty);
|
||||||
|
@ -4966,7 +4946,7 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
// We treat `typing.Type` exactly the same as `builtins.type`:
|
// We treat `typing.Type` exactly the same as `builtins.type`:
|
||||||
SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)),
|
SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)),
|
||||||
SpecialFormType::Tuple => Ok(KnownClass::Tuple.to_instance(db)),
|
SpecialFormType::Tuple => Ok(TupleType::homogeneous(db, Type::unknown())),
|
||||||
|
|
||||||
// Legacy `typing` aliases
|
// Legacy `typing` aliases
|
||||||
SpecialFormType::List => Ok(KnownClass::List.to_instance(db)),
|
SpecialFormType::List => Ok(KnownClass::List.to_instance(db)),
|
||||||
|
@ -5189,7 +5169,10 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
||||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||||
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
Type::Tuple(tuple) => tuple
|
||||||
|
.to_class_type(db)
|
||||||
|
.map(Type::from)
|
||||||
|
.unwrap_or_else(Type::unknown),
|
||||||
|
|
||||||
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
||||||
None => KnownClass::Type.to_instance(db),
|
None => KnownClass::Type.to_instance(db),
|
||||||
|
@ -5343,12 +5326,7 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
Type::Tuple(tuple) => TupleType::from_elements(
|
Type::Tuple(tuple) => Type::Tuple(tuple.apply_type_mapping(db, type_mapping)),
|
||||||
db,
|
|
||||||
tuple
|
|
||||||
.iter(db)
|
|
||||||
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
|
||||||
),
|
|
||||||
|
|
||||||
Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)),
|
Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)),
|
||||||
|
|
||||||
|
@ -5439,10 +5417,9 @@ impl<'db> Type<'db> {
|
||||||
negative.find_legacy_typevars(db, typevars);
|
negative.find_legacy_typevars(db, typevars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple) => {
|
||||||
for element in tuple.iter(db) {
|
tuple.find_legacy_typevars(db, typevars);
|
||||||
element.find_legacy_typevars(db, typevars);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::GenericAlias(alias) => {
|
Type::GenericAlias(alias) => {
|
||||||
|
@ -8134,89 +8111,6 @@ impl<'db> BytesLiteralType<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Ordering
|
|
||||||
/// Ordering is based on the tuple's salsa-assigned id and not on its elements.
|
|
||||||
/// The id may change between runs, or when the tuple was garbage collected and recreated.
|
|
||||||
#[salsa::interned(debug)]
|
|
||||||
#[derive(PartialOrd, Ord)]
|
|
||||||
pub struct TupleType<'db> {
|
|
||||||
#[returns(deref)]
|
|
||||||
elements: Box<[Type<'db>]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'db> TupleType<'db> {
|
|
||||||
fn homogeneous_supertype(self, db: &'db dyn Db) -> Type<'db> {
|
|
||||||
KnownClass::Tuple
|
|
||||||
.to_specialized_instance(db, [UnionType::from_elements(db, self.elements(db))])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
|
|
||||||
Type::Tuple(TupleType::new(db, Box::<[Type<'db>]>::from([])))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_elements<T: Into<Type<'db>>>(
|
|
||||||
db: &'db dyn Db,
|
|
||||||
types: impl IntoIterator<Item = T>,
|
|
||||||
) -> Type<'db> {
|
|
||||||
let mut elements = vec![];
|
|
||||||
|
|
||||||
for ty in types {
|
|
||||||
let ty = ty.into();
|
|
||||||
if ty.is_never() {
|
|
||||||
return Type::Never;
|
|
||||||
}
|
|
||||||
elements.push(ty);
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::Tuple(Self::new(db, elements.into_boxed_slice()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a normalized version of `self`.
|
|
||||||
///
|
|
||||||
/// See [`Type::normalized`] for more details.
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
|
||||||
let elements: Box<[Type<'db>]> = self
|
|
||||||
.elements(db)
|
|
||||||
.iter()
|
|
||||||
.map(|ty| ty.normalized(db))
|
|
||||||
.collect();
|
|
||||||
TupleType::new(db, elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
|
||||||
let self_elements = self.elements(db);
|
|
||||||
let other_elements = other.elements(db);
|
|
||||||
self_elements.len() == other_elements.len()
|
|
||||||
&& self_elements
|
|
||||||
.iter()
|
|
||||||
.zip(other_elements)
|
|
||||||
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
|
||||||
let self_elements = self.elements(db);
|
|
||||||
let other_elements = other.elements(db);
|
|
||||||
self_elements.len() == other_elements.len()
|
|
||||||
&& self_elements
|
|
||||||
.iter()
|
|
||||||
.zip(other_elements)
|
|
||||||
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, db: &'db dyn Db, index: usize) -> Option<Type<'db>> {
|
|
||||||
self.elements(db).get(index).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self, db: &'db dyn Db) -> usize {
|
|
||||||
self.elements(db).len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self, db: &'db dyn Db) -> impl Iterator<Item = Type<'db>> + 'db + '_ {
|
|
||||||
self.elements(db).iter().copied()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub(crate) enum BoundSuperError<'db> {
|
pub(crate) enum BoundSuperError<'db> {
|
||||||
InvalidPivotClassType {
|
InvalidPivotClassType {
|
||||||
|
|
|
@ -4,7 +4,8 @@ use std::ops::{Deref, DerefMut};
|
||||||
use itertools::{Either, Itertools};
|
use itertools::{Either, Itertools};
|
||||||
|
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use crate::types::{KnownClass, TupleType};
|
use crate::types::KnownClass;
|
||||||
|
use crate::types::tuple::{TupleSpec, TupleType};
|
||||||
|
|
||||||
use super::Type;
|
use super::Type;
|
||||||
|
|
||||||
|
@ -210,11 +211,15 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||||
Type::BooleanLiteral(false),
|
Type::BooleanLiteral(false),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple_type) => {
|
||||||
// Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
|
// Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
|
||||||
// should not be expanded here.
|
// should not be expanded here.
|
||||||
|
let tuple = tuple_type.tuple(db);
|
||||||
|
if !matches!(tuple, TupleSpec::Fixed(_)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let expanded = tuple
|
let expanded = tuple
|
||||||
.iter(db)
|
.all_elements()
|
||||||
.map(|element| {
|
.map(|element| {
|
||||||
if let Some(expanded) = expand_type(db, element) {
|
if let Some(expanded) = expand_type(db, element) {
|
||||||
Either::Left(expanded.into_iter())
|
Either::Left(expanded.into_iter())
|
||||||
|
@ -242,7 +247,8 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::db::tests::setup_db;
|
use crate::db::tests::setup_db;
|
||||||
use crate::types::{KnownClass, TupleType, Type, UnionType};
|
use crate::types::tuple::TupleType;
|
||||||
|
use crate::types::{KnownClass, Type, UnionType};
|
||||||
|
|
||||||
use super::expand_type;
|
use super::expand_type;
|
||||||
|
|
||||||
|
@ -308,7 +314,6 @@ mod tests {
|
||||||
TupleType::from_elements(&db, [false_ty, bytes_ty]),
|
TupleType::from_elements(&db, [false_ty, bytes_ty]),
|
||||||
];
|
];
|
||||||
let expanded = expand_type(&db, tuple_type2).unwrap();
|
let expanded = expand_type(&db, tuple_type2).unwrap();
|
||||||
assert_eq!(expanded.len(), expected_types.len());
|
|
||||||
assert_eq!(expanded, expected_types);
|
assert_eq!(expanded, expected_types);
|
||||||
|
|
||||||
// Mixed set of elements where some can be expanded while others cannot be.
|
// Mixed set of elements where some can be expanded while others cannot be.
|
||||||
|
@ -328,7 +333,16 @@ mod tests {
|
||||||
TupleType::from_elements(&db, [false_ty, int_ty, bytes_ty, str_ty]),
|
TupleType::from_elements(&db, [false_ty, int_ty, bytes_ty, str_ty]),
|
||||||
];
|
];
|
||||||
let expanded = expand_type(&db, tuple_type3).unwrap();
|
let expanded = expand_type(&db, tuple_type3).unwrap();
|
||||||
assert_eq!(expanded.len(), expected_types.len());
|
|
||||||
assert_eq!(expanded, expected_types);
|
assert_eq!(expanded, expected_types);
|
||||||
|
|
||||||
|
// Variable-length tuples are not expanded.
|
||||||
|
let variable_length_tuple = TupleType::mixed(
|
||||||
|
&db,
|
||||||
|
[bool_ty],
|
||||||
|
int_ty,
|
||||||
|
[UnionType::from_elements(&db, [str_ty, bytes_ty]), str_ty],
|
||||||
|
);
|
||||||
|
let expanded = expand_type(&db, variable_length_tuple);
|
||||||
|
assert!(expanded.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,10 @@ use crate::types::function::{
|
||||||
};
|
};
|
||||||
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::tuple::TupleType;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType,
|
BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType,
|
||||||
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TupleType, TypeMapping, UnionType,
|
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TypeMapping, UnionType,
|
||||||
WrapperDescriptorKind, ide_support, todo_type,
|
WrapperDescriptorKind, ide_support, todo_type,
|
||||||
};
|
};
|
||||||
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic};
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
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::tuple::TupleType;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeRelation, TypeVarInstance,
|
CallableType, DataclassParams, KnownInstanceType, TypeMapping, TypeRelation, TypeVarInstance,
|
||||||
};
|
};
|
||||||
|
@ -30,8 +31,8 @@ use crate::{
|
||||||
place_table, semantic_index, use_def_map,
|
place_table, semantic_index, use_def_map,
|
||||||
},
|
},
|
||||||
types::{
|
types::{
|
||||||
CallArgumentTypes, CallError, CallErrorKind, MetaclassCandidate, TupleType, UnionBuilder,
|
CallArgumentTypes, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType,
|
||||||
UnionType, definition_expression_type,
|
definition_expression_type,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
|
@ -203,6 +204,8 @@ impl<'db> GenericAlias<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||||
) {
|
) {
|
||||||
|
// A tuple's specialization will include all of its element types, so we don't need to also
|
||||||
|
// look in `self.tuple`.
|
||||||
self.specialization(db).find_legacy_typevars(db, typevars);
|
self.specialization(db).find_legacy_typevars(db, typevars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -761,58 +764,46 @@ impl<'db> ClassLiteral<'db> {
|
||||||
index.expect_single_definition(body_scope.node(db).expect_class(&module))
|
index.expect_single_definition(body_scope.node(db).expect_class(&module))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_specialization(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
f: impl FnOnce(GenericContext<'db>) -> Specialization<'db>,
|
||||||
|
) -> ClassType<'db> {
|
||||||
|
match self.generic_context(db) {
|
||||||
|
None => ClassType::NonGeneric(self),
|
||||||
|
Some(generic_context) => {
|
||||||
|
let specialization = f(generic_context);
|
||||||
|
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_optional_specialization(
|
pub(crate) fn apply_optional_specialization(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: Option<Specialization<'db>>,
|
specialization: Option<Specialization<'db>>,
|
||||||
) -> ClassType<'db> {
|
) -> ClassType<'db> {
|
||||||
match (self.generic_context(db), specialization) {
|
self.apply_specialization(db, |generic_context| {
|
||||||
(None, _) => ClassType::NonGeneric(self),
|
specialization.unwrap_or_else(|| generic_context.default_specialization(db))
|
||||||
(Some(generic_context), None) => {
|
})
|
||||||
let specialization = generic_context.default_specialization(db);
|
|
||||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
|
||||||
}
|
|
||||||
(Some(_), Some(specialization)) => {
|
|
||||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default specialization of this class. For non-generic classes, the class is
|
/// Returns the default specialization of this class. For non-generic classes, the class is
|
||||||
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
||||||
/// applies the default specialization to the class's typevars.
|
/// applies the default specialization to the class's typevars.
|
||||||
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||||
match self.generic_context(db) {
|
self.apply_specialization(db, |generic_context| {
|
||||||
None => ClassType::NonGeneric(self),
|
generic_context.default_specialization(db)
|
||||||
Some(generic_context) => {
|
})
|
||||||
let specialization = generic_context.default_specialization(db);
|
|
||||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a specialization of this class with a `@Todo`-type
|
|
||||||
pub(crate) fn todo_specialization(self, db: &'db dyn Db, todo: &'static str) -> ClassType<'db> {
|
|
||||||
match self.generic_context(db) {
|
|
||||||
None => ClassType::NonGeneric(self),
|
|
||||||
Some(generic_context) => {
|
|
||||||
let specialization = generic_context.todo_specialization(db, todo);
|
|
||||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the unknown specialization of this class. For non-generic classes, the class is
|
/// Returns the unknown specialization of this class. For non-generic classes, the class is
|
||||||
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
/// returned unchanged. For a non-specialized generic class, we return a generic alias that
|
||||||
/// maps each of the class's typevars to `Unknown`.
|
/// maps each of the class's typevars to `Unknown`.
|
||||||
pub(crate) fn unknown_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
pub(crate) fn unknown_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||||
match self.generic_context(db) {
|
self.apply_specialization(db, |generic_context| {
|
||||||
None => ClassType::NonGeneric(self),
|
generic_context.unknown_specialization(db)
|
||||||
Some(generic_context) => {
|
})
|
||||||
let specialization = generic_context.unknown_specialization(db);
|
|
||||||
ClassType::Generic(GenericAlias::new(db, self, specialization))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator over the inferred types of this class's *explicit* bases.
|
/// Return an iterator over the inferred types of this class's *explicit* bases.
|
||||||
|
@ -2448,22 +2439,20 @@ impl<'db> KnownClass {
|
||||||
.unwrap_or_else(Type::unknown)
|
.unwrap_or_else(Type::unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
|
/// Lookup a generic [`KnownClass`] in typeshed and return a [`Type`]
|
||||||
/// representing all possible instances of the generic class with a specialization.
|
/// representing a specialization of that class.
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
||||||
/// number of types, a debug-level log message will be emitted stating this.
|
/// number of types, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn to_specialized_instance(
|
pub(crate) fn to_specialized_class_type(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: impl IntoIterator<Item = Type<'db>>,
|
specialization: impl IntoIterator<Item = Type<'db>>,
|
||||||
) -> Type<'db> {
|
) -> Option<ClassType<'db>> {
|
||||||
let Type::ClassLiteral(class_literal) = self.to_class_literal(db) else {
|
let Type::ClassLiteral(class_literal) = self.to_class_literal(db) else {
|
||||||
return Type::unknown();
|
return None;
|
||||||
};
|
|
||||||
let Some(generic_context) = class_literal.generic_context(db) else {
|
|
||||||
return Type::instance(db, ClassType::NonGeneric(class_literal));
|
|
||||||
};
|
};
|
||||||
|
let generic_context = class_literal.generic_context(db)?;
|
||||||
|
|
||||||
let types = specialization.into_iter().collect::<Box<[_]>>();
|
let types = specialization.into_iter().collect::<Box<[_]>>();
|
||||||
if types.len() != generic_context.len(db) {
|
if types.len() != generic_context.len(db) {
|
||||||
|
@ -2477,21 +2466,32 @@ impl<'db> KnownClass {
|
||||||
self.display(db)
|
self.display(db)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Type::instance(db, class_literal.default_specialization(db));
|
return Some(class_literal.default_specialization(db));
|
||||||
}
|
}
|
||||||
|
|
||||||
let specialization = generic_context.specialize(db, types);
|
Some(class_literal.apply_specialization(db, |_| generic_context.specialize(db, types)))
|
||||||
Type::instance(
|
}
|
||||||
db,
|
|
||||||
ClassType::Generic(GenericAlias::new(db, class_literal, specialization)),
|
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
|
||||||
)
|
/// representing all possible instances of the generic class with a specialization.
|
||||||
|
///
|
||||||
|
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
|
||||||
|
/// number of types, a debug-level log message will be emitted stating this.
|
||||||
|
pub(crate) fn to_specialized_instance(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
specialization: impl IntoIterator<Item = Type<'db>>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
self.to_specialized_class_type(db, specialization)
|
||||||
|
.and_then(|class_type| Type::from(class_type).to_instance(db))
|
||||||
|
.unwrap_or_else(Type::unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
/// Attempt to lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||||
///
|
///
|
||||||
/// Return an error if the symbol cannot be found in the expected typeshed module,
|
/// Return an error if the symbol cannot be found in the expected typeshed module,
|
||||||
/// or if the symbol is not a class definition, or if the symbol is possibly unbound.
|
/// or if the symbol is not a class definition, or if the symbol is possibly unbound.
|
||||||
pub(crate) fn try_to_class_literal(
|
fn try_to_class_literal_without_logging(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
) -> Result<ClassLiteral<'db>, KnownClassLookupError<'db>> {
|
) -> Result<ClassLiteral<'db>, KnownClassLookupError<'db>> {
|
||||||
|
@ -2511,14 +2511,13 @@ impl<'db> KnownClass {
|
||||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||||
///
|
///
|
||||||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
pub(crate) fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
pub(crate) fn try_to_class_literal(self, db: &'db dyn Db) -> Option<ClassLiteral<'db>> {
|
||||||
// a cache of the `KnownClass`es that we have already failed to lookup in typeshed
|
// a cache of the `KnownClass`es that we have already failed to lookup in typeshed
|
||||||
// (and therefore that we've already logged a warning for)
|
// (and therefore that we've already logged a warning for)
|
||||||
static MESSAGES: LazyLock<Mutex<FxHashSet<KnownClass>>> = LazyLock::new(Mutex::default);
|
static MESSAGES: LazyLock<Mutex<FxHashSet<KnownClass>>> = LazyLock::new(Mutex::default);
|
||||||
|
|
||||||
self.try_to_class_literal(db)
|
self.try_to_class_literal_without_logging(db)
|
||||||
.map(Type::ClassLiteral)
|
.or_else(|lookup_error| {
|
||||||
.unwrap_or_else(|lookup_error| {
|
|
||||||
if MESSAGES.lock().unwrap().insert(self) {
|
if MESSAGES.lock().unwrap().insert(self) {
|
||||||
if matches!(
|
if matches!(
|
||||||
lookup_error,
|
lookup_error,
|
||||||
|
@ -2535,12 +2534,22 @@ impl<'db> KnownClass {
|
||||||
|
|
||||||
match lookup_error {
|
match lookup_error {
|
||||||
KnownClassLookupError::ClassPossiblyUnbound { class_literal, .. } => {
|
KnownClassLookupError::ClassPossiblyUnbound { class_literal, .. } => {
|
||||||
class_literal.into()
|
Ok(class_literal)
|
||||||
}
|
}
|
||||||
KnownClassLookupError::ClassNotFound { .. }
|
KnownClassLookupError::ClassNotFound { .. }
|
||||||
| KnownClassLookupError::SymbolNotAClass { .. } => Type::unknown(),
|
| KnownClassLookupError::SymbolNotAClass { .. } => Err(()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`] representing that class-literal.
|
||||||
|
///
|
||||||
|
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||||
|
pub(crate) fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
|
self.try_to_class_literal(db)
|
||||||
|
.map(Type::ClassLiteral)
|
||||||
|
.unwrap_or_else(Type::unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
|
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
|
||||||
|
@ -2557,7 +2566,7 @@ impl<'db> KnownClass {
|
||||||
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
/// Return `true` if this symbol can be resolved to a class definition `class` in typeshed,
|
||||||
/// *and* `class` is a subclass of `other`.
|
/// *and* `class` is a subclass of `other`.
|
||||||
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
pub(super) fn is_subclass_of(self, db: &'db dyn Db, other: ClassType<'db>) -> bool {
|
||||||
self.try_to_class_literal(db)
|
self.try_to_class_literal_without_logging(db)
|
||||||
.is_ok_and(|class| class.is_subclass_of(db, None, other))
|
.is_ok_and(|class| class.is_subclass_of(db, None, other))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,7 @@ impl<'db> ClassBase<'db> {
|
||||||
if literal.is_known(db, KnownClass::Any) {
|
if literal.is_known(db, KnownClass::Any) {
|
||||||
Some(Self::Dynamic(DynamicType::Any))
|
Some(Self::Dynamic(DynamicType::Any))
|
||||||
} else if literal.is_known(db, KnownClass::NamedTuple) {
|
} else if literal.is_known(db, KnownClass::NamedTuple) {
|
||||||
|
// TODO: Figure out the tuple spec for the named tuple
|
||||||
Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db))
|
Self::try_from_type(db, KnownClass::Tuple.to_class_literal(db))
|
||||||
} else {
|
} else {
|
||||||
Some(Self::Class(literal.default_specialization(db)))
|
Some(Self::Class(literal.default_specialization(db)))
|
||||||
|
|
|
@ -14,6 +14,7 @@ use crate::types::string_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::tuple::TupleType;
|
||||||
use crate::types::{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;
|
||||||
|
@ -1606,7 +1607,7 @@ pub(super) fn report_index_out_of_bounds(
|
||||||
kind: &'static str,
|
kind: &'static str,
|
||||||
node: AnyNodeRef,
|
node: AnyNodeRef,
|
||||||
tuple_ty: Type,
|
tuple_ty: Type,
|
||||||
length: usize,
|
length: impl std::fmt::Display,
|
||||||
index: i64,
|
index: i64,
|
||||||
) {
|
) {
|
||||||
let Some(builder) = context.report_lint(&INDEX_OUT_OF_BOUNDS, node) else {
|
let Some(builder) = context.report_lint(&INDEX_OUT_OF_BOUNDS, node) else {
|
||||||
|
@ -2120,7 +2121,7 @@ pub(crate) fn report_invalid_or_unsupported_base(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tuple_of_types = KnownClass::Tuple.to_specialized_instance(db, [instance_of_type]);
|
let tuple_of_types = TupleType::homogeneous(db, instance_of_type);
|
||||||
|
|
||||||
let explain_mro_entries = |diagnostic: &mut LintDiagnosticGuard| {
|
let explain_mro_entries = |diagnostic: &mut LintDiagnosticGuard| {
|
||||||
diagnostic.info(
|
diagnostic.info(
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::types::class::{ClassLiteral, ClassType, GenericAlias};
|
||||||
use crate::types::function::{FunctionType, OverloadLiteral};
|
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::tuple::TupleSpec;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType,
|
CallableType, IntersectionType, KnownClass, MethodWrapperKind, Protocol, StringLiteralType,
|
||||||
SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
|
SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
|
||||||
|
@ -190,16 +191,7 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
|
|
||||||
escape.bytes_repr(TripleQuotes::No).write(f)
|
escape.bytes_repr(TripleQuotes::No).write(f)
|
||||||
}
|
}
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f),
|
||||||
f.write_str("tuple[")?;
|
|
||||||
let elements = tuple.elements(self.db);
|
|
||||||
if elements.is_empty() {
|
|
||||||
f.write_str("()")?;
|
|
||||||
} else {
|
|
||||||
elements.display(self.db).fmt(f)?;
|
|
||||||
}
|
|
||||||
f.write_str("]")
|
|
||||||
}
|
|
||||||
Type::TypeVar(typevar) => f.write_str(typevar.name(self.db)),
|
Type::TypeVar(typevar) => f.write_str(typevar.name(self.db)),
|
||||||
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
|
Type::AlwaysTruthy => f.write_str("AlwaysTruthy"),
|
||||||
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),
|
Type::AlwaysFalsy => f.write_str("AlwaysFalsy"),
|
||||||
|
@ -224,6 +216,67 @@ impl Display for DisplayRepresentation<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'db> TupleSpec<'db> {
|
||||||
|
pub(crate) fn display(&'db self, db: &'db dyn Db) -> DisplayTuple<'db> {
|
||||||
|
DisplayTuple { tuple: self, db }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct DisplayTuple<'db> {
|
||||||
|
tuple: &'db TupleSpec<'db>,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DisplayTuple<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("tuple[")?;
|
||||||
|
match self.tuple {
|
||||||
|
TupleSpec::Fixed(tuple) => {
|
||||||
|
let elements = tuple.elements_slice();
|
||||||
|
if elements.is_empty() {
|
||||||
|
f.write_str("()")?;
|
||||||
|
} else {
|
||||||
|
elements.display(self.db).fmt(f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder key for which snippets of text need to be included depending on whether
|
||||||
|
// the tuple contains a prefix and/or suffix:
|
||||||
|
//
|
||||||
|
// tuple[ yyy, ... ]
|
||||||
|
// tuple[xxx, *tuple[yyy, ...] ]
|
||||||
|
// tuple[xxx, *tuple[yyy, ...], zzz]
|
||||||
|
// tuple[ *tuple[yyy, ...], zzz]
|
||||||
|
// PPPPPPPPPPPP P
|
||||||
|
// SSSSSSS SSSSSS
|
||||||
|
//
|
||||||
|
// (Anything that appears above only a P is included only if there's a prefix; anything
|
||||||
|
// above only an S is included only if there's a suffix; anything about both a P and an
|
||||||
|
// S is included if there is either a prefix or a suffix. The initial `tuple[` and
|
||||||
|
// trailing `]` are printed elsewhere. The `yyy, ...` is printed no matter what.)
|
||||||
|
TupleSpec::Variable(tuple) => {
|
||||||
|
if !tuple.prefix.is_empty() {
|
||||||
|
tuple.prefix.display(self.db).fmt(f)?;
|
||||||
|
f.write_str(", ")?;
|
||||||
|
}
|
||||||
|
if !tuple.prefix.is_empty() || !tuple.suffix.is_empty() {
|
||||||
|
f.write_str("*tuple[")?;
|
||||||
|
}
|
||||||
|
tuple.variable.display(self.db).fmt(f)?;
|
||||||
|
f.write_str(", ...")?;
|
||||||
|
if !tuple.prefix.is_empty() || !tuple.suffix.is_empty() {
|
||||||
|
f.write_str("]")?;
|
||||||
|
}
|
||||||
|
if !tuple.suffix.is_empty() {
|
||||||
|
f.write_str(", ")?;
|
||||||
|
tuple.suffix.display(self.db).fmt(f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_str("]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'db> OverloadLiteral<'db> {
|
impl<'db> OverloadLiteral<'db> {
|
||||||
// Not currently used, but useful for debugging.
|
// Not currently used, but useful for debugging.
|
||||||
#[expect(dead_code)]
|
#[expect(dead_code)]
|
||||||
|
@ -307,15 +360,19 @@ pub(crate) struct DisplayGenericAlias<'db> {
|
||||||
|
|
||||||
impl Display for DisplayGenericAlias<'_> {
|
impl Display for DisplayGenericAlias<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
if self.origin.is_known(self.db, KnownClass::Tuple) {
|
||||||
f,
|
self.specialization.tuple(self.db).display(self.db).fmt(f)
|
||||||
"{origin}{specialization}",
|
} else {
|
||||||
origin = self.origin.name(self.db),
|
write!(
|
||||||
specialization = self.specialization.display_short(
|
f,
|
||||||
self.db,
|
"{origin}{specialization}",
|
||||||
TupleSpecialization::from_class(self.db, self.origin)
|
origin = self.origin.name(self.db),
|
||||||
),
|
specialization = self.specialization.display_short(
|
||||||
)
|
self.db,
|
||||||
|
TupleSpecialization::from_class(self.db, self.origin)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,10 @@ use crate::types::class::ClassType;
|
||||||
use crate::types::class_base::ClassBase;
|
use crate::types::class_base::ClassBase;
|
||||||
use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType};
|
use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType};
|
||||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||||
|
use crate::types::tuple::{TupleSpec, TupleType};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
|
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||||
TypeVarVariance, UnionType, declaration_type, todo_type,
|
TypeVarVariance, UnionType, declaration_type,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
@ -143,20 +144,6 @@ impl<'db> GenericContext<'db> {
|
||||||
self.specialize_partial(db, &vec![None; self.variables(db).len()])
|
self.specialize_partial(db, &vec![None; self.variables(db).len()])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)] // Only unused in release builds
|
|
||||||
pub(crate) fn todo_specialization(
|
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
todo: &'static str,
|
|
||||||
) -> Specialization<'db> {
|
|
||||||
let types = self
|
|
||||||
.variables(db)
|
|
||||||
.iter()
|
|
||||||
.map(|typevar| typevar.default_ty(db).unwrap_or(todo_type!(todo)))
|
|
||||||
.collect();
|
|
||||||
self.specialize(db, types)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||||
let types = self
|
let types = self
|
||||||
.variables(db)
|
.variables(db)
|
||||||
|
@ -185,7 +172,17 @@ impl<'db> GenericContext<'db> {
|
||||||
types: Box<[Type<'db>]>,
|
types: Box<[Type<'db>]>,
|
||||||
) -> Specialization<'db> {
|
) -> Specialization<'db> {
|
||||||
assert!(self.variables(db).len() == types.len());
|
assert!(self.variables(db).len() == types.len());
|
||||||
Specialization::new(db, self, types)
|
Specialization::new(db, self, types, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a specialization of this generic context for the `tuple` class.
|
||||||
|
pub(crate) fn specialize_tuple(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
tuple: TupleType<'db>,
|
||||||
|
) -> Specialization<'db> {
|
||||||
|
let element_type = UnionType::from_elements(db, tuple.tuple(db).all_elements());
|
||||||
|
Specialization::new(db, self, Box::from([element_type]), Some(tuple))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
||||||
|
@ -230,7 +227,7 @@ impl<'db> GenericContext<'db> {
|
||||||
expanded[idx] = default;
|
expanded[idx] = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
Specialization::new(db, self, expanded.into_boxed_slice())
|
Specialization::new(db, self, expanded.into_boxed_slice(), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||||
|
@ -273,9 +270,24 @@ pub struct Specialization<'db> {
|
||||||
pub(crate) generic_context: GenericContext<'db>,
|
pub(crate) generic_context: GenericContext<'db>,
|
||||||
#[returns(deref)]
|
#[returns(deref)]
|
||||||
pub(crate) types: Box<[Type<'db>]>,
|
pub(crate) types: Box<[Type<'db>]>,
|
||||||
|
|
||||||
|
/// For specializations of `tuple`, we also store more detailed information about the tuple's
|
||||||
|
/// elements, above what the class's (single) typevar can represent.
|
||||||
|
tuple_inner: Option<TupleType<'db>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> Specialization<'db> {
|
impl<'db> Specialization<'db> {
|
||||||
|
/// Returns the tuple spec for a specialization of the `tuple` class.
|
||||||
|
pub(crate) fn tuple(self, db: &'db dyn Db) -> &'db TupleSpec<'db> {
|
||||||
|
if let Some(tuple) = self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db)) {
|
||||||
|
return tuple;
|
||||||
|
}
|
||||||
|
if let [element_type] = self.types(db) {
|
||||||
|
return TupleType::new(db, TupleSpec::homogeneous(*element_type)).tuple(db);
|
||||||
|
}
|
||||||
|
TupleType::new(db, TupleSpec::homogeneous(Type::unknown())).tuple(db)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
|
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
|
||||||
/// mapping.
|
/// mapping.
|
||||||
pub(crate) fn get(self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
|
pub(crate) fn get(self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
|
||||||
|
@ -313,7 +325,10 @@ impl<'db> Specialization<'db> {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||||
.collect();
|
.collect();
|
||||||
Specialization::new(db, self.generic_context(db), types)
|
let tuple_inner = self
|
||||||
|
.tuple_inner(db)
|
||||||
|
.map(|tuple| tuple.apply_type_mapping(db, type_mapping));
|
||||||
|
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies an optional specialization to this specialization.
|
/// Applies an optional specialization to this specialization.
|
||||||
|
@ -350,12 +365,14 @@ impl<'db> Specialization<'db> {
|
||||||
_ => UnionType::from_elements(db, [self_type, other_type]),
|
_ => UnionType::from_elements(db, [self_type, other_type]),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Specialization::new(db, self.generic_context(db), types)
|
// TODO: Combine the tuple specs too
|
||||||
|
Specialization::new(db, self.generic_context(db), types, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||||
let types: Box<[_]> = self.types(db).iter().map(|ty| ty.normalized(db)).collect();
|
let types: Box<[_]> = self.types(db).iter().map(|ty| ty.normalized(db)).collect();
|
||||||
Self::new(db, self.generic_context(db), types)
|
let tuple_inner = self.tuple_inner(db).map(|tuple| tuple.normalized(db));
|
||||||
|
Self::new(db, self.generic_context(db), types, tuple_inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||||
|
@ -374,7 +391,11 @@ impl<'db> Specialization<'db> {
|
||||||
vartype.materialize(db, variance)
|
vartype.materialize(db, variance)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Specialization::new(db, self.generic_context(db), types)
|
let tuple_inner = self.tuple_inner(db).map(|tuple| {
|
||||||
|
// Tuples are immutable, so tuple element types are always in covariant position.
|
||||||
|
tuple.materialize(db, variance)
|
||||||
|
});
|
||||||
|
Specialization::new(db, self.generic_context(db), types, tuple_inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn has_relation_to(
|
pub(crate) fn has_relation_to(
|
||||||
|
@ -388,6 +409,11 @@ impl<'db> Specialization<'db> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
|
||||||
|
{
|
||||||
|
return self_tuple.has_relation_to(db, other_tuple, relation);
|
||||||
|
}
|
||||||
|
|
||||||
for ((typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
for ((typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
|
||||||
.zip(self.types(db))
|
.zip(self.types(db))
|
||||||
.zip(other.types(db))
|
.zip(other.types(db))
|
||||||
|
@ -570,7 +596,8 @@ impl<'db> SpecializationBuilder<'db> {
|
||||||
.unwrap_or(variable.default_ty(self.db).unwrap_or(Type::unknown()))
|
.unwrap_or(variable.default_ty(self.db).unwrap_or(Type::unknown()))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Specialization::new(self.db, generic_context, types)
|
// TODO Infer the tuple spec for a tuple type
|
||||||
|
Specialization::new(self.db, generic_context, types, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_type_mapping(&mut self, typevar: TypeVarInstance<'db>, ty: Type<'db>) {
|
fn add_type_mapping(&mut self, typevar: TypeVarInstance<'db>, ty: Type<'db>) {
|
||||||
|
@ -641,14 +668,19 @@ impl<'db> SpecializationBuilder<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => {
|
(Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => {
|
||||||
let formal_elements = formal_tuple.elements(self.db);
|
let formal_tuple = formal_tuple.tuple(self.db);
|
||||||
let actual_elements = actual_tuple.elements(self.db);
|
let actual_tuple = actual_tuple.tuple(self.db);
|
||||||
if formal_elements.len() == actual_elements.len() {
|
match (formal_tuple, actual_tuple) {
|
||||||
for (formal_element, actual_element) in
|
(TupleSpec::Fixed(formal_tuple), TupleSpec::Fixed(actual_tuple)) => {
|
||||||
formal_elements.iter().zip(actual_elements)
|
if formal_tuple.len() == actual_tuple.len() {
|
||||||
{
|
for (formal_element, actual_element) in formal_tuple.elements().zip(actual_tuple.elements()) {
|
||||||
self.infer(*formal_element, *actual_element)?;
|
self.infer(formal_element, actual_element)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Infer specializations of variable-length tuples
|
||||||
|
(TupleSpec::Variable(_), _) | (_, TupleSpec::Variable(_)) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,11 +117,16 @@ impl AllMembers {
|
||||||
| Type::KnownInstance(_)
|
| Type::KnownInstance(_)
|
||||||
| Type::TypeVar(_)
|
| Type::TypeVar(_)
|
||||||
| Type::BoundSuper(_)
|
| Type::BoundSuper(_)
|
||||||
| Type::TypeIs(_) => {
|
| Type::TypeIs(_) => match ty.to_meta_type(db) {
|
||||||
if let Type::ClassLiteral(class_literal) = ty.to_meta_type(db) {
|
Type::ClassLiteral(class_literal) => {
|
||||||
self.extend_with_class_members(db, class_literal);
|
self.extend_with_class_members(db, class_literal);
|
||||||
}
|
}
|
||||||
}
|
Type::GenericAlias(generic_alias) => {
|
||||||
|
let class_literal = generic_alias.origin(db);
|
||||||
|
self.extend_with_class_members(db, class_literal);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
|
||||||
Type::ModuleLiteral(literal) => {
|
Type::ModuleLiteral(literal) => {
|
||||||
self.extend_with_type(db, KnownClass::ModuleType.to_instance(db));
|
self.extend_with_type(db, KnownClass::ModuleType.to_instance(db));
|
||||||
|
|
|
@ -95,15 +95,16 @@ use crate::types::function::{
|
||||||
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::tuple::{TupleSpec, TupleType};
|
||||||
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, GenericAlias, IntersectionBuilder, IntersectionType, KnownClass,
|
DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType,
|
||||||
KnownInstanceType, LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate,
|
LintDiagnosticGuard, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
|
||||||
PEP695TypeAliasType, Parameter, ParameterForm, Parameters, SpecialFormType, StringLiteralType,
|
ParameterForm, Parameters, SpecialFormType, StringLiteralType, SubclassOfType, Truthiness,
|
||||||
SubclassOfType, Truthiness, TupleType, Type, TypeAliasType, TypeAndQualifiers,
|
Type, TypeAliasType, TypeAndQualifiers, TypeArrayDisplay, TypeIsType, TypeQualifiers,
|
||||||
TypeArrayDisplay, TypeIsType, TypeQualifiers, TypeVarBoundOrConstraints, TypeVarInstance,
|
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionBuilder,
|
||||||
TypeVarKind, TypeVarVariance, UnionBuilder, UnionType, binding_type, todo_type,
|
UnionType, binding_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};
|
||||||
|
@ -2443,7 +2444,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
todo_type!("PEP 646")
|
todo_type!("PEP 646")
|
||||||
} else {
|
} else {
|
||||||
let annotated_type = self.file_expression_type(annotation);
|
let annotated_type = self.file_expression_type(annotation);
|
||||||
KnownClass::Tuple.to_specialized_instance(self.db(), [annotated_type])
|
TupleType::homogeneous(self.db(), annotated_type)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_declaration_with_binding(
|
self.add_declaration_with_binding(
|
||||||
|
@ -2455,7 +2456,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
parameter.into(),
|
parameter.into(),
|
||||||
definition,
|
definition,
|
||||||
KnownClass::Tuple.to_specialized_instance(self.db(), [Type::unknown()]),
|
TupleType::homogeneous(self.db(), Type::unknown()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2832,7 +2833,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
// it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
|
// it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
|
||||||
let symbol_ty = if let Type::Tuple(tuple) = node_ty {
|
let symbol_ty = if let Type::Tuple(tuple) = node_ty {
|
||||||
let mut builder = UnionBuilder::new(self.db());
|
let mut builder = UnionBuilder::new(self.db());
|
||||||
for element in tuple.elements(self.db()).iter().copied() {
|
for element in tuple.tuple(self.db()).all_elements() {
|
||||||
builder = builder.add(
|
builder = builder.add(
|
||||||
if element.is_assignable_to(self.db(), type_base_exception) {
|
if element.is_assignable_to(self.db(), type_base_exception) {
|
||||||
element.to_instance(self.db()).expect(
|
element.to_instance(self.db()).expect(
|
||||||
|
@ -2855,7 +2856,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
)
|
)
|
||||||
} else if node_ty.is_assignable_to(
|
} else if node_ty.is_assignable_to(
|
||||||
self.db(),
|
self.db(),
|
||||||
KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]),
|
TupleType::homogeneous(self.db(), type_base_exception),
|
||||||
) {
|
) {
|
||||||
extract_tuple_specialization(self.db(), node_ty)
|
extract_tuple_specialization(self.db(), node_ty)
|
||||||
.unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db()))
|
.unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db()))
|
||||||
|
@ -2865,7 +2866,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.db(),
|
self.db(),
|
||||||
[
|
[
|
||||||
type_base_exception,
|
type_base_exception,
|
||||||
KnownClass::Tuple.to_specialized_instance(self.db(), [type_base_exception]),
|
TupleType::homogeneous(self.db(), type_base_exception),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
@ -3698,9 +3699,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
ast::Expr::List(ast::ExprList { elts, .. })
|
ast::Expr::List(ast::ExprList { elts, .. })
|
||||||
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||||
let mut assigned_tys = match assigned_ty {
|
let mut assigned_tys = match assigned_ty {
|
||||||
Some(Type::Tuple(tuple)) => {
|
Some(Type::Tuple(tuple)) => Either::Left(tuple.tuple(self.db()).all_elements()),
|
||||||
Either::Left(tuple.elements(self.db()).iter().copied())
|
|
||||||
}
|
|
||||||
Some(_) | None => Either::Right(std::iter::empty()),
|
Some(_) | None => Either::Right(std::iter::empty()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6940,6 +6939,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
(Type::BooleanLiteral(b1), Type::BooleanLiteral(b2), ast::Operator::BitXor) => {
|
(Type::BooleanLiteral(b1), Type::BooleanLiteral(b2), ast::Operator::BitXor) => {
|
||||||
Some(Type::BooleanLiteral(b1 ^ b2))
|
Some(Type::BooleanLiteral(b1 ^ b2))
|
||||||
}
|
}
|
||||||
|
|
||||||
(Type::BooleanLiteral(b1), Type::BooleanLiteral(_) | Type::IntLiteral(_), op) => self
|
(Type::BooleanLiteral(b1), Type::BooleanLiteral(_) | Type::IntLiteral(_), op) => self
|
||||||
.infer_binary_expression_type(
|
.infer_binary_expression_type(
|
||||||
node,
|
node,
|
||||||
|
@ -6956,19 +6956,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
Type::IntLiteral(i64::from(b2)),
|
Type::IntLiteral(i64::from(b2)),
|
||||||
op,
|
op,
|
||||||
),
|
),
|
||||||
(Type::Tuple(lhs), Type::Tuple(rhs), ast::Operator::Add) => {
|
|
||||||
// Note: this only works on heterogeneous tuples.
|
|
||||||
let lhs_elements = lhs.elements(self.db());
|
|
||||||
let rhs_elements = rhs.elements(self.db());
|
|
||||||
|
|
||||||
Some(TupleType::from_elements(
|
(Type::Tuple(lhs), Type::Tuple(rhs), ast::Operator::Add) => Some(Type::tuple(
|
||||||
|
self.db(),
|
||||||
|
TupleType::new(
|
||||||
self.db(),
|
self.db(),
|
||||||
lhs_elements
|
lhs.tuple(self.db()).concat(self.db(), rhs.tuple(self.db())),
|
||||||
.iter()
|
),
|
||||||
.copied()
|
)),
|
||||||
.chain(rhs_elements.iter().copied()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We've handled all of the special cases that we support for literals, so we need to
|
// We've handled all of the special cases that we support for literals, so we need to
|
||||||
// fall back on looking for dunder methods on one of the operand types.
|
// fall back on looking for dunder methods on one of the operand types.
|
||||||
|
@ -7425,19 +7420,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
// tuples.
|
// tuples.
|
||||||
//
|
//
|
||||||
// Ref: https://github.com/astral-sh/ruff/pull/18251#discussion_r2115909311
|
// Ref: https://github.com/astral-sh/ruff/pull/18251#discussion_r2115909311
|
||||||
if tuple.len(self.db()) > 1 << 12 {
|
let (minimum_length, _) = tuple.tuple(self.db()).size_hint();
|
||||||
|
if minimum_length > 1 << 12 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut definitely_true = false;
|
let mut definitely_true = false;
|
||||||
let mut definitely_false = true;
|
let mut definitely_false = true;
|
||||||
for element in tuple.elements(self.db()) {
|
for element in tuple.tuple(self.db()).all_elements() {
|
||||||
if element.is_string_literal() {
|
if element.is_string_literal() {
|
||||||
if literal == *element {
|
if literal == element {
|
||||||
definitely_true = true;
|
definitely_true = true;
|
||||||
definitely_false = false;
|
definitely_false = false;
|
||||||
}
|
}
|
||||||
} else if !literal.is_disjoint_from(self.db(), *element) {
|
} else if !literal.is_disjoint_from(self.db(), element) {
|
||||||
definitely_false = false;
|
definitely_false = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7697,12 +7693,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(Type::Tuple(lhs), Type::Tuple(rhs)) => {
|
(Type::Tuple(lhs), Type::Tuple(rhs)) => {
|
||||||
// Note: This only works on heterogeneous tuple types.
|
let lhs_tuple = lhs.tuple(self.db());
|
||||||
let lhs_elements = lhs.elements(self.db());
|
let rhs_tuple = rhs.tuple(self.db());
|
||||||
let rhs_elements = rhs.elements(self.db());
|
|
||||||
|
|
||||||
let mut tuple_rich_comparison =
|
let mut tuple_rich_comparison =
|
||||||
|op| self.infer_tuple_rich_comparison(lhs_elements, op, rhs_elements, range);
|
|op| self.infer_tuple_rich_comparison(lhs_tuple, op, rhs_tuple, range);
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq),
|
ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq),
|
||||||
|
@ -7712,14 +7707,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt),
|
ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt),
|
||||||
ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge),
|
ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge),
|
||||||
ast::CmpOp::In | ast::CmpOp::NotIn => {
|
ast::CmpOp::In | ast::CmpOp::NotIn => {
|
||||||
let mut eq_count = 0usize;
|
let mut any_eq = false;
|
||||||
let mut not_eq_count = 0usize;
|
let mut any_ambiguous = false;
|
||||||
|
|
||||||
for ty in rhs_elements {
|
for ty in rhs_tuple.all_elements() {
|
||||||
let eq_result = self.infer_binary_type_comparison(
|
let eq_result = self.infer_binary_type_comparison(
|
||||||
Type::Tuple(lhs),
|
Type::Tuple(lhs),
|
||||||
ast::CmpOp::Eq,
|
ast::CmpOp::Eq,
|
||||||
*ty,
|
ty,
|
||||||
range,
|
range,
|
||||||
).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`");
|
).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`");
|
||||||
|
|
||||||
|
@ -7729,16 +7724,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
// for different union variants. Instead, this is just for us to
|
// for different union variants. Instead, this is just for us to
|
||||||
// evaluate a possibly truthy value to `false` or `true`.
|
// evaluate a possibly truthy value to `false` or `true`.
|
||||||
ty => match ty.bool(self.db()) {
|
ty => match ty.bool(self.db()) {
|
||||||
Truthiness::AlwaysTrue => eq_count += 1,
|
Truthiness::AlwaysTrue => any_eq = true,
|
||||||
Truthiness::AlwaysFalse => not_eq_count += 1,
|
Truthiness::AlwaysFalse => (),
|
||||||
Truthiness::Ambiguous => (),
|
Truthiness::Ambiguous => any_ambiguous = true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if eq_count >= 1 {
|
if any_eq {
|
||||||
Ok(Type::BooleanLiteral(op.is_in()))
|
Ok(Type::BooleanLiteral(op.is_in()))
|
||||||
} else if not_eq_count == rhs_elements.len() {
|
} else if !any_ambiguous {
|
||||||
Ok(Type::BooleanLiteral(op.is_not_in()))
|
Ok(Type::BooleanLiteral(op.is_not_in()))
|
||||||
} else {
|
} else {
|
||||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||||
|
@ -7914,13 +7909,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
/// see `<https://github.com/python/cpython/blob/9d6366b60d01305fc5e45100e0cd13e358aa397d/Objects/tupleobject.c#L637>`
|
/// see `<https://github.com/python/cpython/blob/9d6366b60d01305fc5e45100e0cd13e358aa397d/Objects/tupleobject.c#L637>`
|
||||||
fn infer_tuple_rich_comparison(
|
fn infer_tuple_rich_comparison(
|
||||||
&mut self,
|
&mut self,
|
||||||
left: &[Type<'db>],
|
left: &TupleSpec<'db>,
|
||||||
op: RichCompareOperator,
|
op: RichCompareOperator,
|
||||||
right: &[Type<'db>],
|
right: &TupleSpec<'db>,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||||
let left_iter = left.iter().copied();
|
// If either tuple is variable length, we can make no assumptions about the relative
|
||||||
let right_iter = right.iter().copied();
|
// lengths of the tuples, and therefore neither about how they compare lexicographically.
|
||||||
|
// TODO: Consider comparing the prefixes of the tuples, since that could give a comparison
|
||||||
|
// result regardless of how long the variable-length tuple is.
|
||||||
|
let (TupleSpec::Fixed(left), TupleSpec::Fixed(right)) = (left, right) else {
|
||||||
|
return Ok(Type::unknown());
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_iter = left.elements();
|
||||||
|
let right_iter = right.elements();
|
||||||
|
|
||||||
let mut builder = UnionBuilder::new(self.db());
|
let mut builder = UnionBuilder::new(self.db());
|
||||||
|
|
||||||
|
@ -8052,11 +8055,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
// special cases, too.
|
// special cases, too.
|
||||||
if let Type::ClassLiteral(class) = value_ty {
|
if let Type::ClassLiteral(class) = value_ty {
|
||||||
if class.is_known(self.db(), KnownClass::Tuple) {
|
if class.is_known(self.db(), KnownClass::Tuple) {
|
||||||
self.infer_expression(slice);
|
return self
|
||||||
// TODO heterogeneous and homogeneous tuples in value expressions
|
.infer_tuple_type_expression(slice)
|
||||||
return Type::from(
|
.to_meta_type(self.db());
|
||||||
class.todo_specialization(self.db(), "Generic tuple specializations"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if let Some(generic_context) = class.generic_context(self.db()) {
|
if let Some(generic_context) = class.generic_context(self.db()) {
|
||||||
return self.infer_explicit_class_specialization(
|
return self.infer_explicit_class_specialization(
|
||||||
|
@ -8067,6 +8068,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty {
|
||||||
|
return self
|
||||||
|
.infer_tuple_type_expression(slice)
|
||||||
|
.to_meta_type(self.db());
|
||||||
|
}
|
||||||
|
|
||||||
let slice_ty = self.infer_expression(slice);
|
let slice_ty = self.infer_expression(slice);
|
||||||
let result_ty = self.infer_subscript_expression_types(value, value_ty, slice_ty);
|
let result_ty = self.infer_subscript_expression_types(value, value_ty, slice_ty);
|
||||||
|
@ -8113,9 +8119,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.matching_overloads()
|
.matching_overloads()
|
||||||
.next()
|
.next()
|
||||||
.expect("valid bindings should have matching overload");
|
.expect("valid bindings should have matching overload");
|
||||||
let specialization =
|
Type::from(generic_class.apply_specialization(self.db(), |_| {
|
||||||
generic_context.specialize_partial(self.db(), overload.parameter_types());
|
generic_context.specialize_partial(self.db(), overload.parameter_types())
|
||||||
Type::from(GenericAlias::new(self.db(), generic_class, specialization))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_subscript_expression_types(
|
fn infer_subscript_expression_types(
|
||||||
|
@ -8137,18 +8143,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||||
(Type::Tuple(tuple_ty), Type::IntLiteral(int), _) if i32::try_from(int).is_ok() => {
|
(Type::Tuple(tuple_ty), Type::IntLiteral(int), _) if i32::try_from(int).is_ok() => {
|
||||||
let elements = tuple_ty.elements(self.db());
|
let tuple = tuple_ty.tuple(self.db());
|
||||||
elements
|
tuple
|
||||||
.iter()
|
.py_index(
|
||||||
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
self.db(),
|
||||||
.copied()
|
i32::try_from(int).expect("checked in branch arm"),
|
||||||
|
)
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
report_index_out_of_bounds(
|
report_index_out_of_bounds(
|
||||||
&self.context,
|
&self.context,
|
||||||
"tuple",
|
"tuple",
|
||||||
value_node.into(),
|
value_node.into(),
|
||||||
value_ty,
|
value_ty,
|
||||||
elements.len(),
|
tuple.display_minimum_length(),
|
||||||
int,
|
int,
|
||||||
);
|
);
|
||||||
Type::unknown()
|
Type::unknown()
|
||||||
|
@ -8156,9 +8163,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
// Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)`
|
// Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)`
|
||||||
(Type::Tuple(tuple_ty), _, Some(SliceLiteral { start, stop, step })) => {
|
(Type::Tuple(tuple_ty), _, Some(SliceLiteral { start, stop, step })) => {
|
||||||
let elements = tuple_ty.elements(self.db());
|
let TupleSpec::Fixed(tuple) = tuple_ty.tuple(self.db()) else {
|
||||||
|
return todo_type!("slice into variable-length tuple");
|
||||||
|
};
|
||||||
|
|
||||||
if let Ok(new_elements) = elements.py_slice(start, stop, step) {
|
if let Ok(new_elements) = tuple.py_slice(self.db(), start, stop, step) {
|
||||||
TupleType::from_elements(self.db(), new_elements)
|
TupleType::from_elements(self.db(), new_elements)
|
||||||
} else {
|
} else {
|
||||||
report_slice_step_size_zero(&self.context, value_node.into());
|
report_slice_step_size_zero(&self.context, value_node.into());
|
||||||
|
@ -8170,9 +8179,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
if i32::try_from(int).is_ok() =>
|
if i32::try_from(int).is_ok() =>
|
||||||
{
|
{
|
||||||
let literal_value = literal_ty.value(self.db());
|
let literal_value = literal_ty.value(self.db());
|
||||||
literal_value
|
(&mut literal_value.chars())
|
||||||
.chars()
|
.py_index(
|
||||||
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
self.db(),
|
||||||
|
i32::try_from(int).expect("checked in branch arm"),
|
||||||
|
)
|
||||||
.map(|ch| Type::string_literal(self.db(), &ch.to_string()))
|
.map(|ch| Type::string_literal(self.db(), &ch.to_string()))
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
report_index_out_of_bounds(
|
report_index_out_of_bounds(
|
||||||
|
@ -8192,7 +8203,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
|
|
||||||
let chars: Vec<_> = literal_value.chars().collect();
|
let chars: Vec<_> = literal_value.chars().collect();
|
||||||
|
|
||||||
if let Ok(new_chars) = chars.py_slice(start, stop, step) {
|
if let Ok(new_chars) = chars.py_slice(self.db(), start, stop, step) {
|
||||||
let literal: String = new_chars.collect();
|
let literal: String = new_chars.collect();
|
||||||
Type::string_literal(self.db(), &literal)
|
Type::string_literal(self.db(), &literal)
|
||||||
} else {
|
} else {
|
||||||
|
@ -8206,8 +8217,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
{
|
{
|
||||||
let literal_value = literal_ty.value(self.db());
|
let literal_value = literal_ty.value(self.db());
|
||||||
literal_value
|
literal_value
|
||||||
.iter()
|
.py_index(
|
||||||
.py_index(i32::try_from(int).expect("checked in branch arm"))
|
self.db(),
|
||||||
|
i32::try_from(int).expect("checked in branch arm"),
|
||||||
|
)
|
||||||
.map(|byte| Type::IntLiteral((*byte).into()))
|
.map(|byte| Type::IntLiteral((*byte).into()))
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
report_index_out_of_bounds(
|
report_index_out_of_bounds(
|
||||||
|
@ -8225,7 +8238,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
(Type::BytesLiteral(literal_ty), _, Some(SliceLiteral { start, stop, step })) => {
|
(Type::BytesLiteral(literal_ty), _, Some(SliceLiteral { start, stop, step })) => {
|
||||||
let literal_value = literal_ty.value(self.db());
|
let literal_value = literal_ty.value(self.db());
|
||||||
|
|
||||||
if let Ok(new_bytes) = literal_value.py_slice(start, stop, step) {
|
if let Ok(new_bytes) = literal_value.py_slice(self.db(), start, stop, step) {
|
||||||
let new_bytes: Vec<u8> = new_bytes.copied().collect();
|
let new_bytes: Vec<u8> = new_bytes.copied().collect();
|
||||||
Type::bytes_literal(self.db(), &new_bytes)
|
Type::bytes_literal(self.db(), &new_bytes)
|
||||||
} else {
|
} else {
|
||||||
|
@ -8243,14 +8256,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
value_ty,
|
value_ty,
|
||||||
Type::IntLiteral(i64::from(bool)),
|
Type::IntLiteral(i64::from(bool)),
|
||||||
),
|
),
|
||||||
(Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars), _) => self
|
(Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars), _) => {
|
||||||
.legacy_generic_class_context(
|
let TupleSpec::Fixed(typevars) = typevars.tuple(self.db()) else {
|
||||||
|
// TODO: emit a diagnostic
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
|
self.legacy_generic_class_context(
|
||||||
value_node,
|
value_node,
|
||||||
typevars.elements(self.db()),
|
typevars.elements_slice(),
|
||||||
LegacyGenericBase::Protocol,
|
LegacyGenericBase::Protocol,
|
||||||
)
|
)
|
||||||
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context)))
|
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context)))
|
||||||
.unwrap_or_else(Type::unknown),
|
.unwrap_or_else(Type::unknown)
|
||||||
|
}
|
||||||
(Type::SpecialForm(SpecialFormType::Protocol), typevar, _) => self
|
(Type::SpecialForm(SpecialFormType::Protocol), typevar, _) => self
|
||||||
.legacy_generic_class_context(
|
.legacy_generic_class_context(
|
||||||
value_node,
|
value_node,
|
||||||
|
@ -8263,14 +8281,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
// TODO: emit a diagnostic
|
// TODO: emit a diagnostic
|
||||||
todo_type!("doubly-specialized typing.Protocol")
|
todo_type!("doubly-specialized typing.Protocol")
|
||||||
}
|
}
|
||||||
(Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars), _) => self
|
(Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars), _) => {
|
||||||
.legacy_generic_class_context(
|
let TupleSpec::Fixed(typevars) = typevars.tuple(self.db()) else {
|
||||||
|
// TODO: emit a diagnostic
|
||||||
|
return Type::unknown();
|
||||||
|
};
|
||||||
|
self.legacy_generic_class_context(
|
||||||
value_node,
|
value_node,
|
||||||
typevars.elements(self.db()),
|
typevars.elements_slice(),
|
||||||
LegacyGenericBase::Generic,
|
LegacyGenericBase::Generic,
|
||||||
)
|
)
|
||||||
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context)))
|
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context)))
|
||||||
.unwrap_or_else(Type::unknown),
|
.unwrap_or_else(Type::unknown)
|
||||||
|
}
|
||||||
(Type::SpecialForm(SpecialFormType::Generic), typevar, _) => self
|
(Type::SpecialForm(SpecialFormType::Generic), typevar, _) => self
|
||||||
.legacy_generic_class_context(
|
.legacy_generic_class_context(
|
||||||
value_node,
|
value_node,
|
||||||
|
@ -9167,10 +9190,23 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
todo_type!("ellipsis literal in type expression")
|
todo_type!("ellipsis literal in type expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::Expr::Starred(starred) => {
|
ast::Expr::Starred(starred) => self.infer_starred_type_expression(starred),
|
||||||
self.infer_starred_expression(starred);
|
}
|
||||||
todo_type!("PEP 646")
|
}
|
||||||
}
|
|
||||||
|
fn infer_starred_type_expression(&mut self, starred: &ast::ExprStarred) -> Type<'db> {
|
||||||
|
let ast::ExprStarred {
|
||||||
|
range: _,
|
||||||
|
node_index: _,
|
||||||
|
value,
|
||||||
|
ctx: _,
|
||||||
|
} = starred;
|
||||||
|
|
||||||
|
let starred_type = self.infer_type_expression(value);
|
||||||
|
if let Type::Tuple(_) = starred_type {
|
||||||
|
starred_type
|
||||||
|
} else {
|
||||||
|
todo_type!("PEP 646")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9225,7 +9261,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
match element {
|
match element {
|
||||||
ast::Expr::Starred(_) => true,
|
ast::Expr::Starred(_) => !matches!(element_ty, Type::Tuple(_)),
|
||||||
ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => {
|
ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => {
|
||||||
let value_ty = if builder.deferred_state.in_string_annotation() {
|
let value_ty = if builder.deferred_state.in_string_annotation() {
|
||||||
// Using `.expression_type` does not work in string annotations, because
|
// Using `.expression_type` does not work in string annotations, because
|
||||||
|
@ -9246,13 +9282,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
ast::Expr::Tuple(elements) => {
|
ast::Expr::Tuple(elements) => {
|
||||||
if let [element, ellipsis @ ast::Expr::EllipsisLiteral(_)] = &*elements.elts {
|
if let [element, ellipsis @ ast::Expr::EllipsisLiteral(_)] = &*elements.elts {
|
||||||
self.infer_expression(ellipsis);
|
self.infer_expression(ellipsis);
|
||||||
let result = KnownClass::Tuple
|
let result =
|
||||||
.to_specialized_instance(self.db(), [self.infer_type_expression(element)]);
|
TupleType::homogeneous(self.db(), self.infer_type_expression(element));
|
||||||
self.store_expression_type(tuple_slice, result);
|
self.store_expression_type(tuple_slice, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut element_types = Vec::with_capacity(elements.len());
|
let mut element_types = TupleSpec::with_capacity(elements.len());
|
||||||
|
|
||||||
// Whether to infer `Todo` for the whole tuple
|
// Whether to infer `Todo` for the whole tuple
|
||||||
// (see docstring for `element_could_alter_type_of_whole_tuple`)
|
// (see docstring for `element_could_alter_type_of_whole_tuple`)
|
||||||
|
@ -9262,13 +9298,22 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
let element_ty = self.infer_type_expression(element);
|
let element_ty = self.infer_type_expression(element);
|
||||||
return_todo |=
|
return_todo |=
|
||||||
element_could_alter_type_of_whole_tuple(element, element_ty, self);
|
element_could_alter_type_of_whole_tuple(element, element_ty, self);
|
||||||
element_types.push(element_ty);
|
if let ast::Expr::Starred(_) = element {
|
||||||
|
if let Type::Tuple(inner_tuple) = element_ty {
|
||||||
|
element_types =
|
||||||
|
element_types.concat(self.db(), inner_tuple.tuple(self.db()));
|
||||||
|
} else {
|
||||||
|
// TODO: emit a diagnostic
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
element_types.push(element_ty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ty = if return_todo {
|
let ty = if return_todo {
|
||||||
todo_type!("PEP 646")
|
todo_type!("PEP 646")
|
||||||
} else {
|
} else {
|
||||||
TupleType::from_elements(self.db(), element_types)
|
Type::tuple(self.db(), TupleType::new(self.db(), element_types))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Here, we store the type for the inner `int, str` tuple-expression,
|
// Here, we store the type for the inner `int, str` tuple-expression,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::marker::PhantomData;
|
||||||
use super::protocol_class::ProtocolInterface;
|
use super::protocol_class::ProtocolInterface;
|
||||||
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
||||||
use crate::place::{Boundness, Place, PlaceAndQualifiers};
|
use crate::place::{Boundness, Place, PlaceAndQualifiers};
|
||||||
|
use crate::types::tuple::TupleType;
|
||||||
use crate::types::{ClassLiteral, DynamicType, TypeMapping, TypeRelation, TypeVarInstance};
|
use crate::types::{ClassLiteral, DynamicType, TypeMapping, TypeRelation, TypeVarInstance};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
@ -12,12 +13,18 @@ pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
||||||
|
|
||||||
impl<'db> Type<'db> {
|
impl<'db> Type<'db> {
|
||||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||||
if class.is_known(db, KnownClass::Any) {
|
match (class, class.known(db)) {
|
||||||
Self::Dynamic(DynamicType::Any)
|
(_, Some(KnownClass::Any)) => Self::Dynamic(DynamicType::Any),
|
||||||
} else if class.class_literal(db).0.is_protocol(db) {
|
(ClassType::NonGeneric(_), Some(KnownClass::Tuple)) => {
|
||||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
|
TupleType::homogeneous(db, Type::unknown())
|
||||||
} else {
|
}
|
||||||
Self::NominalInstance(NominalInstanceType::from_class(class))
|
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => {
|
||||||
|
Self::tuple(db, TupleType::new(db, alias.specialization(db).tuple(db)))
|
||||||
|
}
|
||||||
|
_ if class.class_literal(db).0.is_protocol(db) => {
|
||||||
|
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
|
||||||
|
}
|
||||||
|
_ => Self::NominalInstance(NominalInstanceType::from_class(class)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,11 +105,24 @@ impl<'db> NominalInstanceType<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
if self.class.is_final(db) && !self.class.is_subclass_of(db, other.class) {
|
self.is_disjoint_from_nominal_instance_of_class(db, other.class)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this method only exists so that we can check disjointness between nominal
|
||||||
|
// instances of `tuple` and some other class. Tuples are currently represented by the
|
||||||
|
// `Type::Tuple` variant, not `Type::NominalInstance`. We have a TODO to try to remove the
|
||||||
|
// dedicated `Tuple` variant in favor of `NominalInstance`; if we can do that, then we won't
|
||||||
|
// need this method, and its logic can be subsumed into `is_disjoint_from`.
|
||||||
|
pub(super) fn is_disjoint_from_nominal_instance_of_class(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other_class: ClassType,
|
||||||
|
) -> bool {
|
||||||
|
if self.class.is_final(db) && !self.class.is_subclass_of(db, other_class) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if other.class.is_final(db) && !other.class.is_subclass_of(db, self.class) {
|
if other_class.is_final(db) && !other_class.is_subclass_of(db, self.class) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +136,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||||
if self_metaclass == type_type {
|
if self_metaclass == type_type {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let other_metaclass = other.class.metaclass_instance_type(db);
|
let other_metaclass = other_class.metaclass_instance_type(db);
|
||||||
if other_metaclass == type_type {
|
if other_metaclass == type_type {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,8 +175,8 @@ impl ClassInfoConstraintFunction {
|
||||||
match classinfo {
|
match classinfo {
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple) => {
|
||||||
let mut builder = UnionBuilder::new(db);
|
let mut builder = UnionBuilder::new(db);
|
||||||
for element in tuple.elements(db) {
|
for element in tuple.tuple(db).all_elements() {
|
||||||
builder = builder.add(self.generate_constraint(db, *element)?);
|
builder = builder.add(self.generate_constraint(db, element)?);
|
||||||
}
|
}
|
||||||
Some(builder.build())
|
Some(builder.build())
|
||||||
}
|
}
|
||||||
|
@ -540,7 +540,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||||
match rhs_ty {
|
match rhs_ty {
|
||||||
Type::Tuple(rhs_tuple) => Some(UnionType::from_elements(
|
Type::Tuple(rhs_tuple) => Some(UnionType::from_elements(
|
||||||
self.db,
|
self.db,
|
||||||
rhs_tuple.elements(self.db),
|
rhs_tuple.tuple(self.db).all_elements(),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Type::StringLiteral(string_literal) => Some(UnionType::from_elements(
|
Type::StringLiteral(string_literal) => Some(UnionType::from_elements(
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::db::tests::TestDb;
|
use crate::db::tests::TestDb;
|
||||||
use crate::place::{builtins_symbol, known_module_symbol};
|
use crate::place::{builtins_symbol, known_module_symbol};
|
||||||
|
use crate::types::tuple::TupleType;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
BoundMethodType, CallableType, IntersectionBuilder, KnownClass, Parameter, Parameters,
|
BoundMethodType, CallableType, IntersectionBuilder, KnownClass, Parameter, Parameters,
|
||||||
Signature, SpecialFormType, SubclassOfType, TupleType, Type, UnionType,
|
Signature, SpecialFormType, SubclassOfType, Type, UnionType,
|
||||||
};
|
};
|
||||||
use crate::{Db, KnownModule};
|
use crate::{Db, KnownModule};
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
|
|
|
@ -36,7 +36,7 @@ impl SlotsKind {
|
||||||
match slots_ty {
|
match slots_ty {
|
||||||
// __slots__ = ("a", "b")
|
// __slots__ = ("a", "b")
|
||||||
Type::Tuple(tuple) => {
|
Type::Tuple(tuple) => {
|
||||||
if tuple.elements(db).is_empty() {
|
if tuple.tuple(db).is_empty() {
|
||||||
Self::Empty
|
Self::Empty
|
||||||
} else {
|
} else {
|
||||||
Self::NotEmpty
|
Self::NotEmpty
|
||||||
|
|
868
crates/ty_python_semantic/src/types/tuple.rs
Normal file
868
crates/ty_python_semantic/src/types/tuple.rs
Normal file
|
@ -0,0 +1,868 @@
|
||||||
|
//! Types describing fixed- and variable-length tuples.
|
||||||
|
//!
|
||||||
|
//! At runtime, a Python tuple is a fixed-length immutable list of values. There is no restriction
|
||||||
|
//! on the types of the elements of a tuple value. In the type system, we want to model both
|
||||||
|
//! "heterogeneous" tuples that have elements of a fixed sequence of specific types, and
|
||||||
|
//! "homogeneous" tuples that have an unknown number of elements of the same single type. And in
|
||||||
|
//! fact, we want to model tuples that are a combination of the two ("mixed" tuples), with a
|
||||||
|
//! heterogeneous prefix and/or suffix, and a homogeneous portion of unknown length in between
|
||||||
|
//! those.
|
||||||
|
//!
|
||||||
|
//! The description of which elements can appear in a `tuple` is called a [`TupleSpec`]. Other
|
||||||
|
//! things besides `tuple` instances can be described by a tuple spec — for instance, the targets
|
||||||
|
//! of an unpacking assignment. A `tuple` specialization that includes `Never` as one of its
|
||||||
|
//! fixed-length elements cannot be instantiated. We reduce the entire `tuple` type down to
|
||||||
|
//! `Never`. The same is not true of tuple specs in general. (That means that it is [`TupleType`]
|
||||||
|
//! that adds that "collapse `Never`" behavior, whereas [`TupleSpec`] allows you to add any element
|
||||||
|
//! types, including `Never`.)
|
||||||
|
|
||||||
|
use itertools::Either;
|
||||||
|
|
||||||
|
use crate::types::class::{ClassType, KnownClass};
|
||||||
|
use crate::types::{Type, TypeMapping, TypeRelation, TypeVarInstance, TypeVarVariance, UnionType};
|
||||||
|
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
||||||
|
use crate::{Db, FxOrderSet};
|
||||||
|
|
||||||
|
/// # Ordering
|
||||||
|
/// Ordering is based on the tuple's salsa-assigned id and not on its elements.
|
||||||
|
/// The id may change between runs, or when the tuple was garbage collected and recreated.
|
||||||
|
#[salsa::interned(debug)]
|
||||||
|
#[derive(PartialOrd, Ord)]
|
||||||
|
pub struct TupleType<'db> {
|
||||||
|
#[returns(ref)]
|
||||||
|
pub(crate) tuple: TupleSpec<'db>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> Type<'db> {
|
||||||
|
pub(crate) fn tuple(db: &'db dyn Db, tuple: TupleType<'db>) -> Self {
|
||||||
|
// If a fixed-length (i.e., mandatory) element of the tuple is `Never`, then it's not
|
||||||
|
// possible to instantiate the tuple as a whole. (This is not true of the variable-length
|
||||||
|
// portion of the tuple, since it can contain no elements.)
|
||||||
|
if tuple.tuple(db).fixed_elements().any(|ty| ty.is_never()) {
|
||||||
|
return Type::Never;
|
||||||
|
}
|
||||||
|
Self::Tuple(tuple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> TupleType<'db> {
|
||||||
|
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
|
||||||
|
Type::tuple(
|
||||||
|
db,
|
||||||
|
TupleType::new(db, TupleSpec::from(FixedLengthTupleSpec::empty())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_elements(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
types: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
Type::tuple(
|
||||||
|
db,
|
||||||
|
TupleType::new(
|
||||||
|
db,
|
||||||
|
TupleSpec::from(FixedLengthTupleSpec::from_elements(types)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn mixed(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
prefix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||||
|
variable: Type<'db>,
|
||||||
|
suffix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
Type::tuple(
|
||||||
|
db,
|
||||||
|
TupleType::new(
|
||||||
|
db,
|
||||||
|
TupleSpec::from(VariableLengthTupleSpec::mixed(prefix, variable, suffix)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn homogeneous(db: &'db dyn Db, element: Type<'db>) -> Type<'db> {
|
||||||
|
Type::tuple(db, TupleType::new(db, TupleSpec::homogeneous(element)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||||
|
KnownClass::Tuple
|
||||||
|
.try_to_class_literal(db)
|
||||||
|
.and_then(|class_literal| match class_literal.generic_context(db) {
|
||||||
|
None => Some(ClassType::NonGeneric(class_literal)),
|
||||||
|
Some(generic_context) if generic_context.variables(db).len() != 1 => None,
|
||||||
|
Some(generic_context) => Some(
|
||||||
|
class_literal
|
||||||
|
.apply_specialization(db, |_| generic_context.specialize_tuple(db, self)),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a normalized version of `self`.
|
||||||
|
///
|
||||||
|
/// See [`Type::normalized`] for more details.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||||
|
TupleType::new(db, self.tuple(db).normalized(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||||
|
TupleType::new(db, self.tuple(db).materialize(db, variance))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_type_mapping<'a>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
type_mapping: &TypeMapping<'a, 'db>,
|
||||||
|
) -> Self {
|
||||||
|
TupleType::new(db, self.tuple(db).apply_type_mapping(db, type_mapping))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_legacy_typevars(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||||
|
) {
|
||||||
|
self.tuple(db).find_legacy_typevars(db, typevars);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn has_relation_to(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: Self,
|
||||||
|
relation: TypeRelation,
|
||||||
|
) -> bool {
|
||||||
|
self.tuple(db)
|
||||||
|
.has_relation_to(db, other.tuple(db), relation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
self.tuple(db).is_equivalent_to(db, other.tuple(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
self.tuple(db).is_gradual_equivalent_to(db, other.tuple(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
self.tuple(db).is_disjoint_from(db, other.tuple(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_fully_static(self, db: &'db dyn Db) -> bool {
|
||||||
|
self.tuple(db).is_fully_static(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||||
|
self.tuple(db).is_single_valued(db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fixed-length tuple spec.
|
||||||
|
///
|
||||||
|
/// Tuple specs are used for more than just `tuple` instances, so they allow `Never` to appear as a
|
||||||
|
/// fixed-length element type. [`TupleType`] adds that additional invariant (since a tuple that
|
||||||
|
/// must contain an element that can't be instantiated, can't be instantiated itself).
|
||||||
|
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, salsa::Update)]
|
||||||
|
pub struct FixedLengthTupleSpec<'db>(Vec<Type<'db>>);
|
||||||
|
|
||||||
|
impl<'db> FixedLengthTupleSpec<'db> {
|
||||||
|
pub(crate) fn empty() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Self(Vec::with_capacity(capacity))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_elements(elements: impl IntoIterator<Item = impl Into<Type<'db>>>) -> Self {
|
||||||
|
Self(elements.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn elements_slice(&self) -> &[Type<'db>] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||||
|
self.0.iter().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of this tuple.
|
||||||
|
pub(crate) fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn concat(&self, other: &TupleSpec<'db>) -> TupleSpec<'db> {
|
||||||
|
match other {
|
||||||
|
TupleSpec::Fixed(other) => {
|
||||||
|
let mut elements = Vec::with_capacity(self.0.len() + other.0.len());
|
||||||
|
elements.extend_from_slice(&self.0);
|
||||||
|
elements.extend_from_slice(&other.0);
|
||||||
|
TupleSpec::Fixed(FixedLengthTupleSpec(elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
TupleSpec::Variable(other) => {
|
||||||
|
let mut prefix = Vec::with_capacity(self.0.len() + other.prefix.len());
|
||||||
|
prefix.extend_from_slice(&self.0);
|
||||||
|
prefix.extend_from_slice(&other.prefix);
|
||||||
|
TupleSpec::Variable(VariableLengthTupleSpec {
|
||||||
|
prefix,
|
||||||
|
variable: other.variable,
|
||||||
|
suffix: other.suffix.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn push(&mut self, element: Type<'db>) {
|
||||||
|
self.0.push(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn extend_from_slice(&mut self, elements: &[Type<'db>]) {
|
||||||
|
self.0.extend_from_slice(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||||
|
Self(self.0.iter().map(|ty| ty.normalized(db)).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||||
|
Self(
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.materialize(db, variance))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||||
|
Self(
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_legacy_typevars(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||||
|
) {
|
||||||
|
for ty in &self.0 {
|
||||||
|
ty.find_legacy_typevars(db, typevars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_relation_to(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: &TupleSpec<'db>,
|
||||||
|
relation: TypeRelation,
|
||||||
|
) -> bool {
|
||||||
|
match other {
|
||||||
|
TupleSpec::Fixed(other) => {
|
||||||
|
self.0.len() == other.0.len()
|
||||||
|
&& (self.0.iter())
|
||||||
|
.zip(&other.0)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.has_relation_to(db, *other_ty, relation))
|
||||||
|
}
|
||||||
|
|
||||||
|
TupleSpec::Variable(other) => {
|
||||||
|
// This tuple must have enough elements to match up with the other tuple's prefix
|
||||||
|
// and suffix, and each of those elements must pairwise satisfy the relation.
|
||||||
|
let mut self_iter = self.0.iter();
|
||||||
|
for other_ty in &other.prefix {
|
||||||
|
let Some(self_ty) = self_iter.next() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for other_ty in other.suffix.iter().rev() {
|
||||||
|
let Some(self_ty) = self_iter.next_back() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In addition, any remaining elements in this tuple must satisfy the
|
||||||
|
// variable-length portion of the other tuple.
|
||||||
|
self_iter.all(|self_ty| self_ty.has_relation_to(db, other.variable, relation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
|
self.0.len() == other.0.len()
|
||||||
|
&& (self.0.iter())
|
||||||
|
.zip(&other.0)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
|
self.0.len() == other.0.len()
|
||||||
|
&& (self.0.iter())
|
||||||
|
.zip(&other.0)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_disjoint_from(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
|
self.0.len() != other.0.len()
|
||||||
|
|| (self.0.iter())
|
||||||
|
.zip(&other.0)
|
||||||
|
.any(|(self_ty, other_ty)| self_ty.is_disjoint_from(db, *other_ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fully_static(&self, db: &'db dyn Db) -> bool {
|
||||||
|
self.0.iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||||
|
self.0.iter().all(|ty| ty.is_single_valued(db))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> PyIndex<'db> for &FixedLengthTupleSpec<'db> {
|
||||||
|
type Item = Type<'db>;
|
||||||
|
|
||||||
|
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError> {
|
||||||
|
self.0.as_slice().py_index(db, index).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> PySlice<'db> for FixedLengthTupleSpec<'db> {
|
||||||
|
type Item = Type<'db>;
|
||||||
|
|
||||||
|
fn py_slice(
|
||||||
|
&'db self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
start: Option<i32>,
|
||||||
|
stop: Option<i32>,
|
||||||
|
step: Option<i32>,
|
||||||
|
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError> {
|
||||||
|
self.0.py_slice(db, start, stop, step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A variable-length tuple spec.
|
||||||
|
///
|
||||||
|
/// The tuple spec can contain a fixed-length heterogeneous prefix and/or suffix. All of the
|
||||||
|
/// elements of the variable-length portion must be of the same type.
|
||||||
|
///
|
||||||
|
/// Tuple specs are used for more than just `tuple` instances, so they allow `Never` to appear as a
|
||||||
|
/// fixed-length element type. [`TupleType`] adds that additional invariant (since a tuple that
|
||||||
|
/// must contain an element that can't be instantiated, can't be instantiated itself).
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
|
||||||
|
pub struct VariableLengthTupleSpec<'db> {
|
||||||
|
pub(crate) prefix: Vec<Type<'db>>,
|
||||||
|
pub(crate) variable: Type<'db>,
|
||||||
|
pub(crate) suffix: Vec<Type<'db>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> VariableLengthTupleSpec<'db> {
|
||||||
|
/// Creates a new tuple spec containing zero or more elements of a given type, with no prefix
|
||||||
|
/// or suffix.
|
||||||
|
fn homogeneous(ty: Type<'db>) -> Self {
|
||||||
|
Self {
|
||||||
|
prefix: vec![],
|
||||||
|
variable: ty,
|
||||||
|
suffix: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn mixed(
|
||||||
|
prefix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||||
|
variable: Type<'db>,
|
||||||
|
suffix: impl IntoIterator<Item = impl Into<Type<'db>>>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
prefix: prefix.into_iter().map(Into::into).collect(),
|
||||||
|
variable,
|
||||||
|
suffix: suffix.into_iter().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fixed_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||||
|
(self.prefix.iter().copied()).chain(self.suffix.iter().copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||||
|
(self.prefix.iter().copied())
|
||||||
|
.chain(std::iter::once(self.variable))
|
||||||
|
.chain(self.suffix.iter().copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum length of this tuple.
|
||||||
|
pub(crate) fn minimum_length(&self) -> usize {
|
||||||
|
self.prefix.len() + self.suffix.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn concat(&self, db: &'db dyn Db, other: &TupleSpec<'db>) -> TupleSpec<'db> {
|
||||||
|
match other {
|
||||||
|
TupleSpec::Fixed(other) => {
|
||||||
|
let mut suffix = Vec::with_capacity(self.suffix.len() + other.0.len());
|
||||||
|
suffix.extend_from_slice(&self.suffix);
|
||||||
|
suffix.extend_from_slice(&other.0);
|
||||||
|
TupleSpec::Variable(VariableLengthTupleSpec {
|
||||||
|
prefix: self.prefix.clone(),
|
||||||
|
variable: self.variable,
|
||||||
|
suffix,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
TupleSpec::Variable(other) => {
|
||||||
|
let variable = UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
(self.suffix.iter().copied())
|
||||||
|
.chain([self.variable, other.variable])
|
||||||
|
.chain(other.prefix.iter().copied()),
|
||||||
|
);
|
||||||
|
TupleSpec::Variable(VariableLengthTupleSpec {
|
||||||
|
prefix: self.prefix.clone(),
|
||||||
|
variable,
|
||||||
|
suffix: other.suffix.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, element: Type<'db>) {
|
||||||
|
self.suffix.push(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||||
|
Self {
|
||||||
|
prefix: self.prefix.iter().map(|ty| ty.normalized(db)).collect(),
|
||||||
|
variable: self.variable.normalized(db),
|
||||||
|
suffix: self.suffix.iter().map(|ty| ty.normalized(db)).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||||
|
Self {
|
||||||
|
prefix: self
|
||||||
|
.prefix
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.materialize(db, variance))
|
||||||
|
.collect(),
|
||||||
|
variable: self.variable.materialize(db, variance),
|
||||||
|
suffix: self
|
||||||
|
.suffix
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.materialize(db, variance))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||||
|
Self {
|
||||||
|
prefix: self
|
||||||
|
.prefix
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||||
|
.collect(),
|
||||||
|
variable: self.variable.apply_type_mapping(db, type_mapping),
|
||||||
|
suffix: self
|
||||||
|
.suffix
|
||||||
|
.iter()
|
||||||
|
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_legacy_typevars(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||||
|
) {
|
||||||
|
for ty in &self.prefix {
|
||||||
|
ty.find_legacy_typevars(db, typevars);
|
||||||
|
}
|
||||||
|
self.variable.find_legacy_typevars(db, typevars);
|
||||||
|
for ty in &self.suffix {
|
||||||
|
ty.find_legacy_typevars(db, typevars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_relation_to(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
other: &TupleSpec<'db>,
|
||||||
|
relation: TypeRelation,
|
||||||
|
) -> bool {
|
||||||
|
match other {
|
||||||
|
TupleSpec::Fixed(other) => {
|
||||||
|
// The `...` length specifier of a variable-length tuple type is interpreted
|
||||||
|
// differently depending on the type of the variable-length elements.
|
||||||
|
//
|
||||||
|
// It typically represents the _union_ of all possible lengths. That means that a
|
||||||
|
// variable-length tuple type is not a subtype of _any_ fixed-length tuple type.
|
||||||
|
//
|
||||||
|
// However, as a special case, if the variable-length portion of the tuple is `Any`
|
||||||
|
// (or any other dynamic type), then the `...` is the _gradual choice_ of all
|
||||||
|
// possible lengths. This means that `tuple[Any, ...]` can match any tuple of any
|
||||||
|
// length.
|
||||||
|
if relation == TypeRelation::Subtyping || !matches!(self.variable, Type::Dynamic(_))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In addition, the other tuple must have enough elements to match up with this
|
||||||
|
// tuple's prefix and suffix, and each of those elements must pairwise satisfy the
|
||||||
|
// relation.
|
||||||
|
let mut other_iter = other.0.iter();
|
||||||
|
for self_ty in &self.prefix {
|
||||||
|
let Some(other_ty) = other_iter.next() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for self_ty in self.suffix.iter().rev() {
|
||||||
|
let Some(other_ty) = other_iter.next_back() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !self_ty.has_relation_to(db, *other_ty, relation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
TupleSpec::Variable(other) => {
|
||||||
|
// The overlapping parts of the prefixes and suffixes must satisfy the relation.
|
||||||
|
let mut self_prefix = self.prefix.iter();
|
||||||
|
let mut other_prefix = other.prefix.iter();
|
||||||
|
let prefixes_match = (&mut self_prefix)
|
||||||
|
.zip(&mut other_prefix)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.has_relation_to(db, *other_ty, relation));
|
||||||
|
if !prefixes_match {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut self_suffix = self.suffix.iter().rev();
|
||||||
|
let mut other_suffix = other.suffix.iter().rev();
|
||||||
|
let suffixes_match = (&mut self_suffix)
|
||||||
|
.zip(&mut other_suffix)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.has_relation_to(db, *other_ty, relation));
|
||||||
|
if !suffixes_match {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any remaining parts of either prefix or suffix must satisfy the relation with
|
||||||
|
// the other tuple's variable-length portion.
|
||||||
|
let prefix_matches_variable = self_prefix
|
||||||
|
.all(|self_ty| self_ty.has_relation_to(db, other.variable, relation));
|
||||||
|
if !prefix_matches_variable {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let prefix_matches_variable = other_prefix
|
||||||
|
.all(|other_ty| self.variable.has_relation_to(db, *other_ty, relation));
|
||||||
|
if !prefix_matches_variable {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let suffix_matches_variable = self_suffix
|
||||||
|
.all(|self_ty| self_ty.has_relation_to(db, other.variable, relation));
|
||||||
|
if !suffix_matches_variable {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let suffix_matches_variable = other_suffix
|
||||||
|
.all(|other_ty| self.variable.has_relation_to(db, *other_ty, relation));
|
||||||
|
if !suffix_matches_variable {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And lastly, the variable-length portions must satisfy the relation.
|
||||||
|
self.variable.has_relation_to(db, other.variable, relation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
|
self.prefix.len() == other.prefix.len()
|
||||||
|
&& self.suffix.len() == other.suffix.len()
|
||||||
|
&& self.variable.is_equivalent_to(db, other.variable)
|
||||||
|
&& (self.prefix.iter())
|
||||||
|
.zip(&other.prefix)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
||||||
|
&& (self.suffix.iter())
|
||||||
|
.zip(&other.suffix)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_equivalent_to(db, *other_ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
|
self.prefix.len() == other.prefix.len()
|
||||||
|
&& self.suffix.len() == other.suffix.len()
|
||||||
|
&& self.variable.is_gradual_equivalent_to(db, other.variable)
|
||||||
|
&& (self.prefix.iter())
|
||||||
|
.zip(&other.prefix)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||||
|
&& (self.suffix.iter())
|
||||||
|
.zip(&other.suffix)
|
||||||
|
.all(|(self_ty, other_ty)| self_ty.is_gradual_equivalent_to(db, *other_ty))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fully_static(&self, db: &'db dyn Db) -> bool {
|
||||||
|
self.variable.is_fully_static(db)
|
||||||
|
&& self.prefix.iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
&& self.suffix.iter().all(|ty| ty.is_fully_static(db))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> PyIndex<'db> for &VariableLengthTupleSpec<'db> {
|
||||||
|
type Item = Type<'db>;
|
||||||
|
|
||||||
|
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError> {
|
||||||
|
match Nth::from_index(index) {
|
||||||
|
Nth::FromStart(index) => {
|
||||||
|
if let Some(element) = self.prefix.get(index) {
|
||||||
|
// index is small enough that it lands in the prefix of the tuple.
|
||||||
|
return Ok(*element);
|
||||||
|
}
|
||||||
|
|
||||||
|
// index is large enough that it lands past the prefix. The tuple can always be
|
||||||
|
// large enough that it lands in the variable-length portion. It might also be
|
||||||
|
// small enough to land in the suffix.
|
||||||
|
let index_past_prefix = index - self.prefix.len() + 1;
|
||||||
|
Ok(UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
std::iter::once(self.variable)
|
||||||
|
.chain(self.suffix.iter().copied().take(index_past_prefix)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
Nth::FromEnd(index_from_end) => {
|
||||||
|
if index_from_end < self.suffix.len() {
|
||||||
|
// index is small enough that it lands in the suffix of the tuple.
|
||||||
|
return Ok(self.suffix[self.suffix.len() - index_from_end - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// index is large enough that it lands past the suffix. The tuple can always be
|
||||||
|
// large enough that it lands in the variable-length portion. It might also be
|
||||||
|
// small enough to land in the prefix.
|
||||||
|
let index_past_suffix = index_from_end - self.suffix.len() + 1;
|
||||||
|
Ok(UnionType::from_elements(
|
||||||
|
db,
|
||||||
|
(self.prefix.iter().rev().copied())
|
||||||
|
.take(index_past_suffix)
|
||||||
|
.rev()
|
||||||
|
.chain(std::iter::once(self.variable)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tuple spec that might be fixed- or variable-length.
|
||||||
|
///
|
||||||
|
/// Tuple specs are used for more than just `tuple` instances, so they allow `Never` to appear as a
|
||||||
|
/// fixed-length element type. [`TupleType`] adds that additional invariant (since a tuple that
|
||||||
|
/// must contain an element that can't be instantiated, can't be instantiated itself).
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
|
||||||
|
pub enum TupleSpec<'db> {
|
||||||
|
Fixed(FixedLengthTupleSpec<'db>),
|
||||||
|
Variable(VariableLengthTupleSpec<'db>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> TupleSpec<'db> {
|
||||||
|
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
||||||
|
TupleSpec::Fixed(FixedLengthTupleSpec::with_capacity(capacity))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn homogeneous(element: Type<'db>) -> Self {
|
||||||
|
TupleSpec::from(VariableLengthTupleSpec::homogeneous(element))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator of all of the fixed-length element types of this tuple.
|
||||||
|
pub(crate) fn fixed_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => Either::Left(tuple.elements()),
|
||||||
|
TupleSpec::Variable(tuple) => Either::Right(tuple.fixed_elements()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator of all of the element types of this tuple. Does not deduplicate the
|
||||||
|
/// elements, and does not distinguish between fixed- and variable-length elements.
|
||||||
|
pub(crate) fn all_elements(&self) -> impl Iterator<Item = Type<'db>> + '_ {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => Either::Left(tuple.elements()),
|
||||||
|
TupleSpec::Variable(tuple) => Either::Right(tuple.all_elements()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn display_minimum_length(&self) -> String {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.len().to_string(),
|
||||||
|
TupleSpec::Variable(tuple) => format!("at least {}", tuple.minimum_length()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the minimum and maximum length of this tuple. (The maximum length will be `None`
|
||||||
|
/// for a tuple with a variable-length portion.)
|
||||||
|
pub(crate) fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => {
|
||||||
|
let len = tuple.len();
|
||||||
|
(len, Some(len))
|
||||||
|
}
|
||||||
|
TupleSpec::Variable(tuple) => (tuple.minimum_length(), None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.is_empty(),
|
||||||
|
TupleSpec::Variable(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Concatenates another tuple to the end of this tuple, returning a new tuple.
|
||||||
|
pub(crate) fn concat(&self, db: &'db dyn Db, other: &Self) -> Self {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.concat(other),
|
||||||
|
TupleSpec::Variable(tuple) => tuple.concat(db, other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn push(&mut self, element: Type<'db>) {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.push(element),
|
||||||
|
TupleSpec::Variable(tuple) => tuple.push(element),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => TupleSpec::Fixed(tuple.normalized(db)),
|
||||||
|
TupleSpec::Variable(tuple) => TupleSpec::Variable(tuple.normalized(db)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn materialize(&self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => TupleSpec::Fixed(tuple.materialize(db, variance)),
|
||||||
|
TupleSpec::Variable(tuple) => TupleSpec::Variable(tuple.materialize(db, variance)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => TupleSpec::Fixed(tuple.apply_type_mapping(db, type_mapping)),
|
||||||
|
TupleSpec::Variable(tuple) => {
|
||||||
|
TupleSpec::Variable(tuple.apply_type_mapping(db, type_mapping))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_legacy_typevars(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
typevars: &mut FxOrderSet<TypeVarInstance<'db>>,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.find_legacy_typevars(db, typevars),
|
||||||
|
TupleSpec::Variable(tuple) => tuple.find_legacy_typevars(db, typevars),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_relation_to(&self, db: &'db dyn Db, other: &Self, relation: TypeRelation) -> bool {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(self_tuple) => self_tuple.has_relation_to(db, other, relation),
|
||||||
|
TupleSpec::Variable(self_tuple) => self_tuple.has_relation_to(db, other, relation),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_equivalent_to(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(TupleSpec::Fixed(self_tuple), TupleSpec::Fixed(other_tuple)) => {
|
||||||
|
self_tuple.is_equivalent_to(db, other_tuple)
|
||||||
|
}
|
||||||
|
(TupleSpec::Variable(self_tuple), TupleSpec::Variable(other_tuple)) => {
|
||||||
|
self_tuple.is_equivalent_to(db, other_tuple)
|
||||||
|
}
|
||||||
|
(TupleSpec::Fixed(_), TupleSpec::Variable(_))
|
||||||
|
| (TupleSpec::Variable(_), TupleSpec::Fixed(_)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_gradual_equivalent_to(&self, db: &'db dyn Db, other: &TupleSpec<'db>) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(TupleSpec::Fixed(self_tuple), TupleSpec::Fixed(other_tuple)) => {
|
||||||
|
self_tuple.is_gradual_equivalent_to(db, other_tuple)
|
||||||
|
}
|
||||||
|
(TupleSpec::Variable(self_tuple), TupleSpec::Variable(other_tuple)) => {
|
||||||
|
self_tuple.is_gradual_equivalent_to(db, other_tuple)
|
||||||
|
}
|
||||||
|
(TupleSpec::Fixed(_), TupleSpec::Variable(_))
|
||||||
|
| (TupleSpec::Variable(_), TupleSpec::Fixed(_)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_disjoint_from(&self, db: &'db dyn Db, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(TupleSpec::Fixed(self_tuple), TupleSpec::Fixed(other_tuple)) => {
|
||||||
|
self_tuple.is_disjoint_from(db, other_tuple)
|
||||||
|
}
|
||||||
|
// Two pure homogeneous tuples `tuple[A, ...]` and `tuple[B, ...]` can never be
|
||||||
|
// disjoint even if A and B are disjoint, because `tuple[()]` would be assignable to
|
||||||
|
// both.
|
||||||
|
// TODO: Consider checking for disjointness between the tuples' prefixes and suffixes.
|
||||||
|
(TupleSpec::Variable(_), TupleSpec::Variable(_)) => false,
|
||||||
|
// TODO: Consider checking for disjointness between the fixed-length tuple and the
|
||||||
|
// variable-length tuple's prefix/suffix.
|
||||||
|
(TupleSpec::Fixed(_), TupleSpec::Variable(_))
|
||||||
|
| (TupleSpec::Variable(_), TupleSpec::Fixed(_)) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fully_static(&self, db: &'db dyn Db) -> bool {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.is_fully_static(db),
|
||||||
|
TupleSpec::Variable(tuple) => tuple.is_fully_static(db),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.is_single_valued(db),
|
||||||
|
TupleSpec::Variable(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> From<FixedLengthTupleSpec<'db>> for TupleSpec<'db> {
|
||||||
|
fn from(tuple: FixedLengthTupleSpec<'db>) -> Self {
|
||||||
|
TupleSpec::Fixed(tuple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> From<VariableLengthTupleSpec<'db>> for TupleSpec<'db> {
|
||||||
|
fn from(tuple: VariableLengthTupleSpec<'db>) -> Self {
|
||||||
|
TupleSpec::Variable(tuple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> PyIndex<'db> for &TupleSpec<'db> {
|
||||||
|
type Item = Type<'db>;
|
||||||
|
|
||||||
|
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError> {
|
||||||
|
match self {
|
||||||
|
TupleSpec::Fixed(tuple) => tuple.py_index(db, index),
|
||||||
|
TupleSpec::Variable(tuple) => tuple.py_index(db, index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,12 +9,13 @@ use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
||||||
use crate::semantic_index::place::ScopeId;
|
use crate::semantic_index::place::ScopeId;
|
||||||
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types};
|
use crate::types::tuple::{FixedLengthTupleSpec, TupleSpec, TupleType};
|
||||||
|
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types, todo_type};
|
||||||
use crate::unpack::{UnpackKind, UnpackValue};
|
use crate::unpack::{UnpackKind, UnpackValue};
|
||||||
|
|
||||||
use super::context::InferContext;
|
use super::context::InferContext;
|
||||||
use super::diagnostic::INVALID_ASSIGNMENT;
|
use super::diagnostic::INVALID_ASSIGNMENT;
|
||||||
use super::{KnownClass, TupleType, UnionType};
|
use super::{KnownClass, UnionType};
|
||||||
|
|
||||||
/// Unpacks the value expression type to their respective targets.
|
/// Unpacks the value expression type to their respective targets.
|
||||||
pub(crate) struct Unpacker<'db, 'ast> {
|
pub(crate) struct Unpacker<'db, 'ast> {
|
||||||
|
@ -152,53 +153,55 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
||||||
_ => ty,
|
_ => ty,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(tuple_ty) = ty.into_tuple() {
|
if let Type::Tuple(tuple_ty) = ty {
|
||||||
let tuple_ty_elements =
|
let tuple = self.tuple_ty_elements(target, elts, tuple_ty, value_expr);
|
||||||
self.tuple_ty_elements(target, elts, tuple_ty, value_expr);
|
|
||||||
|
|
||||||
let length_mismatch =
|
let length_mismatch = match elts.len().cmp(&tuple.len()) {
|
||||||
match elts.len().cmp(&tuple_ty_elements.len()) {
|
Ordering::Less => {
|
||||||
Ordering::Less => {
|
if let Some(builder) =
|
||||||
if let Some(builder) =
|
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
{
|
||||||
{
|
let mut diag =
|
||||||
let mut diag =
|
builder.into_diagnostic("Too many values to unpack");
|
||||||
builder.into_diagnostic("Too many values to unpack");
|
diag.set_primary_message(format_args!(
|
||||||
diag.set_primary_message(format_args!(
|
"Expected {}",
|
||||||
"Expected {}",
|
elts.len(),
|
||||||
elts.len(),
|
));
|
||||||
));
|
diag.annotate(
|
||||||
diag.annotate(self.context.secondary(value_expr).message(
|
self.context
|
||||||
format_args!("Got {}", tuple_ty_elements.len()),
|
.secondary(value_expr)
|
||||||
));
|
.message(format_args!("Got {}", tuple.len())),
|
||||||
}
|
);
|
||||||
true
|
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
true
|
||||||
if let Some(builder) =
|
}
|
||||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
Ordering::Greater => {
|
||||||
{
|
if let Some(builder) =
|
||||||
let mut diag =
|
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||||
builder.into_diagnostic("Not enough values to unpack");
|
{
|
||||||
diag.set_primary_message(format_args!(
|
let mut diag =
|
||||||
"Expected {}",
|
builder.into_diagnostic("Not enough values to unpack");
|
||||||
elts.len(),
|
diag.set_primary_message(format_args!(
|
||||||
));
|
"Expected {}",
|
||||||
diag.annotate(self.context.secondary(value_expr).message(
|
elts.len(),
|
||||||
format_args!("Got {}", tuple_ty_elements.len()),
|
));
|
||||||
));
|
diag.annotate(
|
||||||
}
|
self.context
|
||||||
true
|
.secondary(value_expr)
|
||||||
|
.message(format_args!("Got {}", tuple.len())),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ordering::Equal => false,
|
true
|
||||||
};
|
}
|
||||||
|
Ordering::Equal => false,
|
||||||
|
};
|
||||||
|
|
||||||
for (index, ty) in tuple_ty_elements.iter().enumerate() {
|
for (index, ty) in tuple.elements().enumerate() {
|
||||||
if let Some(element_types) = target_types.get_mut(index) {
|
if let Some(element_types) = target_types.get_mut(index) {
|
||||||
if length_mismatch {
|
if length_mismatch {
|
||||||
element_types.push(Type::unknown());
|
element_types.push(Type::unknown());
|
||||||
} else {
|
} else {
|
||||||
element_types.push(*ty);
|
element_types.push(ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,24 +251,36 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
||||||
targets: &[ast::Expr],
|
targets: &[ast::Expr],
|
||||||
tuple_ty: TupleType<'db>,
|
tuple_ty: TupleType<'db>,
|
||||||
value_expr: AnyNodeRef<'_>,
|
value_expr: AnyNodeRef<'_>,
|
||||||
) -> Cow<'_, [Type<'db>]> {
|
) -> Cow<'_, FixedLengthTupleSpec<'db>> {
|
||||||
|
let TupleSpec::Fixed(tuple) = tuple_ty.tuple(self.db()) else {
|
||||||
|
let todo = todo_type!("Unpack variable-length tuple");
|
||||||
|
return Cow::Owned(FixedLengthTupleSpec::from_elements(targets.iter().map(
|
||||||
|
|target| {
|
||||||
|
if target.is_starred_expr() {
|
||||||
|
KnownClass::List.to_specialized_instance(self.db(), [todo])
|
||||||
|
} else {
|
||||||
|
todo
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
// If there is a starred expression, it will consume all of the types at that location.
|
// If there is a starred expression, it will consume all of the types at that location.
|
||||||
let Some(starred_index) = targets.iter().position(ast::Expr::is_starred_expr) else {
|
let Some(starred_index) = targets.iter().position(ast::Expr::is_starred_expr) else {
|
||||||
// Otherwise, the types will be unpacked 1-1 to the targets.
|
// Otherwise, the types will be unpacked 1-1 to the targets.
|
||||||
return Cow::Borrowed(tuple_ty.elements(self.db()));
|
return Cow::Borrowed(tuple);
|
||||||
};
|
};
|
||||||
|
|
||||||
if tuple_ty.len(self.db()) >= targets.len() - 1 {
|
if tuple.len() >= targets.len() - 1 {
|
||||||
// This branch is only taken when there are enough elements in the tuple type to
|
// This branch is only taken when there are enough elements in the tuple type to
|
||||||
// combine for the starred expression. So, the arithmetic and indexing operations are
|
// combine for the starred expression. So, the arithmetic and indexing operations are
|
||||||
// safe to perform.
|
// safe to perform.
|
||||||
let mut element_types = Vec::with_capacity(targets.len());
|
let mut element_types = FixedLengthTupleSpec::with_capacity(targets.len());
|
||||||
|
let tuple_elements = tuple.elements_slice();
|
||||||
|
|
||||||
// Insert all the elements before the starred expression.
|
// Insert all the elements before the starred expression.
|
||||||
element_types.extend_from_slice(
|
// SAFETY: Safe because of the length check above.
|
||||||
// SAFETY: Safe because of the length check above.
|
element_types.extend_from_slice(&tuple_elements[..starred_index]);
|
||||||
&tuple_ty.elements(self.db())[..starred_index],
|
|
||||||
);
|
|
||||||
|
|
||||||
// The number of target expressions that are remaining after the starred expression.
|
// The number of target expressions that are remaining after the starred expression.
|
||||||
// For example, in `(a, *b, c, d) = ...`, the index of starred element `b` is 1 and the
|
// For example, in `(a, *b, c, d) = ...`, the index of starred element `b` is 1 and the
|
||||||
|
@ -276,11 +291,10 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
||||||
// expression, in an exclusive manner. For example, in `(a, *b, c) = (1, 2, 3, 4)`, the
|
// expression, in an exclusive manner. For example, in `(a, *b, c) = (1, 2, 3, 4)`, the
|
||||||
// starred expression `b` will consume the elements `Literal[2]` and `Literal[3]` and
|
// starred expression `b` will consume the elements `Literal[2]` and `Literal[3]` and
|
||||||
// the index value would be 3.
|
// the index value would be 3.
|
||||||
let starred_end_index = tuple_ty.len(self.db()) - remaining;
|
let starred_end_index = tuple.len() - remaining;
|
||||||
|
|
||||||
// SAFETY: Safe because of the length check above.
|
// SAFETY: Safe because of the length check above.
|
||||||
let starred_element_types =
|
let starred_element_types = &tuple_elements[starred_index..starred_end_index];
|
||||||
&tuple_ty.elements(self.db())[starred_index..starred_end_index];
|
|
||||||
|
|
||||||
element_types.push(KnownClass::List.to_specialized_instance(
|
element_types.push(KnownClass::List.to_specialized_instance(
|
||||||
self.db(),
|
self.db(),
|
||||||
|
@ -292,10 +306,8 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
||||||
));
|
));
|
||||||
|
|
||||||
// Insert the types remaining that aren't consumed by the starred expression.
|
// Insert the types remaining that aren't consumed by the starred expression.
|
||||||
element_types.extend_from_slice(
|
// SAFETY: Safe because of the length check above.
|
||||||
// SAFETY: Safe because of the length check above.
|
element_types.extend_from_slice(&tuple_elements[starred_end_index..]);
|
||||||
&tuple_ty.elements(self.db())[starred_end_index..],
|
|
||||||
);
|
|
||||||
|
|
||||||
Cow::Owned(element_types)
|
Cow::Owned(element_types)
|
||||||
} else {
|
} else {
|
||||||
|
@ -305,22 +317,19 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
|
||||||
diag.annotate(
|
diag.annotate(
|
||||||
self.context
|
self.context
|
||||||
.secondary(value_expr)
|
.secondary(value_expr)
|
||||||
.message(format_args!("Got {}", tuple_ty.len(self.db()))),
|
.message(format_args!("Got {}", tuple.len())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cow::Owned(
|
Cow::Owned(FixedLengthTupleSpec::from_elements(targets.iter().map(
|
||||||
targets
|
|target| {
|
||||||
.iter()
|
if target.is_starred_expr() {
|
||||||
.map(|target| {
|
KnownClass::List.to_specialized_instance(self.db(), [Type::unknown()])
|
||||||
if target.is_starred_expr() {
|
} else {
|
||||||
KnownClass::List.to_specialized_instance(self.db(), [Type::unknown()])
|
Type::unknown()
|
||||||
} else {
|
}
|
||||||
Type::unknown()
|
},
|
||||||
}
|
)))
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,15 @@
|
||||||
|
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
|
|
||||||
|
use crate::Db;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub(crate) struct OutOfBoundsError;
|
pub(crate) struct OutOfBoundsError;
|
||||||
|
|
||||||
pub(crate) trait PyIndex {
|
pub(crate) trait PyIndex<'db> {
|
||||||
type Item;
|
type Item: 'db;
|
||||||
|
|
||||||
fn py_index(&mut self, index: i32) -> Result<Self::Item, OutOfBoundsError>;
|
fn py_index(self, db: &'db dyn Db, index: i32) -> Result<Self::Item, OutOfBoundsError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_nonnegative_i32(index: i32) -> usize {
|
fn from_nonnegative_i32(index: i32) -> usize {
|
||||||
|
@ -39,13 +41,13 @@ enum Position {
|
||||||
AfterEnd,
|
AfterEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Nth {
|
pub(crate) enum Nth {
|
||||||
FromStart(usize),
|
FromStart(usize),
|
||||||
FromEnd(usize),
|
FromEnd(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Nth {
|
impl Nth {
|
||||||
fn from_index(index: i32) -> Self {
|
pub(crate) fn from_index(index: i32) -> Self {
|
||||||
if index >= 0 {
|
if index >= 0 {
|
||||||
Nth::FromStart(from_nonnegative_i32(index))
|
Nth::FromStart(from_nonnegative_i32(index))
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,13 +77,26 @@ impl Nth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, T> PyIndex for T
|
impl<'db, T> PyIndex<'db> for &'db [T] {
|
||||||
|
type Item = &'db T;
|
||||||
|
|
||||||
|
fn py_index(self, _db: &'db dyn Db, index: i32) -> Result<&'db T, OutOfBoundsError> {
|
||||||
|
match Nth::from_index(index) {
|
||||||
|
Nth::FromStart(nth) => self.get(nth).ok_or(OutOfBoundsError),
|
||||||
|
Nth::FromEnd(nth_rev) => (self.len().checked_sub(nth_rev + 1))
|
||||||
|
.map(|idx| &self[idx])
|
||||||
|
.ok_or(OutOfBoundsError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db, I: 'db, T> PyIndex<'db> for &mut T
|
||||||
where
|
where
|
||||||
T: DoubleEndedIterator<Item = I>,
|
T: DoubleEndedIterator<Item = I>,
|
||||||
{
|
{
|
||||||
type Item = I;
|
type Item = I;
|
||||||
|
|
||||||
fn py_index(&mut self, index: i32) -> Result<I, OutOfBoundsError> {
|
fn py_index(self, _db: &'db dyn Db, index: i32) -> Result<I, OutOfBoundsError> {
|
||||||
match Nth::from_index(index) {
|
match Nth::from_index(index) {
|
||||||
Nth::FromStart(nth) => self.nth(nth).ok_or(OutOfBoundsError),
|
Nth::FromStart(nth) => self.nth(nth).ok_or(OutOfBoundsError),
|
||||||
Nth::FromEnd(nth_rev) => self.nth_back(nth_rev).ok_or(OutOfBoundsError),
|
Nth::FromEnd(nth_rev) => self.nth_back(nth_rev).ok_or(OutOfBoundsError),
|
||||||
|
@ -92,32 +107,28 @@ where
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub(crate) struct StepSizeZeroError;
|
pub(crate) struct StepSizeZeroError;
|
||||||
|
|
||||||
pub(crate) trait PySlice {
|
pub(crate) trait PySlice<'db> {
|
||||||
type Item;
|
type Item: 'db;
|
||||||
|
|
||||||
fn py_slice(
|
fn py_slice(
|
||||||
&self,
|
&'db self,
|
||||||
|
db: &'db dyn Db,
|
||||||
start: Option<i32>,
|
start: Option<i32>,
|
||||||
stop: Option<i32>,
|
stop: Option<i32>,
|
||||||
step: Option<i32>,
|
step: Option<i32>,
|
||||||
) -> Result<
|
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError>;
|
||||||
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
|
||||||
StepSizeZeroError,
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> PySlice for [T] {
|
impl<'db, T: 'db> PySlice<'db> for [T] {
|
||||||
type Item = T;
|
type Item = T;
|
||||||
|
|
||||||
fn py_slice(
|
fn py_slice(
|
||||||
&self,
|
&'db self,
|
||||||
|
_db: &'db dyn Db,
|
||||||
start: Option<i32>,
|
start: Option<i32>,
|
||||||
stop: Option<i32>,
|
stop: Option<i32>,
|
||||||
step_int: Option<i32>,
|
step_int: Option<i32>,
|
||||||
) -> Result<
|
) -> Result<impl Iterator<Item = &'db Self::Item>, StepSizeZeroError> {
|
||||||
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
|
||||||
StepSizeZeroError,
|
|
||||||
> {
|
|
||||||
let step_int = step_int.unwrap_or(1);
|
let step_int = step_int.unwrap_or(1);
|
||||||
if step_int == 0 {
|
if step_int == 0 {
|
||||||
return Err(StepSizeZeroError);
|
return Err(StepSizeZeroError);
|
||||||
|
@ -194,6 +205,8 @@ impl<T> PySlice for [T] {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[expect(clippy::redundant_clone)]
|
#[expect(clippy::redundant_clone)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::Db;
|
||||||
|
use crate::db::tests::setup_db;
|
||||||
use crate::util::subscript::{OutOfBoundsError, StepSizeZeroError};
|
use crate::util::subscript::{OutOfBoundsError, StepSizeZeroError};
|
||||||
|
|
||||||
use super::{PyIndex, PySlice};
|
use super::{PyIndex, PySlice};
|
||||||
|
@ -201,302 +214,387 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_index_empty() {
|
fn py_index_empty() {
|
||||||
|
let db = setup_db();
|
||||||
let iter = std::iter::empty::<char>();
|
let iter = std::iter::empty::<char>();
|
||||||
|
|
||||||
assert_eq!(iter.clone().py_index(0), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, 0), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, 1), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().py_index(-1), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, -1), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().py_index(i32::MIN), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, i32::MIN), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().py_index(i32::MAX), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, i32::MAX), Err(OutOfBoundsError));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_index_single_element() {
|
fn py_index_single_element() {
|
||||||
|
let db = setup_db();
|
||||||
let iter = ['a'].into_iter();
|
let iter = ['a'].into_iter();
|
||||||
|
|
||||||
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
assert_eq!(iter.clone().py_index(&db, 0), Ok('a'));
|
||||||
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, 1), Err(OutOfBoundsError));
|
||||||
assert_eq!(iter.clone().py_index(-1), Ok('a'));
|
assert_eq!(iter.clone().py_index(&db, -1), Ok('a'));
|
||||||
assert_eq!(iter.clone().py_index(-2), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, -2), Err(OutOfBoundsError));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_index_more_elements() {
|
fn py_index_more_elements() {
|
||||||
|
let db = setup_db();
|
||||||
let iter = ['a', 'b', 'c', 'd', 'e'].into_iter();
|
let iter = ['a', 'b', 'c', 'd', 'e'].into_iter();
|
||||||
|
|
||||||
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
assert_eq!(iter.clone().py_index(&db, 0), Ok('a'));
|
||||||
assert_eq!(iter.clone().py_index(1), Ok('b'));
|
assert_eq!(iter.clone().py_index(&db, 1), Ok('b'));
|
||||||
assert_eq!(iter.clone().py_index(4), Ok('e'));
|
assert_eq!(iter.clone().py_index(&db, 4), Ok('e'));
|
||||||
assert_eq!(iter.clone().py_index(5), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, 5), Err(OutOfBoundsError));
|
||||||
|
|
||||||
assert_eq!(iter.clone().py_index(-1), Ok('e'));
|
assert_eq!(iter.clone().py_index(&db, -1), Ok('e'));
|
||||||
assert_eq!(iter.clone().py_index(-2), Ok('d'));
|
assert_eq!(iter.clone().py_index(&db, -2), Ok('d'));
|
||||||
assert_eq!(iter.clone().py_index(-5), Ok('a'));
|
assert_eq!(iter.clone().py_index(&db, -5), Ok('a'));
|
||||||
assert_eq!(iter.clone().py_index(-6), Err(OutOfBoundsError));
|
assert_eq!(iter.clone().py_index(&db, -6), Err(OutOfBoundsError));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_index_uses_full_index_range() {
|
fn py_index_uses_full_index_range() {
|
||||||
|
let db = setup_db();
|
||||||
let iter = 0..=u32::MAX;
|
let iter = 0..=u32::MAX;
|
||||||
|
|
||||||
// u32::MAX - |i32::MIN| + 1 = 2^32 - 1 - 2^31 + 1 = 2^31
|
// u32::MAX - |i32::MIN| + 1 = 2^32 - 1 - 2^31 + 1 = 2^31
|
||||||
assert_eq!(iter.clone().py_index(i32::MIN), Ok(2u32.pow(31)));
|
assert_eq!(iter.clone().py_index(&db, i32::MIN), Ok(2u32.pow(31)));
|
||||||
assert_eq!(iter.clone().py_index(-2), Ok(u32::MAX - 2 + 1));
|
assert_eq!(iter.clone().py_index(&db, -2), Ok(u32::MAX - 2 + 1));
|
||||||
assert_eq!(iter.clone().py_index(-1), Ok(u32::MAX - 1 + 1));
|
assert_eq!(iter.clone().py_index(&db, -1), Ok(u32::MAX - 1 + 1));
|
||||||
|
|
||||||
assert_eq!(iter.clone().py_index(0), Ok(0));
|
assert_eq!(iter.clone().py_index(&db, 0), Ok(0));
|
||||||
assert_eq!(iter.clone().py_index(1), Ok(1));
|
assert_eq!(iter.clone().py_index(&db, 1), Ok(1));
|
||||||
assert_eq!(iter.clone().py_index(i32::MAX), Ok(i32::MAX as u32));
|
assert_eq!(iter.clone().py_index(&db, i32::MAX), Ok(i32::MAX as u32));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_eq_slice<const N: usize, const M: usize>(
|
fn assert_eq_slice<const N: usize, const M: usize>(
|
||||||
|
db: &dyn Db,
|
||||||
input: &[char; N],
|
input: &[char; N],
|
||||||
start: Option<i32>,
|
start: Option<i32>,
|
||||||
stop: Option<i32>,
|
stop: Option<i32>,
|
||||||
step: Option<i32>,
|
step: Option<i32>,
|
||||||
expected: &[char; M],
|
expected: &[char; M],
|
||||||
) {
|
) {
|
||||||
assert_equal(input.py_slice(start, stop, step).unwrap(), expected.iter());
|
assert_equal(
|
||||||
|
input.py_slice(db, start, stop, step).unwrap(),
|
||||||
|
expected.iter(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_slice_empty_input() {
|
fn py_slice_empty_input() {
|
||||||
|
let db = setup_db();
|
||||||
let input = [];
|
let input = [];
|
||||||
|
|
||||||
assert_eq_slice(&input, None, None, None, &[]);
|
assert_eq_slice(&db, &input, None, None, None, &[]);
|
||||||
assert_eq_slice(&input, Some(0), None, None, &[]);
|
assert_eq_slice(&db, &input, Some(0), None, None, &[]);
|
||||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
assert_eq_slice(&db, &input, None, Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(0), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
assert_eq_slice(&db, &input, Some(-5), Some(-5), None, &[]);
|
||||||
assert_eq_slice(&input, None, None, Some(-1), &[]);
|
assert_eq_slice(&db, &input, None, None, Some(-1), &[]);
|
||||||
assert_eq_slice(&input, None, None, Some(2), &[]);
|
assert_eq_slice(&db, &input, None, None, Some(2), &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_slice_single_element_input() {
|
fn py_slice_single_element_input() {
|
||||||
|
let db = setup_db();
|
||||||
let input = ['a'];
|
let input = ['a'];
|
||||||
|
|
||||||
assert_eq_slice(&input, None, None, None, &['a']);
|
assert_eq_slice(&db, &input, None, None, None, &['a']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), None, None, &['a']);
|
assert_eq_slice(&db, &input, Some(0), None, None, &['a']);
|
||||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
assert_eq_slice(&db, &input, None, Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(0), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
assert_eq_slice(&db, &input, Some(0), Some(1), None, &['a']);
|
||||||
assert_eq_slice(&input, Some(0), Some(2), None, &['a']);
|
assert_eq_slice(&db, &input, Some(0), Some(2), None, &['a']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-1), None, None, &['a']);
|
assert_eq_slice(&db, &input, Some(-1), None, None, &['a']);
|
||||||
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
assert_eq_slice(&db, &input, Some(-1), Some(-1), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-1), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(-1), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-1), Some(1), None, &['a']);
|
assert_eq_slice(&db, &input, Some(-1), Some(1), None, &['a']);
|
||||||
assert_eq_slice(&input, Some(-1), Some(2), None, &['a']);
|
assert_eq_slice(&db, &input, Some(-1), Some(2), None, &['a']);
|
||||||
assert_eq_slice(&input, None, Some(-1), None, &[]);
|
assert_eq_slice(&db, &input, None, Some(-1), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-2), None, None, &['a']);
|
assert_eq_slice(&db, &input, Some(-2), None, None, &['a']);
|
||||||
assert_eq_slice(&input, Some(-2), Some(-1), None, &[]);
|
assert_eq_slice(&db, &input, Some(-2), Some(-1), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-2), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(-2), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-2), Some(1), None, &['a']);
|
assert_eq_slice(&db, &input, Some(-2), Some(1), None, &['a']);
|
||||||
assert_eq_slice(&input, Some(-2), Some(2), None, &['a']);
|
assert_eq_slice(&db, &input, Some(-2), Some(2), None, &['a']);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_slice_nonnegative_indices() {
|
fn py_slice_nonnegative_indices() {
|
||||||
|
let db = setup_db();
|
||||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
assert_eq_slice(&db, &input, None, Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, None, Some(1), None, &['a']);
|
assert_eq_slice(&db, &input, None, Some(1), None, &['a']);
|
||||||
assert_eq_slice(&input, None, Some(4), None, &['a', 'b', 'c', 'd']);
|
assert_eq_slice(&db, &input, None, Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, None, Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, None, Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
assert_eq_slice(&input, None, Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, None, Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
assert_eq_slice(&input, None, None, None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, None, None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(0), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
assert_eq_slice(&db, &input, Some(0), Some(1), None, &['a']);
|
||||||
assert_eq_slice(&input, Some(0), Some(4), None, &['a', 'b', 'c', 'd']);
|
assert_eq_slice(&db, &input, Some(0), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, Some(0), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(
|
||||||
assert_eq_slice(&input, Some(0), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
&db,
|
||||||
assert_eq_slice(&input, Some(0), None, None, &['a', 'b', 'c', 'd', 'e']);
|
&input,
|
||||||
|
Some(0),
|
||||||
|
Some(5),
|
||||||
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(
|
||||||
|
&db,
|
||||||
|
&input,
|
||||||
|
Some(0),
|
||||||
|
Some(6),
|
||||||
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(1), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(1), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(1), Some(1), None, &[]);
|
assert_eq_slice(&db, &input, Some(1), Some(1), None, &[]);
|
||||||
assert_eq_slice(&input, Some(1), Some(2), None, &['b']);
|
assert_eq_slice(&db, &input, Some(1), Some(2), None, &['b']);
|
||||||
assert_eq_slice(&input, Some(1), Some(4), None, &['b', 'c', 'd']);
|
assert_eq_slice(&db, &input, Some(1), Some(4), None, &['b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, Some(1), Some(5), None, &['b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, Some(1), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||||
assert_eq_slice(&input, Some(1), Some(6), None, &['b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, Some(1), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||||
assert_eq_slice(&input, Some(1), None, None, &['b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, Some(1), None, None, &['b', 'c', 'd', 'e']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(4), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(4), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(4), Some(4), None, &[]);
|
assert_eq_slice(&db, &input, Some(4), Some(4), None, &[]);
|
||||||
assert_eq_slice(&input, Some(4), Some(5), None, &['e']);
|
assert_eq_slice(&db, &input, Some(4), Some(5), None, &['e']);
|
||||||
assert_eq_slice(&input, Some(4), Some(6), None, &['e']);
|
assert_eq_slice(&db, &input, Some(4), Some(6), None, &['e']);
|
||||||
assert_eq_slice(&input, Some(4), None, None, &['e']);
|
assert_eq_slice(&db, &input, Some(4), None, None, &['e']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(5), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(5), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(5), Some(5), None, &[]);
|
assert_eq_slice(&db, &input, Some(5), Some(5), None, &[]);
|
||||||
assert_eq_slice(&input, Some(5), Some(6), None, &[]);
|
assert_eq_slice(&db, &input, Some(5), Some(6), None, &[]);
|
||||||
assert_eq_slice(&input, Some(5), None, None, &[]);
|
assert_eq_slice(&db, &input, Some(5), None, None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(6), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(6), Some(0), None, &[]);
|
||||||
assert_eq_slice(&input, Some(6), Some(6), None, &[]);
|
assert_eq_slice(&db, &input, Some(6), Some(6), None, &[]);
|
||||||
assert_eq_slice(&input, Some(6), None, None, &[]);
|
assert_eq_slice(&db, &input, Some(6), None, None, &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_slice_negative_indices() {
|
fn py_slice_negative_indices() {
|
||||||
|
let db = setup_db();
|
||||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-6), None, None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(
|
||||||
assert_eq_slice(&input, Some(-6), Some(-1), None, &['a', 'b', 'c', 'd']);
|
&db,
|
||||||
assert_eq_slice(&input, Some(-6), Some(-4), None, &['a']);
|
&input,
|
||||||
assert_eq_slice(&input, Some(-6), Some(-5), None, &[]);
|
Some(-6),
|
||||||
assert_eq_slice(&input, Some(-6), Some(-6), None, &[]);
|
None,
|
||||||
assert_eq_slice(&input, Some(-6), Some(-10), None, &[]);
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(-4), None, &['a']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(-6), None, &[]);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(-10), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-5), None, None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(
|
||||||
assert_eq_slice(&input, Some(-5), Some(-1), None, &['a', 'b', 'c', 'd']);
|
&db,
|
||||||
assert_eq_slice(&input, Some(-5), Some(-4), None, &['a']);
|
&input,
|
||||||
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
Some(-5),
|
||||||
assert_eq_slice(&input, Some(-5), Some(-6), None, &[]);
|
None,
|
||||||
assert_eq_slice(&input, Some(-5), Some(-10), None, &[]);
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(-4), None, &['a']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(-5), None, &[]);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(-6), None, &[]);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(-10), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-4), None, None, &['b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, Some(-4), None, None, &['b', 'c', 'd', 'e']);
|
||||||
assert_eq_slice(&input, Some(-4), Some(-1), None, &['b', 'c', 'd']);
|
assert_eq_slice(&db, &input, Some(-4), Some(-1), None, &['b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, Some(-4), Some(-3), None, &['b']);
|
assert_eq_slice(&db, &input, Some(-4), Some(-3), None, &['b']);
|
||||||
assert_eq_slice(&input, Some(-4), Some(-4), None, &[]);
|
assert_eq_slice(&db, &input, Some(-4), Some(-4), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-4), Some(-10), None, &[]);
|
assert_eq_slice(&db, &input, Some(-4), Some(-10), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-1), None, None, &['e']);
|
assert_eq_slice(&db, &input, Some(-1), None, None, &['e']);
|
||||||
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
assert_eq_slice(&db, &input, Some(-1), Some(-1), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-1), Some(-10), None, &[]);
|
assert_eq_slice(&db, &input, Some(-1), Some(-10), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, None, Some(-1), None, &['a', 'b', 'c', 'd']);
|
assert_eq_slice(&db, &input, None, Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, None, Some(-4), None, &['a']);
|
assert_eq_slice(&db, &input, None, Some(-4), None, &['a']);
|
||||||
assert_eq_slice(&input, None, Some(-5), None, &[]);
|
assert_eq_slice(&db, &input, None, Some(-5), None, &[]);
|
||||||
assert_eq_slice(&input, None, Some(-6), None, &[]);
|
assert_eq_slice(&db, &input, None, Some(-6), None, &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_slice_mixed_positive_negative_indices() {
|
fn py_slice_mixed_positive_negative_indices() {
|
||||||
|
let db = setup_db();
|
||||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), Some(-1), None, &['a', 'b', 'c', 'd']);
|
assert_eq_slice(&db, &input, Some(0), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, Some(1), Some(-1), None, &['b', 'c', 'd']);
|
assert_eq_slice(&db, &input, Some(1), Some(-1), None, &['b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, Some(3), Some(-1), None, &['d']);
|
assert_eq_slice(&db, &input, Some(3), Some(-1), None, &['d']);
|
||||||
assert_eq_slice(&input, Some(4), Some(-1), None, &[]);
|
assert_eq_slice(&db, &input, Some(4), Some(-1), None, &[]);
|
||||||
assert_eq_slice(&input, Some(5), Some(-1), None, &[]);
|
assert_eq_slice(&db, &input, Some(5), Some(-1), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), Some(-4), None, &['a']);
|
assert_eq_slice(&db, &input, Some(0), Some(-4), None, &['a']);
|
||||||
assert_eq_slice(&input, Some(1), Some(-4), None, &[]);
|
assert_eq_slice(&db, &input, Some(1), Some(-4), None, &[]);
|
||||||
assert_eq_slice(&input, Some(3), Some(-4), None, &[]);
|
assert_eq_slice(&db, &input, Some(3), Some(-4), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), Some(-5), None, &[]);
|
assert_eq_slice(&db, &input, Some(0), Some(-5), None, &[]);
|
||||||
assert_eq_slice(&input, Some(1), Some(-5), None, &[]);
|
assert_eq_slice(&db, &input, Some(1), Some(-5), None, &[]);
|
||||||
assert_eq_slice(&input, Some(3), Some(-5), None, &[]);
|
assert_eq_slice(&db, &input, Some(3), Some(-5), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), Some(-6), None, &[]);
|
assert_eq_slice(&db, &input, Some(0), Some(-6), None, &[]);
|
||||||
assert_eq_slice(&input, Some(1), Some(-6), None, &[]);
|
assert_eq_slice(&db, &input, Some(1), Some(-6), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-6), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(
|
||||||
assert_eq_slice(&input, Some(-6), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
&db,
|
||||||
assert_eq_slice(&input, Some(-6), Some(4), None, &['a', 'b', 'c', 'd']);
|
&input,
|
||||||
assert_eq_slice(&input, Some(-6), Some(1), None, &['a']);
|
Some(-6),
|
||||||
assert_eq_slice(&input, Some(-6), Some(0), None, &[]);
|
Some(6),
|
||||||
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(
|
||||||
|
&db,
|
||||||
|
&input,
|
||||||
|
Some(-6),
|
||||||
|
Some(5),
|
||||||
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-6), Some(0), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-5), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
assert_eq_slice(
|
||||||
assert_eq_slice(&input, Some(-5), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
&db,
|
||||||
assert_eq_slice(&input, Some(-5), Some(4), None, &['a', 'b', 'c', 'd']);
|
&input,
|
||||||
assert_eq_slice(&input, Some(-5), Some(1), None, &['a']);
|
Some(-5),
|
||||||
assert_eq_slice(&input, Some(-5), Some(0), None, &[]);
|
Some(6),
|
||||||
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(
|
||||||
|
&db,
|
||||||
|
&input,
|
||||||
|
Some(-5),
|
||||||
|
Some(5),
|
||||||
|
None,
|
||||||
|
&['a', 'b', 'c', 'd', 'e'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(1), None, &['a']);
|
||||||
|
assert_eq_slice(&db, &input, Some(-5), Some(0), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-4), Some(6), None, &['b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, Some(-4), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||||
assert_eq_slice(&input, Some(-4), Some(5), None, &['b', 'c', 'd', 'e']);
|
assert_eq_slice(&db, &input, Some(-4), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||||
assert_eq_slice(&input, Some(-4), Some(4), None, &['b', 'c', 'd']);
|
assert_eq_slice(&db, &input, Some(-4), Some(4), None, &['b', 'c', 'd']);
|
||||||
assert_eq_slice(&input, Some(-4), Some(2), None, &['b']);
|
assert_eq_slice(&db, &input, Some(-4), Some(2), None, &['b']);
|
||||||
assert_eq_slice(&input, Some(-4), Some(1), None, &[]);
|
assert_eq_slice(&db, &input, Some(-4), Some(1), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-4), Some(0), None, &[]);
|
assert_eq_slice(&db, &input, Some(-4), Some(0), None, &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-1), Some(6), None, &['e']);
|
assert_eq_slice(&db, &input, Some(-1), Some(6), None, &['e']);
|
||||||
assert_eq_slice(&input, Some(-1), Some(5), None, &['e']);
|
assert_eq_slice(&db, &input, Some(-1), Some(5), None, &['e']);
|
||||||
assert_eq_slice(&input, Some(-1), Some(4), None, &[]);
|
assert_eq_slice(&db, &input, Some(-1), Some(4), None, &[]);
|
||||||
assert_eq_slice(&input, Some(-1), Some(1), None, &[]);
|
assert_eq_slice(&db, &input, Some(-1), Some(1), None, &[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_slice_step_forward() {
|
fn py_slice_step_forward() {
|
||||||
|
let db = setup_db();
|
||||||
// indices: 0 1 2 3 4 5 6
|
// indices: 0 1 2 3 4 5 6
|
||||||
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||||
|
|
||||||
// Step size zero is invalid:
|
// Step size zero is invalid:
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
input.py_slice(None, None, Some(0)),
|
input.py_slice(&db, None, None, Some(0)),
|
||||||
Err(StepSizeZeroError)
|
Err(StepSizeZeroError)
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
input.py_slice(Some(0), Some(5), Some(0)),
|
input.py_slice(&db, Some(0), Some(5), Some(0)),
|
||||||
Err(StepSizeZeroError)
|
Err(StepSizeZeroError)
|
||||||
));
|
));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
input.py_slice(Some(0), Some(0), Some(0)),
|
input.py_slice(&db, Some(0), Some(0), Some(0)),
|
||||||
Err(StepSizeZeroError)
|
Err(StepSizeZeroError)
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), Some(8), Some(2), &['a', 'c', 'e', 'g']);
|
assert_eq_slice(
|
||||||
assert_eq_slice(&input, Some(0), Some(7), Some(2), &['a', 'c', 'e', 'g']);
|
&db,
|
||||||
assert_eq_slice(&input, Some(0), Some(6), Some(2), &['a', 'c', 'e']);
|
&input,
|
||||||
assert_eq_slice(&input, Some(0), Some(5), Some(2), &['a', 'c', 'e']);
|
Some(0),
|
||||||
assert_eq_slice(&input, Some(0), Some(4), Some(2), &['a', 'c']);
|
Some(8),
|
||||||
assert_eq_slice(&input, Some(0), Some(3), Some(2), &['a', 'c']);
|
Some(2),
|
||||||
assert_eq_slice(&input, Some(0), Some(2), Some(2), &['a']);
|
&['a', 'c', 'e', 'g'],
|
||||||
assert_eq_slice(&input, Some(0), Some(1), Some(2), &['a']);
|
);
|
||||||
assert_eq_slice(&input, Some(0), Some(0), Some(2), &[]);
|
assert_eq_slice(
|
||||||
assert_eq_slice(&input, Some(1), Some(5), Some(2), &['b', 'd']);
|
&db,
|
||||||
|
&input,
|
||||||
|
Some(0),
|
||||||
|
Some(7),
|
||||||
|
Some(2),
|
||||||
|
&['a', 'c', 'e', 'g'],
|
||||||
|
);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), Some(6), Some(2), &['a', 'c', 'e']);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), Some(5), Some(2), &['a', 'c', 'e']);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), Some(4), Some(2), &['a', 'c']);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), Some(3), Some(2), &['a', 'c']);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), Some(2), Some(2), &['a']);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), Some(1), Some(2), &['a']);
|
||||||
|
assert_eq_slice(&db, &input, Some(0), Some(0), Some(2), &[]);
|
||||||
|
assert_eq_slice(&db, &input, Some(1), Some(5), Some(2), &['b', 'd']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), Some(7), Some(3), &['a', 'd', 'g']);
|
assert_eq_slice(&db, &input, Some(0), Some(7), Some(3), &['a', 'd', 'g']);
|
||||||
assert_eq_slice(&input, Some(0), Some(6), Some(3), &['a', 'd']);
|
assert_eq_slice(&db, &input, Some(0), Some(6), Some(3), &['a', 'd']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(0), None, Some(10), &['a']);
|
assert_eq_slice(&db, &input, Some(0), None, Some(10), &['a']);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_slice_step_backward() {
|
fn py_slice_step_backward() {
|
||||||
|
let db = setup_db();
|
||||||
// indices: 0 1 2 3 4 5 6
|
// indices: 0 1 2 3 4 5 6
|
||||||
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(7), Some(0), Some(-2), &['g', 'e', 'c']);
|
assert_eq_slice(&db, &input, Some(7), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||||
assert_eq_slice(&input, Some(6), Some(0), Some(-2), &['g', 'e', 'c']);
|
assert_eq_slice(&db, &input, Some(6), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||||
assert_eq_slice(&input, Some(5), Some(0), Some(-2), &['f', 'd', 'b']);
|
assert_eq_slice(&db, &input, Some(5), Some(0), Some(-2), &['f', 'd', 'b']);
|
||||||
assert_eq_slice(&input, Some(4), Some(0), Some(-2), &['e', 'c']);
|
assert_eq_slice(&db, &input, Some(4), Some(0), Some(-2), &['e', 'c']);
|
||||||
assert_eq_slice(&input, Some(3), Some(0), Some(-2), &['d', 'b']);
|
assert_eq_slice(&db, &input, Some(3), Some(0), Some(-2), &['d', 'b']);
|
||||||
assert_eq_slice(&input, Some(2), Some(0), Some(-2), &['c']);
|
assert_eq_slice(&db, &input, Some(2), Some(0), Some(-2), &['c']);
|
||||||
assert_eq_slice(&input, Some(1), Some(0), Some(-2), &['b']);
|
assert_eq_slice(&db, &input, Some(1), Some(0), Some(-2), &['b']);
|
||||||
assert_eq_slice(&input, Some(0), Some(0), Some(-2), &[]);
|
assert_eq_slice(&db, &input, Some(0), Some(0), Some(-2), &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(7), None, Some(-2), &['g', 'e', 'c', 'a']);
|
assert_eq_slice(&db, &input, Some(7), None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||||
assert_eq_slice(&input, None, None, Some(-2), &['g', 'e', 'c', 'a']);
|
assert_eq_slice(&db, &input, None, None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||||
assert_eq_slice(&input, None, Some(0), Some(-2), &['g', 'e', 'c']);
|
assert_eq_slice(&db, &input, None, Some(0), Some(-2), &['g', 'e', 'c']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(5), Some(1), Some(-2), &['f', 'd']);
|
assert_eq_slice(&db, &input, Some(5), Some(1), Some(-2), &['f', 'd']);
|
||||||
assert_eq_slice(&input, Some(5), Some(2), Some(-2), &['f', 'd']);
|
assert_eq_slice(&db, &input, Some(5), Some(2), Some(-2), &['f', 'd']);
|
||||||
assert_eq_slice(&input, Some(5), Some(3), Some(-2), &['f']);
|
assert_eq_slice(&db, &input, Some(5), Some(3), Some(-2), &['f']);
|
||||||
assert_eq_slice(&input, Some(5), Some(4), Some(-2), &['f']);
|
assert_eq_slice(&db, &input, Some(5), Some(4), Some(-2), &['f']);
|
||||||
assert_eq_slice(&input, Some(5), Some(5), Some(-2), &[]);
|
assert_eq_slice(&db, &input, Some(5), Some(5), Some(-2), &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(6), None, Some(-3), &['g', 'd', 'a']);
|
assert_eq_slice(&db, &input, Some(6), None, Some(-3), &['g', 'd', 'a']);
|
||||||
assert_eq_slice(&input, Some(6), Some(0), Some(-3), &['g', 'd']);
|
assert_eq_slice(&db, &input, Some(6), Some(0), Some(-3), &['g', 'd']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(7), None, Some(-10), &['g']);
|
assert_eq_slice(&db, &input, Some(7), None, Some(-10), &['g']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-6), Some(-9), Some(-1), &['b', 'a']);
|
assert_eq_slice(&db, &input, Some(-6), Some(-9), Some(-1), &['b', 'a']);
|
||||||
assert_eq_slice(&input, Some(-6), Some(-8), Some(-1), &['b', 'a']);
|
assert_eq_slice(&db, &input, Some(-6), Some(-8), Some(-1), &['b', 'a']);
|
||||||
assert_eq_slice(&input, Some(-6), Some(-7), Some(-1), &['b']);
|
assert_eq_slice(&db, &input, Some(-6), Some(-7), Some(-1), &['b']);
|
||||||
assert_eq_slice(&input, Some(-6), Some(-6), Some(-1), &[]);
|
assert_eq_slice(&db, &input, Some(-6), Some(-6), Some(-1), &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-7), Some(-9), Some(-1), &['a']);
|
assert_eq_slice(&db, &input, Some(-7), Some(-9), Some(-1), &['a']);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-8), Some(-9), Some(-1), &[]);
|
assert_eq_slice(&db, &input, Some(-8), Some(-9), Some(-1), &[]);
|
||||||
assert_eq_slice(&input, Some(-9), Some(-9), Some(-1), &[]);
|
assert_eq_slice(&db, &input, Some(-9), Some(-9), Some(-1), &[]);
|
||||||
|
|
||||||
assert_eq_slice(&input, Some(-6), Some(-2), Some(-1), &[]);
|
assert_eq_slice(&db, &input, Some(-6), Some(-2), Some(-1), &[]);
|
||||||
assert_eq_slice(&input, Some(-9), Some(-6), Some(-1), &[]);
|
assert_eq_slice(&db, &input, Some(-9), Some(-6), Some(-1), &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue