From 3a9af7c72f91da4f66bde8db13a009b8d400697d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 May 2023 10:43:42 -0300 Subject: [PATCH] Add support for partial/full specialization of Generic interfaces --- CHANGELOG.rst | 1 + src/oop_ext/interface/_interface.py | 10 +++++++ .../interface/_tests/test_interface.py | 28 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d27262e..0604430 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,7 @@ 2.2.0 (UNRELEASED) ------------------ +* Added support for interfaces that subclass ``Generic`` and partial/full implementations (meaning the implementation itself is not ``Generic``). * Added support for Python 3.10. 2.1.0 (2021-03-19) diff --git a/src/oop_ext/interface/_interface.py b/src/oop_ext/interface/_interface.py index d93994a..8bd188b 100644 --- a/src/oop_ext/interface/_interface.py +++ b/src/oop_ext/interface/_interface.py @@ -684,6 +684,16 @@ def __GetInterfaceMethodsAndAttrs( if attr in self.INTERFACE_OWN_METHODS: continue + # error: Argument 2 to "issubclass" has incompatible type ""; expected "_ClassInfo" [arg-type] + # This fails during type-checking, but is valid at runtime; let's keep this + # check to ensure we do not break existing cases by accident. + if issubclass(interface, Generic): # type:ignore[arg-type] + # Do not check some specific methods that Generic declares, + # in case we have a Generic interface and a partial/complete + # specialization as implementation (see testGenericSupport). + if attr in ("__class_getitem__", "__init_subclass__"): + continue + val = getattr(interface, attr) if type(val) in self._ATTRIBUTE_CLASSES: diff --git a/src/oop_ext/interface/_tests/test_interface.py b/src/oop_ext/interface/_tests/test_interface.py index 6c0706d..f809b0e 100644 --- a/src/oop_ext/interface/_tests/test_interface.py +++ b/src/oop_ext/interface/_tests/test_interface.py @@ -1,5 +1,6 @@ import re import textwrap +from typing import Any from typing import List import pytest @@ -1077,3 +1078,30 @@ def AfterCaption(*args): assert Foo.GetCaption() == "Foo" assert Foo().GetValues("m") == [0.1, 10.0] + + +def testGenericSupport() -> None: + from typing import Generic, TypeVar + + T = TypeVar("T") + + class AnInterface(Generic[T], interface.Interface, interface.TypeCheckingSupport): + def foo(self, v: T) -> T: + ... + + @interface.ImplementsInterface(AnInterface) + class Specialization: + def foo(self, v: str) -> str: + return v + "bar" + + @interface.ImplementsInterface(AnInterface) + class FooGeneric(Generic[T]): + def foo(self, v: T) -> T: + return v + + spec = Specialization() + assert spec.foo("hello") == "hellobar" + + generic = FooGeneric[Any]() + assert generic.foo("hello") == "hello" + assert generic.foo(10) == 10