Skip to content

Commit d607245

Browse files
chadrikilevkivskyi
authored andcommitted
Add support for future|past.utils.with_metaclass (#7210)
This util function has the same signature and basic behavior as `six.with_metaclass`. Fixes #7193.
1 parent 742d33a commit d607245

File tree

5 files changed

+162
-1
lines changed

5 files changed

+162
-1
lines changed

mypy/semanal.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1541,6 +1541,8 @@ def update_metaclass(self, defn: ClassDef) -> None:
15411541
* __metaclass__ attribute in Python 2
15421542
* six.with_metaclass(M, B1, B2, ...)
15431543
* @six.add_metaclass(M)
1544+
* future.utils.with_metaclass(M, B1, B2, ...)
1545+
* past.utils.with_metaclass(M, B1, B2, ...)
15441546
"""
15451547

15461548
# Look for "__metaclass__ = <metaclass>" in Python 2
@@ -1561,7 +1563,9 @@ def update_metaclass(self, defn: ClassDef) -> None:
15611563
base_expr = defn.base_type_exprs[0]
15621564
if isinstance(base_expr, CallExpr) and isinstance(base_expr.callee, RefExpr):
15631565
base_expr.callee.accept(self)
1564-
if (base_expr.callee.fullname == 'six.with_metaclass'
1566+
if (base_expr.callee.fullname in {'six.with_metaclass',
1567+
'future.utils.with_metaclass',
1568+
'past.utils.with_metaclass'}
15651569
and len(base_expr.args) >= 1
15661570
and all(kind == ARG_POS for kind in base_expr.arg_kinds)):
15671571
with_meta_expr = base_expr.args[0]

test-data/unit/check-classes.test

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4899,6 +4899,116 @@ class F(six.with_metaclass(t.M)): pass
48994899
@six.add_metaclass(t.M)
49004900
class G: pass
49014901

4902+
-- Special support for future.utils
4903+
-- --------------------------------
4904+
4905+
[case testFutureMetaclass]
4906+
import future.utils
4907+
class M(type):
4908+
x = 5
4909+
class A(future.utils.with_metaclass(M)): pass
4910+
reveal_type(type(A).x) # N: Revealed type is 'builtins.int'
4911+
4912+
[case testFutureMetaclass_python2]
4913+
import future.utils
4914+
class M(type):
4915+
x = 5
4916+
class A(future.utils.with_metaclass(M)): pass
4917+
reveal_type(type(A).x) # N: Revealed type is 'builtins.int'
4918+
4919+
[case testFromFutureMetaclass]
4920+
from future.utils import with_metaclass
4921+
class M(type):
4922+
x = 5
4923+
class A(with_metaclass(M)): pass
4924+
reveal_type(type(A).x) # N: Revealed type is 'builtins.int'
4925+
4926+
[case testFutureMetaclassImportFrom]
4927+
import future.utils
4928+
from metadefs import M
4929+
class A(future.utils.with_metaclass(M)): pass
4930+
reveal_type(type(A).x) # N: Revealed type is 'builtins.int'
4931+
[file metadefs.py]
4932+
class M(type):
4933+
x = 5
4934+
4935+
[case testFutureMetaclassImport]
4936+
import future.utils
4937+
import metadefs
4938+
class A(future.utils.with_metaclass(metadefs.M)): pass
4939+
reveal_type(type(A).x) # N: Revealed type is 'builtins.int'
4940+
[file metadefs.py]
4941+
class M(type):
4942+
x = 5
4943+
4944+
[case testFutureMetaclassAndBase]
4945+
from typing import Iterable, Iterator
4946+
import future.utils
4947+
class M(type, Iterable[int]):
4948+
x = 5
4949+
def __iter__(self) -> Iterator[int]: ...
4950+
class A:
4951+
def foo(self): pass
4952+
class B:
4953+
def bar(self): pass
4954+
class C1(future.utils.with_metaclass(M, A)): pass
4955+
class C2(future.utils.with_metaclass(M, A, B)): pass
4956+
reveal_type(type(C1).x) # N: Revealed type is 'builtins.int'
4957+
reveal_type(type(C2).x) # N: Revealed type is 'builtins.int'
4958+
C1().foo()
4959+
C1().bar() # E: "C1" has no attribute "bar"
4960+
for x in C1: reveal_type(x) # N: Revealed type is 'builtins.int*'
4961+
for x in C2: reveal_type(x) # N: Revealed type is 'builtins.int*'
4962+
C2().foo()
4963+
C2().bar()
4964+
C2().baz() # E: "C2" has no attribute "baz"
4965+
4966+
[case testFutureMetaclassGenerics]
4967+
from typing import Generic, GenericMeta, TypeVar
4968+
import future.utils
4969+
class DestroyableMeta(type):
4970+
pass
4971+
class Destroyable(future.utils.with_metaclass(DestroyableMeta)):
4972+
pass
4973+
T_co = TypeVar('T_co', bound='Destroyable', covariant=True)
4974+
class ArcMeta(GenericMeta, DestroyableMeta):
4975+
pass
4976+
class Arc(future.utils.with_metaclass(ArcMeta, Generic[T_co], Destroyable)):
4977+
pass
4978+
class MyDestr(Destroyable):
4979+
pass
4980+
reveal_type(Arc[MyDestr]()) # N: Revealed type is '__main__.Arc[__main__.MyDestr*]'
4981+
[builtins fixtures/bool.pyi]
4982+
[typing fixtures/typing-full.pyi]
4983+
4984+
[case testFutureMetaclassErrors]
4985+
import future.utils
4986+
class M(type): pass
4987+
class A(object): pass
4988+
def f() -> type: return M
4989+
class C1(future.utils.with_metaclass(M), object): pass # E: Unsupported dynamic base class "future.utils.with_metaclass"
4990+
class C2(C1, future.utils.with_metaclass(M)): pass # E: Unsupported dynamic base class "future.utils.with_metaclass"
4991+
class C3(future.utils.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported
4992+
class C4(future.utils.with_metaclass(M), metaclass=M): pass # E: Multiple metaclass definitions
4993+
class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not supported for 'C5'
4994+
4995+
class M1(type): pass
4996+
class Q1(metaclass=M1): pass
4997+
class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Inconsistent metaclass structure for 'CQW'
4998+
4999+
[case testFutureMetaclassErrors_python2]
5000+
# flags: --python-version 2.7
5001+
import future.utils
5002+
class M(type): pass
5003+
class C4(future.utils.with_metaclass(M)): # E: Multiple metaclass definitions
5004+
__metaclass__ = M
5005+
5006+
[case testFutureMetaclassAny]
5007+
import t # type: ignore
5008+
import future.utils
5009+
class E(metaclass=t.M): pass
5010+
class F(future.utils.with_metaclass(t.M)): pass
5011+
49025012
-- Misc
49035013
-- ----
49045014

test-data/unit/check-newsemanal.test

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,50 @@ class A(six.with_metaclass(B, Defer)):
12041204
class Defer:
12051205
x: str
12061206

1207+
[case testNewAnalyzerMetaclassFuture1]
1208+
import future.utils
1209+
1210+
class A(future.utils.with_metaclass(B)):
1211+
pass
1212+
1213+
class B(type):
1214+
def f(cls) -> int:
1215+
return 0
1216+
1217+
reveal_type(A.f()) # N: Revealed type is 'builtins.int'
1218+
1219+
[case testNewAnalyzerMetaclassFuture3]
1220+
import future.utils
1221+
1222+
class A(future.utils.with_metaclass(B, Defer)):
1223+
pass
1224+
1225+
class B(type):
1226+
def f(cls) -> int:
1227+
return 0
1228+
1229+
class Defer:
1230+
x: str
1231+
1232+
reveal_type(A.f()) # N: Revealed type is 'builtins.int'
1233+
reveal_type(A.x) # N: Revealed type is 'builtins.str'
1234+
1235+
[case testNewAnalyzerMetaclassFuture4]
1236+
import future.utils
1237+
1238+
class B(type):
1239+
def f(cls) -> int:
1240+
return 0
1241+
1242+
reveal_type(A.f()) # N: Revealed type is 'builtins.int'
1243+
reveal_type(A.x) # N: Revealed type is 'builtins.str'
1244+
1245+
class A(future.utils.with_metaclass(B, Defer)):
1246+
pass
1247+
1248+
class Defer:
1249+
x: str
1250+
12071251
[case testNewAnalyzerMetaclass1_python2]
12081252
class A:
12091253
__metaclass__ = B
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from __future__ import absolute_import, print_function
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from typing import Type
2+
def with_metaclass(meta: Type[type], *bases: type) -> type: pass

0 commit comments

Comments
 (0)