From bc6e8b58cec835e2c543b3cf8a9921817b208f51 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 4 Aug 2025 13:48:19 +0100 Subject: [PATCH] [ty] Return `Option` from `infer_tuple_type_expression` (#19735) ## Summary This PR reduces the virality of some of the `Todo` types in `infer_tuple_type_expression`. Rather than inferring `Todo`, we instead infer `tuple[Todo, ...]`. This reflects the fact that whatever the contents of the slice in a `tuple[]` type expression, we would always infer some kind of tuple type as the result of the type expression. Any tuple type should be assignable to `tuple[Todo, ...]`, so this shouldn't introduce any new false positives; this can be seen in the ecosystem report. As a result of the change, we are now able to enforce in the signature of `Type::infer_tuple_type_expression` that it returns an `Option>`, which is more strongly typed and expresses clearly the invariant that a tuple type expression should always be inferred as a `tuple` type. To enable this, it was necessary to refactor several `TupleType` constructors in `tuple.rs` so that they return `Option` rather than `Type`; this means that callers of these constructor functions are now free to either propagate the `Option>` or convert it to a `Type<'db>`. ## Test Plan Mdtests updated. --- crates/ty/docs/rules.md | 118 +++++++++--------- .../resources/mdtest/annotations/starred.md | 2 +- .../annotations/unsupported_special_forms.md | 1 + .../mdtest/assignment/annotations.md | 2 +- crates/ty_python_semantic/src/types.rs | 16 +-- .../src/types/call/arguments.rs | 36 +++--- .../ty_python_semantic/src/types/call/bind.rs | 14 +-- crates/ty_python_semantic/src/types/class.rs | 13 +- .../src/types/diagnostic.rs | 3 +- .../ty_python_semantic/src/types/generics.rs | 2 +- crates/ty_python_semantic/src/types/infer.rs | 43 ++++--- .../ty_python_semantic/src/types/instance.rs | 2 +- .../types/property_tests/type_generation.rs | 4 +- crates/ty_python_semantic/src/types/tuple.rs | 42 ++++--- 14 files changed, 156 insertions(+), 142 deletions(-) diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index c4c39c0260..563ed1aee3 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -36,7 +36,7 @@ def test(): -> "int": Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L100) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99) **What it does** @@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L144) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143) **What it does** @@ -88,7 +88,7 @@ f(int) # error Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L170) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169) **What it does** @@ -117,7 +117,7 @@ a = 1 Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L195) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194) **What it does** @@ -147,7 +147,7 @@ class C(A, B): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L221) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220) **What it does** @@ -177,7 +177,7 @@ class B(A): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L286) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285) **What it does** @@ -202,7 +202,7 @@ class B(A, A): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L307) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L306) **What it does** @@ -306,7 +306,7 @@ def test(): -> "Literal[5]": Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L449) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448) **What it does** @@ -334,7 +334,7 @@ class C(A, B): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L473) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472) **What it does** @@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L339) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L338) **What it does** @@ -445,7 +445,7 @@ an atypical memory layout. Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L493) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L492) **What it does** @@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type] Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L533) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L532) **What it does** @@ -496,7 +496,7 @@ a: int = '' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1537) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1536) **What it does** @@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L555) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L554) **What it does** @@ -550,7 +550,7 @@ class A(42): ... # error: [invalid-base] Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L606) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L605) **What it does** @@ -575,7 +575,7 @@ with 1: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L627) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626) **What it does** @@ -602,7 +602,7 @@ a: str Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L650) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L649) **What it does** @@ -644,7 +644,7 @@ except ZeroDivisionError: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L686) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L685) **What it does** @@ -675,7 +675,7 @@ class C[U](Generic[T]): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L712) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L711) **What it does** @@ -708,7 +708,7 @@ def f(t: TypeVar("U")): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L761) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L760) **What it does** @@ -740,7 +740,7 @@ class B(metaclass=f): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L788) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L787) **What it does** @@ -788,7 +788,7 @@ def foo(x: int) -> int: ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L831) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L830) **What it does** @@ -812,7 +812,7 @@ def f(a: int = ''): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L421) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L420) **What it does** @@ -844,7 +844,7 @@ TypeError: Protocols can only inherit from other protocols, got Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L851) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L850) Checks for `raise` statements that raise non-exceptions or use invalid @@ -891,7 +891,7 @@ def g(): Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L514) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L513) **What it does** @@ -914,7 +914,7 @@ def func() -> int: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L894) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L893) **What it does** @@ -968,7 +968,7 @@ TODO #14889 Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L740) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739) **What it does** @@ -993,7 +993,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L933) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L932) **What it does** @@ -1021,7 +1021,7 @@ TYPE_CHECKING = '' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L957) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L956) **What it does** @@ -1049,7 +1049,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1009) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1008) **What it does** @@ -1081,7 +1081,7 @@ f(10) # Error Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L981) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L980) **What it does** @@ -1113,7 +1113,7 @@ class C: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1037) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036) **What it does** @@ -1146,7 +1146,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1066) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1065) **What it does** @@ -1169,7 +1169,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1085) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1084) **What it does** @@ -1196,7 +1196,7 @@ func("string") # error: [no-matching-overload] Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1108) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107) **What it does** @@ -1218,7 +1218,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1126) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1125) **What it does** @@ -1242,7 +1242,7 @@ for i in 34: # TypeError: 'int' object is not iterable Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1177) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1176) **What it does** @@ -1296,7 +1296,7 @@ def test(): -> "int": Default level: [`error`](../rules.md#rule-levels "This lint has a default level of '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#L1513) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1512) **What it does** @@ -1324,7 +1324,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1268) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1267) **What it does** @@ -1351,7 +1351,7 @@ class B(A): ... # Error raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1313) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) **What it does** @@ -1376,7 +1376,7 @@ f("foo") # Error raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1291) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290) **What it does** @@ -1402,7 +1402,7 @@ def _(x: int): Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1334) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1333) **What it does** @@ -1446,7 +1446,7 @@ class A: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1391) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1390) **What it does** @@ -1471,7 +1471,7 @@ f(x=1, y=2) # Error raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1412) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411) **What it does** @@ -1497,7 +1497,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1434) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433) **What it does** @@ -1520,7 +1520,7 @@ import foo # ModuleNotFoundError: No module named 'foo' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1453) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1452) **What it does** @@ -1543,7 +1543,7 @@ print(x) # NameError: name 'x' is not defined Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1146) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1145) **What it does** @@ -1578,7 +1578,7 @@ b1 < b2 < b1 # exception raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1472) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1471) **What it does** @@ -1604,7 +1604,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [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#L1494) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1493) **What it does** @@ -1627,7 +1627,7 @@ l[1:10:0] # ValueError: slice step cannot be zero Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L265) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264) **What it does** @@ -1680,7 +1680,7 @@ a = 20 / 0 # type: ignore Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [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#L1198) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1197) **What it does** @@ -1706,7 +1706,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [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#L118) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117) **What it does** @@ -1736,7 +1736,7 @@ A()[0] # TypeError: 'A' object is not subscriptable Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [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#L1220) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1219) **What it does** @@ -1766,7 +1766,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [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#L1565) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1564) **What it does** @@ -1791,7 +1791,7 @@ cast(int, f()) # Redundant Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [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#L1373) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372) **What it does** @@ -1842,7 +1842,7 @@ a = 20 / 0 # ty: ignore[division-by-zero] Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1586) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1585) **What it does** @@ -1896,7 +1896,7 @@ def g(): Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [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#L573) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L572) **What it does** @@ -1933,7 +1933,7 @@ class D(C): ... # error: [unsupported-base] Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · [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#L247) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246) **What it does** @@ -1955,7 +1955,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · [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#L1246) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1245) **What it does** diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md index 3bb3de68bf..e7eb565cca 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/starred.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/starred.md @@ -18,5 +18,5 @@ def append_int(*args: *Ts) -> tuple[*Ts, int]: return (*args, 1) # TODO should be tuple[Literal[True], Literal["a"], int] -reveal_type(append_int(True, "a")) # revealed: @Todo(PEP 646) +reveal_type(append_int(True, "a")) # revealed: tuple[@Todo(PEP 646), ...] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index b4e5327d7a..60d9826628 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -17,6 +17,7 @@ Alias: TypeAlias = int def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...] reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`) + return args def g() -> TypeGuard[int]: ... def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co: diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md index 12183c5b28..f28e68c0d9 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md @@ -59,7 +59,7 @@ reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]] reveal_type(e) # revealed: tuple[str, ...] reveal_type(f) # revealed: tuple[str, *tuple[int, ...], bytes] -reveal_type(g) # revealed: @Todo(PEP 646) +reveal_type(g) # revealed: tuple[@Todo(PEP 646), ...] reveal_type(h) # revealed: tuple[list[int], list[int]] reveal_type(i) # revealed: tuple[str | int, str | int] diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index c3f747bcee..e7f19f03fd 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -3801,7 +3801,7 @@ impl<'db> Type<'db> { db, [ KnownClass::Str.to_instance(db), - TupleType::homogeneous(db, KnownClass::Str.to_instance(db)), + Type::homogeneous_tuple(db, KnownClass::Str.to_instance(db)), ], )), Parameter::positional_only(Some(Name::new_static("start"))) @@ -4114,7 +4114,7 @@ impl<'db> Type<'db> { Parameter::positional_only(Some(Name::new_static("name"))) .with_annotated_type(str_instance), Parameter::positional_only(Some(Name::new_static("bases"))) - .with_annotated_type(TupleType::homogeneous( + .with_annotated_type(Type::homogeneous_tuple( db, type_instance, )), @@ -4304,7 +4304,7 @@ impl<'db> Type<'db> { .with_annotated_type(Type::any()) .type_form(), Parameter::keyword_only(Name::new_static("type_params")) - .with_annotated_type(TupleType::homogeneous( + .with_annotated_type(Type::homogeneous_tuple( db, UnionType::from_elements( db, @@ -4315,7 +4315,7 @@ impl<'db> Type<'db> { ], ), )) - .with_default_type(TupleType::empty(db)), + .with_default_type(Type::empty_tuple(db)), ]), None, ), @@ -4401,7 +4401,7 @@ impl<'db> Type<'db> { CallableBinding::from_overloads( self, [ - Signature::new(Parameters::empty(), Some(TupleType::empty(db))), + Signature::new(Parameters::empty(), Some(Type::empty_tuple(db))), Signature::new( Parameters::new([Parameter::positional_only(Some( Name::new_static("iterable"), @@ -4409,7 +4409,7 @@ impl<'db> Type<'db> { .with_annotated_type( KnownClass::Iterable.to_specialized_instance(db, [object]), )]), - Some(TupleType::homogeneous(db, object)), + Some(Type::homogeneous_tuple(db, object)), ), ], ) @@ -5267,7 +5267,7 @@ impl<'db> Type<'db> { // We treat `typing.Type` exactly the same as `builtins.type`: SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)), - SpecialFormType::Tuple => Ok(TupleType::homogeneous(db, Type::unknown())), + SpecialFormType::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())), // Legacy `typing` aliases SpecialFormType::List => Ok(KnownClass::List.to_instance(db)), @@ -5458,7 +5458,7 @@ impl<'db> Type<'db> { Type::Union(UnionType::new(db, elements)) }; - TupleType::from_elements( + Type::heterogeneous_tuple( db, [ Type::IntLiteral(python_version.major.into()), diff --git a/crates/ty_python_semantic/src/types/call/arguments.rs b/crates/ty_python_semantic/src/types/call/arguments.rs index 5259872902..2fea3fd1b0 100644 --- a/crates/ty_python_semantic/src/types/call/arguments.rs +++ b/crates/ty_python_semantic/src/types/call/arguments.rs @@ -6,7 +6,7 @@ use ruff_python_ast as ast; use crate::Db; use crate::types::KnownClass; use crate::types::enums::enum_member_literals; -use crate::types::tuple::{TupleLength, TupleSpec, TupleType}; +use crate::types::tuple::{TupleLength, TupleSpec}; use super::Type; @@ -246,7 +246,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option>> { } }) .multi_cartesian_product() - .map(|types| TupleType::from_elements(db, types)) + .map(|types| Type::heterogeneous_tuple(db, types)) .collect::>(); if expanded.len() == 1 { // There are no elements in the tuple type that can be expanded. @@ -306,17 +306,17 @@ mod tests { let false_ty = Type::BooleanLiteral(false); // Empty tuple - let empty_tuple = TupleType::empty(&db); + let empty_tuple = Type::empty_tuple(&db); let expanded = expand_type(&db, empty_tuple); assert!(expanded.is_none()); // None of the elements can be expanded. - let tuple_type1 = TupleType::from_elements(&db, [int_ty, str_ty]); + let tuple_type1 = Type::heterogeneous_tuple(&db, [int_ty, str_ty]); let expanded = expand_type(&db, tuple_type1); assert!(expanded.is_none()); // All elements can be expanded. - let tuple_type2 = TupleType::from_elements( + let tuple_type2 = Type::heterogeneous_tuple( &db, [ bool_ty, @@ -324,18 +324,18 @@ mod tests { ], ); let expected_types = [ - TupleType::from_elements(&db, [true_ty, int_ty]), - TupleType::from_elements(&db, [true_ty, str_ty]), - TupleType::from_elements(&db, [true_ty, bytes_ty]), - TupleType::from_elements(&db, [false_ty, int_ty]), - TupleType::from_elements(&db, [false_ty, str_ty]), - TupleType::from_elements(&db, [false_ty, bytes_ty]), + Type::heterogeneous_tuple(&db, [true_ty, int_ty]), + Type::heterogeneous_tuple(&db, [true_ty, str_ty]), + Type::heterogeneous_tuple(&db, [true_ty, bytes_ty]), + Type::heterogeneous_tuple(&db, [false_ty, int_ty]), + Type::heterogeneous_tuple(&db, [false_ty, str_ty]), + Type::heterogeneous_tuple(&db, [false_ty, bytes_ty]), ]; let expanded = expand_type(&db, tuple_type2).unwrap(); assert_eq!(expanded, expected_types); // Mixed set of elements where some can be expanded while others cannot be. - let tuple_type3 = TupleType::from_elements( + let tuple_type3 = Type::heterogeneous_tuple( &db, [ bool_ty, @@ -345,21 +345,21 @@ mod tests { ], ); let expected_types = [ - TupleType::from_elements(&db, [true_ty, int_ty, str_ty, str_ty]), - TupleType::from_elements(&db, [true_ty, int_ty, bytes_ty, str_ty]), - TupleType::from_elements(&db, [false_ty, int_ty, str_ty, str_ty]), - TupleType::from_elements(&db, [false_ty, int_ty, bytes_ty, str_ty]), + Type::heterogeneous_tuple(&db, [true_ty, int_ty, str_ty, str_ty]), + Type::heterogeneous_tuple(&db, [true_ty, int_ty, bytes_ty, str_ty]), + Type::heterogeneous_tuple(&db, [false_ty, int_ty, str_ty, str_ty]), + Type::heterogeneous_tuple(&db, [false_ty, int_ty, bytes_ty, str_ty]), ]; let expanded = expand_type(&db, tuple_type3).unwrap(); assert_eq!(expanded, expected_types); // Variable-length tuples are not expanded. - let variable_length_tuple = TupleType::mixed( + let variable_length_tuple = Type::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()); } diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 32dc8ad258..003d06d7ae 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -390,9 +390,9 @@ impl<'db> Bindings<'db> { ); } Some("__constraints__") => { - overload.set_return_type(TupleType::from_elements( + overload.set_return_type(Type::heterogeneous_tuple( db, - typevar.constraints(db).into_iter().flatten().copied(), + typevar.constraints(db).into_iter().flatten(), )); } Some("__default__") => { @@ -674,7 +674,7 @@ impl<'db> Bindings<'db> { Some(names) => { let mut names = names.iter().collect::>(); names.sort(); - TupleType::from_elements( + Type::heterogeneous_tuple( db, names.iter().map(|name| { Type::string_literal(db, name.as_str()) @@ -694,7 +694,7 @@ impl<'db> Bindings<'db> { let return_ty = match ty { Type::ClassLiteral(class) => { if let Some(metadata) = enums::enum_metadata(db, *class) { - TupleType::from_elements( + Type::heterogeneous_tuple( db, metadata .members @@ -714,7 +714,7 @@ impl<'db> Bindings<'db> { Some(KnownFunction::AllMembers) => { if let [Some(ty)] = overload.parameter_types() { - overload.set_return_type(TupleType::from_elements( + overload.set_return_type(Type::heterogeneous_tuple( db, ide_support::all_members(db, *ty) .into_iter() @@ -1458,7 +1458,7 @@ impl<'db> CallableBinding<'db> { } let top_materialized_argument_type = - TupleType::from_elements(db, top_materialized_argument_types); + Type::heterogeneous_tuple(db, top_materialized_argument_types); // A flag to indicate whether we've found the overload that makes the remaining overloads // unmatched for the given argument types. @@ -1494,7 +1494,7 @@ impl<'db> CallableBinding<'db> { parameter_types.push(UnionType::from_elements(db, current_parameter_types)); } if top_materialized_argument_type - .is_assignable_to(db, TupleType::from_elements(db, parameter_types)) + .is_assignable_to(db, Type::heterogeneous_tuple(db, parameter_types)) { filter_remaining_overloads = true; } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index d1d081109f..2e62307087 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -20,7 +20,7 @@ use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::generics::{GenericContext, Specialization, walk_specialization}; use crate::types::infer::nearest_enclosing_class; use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature}; -use crate::types::tuple::{TupleSpec, TupleType}; +use crate::types::tuple::TupleSpec; use crate::types::{ BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams, DeprecatedInstance, DynamicType, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, @@ -778,7 +778,7 @@ impl<'db> ClassType<'db> { overload_signatures.push(synthesize_getitem_overload_signature( KnownClass::Slice.to_instance(db), - TupleType::homogeneous(db, all_elements_unioned), + Type::homogeneous_tuple(db, all_elements_unioned), )); let getitem_signature = @@ -841,7 +841,8 @@ impl<'db> ClassType<'db> { // - an unspecialized tuple // - a tuple with no minimum length if specialization.is_none_or(|spec| spec.tuple(db).len().minimum() == 0) { - iterable_parameter = iterable_parameter.with_default_type(TupleType::empty(db)); + iterable_parameter = + iterable_parameter.with_default_type(Type::empty_tuple(db)); } let parameters = Parameters::new([ @@ -1533,7 +1534,7 @@ impl<'db> ClassLiteral<'db> { } } else { let name = Type::string_literal(db, self.name(db)); - let bases = TupleType::from_elements(db, self.explicit_bases(db).iter().copied()); + let bases = Type::heterogeneous_tuple(db, self.explicit_bases(db)); let namespace = KnownClass::Dict .to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::any()]); @@ -1624,8 +1625,8 @@ impl<'db> ClassLiteral<'db> { policy: MemberLookupPolicy, ) -> PlaceAndQualifiers<'db> { if name == "__mro__" { - let tuple_elements = self.iter_mro(db, specialization).map(Type::from); - return Place::bound(TupleType::from_elements(db, tuple_elements)).into(); + let tuple_elements = self.iter_mro(db, specialization); + return Place::bound(Type::heterogeneous_tuple(db, tuple_elements)).into(); } self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization)) diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 8226a293e3..32af3ba73b 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -15,7 +15,6 @@ use crate::types::string_annotation::{ IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION, RAW_STRING_TYPE_ANNOTATION, }; -use crate::types::tuple::TupleType; use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral}; use crate::util::diagnostics::format_enumeration; use crate::{Db, FxIndexMap, Module, ModuleName, Program, declare_lint}; @@ -2430,7 +2429,7 @@ pub(crate) fn report_invalid_or_unsupported_base( return; } - let tuple_of_types = TupleType::homogeneous(db, instance_of_type); + let tuple_of_types = Type::homogeneous_tuple(db, instance_of_type); let explain_mro_entries = |diagnostic: &mut LintDiagnosticGuard| { diagnostic.info( diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 56f5b2e99b..4e78c4a1cd 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -242,7 +242,7 @@ impl<'db> GenericContext<'db> { /// Returns a tuple type of the typevars introduced by this generic context. pub(crate) fn as_tuple(self, db: &'db dyn Db) -> Type<'db> { - TupleType::from_elements( + Type::heterogeneous_tuple( db, self.variables(db) .iter() diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 2db0ed7d32..bd88c61f21 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -2868,7 +2868,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { todo_type!("PEP 646") } else { let annotated_type = self.file_expression_type(annotation); - TupleType::homogeneous(self.db(), annotated_type) + Type::homogeneous_tuple(self.db(), annotated_type) }; self.add_declaration_with_binding( @@ -2880,7 +2880,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.add_binding( parameter.into(), definition, - TupleType::homogeneous(self.db(), Type::unknown()), + Type::homogeneous_tuple(self.db(), Type::unknown()), ); } } @@ -3293,7 +3293,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) } else if node_ty.is_assignable_to( self.db(), - TupleType::homogeneous(self.db(), type_base_exception), + Type::homogeneous_tuple(self.db(), type_base_exception), ) { extract_tuple_specialization(self.db(), node_ty) .unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db())) @@ -3303,7 +3303,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.db(), [ type_base_exception, - TupleType::homogeneous(self.db(), type_base_exception), + Type::homogeneous_tuple(self.db(), type_base_exception), ], ), ) { @@ -5611,7 +5611,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // consuming the whole iterator). let element_types: Vec<_> = elts.iter().map(|elt| self.infer_expression(elt)).collect(); - TupleType::from_elements(self.db(), element_types) + Type::heterogeneous_tuple(self.db(), element_types) } fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> { @@ -8486,8 +8486,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // special cases, too. if let Type::ClassLiteral(class) = value_ty { if class.is_tuple(self.db()) { - return self - .infer_tuple_type_expression(slice) + return Type::tuple(self.infer_tuple_type_expression(slice)) .to_meta_type(self.db()); } if let Some(generic_context) = class.generic_context(self.db()) { @@ -8500,9 +8499,7 @@ 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()); + return Type::tuple(self.infer_tuple_type_expression(slice)).to_meta_type(self.db()); } let slice_ty = self.infer_expression(slice); @@ -8525,7 +8522,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ); self.store_expression_type( slice_node, - TupleType::from_elements(self.db(), arguments.iter_types()), + Type::heterogeneous_tuple(self.db(), arguments.iter_types()), ); arguments } @@ -8617,7 +8614,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; if let Ok(new_elements) = tuple.py_slice(db, start, stop, step) { - TupleType::from_elements(db, new_elements) + Type::heterogeneous_tuple(db, new_elements) } else { report_slice_step_size_zero(context, value_node.into()); Type::unknown() @@ -9644,7 +9641,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let hinted_type = if list.len() == 1 { KnownClass::List.to_specialized_instance(db, inner_types) } else { - TupleType::from_elements(db, inner_types) + Type::heterogeneous_tuple(db, inner_types) }; diagnostic.set_primary_message(format_args!( @@ -9676,7 +9673,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Type::Dynamic(DynamicType::Todo(_) | DynamicType::Unknown) ) }) { - let hinted_type = TupleType::from_elements(self.db(), inner_types); + let hinted_type = Type::heterogeneous_tuple(self.db(), inner_types); diagnostic.set_primary_message(format_args!( "Did you mean `{}`?", hinted_type.display(self.db()), @@ -9895,7 +9892,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ) -> Type<'db> { match value_ty { Type::ClassLiteral(class_literal) => match class_literal.known(self.db()) { - Some(KnownClass::Tuple) => self.infer_tuple_type_expression(slice), + Some(KnownClass::Tuple) => Type::tuple(self.infer_tuple_type_expression(slice)), Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice), _ => self.infer_subscript_type_expression(subscript, value_ty), }, @@ -9920,7 +9917,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } /// Given the slice of a `tuple[]` annotation, return the type that the annotation represents - fn infer_tuple_type_expression(&mut self, tuple_slice: &ast::Expr) -> Type<'db> { + fn infer_tuple_type_expression(&mut self, tuple_slice: &ast::Expr) -> Option> { /// In most cases, if a subelement of the tuple is inferred as `Todo`, /// we should only infer `Todo` for that specific subelement. /// Certain specific AST nodes can however change the meaning of the entire tuple, @@ -9961,7 +9958,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_expression(ellipsis); let result = TupleType::homogeneous(self.db(), self.infer_type_expression(element)); - self.store_expression_type(tuple_slice, result); + self.store_expression_type(tuple_slice, Type::tuple(result)); return result; } @@ -9988,15 +9985,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } let ty = if return_todo { - todo_type!("PEP 646") + TupleType::homogeneous(self.db(), todo_type!("PEP 646")) } else { - Type::tuple(TupleType::new(self.db(), element_types)) + TupleType::new(self.db(), element_types) }; // Here, we store the type for the inner `int, str` tuple-expression, // while the type for the outer `tuple[int, str]` slice-expression is // stored in the surrounding `infer_type_expression` call: - self.store_expression_type(tuple_slice, ty); + self.store_expression_type(tuple_slice, Type::tuple(ty)); ty } @@ -10004,7 +10001,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let single_element_ty = self.infer_type_expression(single_element); if element_could_alter_type_of_whole_tuple(single_element, single_element_ty, self) { - todo_type!("PEP 646") + TupleType::homogeneous(self.db(), todo_type!("PEP 646")) } else { TupleType::from_elements(self.db(), std::iter::once(single_element_ty)) } @@ -10668,7 +10665,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Type::unknown() } SpecialFormType::Type => self.infer_subclass_of_type_expression(arguments_slice), - SpecialFormType::Tuple => self.infer_tuple_type_expression(arguments_slice), + SpecialFormType::Tuple => { + Type::tuple(self.infer_tuple_type_expression(arguments_slice)) + } SpecialFormType::Generic | SpecialFormType::Protocol => { self.infer_expression(arguments_slice); if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 3959b041e1..7765278413 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -19,7 +19,7 @@ impl<'db> Type<'db> { match (class, class.known(db)) { (_, Some(KnownClass::Any)) => Self::Dynamic(DynamicType::Any), (ClassType::NonGeneric(_), Some(KnownClass::Tuple)) => { - TupleType::homogeneous(db, Type::unknown()) + Type::tuple(TupleType::homogeneous(db, Type::unknown())) } (ClassType::Generic(alias), Some(KnownClass::Tuple)) => { Self::tuple(TupleType::new(db, alias.specialization(db).tuple(db))) diff --git a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs index 7968872248..55452144a6 100644 --- a/crates/ty_python_semantic/src/types/property_tests/type_generation.rs +++ b/crates/ty_python_semantic/src/types/property_tests/type_generation.rs @@ -195,13 +195,13 @@ impl Ty { } Ty::FixedLengthTuple(tys) => { let elements = tys.into_iter().map(|ty| ty.into_type(db)); - TupleType::from_elements(db, elements) + Type::heterogeneous_tuple(db, elements) } Ty::VariableLengthTuple(prefix, variable, suffix) => { let prefix = prefix.into_iter().map(|ty| ty.into_type(db)); let variable = variable.into_type(db); let suffix = suffix.into_iter().map(|ty| ty.into_type(db)); - TupleType::mixed(db, prefix, variable, suffix) + Type::tuple(TupleType::mixed(db, prefix, variable, suffix)) } Ty::SubclassOfAny => SubclassOfType::subclass_of_any(), Ty::SubclassOfBuiltinClass(s) => SubclassOfType::from( diff --git a/crates/ty_python_semantic/src/types/tuple.rs b/crates/ty_python_semantic/src/types/tuple.rs index 967db3d602..63c43aa0b9 100644 --- a/crates/ty_python_semantic/src/types/tuple.rs +++ b/crates/ty_python_semantic/src/types/tuple.rs @@ -151,6 +151,25 @@ impl<'db> Type<'db> { }; Self::Tuple(tuple) } + + pub(crate) fn homogeneous_tuple(db: &'db dyn Db, element: Type<'db>) -> Self { + Type::tuple(TupleType::homogeneous(db, element)) + } + + pub(crate) fn heterogeneous_tuple(db: &'db dyn Db, elements: I) -> Self + where + I: IntoIterator, + T: Into>, + { + Type::tuple(TupleType::from_elements( + db, + elements.into_iter().map(Into::into), + )) + } + + pub(crate) fn empty_tuple(db: &'db dyn Db) -> Self { + Type::Tuple(TupleType::empty(db)) + } } impl<'db> TupleType<'db> { @@ -180,18 +199,16 @@ impl<'db> TupleType<'db> { Some(TupleType::new_internal(db, tuple_key)) } - pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> { - Type::tuple(TupleType::new( - db, - TupleSpec::from(FixedLengthTuple::empty()), - )) + pub(crate) fn empty(db: &'db dyn Db) -> Self { + TupleType::new(db, TupleSpec::from(FixedLengthTuple::empty())) + .expect("TupleType::new() should always return `Some` for an empty `TupleSpec`") } pub(crate) fn from_elements( db: &'db dyn Db, types: impl IntoIterator>, - ) -> Type<'db> { - Type::tuple(TupleType::new(db, TupleSpec::from_elements(types))) + ) -> Option { + TupleType::new(db, TupleSpec::from_elements(types)) } #[cfg(test)] @@ -200,15 +217,12 @@ impl<'db> TupleType<'db> { prefix: impl IntoIterator>, variable: Type<'db>, suffix: impl IntoIterator>, - ) -> Type<'db> { - Type::tuple(TupleType::new( - db, - VariableLengthTuple::mixed(prefix, variable, suffix), - )) + ) -> Option { + TupleType::new(db, VariableLengthTuple::mixed(prefix, variable, suffix)) } - pub(crate) fn homogeneous(db: &'db dyn Db, element: Type<'db>) -> Type<'db> { - Type::tuple(TupleType::new(db, TupleSpec::homogeneous(element))) + pub(crate) fn homogeneous(db: &'db dyn Db, element: Type<'db>) -> Option { + TupleType::new(db, TupleSpec::homogeneous(element)) } pub(crate) fn to_class_type(self, db: &'db dyn Db) -> Option> {