Skip to content

Support six.with_metaclass #3364

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 7 commits into from
May 17, 2017
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
26 changes: 26 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,12 @@ def clean_up_bases_and_infer_type_variables(self, defn: ClassDef) -> None:
Now we will remove Generic[T] from bases of Foo and infer that the
type variable 'T' is a type argument of Foo.

We also process six.with_metaclass() here.

Note that this is performed *before* semantic analysis.
"""
# First process six.with_metaclass if present and well-formed
defn.base_type_exprs, defn.metaclass = self.check_with_metaclass(defn)
removed = [] # type: List[int]
declared_tvars = [] # type: TypeVarList
for i, base_expr in enumerate(defn.base_type_exprs):
Expand Down Expand Up @@ -938,6 +942,7 @@ def analyze_base_classes(self, defn: ClassDef) -> None:

base_types = [] # type: List[Instance]
info = defn.info

for base_expr in defn.base_type_exprs:
try:
base = self.expr_to_analyzed_type(base_expr)
Expand Down Expand Up @@ -989,6 +994,27 @@ def analyze_base_classes(self, defn: ClassDef) -> None:
if defn.info.is_enum and defn.type_vars:
self.fail("Enum class cannot be generic", defn)

def check_with_metaclass(self, defn: ClassDef) -> Tuple[List[Expression], Optional[str]]:
# Special-case six.with_metaclass(M, B1, B2, ...).
base_type_exprs, metaclass = defn.base_type_exprs, defn.metaclass
if metaclass is None and len(base_type_exprs) == 1:
base_expr = 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'
and len(base_expr.args) >= 1
and all(kind == ARG_POS for kind in base_expr.arg_kinds)):
metaclass_expr = base_expr.args[0]
if isinstance(metaclass_expr, NameExpr):
metaclass = metaclass_expr.name
elif isinstance(metaclass_expr, MemberExpr):
metaclass = get_member_expr_fullname(metaclass_expr)
else:
self.fail("Dynamic metaclass not supported for '%s'" % defn.name,
metaclass_expr)
return (base_expr.args[1:], metaclass)
return (base_type_exprs, metaclass)

def expr_to_analyzed_type(self, expr: Expression) -> Type:
if isinstance(expr, CallExpr):
expr.accept(self)
Expand Down
91 changes: 91 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -3137,3 +3137,94 @@ class M(type):
class A(metaclass=M): pass
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'

[case testSixWithMetaclass]
import six
class M(type):
x = 5
class A(six.with_metaclass(M)): pass
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'

[case testSixWithMetaclass_python2]
import six
class M(type):
x = 5
class A(six.with_metaclass(M)): pass
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'

[case testFromSixWithMetaclass]
from six import with_metaclass
class M(type):
x = 5
class A(with_metaclass(M)): pass
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'

[case testSixWithMetaclassImportFrom]
import six
from metadefs import M
class A(six.with_metaclass(M)): pass
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'
[file metadefs.py]
class M(type):
x = 5

[case testSixWithMetaclassImport]
import six
import metadefs
class A(six.with_metaclass(metadefs.M)): pass
reveal_type(type(A).x) # E: Revealed type is 'builtins.int'
[file metadefs.py]
class M(type):
x = 5

[case testSixWithMetaclassAndBase]
import six
class M(type):
x = 5
class A:
def foo(self): pass
class B:
def bar(self): pass
class C1(six.with_metaclass(M, A)): pass
class C2(six.with_metaclass(M, A, B)): pass
reveal_type(type(C1).x) # E: Revealed type is 'builtins.int'
reveal_type(type(C2).x) # E: Revealed type is 'builtins.int'
C1().foo()
C1().bar() # E: "C1" has no attribute "bar"
C2().foo()
C2().bar()
C2().baz() # E: "C2" has no attribute "baz"

[case testSixWithMetaclassGenerics]
from typing import Generic, GenericMeta, TypeVar
import six
class DestroyableMeta(type):
pass
class Destroyable(six.with_metaclass(DestroyableMeta)):
pass
T_co = TypeVar('T_co', bound='Destroyable', covariant=True)
class ArcMeta(GenericMeta, DestroyableMeta):
pass
class Arc(six.with_metaclass(ArcMeta, Generic[T_co], Destroyable)):
pass
class MyDestr(Destroyable):
pass
reveal_type(Arc[MyDestr]()) # E: Revealed type is '__main__.Arc[__main__.MyDestr*]'
[builtins fixtures/bool.pyi]

[case testSixWithMetaclassErrors]
import six
class M(type): pass
class A(object): pass
def f() -> type: return M
class C1(six.with_metaclass(M), object): pass # E: Invalid base class
class C2(C1, six.with_metaclass(M)): pass # E: Invalid base class
class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported
class C4(six.with_metaclass(M), metaclass=M): pass # E: Invalid base class
class C5(six.with_metaclass(f())): pass # E: Dynamic metaclass not supported for 'C5'

[case testSixWithMetaclassErrors_python2-skip]
# No error here yet
import six
class M(type): pass
class C4(six.with_metaclass(M)):
__metaclass__ = M
2 changes: 2 additions & 0 deletions test-data/unit/lib-stub/six.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from typing import Type
def with_metaclass(mcls: Type[type], *args: type) -> type: pass
2 changes: 2 additions & 0 deletions test-data/unit/lib-stub/typing.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from abc import abstractmethod

class GenericMeta(type): pass

cast = 0
overload = 0
Any = 0
Expand Down