Skip to content

Add support for future.utils.with_metaclass #7210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ = <metaclass>" in Python 2
Expand All @@ -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]
Expand Down
110 changes: 110 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
-- ----

Expand Down
44 changes: 44 additions & 0 deletions test-data/unit/check-newsemanal.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/lib-stub/future/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from __future__ import absolute_import, print_function
2 changes: 2 additions & 0 deletions test-data/unit/lib-stub/future/utils.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from typing import Type
def with_metaclass(meta: Type[type], *bases: type) -> type: pass