Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 51b4e0c

Browse files
authored
Support metaclasses in fine grained mode (#4752)
The PR is straightforward. It also fixes a metaclass dependency bug in normal incremental mode. I add tests for both Python 2 and Python 3, although most of them are very similar, I feel safer to check both versions thoroughly.
1 parent 89722f6 commit 51b4e0c

7 files changed

Lines changed: 582 additions & 6 deletions

File tree

mypy/indirection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ def visit_instance(self, t: types.Instance) -> Set[str]:
7474
# change property types, change the MRO itself, etc.
7575
for s in t.type.mro:
7676
out.update(split_module_names(s.module_name))
77+
if t.type.metaclass_type is not None:
78+
out.update(split_module_names(t.type.metaclass_type.type.module_name))
7779
return out
7880

7981
def visit_callable_type(self, t: types.CallableType) -> Set[str]:

mypy/server/astdiff.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ def snapshot_definition(node: Optional[SymbolNode],
193193
node.fallback_to_any,
194194
node.is_named_tuple,
195195
node.is_newtype,
196+
# We need this to e.g. trigger metaclass calculation in subclasses.
197+
snapshot_optional_type(node.metaclass_type),
196198
snapshot_optional_type(node.tuple_type),
197199
snapshot_optional_type(node.typeddict_type),
198200
[base.fullname() for base in node.mro],

mypy/server/aststrip.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ def strip_type_info(self, info: TypeInfo) -> None:
100100
info.tuple_type = None
101101
info._cache = set()
102102
info._cache_proper = set()
103+
info.declared_metaclass = None
104+
info.metaclass_type = None
103105

104106
def visit_func_def(self, node: FuncDef) -> None:
105107
if not self.recurse_into_functions:

mypy/server/deps.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,8 @@ def __init__(self,
166166
self.is_package_init_file = False
167167

168168
# TODO (incomplete):
169-
# from m import *
170169
# await
171170
# protocols
172-
# metaclasses
173171

174172
def visit_mypy_file(self, o: MypyFile) -> None:
175173
self.scope.enter_file(o.fullname())
@@ -232,6 +230,8 @@ def process_type_info(self, info: TypeInfo) -> None:
232230
self.add_type_dependencies(info.tuple_type, target=make_trigger(target))
233231
if info.typeddict_type:
234232
self.add_type_dependencies(info.typeddict_type, target=make_trigger(target))
233+
if info.declared_metaclass:
234+
self.add_type_dependencies(info.declared_metaclass, target=make_trigger(target))
235235
# TODO: Add dependencies based on remaining TypeInfo attributes.
236236
self.add_type_alias_deps(self.scope.current_target())
237237
for name, node in info.names.items():
@@ -530,7 +530,6 @@ def add_operator_method_dependency_for_type(self, typ: Type, method: str) -> Non
530530
# Note that operator methods can't be (non-metaclass) methods of type objects
531531
# (that is, TypeType objects or Callables representing a type).
532532
# TODO: TypedDict
533-
# TODO: metaclasses
534533
if isinstance(typ, TypeVarType):
535534
typ = typ.upper_bound
536535
if isinstance(typ, TupleType):
@@ -541,6 +540,11 @@ def add_operator_method_dependency_for_type(self, typ: Type, method: str) -> Non
541540
elif isinstance(typ, UnionType):
542541
for item in typ.items:
543542
self.add_operator_method_dependency_for_type(item, method)
543+
elif isinstance(typ, FunctionLike) and typ.is_type_obj():
544+
self.add_operator_method_dependency_for_type(typ.fallback, method)
545+
elif isinstance(typ, TypeType):
546+
if isinstance(typ.item, Instance) and typ.item.type.metaclass_type is not None:
547+
self.add_operator_method_dependency_for_type(typ.item.type.metaclass_type, method)
544548

545549
def visit_generator_expr(self, e: GeneratorExpr) -> None:
546550
super().visit_generator_expr(e)
@@ -613,15 +617,21 @@ def attribute_triggers(self, typ: Type, name: str) -> List[str]:
613617
return [make_trigger(member)]
614618
elif isinstance(typ, FunctionLike) and typ.is_type_obj():
615619
member = '%s.%s' % (typ.type_object().fullname(), name)
616-
return [make_trigger(member)]
620+
triggers = [make_trigger(member)]
621+
triggers.extend(self.attribute_triggers(typ.fallback, name))
622+
return triggers
617623
elif isinstance(typ, UnionType):
618624
targets = []
619625
for item in typ.items:
620626
targets.extend(self.attribute_triggers(item, name))
621627
return targets
622628
elif isinstance(typ, TypeType):
623-
# TODO: Metaclass attribute lookup
624-
return self.attribute_triggers(typ.item, name)
629+
triggers = self.attribute_triggers(typ.item, name)
630+
if isinstance(typ.item, Instance) and typ.item.type.metaclass_type is not None:
631+
triggers.append(make_trigger('%s.%s' %
632+
(typ.item.type.metaclass_type.type.fullname(),
633+
name)))
634+
return triggers
625635
else:
626636
return []
627637

test-data/unit/check-incremental.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4020,6 +4020,26 @@ class Baz:
40204020
[out2]
40214021
tmp/a.py:3: error: Unsupported operand types for + ("int" and "Optional[int]")
40224022

4023+
[case testIncrementalMetaclassUpdate]
4024+
import a
4025+
[file a.py]
4026+
from b import B
4027+
B.x
4028+
4029+
[file b.py]
4030+
import c
4031+
class B(metaclass=c.M): pass
4032+
4033+
[file c.py]
4034+
class M(type):
4035+
x: int
4036+
4037+
[file c.py.2]
4038+
class M(type):
4039+
y: int
4040+
[out]
4041+
[out2]
4042+
tmp/a.py:2: error: "Type[B]" has no attribute "x"
40234043

40244044
[case testIncrementalLotsOfInheritance]
40254045
import a

test-data/unit/deps-types.test

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,148 @@ def h() -> None:
153153
<m.ff> -> m, m.h
154154
<m.g> -> m.f
155155

156+
[case testMetaclassAttributes]
157+
from mod import C
158+
from typing import Type
159+
def f(arg: Type[C]) -> None:
160+
arg.x
161+
[file mod.py]
162+
class M(type):
163+
x: int
164+
class C(metaclass=M):
165+
pass
166+
[out]
167+
<mod.C.x> -> m.f
168+
<mod.C> -> <m.f>, m, m.f
169+
<mod.M.x> -> m.f
170+
171+
[case testMetaclassAttributesDirect]
172+
from mod import C
173+
def f() -> None:
174+
C.x
175+
[file mod.py]
176+
class M(type):
177+
x: int
178+
class C(metaclass=M):
179+
pass
180+
[out]
181+
<mod.C.__init__> -> m.f
182+
<mod.C.__new__> -> m.f
183+
<mod.C.x> -> m.f
184+
<mod.C> -> m, m.f
185+
<mod.M.x> -> m.f
186+
187+
[case testMetaclassOperators]
188+
from mod import C
189+
from typing import Type
190+
def f(arg: Type[C]) -> None:
191+
arg + arg
192+
[file mod.py]
193+
class M(type):
194+
def __add__(self, other: M) -> M:
195+
pass
196+
class C(metaclass=M):
197+
pass
198+
[out]
199+
<mod.C> -> <m.f>, m, m.f
200+
<mod.M.__add__> -> m.f
201+
<mod.M.__radd__> -> m.f
202+
203+
[case testMetaclassOperatorsDirect]
204+
from mod import C
205+
def f() -> None:
206+
C + C
207+
[file mod.py]
208+
class M(type):
209+
def __add__(self, other: M) -> M:
210+
pass
211+
class C(metaclass=M):
212+
pass
213+
[out]
214+
<mod.C.__init__> -> m.f
215+
<mod.C.__new__> -> m.f
216+
<mod.C> -> m, m.f
217+
<mod.M.__add__> -> m.f
218+
<mod.M.__radd__> -> m.f
219+
220+
[case testMetaclassDepsDeclared]
221+
import mod
222+
class C(metaclass=mod.M):
223+
pass
224+
[file mod.py]
225+
class M(type):
226+
pass
227+
[out]
228+
<m.C> -> m.C
229+
<mod.M> -> <m.C>
230+
<mod> -> m
231+
232+
[case testMetaclassDepsDeclared_python2]
233+
# flags: --py2
234+
import mod
235+
class C:
236+
__metaclass__ = mod.M
237+
[file mod.py]
238+
class M(type):
239+
pass
240+
[out]
241+
<m.C> -> m.C
242+
<mod.M.__init__> -> m
243+
<mod.M.__new__> -> m
244+
<mod.M> -> <m.C>, m
245+
<mod> -> m
246+
247+
[case testMetaclassDepsDeclaredNested]
248+
import mod
249+
250+
def func() -> None:
251+
class C(metaclass=mod.M):
252+
pass
253+
[file mod.py]
254+
class M(type):
255+
pass
256+
[out]
257+
<m.func> -> m.func
258+
<mod.M> -> <m.func>
259+
<mod> -> m
260+
261+
[case testMetaclassAttributes_python2]
262+
# flags: --py2
263+
from mod import C
264+
from typing import Type
265+
def f(arg):
266+
# type: (Type[C]) -> None
267+
arg.x
268+
[file mod.py]
269+
class M(type):
270+
x = None # type: int
271+
class C:
272+
__metaclass__ = M
273+
[out]
274+
<mod.C.x> -> m.f
275+
<mod.C> -> <m.f>, m, m.f
276+
<mod.M.x> -> m.f
277+
278+
[case testMetaclassOperatorsDirect_python2]
279+
# flags: --py2
280+
from mod import C
281+
def f():
282+
# type: () -> None
283+
C + C
284+
[file mod.py]
285+
class M(type):
286+
def __add__(self, other):
287+
# type: (M) -> M
288+
pass
289+
class C:
290+
__metaclass__ = M
291+
[out]
292+
<mod.C.__init__> -> m.f
293+
<mod.C.__new__> -> m.f
294+
<mod.C> -> m, m.f
295+
<mod.M.__add__> -> m.f
296+
<mod.M.__radd__> -> m.f
297+
156298
-- Type aliases
157299

158300
[case testAliasDepsNormalMod]

0 commit comments

Comments
 (0)