From 07d354e3291fedfeab0bbe790e067602ba095168 Mon Sep 17 00:00:00 2001 From: Amethyst Reese Date: Mon, 2 Feb 2026 13:51:32 -0800 Subject: [PATCH 1/4] Replace blacken-docs in prek config --- .pre-commit-config.yaml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbf15a5d8d8a9..a041de36b0b7c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -115,20 +115,23 @@ repos: )$ priority: 1 - # Priority 2: blacken-docs runs after markdownlint-fix (both modify markdown). - - repo: https://github.com/adamchainz/blacken-docs - rev: 1.20.0 + # Priority 2: ruffen-docs runs after markdownlint-fix (both modify markdown). + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.1 hooks: - - id: blacken-docs - language: python # means renovate will also update `additional_dependencies` - args: ["--pyi", "--line-length", "130"] - files: '^crates/.*/resources/mdtest/.*\.md' - exclude: | - (?x)^( - .*?invalid(_.+)*_syntax\.md - )$ - additional_dependencies: - - black==26.1.0 + - id: ruff-format + name: mdtest format + args: + [ + "--preview", + "--line-length", + "130", + "--extension", + "py:pyi,python:pyi", + ] + types_or: [markdown] + files: '^crates/.*/resources/mdtest/.*\.md$' + pass_filenames: true priority: 2 # `actionlint` hook, for verifying correct syntax in GitHub Actions workflows. From 8d64e076afcd098ef9ecea1f1d578f6b7a644251 Mon Sep 17 00:00:00 2001 From: Amethyst Reese Date: Mon, 2 Feb 2026 14:07:12 -0800 Subject: [PATCH 2/4] Skip formatting some intentional pieces --- .../ty_python_semantic/resources/mdtest/annotations/self.md | 2 +- .../resources/mdtest/annotations/string.md | 4 ++-- crates/ty_python_semantic/resources/mdtest/import/star.md | 2 +- crates/ty_python_semantic/resources/mdtest/literal/bytes.md | 2 +- .../ty_python_semantic/resources/mdtest/literal/f_string.md | 6 +++--- .../resources/mdtest/suppressions/type_ignore.md | 1 + 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 8a6332305d535..1113bdf137ed2 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -860,7 +860,7 @@ bound at `C.f`. from typing import Self from ty_extensions import generic_context -class C[T](): +class C[T](): # fmt:skip def f(self: Self): def b(x: Self): reveal_type(x) # revealed: Self@f diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/string.md b/crates/ty_python_semantic/resources/mdtest/annotations/string.md index 29e2b1034b42e..77131c310a98a 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/string.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/string.md @@ -87,7 +87,7 @@ def f1( h: """int""", # error: [byte-string-type-annotation] "Type expressions cannot use bytes literal" i: "b'int'", -): +): # fmt:skip reveal_type(a) # revealed: Unknown reveal_type(b) # revealed: Unknown reveal_type(c) # revealed: Unknown @@ -104,7 +104,7 @@ def f1( ```py from typing import Literal -def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]): +def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]): # fmt:skip reveal_type(v) # revealed: Literal["a", "b", "de", "f", "g", "h", b"c"] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/import/star.md b/crates/ty_python_semantic/resources/mdtest/import/star.md index 72c2a97e49ca9..a2246a8f4a158 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/star.md +++ b/crates/ty_python_semantic/resources/mdtest/import/star.md @@ -158,7 +158,7 @@ from collections import OrderedDict from collections import OrderedDict as Foo A, B = 1, (C := 2) -D: (E := 4) = (F := 5) # error: [invalid-type-form] +D: (E := 4) = (F := 5) # error: [invalid-type-form] # fmt:skip for G in [1]: ... diff --git a/crates/ty_python_semantic/resources/mdtest/literal/bytes.md b/crates/ty_python_semantic/resources/mdtest/literal/bytes.md index 391eff8ad5546..70f4975ce5fe2 100644 --- a/crates/ty_python_semantic/resources/mdtest/literal/bytes.md +++ b/crates/ty_python_semantic/resources/mdtest/literal/bytes.md @@ -3,7 +3,7 @@ ## Simple ```py -reveal_type(b"t" b"y") # revealed: Literal[b"ty"] +reveal_type(b"t" b"y") # revealed: Literal[b"ty"] # fmt:skip reveal_type(b"hello") # revealed: Literal[b"hello"] reveal_type(b"world" + b"!") # revealed: Literal[b"world!"] reveal_type(b"\xff\x00") # revealed: Literal[b"\xff\x00"] diff --git a/crates/ty_python_semantic/resources/mdtest/literal/f_string.md b/crates/ty_python_semantic/resources/mdtest/literal/f_string.md index ac51ba56d7c52..759f82a0827fd 100644 --- a/crates/ty_python_semantic/resources/mdtest/literal/f_string.md +++ b/crates/ty_python_semantic/resources/mdtest/literal/f_string.md @@ -8,10 +8,10 @@ from typing_extensions import Literal def _(x: Literal[0], y: str, z: Literal[False]): reveal_type(f"hello") # revealed: Literal["hello"] reveal_type(f"h {x}") # revealed: Literal["h 0"] - reveal_type("one " f"single " f"literal") # revealed: Literal["one single literal"] - reveal_type("first " f"second({x})" f" third") # revealed: Literal["first second(0) third"] + reveal_type("one " f"single " f"literal") # revealed: Literal["one single literal"] # fmt:skip + reveal_type("first " f"second({x})" f" third") # revealed: Literal["first second(0) third"] # fmt:skip reveal_type(f"-{y}-") # revealed: str - reveal_type(f"-{y}-" f"--" "--") # revealed: str + reveal_type(f"-{y}-" f"--" "--") # revealed: str # fmt:skip reveal_type(f"{z} == {False} is {True}") # revealed: Literal["False == False is True"] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md b/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md index 1b663ff33358d..bff808a2cd4d1 100644 --- a/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md +++ b/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md @@ -104,6 +104,7 @@ a = f""" For multiline-interpolation, put the ignore comment on the expression's start or end line: ```py +# fmt:off a = f""" { 10 / # type: ignore From 1fa117aef5be59ebc4ab8ed4168096d445a96b49 Mon Sep 17 00:00:00 2001 From: Amethyst Reese Date: Mon, 2 Feb 2026 14:19:25 -0800 Subject: [PATCH 3/4] Run new prek hook --- .../resources/mdtest/annotations/deferred.md | 2 +- .../mdtest/assignment/annotations.md | 2 +- .../resources/mdtest/assignment/augmented.md | 1 + .../resources/mdtest/call/abstract_method.md | 1 - .../resources/mdtest/call/constructor.md | 2 + .../resources/mdtest/call/dunder.md | 1 + .../resources/mdtest/call/overloads.md | 6 -- .../resources/mdtest/call/union.md | 2 + .../resources/mdtest/class/super.md | 2 + .../mdtest/conditional/if_statement.md | 4 +- .../mdtest/dataclasses/dataclass_transform.md | 12 ++++ .../mdtest/dataclasses/dataclasses.md | 1 + .../mdtest/diagnostics/invalid_await.md | 3 +- .../diagnostics/semantic_syntax_errors.md | 8 +-- .../resources/mdtest/exception/basic.md | 22 +++---- .../mdtest/exception/control_flow.md | 1 - .../resources/mdtest/expression/boolean.md | 8 +-- .../resources/mdtest/final.md | 32 ++++----- .../resources/mdtest/function/return_type.md | 7 +- .../mdtest/generics/legacy/variables.md | 1 + .../mdtest/generics/pep695/paramspec.md | 2 - .../resources/mdtest/import/conventions.md | 2 +- .../mdtest/import/nonstandard_conventions.md | 2 + .../resources/mdtest/import/star.md | 46 ++++++------- .../resources/mdtest/liskov.md | 3 +- .../resources/mdtest/loops/for.md | 12 ++++ .../resources/mdtest/loops/while_loop.md | 2 +- .../resources/mdtest/mro.md | 5 ++ .../resources/mdtest/named_tuple.md | 1 - .../resources/mdtest/narrow/type_guards.md | 11 ++-- .../resources/mdtest/overloads.md | 2 - .../resources/mdtest/override.md | 65 ++++++------------- .../resources/mdtest/protocols.md | 1 + .../resources/mdtest/public_types.md | 1 + .../resources/mdtest/stubs/class.md | 1 - .../resources/mdtest/subscript/class.md | 2 + .../resources/mdtest/subscript/instance.md | 1 + .../mdtest/suppressions/type_ignore.md | 2 +- .../type_properties/is_equivalent_to.md | 1 - .../mdtest/type_properties/is_subtype_of.md | 6 -- .../resources/mdtest/with/async.md | 16 ++--- .../resources/mdtest/with/sync.md | 16 ++--- 42 files changed, 154 insertions(+), 164 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/deferred.md b/crates/ty_python_semantic/resources/mdtest/annotations/deferred.md index 0a1f8ea912a87..9ddb633fee9c7 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/deferred.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/deferred.md @@ -6,6 +6,7 @@ ```pyi def get_foo() -> Foo: ... + class Foo: ... ``` @@ -256,7 +257,6 @@ Forward references in class keyword arguments are allowed in stub files. ```pyi class Foo(metaclass=SomeMeta): ... - class SomeMeta(type): ... ``` diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md index 06c786ee1e3d3..7d1f6b2e7dc0b 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md @@ -399,7 +399,7 @@ reveal_type(x) # revealed: Foo ```pyi x: int = 1 -reveal_type(x) # revealed: Literal[1] +reveal_type(x) # revealed: Literal[1] ``` ## Annotations influence generic call inference diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md b/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md index fc4af5a52e1ac..a572aa0bccf20 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/augmented.md @@ -60,6 +60,7 @@ def _(flag: bool): if flag: def __iadd__(self, other: int) -> str: return "Hello, world!" + else: def __iadd__(self, other: int) -> int: return 42 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 92bac8e198c01..0f8b0ddde6257 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/abstract_method.md +++ b/crates/ty_python_semantic/resources/mdtest/call/abstract_method.md @@ -225,7 +225,6 @@ class Foo(ABC): @classmethod @abstractmethod def classmethod(cls) -> int: ... - @staticmethod @abstractmethod def staticmethod() -> int: ... diff --git a/crates/ty_python_semantic/resources/mdtest/call/constructor.md b/crates/ty_python_semantic/resources/mdtest/call/constructor.md index 1df6827ebefd5..35507ef5bb5e2 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/constructor.md +++ b/crates/ty_python_semantic/resources/mdtest/call/constructor.md @@ -114,6 +114,7 @@ def _(flag: bool) -> None: class Foo: if flag: def __new__(cls, x: int): ... + else: def __new__(cls, x: int, y: int = 1): ... @@ -320,6 +321,7 @@ def _(flag: bool) -> None: class Foo: if flag: def __init__(self, x: int): ... + else: def __init__(self, x: int, y: int = 1): ... diff --git a/crates/ty_python_semantic/resources/mdtest/call/dunder.md b/crates/ty_python_semantic/resources/mdtest/call/dunder.md index 258702b6b615f..614caca5efbd3 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/dunder.md +++ b/crates/ty_python_semantic/resources/mdtest/call/dunder.md @@ -220,6 +220,7 @@ def _(flag: bool): if flag: def __getitem__(self, key: int) -> str: return str(key) + else: def __getitem__(self, key: int) -> bytes: return bytes() diff --git a/crates/ty_python_semantic/resources/mdtest/call/overloads.md b/crates/ty_python_semantic/resources/mdtest/call/overloads.md index d5b6228faa33c..9b4d6bfb631be 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/call/overloads.md @@ -428,7 +428,6 @@ class SomeEnum(Enum): B = 2 C = 3 - class A: ... class B: ... class C: ... @@ -1494,19 +1493,16 @@ class B: ... def f1(x: int) -> A: ... @overload def f1(x: Any, y: Any) -> A: ... - @overload def f2(x: int) -> A: ... @overload def f2(x: Any, y: Any) -> B: ... - @overload def f3(x: int) -> A: ... @overload def f3(x: Any, y: Any) -> A: ... @overload def f3(x: Any, y: Any, *, z: str) -> B: ... - @overload def f4(x: int) -> A: ... @overload @@ -1550,14 +1546,12 @@ def f1(x1: T1, x2: T2, /) -> tuple[T1, T2]: ... def f1(x1: T1, x2: T2, x3: T3, /) -> tuple[T1, T2, T3]: ... @overload def f1(*args: Any) -> tuple[Any, ...]: ... - @overload def f2(x1: T1) -> tuple[T1]: ... @overload def f2(x1: T1, x2: T2) -> tuple[T1, T2]: ... @overload def f2(*args: Any, **kwargs: Any) -> tuple[Any, ...]: ... - @overload def f3(x: T1) -> tuple[T1]: ... @overload diff --git a/crates/ty_python_semantic/resources/mdtest/call/union.md b/crates/ty_python_semantic/resources/mdtest/call/union.md index 871ac3b89e5f0..7052f44fa8ac4 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/union.md +++ b/crates/ty_python_semantic/resources/mdtest/call/union.md @@ -7,6 +7,7 @@ def _(flag: bool): if flag: def f() -> int: return 1 + else: def f() -> str: return "foo" @@ -859,6 +860,7 @@ def _(flag: bool): if flag: def f(x: T) -> int: return 1 + else: def f(x: dict[str, int]) -> int: return 1 diff --git a/crates/ty_python_semantic/resources/mdtest/class/super.md b/crates/ty_python_semantic/resources/mdtest/class/super.md index fbebf3d3ec93a..e449c46907bec 100644 --- a/crates/ty_python_semantic/resources/mdtest/class/super.md +++ b/crates/ty_python_semantic/resources/mdtest/class/super.md @@ -639,8 +639,10 @@ def coinflip() -> bool: def f(): if coinflip(): class A: ... + else: class A: ... + super(A, A()) # error: [invalid-super-argument] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/conditional/if_statement.md b/crates/ty_python_semantic/resources/mdtest/conditional/if_statement.md index f55dc41160a98..793222dff8886 100644 --- a/crates/ty_python_semantic/resources/mdtest/conditional/if_statement.md +++ b/crates/ty_python_semantic/resources/mdtest/conditional/if_statement.md @@ -156,8 +156,8 @@ class NotBoolable: # error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`" if NotBoolable(): - ... + pass # error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`" elif NotBoolable(): - ... + pass ``` diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md index 68335fd987cf1..f696cdcfef4c6 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclass_transform.md @@ -226,6 +226,7 @@ from dataclasses import field @dataclass_transform(kw_only_default=True) def create_model(*, kw_only: bool = True): ... + @create_model() class A: name: str @@ -282,6 +283,7 @@ from typing import dataclass_transform @dataclass_transform(frozen_default=True) def create_model(*, frozen: bool = True): ... + @create_model() class ImmutableModel: name: str @@ -339,6 +341,7 @@ from typing import dataclass_transform @dataclass_transform(eq_default=True, order_default=False, kw_only_default=True, frozen_default=True) def create_model(*, eq: bool = True, order: bool = False, kw_only: bool = True, frozen: bool = True): ... + @create_model(eq=False, order=True, kw_only=False, frozen=False) class OverridesAllParametersModel: name: str @@ -367,6 +370,7 @@ from typing import dataclass_transform @dataclass_transform(frozen_default=True) def default_frozen_model(*, frozen: bool = True, order: bool = False): ... + @default_frozen_model() class Frozen: name: str @@ -466,6 +470,7 @@ from typing import dataclass_transform @dataclass_transform(frozen_default=True) def frozen_model(*, frozen: bool = True): ... + @frozen_model() class FrozenWithOverrides: x: int @@ -497,6 +502,7 @@ from typing import dataclass_transform @dataclass_transform() def ordered_model(*, order: bool = False): ... + @ordered_model(order=True) class OrderedWithOverrides: x: int @@ -652,6 +658,7 @@ reveal_type(alice.age) # revealed: int | None from typing_extensions import dataclass_transform, Any def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ... + @dataclass_transform(field_specifiers=(fancy_field,)) class FancyMeta(type): def __new__(cls, name, bases, namespace): @@ -680,6 +687,7 @@ reveal_type(alice.age) # revealed: int | None from typing_extensions import dataclass_transform, Any def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ... + @dataclass_transform(field_specifiers=(fancy_field,)) class FancyBase: def __init_subclass__(cls): @@ -763,6 +771,7 @@ from typing import Any from typing_extensions import dataclass_transform def field(**kwargs: Any) -> Any: ... + @dataclass_transform(field_specifiers=(field,)) class ModelMeta(type): ... @@ -790,6 +799,7 @@ from typing import Any from typing_extensions import dataclass_transform def field(**kwargs: Any) -> Any: ... + @dataclass_transform(field_specifiers=(field,)) class ModelBase: ... @@ -1094,6 +1104,7 @@ class InvalidKWOnlyDefaultModel: from typing_extensions import Any, dataclass_transform def field(*, init: bool = True, kw_only: bool = False, default: Any = ...) -> Any: ... + @dataclass_transform(field_specifiers=(field,)) class ModelMeta(type): ... @@ -1140,6 +1151,7 @@ class InvalidKWOnlyDefaultModel(KWOnlyDefaultModelBase): from typing_extensions import Any, dataclass_transform def field(*, init: bool = True, kw_only: bool = False, default: Any = ...) -> Any: ... + @dataclass_transform(field_specifiers=(field,)) class ModelBase: def __init_subclass__(cls): diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md index e3a274759a8e3..f1e8d09fe8ad7 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md @@ -258,6 +258,7 @@ from ty_extensions import TypeOf class SomeClass: ... def some_function() -> None: ... + @dataclass class D: function_literal: TypeOf[some_function] diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md index 24f5f98c49266..e5a1cf9158439 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md @@ -92,11 +92,10 @@ from datetime import datetime class UnawaitableUnion: if datetime.today().weekday() == 6: - def __await__(self) -> typing.Generator[typing.Any, None, None]: yield - else: + else: def __await__(self) -> int: return 5 diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md index 3fe8d965a3750..0b1e5b69b990e 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md @@ -98,7 +98,7 @@ python-version = "3.10" match 2: # error: [invalid-syntax] "mapping pattern checks duplicate key `"x"`" case {"x": 1, "x": 2}: - ... + pass ``` ## Duplicate `match` class attribute @@ -224,7 +224,7 @@ def func(): def gen(): # error: [invalid-syntax] "Starred expression cannot be used here" - yield * [1, 2, 3] + yield *[1, 2, 3] # error: [invalid-syntax] "Starred expression cannot be used here" for *x in range(10): @@ -340,10 +340,10 @@ def _(): # error: [invalid-syntax] "`async for` outside of an asynchronous function" async for _ in elements(1): - ... + pass # error: [invalid-syntax] "`async with` outside of an asynchronous function" async with elements(1) as x: - ... + pass # error: [invalid-syntax] "asynchronous comprehension outside of an asynchronous function" [x async for x in elements(1)] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/exception/basic.md b/crates/ty_python_semantic/resources/mdtest/exception/basic.md index a284cefa4238f..aa48986743e0b 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/basic.md @@ -71,9 +71,9 @@ from compat import BASE_EXCEPTION_CLASS # error: [unresolved-import] "Cannot re class Error(BASE_EXCEPTION_CLASS): ... try: - ... + pass except Error as err: - ... + pass ``` ## Exception with no captured type @@ -158,22 +158,22 @@ except int: try: raise AttributeError() # fine except: - ... + pass try: raise FloatingPointError # fine except: - ... + pass try: raise 1 # error: [invalid-raise] except: - ... + pass try: raise int # error: [invalid-raise] except: - ... + pass def _(e: Exception | type[Exception]): raise e # fine @@ -189,31 +189,31 @@ def _(): try: raise EOFError() from GeneratorExit # fine except: - ... + pass def _(): try: raise StopIteration from MemoryError() # fine except: - ... + pass def _(): try: raise BufferError() from None # fine except: - ... + pass def _(): try: raise ZeroDivisionError from False # error: [invalid-raise] except: - ... + pass def _(): try: raise SystemExit from bool() # error: [invalid-raise] except: - ... + pass def _(): try: diff --git a/crates/ty_python_semantic/resources/mdtest/exception/control_flow.md b/crates/ty_python_semantic/resources/mdtest/exception/control_flow.md index 3cdc3d14cd351..249119ed7951b 100644 --- a/crates/ty_python_semantic/resources/mdtest/exception/control_flow.md +++ b/crates/ty_python_semantic/resources/mdtest/exception/control_flow.md @@ -572,7 +572,6 @@ def could_raise_returns_E() -> E: x = 1 try: - def foo(param=could_raise_returns_A()): x = could_raise_returns_A() diff --git a/crates/ty_python_semantic/resources/mdtest/expression/boolean.md b/crates/ty_python_semantic/resources/mdtest/expression/boolean.md index 67ba0f4f05194..5f2bf3c457229 100644 --- a/crates/ty_python_semantic/resources/mdtest/expression/boolean.md +++ b/crates/ty_python_semantic/resources/mdtest/expression/boolean.md @@ -223,7 +223,7 @@ class NotBoolable: # TODO: This should emit an error that `NotBoolable` can't be converted to a bool but it currently doesn't # because `Never` is assignable to `bool`. This probably requires dead code analysis to fix. if NotBoolable(): - ... + pass ``` ## Not callable `__bool__` @@ -234,7 +234,7 @@ class NotBoolable: # error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`" if NotBoolable(): - ... + pass ``` ## Not-boolable union @@ -246,7 +246,7 @@ def test(cond: bool): # error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`" if NotBoolable(): - ... + pass ``` ## Union with some variants implementing `__bool__` incorrectly @@ -260,5 +260,5 @@ def test(cond: bool): # error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `Literal[10] | NotBoolable`" if a: - ... + pass ``` diff --git a/crates/ty_python_semantic/resources/mdtest/final.md b/crates/ty_python_semantic/resources/mdtest/final.md index 75cd6510ff71a..970f9d38f6cad 100644 --- a/crates/ty_python_semantic/resources/mdtest/final.md +++ b/crates/ty_python_semantic/resources/mdtest/final.md @@ -42,39 +42,30 @@ def lossy_decorator(fn: Callable) -> Callable: ... class Parent: @final def foo(self): ... - @final @property def my_property1(self) -> int: ... - @property @final def my_property2(self) -> int: ... - @property @final def my_property3(self) -> int: ... - @final @classmethod def class_method1(cls) -> int: ... - @classmethod @final def class_method2(cls) -> int: ... - @final @staticmethod def static_method1() -> int: ... - @staticmethod @final def static_method2() -> int: ... - @lossy_decorator @final def decorated_1(self): ... - @final @lossy_decorator def decorated_2(self): ... @@ -87,31 +78,23 @@ class Child(Parent): def foo(self): ... @property def my_property1(self) -> int: ... # error: [override-of-final-method] - @property def my_property2(self) -> int: ... # error: [override-of-final-method] @my_property2.setter def my_property2(self, x: int) -> None: ... - @property def my_property3(self) -> int: ... # error: [override-of-final-method] @my_property3.deleter def my_proeprty3(self) -> None: ... - @classmethod def class_method1(cls) -> int: ... # error: [override-of-final-method] - @staticmethod def static_method1() -> int: ... # error: [override-of-final-method] - @classmethod def class_method2(cls) -> int: ... # error: [override-of-final-method] - @staticmethod def static_method2() -> int: ... # error: [override-of-final-method] - def decorated_1(self): ... # TODO: should emit [override-of-final-method] - @lossy_decorator def decorated_2(self): ... # TODO: should emit [override-of-final-method] @@ -207,7 +190,6 @@ class Good: def bar(self, x: str) -> str: ... @overload def bar(self, x: int) -> int: ... - @final @overload def baz(self, x: str) -> str: ... @@ -219,7 +201,6 @@ class ChildOfGood(Good): def bar(self, x: str) -> str: ... @overload def bar(self, x: int) -> int: ... # error: [override-of-final-method] - @overload def baz(self, x: str) -> str: ... @overload @@ -232,7 +213,6 @@ class Bad: @final # error: [invalid-overload] def bar(self, x: int) -> int: ... - @overload def baz(self, x: str) -> str: ... @final @@ -245,7 +225,6 @@ class ChildOfBad(Bad): def bar(self, x: str) -> str: ... @overload def bar(self, x: int) -> int: ... # error: [override-of-final-method] - @overload def baz(self, x: str) -> str: ... @overload @@ -477,11 +456,13 @@ class A: if coinflip(): @final def method1(self) -> None: ... + else: def method1(self) -> None: ... if coinflip(): def method2(self) -> None: ... + else: @final def method2(self) -> None: ... @@ -489,15 +470,18 @@ class A: if coinflip(): @final def method3(self) -> None: ... + else: @final def method3(self) -> None: ... if coinflip(): def method4(self) -> None: ... + elif coinflip(): @final def method4(self) -> None: ... + else: def method4(self) -> None: ... @@ -518,11 +502,13 @@ class B(A): class C(A): if coinflip(): def method1(self) -> None: ... # error: [override-of-final-method] + else: pass if coinflip(): def method2(self) -> None: ... # error: [override-of-final-method] + else: def method2(self) -> None: ... @@ -555,6 +541,7 @@ class Parent: def foooo(self) -> None: ... @final def baaaaar(self) -> None: ... + else: @final def bar(self) -> None: ... @@ -573,6 +560,7 @@ class Child(Parent): if sys.version_info >= (3, 10): def foooo(self) -> None: ... # error: [override-of-final-method] def baz(self) -> None: ... + else: # Fine because this doesn't override any reachable definitions def foooo(self) -> None: ... @@ -601,6 +589,7 @@ class Foo: @overload @final def method(self, x: int) -> int: ... + else: @overload def method(self, x: int) -> int: ... @@ -610,6 +599,7 @@ class Foo: if sys.version_info >= (3, 10): @overload def method2(self, x: int) -> int: ... + else: @overload @final diff --git a/crates/ty_python_semantic/resources/mdtest/function/return_type.md b/crates/ty_python_semantic/resources/mdtest/function/return_type.md index bb107e36b65c8..61fa6480db95b 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/return_type.md +++ b/crates/ty_python_semantic/resources/mdtest/function/return_type.md @@ -42,7 +42,6 @@ ellipsis (`...`) or `pass`. ```pyi def f() -> int: ... - def f() -> int: pass @@ -166,7 +165,7 @@ else: reveal_type(f) # revealed: def f() -> int if not TYPE_CHECKING: - ... + pass elif True: def g() -> str: ... @@ -183,7 +182,7 @@ else: reveal_type(i) # revealed: def i() -> str if False: - ... + pass elif TYPE_CHECKING: def j() -> str: ... @@ -191,7 +190,7 @@ else: def j_() -> str: ... # error: [empty-body] if False: - ... + pass elif not TYPE_CHECKING: def k_() -> str: ... # error: [empty-body] diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index 5a71df618ec2f..55bbd50076a08 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -503,6 +503,7 @@ maintain compatibility.) ```pyi from typing import TypeVar + T = TypeVar("T", default=int) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md index 6942b75f615ef..377faa922db08 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md @@ -649,12 +649,10 @@ from typing import overload def int_int(x: int) -> int: ... @overload def int_int(x: str) -> int: ... - @overload def int_str(x: int) -> int: ... @overload def int_str(x: str) -> str: ... - @overload def str_str(x: int) -> str: ... @overload diff --git a/crates/ty_python_semantic/resources/mdtest/import/conventions.md b/crates/ty_python_semantic/resources/mdtest/import/conventions.md index d2ec8320d535c..e00a52b3919fd 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/conventions.md +++ b/crates/ty_python_semantic/resources/mdtest/import/conventions.md @@ -212,7 +212,7 @@ reveal_type(Foo) # revealed: ```pyi from b import Foo -__all__ = ['Foo'] +__all__ = ["Foo"] ``` `b.pyi`: diff --git a/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md b/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md index e17a026e3257b..29852f1502fba 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md +++ b/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md @@ -551,6 +551,7 @@ decision that mostly fell out of the implementation details and can be changed!) ```pyi from . import imported + Z: int = 17 ``` @@ -719,6 +720,7 @@ re-export from `submodule`. ```pyi from . import fails + X: int = 42 ``` diff --git a/crates/ty_python_semantic/resources/mdtest/import/star.md b/crates/ty_python_semantic/resources/mdtest/import/star.md index a2246a8f4a158..4aa8c2766db07 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/star.md +++ b/crates/ty_python_semantic/resources/mdtest/import/star.md @@ -161,7 +161,7 @@ A, B = 1, (C := 2) D: (E := 4) = (F := 5) # error: [invalid-type-form] # fmt:skip for G in [1]: - ... + pass for (H := 4).whatever in [2]: # error: [unresolved-attribute] ... @@ -187,13 +187,13 @@ def get_object() -> object: match get_object(): case {"something": M}: - ... + pass case [*N]: - ... + pass case [O]: - ... + pass case I(foo=R): - ... + pass case P | Q: # error: [invalid-syntax] "alternative patterns bind different names" ... @@ -202,14 +202,14 @@ match 56: ... case object(S): - ... + pass match 12345: case x if something_unresolvable: # error: [unresolved-reference] ... case T: - ... + pass def boolean_condition() -> bool: return True @@ -283,25 +283,25 @@ K = 11 L = 12 for A in [1]: - ... + pass match 42: case {"something": B}: - ... + pass case [*C]: - ... + pass case [D]: - ... + pass # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable" # error: [invalid-syntax] "alternative patterns bind different names" case E | F: - ... + pass case object(foo=G): - ... + pass case object(H): - ... + pass case I: - ... + pass def boolean_condition() -> bool: return True @@ -353,25 +353,25 @@ might not take place, each symbol is definitely bound by a later definition. from typing import Literal for A in [1]: - ... + pass match 42: case {"something": B}: - ... + pass case [*C]: - ... + pass case [D]: - ... + pass # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable" # error: [invalid-syntax] "alternative patterns bind different names" case E | F: - ... + pass case object(foo=G): - ... + pass case object(H): - ... + pass case I: - ... + pass def boolean_condition() -> bool: return True diff --git a/crates/ty_python_semantic/resources/mdtest/liskov.md b/crates/ty_python_semantic/resources/mdtest/liskov.md index 9bc2d4af51337..8bbe34c239958 100644 --- a/crates/ty_python_semantic/resources/mdtest/liskov.md +++ b/crates/ty_python_semantic/resources/mdtest/liskov.md @@ -72,7 +72,7 @@ class Sub8(Super): def method(self, x: int, *args, **kwargs): ... # fine class Sub9(Super): - def method(self, x: int, extra_positional_arg=42, /): ... # fine + def method(self, x: int, extra_positional_arg=42, /): ... # fine class Sub10(Super): def method(self, x: int, extra_pos_or_kw_arg=42): ... # fine @@ -415,6 +415,7 @@ from dataclasses import dataclass, InitVar from typing_extensions import Self class Grandparent: ... + class Parent(Grandparent): def __new__(cls, x: int) -> Self: ... def __init__(self, x: int) -> None: ... diff --git a/crates/ty_python_semantic/resources/mdtest/loops/for.md b/crates/ty_python_semantic/resources/mdtest/loops/for.md index 4996029c2975b..84507af2be267 100644 --- a/crates/ty_python_semantic/resources/mdtest/loops/for.md +++ b/crates/ty_python_semantic/resources/mdtest/loops/for.md @@ -512,6 +512,7 @@ def _(flag: bool): if flag: def __call__(self, *args, **kwargs) -> Iterator: return Iterator() + else: __call__: None = None @@ -522,6 +523,7 @@ def _(flag: bool): if flag: def __iter__(self) -> Iterator: return Iterator() + else: __iter__: None = None @@ -713,6 +715,7 @@ def _(flag: bool): if flag: def __call__(self, *args, **kwargs) -> int: return 42 + else: __call__: None = None @@ -723,6 +726,7 @@ def _(flag: bool): if flag: def __getitem__(self, key: int) -> int: return 42 + else: __getitem__: None = None @@ -790,6 +794,7 @@ def _(flag: bool): if flag: def __iter__(self) -> Iterator: return Iterator() + else: def __iter__(self, invalid_extra_arg) -> Iterator: return Iterator() @@ -802,6 +807,7 @@ def _(flag: bool): if flag: def __iter__(self) -> Iterator: return Iterator() + else: __iter__: None = None @@ -821,6 +827,7 @@ def _(flag: bool): if flag: def __next__(self) -> int: return 42 + else: def __next__(self, invalid_extra_arg) -> str: return "foo" @@ -829,6 +836,7 @@ def _(flag: bool): if flag: def __next__(self) -> int: return 42 + else: __next__: None = None @@ -860,6 +868,7 @@ def _(flag: bool): if flag: def __getitem__(self, item: int) -> str: return "foo" + else: __getitem__: None = None @@ -867,6 +876,7 @@ def _(flag: bool): if flag: def __getitem__(self, item: int) -> str: return "foo" + else: def __getitem__(self, item: str) -> int: return 42 @@ -895,6 +905,7 @@ def _(flag: bool, flag2: bool): if flag: def __getitem__(self, item: int) -> str: return "foo" + else: __getitem__: None = None @@ -906,6 +917,7 @@ def _(flag: bool, flag2: bool): if flag: def __getitem__(self, item: int) -> str: return "foo" + else: def __getitem__(self, item: str) -> int: return 42 diff --git a/crates/ty_python_semantic/resources/mdtest/loops/while_loop.md b/crates/ty_python_semantic/resources/mdtest/loops/while_loop.md index a14f891d932f2..e917582c5804a 100644 --- a/crates/ty_python_semantic/resources/mdtest/loops/while_loop.md +++ b/crates/ty_python_semantic/resources/mdtest/loops/while_loop.md @@ -125,7 +125,7 @@ class NotBoolable: # error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`" while NotBoolable(): - ... + pass ``` ## Walrus definitions in the condition are always evaluated diff --git a/crates/ty_python_semantic/resources/mdtest/mro.md b/crates/ty_python_semantic/resources/mdtest/mro.md index 42d84ba575284..e53889a7d810b 100644 --- a/crates/ty_python_semantic/resources/mdtest/mro.md +++ b/crates/ty_python_semantic/resources/mdtest/mro.md @@ -293,6 +293,7 @@ reveal_mro(Foo) # revealed: (, Unknown, ) def f(): if returns_bool(): class C: ... + else: class C: ... @@ -709,19 +710,23 @@ python-version = "3.13" from ty_extensions import reveal_mro class C(C.a): ... + reveal_type(C.__class__) # revealed: reveal_mro(C) # revealed: (, Unknown, ) class D(D.a): a: D + reveal_type(D.__class__) # revealed: reveal_mro(D) # revealed: (, Unknown, ) class E[T](E.a): ... + reveal_type(E.__class__) # revealed: reveal_mro(E) # revealed: (, Unknown, typing.Generic, ) class F[T](F(), F): ... # error: [cyclic-class-definition] + reveal_type(F.__class__) # revealed: type[Unknown] reveal_mro(F) # revealed: (, Unknown, ) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 707703d7f3df2..f9579ce8cbd71 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -436,7 +436,6 @@ outer class is just a regular class inheriting from it. This is equivalent to: ```py class _Foo(NamedTuple): ... - class Foo(_Foo): # Regular class, not a namedtuple ... ``` diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md b/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md index b3527fcfe0c85..5317d47a78274 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/type_guards.md @@ -74,14 +74,17 @@ class _: def _(a) -> TypeIs[str]: ... # errors - def _(self) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow" - def _(self, /, *, a) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow" + # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow" + def _(self) -> TypeGuard[str]: ... + # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow" + def _(self, /, *, a) -> TypeGuard[str]: ... @classmethod def _(cls) -> TypeIs[str]: ... # error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow" @classmethod def _() -> TypeIs[str]: ... # error: [invalid-type-guard-definition] "`TypeIs` function must have a parameter to narrow" @staticmethod - def _(*, a) -> TypeGuard[str]: ... # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow" + # error: [invalid-type-guard-definition] "`TypeGuard` function must have a parameter to narrow" + def _(*, a) -> TypeGuard[str]: ... ``` For `TypeIs` functions, the narrowed type must be assignable to the declared type of that parameter, @@ -238,7 +241,7 @@ def _(d: Any): ... if g(*d): - ... + pass if f("foo"): # TODO: error: [invalid-type-guard-call] ... diff --git a/crates/ty_python_semantic/resources/mdtest/overloads.md b/crates/ty_python_semantic/resources/mdtest/overloads.md index 5da5db6e8e655..4dda8e1055cdf 100644 --- a/crates/ty_python_semantic/resources/mdtest/overloads.md +++ b/crates/ty_python_semantic/resources/mdtest/overloads.md @@ -721,14 +721,12 @@ class Foo: def method1(self, x: int) -> int: ... @overload def method1(self, x: str) -> str: ... - @overload def method2(self, x: int) -> int: ... @final @overload # error: [invalid-overload] def method2(self, x: str) -> str: ... - @overload def method3(self, x: int) -> int: ... @final diff --git a/crates/ty_python_semantic/resources/mdtest/override.md b/crates/ty_python_semantic/resources/mdtest/override.md index 31aafc2a2555a..360290082fdec 100644 --- a/crates/ty_python_semantic/resources/mdtest/override.md +++ b/crates/ty_python_semantic/resources/mdtest/override.md @@ -19,10 +19,8 @@ class A: class Parent: def foo(self): ... - @property def my_property1(self) -> int: ... - @property def my_property2(self) -> int: ... @@ -30,63 +28,47 @@ class Parent: @classmethod def class_method1(cls) -> int: ... - @staticmethod def static_method1() -> int: ... - @classmethod def class_method2(cls) -> int: ... - @staticmethod def static_method2() -> int: ... - @lossy_decorator def decorated_1(self): ... - @lossy_decorator def decorated_2(self): ... - @lossy_decorator def decorated_3(self): ... class Child(Parent): @override def foo(self): ... # fine: overrides `Parent.foo` - @property @override def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` - @override @property def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2` - @override def baz(self): ... # fine: overrides `Parent.baz` - @classmethod @override def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` - @staticmethod @override def static_method1() -> int: ... # fine: overrides `Parent.static_method1` - @override @classmethod def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` - @override @staticmethod def static_method2() -> int: ... # fine: overrides `Parent.static_method2` - @override def decorated_1(self): ... # fine: overrides `Parent.decorated_1` - @override @lossy_decorator def decorated_2(self): ... # fine: overrides `Parent.decorated_2` - @lossy_decorator @override def decorated_3(self): ... # fine: overrides `Parent.decorated_3` @@ -96,37 +78,28 @@ class OtherChild(Parent): ... class Grandchild(OtherChild): @override def foo(self): ... # fine: overrides `Parent.foo` - @override @property def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` - @override def baz(self): ... # fine: overrides `Parent.baz` - @classmethod @override def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` - @staticmethod @override def static_method1() -> int: ... # fine: overrides `Parent.static_method1` - @override @classmethod def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` - @override @staticmethod def static_method2() -> int: ... # fine: overrides `Parent.static_method2` - @override def decorated_1(self): ... # fine: overrides `Parent.decorated_1` - @override @lossy_decorator def decorated_2(self): ... # fine: overrides `Parent.decorated_2` - @lossy_decorator @override def decorated_3(self): ... # fine: overrides `Parent.decorated_3` @@ -134,41 +107,32 @@ class Grandchild(OtherChild): class Invalid: @override def ___reprrr__(self): ... # error: [invalid-explicit-override] - @override @classmethod def foo(self): ... # error: [invalid-explicit-override] - @classmethod @override def bar(self): ... # error: [invalid-explicit-override] - @staticmethod @override def baz(): ... # error: [invalid-explicit-override] - @override @staticmethod def eggs(): ... # error: [invalid-explicit-override] - @property @override def bad_property1(self) -> int: ... # error: [invalid-explicit-override] - @override @property def bad_property2(self) -> int: ... # error: [invalid-explicit-override] - @property @override def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override] @bad_settable_property.setter def bad_settable_property(self, x: int) -> None: ... - @lossy_decorator @override def lossy(self): ... # TODO: should emit `invalid-explicit-override` here - @override @lossy_decorator def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here @@ -179,7 +143,6 @@ class LiskovViolatingButNotOverrideViolating(Parent): @override @property def foo(self) -> int: ... - @override def my_property1(self) -> int: ... @@ -189,7 +152,6 @@ class LiskovViolatingButNotOverrideViolating(Parent): @staticmethod @override def class_method1() -> int: ... # error: [invalid-method-override] - @classmethod @override def static_method1(cls) -> int: ... @@ -239,6 +201,7 @@ class Parent: if coinflip(): def method3(self) -> None: ... def method4(self) -> None: ... + else: def method3(self) -> None: ... def method4(self) -> None: ... @@ -259,6 +222,7 @@ class Child(Parent): if coinflip(): @override def method4(self) -> None: ... + else: @override def method4(self) -> None: ... @@ -270,6 +234,7 @@ class Child(Parent): if coinflip(): @override def method6(self) -> None: ... + else: @override def method6(self) -> None: ... @@ -281,6 +246,7 @@ class Child(Parent): if coinflip(): @override def method8(self) -> None: ... # error: [invalid-explicit-override] + else: @override def method8(self) -> None: ... @@ -302,6 +268,7 @@ def coinflip() -> bool: class Foo: if coinflip(): def method(self, x): ... + elif coinflip(): @overload def method(self, x: str) -> str: ... @@ -310,6 +277,7 @@ class Foo: @override def method(self, x: str | int) -> str | int: # error: [invalid-explicit-override] return x + elif coinflip(): @override def method(self, x): ... @@ -326,6 +294,7 @@ def coinflip() -> bool: class Foo: if coinflip(): def method(self, x): ... + elif coinflip(): @overload @override @@ -335,19 +304,21 @@ class Foo: if coinflip(): def method2(self, x): ... + elif coinflip(): @overload @override def method2(self, x: str) -> str: ... @overload def method2(self, x: int) -> int: ... + else: - # TODO: not sure why this is being emitted on this line rather than on - # the first overload in the `elif` block? Ideally it would be emitted - # on the first reachable definition, but perhaps this is due to the way - # name lookups are deferred in stub files...? -- AW - @override - def method2(self, x): ... # error: [invalid-explicit-override] + # TODO: not sure why this is being emitted on this line rather than on + # the first overload in the `elif` block? Ideally it would be emitted + # on the first reachable definition, but perhaps this is due to the way + # name lookups are deferred in stub files...? -- AW + @override + def method2(self, x): ... # error: [invalid-explicit-override] ``` ## Definitions in statically known branches @@ -365,6 +336,7 @@ class Parent: if sys.version_info >= (3, 10): def foo(self) -> None: ... def foooo(self) -> None: ... + else: def bar(self) -> None: ... def baz(self) -> None: ... @@ -384,6 +356,7 @@ class Child(Parent): def foooo(self) -> None: ... @override def baz(self) -> None: ... # error: [invalid-explicit-override] + else: # This doesn't override any reachable definitions, # but the subclass definition also isn't a reachable definition @@ -452,7 +425,6 @@ class Spam: @override # error: [invalid-overload] "`@override` decorator should be applied only to the first overload" def foo(self, x: int) -> int: ... - @overload @override def bar(self, x: str) -> str: ... # error: [invalid-explicit-override] @@ -460,7 +432,6 @@ class Spam: @override # error: [invalid-overload] "`@override` decorator should be applied only to the first overload" def bar(self, x: int) -> int: ... - @overload @override def baz(self, x: str) -> str: ... # error: [invalid-explicit-override] @@ -484,6 +455,7 @@ class Foo: @overload @override def method(self, x: int) -> int: ... # error: [invalid-explicit-override] + else: @overload def method(self, x: int) -> int: ... @@ -493,6 +465,7 @@ class Foo: if sys.version_info >= (3, 10): @overload def method2(self, x: int) -> int: ... + else: @overload @override diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 9e1b8272f9f4b..c5285d4821aab 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -588,6 +588,7 @@ class Foo(Protocol): a: int b = 42 def c(self) -> None: ... + else: d: int e = 56 # error: [ambiguous-protocol-member] diff --git a/crates/ty_python_semantic/resources/mdtest/public_types.md b/crates/ty_python_semantic/resources/mdtest/public_types.md index f8e522e767202..8287e47fe7486 100644 --- a/crates/ty_python_semantic/resources/mdtest/public_types.md +++ b/crates/ty_python_semantic/resources/mdtest/public_types.md @@ -438,6 +438,7 @@ if flag(): def g(x: int) -> int: ... @overload def g(x: str) -> str: ... + else: g: str diff --git a/crates/ty_python_semantic/resources/mdtest/stubs/class.md b/crates/ty_python_semantic/resources/mdtest/stubs/class.md index e76316860adda..4d9f8b639cc90 100644 --- a/crates/ty_python_semantic/resources/mdtest/stubs/class.md +++ b/crates/ty_python_semantic/resources/mdtest/stubs/class.md @@ -14,7 +14,6 @@ In type stubs, classes can reference themselves in their base class definitions. from ty_extensions import reveal_mro class Foo[T]: ... - class Bar(Foo[Bar]): ... reveal_type(Bar) # revealed: diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/class.md b/crates/ty_python_semantic/resources/mdtest/subscript/class.md index 5165dbe45cb96..48ee50f8b6d34 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/class.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/class.md @@ -33,6 +33,7 @@ def _(flag: bool): if flag: def __class_getitem__(cls, item: int) -> str: return str(item) + else: def __class_getitem__(cls, item: int) -> int: return item @@ -69,6 +70,7 @@ def _(flag: bool): else: class Spam: ... + # error: [not-subscriptable] "Cannot subscript object of type `` with no `__class_getitem__` method" # revealed: str | Unknown reveal_type(Spam[42]) diff --git a/crates/ty_python_semantic/resources/mdtest/subscript/instance.md b/crates/ty_python_semantic/resources/mdtest/subscript/instance.md index e9691f255a1d7..90e96fdc36aa9 100644 --- a/crates/ty_python_semantic/resources/mdtest/subscript/instance.md +++ b/crates/ty_python_semantic/resources/mdtest/subscript/instance.md @@ -42,6 +42,7 @@ def _(flag: bool): if flag: def __getitem__(self, index: int) -> int: return index + else: def __getitem__(self, index: int) -> str: return str(index) diff --git a/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md b/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md index bff808a2cd4d1..34bc83828e07f 100644 --- a/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md +++ b/crates/ty_python_semantic/resources/mdtest/suppressions/type_ignore.md @@ -125,7 +125,7 @@ But not at the end of the f-string: ```py a = f""" { - 10 / 0 # error: [division-by-zero] + 10 / 0 # error: [division-by-zero] } """ # error: [unused-type-ignore-comment] # type: ignore ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md index 71d53f44a36c1..b512947ca4a59 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -415,7 +415,6 @@ class Child(Parent): ... def pg(a: Parent) -> None: ... @overload def pg(a: Grandparent) -> None: ... - @overload def cpg(a: Child) -> None: ... @overload diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md index 542dd7fb5308e..c886ff6bcb9d3 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md @@ -2144,31 +2144,26 @@ class Other: ... def pg(a: Parent) -> None: ... @overload def pg(a: Grandparent) -> None: ... - @overload def po(a: Parent) -> None: ... @overload def po(a: Other) -> None: ... - @overload def go(a: Grandparent) -> None: ... @overload def go(a: Other) -> None: ... - @overload def cpg(a: Child) -> None: ... @overload def cpg(a: Parent) -> None: ... @overload def cpg(a: Grandparent) -> None: ... - @overload def empty_go() -> Child: ... @overload def empty_go(a: Grandparent) -> None: ... @overload def empty_go(a: Other) -> Other: ... - @overload def empty_cp() -> Parent: ... @overload @@ -2216,7 +2211,6 @@ class B: ... def overload_ab(x: A) -> None: ... @overload def overload_ab(x: B) -> None: ... - @overload def overload_ba(x: B) -> None: ... @overload diff --git a/crates/ty_python_semantic/resources/mdtest/with/async.md b/crates/ty_python_semantic/resources/mdtest/with/async.md index 9802c85c4eb38..f0e729ae48ab1 100644 --- a/crates/ty_python_semantic/resources/mdtest/with/async.md +++ b/crates/ty_python_semantic/resources/mdtest/with/async.md @@ -43,7 +43,7 @@ class Manager: ... async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`" async with Manager(): - ... + pass ``` ## Context manager without an `__aenter__` method @@ -55,7 +55,7 @@ class Manager: async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__`" async with Manager(): - ... + pass ``` ## Context manager without an `__aexit__` method @@ -67,7 +67,7 @@ class Manager: async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aexit__`" async with Manager(): - ... + pass ``` ## Context manager with non-callable `__aenter__` attribute @@ -81,7 +81,7 @@ class Manager: async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not correctly implement `__aenter__`" async with Manager(): - ... + pass ``` ## Context manager with non-callable `__aexit__` attribute @@ -97,7 +97,7 @@ class Manager: async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not correctly implement `__aexit__`" async with Manager(): - ... + pass ``` ## Context expression with possibly-unbound union variants @@ -166,7 +166,7 @@ class Manager: async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`" async with Manager(): - ... + pass ``` ## Incorrect signatures @@ -182,7 +182,7 @@ class Manager: async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`" async with Manager(): - ... + pass ``` ## Incorrect number of arguments @@ -197,7 +197,7 @@ class Manager: async def main(): # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`" async with Manager(): - ... + pass ``` ## `@asynccontextmanager` diff --git a/crates/ty_python_semantic/resources/mdtest/with/sync.md b/crates/ty_python_semantic/resources/mdtest/with/sync.md index 15d6aec51e408..da383f065b6c9 100644 --- a/crates/ty_python_semantic/resources/mdtest/with/sync.md +++ b/crates/ty_python_semantic/resources/mdtest/with/sync.md @@ -47,7 +47,7 @@ class Manager: ... # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" with Manager(): - ... + pass ``` ## Context manager without an `__enter__` method @@ -58,7 +58,7 @@ class Manager: # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__`" with Manager(): - ... + pass ``` ## Context manager without an `__exit__` method @@ -69,7 +69,7 @@ class Manager: # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__exit__`" with Manager(): - ... + pass ``` ## Context manager with non-callable `__enter__` attribute @@ -82,7 +82,7 @@ class Manager: # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not correctly implement `__enter__`" with Manager(): - ... + pass ``` ## Context manager with non-callable `__exit__` attribute @@ -97,7 +97,7 @@ class Manager: # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not correctly implement `__exit__`" with Manager(): - ... + pass ``` ## Context expression with possibly-unbound union variants @@ -164,7 +164,7 @@ class Manager: # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" with Manager(): - ... + pass ``` ## Incorrect signatures @@ -179,7 +179,7 @@ class Manager: # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" with Manager(): - ... + pass ``` ## Incorrect number of arguments @@ -193,5 +193,5 @@ class Manager: # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" with Manager(): - ... + pass ``` From e2ee456fe1786240ac09607c98ee7fdc9f577b71 Mon Sep 17 00:00:00 2001 From: Amethyst Reese Date: Mon, 2 Feb 2026 14:20:55 -0800 Subject: [PATCH 4/4] Update snapshots --- ...of_as\342\200\246_(5b8c1b4d846bc544).snap" | 4 +- ...fined\342\200\246_(fc7b496fd1986deb).snap" | 327 ++++----- ..._a_me\342\200\246_(338615109711a91b).snap" | 623 +++++++++--------- ...ods_d\342\200\246_(861757f48340ed92).snap" | 252 ++++--- ...atica\342\200\246_(29a698d9deaf7318).snap" | 86 +-- ...llabl\342\200\246_(49a21e4b7fe6e97b).snap" | 66 +- ...d_`__\342\200\246_(6388761c90a0555c).snap" | 58 +- ...d_`__\342\200\246_(6805a6032e504b63).snap" | 58 +- ...d_`__\342\200\246_(c626bde8651b643a).snap" | 74 ++- ...g_`__\342\200\246_(77269542b8e81774).snap" | 72 +- ...eturn\342\200\246_(fedf62ffaca0f2d7).snap" | 23 +- ...Method_parameters_(d98059266bcc1e13).snap" | 2 +- ...ludes\342\200\246_(d2532518c44112c8).snap" | 19 +- ...00\246_-_`@final`_(f8e529ec23a61665).snap" | 89 +-- ...override`_-_Basics_(b7c220f8171f11f0).snap | 494 +++++++------- ...s_in_\342\200\246_(21be5d9bdab1c844).snap" | 23 +- ..._the_\342\200\246_(93e8ab913ead83b2).snap" | 34 +- ...of_no\342\200\246_(b07503f9b773ea61).snap" | 4 +- 18 files changed, 1142 insertions(+), 1166 deletions(-) diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/async.md_-_Async_with_statement\342\200\246_-_Accidental_use_of_as\342\200\246_(5b8c1b4d846bc544).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/async.md_-_Async_with_statement\342\200\246_-_Accidental_use_of_as\342\200\246_(5b8c1b4d846bc544).snap" index 9bdd478aad391..c82b55bc205d2 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/async.md_-_Async_with_statement\342\200\246_-_Accidental_use_of_as\342\200\246_(5b8c1b4d846bc544).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/async.md_-_Async_with_statement\342\200\246_-_Accidental_use_of_as\342\200\246_(5b8c1b4d846bc544).snap" @@ -20,7 +20,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/with/async.md 5 | async def main(): 6 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aenter__` and `__aexit__`" 7 | async with Manager(): -8 | ... +8 | pass ``` # Diagnostics @@ -33,7 +33,7 @@ error[invalid-context-manager]: Object of type `Manager` cannot be used with `as 6 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `async with` because it does not implement `__aent… 7 | async with Manager(): | ^^^^^^^^^ -8 | ... +8 | pass | info: Objects of type `Manager` can be used as sync context managers info: Consider using `with` here diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_possibly-undefined\342\200\246_(fc7b496fd1986deb).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_possibly-undefined\342\200\246_(fc7b496fd1986deb).snap" index e758257e92b4e..5478aa695e5f5 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_possibly-undefined\342\200\246_(fc7b496fd1986deb).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_A_possibly-undefined\342\200\246_(fc7b496fd1986deb).snap" @@ -22,76 +22,83 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 7 | if coinflip(): 8 | @final 9 | def method1(self) -> None: ... -10 | else: -11 | def method1(self) -> None: ... -12 | -13 | if coinflip(): -14 | def method2(self) -> None: ... -15 | else: -16 | @final -17 | def method2(self) -> None: ... -18 | -19 | if coinflip(): -20 | @final -21 | def method3(self) -> None: ... -22 | else: -23 | @final -24 | def method3(self) -> None: ... -25 | -26 | if coinflip(): -27 | def method4(self) -> None: ... -28 | elif coinflip(): -29 | @final +10 | +11 | else: +12 | def method1(self) -> None: ... +13 | +14 | if coinflip(): +15 | def method2(self) -> None: ... +16 | +17 | else: +18 | @final +19 | def method2(self) -> None: ... +20 | +21 | if coinflip(): +22 | @final +23 | def method3(self) -> None: ... +24 | +25 | else: +26 | @final +27 | def method3(self) -> None: ... +28 | +29 | if coinflip(): 30 | def method4(self) -> None: ... -31 | else: -32 | def method4(self) -> None: ... -33 | -34 | class B(A): -35 | def method1(self) -> None: ... # error: [override-of-final-method] -36 | def method2(self) -> None: ... # error: [override-of-final-method] -37 | def method3(self) -> None: ... # error: [override-of-final-method] +31 | +32 | elif coinflip(): +33 | @final +34 | def method4(self) -> None: ... +35 | +36 | else: +37 | def method4(self) -> None: ... 38 | -39 | # check that autofixes don't introduce invalid syntax -40 | # if there are multiple statements on one line -41 | # -42 | # TODO: we should emit a Liskov violation here too -43 | # error: [override-of-final-method] -44 | method4 = 42 -45 | unrelated = 56 # fmt: skip -46 | -47 | # Possible overrides of possibly `@final` methods... -48 | class C(A): -49 | if coinflip(): -50 | def method1(self) -> None: ... # error: [override-of-final-method] -51 | else: -52 | pass -53 | +39 | class B(A): +40 | def method1(self) -> None: ... # error: [override-of-final-method] +41 | def method2(self) -> None: ... # error: [override-of-final-method] +42 | def method3(self) -> None: ... # error: [override-of-final-method] +43 | +44 | # check that autofixes don't introduce invalid syntax +45 | # if there are multiple statements on one line +46 | # +47 | # TODO: we should emit a Liskov violation here too +48 | # error: [override-of-final-method] +49 | method4 = 42 +50 | unrelated = 56 # fmt: skip +51 | +52 | # Possible overrides of possibly `@final` methods... +53 | class C(A): 54 | if coinflip(): -55 | def method2(self) -> None: ... # error: [override-of-final-method] -56 | else: -57 | def method2(self) -> None: ... -58 | -59 | if coinflip(): -60 | def method3(self) -> None: ... # error: [override-of-final-method] -61 | -62 | # TODO: we should emit Liskov violations here too: -63 | if coinflip(): -64 | method4 = 42 # error: [override-of-final-method] -65 | else: -66 | method4 = 56 +55 | def method1(self) -> None: ... # error: [override-of-final-method] +56 | +57 | else: +58 | pass +59 | +60 | if coinflip(): +61 | def method2(self) -> None: ... # error: [override-of-final-method] +62 | +63 | else: +64 | def method2(self) -> None: ... +65 | +66 | if coinflip(): +67 | def method3(self) -> None: ... # error: [override-of-final-method] +68 | +69 | # TODO: we should emit Liskov violations here too: +70 | if coinflip(): +71 | method4 = 42 # error: [override-of-final-method] +72 | else: +73 | method4 = 56 ``` # Diagnostics ``` error[override-of-final-method]: Cannot override `A.method1` - --> src/mdtest_snippet.py:35:9 + --> src/mdtest_snippet.py:40:9 | -34 | class B(A): -35 | def method1(self) -> None: ... # error: [override-of-final-method] +39 | class B(A): +40 | def method1(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -36 | def method2(self) -> None: ... # error: [override-of-final-method] -37 | def method3(self) -> None: ... # error: [override-of-final-method] +41 | def method2(self) -> None: ... # error: [override-of-final-method] +42 | def method3(self) -> None: ... # error: [override-of-final-method] | info: `A.method1` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.py:8:9 @@ -102,116 +109,114 @@ info: `A.method1` is decorated with `@final`, forbidding overrides | ------ 9 | def method1(self) -> None: ... | ------- `A.method1` defined here -10 | else: -11 | def method1(self) -> None: ... +10 | +11 | else: | help: Remove the override of `method1` info: rule `override-of-final-method` is enabled by default -32 | def method4(self) -> None: ... -33 | -34 | class B(A): - - def method1(self) -> None: ... # error: [override-of-final-method] -35 + # error: [override-of-final-method] -36 | def method2(self) -> None: ... # error: [override-of-final-method] -37 | def method3(self) -> None: ... # error: [override-of-final-method] +37 | def method4(self) -> None: ... 38 | +39 | class B(A): + - def method1(self) -> None: ... # error: [override-of-final-method] +40 + # error: [override-of-final-method] +41 | def method2(self) -> None: ... # error: [override-of-final-method] +42 | def method3(self) -> None: ... # error: [override-of-final-method] +43 | note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `A.method2` - --> src/mdtest_snippet.py:36:9 + --> src/mdtest_snippet.py:41:9 | -34 | class B(A): -35 | def method1(self) -> None: ... # error: [override-of-final-method] -36 | def method2(self) -> None: ... # error: [override-of-final-method] +39 | class B(A): +40 | def method1(self) -> None: ... # error: [override-of-final-method] +41 | def method2(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -37 | def method3(self) -> None: ... # error: [override-of-final-method] +42 | def method3(self) -> None: ... # error: [override-of-final-method] | info: `A.method2` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.py:16:9 + --> src/mdtest_snippet.py:18:9 | -14 | def method2(self) -> None: ... -15 | else: -16 | @final +17 | else: +18 | @final | ------ -17 | def method2(self) -> None: ... +19 | def method2(self) -> None: ... | ------- `A.method2` defined here -18 | -19 | if coinflip(): +20 | +21 | if coinflip(): | help: Remove the override of `method2` info: rule `override-of-final-method` is enabled by default -33 | -34 | class B(A): -35 | def method1(self) -> None: ... # error: [override-of-final-method] - - def method2(self) -> None: ... # error: [override-of-final-method] -36 + # error: [override-of-final-method] -37 | def method3(self) -> None: ... # error: [override-of-final-method] 38 | -39 | # check that autofixes don't introduce invalid syntax +39 | class B(A): +40 | def method1(self) -> None: ... # error: [override-of-final-method] + - def method2(self) -> None: ... # error: [override-of-final-method] +41 + # error: [override-of-final-method] +42 | def method3(self) -> None: ... # error: [override-of-final-method] +43 | +44 | # check that autofixes don't introduce invalid syntax note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `A.method3` - --> src/mdtest_snippet.py:37:9 + --> src/mdtest_snippet.py:42:9 | -35 | def method1(self) -> None: ... # error: [override-of-final-method] -36 | def method2(self) -> None: ... # error: [override-of-final-method] -37 | def method3(self) -> None: ... # error: [override-of-final-method] +40 | def method1(self) -> None: ... # error: [override-of-final-method] +41 | def method2(self) -> None: ... # error: [override-of-final-method] +42 | def method3(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -38 | -39 | # check that autofixes don't introduce invalid syntax +43 | +44 | # check that autofixes don't introduce invalid syntax | info: `A.method3` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.py:20:9 + --> src/mdtest_snippet.py:22:9 | -19 | if coinflip(): -20 | @final +21 | if coinflip(): +22 | @final | ------ -21 | def method3(self) -> None: ... +23 | def method3(self) -> None: ... | ------- `A.method3` defined here -22 | else: -23 | @final +24 | +25 | else: | help: Remove the override of `method3` info: rule `override-of-final-method` is enabled by default -34 | class B(A): -35 | def method1(self) -> None: ... # error: [override-of-final-method] -36 | def method2(self) -> None: ... # error: [override-of-final-method] +39 | class B(A): +40 | def method1(self) -> None: ... # error: [override-of-final-method] +41 | def method2(self) -> None: ... # error: [override-of-final-method] - def method3(self) -> None: ... # error: [override-of-final-method] -37 + # error: [override-of-final-method] -38 | -39 | # check that autofixes don't introduce invalid syntax -40 | # if there are multiple statements on one line +42 + # error: [override-of-final-method] +43 | +44 | # check that autofixes don't introduce invalid syntax +45 | # if there are multiple statements on one line note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `A.method4` - --> src/mdtest_snippet.py:44:5 + --> src/mdtest_snippet.py:49:5 | -42 | # TODO: we should emit a Liskov violation here too -43 | # error: [override-of-final-method] -44 | method4 = 42 +47 | # TODO: we should emit a Liskov violation here too +48 | # error: [override-of-final-method] +49 | method4 = 42 | ^^^^^^^ Overrides a definition from superclass `A` -45 | unrelated = 56 # fmt: skip +50 | unrelated = 56 # fmt: skip | info: `A.method4` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.py:29:9 + --> src/mdtest_snippet.py:33:9 | -27 | def method4(self) -> None: ... -28 | elif coinflip(): -29 | @final +32 | elif coinflip(): +33 | @final | ------ -30 | def method4(self) -> None: ... +34 | def method4(self) -> None: ... | ------- `A.method4` defined here -31 | else: -32 | def method4(self) -> None: ... +35 | +36 | else: | help: Remove the override of `method4` info: rule `override-of-final-method` is enabled by default @@ -220,14 +225,14 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `A.method1` - --> src/mdtest_snippet.py:50:13 + --> src/mdtest_snippet.py:55:13 | -48 | class C(A): -49 | if coinflip(): -50 | def method1(self) -> None: ... # error: [override-of-final-method] +53 | class C(A): +54 | if coinflip(): +55 | def method1(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -51 | else: -52 | pass +56 | +57 | else: | info: `A.method1` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.py:8:9 @@ -238,8 +243,8 @@ info: `A.method1` is decorated with `@final`, forbidding overrides | ------ 9 | def method1(self) -> None: ... | ------- `A.method1` defined here -10 | else: -11 | def method1(self) -> None: ... +10 | +11 | else: | help: Remove the override of `method1` info: rule `override-of-final-method` is enabled by default @@ -248,25 +253,24 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `A.method2` - --> src/mdtest_snippet.py:55:13 + --> src/mdtest_snippet.py:61:13 | -54 | if coinflip(): -55 | def method2(self) -> None: ... # error: [override-of-final-method] +60 | if coinflip(): +61 | def method2(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -56 | else: -57 | def method2(self) -> None: ... +62 | +63 | else: | info: `A.method2` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.py:16:9 + --> src/mdtest_snippet.py:18:9 | -14 | def method2(self) -> None: ... -15 | else: -16 | @final +17 | else: +18 | @final | ------ -17 | def method2(self) -> None: ... +19 | def method2(self) -> None: ... | ------- `A.method2` defined here -18 | -19 | if coinflip(): +20 | +21 | if coinflip(): | help: Remove the override of `method2` info: rule `override-of-final-method` is enabled by default @@ -275,24 +279,24 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `A.method3` - --> src/mdtest_snippet.py:60:13 + --> src/mdtest_snippet.py:67:13 | -59 | if coinflip(): -60 | def method3(self) -> None: ... # error: [override-of-final-method] +66 | if coinflip(): +67 | def method3(self) -> None: ... # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -61 | -62 | # TODO: we should emit Liskov violations here too: +68 | +69 | # TODO: we should emit Liskov violations here too: | info: `A.method3` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.py:20:9 + --> src/mdtest_snippet.py:22:9 | -19 | if coinflip(): -20 | @final +21 | if coinflip(): +22 | @final | ------ -21 | def method3(self) -> None: ... +23 | def method3(self) -> None: ... | ------- `A.method3` defined here -22 | else: -23 | @final +24 | +25 | else: | help: Remove the override of `method3` info: rule `override-of-final-method` is enabled by default @@ -301,26 +305,25 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `A.method4` - --> src/mdtest_snippet.py:64:9 + --> src/mdtest_snippet.py:71:9 | -62 | # TODO: we should emit Liskov violations here too: -63 | if coinflip(): -64 | method4 = 42 # error: [override-of-final-method] +69 | # TODO: we should emit Liskov violations here too: +70 | if coinflip(): +71 | method4 = 42 # error: [override-of-final-method] | ^^^^^^^ Overrides a definition from superclass `A` -65 | else: -66 | method4 = 56 +72 | else: +73 | method4 = 56 | info: `A.method4` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.py:29:9 + --> src/mdtest_snippet.py:33:9 | -27 | def method4(self) -> None: ... -28 | elif coinflip(): -29 | @final +32 | elif coinflip(): +33 | @final | ------ -30 | def method4(self) -> None: ... +34 | def method4(self) -> None: ... | ------- `A.method4` defined here -31 | else: -32 | def method4(self) -> None: ... +35 | +36 | else: | help: Remove the override of `method4` info: rule `override-of-final-method` is enabled by default diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Cannot_override_a_me\342\200\246_(338615109711a91b).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Cannot_override_a_me\342\200\246_(338615109711a91b).snap" index 90573ad938d3b..7ab52937a2c7b 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Cannot_override_a_me\342\200\246_(338615109711a91b).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Cannot_override_a_me\342\200\246_(338615109711a91b).snap" @@ -20,142 +20,125 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 5 | class Parent: 6 | @final 7 | def foo(self): ... - 8 | - 9 | @final - 10 | @property - 11 | def my_property1(self) -> int: ... - 12 | - 13 | @property - 14 | @final - 15 | def my_property2(self) -> int: ... - 16 | - 17 | @property - 18 | @final - 19 | def my_property3(self) -> int: ... - 20 | + 8 | @final + 9 | @property + 10 | def my_property1(self) -> int: ... + 11 | @property + 12 | @final + 13 | def my_property2(self) -> int: ... + 14 | @property + 15 | @final + 16 | def my_property3(self) -> int: ... + 17 | @final + 18 | @classmethod + 19 | def class_method1(cls) -> int: ... + 20 | @classmethod 21 | @final - 22 | @classmethod - 23 | def class_method1(cls) -> int: ... - 24 | - 25 | @classmethod - 26 | @final - 27 | def class_method2(cls) -> int: ... - 28 | - 29 | @final - 30 | @staticmethod - 31 | def static_method1() -> int: ... - 32 | - 33 | @staticmethod - 34 | @final - 35 | def static_method2() -> int: ... - 36 | - 37 | @lossy_decorator - 38 | @final - 39 | def decorated_1(self): ... - 40 | - 41 | @final - 42 | @lossy_decorator - 43 | def decorated_2(self): ... - 44 | - 45 | class Child(Parent): - 46 | # explicitly test the concise diagnostic message, - 47 | # which is different to the verbose diagnostic summary message: - 48 | # - 49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" - 50 | def foo(self): ... - 51 | @property - 52 | def my_property1(self) -> int: ... # error: [override-of-final-method] - 53 | - 54 | @property - 55 | def my_property2(self) -> int: ... # error: [override-of-final-method] - 56 | @my_property2.setter - 57 | def my_property2(self, x: int) -> None: ... - 58 | - 59 | @property - 60 | def my_property3(self) -> int: ... # error: [override-of-final-method] - 61 | @my_property3.deleter - 62 | def my_proeprty3(self) -> None: ... + 22 | def class_method2(cls) -> int: ... + 23 | @final + 24 | @staticmethod + 25 | def static_method1() -> int: ... + 26 | @staticmethod + 27 | @final + 28 | def static_method2() -> int: ... + 29 | @lossy_decorator + 30 | @final + 31 | def decorated_1(self): ... + 32 | @final + 33 | @lossy_decorator + 34 | def decorated_2(self): ... + 35 | + 36 | class Child(Parent): + 37 | # explicitly test the concise diagnostic message, + 38 | # which is different to the verbose diagnostic summary message: + 39 | # + 40 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" + 41 | def foo(self): ... + 42 | @property + 43 | def my_property1(self) -> int: ... # error: [override-of-final-method] + 44 | @property + 45 | def my_property2(self) -> int: ... # error: [override-of-final-method] + 46 | @my_property2.setter + 47 | def my_property2(self, x: int) -> None: ... + 48 | @property + 49 | def my_property3(self) -> int: ... # error: [override-of-final-method] + 50 | @my_property3.deleter + 51 | def my_proeprty3(self) -> None: ... + 52 | @classmethod + 53 | def class_method1(cls) -> int: ... # error: [override-of-final-method] + 54 | @staticmethod + 55 | def static_method1() -> int: ... # error: [override-of-final-method] + 56 | @classmethod + 57 | def class_method2(cls) -> int: ... # error: [override-of-final-method] + 58 | @staticmethod + 59 | def static_method2() -> int: ... # error: [override-of-final-method] + 60 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] + 61 | @lossy_decorator + 62 | def decorated_2(self): ... # TODO: should emit [override-of-final-method] 63 | - 64 | @classmethod - 65 | def class_method1(cls) -> int: ... # error: [override-of-final-method] - 66 | - 67 | @staticmethod - 68 | def static_method1() -> int: ... # error: [override-of-final-method] - 69 | - 70 | @classmethod - 71 | def class_method2(cls) -> int: ... # error: [override-of-final-method] - 72 | - 73 | @staticmethod - 74 | def static_method2() -> int: ... # error: [override-of-final-method] - 75 | - 76 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] - 77 | - 78 | @lossy_decorator - 79 | def decorated_2(self): ... # TODO: should emit [override-of-final-method] - 80 | - 81 | class OtherChild(Parent): ... + 64 | class OtherChild(Parent): ... + 65 | + 66 | class Grandchild(OtherChild): + 67 | # TODO: The Liskov violation here maybe shouldn't be emitted? Whether called on the + 68 | # type or on an instance, it will behave the same from the caller's perspective. The only + 69 | # difference is whether the method body gets access to `self`, which is not a + 70 | # concern of Liskov. + 71 | @staticmethod + 72 | # error: [override-of-final-method] + 73 | # error: [invalid-method-override] + 74 | def foo(): ... + 75 | @property + 76 | # TODO: we should emit a Liskov violation here too + 77 | # error: [override-of-final-method] + 78 | def my_property1(self) -> str: ... + 79 | # TODO: we should emit a Liskov violation here too + 80 | # error: [override-of-final-method] + 81 | class_method1 = None 82 | - 83 | class Grandchild(OtherChild): - 84 | # TODO: The Liskov violation here maybe shouldn't be emitted? Whether called on the - 85 | # type or on an instance, it will behave the same from the caller's perspective. The only - 86 | # difference is whether the method body gets access to `self`, which is not a - 87 | # concern of Liskov. - 88 | @staticmethod - 89 | # error: [override-of-final-method] - 90 | # error: [invalid-method-override] - 91 | def foo(): ... - 92 | @property - 93 | # TODO: we should emit a Liskov violation here too - 94 | # error: [override-of-final-method] - 95 | def my_property1(self) -> str: ... - 96 | # TODO: we should emit a Liskov violation here too - 97 | # error: [override-of-final-method] - 98 | class_method1 = None - 99 | -100 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: -101 | -102 | T = TypeVar("T") -103 | -104 | def identity(x: T) -> T: ... -105 | -106 | class Foo: -107 | @final + 83 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: + 84 | + 85 | T = TypeVar("T") + 86 | + 87 | def identity(x: T) -> T: ... + 88 | + 89 | class Foo: + 90 | @final + 91 | @identity + 92 | @identity + 93 | @identity + 94 | @identity + 95 | @identity + 96 | @identity + 97 | @identity + 98 | @identity + 99 | @identity +100 | @identity +101 | @identity +102 | @identity +103 | @identity +104 | @identity +105 | @identity +106 | @identity +107 | @identity 108 | @identity -109 | @identity -110 | @identity -111 | @identity -112 | @identity -113 | @identity -114 | @identity -115 | @identity -116 | @identity -117 | @identity -118 | @identity -119 | @identity -120 | @identity -121 | @identity -122 | @identity -123 | @identity -124 | @identity -125 | @identity -126 | def bar(self): ... -127 | -128 | class Baz(Foo): -129 | def bar(self): ... # error: [override-of-final-method] +109 | def bar(self): ... +110 | +111 | class Baz(Foo): +112 | def bar(self): ... # error: [override-of-final-method] ``` # Diagnostics ``` error[override-of-final-method]: Cannot override `Parent.foo` - --> src/mdtest_snippet.pyi:50:9 + --> src/mdtest_snippet.pyi:41:9 | -48 | # -49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" -50 | def foo(self): ... +39 | # +40 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" +41 | def foo(self): ... | ^^^ Overrides a definition from superclass `Parent` -51 | @property -52 | def my_property1(self) -> int: ... # error: [override-of-final-method] +42 | @property +43 | def my_property1(self) -> int: ... # error: [override-of-final-method] | info: `Parent.foo` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:6:5 @@ -165,46 +148,46 @@ info: `Parent.foo` is decorated with `@final`, forbidding overrides | ------ 7 | def foo(self): ... | --- `Parent.foo` defined here -8 | -9 | @final +8 | @final +9 | @property | help: Remove the override of `foo` info: rule `override-of-final-method` is enabled by default -47 | # which is different to the verbose diagnostic summary message: -48 | # -49 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" +38 | # which is different to the verbose diagnostic summary message: +39 | # +40 | # error: [override-of-final-method] "Cannot override final member `foo` from superclass `Parent`" - def foo(self): ... -50 + -51 | @property -52 | def my_property1(self) -> int: ... # error: [override-of-final-method] -53 | +41 + +42 | @property +43 | def my_property1(self) -> int: ... # error: [override-of-final-method] +44 | @property note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.my_property1` - --> src/mdtest_snippet.pyi:52:9 + --> src/mdtest_snippet.pyi:43:9 | -50 | def foo(self): ... -51 | @property -52 | def my_property1(self) -> int: ... # error: [override-of-final-method] +41 | def foo(self): ... +42 | @property +43 | def my_property1(self) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -53 | -54 | @property +44 | @property +45 | def my_property2(self) -> int: ... # error: [override-of-final-method] | info: `Parent.my_property1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:9:5 + --> src/mdtest_snippet.pyi:8:5 | + 6 | @final 7 | def foo(self): ... - 8 | - 9 | @final + 8 | @final | ------ -10 | @property -11 | def my_property1(self) -> int: ... + 9 | @property +10 | def my_property1(self) -> int: ... | ------------ `Parent.my_property1` defined here -12 | -13 | @property +11 | @property +12 | @final | help: Remove the override of `my_property1` info: rule `override-of-final-method` is enabled by default @@ -213,24 +196,26 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `Parent.my_property2` - --> src/mdtest_snippet.pyi:55:9 + --> src/mdtest_snippet.pyi:45:9 | -54 | @property -55 | def my_property2(self) -> int: ... # error: [override-of-final-method] +43 | def my_property1(self) -> int: ... # error: [override-of-final-method] +44 | @property +45 | def my_property2(self) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -56 | @my_property2.setter -57 | def my_property2(self, x: int) -> None: ... +46 | @my_property2.setter +47 | def my_property2(self, x: int) -> None: ... | info: `Parent.my_property2` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:14:5 + --> src/mdtest_snippet.pyi:12:5 | -13 | @property -14 | @final +10 | def my_property1(self) -> int: ... +11 | @property +12 | @final | ------ -15 | def my_property2(self) -> int: ... +13 | def my_property2(self) -> int: ... | ------------ `Parent.my_property2` defined here -16 | -17 | @property +14 | @property +15 | @final | help: Remove the getter and setter for `my_property2` info: rule `override-of-final-method` is enabled by default @@ -239,24 +224,26 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `Parent.my_property3` - --> src/mdtest_snippet.pyi:60:9 + --> src/mdtest_snippet.pyi:49:9 | -59 | @property -60 | def my_property3(self) -> int: ... # error: [override-of-final-method] +47 | def my_property2(self, x: int) -> None: ... +48 | @property +49 | def my_property3(self) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -61 | @my_property3.deleter -62 | def my_proeprty3(self) -> None: ... +50 | @my_property3.deleter +51 | def my_proeprty3(self) -> None: ... | info: `Parent.my_property3` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:18:5 + --> src/mdtest_snippet.pyi:15:5 | -17 | @property -18 | @final +13 | def my_property2(self) -> int: ... +14 | @property +15 | @final | ------ -19 | def my_property3(self) -> int: ... +16 | def my_property3(self) -> int: ... | ------------ `Parent.my_property3` defined here -20 | -21 | @final +17 | @final +18 | @classmethod | help: Remove the override of `my_property3` info: rule `override-of-final-method` is enabled by default @@ -265,162 +252,168 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `Parent.class_method1` - --> src/mdtest_snippet.pyi:65:9 + --> src/mdtest_snippet.pyi:53:9 | -64 | @classmethod -65 | def class_method1(cls) -> int: ... # error: [override-of-final-method] +51 | def my_proeprty3(self) -> None: ... +52 | @classmethod +53 | def class_method1(cls) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -66 | -67 | @staticmethod +54 | @staticmethod +55 | def static_method1() -> int: ... # error: [override-of-final-method] | info: `Parent.class_method1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:21:5 + --> src/mdtest_snippet.pyi:17:5 | -19 | def my_property3(self) -> int: ... -20 | -21 | @final +15 | @final +16 | def my_property3(self) -> int: ... +17 | @final | ------ -22 | @classmethod -23 | def class_method1(cls) -> int: ... +18 | @classmethod +19 | def class_method1(cls) -> int: ... | ------------- `Parent.class_method1` defined here -24 | -25 | @classmethod +20 | @classmethod +21 | @final | help: Remove the override of `class_method1` info: rule `override-of-final-method` is enabled by default -61 | @my_property3.deleter -62 | def my_proeprty3(self) -> None: ... -63 | +49 | def my_property3(self) -> int: ... # error: [override-of-final-method] +50 | @my_property3.deleter +51 | def my_proeprty3(self) -> None: ... - @classmethod - def class_method1(cls) -> int: ... # error: [override-of-final-method] -64 + # error: [override-of-final-method] -65 | -66 | @staticmethod -67 | def static_method1() -> int: ... # error: [override-of-final-method] +52 + # error: [override-of-final-method] +53 | @staticmethod +54 | def static_method1() -> int: ... # error: [override-of-final-method] +55 | @classmethod note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.static_method1` - --> src/mdtest_snippet.pyi:68:9 + --> src/mdtest_snippet.pyi:55:9 | -67 | @staticmethod -68 | def static_method1() -> int: ... # error: [override-of-final-method] +53 | def class_method1(cls) -> int: ... # error: [override-of-final-method] +54 | @staticmethod +55 | def static_method1() -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -69 | -70 | @classmethod +56 | @classmethod +57 | def class_method2(cls) -> int: ... # error: [override-of-final-method] | info: `Parent.static_method1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:29:5 + --> src/mdtest_snippet.pyi:23:5 | -27 | def class_method2(cls) -> int: ... -28 | -29 | @final +21 | @final +22 | def class_method2(cls) -> int: ... +23 | @final | ------ -30 | @staticmethod -31 | def static_method1() -> int: ... +24 | @staticmethod +25 | def static_method1() -> int: ... | -------------- `Parent.static_method1` defined here -32 | -33 | @staticmethod +26 | @staticmethod +27 | @final | help: Remove the override of `static_method1` info: rule `override-of-final-method` is enabled by default -64 | @classmethod -65 | def class_method1(cls) -> int: ... # error: [override-of-final-method] -66 | +51 | def my_proeprty3(self) -> None: ... +52 | @classmethod +53 | def class_method1(cls) -> int: ... # error: [override-of-final-method] - @staticmethod - def static_method1() -> int: ... # error: [override-of-final-method] -67 + # error: [override-of-final-method] -68 | -69 | @classmethod -70 | def class_method2(cls) -> int: ... # error: [override-of-final-method] +54 + # error: [override-of-final-method] +55 | @classmethod +56 | def class_method2(cls) -> int: ... # error: [override-of-final-method] +57 | @staticmethod note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.class_method2` - --> src/mdtest_snippet.pyi:71:9 + --> src/mdtest_snippet.pyi:57:9 | -70 | @classmethod -71 | def class_method2(cls) -> int: ... # error: [override-of-final-method] +55 | def static_method1() -> int: ... # error: [override-of-final-method] +56 | @classmethod +57 | def class_method2(cls) -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -72 | -73 | @staticmethod +58 | @staticmethod +59 | def static_method2() -> int: ... # error: [override-of-final-method] | info: `Parent.class_method2` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:26:5 + --> src/mdtest_snippet.pyi:21:5 | -25 | @classmethod -26 | @final +19 | def class_method1(cls) -> int: ... +20 | @classmethod +21 | @final | ------ -27 | def class_method2(cls) -> int: ... +22 | def class_method2(cls) -> int: ... | ------------- `Parent.class_method2` defined here -28 | -29 | @final +23 | @final +24 | @staticmethod | help: Remove the override of `class_method2` info: rule `override-of-final-method` is enabled by default -67 | @staticmethod -68 | def static_method1() -> int: ... # error: [override-of-final-method] -69 | +53 | def class_method1(cls) -> int: ... # error: [override-of-final-method] +54 | @staticmethod +55 | def static_method1() -> int: ... # error: [override-of-final-method] - @classmethod - def class_method2(cls) -> int: ... # error: [override-of-final-method] -70 + # error: [override-of-final-method] -71 | -72 | @staticmethod -73 | def static_method2() -> int: ... # error: [override-of-final-method] +56 + # error: [override-of-final-method] +57 | @staticmethod +58 | def static_method2() -> int: ... # error: [override-of-final-method] +59 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.static_method2` - --> src/mdtest_snippet.pyi:74:9 + --> src/mdtest_snippet.pyi:59:9 | -73 | @staticmethod -74 | def static_method2() -> int: ... # error: [override-of-final-method] +57 | def class_method2(cls) -> int: ... # error: [override-of-final-method] +58 | @staticmethod +59 | def static_method2() -> int: ... # error: [override-of-final-method] | ^^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -75 | -76 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] +60 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] +61 | @lossy_decorator | info: `Parent.static_method2` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:34:5 + --> src/mdtest_snippet.pyi:27:5 | -33 | @staticmethod -34 | @final +25 | def static_method1() -> int: ... +26 | @staticmethod +27 | @final | ------ -35 | def static_method2() -> int: ... +28 | def static_method2() -> int: ... | -------------- `Parent.static_method2` defined here -36 | -37 | @lossy_decorator +29 | @lossy_decorator +30 | @final | help: Remove the override of `static_method2` info: rule `override-of-final-method` is enabled by default -70 | @classmethod -71 | def class_method2(cls) -> int: ... # error: [override-of-final-method] -72 | +55 | def static_method1() -> int: ... # error: [override-of-final-method] +56 | @classmethod +57 | def class_method2(cls) -> int: ... # error: [override-of-final-method] - @staticmethod - def static_method2() -> int: ... # error: [override-of-final-method] -73 + # error: [override-of-final-method] -74 | -75 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] -76 | +58 + # error: [override-of-final-method] +59 | def decorated_1(self): ... # TODO: should emit [override-of-final-method] +60 | @lossy_decorator +61 | def decorated_2(self): ... # TODO: should emit [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` ``` error[invalid-method-override]: Invalid override of method `foo` - --> src/mdtest_snippet.pyi:91:9 + --> src/mdtest_snippet.pyi:74:9 | -89 | # error: [override-of-final-method] -90 | # error: [invalid-method-override] -91 | def foo(): ... +72 | # error: [override-of-final-method] +73 | # error: [invalid-method-override] +74 | def foo(): ... | ^^^^^ Definition is incompatible with `Parent.foo` -92 | @property -93 | # TODO: we should emit a Liskov violation here too +75 | @property +76 | # TODO: we should emit a Liskov violation here too | ::: src/mdtest_snippet.pyi:7:9 | @@ -428,8 +421,8 @@ error[invalid-method-override]: Invalid override of method `foo` 6 | @final 7 | def foo(self): ... | --------- `Parent.foo` defined here - 8 | - 9 | @final + 8 | @final + 9 | @property | info: `Grandchild.foo` is a staticmethod but `Parent.foo` is an instance method info: This violates the Liskov Substitution Principle @@ -439,14 +432,14 @@ info: rule `invalid-method-override` is enabled by default ``` error[override-of-final-method]: Cannot override `Parent.foo` - --> src/mdtest_snippet.pyi:91:9 + --> src/mdtest_snippet.pyi:74:9 | -89 | # error: [override-of-final-method] -90 | # error: [invalid-method-override] -91 | def foo(): ... +72 | # error: [override-of-final-method] +73 | # error: [invalid-method-override] +74 | def foo(): ... | ^^^ Overrides a definition from superclass `Parent` -92 | @property -93 | # TODO: we should emit a Liskov violation here too +75 | @property +76 | # TODO: we should emit a Liskov violation here too | info: `Parent.foo` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:6:5 @@ -456,49 +449,49 @@ info: `Parent.foo` is decorated with `@final`, forbidding overrides | ------ 7 | def foo(self): ... | --- `Parent.foo` defined here -8 | -9 | @final +8 | @final +9 | @property | help: Remove the override of `foo` info: rule `override-of-final-method` is enabled by default -85 | # type or on an instance, it will behave the same from the caller's perspective. The only -86 | # difference is whether the method body gets access to `self`, which is not a -87 | # concern of Liskov. +68 | # type or on an instance, it will behave the same from the caller's perspective. The only +69 | # difference is whether the method body gets access to `self`, which is not a +70 | # concern of Liskov. - @staticmethod - # error: [override-of-final-method] - # error: [invalid-method-override] - def foo(): ... -88 + -89 | @property -90 | # TODO: we should emit a Liskov violation here too -91 | # error: [override-of-final-method] +71 + +72 | @property +73 | # TODO: we should emit a Liskov violation here too +74 | # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Parent.my_property1` - --> src/mdtest_snippet.pyi:95:9 + --> src/mdtest_snippet.pyi:78:9 | -93 | # TODO: we should emit a Liskov violation here too -94 | # error: [override-of-final-method] -95 | def my_property1(self) -> str: ... +76 | # TODO: we should emit a Liskov violation here too +77 | # error: [override-of-final-method] +78 | def my_property1(self) -> str: ... | ^^^^^^^^^^^^ Overrides a definition from superclass `Parent` -96 | # TODO: we should emit a Liskov violation here too -97 | # error: [override-of-final-method] +79 | # TODO: we should emit a Liskov violation here too +80 | # error: [override-of-final-method] | info: `Parent.my_property1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:9:5 + --> src/mdtest_snippet.pyi:8:5 | + 6 | @final 7 | def foo(self): ... - 8 | - 9 | @final + 8 | @final | ------ -10 | @property -11 | def my_property1(self) -> int: ... + 9 | @property +10 | def my_property1(self) -> int: ... | ------------ `Parent.my_property1` defined here -12 | -13 | @property +11 | @property +12 | @final | help: Remove the override of `my_property1` info: rule `override-of-final-method` is enabled by default @@ -507,27 +500,27 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `Parent.class_method1` - --> src/mdtest_snippet.pyi:98:5 - | - 96 | # TODO: we should emit a Liskov violation here too - 97 | # error: [override-of-final-method] - 98 | class_method1 = None - | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` - 99 | -100 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: - | + --> src/mdtest_snippet.pyi:81:5 + | +79 | # TODO: we should emit a Liskov violation here too +80 | # error: [override-of-final-method] +81 | class_method1 = None + | ^^^^^^^^^^^^^ Overrides a definition from superclass `Parent` +82 | +83 | # Diagnostic edge case: `final` is very far away from the method definition in the source code: + | info: `Parent.class_method1` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:21:5 + --> src/mdtest_snippet.pyi:17:5 | -19 | def my_property3(self) -> int: ... -20 | -21 | @final +15 | @final +16 | def my_property3(self) -> int: ... +17 | @final | ------ -22 | @classmethod -23 | def class_method1(cls) -> int: ... +18 | @classmethod +19 | def class_method1(cls) -> int: ... | ------------- `Parent.class_method1` defined here -24 | -25 | @classmethod +20 | @classmethod +21 | @final | help: Remove the override of `class_method1` info: rule `override-of-final-method` is enabled by default @@ -536,37 +529,37 @@ info: rule `override-of-final-method` is enabled by default ``` error[override-of-final-method]: Cannot override `Foo.bar` - --> src/mdtest_snippet.pyi:129:9 + --> src/mdtest_snippet.pyi:112:9 | -128 | class Baz(Foo): -129 | def bar(self): ... # error: [override-of-final-method] +111 | class Baz(Foo): +112 | def bar(self): ... # error: [override-of-final-method] | ^^^ Overrides a definition from superclass `Foo` | info: `Foo.bar` is decorated with `@final`, forbidding overrides - --> src/mdtest_snippet.pyi:107:5 + --> src/mdtest_snippet.pyi:90:5 | -106 | class Foo: -107 | @final + 89 | class Foo: + 90 | @final | ------ -108 | @identity -109 | @identity + 91 | @identity + 92 | @identity | - ::: src/mdtest_snippet.pyi:126:9 + ::: src/mdtest_snippet.pyi:109:9 | -124 | @identity -125 | @identity -126 | def bar(self): ... +107 | @identity +108 | @identity +109 | def bar(self): ... | --- `Foo.bar` defined here -127 | -128 | class Baz(Foo): +110 | +111 | class Baz(Foo): | help: Remove the override of `bar` info: rule `override-of-final-method` is enabled by default -126 | def bar(self): ... -127 | -128 | class Baz(Foo): +109 | def bar(self): ... +110 | +111 | class Baz(Foo): - def bar(self): ... # error: [override-of-final-method] -129 + pass # error: [override-of-final-method] +112 + pass # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` 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 94e882fb8de86..06ab93e3e26a6 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" @@ -21,49 +21,45 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 6 | def bar(self, x: str) -> str: ... 7 | @overload 8 | def bar(self, x: int) -> int: ... - 9 | -10 | @final -11 | @overload -12 | def baz(self, x: str) -> str: ... -13 | @overload -14 | def baz(self, x: int) -> int: ... -15 | -16 | class ChildOfGood(Good): -17 | @overload -18 | def bar(self, x: str) -> str: ... -19 | @overload -20 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] -21 | + 9 | @final +10 | @overload +11 | def baz(self, x: str) -> str: ... +12 | @overload +13 | def baz(self, x: int) -> int: ... +14 | +15 | class ChildOfGood(Good): +16 | @overload +17 | def bar(self, x: str) -> str: ... +18 | @overload +19 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] +20 | @overload +21 | def baz(self, x: str) -> str: ... 22 | @overload -23 | def baz(self, x: str) -> str: ... -24 | @overload -25 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] -26 | -27 | class Bad: +23 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] +24 | +25 | class Bad: +26 | @overload +27 | def bar(self, x: str) -> str: ... 28 | @overload -29 | def bar(self, x: str) -> str: ... -30 | @overload -31 | @final -32 | # error: [invalid-overload] -33 | def bar(self, x: int) -> int: ... -34 | +29 | @final +30 | # error: [invalid-overload] +31 | def bar(self, x: int) -> int: ... +32 | @overload +33 | def baz(self, x: str) -> str: ... +34 | @final 35 | @overload -36 | def baz(self, x: str) -> str: ... -37 | @final -38 | @overload -39 | # error: [invalid-overload] -40 | def baz(self, x: int) -> int: ... -41 | -42 | class ChildOfBad(Bad): -43 | @overload -44 | def bar(self, x: str) -> str: ... -45 | @overload -46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] -47 | -48 | @overload -49 | def baz(self, x: str) -> str: ... -50 | @overload -51 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] +36 | # error: [invalid-overload] +37 | def baz(self, x: int) -> int: ... +38 | +39 | class ChildOfBad(Bad): +40 | @overload +41 | def bar(self, x: str) -> str: ... +42 | @overload +43 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] +44 | @overload +45 | def baz(self, x: str) -> str: ... +46 | @overload +47 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] ``` ## main.py @@ -135,14 +131,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md ``` error[override-of-final-method]: Cannot override `Good.bar` - --> src/stub.pyi:20:9 + --> src/stub.pyi:19:9 | -18 | def bar(self, x: str) -> str: ... -19 | @overload -20 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] +17 | def bar(self, x: str) -> str: ... +18 | @overload +19 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] | ^^^ Overrides a definition from superclass `Good` -21 | -22 | @overload +20 | @overload +21 | def baz(self, x: str) -> str: ... | info: `Good.bar` is decorated with `@final`, forbidding overrides --> src/stub.pyi:5:5 @@ -158,80 +154,80 @@ info: `Good.bar` is decorated with `@final`, forbidding overrides | help: Remove all overloads for `bar` info: rule `override-of-final-method` is enabled by default -14 | def baz(self, x: int) -> int: ... -15 | -16 | class ChildOfGood(Good): +13 | def baz(self, x: int) -> int: ... +14 | +15 | class ChildOfGood(Good): - @overload - def bar(self, x: str) -> str: ... - @overload - def bar(self, x: int) -> int: ... # error: [override-of-final-method] -17 + -18 + # error: [override-of-final-method] -19 | +16 + +17 + # error: [override-of-final-method] +18 | @overload +19 | def baz(self, x: str) -> str: ... 20 | @overload -21 | def baz(self, x: str) -> str: ... note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Good.baz` - --> src/stub.pyi:25:9 + --> src/stub.pyi:23:9 | -23 | def baz(self, x: str) -> str: ... -24 | @overload -25 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] +21 | def baz(self, x: str) -> str: ... +22 | @overload +23 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] | ^^^ Overrides a definition from superclass `Good` -26 | -27 | class Bad: +24 | +25 | class Bad: | info: `Good.baz` is decorated with `@final`, forbidding overrides - --> src/stub.pyi:10:5 + --> src/stub.pyi:9:5 | + 7 | @overload 8 | def bar(self, x: int) -> int: ... - 9 | -10 | @final + 9 | @final | ------ -11 | @overload -12 | def baz(self, x: str) -> str: ... +10 | @overload +11 | def baz(self, x: str) -> str: ... | --- `Good.baz` defined here -13 | @overload -14 | def baz(self, x: int) -> int: ... +12 | @overload +13 | def baz(self, x: int) -> int: ... | help: Remove all overloads for `baz` info: rule `override-of-final-method` is enabled by default -19 | @overload -20 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] -21 | +17 | def bar(self, x: str) -> str: ... +18 | @overload +19 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] - @overload - def baz(self, x: str) -> str: ... - @overload - def baz(self, x: int) -> int: ... # error: [override-of-final-method] -22 + -23 + # error: [override-of-final-method] -24 | -25 | class Bad: -26 | @overload +20 + +21 + # error: [override-of-final-method] +22 | +23 | class Bad: +24 | @overload 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:29:9 + --> src/stub.pyi:27:9 | -27 | class Bad: -28 | @overload -29 | def bar(self, x: str) -> str: ... +25 | class Bad: +26 | @overload +27 | def bar(self, x: str) -> str: ... | --- First overload defined here -30 | @overload -31 | @final +28 | @overload +29 | @final | ------ -32 | # error: [invalid-overload] -33 | def bar(self, x: int) -> int: ... +30 | # error: [invalid-overload] +31 | def bar(self, x: int) -> int: ... | ^^^ -34 | -35 | @overload +32 | @overload +33 | def baz(self, x: str) -> str: ... | info: rule `invalid-overload` is enabled by default @@ -239,19 +235,20 @@ info: rule `invalid-overload` is enabled by default ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/stub.pyi:36:9 + --> src/stub.pyi:33:9 | -35 | @overload -36 | def baz(self, x: str) -> str: ... +31 | def bar(self, x: int) -> int: ... +32 | @overload +33 | def baz(self, x: str) -> str: ... | --- First overload defined here -37 | @final +34 | @final | ------ -38 | @overload -39 | # error: [invalid-overload] -40 | def baz(self, x: int) -> int: ... +35 | @overload +36 | # error: [invalid-overload] +37 | def baz(self, x: int) -> int: ... | ^^^ -41 | -42 | class ChildOfBad(Bad): +38 | +39 | class ChildOfBad(Bad): | info: rule `invalid-overload` is enabled by default @@ -259,72 +256,73 @@ info: rule `invalid-overload` is enabled by default ``` error[override-of-final-method]: Cannot override `Bad.bar` - --> src/stub.pyi:46:9 + --> src/stub.pyi:43:9 | -44 | def bar(self, x: str) -> str: ... -45 | @overload -46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] +41 | def bar(self, x: str) -> str: ... +42 | @overload +43 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] | ^^^ Overrides a definition from superclass `Bad` -47 | -48 | @overload +44 | @overload +45 | def baz(self, x: str) -> str: ... | info: `Bad.bar` is decorated with `@final`, forbidding overrides - --> src/stub.pyi:29:9 + --> src/stub.pyi:27:9 | -27 | class Bad: -28 | @overload -29 | def bar(self, x: str) -> str: ... +25 | class Bad: +26 | @overload +27 | def bar(self, x: str) -> str: ... | --- `Bad.bar` defined here -30 | @overload -31 | @final +28 | @overload +29 | @final | help: Remove all overloads for `bar` info: rule `override-of-final-method` is enabled by default -40 | def baz(self, x: int) -> int: ... -41 | -42 | class ChildOfBad(Bad): +37 | def baz(self, x: int) -> int: ... +38 | +39 | class ChildOfBad(Bad): - @overload - def bar(self, x: str) -> str: ... - @overload - def bar(self, x: int) -> int: ... # error: [override-of-final-method] -43 + -44 + # error: [override-of-final-method] -45 | -46 | @overload -47 | def baz(self, x: str) -> str: ... +40 + +41 + # error: [override-of-final-method] +42 | @overload +43 | def baz(self, x: str) -> str: ... +44 | @overload note: This is an unsafe fix and may change runtime behavior ``` ``` error[override-of-final-method]: Cannot override `Bad.baz` - --> src/stub.pyi:51:9 + --> src/stub.pyi:47:9 | -49 | def baz(self, x: str) -> str: ... -50 | @overload -51 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] +45 | def baz(self, x: str) -> str: ... +46 | @overload +47 | def baz(self, x: int) -> int: ... # error: [override-of-final-method] | ^^^ Overrides a definition from superclass `Bad` | info: `Bad.baz` is decorated with `@final`, forbidding overrides - --> src/stub.pyi:36:9 + --> src/stub.pyi:33:9 | -35 | @overload -36 | def baz(self, x: str) -> str: ... +31 | def bar(self, x: int) -> int: ... +32 | @overload +33 | def baz(self, x: str) -> str: ... | --- `Bad.baz` defined here -37 | @final -38 | @overload +34 | @final +35 | @overload | help: Remove all overloads for `baz` info: rule `override-of-final-method` is enabled by default -45 | @overload -46 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] -47 | +41 | def bar(self, x: str) -> str: ... +42 | @overload +43 | def bar(self, x: int) -> int: ... # error: [override-of-final-method] - @overload - def baz(self, x: str) -> str: ... - @overload - def baz(self, x: int) -> int: ... # error: [override-of-final-method] -48 + -49 + # error: [override-of-final-method] +44 + +45 + # error: [override-of-final-method] note: This is an unsafe fix and may change runtime behavior ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloads_in_statica\342\200\246_(29a698d9deaf7318).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloads_in_statica\342\200\246_(29a698d9deaf7318).snap" index 452942063e638..93ed069c37535 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloads_in_statica\342\200\246_(29a698d9deaf7318).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/final.md_-_Tests_for_the_`@typi\342\200\246_-_Overloads_in_statica\342\200\246_(29a698d9deaf7318).snap" @@ -21,48 +21,50 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/final.md 6 | @overload 7 | @final 8 | def method(self, x: int) -> int: ... - 9 | else: -10 | @overload -11 | def method(self, x: int) -> int: ... -12 | @overload -13 | def method(self, x: str) -> str: ... -14 | -15 | if sys.version_info >= (3, 10): -16 | @overload -17 | def method2(self, x: int) -> int: ... -18 | else: -19 | @overload -20 | @final -21 | def method2(self, x: int) -> int: ... -22 | @overload -23 | def method2(self, x: str) -> str: ... -24 | -25 | class Bar(Foo): -26 | @overload -27 | def method(self, x: int) -> int: ... + 9 | +10 | else: +11 | @overload +12 | def method(self, x: int) -> int: ... +13 | @overload +14 | def method(self, x: str) -> str: ... +15 | +16 | if sys.version_info >= (3, 10): +17 | @overload +18 | def method2(self, x: int) -> int: ... +19 | +20 | else: +21 | @overload +22 | @final +23 | def method2(self, x: int) -> int: ... +24 | @overload +25 | def method2(self, x: str) -> str: ... +26 | +27 | class Bar(Foo): 28 | @overload -29 | def method(self, x: str) -> str: ... # error: [override-of-final-method] -30 | -31 | # This is fine: the only overload that is marked `@final` -32 | # is in a statically-unreachable branch -33 | @overload -34 | def method2(self, x: int) -> int: ... +29 | def method(self, x: int) -> int: ... +30 | @overload +31 | def method(self, x: str) -> str: ... # error: [override-of-final-method] +32 | +33 | # This is fine: the only overload that is marked `@final` +34 | # is in a statically-unreachable branch 35 | @overload -36 | def method2(self, x: str) -> str: ... +36 | def method2(self, x: int) -> int: ... +37 | @overload +38 | def method2(self, x: str) -> str: ... ``` # Diagnostics ``` error[override-of-final-method]: Cannot override `Foo.method` - --> src/mdtest_snippet.pyi:29:9 + --> src/mdtest_snippet.pyi:31:9 | -27 | def method(self, x: int) -> int: ... -28 | @overload -29 | def method(self, x: str) -> str: ... # error: [override-of-final-method] +29 | def method(self, x: int) -> int: ... +30 | @overload +31 | def method(self, x: str) -> str: ... # error: [override-of-final-method] | ^^^^^^ Overrides a definition from superclass `Foo` -30 | -31 | # This is fine: the only overload that is marked `@final` +32 | +33 | # This is fine: the only overload that is marked `@final` | info: `Foo.method` is decorated with `@final`, forbidding overrides --> src/mdtest_snippet.pyi:7:9 @@ -73,23 +75,23 @@ info: `Foo.method` is decorated with `@final`, forbidding overrides | ------ 8 | def method(self, x: int) -> int: ... | ------ `Foo.method` defined here - 9 | else: -10 | @overload + 9 | +10 | else: | help: Remove all overloads for `method` info: rule `override-of-final-method` is enabled by default -23 | def method2(self, x: str) -> str: ... -24 | -25 | class Bar(Foo): +25 | def method2(self, x: str) -> str: ... +26 | +27 | class Bar(Foo): - @overload - def method(self, x: int) -> int: ... - @overload - def method(self, x: str) -> str: ... # error: [override-of-final-method] -26 + -27 + # error: [override-of-final-method] -28 | -29 | # This is fine: the only overload that is marked `@final` -30 | # is in a statically-unreachable branch +28 + +29 + # error: [override-of-final-method] +30 | +31 | # This is fine: the only overload that is marked `@final` +32 | # is in a statically-unreachable branch note: This is an unsafe fix and may change runtime behavior ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callabl\342\200\246_(49a21e4b7fe6e97b).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callabl\342\200\246_(49a21e4b7fe6e97b).snap" index 127807c467c41..47c76b3f11e3c 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callabl\342\200\246_(49a21e4b7fe6e97b).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly-not-callabl\342\200\246_(49a21e4b7fe6e97b).snap" @@ -18,41 +18,43 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md 3 | if flag: 4 | def __call__(self, *args, **kwargs) -> int: 5 | return 42 - 6 | else: - 7 | __call__: None = None - 8 | - 9 | class Iterable1: -10 | __getitem__: CustomCallable = CustomCallable() -11 | -12 | class Iterable2: -13 | if flag: -14 | def __getitem__(self, key: int) -> int: -15 | return 42 -16 | else: -17 | __getitem__: None = None -18 | -19 | # error: [not-iterable] -20 | for x in Iterable1(): -21 | # TODO... `int` might be ideal here? -22 | reveal_type(x) # revealed: int | Unknown -23 | -24 | # error: [not-iterable] -25 | for y in Iterable2(): -26 | # TODO... `int` might be ideal here? -27 | reveal_type(y) # revealed: int | Unknown + 6 | + 7 | else: + 8 | __call__: None = None + 9 | +10 | class Iterable1: +11 | __getitem__: CustomCallable = CustomCallable() +12 | +13 | class Iterable2: +14 | if flag: +15 | def __getitem__(self, key: int) -> int: +16 | return 42 +17 | +18 | else: +19 | __getitem__: None = None +20 | +21 | # error: [not-iterable] +22 | for x in Iterable1(): +23 | # TODO... `int` might be ideal here? +24 | reveal_type(x) # revealed: int | Unknown +25 | +26 | # error: [not-iterable] +27 | for y in Iterable2(): +28 | # TODO... `int` might be ideal here? +29 | reveal_type(y) # revealed: int | Unknown ``` # Diagnostics ``` error[not-iterable]: Object of type `Iterable1` may not be iterable - --> src/mdtest_snippet.py:20:14 + --> src/mdtest_snippet.py:22:14 | -19 | # error: [not-iterable] -20 | for x in Iterable1(): +21 | # error: [not-iterable] +22 | for x in Iterable1(): | ^^^^^^^^^^^ -21 | # TODO... `int` might be ideal here? -22 | reveal_type(x) # revealed: int | Unknown +23 | # TODO... `int` might be ideal here? +24 | reveal_type(x) # revealed: int | Unknown | info: It has no `__iter__` method and its `__getitem__` attribute is invalid info: `__getitem__` has type `CustomCallable`, which is not callable @@ -62,13 +64,13 @@ info: rule `not-iterable` is enabled by default ``` error[not-iterable]: Object of type `Iterable2` may not be iterable - --> src/mdtest_snippet.py:25:14 + --> src/mdtest_snippet.py:27:14 | -24 | # error: [not-iterable] -25 | for y in Iterable2(): +26 | # error: [not-iterable] +27 | for y in Iterable2(): | ^^^^^^^^^^^ -26 | # TODO... `int` might be ideal here? -27 | reveal_type(y) # revealed: int | Unknown +28 | # TODO... `int` might be ideal here? +29 | reveal_type(y) # revealed: int | Unknown | info: It has no `__iter__` method and its `__getitem__` attribute is invalid info: `__getitem__` has type `(bound method Iterable2.__getitem__(key: int) -> int) | None`, which is not callable diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6388761c90a0555c).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6388761c90a0555c).snap" index fe43e8ab2c73a..0605039019098 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6388761c90a0555c).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6388761c90a0555c).snap" @@ -22,37 +22,39 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md 7 | if flag: 8 | def __iter__(self) -> Iterator: 9 | return Iterator() -10 | else: -11 | def __iter__(self, invalid_extra_arg) -> Iterator: -12 | return Iterator() -13 | -14 | # error: [not-iterable] -15 | for x in Iterable1(): -16 | reveal_type(x) # revealed: int -17 | -18 | class Iterable2: -19 | if flag: -20 | def __iter__(self) -> Iterator: -21 | return Iterator() -22 | else: -23 | __iter__: None = None -24 | -25 | # error: [not-iterable] -26 | for x in Iterable2(): -27 | # TODO: `int` would probably be better here: -28 | reveal_type(x) # revealed: int | Unknown +10 | +11 | else: +12 | def __iter__(self, invalid_extra_arg) -> Iterator: +13 | return Iterator() +14 | +15 | # error: [not-iterable] +16 | for x in Iterable1(): +17 | reveal_type(x) # revealed: int +18 | +19 | class Iterable2: +20 | if flag: +21 | def __iter__(self) -> Iterator: +22 | return Iterator() +23 | +24 | else: +25 | __iter__: None = None +26 | +27 | # error: [not-iterable] +28 | for x in Iterable2(): +29 | # TODO: `int` would probably be better here: +30 | reveal_type(x) # revealed: int | Unknown ``` # Diagnostics ``` error[not-iterable]: Object of type `Iterable1` may not be iterable - --> src/mdtest_snippet.py:15:14 + --> src/mdtest_snippet.py:16:14 | -14 | # error: [not-iterable] -15 | for x in Iterable1(): +15 | # error: [not-iterable] +16 | for x in Iterable1(): | ^^^^^^^^^^^ -16 | reveal_type(x) # revealed: int +17 | reveal_type(x) # revealed: int | info: Its `__iter__` method may have an invalid signature info: Type of `__iter__` is `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)` @@ -63,13 +65,13 @@ info: rule `not-iterable` is enabled by default ``` error[not-iterable]: Object of type `Iterable2` may not be iterable - --> src/mdtest_snippet.py:26:14 + --> src/mdtest_snippet.py:28:14 | -25 | # error: [not-iterable] -26 | for x in Iterable2(): +27 | # error: [not-iterable] +28 | for x in Iterable2(): | ^^^^^^^^^^^ -27 | # TODO: `int` would probably be better here: -28 | reveal_type(x) # revealed: int | Unknown +29 | # TODO: `int` would probably be better here: +30 | reveal_type(x) # revealed: int | Unknown | info: Its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable info: rule `not-iterable` is enabled by default diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6805a6032e504b63).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6805a6032e504b63).snap" index dd6ae5cbd4e48..eba39535f849b 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6805a6032e504b63).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(6805a6032e504b63).snap" @@ -18,38 +18,40 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md 3 | if flag: 4 | def __getitem__(self, item: int) -> str: 5 | return "foo" - 6 | else: - 7 | __getitem__: None = None - 8 | - 9 | class Iterable2: -10 | if flag: -11 | def __getitem__(self, item: int) -> str: -12 | return "foo" -13 | else: -14 | def __getitem__(self, item: str) -> int: -15 | return 42 -16 | -17 | # error: [not-iterable] -18 | for x in Iterable1(): -19 | # TODO: `str` might be better -20 | reveal_type(x) # revealed: str | Unknown -21 | -22 | # error: [not-iterable] -23 | for y in Iterable2(): -24 | reveal_type(y) # revealed: str | int + 6 | + 7 | else: + 8 | __getitem__: None = None + 9 | +10 | class Iterable2: +11 | if flag: +12 | def __getitem__(self, item: int) -> str: +13 | return "foo" +14 | +15 | else: +16 | def __getitem__(self, item: str) -> int: +17 | return 42 +18 | +19 | # error: [not-iterable] +20 | for x in Iterable1(): +21 | # TODO: `str` might be better +22 | reveal_type(x) # revealed: str | Unknown +23 | +24 | # error: [not-iterable] +25 | for y in Iterable2(): +26 | reveal_type(y) # revealed: str | int ``` # Diagnostics ``` error[not-iterable]: Object of type `Iterable1` may not be iterable - --> src/mdtest_snippet.py:18:14 + --> src/mdtest_snippet.py:20:14 | -17 | # error: [not-iterable] -18 | for x in Iterable1(): +19 | # error: [not-iterable] +20 | for x in Iterable1(): | ^^^^^^^^^^^ -19 | # TODO: `str` might be better -20 | reveal_type(x) # revealed: str | Unknown +21 | # TODO: `str` might be better +22 | reveal_type(x) # revealed: str | Unknown | info: It has no `__iter__` method and its `__getitem__` attribute is invalid info: `__getitem__` has type `(bound method Iterable1.__getitem__(item: int) -> str) | None`, which is not callable @@ -59,12 +61,12 @@ info: rule `not-iterable` is enabled by default ``` error[not-iterable]: Object of type `Iterable2` may not be iterable - --> src/mdtest_snippet.py:23:14 + --> src/mdtest_snippet.py:25:14 | -22 | # error: [not-iterable] -23 | for y in Iterable2(): +24 | # error: [not-iterable] +25 | for y in Iterable2(): | ^^^^^^^^^^^ -24 | reveal_type(y) # revealed: str | int +26 | reveal_type(y) # revealed: str | int | info: It has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(c626bde8651b643a).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(c626bde8651b643a).snap" index 62ad428a213bf..59a6dcceb03d6 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(c626bde8651b643a).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_invalid_`__\342\200\246_(c626bde8651b643a).snap" @@ -18,45 +18,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md 3 | if flag: 4 | def __next__(self) -> int: 5 | return 42 - 6 | else: - 7 | def __next__(self, invalid_extra_arg) -> str: - 8 | return "foo" - 9 | -10 | class Iterator2: -11 | if flag: -12 | def __next__(self) -> int: -13 | return 42 -14 | else: -15 | __next__: None = None -16 | -17 | class Iterable1: -18 | def __iter__(self) -> Iterator1: -19 | return Iterator1() -20 | -21 | class Iterable2: -22 | def __iter__(self) -> Iterator2: -23 | return Iterator2() -24 | -25 | # error: [not-iterable] -26 | for x in Iterable1(): -27 | reveal_type(x) # revealed: int | str -28 | -29 | # error: [not-iterable] -30 | for y in Iterable2(): -31 | # TODO: `int` would probably be better here: -32 | reveal_type(y) # revealed: int | Unknown + 6 | + 7 | else: + 8 | def __next__(self, invalid_extra_arg) -> str: + 9 | return "foo" +10 | +11 | class Iterator2: +12 | if flag: +13 | def __next__(self) -> int: +14 | return 42 +15 | +16 | else: +17 | __next__: None = None +18 | +19 | class Iterable1: +20 | def __iter__(self) -> Iterator1: +21 | return Iterator1() +22 | +23 | class Iterable2: +24 | def __iter__(self) -> Iterator2: +25 | return Iterator2() +26 | +27 | # error: [not-iterable] +28 | for x in Iterable1(): +29 | reveal_type(x) # revealed: int | str +30 | +31 | # error: [not-iterable] +32 | for y in Iterable2(): +33 | # TODO: `int` would probably be better here: +34 | reveal_type(y) # revealed: int | Unknown ``` # Diagnostics ``` error[not-iterable]: Object of type `Iterable1` may not be iterable - --> src/mdtest_snippet.py:26:14 + --> src/mdtest_snippet.py:28:14 | -25 | # error: [not-iterable] -26 | for x in Iterable1(): +27 | # error: [not-iterable] +28 | for x in Iterable1(): | ^^^^^^^^^^^ -27 | reveal_type(x) # revealed: int | str +29 | reveal_type(x) # revealed: int | str | info: Its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method info: Expected signature for `__next__` is `def __next__(self): ...` @@ -66,13 +68,13 @@ info: rule `not-iterable` is enabled by default ``` error[not-iterable]: Object of type `Iterable2` may not be iterable - --> src/mdtest_snippet.py:30:14 + --> src/mdtest_snippet.py:32:14 | -29 | # error: [not-iterable] -30 | for y in Iterable2(): +31 | # error: [not-iterable] +32 | for y in Iterable2(): | ^^^^^^^^^^^ -31 | # TODO: `int` would probably be better here: -32 | reveal_type(y) # revealed: int | Unknown +33 | # TODO: `int` would probably be better here: +34 | reveal_type(y) # revealed: int | Unknown | info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable info: rule `not-iterable` is enabled by default diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__\342\200\246_(77269542b8e81774).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__\342\200\246_(77269542b8e81774).snap" index 8e03240782555..c40d1731696ac 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__\342\200\246_(77269542b8e81774).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/for.md_-_For_loops_-_Possibly_missing_`__\342\200\246_(77269542b8e81774).snap" @@ -22,45 +22,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md 7 | if flag: 8 | def __getitem__(self, item: int) -> str: 9 | return "foo" -10 | else: -11 | __getitem__: None = None -12 | -13 | if flag2: -14 | def __iter__(self) -> Iterator: -15 | return Iterator() -16 | -17 | class Iterable2: -18 | if flag: -19 | def __getitem__(self, item: int) -> str: -20 | return "foo" -21 | else: -22 | def __getitem__(self, item: str) -> int: -23 | return 42 -24 | if flag2: -25 | def __iter__(self) -> Iterator: -26 | return Iterator() -27 | -28 | # error: [not-iterable] -29 | for x in Iterable1(): -30 | # TODO: `bytes | str` might be better -31 | reveal_type(x) # revealed: bytes | str | Unknown -32 | -33 | # error: [not-iterable] -34 | for y in Iterable2(): -35 | reveal_type(y) # revealed: bytes | str | int +10 | +11 | else: +12 | __getitem__: None = None +13 | +14 | if flag2: +15 | def __iter__(self) -> Iterator: +16 | return Iterator() +17 | +18 | class Iterable2: +19 | if flag: +20 | def __getitem__(self, item: int) -> str: +21 | return "foo" +22 | +23 | else: +24 | def __getitem__(self, item: str) -> int: +25 | return 42 +26 | if flag2: +27 | def __iter__(self) -> Iterator: +28 | return Iterator() +29 | +30 | # error: [not-iterable] +31 | for x in Iterable1(): +32 | # TODO: `bytes | str` might be better +33 | reveal_type(x) # revealed: bytes | str | Unknown +34 | +35 | # error: [not-iterable] +36 | for y in Iterable2(): +37 | reveal_type(y) # revealed: bytes | str | int ``` # Diagnostics ``` error[not-iterable]: Object of type `Iterable1` may not be iterable - --> src/mdtest_snippet.py:29:14 + --> src/mdtest_snippet.py:31:14 | -28 | # error: [not-iterable] -29 | for x in Iterable1(): +30 | # error: [not-iterable] +31 | for x in Iterable1(): | ^^^^^^^^^^^ -30 | # TODO: `bytes | str` might be better -31 | reveal_type(x) # revealed: bytes | str | Unknown +32 | # TODO: `bytes | str` might be better +33 | reveal_type(x) # revealed: bytes | str | Unknown | info: It may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable info: rule `not-iterable` is enabled by default @@ -69,12 +71,12 @@ info: rule `not-iterable` is enabled by default ``` error[not-iterable]: Object of type `Iterable2` may not be iterable - --> src/mdtest_snippet.py:34:14 + --> src/mdtest_snippet.py:36:14 | -33 | # error: [not-iterable] -34 | for y in Iterable2(): +35 | # error: [not-iterable] +36 | for y in Iterable2(): | ^^^^^^^^^^^ -35 | reveal_type(y) # revealed: bytes | str | int +37 | reveal_type(y) # revealed: bytes | str | int | info: It may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno\342\200\246_-_Invalid_union_return\342\200\246_(fedf62ffaca0f2d7).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno\342\200\246_-_Invalid_union_return\342\200\246_(fedf62ffaca0f2d7).snap" index d6f1b605d90d9..197bf1b27eb25 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno\342\200\246_-_Invalid_union_return\342\200\246_(fedf62ffaca0f2d7).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno\342\200\246_-_Invalid_union_return\342\200\246_(fedf62ffaca0f2d7).snap" @@ -18,26 +18,25 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_awai 3 | 4 | class UnawaitableUnion: 5 | if datetime.today().weekday() == 6: - 6 | - 7 | def __await__(self) -> typing.Generator[typing.Any, None, None]: - 8 | yield + 6 | def __await__(self) -> typing.Generator[typing.Any, None, None]: + 7 | yield + 8 | 9 | else: -10 | -11 | def __await__(self) -> int: -12 | return 5 -13 | -14 | async def main() -> None: -15 | await UnawaitableUnion() # error: [invalid-await] +10 | def __await__(self) -> int: +11 | return 5 +12 | +13 | async def main() -> None: +14 | await UnawaitableUnion() # error: [invalid-await] ``` # Diagnostics ``` error[invalid-await]: `UnawaitableUnion` is not awaitable - --> src/mdtest_snippet.py:15:11 + --> src/mdtest_snippet.py:14:11 | -14 | async def main() -> None: -15 | await UnawaitableUnion() # error: [invalid-await] +13 | async def main() -> None: +14 | await UnawaitableUnion() # error: [invalid-await] | ^^^^^^^^^^^^^^^^^^ | info: `__await__` returns `Generator[Any, None, None] | int`, which is not a valid iterator diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" index 0947fbec30351..2d3718c4f93a7 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" @@ -39,7 +39,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md 24 | def method(self, x: int, *args, **kwargs): ... # fine 25 | 26 | class Sub9(Super): -27 | def method(self, x: int, extra_positional_arg=42, /): ... # fine +27 | def method(self, x: int, extra_positional_arg=42, /): ... # fine 28 | 29 | class Sub10(Super): 30 | def method(self, x: int, extra_pos_or_kw_arg=42): ... # fine diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_includes\342\200\246_(d2532518c44112c8).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_includes\342\200\246_(d2532518c44112c8).snap" index d86a74b2dbb8e..3298896e03f6d 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_includes\342\200\246_(d2532518c44112c8).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/mro.md_-_Method_Resolution_Or\342\200\246_-_`__bases__`_includes\342\200\246_(d2532518c44112c8).snap" @@ -36,10 +36,11 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md 21 | def f(): 22 | if returns_bool(): 23 | class C: ... -24 | else: -25 | class C: ... -26 | -27 | class D(C): ... # error: [unsupported-base] +24 | +25 | else: +26 | class C: ... +27 | +28 | class D(C): ... # error: [unsupported-base] ``` # Diagnostics @@ -62,12 +63,12 @@ info: rule `unsupported-base` is enabled by default ``` warning[unsupported-base]: Unsupported class base - --> src/mdtest_snippet.py:27:13 + --> src/mdtest_snippet.py:28:13 | -25 | class C: ... -26 | -27 | class D(C): ... # error: [unsupported-base] - | ^ Has type `.C @ src/mdtest_snippet.py:23:15'> | .C @ src/mdtest_snippet.py:25:15'>` +26 | class C: ... +27 | +28 | class D(C): ... # error: [unsupported-base] + | ^ Has type `.C @ src/mdtest_snippet.py:23:15'> | .C @ src/mdtest_snippet.py:26:15'>` | info: ty cannot resolve a consistent method resolution order (MRO) for class `D` due to this base info: Only class objects or `Any` are supported as class bases 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 177092cd1a614..9d39d0edc3e4b 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" @@ -54,24 +54,22 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/overloads.md 6 | def method1(self, x: int) -> int: ... 7 | @overload 8 | def method1(self, x: str) -> str: ... - 9 | -10 | @overload -11 | def method2(self, x: int) -> int: ... -12 | @final -13 | @overload -14 | # error: [invalid-overload] -15 | def method2(self, x: str) -> str: ... -16 | -17 | @overload -18 | def method3(self, x: int) -> int: ... -19 | @final + 9 | @overload +10 | def method2(self, x: int) -> int: ... +11 | @final +12 | @overload +13 | # error: [invalid-overload] +14 | def method2(self, x: str) -> str: ... +15 | @overload +16 | def method3(self, x: int) -> int: ... +17 | @final +18 | @overload +19 | def method3(self, x: str) -> int: ... # error: [invalid-overload] 20 | @overload -21 | def method3(self, x: str) -> int: ... # error: [invalid-overload] -22 | @overload -23 | @final -24 | def method3(self, x: bytes) -> bytes: ... # error: [invalid-overload] -25 | @overload -26 | def method3(self, x: bytearray) -> bytearray: ... +21 | @final +22 | def method3(self, x: bytes) -> bytes: ... # error: [invalid-overload] +23 | @overload +24 | def method3(self, x: bytearray) -> bytearray: ... ``` # Diagnostics @@ -117,19 +115,20 @@ info: rule `invalid-overload` is enabled by default ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/mdtest_snippet.pyi:11:9 + --> src/mdtest_snippet.pyi:10:9 | -10 | @overload -11 | def method2(self, x: int) -> int: ... + 8 | def method1(self, x: str) -> str: ... + 9 | @overload +10 | def method2(self, x: int) -> int: ... | ------- First overload defined here -12 | @final +11 | @final | ------ -13 | @overload -14 | # error: [invalid-overload] -15 | def method2(self, x: str) -> str: ... +12 | @overload +13 | # error: [invalid-overload] +14 | def method2(self, x: str) -> str: ... | ^^^^^^^ -16 | -17 | @overload +15 | @overload +16 | def method3(self, x: int) -> int: ... | info: rule `invalid-overload` is enabled by default @@ -137,18 +136,19 @@ info: rule `invalid-overload` is enabled by default ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/mdtest_snippet.pyi:18:9 + --> src/mdtest_snippet.pyi:16:9 | -17 | @overload -18 | def method3(self, x: int) -> int: ... +14 | def method2(self, x: str) -> str: ... +15 | @overload +16 | def method3(self, x: int) -> int: ... | ------- First overload defined here -19 | @final +17 | @final | ------ -20 | @overload -21 | def method3(self, x: str) -> int: ... # error: [invalid-overload] +18 | @overload +19 | def method3(self, x: str) -> int: ... # error: [invalid-overload] | ^^^^^^^ -22 | @overload -23 | @final +20 | @overload +21 | @final | info: rule `invalid-overload` is enabled by default @@ -156,21 +156,22 @@ info: rule `invalid-overload` is enabled by default ``` error[invalid-overload]: `@final` decorator should be applied only to the first overload - --> src/mdtest_snippet.pyi:18:9 + --> src/mdtest_snippet.pyi:16:9 | -17 | @overload -18 | def method3(self, x: int) -> int: ... +14 | def method2(self, x: str) -> str: ... +15 | @overload +16 | def method3(self, x: int) -> int: ... | ------- First overload defined here -19 | @final +17 | @final +18 | @overload +19 | def method3(self, x: str) -> int: ... # error: [invalid-overload] 20 | @overload -21 | def method3(self, x: str) -> int: ... # error: [invalid-overload] -22 | @overload -23 | @final +21 | @final | ------ -24 | def method3(self, x: bytes) -> bytes: ... # error: [invalid-overload] +22 | def method3(self, x: bytes) -> bytes: ... # error: [invalid-overload] | ^^^^^^^ -25 | @overload -26 | def method3(self, x: bytearray) -> bytearray: ... +23 | @overload +24 | def method3(self, x: bytearray) -> bytearray: ... | info: rule `invalid-overload` is enabled by default diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap index a8ec582431b10..edccc7b3101e2 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/override.md_-_`typing.override`_-_Basics_(b7c220f8171f11f0).snap @@ -23,223 +23,185 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/override.md 8 | 9 | class Parent: 10 | def foo(self): ... - 11 | - 12 | @property - 13 | def my_property1(self) -> int: ... - 14 | - 15 | @property - 16 | def my_property2(self) -> int: ... + 11 | @property + 12 | def my_property1(self) -> int: ... + 13 | @property + 14 | def my_property2(self) -> int: ... + 15 | + 16 | baz = None 17 | - 18 | baz = None - 19 | - 20 | @classmethod - 21 | def class_method1(cls) -> int: ... - 22 | - 23 | @staticmethod - 24 | def static_method1() -> int: ... - 25 | - 26 | @classmethod - 27 | def class_method2(cls) -> int: ... - 28 | - 29 | @staticmethod - 30 | def static_method2() -> int: ... - 31 | - 32 | @lossy_decorator - 33 | def decorated_1(self): ... - 34 | - 35 | @lossy_decorator - 36 | def decorated_2(self): ... - 37 | - 38 | @lossy_decorator - 39 | def decorated_3(self): ... - 40 | - 41 | class Child(Parent): + 18 | @classmethod + 19 | def class_method1(cls) -> int: ... + 20 | @staticmethod + 21 | def static_method1() -> int: ... + 22 | @classmethod + 23 | def class_method2(cls) -> int: ... + 24 | @staticmethod + 25 | def static_method2() -> int: ... + 26 | @lossy_decorator + 27 | def decorated_1(self): ... + 28 | @lossy_decorator + 29 | def decorated_2(self): ... + 30 | @lossy_decorator + 31 | def decorated_3(self): ... + 32 | + 33 | class Child(Parent): + 34 | @override + 35 | def foo(self): ... # fine: overrides `Parent.foo` + 36 | @property + 37 | @override + 38 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` + 39 | @override + 40 | @property + 41 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2` 42 | @override - 43 | def foo(self): ... # fine: overrides `Parent.foo` - 44 | - 45 | @property - 46 | @override - 47 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` - 48 | - 49 | @override - 50 | @property - 51 | def my_property2(self) -> int: ... # fine: overrides `Parent.my_property2` - 52 | + 43 | def baz(self): ... # fine: overrides `Parent.baz` + 44 | @classmethod + 45 | @override + 46 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` + 47 | @staticmethod + 48 | @override + 49 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1` + 50 | @override + 51 | @classmethod + 52 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` 53 | @override - 54 | def baz(self): ... # fine: overrides `Parent.baz` - 55 | - 56 | @classmethod - 57 | @override - 58 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` - 59 | - 60 | @staticmethod - 61 | @override - 62 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1` - 63 | - 64 | @override - 65 | @classmethod - 66 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` - 67 | + 54 | @staticmethod + 55 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2` + 56 | @override + 57 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1` + 58 | @override + 59 | @lossy_decorator + 60 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2` + 61 | @lossy_decorator + 62 | @override + 63 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3` + 64 | + 65 | class OtherChild(Parent): ... + 66 | + 67 | class Grandchild(OtherChild): 68 | @override - 69 | @staticmethod - 70 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2` - 71 | - 72 | @override - 73 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1` - 74 | - 75 | @override - 76 | @lossy_decorator - 77 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2` - 78 | - 79 | @lossy_decorator - 80 | @override - 81 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3` - 82 | - 83 | class OtherChild(Parent): ... - 84 | - 85 | class Grandchild(OtherChild): - 86 | @override - 87 | def foo(self): ... # fine: overrides `Parent.foo` - 88 | + 69 | def foo(self): ... # fine: overrides `Parent.foo` + 70 | @override + 71 | @property + 72 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` + 73 | @override + 74 | def baz(self): ... # fine: overrides `Parent.baz` + 75 | @classmethod + 76 | @override + 77 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` + 78 | @staticmethod + 79 | @override + 80 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1` + 81 | @override + 82 | @classmethod + 83 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` + 84 | @override + 85 | @staticmethod + 86 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2` + 87 | @override + 88 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1` 89 | @override - 90 | @property - 91 | def my_property1(self) -> int: ... # fine: overrides `Parent.my_property1` - 92 | + 90 | @lossy_decorator + 91 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2` + 92 | @lossy_decorator 93 | @override - 94 | def baz(self): ... # fine: overrides `Parent.baz` + 94 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3` 95 | - 96 | @classmethod + 96 | class Invalid: 97 | @override - 98 | def class_method1(cls) -> int: ... # fine: overrides `Parent.class_method1` - 99 | -100 | @staticmethod -101 | @override -102 | def static_method1() -> int: ... # fine: overrides `Parent.static_method1` -103 | -104 | @override -105 | @classmethod -106 | def class_method2(cls) -> int: ... # fine: overrides `Parent.class_method2` -107 | + 98 | def ___reprrr__(self): ... # error: [invalid-explicit-override] + 99 | @override +100 | @classmethod +101 | def foo(self): ... # error: [invalid-explicit-override] +102 | @classmethod +103 | @override +104 | def bar(self): ... # error: [invalid-explicit-override] +105 | @staticmethod +106 | @override +107 | def baz(): ... # error: [invalid-explicit-override] 108 | @override 109 | @staticmethod -110 | def static_method2() -> int: ... # fine: overrides `Parent.static_method2` -111 | +110 | def eggs(): ... # error: [invalid-explicit-override] +111 | @property 112 | @override -113 | def decorated_1(self): ... # fine: overrides `Parent.decorated_1` -114 | -115 | @override -116 | @lossy_decorator -117 | def decorated_2(self): ... # fine: overrides `Parent.decorated_2` -118 | -119 | @lossy_decorator -120 | @override -121 | def decorated_3(self): ... # fine: overrides `Parent.decorated_3` -122 | -123 | class Invalid: -124 | @override -125 | def ___reprrr__(self): ... # error: [invalid-explicit-override] -126 | -127 | @override -128 | @classmethod -129 | def foo(self): ... # error: [invalid-explicit-override] -130 | -131 | @classmethod +113 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override] +114 | @override +115 | @property +116 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override] +117 | @property +118 | @override +119 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override] +120 | @bad_settable_property.setter +121 | def bad_settable_property(self, x: int) -> None: ... +122 | @lossy_decorator +123 | @override +124 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here +125 | @override +126 | @lossy_decorator +127 | def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here +128 | +129 | # TODO: all overrides in this class should cause us to emit *Liskov* violations, +130 | # but not `@override` violations +131 | class LiskovViolatingButNotOverrideViolating(Parent): 132 | @override -133 | def bar(self): ... # error: [invalid-explicit-override] -134 | -135 | @staticmethod -136 | @override -137 | def baz(): ... # error: [invalid-explicit-override] -138 | -139 | @override -140 | @staticmethod -141 | def eggs(): ... # error: [invalid-explicit-override] -142 | -143 | @property -144 | @override -145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override] -146 | -147 | @override -148 | @property -149 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override] -150 | -151 | @property -152 | @override -153 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override] -154 | @bad_settable_property.setter -155 | def bad_settable_property(self, x: int) -> None: ... -156 | -157 | @lossy_decorator -158 | @override -159 | def lossy(self): ... # TODO: should emit `invalid-explicit-override` here -160 | -161 | @override -162 | @lossy_decorator -163 | def lossy2(self): ... # TODO: should emit `invalid-explicit-override` here -164 | -165 | # TODO: all overrides in this class should cause us to emit *Liskov* violations, -166 | # but not `@override` violations -167 | class LiskovViolatingButNotOverrideViolating(Parent): -168 | @override -169 | @property -170 | def foo(self) -> int: ... -171 | -172 | @override -173 | def my_property1(self) -> int: ... -174 | -175 | # TODO: This maybe shouldn't be a Liskov violation? Whether called on the type or -176 | # on an instance, it will behave the same from the caller's perspective. The only difference -177 | # is whether the method body gets access to `cls`, which is not a concern of Liskov. -178 | @staticmethod -179 | @override -180 | def class_method1() -> int: ... # error: [invalid-method-override] -181 | -182 | @classmethod -183 | @override -184 | def static_method1(cls) -> int: ... -185 | -186 | # Diagnostic edge case: `override` is very far away from the method definition in the source code: -187 | -188 | T = TypeVar("T") -189 | -190 | def identity(x: T) -> T: ... -191 | -192 | class Foo: -193 | @override -194 | @identity -195 | @identity -196 | @identity -197 | @identity -198 | @identity -199 | @identity -200 | @identity -201 | @identity -202 | @identity -203 | @identity -204 | @identity -205 | @identity -206 | @identity -207 | @identity -208 | @identity -209 | @identity -210 | @identity -211 | @identity -212 | def bar(self): ... # error: [invalid-explicit-override] +133 | @property +134 | def foo(self) -> int: ... +135 | @override +136 | def my_property1(self) -> int: ... +137 | +138 | # TODO: This maybe shouldn't be a Liskov violation? Whether called on the type or +139 | # on an instance, it will behave the same from the caller's perspective. The only difference +140 | # is whether the method body gets access to `cls`, which is not a concern of Liskov. +141 | @staticmethod +142 | @override +143 | def class_method1() -> int: ... # error: [invalid-method-override] +144 | @classmethod +145 | @override +146 | def static_method1(cls) -> int: ... +147 | +148 | # Diagnostic edge case: `override` is very far away from the method definition in the source code: +149 | +150 | T = TypeVar("T") +151 | +152 | def identity(x: T) -> T: ... +153 | +154 | class Foo: +155 | @override +156 | @identity +157 | @identity +158 | @identity +159 | @identity +160 | @identity +161 | @identity +162 | @identity +163 | @identity +164 | @identity +165 | @identity +166 | @identity +167 | @identity +168 | @identity +169 | @identity +170 | @identity +171 | @identity +172 | @identity +173 | @identity +174 | def bar(self): ... # error: [invalid-explicit-override] ``` # Diagnostics ``` error[invalid-explicit-override]: Method `___reprrr__` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:124:5 + --> src/mdtest_snippet.pyi:97:5 | -123 | class Invalid: -124 | @override + 96 | class Invalid: + 97 | @override | --------- -125 | def ___reprrr__(self): ... # error: [invalid-explicit-override] + 98 | def ___reprrr__(self): ... # error: [invalid-explicit-override] | ^^^^^^^^^^^ -126 | -127 | @override + 99 | @override +100 | @classmethod | info: No `___reprrr__` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -248,17 +210,17 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-explicit-override]: Method `foo` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:127:5 + --> src/mdtest_snippet.pyi:99:5 | -125 | def ___reprrr__(self): ... # error: [invalid-explicit-override] -126 | -127 | @override + 97 | @override + 98 | def ___reprrr__(self): ... # error: [invalid-explicit-override] + 99 | @override | --------- -128 | @classmethod -129 | def foo(self): ... # error: [invalid-explicit-override] +100 | @classmethod +101 | def foo(self): ... # error: [invalid-explicit-override] | ^^^ -130 | -131 | @classmethod +102 | @classmethod +103 | @override | info: No `foo` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -267,15 +229,16 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:132:5 + --> src/mdtest_snippet.pyi:103:5 | -131 | @classmethod -132 | @override +101 | def foo(self): ... # error: [invalid-explicit-override] +102 | @classmethod +103 | @override | --------- -133 | def bar(self): ... # error: [invalid-explicit-override] +104 | def bar(self): ... # error: [invalid-explicit-override] | ^^^ -134 | -135 | @staticmethod +105 | @staticmethod +106 | @override | info: No `bar` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -284,15 +247,16 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-explicit-override]: Method `baz` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:136:5 + --> src/mdtest_snippet.pyi:106:5 | -135 | @staticmethod -136 | @override +104 | def bar(self): ... # error: [invalid-explicit-override] +105 | @staticmethod +106 | @override | --------- -137 | def baz(): ... # error: [invalid-explicit-override] +107 | def baz(): ... # error: [invalid-explicit-override] | ^^^ -138 | -139 | @override +108 | @override +109 | @staticmethod | info: No `baz` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -301,17 +265,17 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-explicit-override]: Method `eggs` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:139:5 + --> src/mdtest_snippet.pyi:108:5 | -137 | def baz(): ... # error: [invalid-explicit-override] -138 | -139 | @override +106 | @override +107 | def baz(): ... # error: [invalid-explicit-override] +108 | @override | --------- -140 | @staticmethod -141 | def eggs(): ... # error: [invalid-explicit-override] +109 | @staticmethod +110 | def eggs(): ... # error: [invalid-explicit-override] | ^^^^ -142 | -143 | @property +111 | @property +112 | @override | info: No `eggs` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -320,15 +284,16 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-explicit-override]: Method `bad_property1` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:144:5 + --> src/mdtest_snippet.pyi:112:5 | -143 | @property -144 | @override +110 | def eggs(): ... # error: [invalid-explicit-override] +111 | @property +112 | @override | --------- -145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override] +113 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override] | ^^^^^^^^^^^^^ -146 | -147 | @override +114 | @override +115 | @property | info: No `bad_property1` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -337,17 +302,17 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-explicit-override]: Method `bad_property2` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:147:5 + --> src/mdtest_snippet.pyi:114:5 | -145 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override] -146 | -147 | @override +112 | @override +113 | def bad_property1(self) -> int: ... # error: [invalid-explicit-override] +114 | @override | --------- -148 | @property -149 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override] +115 | @property +116 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override] | ^^^^^^^^^^^^^ -150 | -151 | @property +117 | @property +118 | @override | info: No `bad_property2` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -356,15 +321,16 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-explicit-override]: Method `bad_settable_property` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:152:5 + --> src/mdtest_snippet.pyi:118:5 | -151 | @property -152 | @override +116 | def bad_property2(self) -> int: ... # error: [invalid-explicit-override] +117 | @property +118 | @override | --------- -153 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override] +119 | def bad_settable_property(self) -> int: ... # error: [invalid-explicit-override] | ^^^^^^^^^^^^^^^^^^^^^ -154 | @bad_settable_property.setter -155 | def bad_settable_property(self, x: int) -> None: ... +120 | @bad_settable_property.setter +121 | def bad_settable_property(self, x: int) -> None: ... | info: No `bad_settable_property` definitions were found on any superclasses of `Invalid` info: rule `invalid-explicit-override` is enabled by default @@ -373,22 +339,22 @@ info: rule `invalid-explicit-override` is enabled by default ``` error[invalid-method-override]: Invalid override of method `class_method1` - --> src/mdtest_snippet.pyi:180:9 + --> src/mdtest_snippet.pyi:143:9 | -178 | @staticmethod -179 | @override -180 | def class_method1() -> int: ... # error: [invalid-method-override] +141 | @staticmethod +142 | @override +143 | def class_method1() -> int: ... # error: [invalid-method-override] | ^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.class_method1` -181 | -182 | @classmethod +144 | @classmethod +145 | @override | - ::: src/mdtest_snippet.pyi:21:9 + ::: src/mdtest_snippet.pyi:19:9 | - 20 | @classmethod - 21 | def class_method1(cls) -> int: ... + 18 | @classmethod + 19 | def class_method1(cls) -> int: ... | ------------------------- `Parent.class_method1` defined here - 22 | - 23 | @staticmethod + 20 | @staticmethod + 21 | def static_method1() -> int: ... | info: `LiskovViolatingButNotOverrideViolating.class_method1` is a staticmethod but `Parent.class_method1` is a classmethod info: This violates the Liskov Substitution Principle @@ -398,20 +364,20 @@ info: rule `invalid-method-override` is enabled by default ``` error[invalid-explicit-override]: Method `bar` is decorated with `@override` but does not override anything - --> src/mdtest_snippet.pyi:212:9 + --> src/mdtest_snippet.pyi:174:9 | -210 | @identity -211 | @identity -212 | def bar(self): ... # error: [invalid-explicit-override] +172 | @identity +173 | @identity +174 | def bar(self): ... # error: [invalid-explicit-override] | ^^^ | - ::: src/mdtest_snippet.pyi:193:5 + ::: src/mdtest_snippet.pyi:155:5 | -192 | class Foo: -193 | @override +154 | class Foo: +155 | @override | --------- -194 | @identity -195 | @identity +156 | @identity +157 | @identity | info: No `bar` definitions were found on any superclasses of `Foo` info: rule `invalid-explicit-override` is enabled by default diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Protocol_members_in_\342\200\246_(21be5d9bdab1c844).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Protocol_members_in_\342\200\246_(21be5d9bdab1c844).snap" index e6cfdc0316586..997ef6b48460e 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Protocol_members_in_\342\200\246_(21be5d9bdab1c844).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/protocols.md_-_Protocols_-_Protocol_members_in_\342\200\246_(21be5d9bdab1c844).snap" @@ -21,25 +21,26 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md 6 | a: int 7 | b = 42 8 | def c(self) -> None: ... - 9 | else: -10 | d: int -11 | e = 56 # error: [ambiguous-protocol-member] -12 | def f(self) -> None: ... -13 | -14 | reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]] + 9 | +10 | else: +11 | d: int +12 | e = 56 # error: [ambiguous-protocol-member] +13 | def f(self) -> None: ... +14 | +15 | reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]] ``` # Diagnostics ``` warning[ambiguous-protocol-member]: Cannot assign to undeclared variable in the body of a protocol class - --> src/mdtest_snippet.py:11:9 + --> src/mdtest_snippet.py:12:9 | - 9 | else: -10 | d: int -11 | e = 56 # error: [ambiguous-protocol-member] +10 | else: +11 | d: int +12 | e = 56 # error: [ambiguous-protocol-member] | ^^^^^^ Consider adding an annotation, e.g. `e: int = ...` -12 | def f(self) -> None: ... +13 | def f(self) -> None: ... | info: Assigning to an undeclared variable in a protocol class leads to an ambiguous interface --> src/mdtest_snippet.py:4:7 diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Invalid_Usages_-_Diagnostic_when_the_\342\200\246_(93e8ab913ead83b2).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Invalid_Usages_-_Diagnostic_when_the_\342\200\246_(93e8ab913ead83b2).snap" index b5dc280c54b17..ea958ae88bfa5 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Invalid_Usages_-_Diagnostic_when_the_\342\200\246_(93e8ab913ead83b2).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/super.md_-_Super_-_Invalid_Usages_-_Diagnostic_when_the_\342\200\246_(93e8ab913ead83b2).snap" @@ -13,28 +13,30 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md ## mdtest_snippet.py ``` -1 | def coinflip() -> bool: -2 | return False -3 | -4 | def f(): -5 | if coinflip(): -6 | class A: ... -7 | else: -8 | class A: ... -9 | super(A, A()) # error: [invalid-super-argument] + 1 | def coinflip() -> bool: + 2 | return False + 3 | + 4 | def f(): + 5 | if coinflip(): + 6 | class A: ... + 7 | + 8 | else: + 9 | class A: ... +10 | +11 | super(A, A()) # error: [invalid-super-argument] ``` # Diagnostics ``` error[invalid-super-argument]: Argument is not a valid class - --> src/mdtest_snippet.py:9:5 - | -7 | else: -8 | class A: ... -9 | super(A, A()) # error: [invalid-super-argument] - | ^^^^^^^^^^^^^ Argument has type `.A @ src/mdtest_snippet.py:6:15'> | .A @ src/mdtest_snippet.py:8:15'>` - | + --> src/mdtest_snippet.py:11:5 + | + 9 | class A: ... +10 | +11 | super(A, A()) # error: [invalid-super-argument] + | ^^^^^^^^^^^^^ Argument has type `.A @ src/mdtest_snippet.py:6:15'> | .A @ src/mdtest_snippet.py:9:15'>` + | info: rule `invalid-super-argument` is enabled by default ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" index 97c84e5f76698..79b8b4a2280e1 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/sync.md_-_With_statements_-_Accidental_use_of_no\342\200\246_(b07503f9b773ea61).snap" @@ -19,7 +19,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/with/sync.md 4 | 5 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `__exit__`" 6 | with Manager(): -7 | ... +7 | pass ``` # Diagnostics @@ -31,7 +31,7 @@ error[invalid-context-manager]: Object of type `Manager` cannot be used with `wi 5 | # error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it does not implement `__enter__` and `… 6 | with Manager(): | ^^^^^^^^^ -7 | ... +7 | pass | info: Objects of type `Manager` can be used as async context managers info: Consider using `async with` here