From 6f487553f0e1589e2918a01dd6c3bb4b883e562c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 4 Apr 2025 21:35:14 +0100 Subject: [PATCH] Allow omitting implementation for abstract overloads --- mypy/checkmember.py | 3 +++ mypy/semanal.py | 9 ++++++++- mypyc/irbuild/prepare.py | 8 ++++++-- test-data/unit/check-functions.test | 25 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 2152e309b1df..1a76372d4731 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -380,6 +380,9 @@ def validate_super_call(node: FuncBase, mx: MemberContext) -> None: if node.impl: impl = node.impl if isinstance(node.impl, FuncDef) else node.impl.func unsafe_super = impl.is_trivial_body + elif not node.is_property and node.items: + assert isinstance(node.items[0], Decorator) + unsafe_super = node.items[0].func.is_trivial_body if unsafe_super: mx.msg.unsafe_super(node.name, node.info.name, mx.context) diff --git a/mypy/semanal.py b/mypy/semanal.py index 60d4f1bde9f8..6d0a62070c8e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1461,8 +1461,15 @@ def handle_missing_overload_implementation(self, defn: OverloadedFuncDef) -> Non item.func.abstract_status = IS_ABSTRACT else: item.abstract_status = IS_ABSTRACT + elif all( + isinstance(item, Decorator) and item.func.abstract_status == IS_ABSTRACT + for item in defn.items + ): + # Since there is no implementation, it can't be called via super(). + if defn.items: + assert isinstance(defn.items[0], Decorator) + defn.items[0].func.is_trivial_body = True else: - # TODO: also allow omitting an implementation for abstract methods in ABCs? self.fail( "An overloaded function outside a stub file must have an implementation", defn, diff --git a/mypyc/irbuild/prepare.py b/mypyc/irbuild/prepare.py index e014d97fedd9..98ff348d8c30 100644 --- a/mypyc/irbuild/prepare.py +++ b/mypyc/irbuild/prepare.py @@ -382,8 +382,12 @@ def prepare_methods_and_attributes( # Handle case for regular function overload else: - assert node.node.impl - prepare_method_def(ir, module_name, cdef, mapper, node.node.impl, options) + if not node.node.impl: + errors.error( + "Overloads without implementation are not supported", path, cdef.line + ) + else: + prepare_method_def(ir, module_name, cdef, mapper, node.node.impl, options) if ir.builtin_base: ir.attributes.clear() diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 8f48d50fc8ec..bd59dfbdfd5e 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3541,3 +3541,28 @@ def f(x: Callable[[Arg(int, 'x')], None]) -> None: pass y: Callable[[Union[int, str]], None] f(y) # E: Argument 1 to "f" has incompatible type "Callable[[Union[int, str]], None]"; expected "Callable[[Arg(int, 'x')], None]" [builtins fixtures/tuple.pyi] + +[case testAbstractOverloadsWithoutImplementationAllowed] +from abc import abstractmethod +from typing import overload, Union + +class Foo: + @overload + @abstractmethod + def foo(self, value: int) -> int: + ... + @overload + @abstractmethod + def foo(self, value: str) -> str: + ... + +class Bar(Foo): + @overload + def foo(self, value: int) -> int: + ... + @overload + def foo(self, value: str) -> str: + ... + + def foo(self, value: Union[int, str]) -> Union[int, str]: + return super().foo(value) # E: Call to abstract method "foo" of "Foo" with trivial body via super() is unsafe