diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 6923aeef6f..78f757a6bb 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -39,7 +39,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -95,7 +95,7 @@ f(int) # error Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -126,7 +126,7 @@ a = 1 Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -158,7 +158,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -190,7 +190,7 @@ class B(A): ... Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -218,7 +218,7 @@ type B = A Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -245,7 +245,7 @@ class B(A, A): ... Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -357,7 +357,7 @@ def test(): -> "Literal[5]": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -387,7 +387,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -502,7 +502,7 @@ an atypical memory layout. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -529,7 +529,7 @@ func("foo") # error: [invalid-argument-type] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -557,7 +557,7 @@ a: int = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -591,7 +591,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -627,7 +627,7 @@ asyncio.run(main()) Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -651,7 +651,7 @@ class A(42): ... # error: [invalid-base] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -678,7 +678,7 @@ with 1: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -707,7 +707,7 @@ a: str Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -751,7 +751,7 @@ except ZeroDivisionError: Default level: error · Added in 0.0.1-alpha.28 · Related issues · -View source +View source @@ -793,7 +793,7 @@ class D(A): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -826,7 +826,7 @@ class C[U](Generic[T]): ... Default level: error · Added in 0.0.1-alpha.17 · Related issues · -View source +View source @@ -865,7 +865,7 @@ carol = Person(name="Carol", age=25) # typo! Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -900,7 +900,7 @@ def f(t: TypeVar("U")): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -934,7 +934,7 @@ class B(metaclass=f): ... Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1041,7 +1041,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule. Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -1073,7 +1073,7 @@ TypeError: can only inherit from a NamedTuple type and Generic Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -1103,7 +1103,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1153,7 +1153,7 @@ def foo(x: int) -> int: ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1179,7 +1179,7 @@ def f(a: int = ''): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1210,7 +1210,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1244,7 +1244,7 @@ TypeError: Protocols can only inherit from other protocols, got Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1293,7 +1293,7 @@ def g(): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1318,7 +1318,7 @@ def func() -> int: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1376,7 +1376,7 @@ TODO #14889 Default level: error · Added in 0.0.1-alpha.6 · Related issues · -View source +View source @@ -1403,7 +1403,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1450,7 +1450,7 @@ Bar[int] # error: too few arguments Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1480,7 +1480,7 @@ TYPE_CHECKING = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1510,7 +1510,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1544,7 +1544,7 @@ f(10) # Error Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1578,7 +1578,7 @@ class C: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1613,7 +1613,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1638,7 +1638,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1671,7 +1671,7 @@ alice["age"] # KeyError Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1700,7 +1700,7 @@ func("string") # error: [no-matching-overload] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1724,7 +1724,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1750,7 +1750,7 @@ for i in 34: # TypeError: 'int' object is not iterable Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1783,7 +1783,7 @@ class B(A): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1810,7 +1810,7 @@ f(1, x=2) # Error raised here Default level: error · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -1868,7 +1868,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1898,7 +1898,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1921,13 +1921,47 @@ class A: ... class B(A): ... # Error raised here ``` +## `super-call-in-named-tuple-method` + + +Default level: error · +Preview (since 0.0.1-alpha.30) · +Related issues · +View source + + + +**What it does** + +Checks for calls to `super()` inside methods of `NamedTuple` classes. + +**Why is this bad?** + +Using `super()` in a method of a `NamedTuple` class will raise an exception at runtime. + +**Examples** + +```python +from typing import NamedTuple + +class F(NamedTuple): + x: int + + def method(self): + super() # error: super() is not supported in methods of NamedTuple classes +``` + +**References** + +- [Python documentation: super()](https://docs.python.org/3/library/functions.html#super) + ## `too-many-positional-arguments` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1954,7 +1988,7 @@ f("foo") # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1982,7 +2016,7 @@ def _(x: int): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2028,7 +2062,7 @@ class A: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2055,7 +2089,7 @@ f(x=1, y=2) # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2083,7 +2117,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2108,7 +2142,7 @@ import foo # ModuleNotFoundError: No module named 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2133,7 +2167,7 @@ print(x) # NameError: name 'x' is not defined Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2170,7 +2204,7 @@ b1 < b2 < b1 # exception raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2198,7 +2232,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2223,7 +2257,7 @@ l[1:10:0] # ValueError: slice step cannot be zero Default level: warn · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -2264,7 +2298,7 @@ class SubProto(BaseProto, Protocol): Default level: warn · Added in 0.0.1-alpha.16 · Related issues · -View source +View source @@ -2352,7 +2386,7 @@ a = 20 / 0 # type: ignore Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2380,7 +2414,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2412,7 +2446,7 @@ A()[0] # TypeError: 'A' object is not subscriptable Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2444,7 +2478,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2471,7 +2505,7 @@ cast(int, f()) # Redundant Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2495,7 +2529,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined Default level: warn · Added in 0.0.1-alpha.15 · Related issues · -View source +View source @@ -2553,7 +2587,7 @@ def g(): Default level: warn · Added in 0.0.1-alpha.7 · Related issues · -View source +View source @@ -2592,7 +2626,7 @@ class D(C): ... # error: [unsupported-base] Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2655,7 +2689,7 @@ def foo(x: int | str) -> int | str: Default level: ignore · Preview (since 0.0.1-alpha.1) · Related issues · -View source +View source @@ -2679,7 +2713,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. Default level: ignore · Added in 0.0.1-alpha.1 · Related issues · -View source +View source diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index c49cf1708c..439cfff06b 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -408,3 +408,77 @@ class Vec2(NamedTuple): Vec2(0.0, 0.0) ``` + +## `super()` is not supported in NamedTuple methods + +Using `super()` in a method of a `NamedTuple` class will raise an exception at runtime. In Python +3.14+, a `TypeError` is raised; in earlier versions, a confusing `RuntimeError` about +`__classcell__` is raised. + +```py +from typing import NamedTuple + +class F(NamedTuple): + x: int + + def method(self): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super() + + def method_with_args(self): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super(F, self) + + def method_with_different_pivot(self): + # Even passing a different pivot class fails. + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super(tuple, self) + + @classmethod + def class_method(cls): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super() + + @staticmethod + def static_method(): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + super() + + @property + def prop(self): + # error: [super-call-in-named-tuple-method] "Cannot use `super()` in a method of NamedTuple class `F`" + return super() +``` + +However, classes that **inherit from** a `NamedTuple` class (but don't directly inherit from +`NamedTuple`) can use `super()` normally: + +```py +from typing import NamedTuple + +class Base(NamedTuple): + x: int + +class Child(Base): + def method(self): + super() +``` + +And regular classes that don't inherit from `NamedTuple` at all can use `super()` as normal: + +```py +class Regular: + def method(self): + super() # fine +``` + +Using `super()` on a `NamedTuple` class also works fine if it occurs outside the class: + +```py +from typing import NamedTuple + +class F(NamedTuple): + x: int + +super(F, F(42)) # fine +``` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 1f613fa568..3c38f6bdfc 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -19,7 +19,7 @@ use crate::semantic_index::{ use crate::types::bound_super::BoundSuperError; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::context::InferContext; -use crate::types::diagnostic::INVALID_TYPE_ALIAS_TYPE; +use crate::types::diagnostic::{INVALID_TYPE_ALIAS_TYPE, SUPER_CALL_IN_NAMED_TUPLE_METHOD}; use crate::types::enums::enum_metadata; use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::generics::{ @@ -5546,6 +5546,20 @@ impl KnownClass { return; }; + // Check if the enclosing class is a `NamedTuple`, which forbids the use of `super()`. + if CodeGeneratorKind::NamedTuple.matches(db, enclosing_class, None) { + if let Some(builder) = context + .report_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD, call_expression) + { + builder.into_diagnostic(format_args!( + "Cannot use `super()` in a method of NamedTuple class `{}`", + enclosing_class.name(db) + )); + } + overload.set_return_type(Type::unknown()); + return; + } + // The type of the first parameter if the given scope is function-like (i.e. function or lambda). // `None` if the scope is not function-like, or has no parameters. let first_param = match scope.node(db) { @@ -5585,6 +5599,22 @@ impl KnownClass { overload.set_return_type(bound_super); } [Some(pivot_class_type), Some(owner_type)] => { + // Check if the enclosing class is a `NamedTuple`, which forbids the use of `super()`. + if let Some(enclosing_class) = nearest_enclosing_class(db, index, scope) { + if CodeGeneratorKind::NamedTuple.matches(db, enclosing_class, None) { + if let Some(builder) = context + .report_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD, call_expression) + { + builder.into_diagnostic(format_args!( + "Cannot use `super()` in a method of NamedTuple class `{}`", + enclosing_class.name(db) + )); + } + overload.set_return_type(Type::unknown()); + return; + } + } + let bound_super = BoundSuperType::build(db, *pivot_class_type, *owner_type) .unwrap_or_else(|err| { err.report_diagnostic(context, call_expression.into()); diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 96aa876b50..8e92c495cf 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -121,6 +121,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&MISSING_TYPED_DICT_KEY); registry.register_lint(&INVALID_METHOD_OVERRIDE); registry.register_lint(&INVALID_EXPLICIT_OVERRIDE); + registry.register_lint(&SUPER_CALL_IN_NAMED_TUPLE_METHOD); // String annotations registry.register_lint(&BYTE_STRING_TYPE_ANNOTATION); @@ -1760,6 +1761,33 @@ declare_lint! { } } +declare_lint! { + /// ## What it does + /// Checks for calls to `super()` inside methods of `NamedTuple` classes. + /// + /// ## Why is this bad? + /// Using `super()` in a method of a `NamedTuple` class will raise an exception at runtime. + /// + /// ## Examples + /// ```python + /// from typing import NamedTuple + /// + /// class F(NamedTuple): + /// x: int + /// + /// def method(self): + /// super() # error: super() is not supported in methods of NamedTuple classes + /// ``` + /// + /// ## References + /// - [Python documentation: super()](https://docs.python.org/3/library/functions.html#super) + pub(crate) static SUPER_CALL_IN_NAMED_TUPLE_METHOD = { + summary: "detects `super()` calls in methods of `NamedTuple` classes", + status: LintStatus::preview("0.0.1-alpha.30"), + default_level: Level::Error, + } +} + declare_lint! { /// ## What it does /// Checks for calls to `reveal_type` without importing it. diff --git a/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap b/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap index 5505fbd5f2..0daa6c768a 100644 --- a/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap +++ b/crates/ty_server/tests/e2e/snapshots/e2e__commands__debug_command.snap @@ -93,6 +93,7 @@ Settings: Settings { "redundant-cast": Warning (Default), "static-assert-error": Error (Default), "subclass-of-final-class": Error (Default), + "super-call-in-named-tuple-method": Error (Default), "too-many-positional-arguments": Error (Default), "type-assertion-failure": Error (Default), "unavailable-implicit-super-arguments": Error (Default), diff --git a/ty.schema.json b/ty.schema.json index 38d5fd1326..e2325d6ac4 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -973,6 +973,16 @@ } ] }, + "super-call-in-named-tuple-method": { + "title": "detects `super()` calls in methods of `NamedTuple` classes", + "description": "## What it does\nChecks for calls to `super()` inside methods of `NamedTuple` classes.\n\n## Why is this bad?\nUsing `super()` in a method of a `NamedTuple` class will raise an exception at runtime.\n\n## Examples\n```python\nfrom typing import NamedTuple\n\nclass F(NamedTuple):\n x: int\n\n def method(self):\n super() # error: super() is not supported in methods of NamedTuple classes\n```\n\n## References\n- [Python documentation: super()](https://docs.python.org/3/library/functions.html#super)", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "too-many-positional-arguments": { "title": "detects calls passing too many positional arguments", "description": "## What it does\nChecks for calls that pass more positional arguments than the callable can accept.\n\n## Why is this bad?\nPassing too many positional arguments will raise `TypeError` at runtime.\n\n## Example\n\n```python\ndef f(): ...\n\nf(\"foo\") # Error raised here\n```",