Skip to content

Commit a96c516

Browse files
committed
Fixes to classes with fine-grained incremental
1 parent b5aa42d commit a96c516

File tree

5 files changed

+70
-7
lines changed

5 files changed

+70
-7
lines changed

mypy/nodes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1954,7 +1954,10 @@ def __init__(self, names: 'SymbolTable', defn: ClassDef, module_name: str) -> No
19541954
self._fullname = defn.fullname
19551955
self.is_abstract = False
19561956
self.abstract_attributes = []
1957-
if defn.type_vars:
1957+
self.add_type_vars()
1958+
1959+
def add_type_vars(self) -> None:
1960+
if self.defn.type_vars:
19581961
for vd in defn.type_vars:
19591962
self.type_vars.append(vd.name)
19601963

mypy/semanal.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,19 @@ def refresh_partial(self, node: Union[MypyFile, FuncItem]) -> None:
276276
def refresh_top_level(self, file_node: MypyFile) -> None:
277277
"""Reanalyze a stale module top-level in fine-grained incremental mode."""
278278
for d in file_node.defs:
279-
if not isinstance(d, (FuncItem, ClassDef)):
279+
if isinstance(d, ClassDef):
280+
self.refresh_class_def(d)
281+
elif not isinstance(d, FuncItem):
280282
self.accept(d)
281283

284+
def refresh_class_def(self, defn: ClassDef) -> None:
285+
with self.analyze_class_body(defn):
286+
for d in defn.defs.body:
287+
if isinstance(d, ClassDef):
288+
self.refresh_class_def(d)
289+
elif not isinstance(d, FuncItem):
290+
self.accept(d)
291+
282292
@contextmanager
283293
def file_context(self, file_node: MypyFile, fnam: str, options: Options,
284294
active_type: Optional[TypeInfo]) -> Iterator[None]:
@@ -607,6 +617,12 @@ def check_function_signature(self, fdef: FuncItem) -> None:
607617
self.fail('Type signature has too many arguments', fdef, blocker=True)
608618

609619
def visit_class_def(self, defn: ClassDef) -> None:
620+
with self.analyze_class_body(defn):
621+
# Analyze class body.
622+
defn.defs.accept(self)
623+
624+
@contextmanager
625+
def analyze_class_body(self, defn: ClassDef) -> Iterator[None]:
610626
self.clean_up_bases_and_infer_type_variables(defn)
611627
if self.analyze_typeddict_classdef(defn):
612628
return
@@ -624,8 +640,7 @@ def visit_class_def(self, defn: ClassDef) -> None:
624640

625641
self.enter_class(defn)
626642

627-
# Analyze class body.
628-
defn.defs.accept(self)
643+
yield
629644

630645
self.calculate_abstract_status(defn.info)
631646
self.setup_type_promotion(defn)

mypy/server/aststrip.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ def strip_top_level(self, file_node: MypyFile) -> None:
3030
for node in file_node.defs:
3131
if not isinstance(node, (FuncItem, ClassDef)):
3232
node.accept(self)
33+
elif isinstance(node, ClassDef):
34+
self.strip_class_body(node)
35+
36+
def strip_class_body(self, node: ClassDef) -> None:
37+
"""Strip class body and type info, but don't strip methods."""
38+
node.info.type_vars = []
39+
node.info.bases = []
40+
node.info.abstract_attributes = []
41+
node.info.add_type_vars()
3342

3443
def visit_func_def(self, node: FuncDef) -> None:
3544
node.expanded = []

mypy/server/update.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ def lookup_target(modules: Dict[str, MypyFile], target: str) -> List[DeferredNod
385385
else:
386386
components = []
387387
node = modules[module] # type: SymbolNode
388-
prev = None # type: SymbolNode
388+
file = None # type: MypyFile
389389
active_class = None
390390
active_class_name = None
391391
for c in components:
@@ -394,14 +394,15 @@ def lookup_target(modules: Dict[str, MypyFile], target: str) -> List[DeferredNod
394394
active_class_name = node.name()
395395
# TODO: Is it possible for the assertion to fail?
396396
assert isinstance(node, (MypyFile, TypeInfo))
397-
prev = node
397+
if isinstance(node, MypyFile):
398+
file = node
398399
node = node.names[c].node
399400
if isinstance(node, TypeInfo):
400401
# A ClassDef target covers the body of the class and everything defined
401402
# within it. To get the body we include the entire surrounding target,
402403
# typically a module top-level, since we don't support processing class
403404
# bodies as separate entitites for simplicity.
404-
result = [DeferredNode(prev, None, None)] # TODO: Nested classes
405+
result = [DeferredNode(file, None, None)]
405406
for name, node in node.names.items():
406407
if isinstance(node, FuncDef):
407408
result.extend(lookup_target(modules, target + '.' + name))

test-data/unit/fine-grained.test

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,3 +813,38 @@ def f() -> str: pass
813813
[out]
814814
==
815815
main:7: error: Incompatible types in assignment (expression has type "str", variable has type "int")
816+
817+
[case testBaseClassDeleted]
818+
import m
819+
820+
class A(m.C):
821+
def f(self) -> None:
822+
self.g() # No error here because m.C becomes an Any base class
823+
def g(self) -> None:
824+
self.x
825+
[file m.py]
826+
class C:
827+
def g(self) -> None: pass
828+
[file m.py.2]
829+
[out]
830+
main:7: error: "A" has no attribute "x"
831+
==
832+
main:3: error: Name 'm.C' is not defined
833+
834+
[case testBaseClassOfNestedClassDeleted]
835+
import m
836+
837+
class A:
838+
class B(m.C):
839+
def f(self) -> None:
840+
self.g() # No error here because m.C becomes an Any base class
841+
def g(self) -> None:
842+
self.x
843+
[file m.py]
844+
class C:
845+
def g(self) -> None: pass
846+
[file m.py.2]
847+
[out]
848+
main:8: error: "B" has no attribute "x"
849+
==
850+
main:4: error: Name 'm.C' is not defined

0 commit comments

Comments
 (0)