diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 9aadce2eca..c122710031 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#L102) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L103) **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#L146) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L147) **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#L172) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L173) **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#L197) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L198) **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#L223) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L224) **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#L288) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L289) **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#L309) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L310) **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#L451) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L478) **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#L475) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L502) **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#L341) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L342) **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#L520) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L547) **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#L560) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L587) **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#L1594) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1621) **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-await) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L582) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L609) **What it does** @@ -562,7 +562,7 @@ asyncio.run(main()) 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#L612) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L639) **What it does** @@ -584,7 +584,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#L663) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L690) **What it does** @@ -609,7 +609,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#L684) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L711) **What it does** @@ -636,7 +636,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#L707) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L734) **What it does** @@ -678,7 +678,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#L743) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L770) **What it does** @@ -709,7 +709,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-key) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L495) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L522) **What it does** @@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height' 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#L769) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L796) **What it does** @@ -771,7 +771,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#L818) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845) **What it does** @@ -798,12 +798,42 @@ class B(metaclass=f): ... - [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) +## `invalid-named-tuple` + + +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-named-tuple) · +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L452) + + +**What it does** + +Checks for invalidly defined `NamedTuple` classes. + +**Why is this bad?** + +An invalidly defined `NamedTuple` class may lead to the type checker +drawing incorrect conclusions. It may also lead to `TypeError`s at runtime. + +**Examples** + +A class definition cannot combine `NamedTuple` with other base classes +in multiple inheritance; doing so raises a `TypeError` at runtime. The sole +exception to this rule is `Generic[]`, which can be used alongside `NamedTuple` +in a class's bases list. + +```pycon +>>> from typing import NamedTuple +>>> class Foo(NamedTuple, object): ... +TypeError: can only inherit from a NamedTuple type and Generic +``` + ## `invalid-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%20invalid-overload) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L872) **What it does** @@ -851,7 +881,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#L888) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L915) **What it does** @@ -875,7 +905,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#L423) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L424) **What it does** @@ -907,7 +937,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#L908) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L935) Checks for `raise` statements that raise non-exceptions or use invalid @@ -954,7 +984,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#L541) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L568) **What it does** @@ -977,7 +1007,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#L951) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L978) **What it does** @@ -1031,7 +1061,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#L797) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L824) **What it does** @@ -1056,7 +1086,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#L990) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1017) **What it does** @@ -1084,7 +1114,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#L1014) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1041) **What it does** @@ -1112,7 +1142,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#L1066) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1093) **What it does** @@ -1144,7 +1174,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#L1038) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1065) **What it does** @@ -1176,7 +1206,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#L1094) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1121) **What it does** @@ -1209,7 +1239,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#L1123) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150) **What it does** @@ -1232,7 +1262,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#L1142) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1169) **What it does** @@ -1259,7 +1289,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#L1165) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1192) **What it does** @@ -1281,7 +1311,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#L1183) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1210) **What it does** @@ -1305,7 +1335,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#L1234) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1261) **What it does** @@ -1359,7 +1389,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#L1570) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1597) **What it does** @@ -1387,7 +1417,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#L1325) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1352) **What it does** @@ -1414,7 +1444,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#L1370) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397) **What it does** @@ -1439,7 +1469,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#L1348) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1375) **What it does** @@ -1465,7 +1495,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#L1391) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1418) **What it does** @@ -1509,7 +1539,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#L1448) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1475) **What it does** @@ -1534,7 +1564,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#L1469) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1496) **What it does** @@ -1560,7 +1590,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#L1491) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1518) **What it does** @@ -1583,7 +1613,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#L1510) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1537) **What it does** @@ -1606,7 +1636,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#L1203) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1230) **What it does** @@ -1641,7 +1671,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#L1529) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1556) **What it does** @@ -1667,7 +1697,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#L1551) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1578) **What it does** @@ -1690,7 +1720,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#L267) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L268) **What it does** @@ -1743,7 +1773,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#L1255) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1282) **What it does** @@ -1769,7 +1799,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#L120) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L121) **What it does** @@ -1799,7 +1829,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#L1277) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1304) **What it does** @@ -1829,7 +1859,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#L1622) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1649) **What it does** @@ -1854,7 +1884,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#L1430) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1457) **What it does** @@ -1905,7 +1935,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#L1643) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1670) **What it does** @@ -1959,7 +1989,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#L630) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L657) **What it does** @@ -1996,7 +2026,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#L249) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L250) **What it does** @@ -2018,7 +2048,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#L1303) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1330) **What it does** diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index b89d0a457e..49d957240e 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -115,15 +115,28 @@ class Location(NamedTuple): ### Multiple Inheritance -Multiple inheritance is not supported for `NamedTuple` classes: + + +Multiple inheritance is not supported for `NamedTuple` classes except with `Generic`: ```py -from typing import NamedTuple +from typing import NamedTuple, Protocol -# This should ideally emit a diagnostic +# error: [invalid-named-tuple] "NamedTuple class `C` cannot use multiple inheritance except with `Generic[]`" class C(NamedTuple, object): id: int - name: str + +# fmt: off + +class D( + int, # error: [invalid-named-tuple] + NamedTuple +): ... + +# fmt: on + +# error: [invalid-named-tuple] +class E(NamedTuple, Protocol): ... ``` ### Inheriting from a `NamedTuple` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/named_tuple.md_-_`NamedTuple`_-_`typing.NamedTuple`_-_Multiple_Inheritance_(82ed33d1b3b433d8).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/named_tuple.md_-_`NamedTuple`_-_`typing.NamedTuple`_-_Multiple_Inheritance_(82ed33d1b3b433d8).snap new file mode 100644 index 0000000000..b2cc9d918e --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/named_tuple.md_-_`NamedTuple`_-_`typing.NamedTuple`_-_Multiple_Inheritance_(82ed33d1b3b433d8).snap @@ -0,0 +1,73 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: named_tuple.md - `NamedTuple` - `typing.NamedTuple` - Multiple Inheritance +mdtest path: crates/ty_python_semantic/resources/mdtest/named_tuple.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | from typing import NamedTuple, Protocol + 2 | + 3 | # error: [invalid-named-tuple] "NamedTuple class `C` cannot use multiple inheritance except with `Generic[]`" + 4 | class C(NamedTuple, object): + 5 | id: int + 6 | + 7 | # fmt: off + 8 | + 9 | class D( +10 | int, # error: [invalid-named-tuple] +11 | NamedTuple +12 | ): ... +13 | +14 | # fmt: on +15 | +16 | # error: [invalid-named-tuple] +17 | class E(NamedTuple, Protocol): ... +``` + +# Diagnostics + +``` +error[invalid-named-tuple]: NamedTuple class `C` cannot use multiple inheritance except with `Generic[]` + --> src/mdtest_snippet.py:4:21 + | +3 | # error: [invalid-named-tuple] "NamedTuple class `C` cannot use multiple inheritance except with `Generic[]`" +4 | class C(NamedTuple, object): + | ^^^^^^ +5 | id: int + | +info: rule `invalid-named-tuple` is enabled by default + +``` + +``` +error[invalid-named-tuple]: NamedTuple class `D` cannot use multiple inheritance except with `Generic[]` + --> src/mdtest_snippet.py:10:5 + | + 9 | class D( +10 | int, # error: [invalid-named-tuple] + | ^^^ +11 | NamedTuple +12 | ): ... + | +info: rule `invalid-named-tuple` is enabled by default + +``` + +``` +error[invalid-named-tuple]: NamedTuple class `E` cannot use multiple inheritance except with `Generic[]` + --> src/mdtest_snippet.py:17:21 + | +16 | # error: [invalid-named-tuple] +17 | class E(NamedTuple, Protocol): ... + | ^^^^^^^^ + | +info: rule `invalid-named-tuple` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index beac62e361..2dcb979fcd 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -227,7 +227,7 @@ impl CodeGeneratorKind { code_generator_of_class(db, class) } - fn matches(self, db: &dyn Db, class: ClassLiteral<'_>) -> bool { + pub(super) fn matches(self, db: &dyn Db, class: ClassLiteral<'_>) -> bool { CodeGeneratorKind::from_class(db, class) == Some(self) } } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 113d0fa508..730ab1627d 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -57,6 +57,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&INVALID_OVERLOAD); registry.register_lint(&INVALID_PARAMETER_DEFAULT); registry.register_lint(&INVALID_PROTOCOL); + registry.register_lint(&INVALID_NAMED_TUPLE); registry.register_lint(&INVALID_RAISE); registry.register_lint(&INVALID_SUPER_ARGUMENT); registry.register_lint(&INVALID_TYPE_CHECKING_CONSTANT); @@ -448,6 +449,32 @@ declare_lint! { } } +declare_lint! { + /// ## What it does + /// Checks for invalidly defined `NamedTuple` classes. + /// + /// ## Why is this bad? + /// An invalidly defined `NamedTuple` class may lead to the type checker + /// drawing incorrect conclusions. It may also lead to `TypeError`s at runtime. + /// + /// ## Examples + /// A class definition cannot combine `NamedTuple` with other base classes + /// in multiple inheritance; doing so raises a `TypeError` at runtime. The sole + /// exception to this rule is `Generic[]`, which can be used alongside `NamedTuple` + /// in a class's bases list. + /// + /// ```pycon + /// >>> from typing import NamedTuple + /// >>> class Foo(NamedTuple, object): ... + /// TypeError: can only inherit from a NamedTuple type and Generic + /// ``` + pub(crate) static INVALID_NAMED_TUPLE = { + summary: "detects invalid `NamedTuple` class definitions", + status: LintStatus::preview("1.0.0"), + default_level: Level::Error, + } +} + declare_lint! { /// ## What it does /// Checks for classes with an inconsistent [method resolution order] (MRO). diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 83c7212d25..a1a3cb2e7b 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -95,16 +95,17 @@ use crate::types::diagnostic::{ self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, - INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_PARAMETER_DEFAULT, - INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, - IncompatibleBases, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, - TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, - UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type, - report_instance_layout_conflict, report_invalid_argument_number_to_special_form, - report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, - report_invalid_assignment, report_invalid_attribute_assignment, - report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict, - report_invalid_return_type, report_possibly_unbound_attribute, + INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE, + INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, + INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, POSSIBLY_UNBOUND_IMPLICIT_CALL, + POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, + UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, + report_implicit_return_type, report_instance_layout_conflict, + report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated, + report_invalid_arguments_to_callable, report_invalid_assignment, + report_invalid_attribute_assignment, report_invalid_generator_function_return_type, + report_invalid_key_on_typed_dict, report_invalid_return_type, + report_possibly_unbound_attribute, }; use crate::types::enums::is_enum_class; use crate::types::function::{ @@ -1110,13 +1111,33 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let is_protocol = class.is_protocol(self.db()); + let is_named_tuple = CodeGeneratorKind::NamedTuple.matches(self.db(), class); let mut solid_bases = IncompatibleBases::default(); // (2) Iterate through the class's explicit bases to check for various possible errors: // - Check for inheritance from plain `Generic`, // - Check for inheritance from a `@final` classes // - If the class is a protocol class: check for inheritance from a non-protocol class + // - If the class is a NamedTuple class: check for multiple inheritance that isn't `Generic[]` for (i, base_class) in class.explicit_bases(self.db()).iter().enumerate() { + if is_named_tuple + && !matches!( + base_class, + Type::SpecialForm(SpecialFormType::NamedTuple) + | Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)) + ) + { + if let Some(builder) = self + .context + .report_lint(&INVALID_NAMED_TUPLE, &class_node.bases()[i]) + { + builder.into_diagnostic(format_args!( + "NamedTuple class `{}` cannot use multiple inheritance except with `Generic[]`", + class.name(self.db()), + )); + } + } + let base_class = match base_class { Type::SpecialForm(SpecialFormType::Generic) => { if let Some(builder) = self diff --git a/ty.schema.json b/ty.schema.json index d94057ef9e..f6043e2d8f 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -561,6 +561,16 @@ } ] }, + "invalid-named-tuple": { + "title": "detects invalid `NamedTuple` class definitions", + "description": "## What it does\nChecks for invalidly defined `NamedTuple` classes.\n\n## Why is this bad?\nAn invalidly defined `NamedTuple` class may lead to the type checker\ndrawing incorrect conclusions. It may also lead to `TypeError`s at runtime.\n\n## Examples\nA class definition cannot combine `NamedTuple` with other base classes\nin multiple inheritance; doing so raises a `TypeError` at runtime. The sole\nexception to this rule is `Generic[]`, which can be used alongside `NamedTuple`\nin a class's bases list.\n\n```pycon\n>>> from typing import NamedTuple\n>>> class Foo(NamedTuple, object): ...\nTypeError: can only inherit from a NamedTuple type and Generic\n```", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "invalid-overload": { "title": "detects invalid `@overload` usages", "description": "## What it does\nChecks for various invalid `@overload` usages.\n\n## Why is this bad?\nThe `@overload` decorator is used to define functions and methods that accepts different\ncombinations of arguments and return different types based on the arguments passed. This is\nmainly beneficial for type checkers. But, if the `@overload` usage is invalid, the type\nchecker may not be able to provide correct type information.\n\n## Example\n\nDefining only one overload:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo(x: int) -> int: ...\ndef foo(x: int | None) -> int | None:\n return x\n```\n\nOr, not providing an implementation for the overloaded definition:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo() -> None: ...\n@overload\ndef foo(x: int) -> int: ...\n```\n\n## References\n- [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload)",