From 0c46e6cfaa6b39f5da353ba7b360447ceee910dc Mon Sep 17 00:00:00 2001 From: Rodrigo Neto Date: Tue, 21 Nov 2023 10:31:02 -0300 Subject: [PATCH] Fix lack of support on checking generic interfaces Previosly verifying non-generic classes that correctly implements generic interfaces used to fail, as the interface holds an special method coming from the typing.Generic inheritance (`__class_getitem__`). The changes here solves this limitation. --- src/oop_ext/interface/_interface.py | 2 +- .../interface/_tests/test_interface.py | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/oop_ext/interface/_interface.py b/src/oop_ext/interface/_interface.py index d93994a..c3ac262 100644 --- a/src/oop_ext/interface/_interface.py +++ b/src/oop_ext/interface/_interface.py @@ -747,7 +747,7 @@ def AssertImplementsFullChecking( # set of methods that might be declared in interfaces but should be not be required by implementations -_INTERFACE_METHODS_TO_IGNORE = {"__init_subclass__"} +_INTERFACE_METHODS_TO_IGNORE = {"__init_subclass__", "__class_getitem__"} def _AssertImplementsFullChecking( diff --git a/src/oop_ext/interface/_tests/test_interface.py b/src/oop_ext/interface/_tests/test_interface.py index 6c0706d..9b2da6c 100644 --- a/src/oop_ext/interface/_tests/test_interface.py +++ b/src/oop_ext/interface/_tests/test_interface.py @@ -1077,3 +1077,36 @@ def AfterCaption(*args): assert Foo.GetCaption() == "Foo" assert Foo().GetValues("m") == [0.1, 10.0] + + +def testGenericInterface() -> None: + """Generic interfaces need to support checking on both generic and non-generic implementers""" + from typing import Generic, TypeVar + + T = TypeVar("T", covariant=True) + + class IFoo(Interface, Generic[T], TypeCheckingSupport): + def GetOutput(self) -> T: # type:ignore[empty-body] + ... + + @ImplementsInterface(IFoo, no_check=True) # Will check later. + class GenericFoo(Generic[T]): + def __init__(self, output: T) -> None: + self.output = output + + @Implements(IFoo.GetOutput) + def GetOutput(self) -> T: + return self.output + + @ImplementsInterface(IFoo, no_check=True) + class NonGenericFoo: + @Implements(IFoo.GetOutput) + def GetOutput(self) -> int: + return 1 + + # This works out of the box. + AssertImplements(GenericFoo, IFoo) + AssertImplements(GenericFoo[str](output="foo"), IFoo) + + # This only works if we skip the verification of `__class_getitem__` method. + AssertImplements(NonGenericFoo, IFoo)