diff --git a/conformance/tests/specialtypes_any.py b/conformance/tests/specialtypes_any.py index 420920563..5c7286adb 100644 --- a/conformance/tests/specialtypes_any.py +++ b/conformance/tests/specialtypes_any.py @@ -6,6 +6,7 @@ # > Every type is consistent with Any. +from collections.abc import Iterator from typing import Any, Callable, assert_type @@ -87,4 +88,106 @@ def method1(self) -> int: assert_type(a.method2(), Any) assert_type(ClassA.method3(), Any) +# > When ``Any`` is present in the bases of a type, +# > it should be considered only after all other known types in the MRO. +class ClassKnown: + + classvar1 = "" + + def __iter__(self) -> Iterator[str]: + yield from self.attr1 + + def __init__(self): + self.attr1: str = "" + + def method1(self) -> str: + return "" + +class AnyFirst(Any, ClassKnown): + + def method2(self) -> str: + return "" + +class AnyLast(ClassKnown, Any): + def method2(self) -> str: + return "" + +class GetattrKnown(ClassKnown): + def __getattr__(self, name: str) -> int: + return 1 + +class AnyFirstGetAttr(Any, GetattrKnown): + def method2(self) -> str: + return "" + +class AnyLastGetAttr(GetattrKnown, Any): + def method2(self) -> str: + return "" + +class AnySub(Any): + ... + +# primarily included to demonstrate intent that this is for the full MRO +class AnySubFirst(AnySub, ClassKnown): + def method2(self) -> str: + ... + + + +af = AnyFirst() +assert_type(af.method1(), str) +assert_type(af.method2(), str) +assert_type(af.attr1, str) +assert_type(af.non_exist_method(), Any) +assert_type(af.non_exist_attr, Any) +assert_type(af.classvar1, str) +assert_type(AnyFirst.classvar1, str) +assert_type(iter(af), Iterator[str]) + +al = AnyLast() +assert_type(al.method1(), str) +assert_type(al.method2(), str) +assert_type(al.attr1, str) +assert_type(al.non_exist_method(), Any) +assert_type(al.non_exist_attr, Any) +assert_type(al.classvar1, str) +assert_type(AnyLast.classvar1, str) +assert_type(iter(al), Iterator[str]) + + +# this example has Any deeper in the inheritence than the bases, specifically as +# an ancestor of the first base, with other known methods and attributes in the second. +full_mro_checked = AnySubFirst() + +assert_type(full_mro_checked.method1(), str) +assert_type(full_mro_checked.method2(), str) +assert_type(full_mro_checked.attr1, str) +assert_type(al.non_exist_method(), Any) +assert_type(al.non_exist_attr, Any) +assert_type(full_mro_checked.classvar1, str) +assert_type(AnySubFirst.classvar1, str) +assert_type(iter(full_mro_checked), Iterator[str]) + +# Note The next two examples check different bases, one of which provides a `__getattr__` +# ensuring the behavior is the same for unaffected cases + +af_getattr = AnyFirstGetAttr() + +assert_type(af_getattr.method1(), str) +assert_type(af_getattr.method2(), str) +assert_type(af_getattr.attr1, str) +assert_type(af_getattr.triggers_getattr, int) +assert_type(af_getattr.classvar1, str) +assert_type(AnyFirstGetAttr.classvar1, str) +assert_type(iter(af_getattr), Iterator[str]) + +al_getattr = AnyLastGetAttr() + +assert_type(al_getattr.method1(), str) +assert_type(al_getattr.method2(), str) +assert_type(al_getattr.attr1, str) +assert_type(al_getattr.triggers_getattr, int) +assert_type(al_getattr.classvar1, str) +assert_type(AnyLastGetAttr.classvar1, str) +assert_type(iter(al_getattr), Iterator[str]) \ No newline at end of file diff --git a/docs/spec/special-types.rst b/docs/spec/special-types.rst index 76ac2cdec..f46a9fd64 100644 --- a/docs/spec/special-types.rst +++ b/docs/spec/special-types.rst @@ -47,7 +47,8 @@ to ``tuple[Any, ...]``. As well, a bare ``Any`` can also be used as a base class. This can be useful for avoiding type checker errors with classes that can duck type anywhere or -are highly dynamic. +are highly dynamic. When ``Any`` is present in the MRO of a type, +it should be considered only after all other known types in the MRO. .. _`none`: