From 2721ae43e603355eb1434e5dc1633748caa6a2a9 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Sun, 14 Jul 2019 10:21:14 -0700 Subject: [PATCH] Add support for future|past.utils.with_metaclass Same function signature and basic behavior as six.with_metaclass --- mypy/semanal.py | 6 +- test-data/unit/check-classes.test | 110 ++++++++++++++++++++ test-data/unit/check-newsemanal.test | 44 ++++++++ test-data/unit/lib-stub/future/__init__.pyi | 1 + test-data/unit/lib-stub/future/utils.pyi | 2 + 5 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 test-data/unit/lib-stub/future/__init__.pyi create mode 100644 test-data/unit/lib-stub/future/utils.pyi diff --git a/mypy/semanal.py b/mypy/semanal.py index 379cc2d926266..cf3e491da26a1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1541,6 +1541,8 @@ def update_metaclass(self, defn: ClassDef) -> None: * __metaclass__ attribute in Python 2 * six.with_metaclass(M, B1, B2, ...) * @six.add_metaclass(M) + * future.utils.with_metaclass(M, B1, B2, ...) + * past.utils.with_metaclass(M, B1, B2, ...) """ # Look for "__metaclass__ = " in Python 2 @@ -1561,7 +1563,9 @@ def update_metaclass(self, defn: ClassDef) -> None: base_expr = defn.base_type_exprs[0] if isinstance(base_expr, CallExpr) and isinstance(base_expr.callee, RefExpr): base_expr.callee.accept(self) - if (base_expr.callee.fullname == 'six.with_metaclass' + if (base_expr.callee.fullname in {'six.with_metaclass', + 'future.utils.with_metaclass', + 'past.utils.with_metaclass'} and len(base_expr.args) >= 1 and all(kind == ARG_POS for kind in base_expr.arg_kinds)): with_meta_expr = base_expr.args[0] diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 59b0e528a1f2c..2be245e922157 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4899,6 +4899,116 @@ class F(six.with_metaclass(t.M)): pass @six.add_metaclass(t.M) class G: pass +-- Special support for future.utils +-- -------------------------------- + +[case testFutureMetaclass] +import future.utils +class M(type): + x = 5 +class A(future.utils.with_metaclass(M)): pass +reveal_type(type(A).x) # N: Revealed type is 'builtins.int' + +[case testFutureMetaclass_python2] +import future.utils +class M(type): + x = 5 +class A(future.utils.with_metaclass(M)): pass +reveal_type(type(A).x) # N: Revealed type is 'builtins.int' + +[case testFromFutureMetaclass] +from future.utils import with_metaclass +class M(type): + x = 5 +class A(with_metaclass(M)): pass +reveal_type(type(A).x) # N: Revealed type is 'builtins.int' + +[case testFutureMetaclassImportFrom] +import future.utils +from metadefs import M +class A(future.utils.with_metaclass(M)): pass +reveal_type(type(A).x) # N: Revealed type is 'builtins.int' +[file metadefs.py] +class M(type): + x = 5 + +[case testFutureMetaclassImport] +import future.utils +import metadefs +class A(future.utils.with_metaclass(metadefs.M)): pass +reveal_type(type(A).x) # N: Revealed type is 'builtins.int' +[file metadefs.py] +class M(type): + x = 5 + +[case testFutureMetaclassAndBase] +from typing import Iterable, Iterator +import future.utils +class M(type, Iterable[int]): + x = 5 + def __iter__(self) -> Iterator[int]: ... +class A: + def foo(self): pass +class B: + def bar(self): pass +class C1(future.utils.with_metaclass(M, A)): pass +class C2(future.utils.with_metaclass(M, A, B)): pass +reveal_type(type(C1).x) # N: Revealed type is 'builtins.int' +reveal_type(type(C2).x) # N: Revealed type is 'builtins.int' +C1().foo() +C1().bar() # E: "C1" has no attribute "bar" +for x in C1: reveal_type(x) # N: Revealed type is 'builtins.int*' +for x in C2: reveal_type(x) # N: Revealed type is 'builtins.int*' +C2().foo() +C2().bar() +C2().baz() # E: "C2" has no attribute "baz" + +[case testFutureMetaclassGenerics] +from typing import Generic, GenericMeta, TypeVar +import future.utils +class DestroyableMeta(type): + pass +class Destroyable(future.utils.with_metaclass(DestroyableMeta)): + pass +T_co = TypeVar('T_co', bound='Destroyable', covariant=True) +class ArcMeta(GenericMeta, DestroyableMeta): + pass +class Arc(future.utils.with_metaclass(ArcMeta, Generic[T_co], Destroyable)): + pass +class MyDestr(Destroyable): + pass +reveal_type(Arc[MyDestr]()) # N: Revealed type is '__main__.Arc[__main__.MyDestr*]' +[builtins fixtures/bool.pyi] +[typing fixtures/typing-full.pyi] + +[case testFutureMetaclassErrors] +import future.utils +class M(type): pass +class A(object): pass +def f() -> type: return M +class C1(future.utils.with_metaclass(M), object): pass # E: Unsupported dynamic base class "future.utils.with_metaclass" +class C2(C1, future.utils.with_metaclass(M)): pass # E: Unsupported dynamic base class "future.utils.with_metaclass" +class C3(future.utils.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported +class C4(future.utils.with_metaclass(M), metaclass=M): pass # E: Multiple metaclass definitions +class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not supported for 'C5' + +class M1(type): pass +class Q1(metaclass=M1): pass +class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Inconsistent metaclass structure for 'CQW' + +[case testFutureMetaclassErrors_python2] +# flags: --python-version 2.7 +import future.utils +class M(type): pass +class C4(future.utils.with_metaclass(M)): # E: Multiple metaclass definitions + __metaclass__ = M + +[case testFutureMetaclassAny] +import t # type: ignore +import future.utils +class E(metaclass=t.M): pass +class F(future.utils.with_metaclass(t.M)): pass + -- Misc -- ---- diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 6a14435ad10c2..8e2dcc72e8fdd 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -1204,6 +1204,50 @@ class A(six.with_metaclass(B, Defer)): class Defer: x: str +[case testNewAnalyzerMetaclassFuture1] +import future.utils + +class A(future.utils.with_metaclass(B)): + pass + +class B(type): + def f(cls) -> int: + return 0 + +reveal_type(A.f()) # N: Revealed type is 'builtins.int' + +[case testNewAnalyzerMetaclassFuture3] +import future.utils + +class A(future.utils.with_metaclass(B, Defer)): + pass + +class B(type): + def f(cls) -> int: + return 0 + +class Defer: + x: str + +reveal_type(A.f()) # N: Revealed type is 'builtins.int' +reveal_type(A.x) # N: Revealed type is 'builtins.str' + +[case testNewAnalyzerMetaclassFuture4] +import future.utils + +class B(type): + def f(cls) -> int: + return 0 + +reveal_type(A.f()) # N: Revealed type is 'builtins.int' +reveal_type(A.x) # N: Revealed type is 'builtins.str' + +class A(future.utils.with_metaclass(B, Defer)): + pass + +class Defer: + x: str + [case testNewAnalyzerMetaclass1_python2] class A: __metaclass__ = B diff --git a/test-data/unit/lib-stub/future/__init__.pyi b/test-data/unit/lib-stub/future/__init__.pyi new file mode 100644 index 0000000000000..b0860a4bae9c6 --- /dev/null +++ b/test-data/unit/lib-stub/future/__init__.pyi @@ -0,0 +1 @@ +from __future__ import absolute_import, print_function diff --git a/test-data/unit/lib-stub/future/utils.pyi b/test-data/unit/lib-stub/future/utils.pyi new file mode 100644 index 0000000000000..1f1e4ec7e5973 --- /dev/null +++ b/test-data/unit/lib-stub/future/utils.pyi @@ -0,0 +1,2 @@ +from typing import Type +def with_metaclass(meta: Type[type], *bases: type) -> type: pass