diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 1c28473a8993a..f3551881334a4 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -8,7 +8,7 @@ Default level: error · Added in 0.0.13 · Related issues · -View source +View source @@ -49,7 +49,7 @@ class Derived(Base): # Error: `Derived` does not implement `method` Default level: warn · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -90,7 +90,7 @@ class SubProto(BaseProto, Protocol): Default level: error · Added in 0.0.14 · Related issues · -View source +View source @@ -126,7 +126,7 @@ def _(x: int): Default level: error · Preview (since 0.0.16) · Related issues · -View source +View source @@ -175,7 +175,7 @@ Foo.method() # Error: cannot call abstract classmethod Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -199,7 +199,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. Default level: error · Added in 0.0.7 · Related issues · -View source +View source @@ -230,7 +230,7 @@ def f(x: object): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -262,7 +262,7 @@ f(int) # error Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -293,7 +293,7 @@ a = 1 Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -325,7 +325,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -357,7 +357,7 @@ class B(A): ... Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -385,7 +385,7 @@ type B = A Default level: error · Added in 0.0.15 · Related issues · -View source +View source @@ -417,7 +417,7 @@ class Example: Default level: warn · Added in 0.0.1-alpha.16 · Related issues · -View source +View source @@ -444,7 +444,7 @@ old_func() # emits [deprecated] diagnostic Default level: ignore · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -473,7 +473,7 @@ false positives it can produce. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -500,7 +500,7 @@ class B(A, A): ... Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -538,7 +538,7 @@ class A: # Crash at runtime Default level: error · Added in 0.0.14 · Related issues · -View source +View source @@ -609,7 +609,7 @@ def foo() -> "intt\b": ... Default level: error · Added in 0.0.20 · Related issues · -View source +View source @@ -641,7 +641,7 @@ def my_function() -> int: Default level: error · Added in 0.0.15 · Related issues · -View source +View source @@ -736,7 +736,7 @@ def test(): -> "Literal[5]": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -766,7 +766,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -792,7 +792,7 @@ t[3] # IndexError: tuple index out of range Default level: warn · Added in 0.0.1-alpha.33 · Related issues · -View source +View source @@ -826,7 +826,7 @@ class MyClass: ... Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -915,7 +915,7 @@ an atypical memory layout. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -942,7 +942,7 @@ func("foo") # error: [invalid-argument-type] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -970,7 +970,7 @@ a: int = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1004,7 +1004,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 @@ -1040,7 +1040,7 @@ asyncio.run(main()) Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1064,7 +1064,7 @@ class A(42): ... # error: [invalid-base] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1091,7 +1091,7 @@ with 1: Default level: error · Added in 0.0.12 · Related issues · -View source +View source @@ -1128,7 +1128,7 @@ class Foo(NamedTuple): Default level: error · Added in 0.0.13 · Related issues · -View source +View source @@ -1160,7 +1160,7 @@ class A: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1189,7 +1189,7 @@ a: str Default level: warn · Added in 0.0.20 · Related issues · -View source +View source @@ -1238,7 +1238,7 @@ class Pet(Enum): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1282,7 +1282,7 @@ except ZeroDivisionError: Default level: error · Added in 0.0.1-alpha.28 · Related issues · -View source +View source @@ -1324,7 +1324,7 @@ class D(A): Default level: error · Added in 0.0.1-alpha.35 · Related issues · -View source +View source @@ -1368,7 +1368,7 @@ class NonFrozenChild(FrozenBase): # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1406,7 +1406,7 @@ class D(Generic[U, T]): ... Default level: error · Added in 0.0.12 · Related issues · -View source +View source @@ -1485,7 +1485,7 @@ a = 20 / 0 # type: ignore Default level: error · Added in 0.0.1-alpha.17 · Related issues · -View source +View source @@ -1524,7 +1524,7 @@ carol = Person(name="Carol", aeg=25) # typo! Default level: warn · Added in 0.0.15 · Related issues · -View source +View source @@ -1585,7 +1585,7 @@ def f(x, y, /): # Python 3.8+ syntax Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1620,7 +1620,7 @@ def f(t: TypeVar("U")): ... Default level: error · Added in 0.0.18 · Related issues · -View source +View source @@ -1648,7 +1648,7 @@ match x: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1682,7 +1682,7 @@ class B(metaclass=f): ... Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1789,7 +1789,7 @@ Correct use of `@override` is enforced by ty's [`invalid-explicit-override`](#in Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -1843,7 +1843,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict Default level: warn · Added in 0.0.31 · Related issues · -View source +View source @@ -1884,7 +1884,7 @@ admin[0] # "Alice" Default level: error · Added in 0.0.1-alpha.27 · Related issues · -View source +View source @@ -1914,7 +1914,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 @@ -1964,7 +1964,7 @@ def foo(x: int) -> int: ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1990,7 +1990,7 @@ def f(a: int = ''): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2021,7 +2021,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 @@ -2055,7 +2055,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 @@ -2104,7 +2104,7 @@ def g(): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2133,7 +2133,7 @@ def func() -> int: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2229,7 +2229,7 @@ class C: ... Default level: error · Added in 0.0.10 · Related issues · -View source +View source @@ -2275,7 +2275,7 @@ class MyClass: Default level: error · Added in 0.0.1-alpha.6 · Related issues · -View source +View source @@ -2302,7 +2302,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 @@ -2349,7 +2349,7 @@ Bar[int] # error: too few arguments Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2379,7 +2379,7 @@ TYPE_CHECKING = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2409,7 +2409,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 @@ -2443,7 +2443,7 @@ f(10) # Error Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -2477,7 +2477,7 @@ class C: Default level: error · Added in 0.0.15 · Related issues · -View source +View source @@ -2508,7 +2508,7 @@ def g[U, T: U](): ... # error: [invalid-type-variable-bound] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2555,7 +2555,7 @@ U = TypeVar('U', list[int], int) # valid constrained Type Default level: error · Added in 0.0.16 · Related issues · -View source +View source @@ -2587,7 +2587,7 @@ U = TypeVar("U", int, str, default=bytes) # error: [invalid-type-variable-defau Default level: error · Added in 0.0.28 · Related issues · -View source +View source @@ -2618,7 +2618,7 @@ class Child(Base): Default level: error · Added in 0.0.14 · Related issues · -View source +View source @@ -2653,7 +2653,7 @@ def f(x: dict): Default level: error · Added in 0.0.9 · Related issues · -View source +View source @@ -2684,7 +2684,7 @@ class Foo(TypedDict): Default level: error · Added in 0.0.25 · Related issues · -View source +View source @@ -2715,7 +2715,7 @@ def gen() -> Iterator[int]: Default level: error · Added in 0.0.14 · Related issues · -View source +View source @@ -2770,7 +2770,7 @@ def h(arg2: type): Default level: error · Added in 0.0.15 · Related issues · -View source +View source @@ -2813,7 +2813,7 @@ def g(arg: object): Default level: warn · Added in 0.0.30 · Related issues · -View source +View source @@ -2851,7 +2851,7 @@ Movie = TypedDict("Film", {"title": str}) # error: [mismatched-type-name] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2876,7 +2876,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 @@ -2909,7 +2909,7 @@ alice["age"] # KeyError Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2938,7 +2938,7 @@ func("string") # error: [no-matching-overload] Default level: error · Added in 0.0.30 · Related issues · -View source +View source @@ -2971,7 +2971,7 @@ class Sub(Super): ... # error: [non-callable-init-subclass] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2997,7 +2997,7 @@ for i in 34: # TypeError: 'int' object is not iterable Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3021,7 +3021,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -3054,7 +3054,7 @@ class B(A): Default level: error · Added in 0.0.16 · Related issues · -View source +View source @@ -3087,7 +3087,7 @@ class B(A): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3114,7 +3114,7 @@ f(1, x=2) # Error raised here Default level: error · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -3141,7 +3141,7 @@ f(x=1) # Error raised here Default level: ignore · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -3174,7 +3174,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 @@ -3206,7 +3206,7 @@ A()[0] # TypeError: 'A' object is not subscriptable Default level: ignore · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -3243,7 +3243,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' Default level: warn · Added in 0.0.23 · Related issues · -View source +View source @@ -3270,7 +3270,7 @@ html.parser # AttributeError: module 'html' has no attribute 'parser' Default level: ignore · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3334,7 +3334,7 @@ def test(): -> "int": Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3361,7 +3361,7 @@ cast(int, f()) # Redundant Default level: warn · Added in 0.0.18 · Related issues · -View source +View source @@ -3393,7 +3393,7 @@ class C: Default level: error · Added in 0.0.20 · Related issues · -View source +View source @@ -3427,7 +3427,7 @@ class Outer[T]: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3457,7 +3457,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 @@ -3486,7 +3486,7 @@ class B(A): ... # Error raised here Default level: error · Added in 0.0.1-alpha.30 · Related issues · -View source +View source @@ -3520,7 +3520,7 @@ class F(NamedTuple): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3547,7 +3547,7 @@ f("foo") # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3575,7 +3575,7 @@ def _(x: int): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3621,7 +3621,7 @@ class A: Default level: error · Added in 0.0.20 · Related issues · -View source +View source @@ -3658,7 +3658,7 @@ class C(Generic[T]): Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3682,7 +3682,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3709,7 +3709,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 @@ -3737,7 +3737,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' Default level: warn · Added in 0.0.1-alpha.15 · Related issues · -View source +View source @@ -3795,7 +3795,7 @@ def g(): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3820,7 +3820,7 @@ import foo # ModuleNotFoundError: No module named 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3845,7 +3845,7 @@ print(x) # NameError: name 'x' is not defined Default level: warn · Added in 0.0.1-alpha.7 · Related issues · -View source +View source @@ -3884,7 +3884,7 @@ class D(C): ... # error: [unsupported-base] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3921,7 +3921,7 @@ b1 < b2 < b1 # exception raised here Default level: ignore · Added in 0.0.12 · Related issues · -View source +View source @@ -3961,7 +3961,7 @@ def factory(base: type[Base]) -> type: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3989,7 +3989,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' Default level: warn · Preview (since 0.0.21) · Related issues · -View source +View source @@ -4095,7 +4095,7 @@ to `false`. Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -4158,7 +4158,7 @@ def foo(x: int | str) -> int | str: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source diff --git a/crates/ty/tests/cli/rule_selection.rs b/crates/ty/tests/cli/rule_selection.rs index ec71fbc18a9f2..02300dc28274a 100644 --- a/crates/ty/tests/cli/rule_selection.rs +++ b/crates/ty/tests/cli/rule_selection.rs @@ -1105,15 +1105,17 @@ fn configuration_all_rules_with_rule_sorting_before_all() -> anyhow::Result<()> exit_code: 1 ----- stdout ----- error[abstract-method-in-final-class]: Final class `Derived` has unimplemented abstract methods - --> test.py:11:7 + --> test.py:6:5 | - 11 | class Derived(Base): - | ^^^^^^^ `foo` is unimplemented - | - ::: test.py:7:9 - | - 7 | def foo(self) -> int: - | --- `foo` declared as abstract on superclass `Base` + 6 | / @abstractmethod + 7 | | def foo(self) -> int: + | |________________________- `foo` declared as abstract on superclass `Base` + 8 | raise NotImplementedError + 9 | + 10 | @final + | ------ + 11 | class Derived(Base): + | ^^^^^^^ `foo` is unimplemented | info: rule `abstract-method-in-final-class` was selected in the configuration file @@ -1166,15 +1168,17 @@ fn overrides_all_rules_with_rule_sorting_before_all() -> anyhow::Result<()> { exit_code: 1 ----- stdout ----- error[abstract-method-in-final-class]: Final class `Derived` has unimplemented abstract methods - --> src/test.py:11:7 - | - 11 | class Derived(Base): - | ^^^^^^^ `foo` is unimplemented - | - ::: src/test.py:7:9 + --> src/test.py:6:5 | - 7 | def foo(self) -> int: - | --- `foo` declared as abstract on superclass `Base` + 6 | / @abstractmethod + 7 | | def foo(self) -> int: + | |________________________- `foo` declared as abstract on superclass `Base` + 8 | raise NotImplementedError + 9 | + 10 | @final + | ------ + 11 | class Derived(Base): + | ^^^^^^^ `foo` is unimplemented | info: rule `abstract-method-in-final-class` was selected in the configuration file diff --git a/crates/ty_python_semantic/resources/mdtest/call/abstract_method.md b/crates/ty_python_semantic/resources/mdtest/call/abstract_method.md index a2f7d9c99447b..6509b63edf230 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/abstract_method.md +++ b/crates/ty_python_semantic/resources/mdtest/call/abstract_method.md @@ -18,16 +18,16 @@ Foo.method() ```snapshot error[call-abstract-method]: Cannot call `method` on class object - --> src/mdtest_snippet.py:9:1 + --> src/mdtest_snippet.py:4:5 | -9 | Foo.method() - | ^^^^^^^^^^^^ `method` is an abstract classmethod with a trivial body - | -info: Method `method` defined here - --> src/mdtest_snippet.py:6:9 - | -6 | def method(cls) -> int: ... - | ^^^^^^ +4 | / @classmethod +5 | | @abstractmethod +6 | | def method(cls) -> int: ... + | |_______________________________- Method `method` defined here +7 | +8 | # snapshot: call-abstract-method +9 | Foo.method() + | ^^^^^^^^^^^^ `method` is an abstract classmethod with a trivial body | ``` diff --git a/crates/ty_python_semantic/resources/mdtest/call/overloads.md b/crates/ty_python_semantic/resources/mdtest/call/overloads.md index 995f05839396a..b9656ca57b7d5 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/call/overloads.md @@ -956,10 +956,11 @@ error[no-matching-overload]: No overload of function `f` matches arguments | info: Limit of argument type expansion reached at argument 9 info: First overload defined here - --> src/overloaded.pyi:8:5 + --> src/overloaded.pyi:7:1 | -8 | def f() -> None: ... - | ^^^^^^^^^^^ +7 | / @overload +8 | | def f() -> None: ... + | |____________________^ First overload defined here | info: Possible overloads for function `f`: info: () -> None diff --git a/crates/ty_python_semantic/resources/mdtest/del.md b/crates/ty_python_semantic/resources/mdtest/del.md index 3149a50829bc6..6ac52f41384f9 100644 --- a/crates/ty_python_semantic/resources/mdtest/del.md +++ b/crates/ty_python_semantic/resources/mdtest/del.md @@ -450,10 +450,15 @@ error[invalid-argument-type]: Cannot delete required key "name" from TypedDict ` | ^^^^^^ | info: Field defined here - --> src/mdtest_snippet.py:4:5 + --> src/mdtest_snippet.py:3:7 | +3 | class Movie(TypedDict): + | ---------------- `Movie` defined here 4 | name: str - | --------- `name` declared as required here; consider making it `NotRequired` + | --------- + | | + | `name` declared as required here + | Consider making it `NotRequired` | info: Only keys marked as `NotRequired` (or in a TypedDict with `total=False`) can be deleted ``` @@ -485,10 +490,15 @@ error[invalid-argument-type]: Cannot delete required key "name" from TypedDict ` | ^^^^^^ | info: Field defined here - --> src/mdtest_snippet.py:12:5 + --> src/mdtest_snippet.py:11:7 | +11 | class MixedMovie(TypedDict): + | --------------------- `MixedMovie` defined here 12 | name: str - | --------- `name` declared as required here; consider making it `NotRequired` + | --------- + | | + | `name` declared as required here + | Consider making it `NotRequired` | info: Only keys marked as `NotRequired` (or in a TypedDict with `total=False`) can be deleted ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Abstract_method_in_g\342\200\246_(6d8b024dda7ced11).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Abstract_method_in_g\342\200\246_(6d8b024dda7ced11).snap" index d65494736608d..875d98df1357d 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Abstract_method_in_g\342\200\246_(6d8b024dda7ced11).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Abstract_method_in_g\342\200\246_(6d8b024dda7ced11).snap" @@ -32,15 +32,19 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md ``` error[abstract-method-in-final-class]: Final class `Child` has unimplemented abstract methods - --> src/mdtest_snippet.py:12:7 + --> src/mdtest_snippet.py:5:5 | -12 | class Child(Parent): # error: [abstract-method-in-final-class] - | ^^^^^ `method` is unimplemented - | - ::: src/mdtest_snippet.py:6:9 - | - 6 | def method(self) -> int: ... - | ------ `method` declared as abstract on superclass `GrandParent` + 5 | / @abstractmethod + 6 | | def method(self) -> int: ... + | |________________________________- `method` declared as abstract on superclass `GrandParent` + 7 | + 8 | class Parent(GrandParent): + 9 | pass +10 | +11 | @final + | ------ +12 | class Child(Parent): # error: [abstract-method-in-final-class] + | ^^^^^ `method` is unimplemented | ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Basic_case_with_ABC_(21e412599c45972a).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Basic_case_with_ABC_(21e412599c45972a).snap" index dda5074f43a4c..77e677b06f64e 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Basic_case_with_ABC_(21e412599c45972a).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Basic_case_with_ABC_(21e412599c45972a).snap" @@ -30,15 +30,17 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md ``` error[abstract-method-in-final-class]: Final class `Derived` has unimplemented abstract methods - --> src/mdtest_snippet.py:10:7 + --> src/mdtest_snippet.py:5:5 | -10 | class Derived(Base): # error: [abstract-method-in-final-class] "Final class `Derived` has unimplemented abstract method `foo`" - | ^^^^^^^ `foo` is unimplemented - | - ::: src/mdtest_snippet.py:6:9 - | - 6 | def foo(self) -> int: - | --- `foo` declared as abstract on superclass `Base` + 5 | / @abstractmethod + 6 | | def foo(self) -> int: + | |________________________- `foo` declared as abstract on superclass `Base` + 7 | raise NotImplementedError + 8 | + 9 | @final + | ------ +10 | class Derived(Base): # error: [abstract-method-in-final-class] "Final class `Derived` has unimplemented abstract method `foo`" + | ^^^^^^^ `foo` is unimplemented | ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(ecae0f4510696c95).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(ecae0f4510696c95).snap" index 34fcdbdb97f77..364cd63c677be 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(ecae0f4510696c95).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(ecae0f4510696c95).snap" @@ -45,13 +45,16 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md ``` error[abstract-method-in-final-class]: Final class `Abstract` has unimplemented abstract methods - --> src/mdtest_snippet.py:6:7 + --> src/mdtest_snippet.py:4:1 | -6 | class Abstract(ABC): - | ^^^^^^^^ Abstract methods `aaaaaaaaaa`, `bbbbbbbb`, `cccccccc`, `ddddddddd`, `eeeeeeeee`, `ffffffff`, `ggggggg`, `hhhhhhhh`, `iiiiiiiii` and `kkkkkkkkkk` are unimplemented -7 | @abstractmethod -8 | def aaaaaaaaaa(self) -> int: ... - | ---------- `aaaaaaaaaa` declared as abstract +4 | @final + | ------ +5 | # error: [abstract-method-in-final-class] "Final class `Abstract` has unimplemented abstract methods `aaaaaaaaaa`, `bbbbbbbb`, `ccccccc… +6 | class Abstract(ABC): + | ^^^^^^^^ Abstract methods `aaaaaaaaaa`, `bbbbbbbb`, `cccccccc`, `ddddddddd`, `eeeeeeeee`, `ffffffff`, `ggggggg`, `hhhhhhhh`, `iiiiiiiii` and `kkkkkkkkkk` are unimplemented +7 | / @abstractmethod +8 | | def aaaaaaaaaa(self) -> int: ... + | |____________________________________- `aaaaaaaaaa` declared as abstract | info: rule `abstract-method-in-final-class` is enabled by default diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(f807ff3716d8ab0d).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(f807ff3716d8ab0d).snap" index 22724e278cf87..1bdc276848e1d 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(f807ff3716d8ab0d).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Diagnostic_when_ther\342\200\246_(f807ff3716d8ab0d).snap" @@ -45,13 +45,16 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md ``` error[abstract-method-in-final-class]: Final class `Abstract` has unimplemented abstract methods - --> src/mdtest_snippet.py:6:7 + --> src/mdtest_snippet.py:4:1 | -6 | class Abstract(ABC): - | ^^^^^^^^ 10 abstract methods are unimplemented, including `aaaaaaaaaa`, `bbbbbbbb` and `cccccccc` -7 | @abstractmethod -8 | def aaaaaaaaaa(self) -> int: ... - | ---------- `aaaaaaaaaa` declared as abstract +4 | @final + | ------ +5 | # error: [abstract-method-in-final-class] "Final class `Abstract` has 10 unimplemented abstract methods, including `aaaaaaaaaa`, `bbbbb… +6 | class Abstract(ABC): + | ^^^^^^^^ 10 abstract methods are unimplemented, including `aaaaaaaaaa`, `bbbbbbbb` and `cccccccc` +7 | / @abstractmethod +8 | | def aaaaaaaaaa(self) -> int: ... + | |____________________________________- `aaaaaaaaaa` declared as abstract | info: Use `--verbose` to see all 10 unimplemented abstract methods diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Multiple_abstract_me\342\200\246_(feafee9a4abbe8d1).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Multiple_abstract_me\342\200\246_(feafee9a4abbe8d1).snap" index 46ea5dcbcadf2..dfc8d0464f2b2 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Multiple_abstract_me\342\200\246_(feafee9a4abbe8d1).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Multiple_abstract_me\342\200\246_(feafee9a4abbe8d1).snap" @@ -41,30 +41,36 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md ``` error[abstract-method-in-final-class]: Final class `MissingAll` has unimplemented abstract methods - --> src/mdtest_snippet.py:13:7 + --> src/mdtest_snippet.py:12:1 | -13 | class MissingAll(Base): # error: [abstract-method-in-final-class] - | ^^^^^^^^^^ Abstract methods `foo`, `bar` and `baz` are unimplemented +12 | @final + | ------ +13 | class MissingAll(Base): # error: [abstract-method-in-final-class] + | ^^^^^^^^^^ Abstract methods `foo`, `bar` and `baz` are unimplemented | - ::: src/mdtest_snippet.py:6:9 + ::: src/mdtest_snippet.py:5:5 | - 6 | def foo(self) -> int: ... - | --- `foo` declared as abstract on superclass `Base` + 5 | / @abstractmethod + 6 | | def foo(self) -> int: ... + | |_____________________________- `foo` declared as abstract on superclass `Base` | ``` ``` error[abstract-method-in-final-class]: Final class `PartiallyImplemented` has unimplemented abstract methods - --> src/mdtest_snippet.py:17:7 + --> src/mdtest_snippet.py:16:1 | -17 | class PartiallyImplemented(Base): # error: [abstract-method-in-final-class] - | ^^^^^^^^^^^^^^^^^^^^ `baz` is unimplemented +16 | @final + | ------ +17 | class PartiallyImplemented(Base): # error: [abstract-method-in-final-class] + | ^^^^^^^^^^^^^^^^^^^^ `baz` is unimplemented | - ::: src/mdtest_snippet.py:10:9 + ::: src/mdtest_snippet.py:9:5 | -10 | def baz(self) -> None: ... - | --- `baz` declared as abstract on superclass `Base` + 9 | / @abstractmethod +10 | | def baz(self) -> None: ... + | |______________________________- `baz` declared as abstract on superclass `Base` | ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Protocol_with_implic\342\200\246_(e373f31c7a7d88e7).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Protocol_with_implic\342\200\246_(e373f31c7a7d88e7).snap" index 055630997ead6..c421af29c49cc 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Protocol_with_implic\342\200\246_(e373f31c7a7d88e7).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_`@final`_class_mus\342\200\246_-_Protocol_with_implic\342\200\246_(e373f31c7a7d88e7).snap" @@ -169,12 +169,13 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md ``` error[abstract-method-in-final-class]: Final class `Q` has unimplemented abstract methods - --> src/mdtest_snippet.py:11:9 + --> src/mdtest_snippet.py:11:5 | 11 | def still_abstractmethod(self): ... - | -------------------- `still_abstractmethod` declared as abstract on superclass `P` + | ----------------------------------- `still_abstractmethod` declared as abstract on superclass `P` 12 | 13 | @final + | ------ 14 | class Q(P): ... # error: [abstract-method-in-final-class] | ^ `still_abstractmethod` is unimplemented | @@ -190,12 +191,13 @@ help: Change the body of `still_abstractmethod` to `return` or `return None` if ``` error[abstract-method-in-final-class]: Final class `S` has unimplemented abstract methods - --> src/mdtest_snippet.py:18:9 + --> src/mdtest_snippet.py:18:5 | 18 | def also_still_abstractmethod(self) -> None: ... - | ------------------------- `also_still_abstractmethod` declared as abstract on superclass `R` + | ------------------------------------------------ `also_still_abstractmethod` declared as abstract on superclass `R` 19 | 20 | @final + | ------ 21 | class S(R): ... # error: [abstract-method-in-final-class] | ^ `also_still_abstractmethod` is unimplemented | @@ -211,15 +213,16 @@ help: Change the body of `also_still_abstractmethod` to `return` or `return None ``` error[abstract-method-in-final-class]: Final class `RaisesSub` has unimplemented abstract methods - --> src/mdtest_snippet.py:28:7 + --> src/mdtest_snippet.py:24:5 | -28 | class RaisesSub(Raises): ... # error: [abstract-method-in-final-class] - | ^^^^^^^^^ `even_this_is_abstract` is unimplemented - | - ::: src/mdtest_snippet.py:24:9 - | -24 | def even_this_is_abstract(self): - | --------------------- `even_this_is_abstract` declared as abstract on superclass `Raises` +24 | / def even_this_is_abstract(self): +25 | | raise NotImplementedError + | |_________________________________- `even_this_is_abstract` declared as abstract on superclass `Raises` +26 | +27 | @final + | ------ +28 | class RaisesSub(Raises): ... # error: [abstract-method-in-final-class] + | ^^^^^^^^^ `even_this_is_abstract` is unimplemented | info: `Raises.even_this_is_abstract` is implicitly abstract because `Raises` is a `Protocol` class and `even_this_is_abstract` lacks an implementation --> src/mdtest_snippet.py:23:7 @@ -232,15 +235,16 @@ info: `Raises.even_this_is_abstract` is implicitly abstract because `Raises` is ``` error[abstract-method-in-final-class]: Final class `AlsoRaisesSub` has unimplemented abstract methods - --> src/mdtest_snippet.py:35:7 - | -35 | class AlsoRaisesSub(AlsoRaises): ... # error: [abstract-method-in-final-class] - | ^^^^^^^^^^^^^ `also_abstractmethod` is unimplemented + --> src/mdtest_snippet.py:31:5 | - ::: src/mdtest_snippet.py:31:9 - | -31 | def also_abstractmethod(self) -> Never: - | ------------------- `also_abstractmethod` declared as abstract on superclass `AlsoRaises` +31 | / def also_abstractmethod(self) -> Never: +32 | | raise NotImplementedError + | |_________________________________- `also_abstractmethod` declared as abstract on superclass `AlsoRaises` +33 | +34 | @final + | ------ +35 | class AlsoRaisesSub(AlsoRaises): ... # error: [abstract-method-in-final-class] + | ^^^^^^^^^^^^^ `also_abstractmethod` is unimplemented | info: `AlsoRaises.also_abstractmethod` is implicitly abstract because `AlsoRaises` is a `Protocol` class and `also_abstractmethod` lacks an implementation --> src/mdtest_snippet.py:30:7 @@ -253,15 +257,16 @@ info: `AlsoRaises.also_abstractmethod` is implicitly abstract because `AlsoRaise ``` error[abstract-method-in-final-class]: Final class `StrangeSub` has unimplemented abstract methods - --> src/mdtest_snippet.py:45:11 - | -45 | class StrangeSub(Strange): ... # error: [abstract-method-in-final-class] - | ^^^^^^^^^^ `weird_abstractmethod` is unimplemented - | - ::: src/mdtest_snippet.py:41:13 + --> src/mdtest_snippet.py:41:9 | -41 | def weird_abstractmethod(self): - | -------------------- `weird_abstractmethod` declared as abstract on superclass `Strange` +41 | / def weird_abstractmethod(self): +42 | | raise x + | |___________________- `weird_abstractmethod` declared as abstract on superclass `Strange` +43 | +44 | @final + | ------ +45 | class StrangeSub(Strange): ... # error: [abstract-method-in-final-class] + | ^^^^^^^^^^ `weird_abstractmethod` is unimplemented | info: `Strange.weird_abstractmethod` is implicitly abstract because `Strange` is a `Protocol` class and `weird_abstractmethod` lacks an implementation --> src/mdtest_snippet.py:40:11 @@ -280,6 +285,7 @@ error[abstract-method-in-final-class]: Final class `HasOverloadSub` has unimplem | --- `foo` declared as abstract on superclass `HasOverloads` 52 | 53 | @final + | ------ 54 | class HasOverloadSub(HasOverloads): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^ `foo` is unimplemented | @@ -294,15 +300,17 @@ info: `HasOverloads.foo` is implicitly abstract because `HasOverloads` is a `Pro ``` error[abstract-method-in-final-class]: Final class `HasAbstractSub` has unimplemented abstract methods - --> src/mdtest_snippet.py:123:7 + --> src/mdtest_snippet.py:122:1 | +122 | @final + | ------ 123 | class HasAbstractSub(HasAbstract): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:72:9 + ::: src/mdtest_snippet.py:72:5 | 72 | def a(self) -> int: ... - | - `a` declared as abstract on superclass `HasAbstract` + | ----------------------- `a` declared as abstract on superclass `HasAbstract` | info: `HasAbstract.a` is implicitly abstract because `HasAbstract` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:71:7 @@ -315,15 +323,18 @@ info: `HasAbstract.a` is implicitly abstract because `HasAbstract` is a `Protoco ``` error[abstract-method-in-final-class]: Final class `HasAbstract2Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:126:7 + --> src/mdtest_snippet.py:125:1 | -126 | class HasAbstract2Sub(HasAbstract2): ... # error: [abstract-method-in-final-class] - | ^^^^^^^^^^^^^^^ `a` is unimplemented +125 | @final + | ------ +126 | class HasAbstract2Sub(HasAbstract2): ... # error: [abstract-method-in-final-class] + | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:75:9 + ::: src/mdtest_snippet.py:75:5 | - 75 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract2` + 75 | / def a(self) -> int: + 76 | | pass + | |____________- `a` declared as abstract on superclass `HasAbstract2` | info: `HasAbstract2.a` is implicitly abstract because `HasAbstract2` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:74:7 @@ -336,15 +347,17 @@ info: `HasAbstract2.a` is implicitly abstract because `HasAbstract2` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract3Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:129:7 + --> src/mdtest_snippet.py:128:1 | +128 | @final + | ------ 129 | class HasAbstract3Sub(HasAbstract4): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:83:9 + ::: src/mdtest_snippet.py:83:5 | 83 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract4` + | ------------------ `a` declared as abstract on superclass `HasAbstract4` | info: `HasAbstract4.a` is implicitly abstract because `HasAbstract4` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:82:7 @@ -357,15 +370,17 @@ info: `HasAbstract4.a` is implicitly abstract because `HasAbstract4` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract4Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:132:7 + --> src/mdtest_snippet.py:131:1 | +131 | @final + | ------ 132 | class HasAbstract4Sub(HasAbstract4): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:83:9 + ::: src/mdtest_snippet.py:83:5 | 83 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract4` + | ------------------ `a` declared as abstract on superclass `HasAbstract4` | info: `HasAbstract4.a` is implicitly abstract because `HasAbstract4` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:82:7 @@ -378,15 +393,17 @@ info: `HasAbstract4.a` is implicitly abstract because `HasAbstract4` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract5Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:135:7 + --> src/mdtest_snippet.py:134:1 | +134 | @final + | ------ 135 | class HasAbstract5Sub(HasAbstract5): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:88:9 + ::: src/mdtest_snippet.py:88:5 | 88 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract5` + | ------------------ `a` declared as abstract on superclass `HasAbstract5` | info: `HasAbstract5.a` is implicitly abstract because `HasAbstract5` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:87:7 @@ -399,15 +416,17 @@ info: `HasAbstract5.a` is implicitly abstract because `HasAbstract5` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract6Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:138:7 + --> src/mdtest_snippet.py:137:1 | +137 | @final + | ------ 138 | class HasAbstract6Sub(HasAbstract6): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:93:9 + ::: src/mdtest_snippet.py:93:5 | 93 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract6` + | ------------------ `a` declared as abstract on superclass `HasAbstract6` | info: `HasAbstract6.a` is implicitly abstract because `HasAbstract6` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:92:7 @@ -420,15 +439,18 @@ info: `HasAbstract6.a` is implicitly abstract because `HasAbstract6` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract7Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:141:7 + --> src/mdtest_snippet.py:140:1 | -141 | class HasAbstract7Sub(HasAbstract7): ... # error: [abstract-method-in-final-class] - | ^^^^^^^^^^^^^^^ `a` is unimplemented +140 | @final + | ------ +141 | class HasAbstract7Sub(HasAbstract7): ... # error: [abstract-method-in-final-class] + | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:105:9 + ::: src/mdtest_snippet.py:105:5 | -105 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract7` +105 | / def a(self) -> int: +106 | | raise NotImplementedError + | |_________________________________- `a` declared as abstract on superclass `HasAbstract7` | info: `HasAbstract7.a` is implicitly abstract because `HasAbstract7` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:104:7 @@ -441,15 +463,18 @@ info: `HasAbstract7.a` is implicitly abstract because `HasAbstract7` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract8Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:144:7 + --> src/mdtest_snippet.py:143:1 | -144 | class HasAbstract8Sub(HasAbstract8): ... # error: [abstract-method-in-final-class] - | ^^^^^^^^^^^^^^^ `a` is unimplemented +143 | @final + | ------ +144 | class HasAbstract8Sub(HasAbstract8): ... # error: [abstract-method-in-final-class] + | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:109:9 + ::: src/mdtest_snippet.py:109:5 | -109 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract8` +109 | / def a(self) -> int: +110 | | raise NotImplementedError() + | |___________________________________- `a` declared as abstract on superclass `HasAbstract8` | info: `HasAbstract8.a` is implicitly abstract because `HasAbstract8` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:108:7 @@ -462,15 +487,17 @@ info: `HasAbstract8.a` is implicitly abstract because `HasAbstract8` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract9Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:147:7 + --> src/mdtest_snippet.py:146:1 | +146 | @final + | ------ 147 | class HasAbstract9Sub(HasAbstract9): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:113:9 + ::: src/mdtest_snippet.py:113:5 | 113 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract9` + | ------------------ `a` declared as abstract on superclass `HasAbstract9` | info: `HasAbstract9.a` is implicitly abstract because `HasAbstract9` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:112:7 @@ -483,15 +510,17 @@ info: `HasAbstract9.a` is implicitly abstract because `HasAbstract9` is a `Proto ``` error[abstract-method-in-final-class]: Final class `HasAbstract10Sub` has unimplemented abstract methods - --> src/mdtest_snippet.py:150:7 + --> src/mdtest_snippet.py:149:1 | +149 | @final + | ------ 150 | class HasAbstract10Sub(HasAbstract10): ... # error: [abstract-method-in-final-class] | ^^^^^^^^^^^^^^^^ `a` is unimplemented | - ::: src/mdtest_snippet.py:118:9 + ::: src/mdtest_snippet.py:118:5 | 118 | def a(self) -> int: - | - `a` declared as abstract on superclass `HasAbstract10` + | ------------------ `a` declared as abstract on superclass `HasAbstract10` | info: `HasAbstract10.a` is implicitly abstract because `HasAbstract10` is a `Protocol` class and `a` lacks an implementation --> src/mdtest_snippet.py:117:7 diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloaded_methods_d\342\200\246_(861757f48340ed92).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloaded_methods_d\342\200\246_(861757f48340ed92).snap" index 9daa93e8d247d..f7d51d3c021f3 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloaded_methods_d\342\200\246_(861757f48340ed92).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloaded_methods_d\342\200\246_(861757f48340ed92).snap" @@ -196,32 +196,34 @@ note: This is an unsafe fix and may change runtime behavior ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/stub.pyi:27:9 + --> src/stub.pyi:26:5 | -27 | def bar(self, x: str) -> str: ... - | --- First overload defined here -28 | @overload -29 | @final - | ------ -30 | # error: [invalid-overload] -31 | def bar(self, x: int) -> int: ... - | ^^^ +26 | / @overload +27 | | def bar(self, x: str) -> str: ... + | |_____________________________________- First overload defined here +28 | @overload +29 | @final + | ------ +30 | # error: [invalid-overload] +31 | def bar(self, x: int) -> int: ... + | ^^^ | ``` ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/stub.pyi:33:9 + --> src/stub.pyi:32:5 | -33 | def baz(self, x: str) -> str: ... - | --- First overload defined here -34 | @final - | ------ -35 | @overload -36 | # error: [invalid-overload] -37 | def baz(self, x: int) -> int: ... - | ^^^ +32 | / @overload +33 | | def baz(self, x: str) -> str: ... + | |_____________________________________- First overload defined here +34 | @final + | ------ +35 | @overload +36 | # error: [invalid-overload] +37 | def baz(self, x: int) -> int: ... + | ^^^ | ``` @@ -322,8 +324,10 @@ note: This is an unsafe fix and may change runtime behavior ``` error[invalid-overload]: `@final` decorator should be applied only to the overload implementation - --> src/main.py:24:5 + --> src/main.py:23:5 | +23 | @overload + | --------- 24 | @final | ------ 25 | def f(self, x: str) -> str: ... # error: [invalid-overload] @@ -343,6 +347,7 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo 31 | @final | ------ 32 | @overload + | --------- 33 | def g(self, x: str) -> str: ... # error: [invalid-overload] | ^ 34 | @overload @@ -355,8 +360,10 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo ``` error[invalid-overload]: `@final` decorator should be applied only to the overload implementation - --> src/main.py:42:5 + --> src/main.py:41:5 | +41 | @overload + | --------- 42 | @final | ------ 43 | def h(self, x: int) -> int: ... # error: [invalid-overload] @@ -374,6 +381,7 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo 49 | @final | ------ 50 | @overload + | --------- 51 | def i(self, x: int) -> int: ... # error: [invalid-overload] | ^ 52 | def i(self, x: int | str) -> int | str: diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" index e816a56a2b91a..d9ebde546e086 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_A_method_call_with_u\342\200\246_(31cb5f881221158e).snap" @@ -37,10 +37,11 @@ error[no-matching-overload]: No overload of bound method `Foo.bar` matches argum | ^^^^^^^^^^^^^^^ | info: First overload defined here - --> src/mdtest_snippet.py:5:9 + --> src/mdtest_snippet.py:4:5 | -5 | def bar(self, x: int) -> int: ... - | ^^^^^^^^^^^^^^^^^^^^^^^^ +4 | / @overload +5 | | def bar(self, x: int) -> int: ... + | |_____________________________________^ First overload defined here | info: Possible overloads for bound method `bar`: info: (self, x: int) -> int diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" index 82bd2e9828655..589d5e1da11ec 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(dd80c593d9136f35).snap" @@ -74,10 +74,11 @@ error[no-matching-overload]: No overload of function `foo` matches arguments | ^^^^^^^^^^^^^^^^^ | info: First overload defined here - --> src/mdtest_snippet.py:6:5 + --> src/mdtest_snippet.py:5:1 | -6 | def foo(a: int, b: int, c: int): ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +5 | / @overload +6 | | def foo(a: int, b: int, c: int): ... + | |____________________________________^ First overload defined here | info: Possible overloads for function `foo`: info: (a: int, b: int, c: int) -> Unknown diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" index 4d6fc88ca0a3b..af1d883c02e3f 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Call_to_function_wit\342\200\246_(f66e3a8a3977c472).snap" @@ -154,10 +154,11 @@ error[no-matching-overload]: No overload of function `foo` matches arguments | ^^^^^^^^^^^^^^^^^ | info: First overload defined here - --> src/mdtest_snippet.py:6:5 + --> src/mdtest_snippet.py:5:1 | -6 | def foo(a: int, b: int, c: int): ... - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +5 | / @overload +6 | | def foo(a: int, b: int, c: int): ... + | |____________________________________^ First overload defined here | info: Possible overloads for function `foo`: info: (a: int, b: int, c: int) -> Unknown diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" index 6813eff250181..3f5f5f7b277b4 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(3553d085684e16a0).snap" @@ -35,10 +35,11 @@ error[no-matching-overload]: No overload of function `f` matches arguments | ^^^^^^^^^ | info: First overload defined here - --> src/mdtest_snippet.py:4:5 + --> src/mdtest_snippet.py:3:1 | -4 | def f(x: int) -> int: ... - | ^^^^^^^^^^^^^^^^ +3 | / @overload +4 | | def f(x: int) -> int: ... + | |_________________________^ First overload defined here | info: Possible overloads for function `f`: info: (x: int) -> int diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" index 3bb2a264ed649..2fce9477dc835 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/no_matching_overload\342\200\246_-_No_matching_overload\342\200\246_-_Calls_to_overloaded_\342\200\246_(36814b28492c01d2).snap" @@ -86,10 +86,10 @@ error[no-matching-overload]: No overload of function `f` matches arguments | ^^^^^^^^^ | info: First overload defined here - --> src/mdtest_snippet.py:4:5 + --> src/mdtest_snippet.py:3:1 | - 4 | def f( - | _____^ + 3 | / @overload + 4 | | def f( 5 | | lion: int, 6 | | turtle: int, 7 | | tortoise: int, @@ -107,7 +107,7 @@ info: First overload defined here 19 | | leopard: int, 20 | | hyena: int, 21 | | ) -> int: ... - | |________^ + | |_____________^ First overload defined here | info: Possible overloads for function `f`: info: (lion: int, turtle: int, tortoise: int, goat: int, capybara: int, chicken: int, ostrich: int, gorilla: int, giraffe: int, condor: int, kangaroo: int, anaconda: int, tarantula: int, millipede: int, leopard: int, hyena: int) -> int diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloa\342\200\246_(84dadf8abd8f2f2).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloa\342\200\246_(84dadf8abd8f2f2).snap" index 80532b781a56a..1ab3a764ef52e 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloa\342\200\246_(84dadf8abd8f2f2).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_At_least_two_overloa\342\200\246_(84dadf8abd8f2f2).snap" @@ -36,8 +36,11 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md ``` error[invalid-overload]: Overloaded function `func` requires at least two overloads - --> src/mdtest_snippet.py:5:5 + --> src/mdtest_snippet.py:3:1 | +3 | @overload + | --------- +4 | # error: [invalid-overload] 5 | def func(x: int) -> int: ... | ^^^^ Only one overload defined here | @@ -46,8 +49,11 @@ error[invalid-overload]: Overloaded function `func` requires at least two overlo ``` error[invalid-overload]: Overloaded function `func` requires at least two overloads - --> src/mdtest_snippet.pyi:5:5 + --> src/mdtest_snippet.pyi:3:1 | +3 | @overload + | --------- +4 | # error: [invalid-overload] 5 | def func(x: int) -> int: ... | ^^^^ Only one overload defined here | diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@classmethod`_(aaa04d4cfa3adaba).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@classmethod`_(aaa04d4cfa3adaba).snap" index a4d5da2d29351..c82a4d44671b0 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@classmethod`_(aaa04d4cfa3adaba).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@classmethod`_(aaa04d4cfa3adaba).snap" @@ -75,8 +75,10 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md ``` error[invalid-overload]: Overloaded function `try_from1` does not use the `@classmethod` decorator consistently - --> src/mdtest_snippet.py:13:9 + --> src/mdtest_snippet.py:12:5 | +12 | @overload + | --------- 13 | def try_from1(cls, x: str) -> None: ... | --------- Missing here 14 | @classmethod @@ -94,8 +96,10 @@ error[invalid-overload]: Overloaded function `try_from2` does not use the `@clas 28 | def try_from2(cls, x: int | str) -> CheckClassMethod | None: | ^^^^^^^^^ | - ::: src/mdtest_snippet.py:22:9 + ::: src/mdtest_snippet.py:21:5 | +21 | @overload + | --------- 22 | def try_from2(cls, x: int) -> CheckClassMethod: ... | --------- Missing here | diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@final`_(f8e529ec23a61665).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@final`_(f8e529ec23a61665).snap" index e69c31f45020b..e8c70eabb2784 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@final`_(f8e529ec23a61665).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@final`_(f8e529ec23a61665).snap" @@ -76,8 +76,10 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md ``` error[invalid-overload]: `@final` decorator should be applied only to the overload implementation - --> src/mdtest_snippet.py:13:5 + --> src/mdtest_snippet.py:12:5 | +12 | @overload + | --------- 13 | @final | ------ 14 | # error: [invalid-overload] @@ -93,8 +95,10 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo ``` error[invalid-overload]: `@final` decorator should be applied only to the overload implementation - --> src/mdtest_snippet.py:24:5 + --> src/mdtest_snippet.py:23:5 | +23 | @overload + | --------- 24 | @final | ------ 25 | # error: [invalid-overload] @@ -108,49 +112,52 @@ error[invalid-overload]: `@final` decorator should be applied only to the overlo ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/mdtest_snippet.pyi:10:9 + --> src/mdtest_snippet.pyi:9:5 | -10 | def method2(self, x: int) -> int: ... - | ------- First overload defined here -11 | @final - | ------ -12 | @overload -13 | # error: [invalid-overload] -14 | def method2(self, x: str) -> str: ... - | ^^^^^^^ + 9 | / @overload +10 | | def method2(self, x: int) -> int: ... + | |_________________________________________- First overload defined here +11 | @final + | ------ +12 | @overload +13 | # error: [invalid-overload] +14 | def method2(self, x: str) -> str: ... + | ^^^^^^^ | ``` ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/mdtest_snippet.pyi:16:9 + --> src/mdtest_snippet.pyi:15:5 | -16 | def method3(self, x: int) -> int: ... - | ------- First overload defined here -17 | @final - | ------ -18 | @overload -19 | def method3(self, x: str) -> int: ... # error: [invalid-overload] - | ^^^^^^^ +15 | / @overload +16 | | def method3(self, x: int) -> int: ... + | |_________________________________________- First overload defined here +17 | @final + | ------ +18 | @overload +19 | def method3(self, x: str) -> int: ... # error: [invalid-overload] + | ^^^^^^^ | ``` ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/mdtest_snippet.pyi:16:9 + --> src/mdtest_snippet.pyi:15:5 | -16 | def method3(self, x: int) -> int: ... - | ------- First overload defined here -17 | @final -18 | @overload -19 | def method3(self, x: str) -> int: ... # error: [invalid-overload] -20 | @overload -21 | @final - | ------ -22 | def method3(self, x: bytes) -> bytes: ... # error: [invalid-overload] - | ^^^^^^^ +15 | / @overload +16 | | def method3(self, x: int) -> int: ... + | |_________________________________________- First overload defined here +17 | @final +18 | @overload +19 | def method3(self, x: str) -> int: ... # error: [invalid-overload] +20 | @overload +21 | @final + | ------ +22 | def method3(self, x: bytes) -> bytes: ... # error: [invalid-overload] + | ^^^^^^^ | ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@override`_(2df210735ca532f9).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@override`_(2df210735ca532f9).snap" index 99adaf5256dc1..9f163a71a4934 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@override`_(2df210735ca532f9).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/overloads.md_-_Overloads_-_Invalid_-_Inconsistent_decorat\342\200\246_-_`@override`_(2df210735ca532f9).snap" @@ -84,8 +84,10 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md ``` error[invalid-overload]: `@override` decorator should be applied only to the overload implementation - --> src/mdtest_snippet.py:24:5 + --> src/mdtest_snippet.py:23:5 | +23 | @overload + | --------- 24 | @override | --------- 25 | # error: [invalid-overload] @@ -99,8 +101,10 @@ error[invalid-overload]: `@override` decorator should be applied only to the ove ``` error[invalid-overload]: `@override` decorator should be applied only to the overload implementation - --> src/mdtest_snippet.py:32:5 + --> src/mdtest_snippet.py:31:5 | +31 | @overload + | --------- 32 | @override | --------- 33 | # error: [invalid-overload] @@ -116,16 +120,17 @@ error[invalid-overload]: `@override` decorator should be applied only to the ove ``` error[invalid-overload]: `@override` decorator should be applied only to the first overload - --> src/mdtest_snippet.pyi:18:9 + --> src/mdtest_snippet.pyi:17:5 | -18 | def method(self, x: int) -> int: ... - | ------ First overload defined here -19 | @overload -20 | @override - | --------- -21 | # error: [invalid-overload] -22 | def method(self, x: str) -> str: ... - | ^^^^^^ +17 | / @overload +18 | | def method(self, x: int) -> int: ... + | |________________________________________- First overload defined here +19 | @overload +20 | @override + | --------- +21 | # error: [invalid-overload] +22 | def method(self, x: str) -> str: ... + | ^^^^^^ | ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_non-keyword_re\342\200\246_(707b284610419a54).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_non-keyword_re\342\200\246_(707b284610419a54).snap" index 4fda6b6a3cd60..65886dd963871 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_non-keyword_re\342\200\246_(707b284610419a54).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f\342\200\246_-_Try_to_cover_all_pos\342\200\246_-_Cover_non-keyword_re\342\200\246_(707b284610419a54).snap" @@ -121,10 +121,11 @@ error[no-matching-overload]: No overload of function `f6` matches arguments | ^^^^ | info: First overload defined here - --> src/mdtest_snippet.py:24:5 + --> src/mdtest_snippet.py:23:1 | -24 | def f6() -> None: ... - | ^^^^^^^^^^^^ +23 | / @overload +24 | | def f6() -> None: ... + | |_____________________^ First overload defined here | info: Possible overloads for function `f6`: info: () -> None diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 9c0d28f37e473..0d7cf02cb1467 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -58,7 +58,7 @@ use crate::types::{ TypeVarVariance, UnionBuilder, UnionType, WrapperDescriptorKind, enums, list_members, }; use crate::{DisplaySettings, FxOrderSet, Program}; -use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity}; +use ruff_db::diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity}; use ruff_python_ast::{self as ast, AnyNodeRef, ArgOrKeyword, PythonVersion}; use ty_module_resolver::KnownModule; use ty_python_core::scope::NodeWithScopeKind; @@ -3518,7 +3518,25 @@ impl<'db> CallableBinding<'db> { SubDiagnosticSeverity::Info, "First overload defined here", ); - sub.annotate(Annotation::primary(overload.spans(context.db()).signature)); + let file = function.file(context.db()); + let module = parsed_module(context.db(), file).load(context.db()); + let node = + overload.node(context.db(), function.file(context.db()), &module); + let range = if node.body.len() == 1 { + node.range() + } else { + TextRange::new( + node.start(), + node.returns + .as_deref() + .map(Ranged::end) + .unwrap_or_else(|| node.parameters.end()), + ) + }; + sub.annotate( + Annotation::primary(Span::from(file).with_range(range)) + .message("First overload defined here"), + ); diag.sub(sub); } diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index ba182a1900d7d..fdd86b1dc9c84 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -30,9 +30,10 @@ use crate::types::{ ProtocolInstanceType, SpecialFormType, SubclassOfInner, Type, TypeContext, TypeVarVariance, binding_type, protocol_class::ProtocolClass, }; -use crate::types::{KnownInstanceType, MemberLookupPolicy, UnionType}; +use crate::types::{KnownInstanceType, MemberLookupPolicy, TypedDictType, UnionType}; use crate::{Db, DisplaySettings, FxIndexMap, Program, declare_lint}; use itertools::Itertools; +use ruff_db::source::source_text; use ruff_db::{ diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity}, parsed::parsed_module, @@ -41,6 +42,7 @@ use ruff_diagnostics::{Edit, Fix, IsolationLevel}; use ruff_python_ast::name::Name; use ruff_python_ast::token::parentheses_iterator; use ruff_python_ast::{self as ast, AnyNodeRef, HasNodeIndex, PythonVersion, StringFlags}; +use ruff_source_file::LineRanges; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::FxHashSet; use std::fmt::{self, Formatter}; @@ -4833,13 +4835,51 @@ pub(crate) fn report_call_to_abstract_method( diag.set_primary_message(format_args!( "`{name}` is an abstract {method_kind} with a trivial body" )); - let spans = function.spans(db); - let mut sub = SubDiagnostic::new( - SubDiagnosticSeverity::Info, - format_args!("Method `{name}` defined here"), + let span = abstract_method_span( + db, + function, + AbstractMethodAnnotationPolicy::AlwaysIncludeBody, + ); + diag.annotate( + Annotation::secondary(span).message(format_args!("Method `{name}` defined here")), ); - sub.annotate(Annotation::primary(spans.name)); - diag.sub(sub); +} + +pub(super) fn abstract_method_span<'db>( + db: &'db dyn Db, + function: FunctionType<'db>, + policy: AbstractMethodAnnotationPolicy, +) -> Span { + let (_, implementation) = function.overloads_and_implementation(db); + + let Some(implementation) = implementation else { + return function.spans(db).name; + }; + + let file = function.file(db); + let module = parsed_module(db, file).load(db); + let node = implementation.node(db, file, &module); + let source_text = source_text(db, file); + + if policy == AbstractMethodAnnotationPolicy::ExcludeVerboseBody + && source_text.line_start(node.name.end()) != source_text.line_start(node.end()) + { + return implementation.spans(db).decorators_and_header; + } + + if let [single_stmt] = &*node.body + && source_text.line_start(single_stmt.start()) == source_text.line_start(single_stmt.end()) + { + Span::from(file).with_range(node.range()) + } else { + implementation.spans(db).decorators_and_header + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(super) enum AbstractMethodAnnotationPolicy { + AlwaysIncludeBody, + ExcludeVerboseBody, } pub(crate) fn report_undeclared_protocol_member( @@ -5344,7 +5384,7 @@ pub(crate) enum TypedDictDeleteErrorKind { pub(crate) fn report_cannot_delete_typed_dict_key<'db>( context: &InferContext<'db, '_>, key_node: AnyNodeRef, - typed_dict_ty: Type<'db>, + typed_dict_ty: TypedDictType<'db>, field_name: &str, field: Option<&crate::types::typed_dict::TypedDictField<'db>>, error_kind: TypedDictDeleteErrorKind, @@ -5354,7 +5394,7 @@ pub(crate) fn report_cannot_delete_typed_dict_key<'db>( return; }; - let typed_dict_name = typed_dict_ty.display(db); + let typed_dict_name = Type::TypedDict(typed_dict_ty).display(db); let mut diagnostic = match error_kind { TypedDictDeleteErrorKind::RequiredKey => builder.into_diagnostic(format_args!( @@ -5373,14 +5413,27 @@ pub(crate) fn report_cannot_delete_typed_dict_key<'db>( let module = parsed_module(db, file).load(db); let mut sub = SubDiagnostic::new(SubDiagnosticSeverity::Info, "Field defined here"); - sub.annotate( - Annotation::secondary( - Span::from(file).with_range(declaration.full_range(db, &module).range()), - ) - .message(format_args!( - "`{field_name}` declared as required here; consider making it `NotRequired`" - )), - ); + for message in [ + format_args!("`{field_name}` declared as required here"), + format_args!("Consider making it `NotRequired`"), + ] { + sub.annotate( + Annotation::secondary( + Span::from(file).with_range(declaration.full_range(db, &module).range()), + ) + .message(message), + ); + } + + if let Some(class) = typed_dict_ty.defining_class() { + sub.annotate( + Annotation::secondary( + Span::from(file).with_range(class.class_literal(db).header_range(db)), + ) + .message(format_args!("`{}` defined here", class.name(db))), + ); + } + diagnostic.sub(sub); } @@ -6329,6 +6382,7 @@ pub(super) fn report_invalid_total_ordering( "`{}` does not define `__lt__`, `__le__`, `__gt__`, or `__ge__`", class.name(db) )); + diagnostic.annotate(context.secondary(class.header_range(db))); diagnostic.info("The decorator will raise `ValueError` at runtime"); } diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index dab6ab0f9eecd..8c93a8d114b15 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -109,6 +109,10 @@ pub(crate) struct FunctionSpans { pub(crate) parameters: Span, /// The span of the annotated return type, if present. pub(crate) return_type: Option, + /// A span that starts at the beginning of the first decorator (if any), + /// and ends at the end of the function signature (either the last parameter, + /// or the return type if present). + pub(crate) decorators_and_header: Span, } bitflags! { @@ -642,6 +646,7 @@ impl<'db> OverloadLiteral<'db> { name: span.clone().with_range(func_def.name.range), parameters: span.clone().with_range(func_def.parameters.range), return_type: return_type_range.map(|range| span.clone().with_range(range)), + decorators_and_header: span.with_range(signature.cover_offset(func_def.start())), } } } diff --git a/crates/ty_python_semantic/src/types/infer/builder/post_inference/overloaded_function.rs b/crates/ty_python_semantic/src/types/infer/builder/post_inference/overloaded_function.rs index 565d40c69b3d4..99bdff20c4d71 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/post_inference/overloaded_function.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/post_inference/overloaded_function.rs @@ -1,4 +1,7 @@ -use ruff_db::diagnostic::Annotation; +use ruff_db::{ + diagnostic::{Annotation, Span}, + parsed::parsed_module, +}; use ruff_text_size::Ranged; use rustc_hash::FxHashSet; @@ -92,6 +95,11 @@ pub(crate) fn check_overloaded_function<'db>( &function_node.name )); diagnostic.set_primary_message("Only one overload defined here"); + if let Some(decorator) = + single_overload.find_known_decorator_span(db, KnownFunction::Overload) + { + diagnostic.annotate(Annotation::secondary(decorator)); + } } } @@ -130,9 +138,10 @@ pub(crate) fn check_overloaded_function<'db>( let function_node = overloads[0].node(db, context.file(), context.module()); if let Some(builder) = context.report_lint(&INVALID_OVERLOAD, &function_node.name) { let mut diagnostic = builder.into_diagnostic(format_args!( - "Overloads for function `{}` must be followed by a non-`@overload`-decorated implementation function", - &function_node.name - )); + "Overloads for function `{}` must be followed by a \ + non-`@overload`-decorated implementation function", + &function_node.name + )); diagnostic.info(format_args!( "Attempting to call `{}` will raise `TypeError` at runtime", &function_node.name @@ -178,7 +187,7 @@ pub(crate) fn check_overloaded_function<'db>( if let Some(builder) = context.report_lint(&INVALID_OVERLOAD, &function_node.name) { let mut diagnostic = builder.into_diagnostic(format_args!( "Overloaded function `{}` does not use the `@{name}` decorator \ - consistently", + consistently", &function_node.name )); for function in decorator_missing { @@ -187,11 +196,16 @@ pub(crate) fn check_overloaded_function<'db>( .secondary(function.focus_range(db, context.module())) .message(format_args!("Missing here")), ); + if let Some(decorator) = + function.find_known_decorator_span(db, KnownFunction::Overload) + { + diagnostic.annotate(Annotation::secondary(decorator)); + } } } } - for (function, decorator) in [ + for (known_function, decorator) in [ (KnownFunction::Final, FunctionDecorators::FINAL), (KnownFunction::Override, FunctionDecorators::OVERRIDE), ] { @@ -207,11 +221,14 @@ pub(crate) fn check_overloaded_function<'db>( }; let mut diagnostic = builder.into_diagnostic(format_args!( "`@{name}` decorator should be applied only to the \ - overload implementation", - name = function.name() + overload implementation", + name = known_function.name() )); - if let Some(decorator) = overload.find_known_decorator_span(db, function) { - diagnostic.annotate(Annotation::secondary(decorator)); + for known_function in [known_function, KnownFunction::Overload] { + if let Some(decorator) = overload.find_known_decorator_span(db, known_function) + { + diagnostic.annotate(Annotation::secondary(decorator)); + } } diagnostic.annotate( context @@ -235,15 +252,22 @@ pub(crate) fn check_overloaded_function<'db>( }; let mut diagnostic = builder.into_diagnostic(format_args!( "`@{name}` decorator should be applied only to the \ - first overload", - name = function.name() + first overload", + name = known_function.name() )); - if let Some(decorator) = overload.find_known_decorator_span(db, function) { + if let Some(decorator) = overload.find_known_decorator_span(db, known_function) { diagnostic.annotate(Annotation::secondary(decorator)); } + let file = function.file(db); + let module = parsed_module(db, file).load(db); + let node = first_overload.node(db, file, &module); + let span = if node.body.len() == 1 { + Span::from(file).with_range(node.range()) + } else { + first_overload.spans(db).decorators_and_header + }; diagnostic.annotate( - context - .secondary(first_overload.focus_range(db, context.module())) + Annotation::secondary(span) .message(format_args!("First overload defined here")), ); } diff --git a/crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs b/crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs index 63690e3275b4d..89c3e4b89c710 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/post_inference/static_class.rs @@ -1,7 +1,6 @@ use itertools::Itertools; use ruff_db::{ - diagnostic::{Annotation, Span, SubDiagnostic, SubDiagnosticSeverity}, - parsed::parsed_module, + diagnostic::{Annotation, SubDiagnostic, SubDiagnosticSeverity}, source::source_text, }; use ruff_diagnostics::{Edit, Fix}; @@ -9,7 +8,6 @@ use ruff_python_ast as ast; use ruff_text_size::{Ranged, TextRange, TextSize}; use rustc_hash::FxHashMap; -use crate::attribute_assignments; use crate::{ TypeQualifiers, diagnostic::format_enumeration, @@ -17,7 +15,7 @@ use crate::{ types::{ CallArguments, ClassBase, ClassLiteral, ClassType, GenericAlias, KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, Parameters, Signature, SpecialFormType, - StaticClassLiteral, Type, + StaticClassLiteral, Type, binding_type, call::Argument, class::{ AbstractMethod, CodeGeneratorKind, FieldKind, MetaclassErrorKind, @@ -26,12 +24,12 @@ use crate::{ context::InferContext, definition_expression_type, diagnostic::{ - ABSTRACT_METHOD_IN_FINAL_CLASS, CONFLICTING_METACLASS, CYCLIC_CLASS_DEFINITION, - DATACLASS_FIELD_ORDER, DUPLICATE_KW_ONLY, FINAL_WITHOUT_VALUE, INCONSISTENT_MRO, - INVALID_ARGUMENT_TYPE, INVALID_BASE, INVALID_DATACLASS, INVALID_GENERIC_CLASS, - INVALID_GENERIC_ENUM, INVALID_METACLASS, INVALID_NAMED_TUPLE, INVALID_PROTOCOL, - INVALID_TYPED_DICT_HEADER, IncompatibleBases, SUBCLASS_OF_FINAL_CLASS, - UNKNOWN_ARGUMENT, report_bad_frozen_dataclass_inheritance, + ABSTRACT_METHOD_IN_FINAL_CLASS, AbstractMethodAnnotationPolicy, CONFLICTING_METACLASS, + CYCLIC_CLASS_DEFINITION, DATACLASS_FIELD_ORDER, DUPLICATE_KW_ONLY, FINAL_WITHOUT_VALUE, + INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE, INVALID_BASE, INVALID_DATACLASS, + INVALID_GENERIC_CLASS, INVALID_GENERIC_ENUM, INVALID_METACLASS, INVALID_NAMED_TUPLE, + INVALID_PROTOCOL, INVALID_TYPED_DICT_HEADER, IncompatibleBases, + SUBCLASS_OF_FINAL_CLASS, UNKNOWN_ARGUMENT, report_bad_frozen_dataclass_inheritance, report_conflicting_metaclass_from_bases, report_duplicate_bases, report_instance_layout_conflict, report_invalid_or_unsupported_base, report_invalid_total_ordering, report_invalid_type_param_order, @@ -53,6 +51,7 @@ use crate::{ visitor::find_over_type, }, }; +use crate::{attribute_assignments, types::diagnostic::abstract_method_span}; use ty_python_core::{SemanticIndex, definition::DefinitionKind, scope::ScopeId}; /// Iterate over all static class definitions (created using `class` statements) to check that @@ -1095,6 +1094,23 @@ fn check_final_class_abstract_methods<'db>( "Final class `{class_name}` has unimplemented abstract methods", )); + let definition_types = infer_definition_types(db, class.definition(db)); + + if let Some(class_node) = class.body_scope(db).node(db).as_class() + && let Some(decorator) = class_node + .node(context.module()) + .decorator_list + .iter() + .find(|decorator| { + definition_types + .expression_type(&decorator.expression) + .as_function_literal() + .is_some_and(|function| function.is_known(db, KnownFunction::Final)) + }) + { + diagnostic.annotate(context.secondary(decorator)); + } + let num_abstract_methods = abstract_methods.len(); if num_abstract_methods == 1 { @@ -1139,19 +1155,25 @@ fn check_final_class_abstract_methods<'db>( kind, } = abstract_method; - let module = parsed_module(db, definition.file(db)).load(db); - let span = Span::from(definition.focus_range(db, &module)); let defining_class_name = defining_class.name(db); - let mut secondary_annotation = Annotation::secondary(span); - secondary_annotation = if defining_class.class_literal(db) == ClassLiteral::Static(class) { - secondary_annotation.message(format_args!("`{first_method_name}` declared as abstract")) - } else { - secondary_annotation.message(format_args!( - "`{first_method_name}` declared as abstract on superclass `{defining_class_name}`", - )) - }; - diagnostic.annotate(secondary_annotation); + if let Type::FunctionLiteral(function) = binding_type(db, *definition) { + let policy = if kind.is_explicit() { + AbstractMethodAnnotationPolicy::ExcludeVerboseBody + } else { + AbstractMethodAnnotationPolicy::AlwaysIncludeBody + }; + let secondary_span = abstract_method_span(db, function, policy); + let mut secondary_annotation = Annotation::secondary(secondary_span); + secondary_annotation = if defining_class.class_literal(db) == ClassLiteral::Static(class) { + secondary_annotation.message(format_args!("`{first_method_name}` declared as abstract")) + } else { + secondary_annotation.message(format_args!( + "`{first_method_name}` declared as abstract on superclass `{defining_class_name}`", + )) + }; + diagnostic.annotate(secondary_annotation); + } if !kind.is_explicit() { let mut sub = SubDiagnostic::new( diff --git a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs index e3fb5f245900f..a6f3324cc3153 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs @@ -1599,7 +1599,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { report_cannot_delete_typed_dict_key( &self.context, (&*target.slice).into(), - object_ty, + typed_dict, key, Some(field), TypedDictDeleteErrorKind::RequiredKey, @@ -1609,7 +1609,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { report_cannot_delete_typed_dict_key( &self.context, (&*target.slice).into(), - object_ty, + typed_dict, key, None, TypedDictDeleteErrorKind::UnknownKey,