diff --git a/mypy/errors.py b/mypy/errors.py index 95ca5550e77a..092c69a3b0f4 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -129,10 +129,20 @@ class Errors: # Set to True to show column numbers in error messages. show_column_numbers = False # type: bool - # Stack of active fine-grained incremental checking targets within - # a module. The first item is always the current module id. + # State for keeping track of the current fine-grained incremental mode target. # (See mypy.server.update for more about targets.) - target = None # type: List[str] + # + # Current module id. + target_module = None # type: Optional[str] + # Partially qualified name of target class; without module prefix (examples: 'C' is top-level, + # 'C.D' nested). + target_class = None # type: Optional[str] + # Short name of the outermost function/method. + target_function = None # type: Optional[str] + # Nesting depth of functions/classes within the outermost function/method. These aren't + # separate targets and they are included in the surrounding function, but we this counter + # for internal bookkeeping. + target_ignore_depth = 0 def __init__(self, show_error_context: bool = False, show_column_numbers: bool = False) -> None: @@ -150,7 +160,10 @@ def initialize(self) -> None: self.used_ignored_lines = defaultdict(set) self.ignored_files = set() self.only_once_messages = set() - self.target = [] + self.target_module = None + self.target_class = None + self.target_function = None + self.target_ignore_depth = 0 def reset(self) -> None: self.initialize() @@ -161,7 +174,10 @@ def copy(self) -> 'Errors': new.import_ctx = self.import_ctx[:] new.type_name = self.type_name[:] new.function_or_member = self.function_or_member[:] - new.target = self.target[:] + new.target_module = self.target_module + new.target_class = self.target_class + new.target_function = self.target_function + new.target_ignore_depth = self.target_ignore_depth return new def set_ignore_prefix(self, prefix: str) -> None: @@ -186,8 +202,10 @@ def set_file(self, file: str, # reporting errors for files other than the one currently being # processed. self.file = file - if module: - self.target = [module] + self.target_module = module + self.target_class = None + self.target_function = None + self.target_ignore_depth = 0 def set_file_ignored_lines(self, file: str, ignored_lines: Set[int], @@ -198,12 +216,18 @@ def set_file_ignored_lines(self, file: str, def push_function(self, name: str) -> None: """Set the current function or member short name (it can be None).""" - self.push_target_component(name) + if self.target_function is None: + self.target_function = name + else: + self.target_ignore_depth += 1 self.function_or_member.append(name) def pop_function(self) -> None: self.function_or_member.pop() - self.pop_target_component() + if self.target_ignore_depth > 0: + self.target_ignore_depth -= 1 + else: + self.target_function = None @contextmanager def enter_function(self, name: str) -> Iterator[None]: @@ -213,30 +237,41 @@ def enter_function(self, name: str) -> Iterator[None]: def push_type(self, name: str) -> None: """Set the short name of the current type (it can be None).""" - self.push_target_component(name) + if self.target_function is not None: + self.target_ignore_depth += 1 + elif self.target_class is None: + self.target_class = name + else: + self.target_class += '.' + name self.type_name.append(name) def pop_type(self) -> None: self.type_name.pop() - self.pop_target_component() - - def push_target_component(self, name: str) -> None: - if self.target and not self.function_or_member[-1]: - self.target.append('{}.{}'.format(self.target[-1], name)) - - def pop_target_component(self) -> None: - if self.target and not self.function_or_member[-1]: - self.target.pop() + if self.target_ignore_depth > 0: + self.target_ignore_depth -= 1 + else: + assert self.target_class is not None + if '.' in self.target_class: + self.target_class = '.'.join(self.target_class.split('.')[:-1]) + else: + self.target_class = None def current_target(self) -> Optional[str]: - if self.target: - return self.target[-1] - return None + if self.target_module is None: + return None + target = self.target_module + if self.target_function is not None: + # Only include class name if we are inside a method, since a class + # target also includes all methods, which is not what we want + # here. Instead, the current target for a class body is the + # enclosing module top level. + if self.target_class is not None: + target += '.' + self.target_class + target += '.' + self.target_function + return target def current_module(self) -> Optional[str]: - if self.target: - return self.target[0] - return None + return self.target_module @contextmanager def enter_type(self, name: str) -> Iterator[None]: diff --git a/mypy/nodes.py b/mypy/nodes.py index a9b535a995e2..32f44065ecf1 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -324,6 +324,7 @@ class FuncBase(Node): # Original, not semantically analyzed type (used for reprocessing) unanalyzed_type = None # type: Optional[mypy.types.Type] # If method, reference to TypeInfo + # TODO: The type should be Optional[TypeInfo] info = None # type: TypeInfo is_property = False _fullname = None # type: str # Name with module prefix @@ -597,6 +598,7 @@ class Var(SymbolNode): _name = None # type: str # Name without module prefix _fullname = None # type: str # Name with module prefix + # TODO: The following should be Optional[TypeInfo] info = None # type: TypeInfo # Defining class (for member variables) type = None # type: Optional[mypy.types.Type] # Declared or inferred type, or None # Is this the first argument to an ordinary method (usually "self")? @@ -1468,7 +1470,7 @@ class SuperExpr(Expression): """Expression super().name""" name = '' - info = None # type: TypeInfo # Type that contains this super expression + info = None # type: Optional[TypeInfo] # Type that contains this super expression call = None # type: CallExpr # The expression super(...) def __init__(self, name: str, call: CallExpr) -> None: diff --git a/mypy/semanal.py b/mypy/semanal.py index 28890fd59268..f94b836d783c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -201,8 +201,6 @@ class SemanticAnalyzerPass2(NodeVisitor[None], SemanticAnalyzerPluginInterface): type = None # type: Optional[TypeInfo] # Stack of outer classes (the second tuple item contains tvars). type_stack = None # type: List[Optional[TypeInfo]] - # Type variables that are bound by the directly enclosing class - bound_tvars = None # type: List[SymbolTableNode] # Type variables bound by the current scope, be it class or function tvar_scope = None # type: TypeVarScope # Per-module options @@ -332,9 +330,11 @@ def file_context(self, file_node: MypyFile, fnam: str, options: Options, self.is_stub_file = fnam.lower().endswith('.pyi') self.is_typeshed_stub_file = self.errors.is_typeshed_file(file_node.path) self.globals = file_node.names + self.tvar_scope = TypeVarScope() if active_type: self.enter_class(active_type.defn.info) - # TODO: Bind class type vars + for tvar in active_type.defn.type_vars: + self.tvar_scope.bind_existing(tvar) yield @@ -909,7 +909,8 @@ def clean_up_bases_and_infer_type_variables(self, defn: ClassDef) -> None: del defn.base_type_exprs[i] tvar_defs = [] # type: List[TypeVarDef] for name, tvar_expr in declared_tvars: - tvar_defs.append(self.tvar_scope.bind(name, tvar_expr)) + tvar_def = self.tvar_scope.bind_new(name, tvar_expr) + tvar_defs.append(tvar_def) defn.type_vars = tvar_defs def analyze_typevar_declaration(self, t: Type) -> Optional[TypeVarList]: @@ -2844,6 +2845,7 @@ def build_enum_call_typeinfo(self, name: str, items: List[str], fullname: str) - var = Var(item) var.info = info var.is_property = True + var._fullname = '{}.{}'.format(self.qualified_name(name), item) info.names[item] = SymbolTableNode(MDEF, var) return info diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index c507ab92f9b0..7f8d416e9703 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -49,13 +49,14 @@ from mypy.nodes import ( Node, MypyFile, SymbolTable, Block, AssignmentStmt, NameExpr, MemberExpr, RefExpr, TypeInfo, - FuncDef, ClassDef, NamedTupleExpr, SymbolNode, Var, Statement, SuperExpr, MDEF + FuncDef, ClassDef, NamedTupleExpr, SymbolNode, Var, Statement, SuperExpr, NewTypeExpr, + OverloadedFuncDef, LambdaExpr, TypedDictExpr, EnumCallExpr, MDEF ) from mypy.traverser import TraverserVisitor from mypy.types import ( Type, TypeVisitor, Instance, AnyType, NoneTyp, CallableType, DeletedType, PartialType, TupleType, TypeType, TypeVarType, TypedDictType, UnboundType, UninhabitedType, UnionType, - Overloaded + Overloaded, TypeVarDef ) from mypy.util import get_prefix @@ -157,14 +158,29 @@ def visit_func_def(self, node: FuncDef) -> None: node = self.fixup(node) if node.type: self.fixup_type(node.type) + if node.info: + node.info = self.fixup(node.info) super().visit_func_def(node) + def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> None: + if node.info: + node.info = self.fixup(node.info) + super().visit_overloaded_func_def(node) + def visit_class_def(self, node: ClassDef) -> None: - # TODO additional things like the MRO + # TODO additional things? node.defs.body = self.replace_statements(node.defs.body) + node.info = self.fixup(node.info) + for tv in node.type_vars: + self.process_type_var_def(tv) self.process_type_info(node.info) super().visit_class_def(node) + def process_type_var_def(self, tv: TypeVarDef) -> None: + for value in tv.values: + self.fixup_type(value) + self.fixup_type(tv.upper_bound) + def visit_assignment_stmt(self, node: AssignmentStmt) -> None: if node.type: self.fixup_type(node.type) @@ -191,7 +207,39 @@ def visit_namedtuple_expr(self, node: NamedTupleExpr) -> None: def visit_super_expr(self, node: SuperExpr) -> None: super().visit_super_expr(node) + if node.info is not None: + node.info = self.fixup(node.info) + + def visit_newtype_expr(self, node: NewTypeExpr) -> None: + if node.info: + node.info = self.fixup(node.info) + self.process_type_info(node.info) + if node.old_type: + self.fixup_type(node.old_type) + super().visit_newtype_expr(node) + + def visit_lambda_expr(self, node: LambdaExpr) -> None: + if node.info: + node.info = self.fixup(node.info) + super().visit_lambda_expr(node) + + def visit_typeddict_expr(self, node: TypedDictExpr) -> None: + node.info = self.fixup(node.info) + super().visit_typeddict_expr(node) + + def visit_enum_call_expr(self, node: EnumCallExpr) -> None: node.info = self.fixup(node.info) + self.process_type_info(node.info) + super().visit_enum_call_expr(node) + + # Others + + def visit_var(self, node: Var) -> None: + if node.info: + node.info = self.fixup(node.info) + if node.type: + self.fixup_type(node.type) + super().visit_var(node) # Helpers @@ -206,7 +254,13 @@ def fixup_type(self, typ: Type) -> None: typ.accept(TypeReplaceVisitor(self.replacements)) def process_type_info(self, info: TypeInfo) -> None: - # TODO additional things like the MRO + # TODO: Additional things: + # - declared_metaclass + # - metaclass_type + # - _promote + # - tuple_type + # - typeddict_type + # - replaced replace_nodes_in_symbol_table(info.names, self.replacements) for i, item in enumerate(info.mro): info.mro[i] = self.fixup(info.mro[i]) diff --git a/mypy/server/aststrip.py b/mypy/server/aststrip.py index de745604c113..e2c2055cdbf2 100644 --- a/mypy/server/aststrip.py +++ b/mypy/server/aststrip.py @@ -43,7 +43,7 @@ from mypy.nodes import ( Node, FuncDef, NameExpr, MemberExpr, RefExpr, MypyFile, FuncItem, ClassDef, AssignmentStmt, ImportFrom, Import, TypeInfo, SymbolTable, Var, CallExpr, Decorator, OverloadedFuncDef, - UNBOUND_IMPORTED, GDEF + SuperExpr, UNBOUND_IMPORTED, GDEF, MDEF ) from mypy.traverser import TraverserVisitor @@ -83,6 +83,10 @@ def visit_class_def(self, node: ClassDef) -> None: node.info.abstract_attributes = [] node.info.mro = [] node.info.add_type_vars() + node.info.tuple_type = None + node.info.typeddict_type = None + node.info._cache = set() + node.info._cache_proper = set() node.base_type_exprs.extend(node.removed_base_type_exprs) node.removed_base_type_exprs = [] with self.enter_class(node.info): @@ -136,20 +140,8 @@ def enter_method(self, info: TypeInfo) -> Iterator[None]: def visit_assignment_stmt(self, node: AssignmentStmt) -> None: node.type = node.unanalyzed_type - if node.type and self.is_class_body: - # Remove attribute defined in the class body from the class namespace to avoid - # bogus "Name already defined" errors. - # - # TODO: Handle multiple assignment, other lvalues - # TODO: What about assignments without type annotations? - assert len(node.lvalues) == 1 - lvalue = node.lvalues[0] - assert isinstance(lvalue, NameExpr) - assert self.type is not None # Because self.is_class_body is True - del self.type.names[lvalue.name] if self.type and not self.is_class_body: # TODO: Handle multiple assignment - # TODO: Merge with above if len(node.lvalues) == 1: lvalue = node.lvalues[0] if isinstance(lvalue, MemberExpr) and lvalue.is_new_def: @@ -192,6 +184,9 @@ def visit_name_expr(self, node: NameExpr) -> None: # Global assignments are processed in semantic analysis pass 1, and we # only want to strip changes made in passes 2 or later. if not (node.kind == GDEF and node.is_new_def): + # Remove defined attributes so that they can recreated during semantic analysis. + if node.kind == MDEF and node.is_new_def: + self.strip_class_attr(node.name) self.strip_ref_expr(node) def visit_member_expr(self, node: MemberExpr) -> None: @@ -205,12 +200,14 @@ def visit_member_expr(self, node: MemberExpr) -> None: # defines an attribute with the same name, and we can't have # multiple definitions for an attribute. Defer to the base class # definition. - if self.type is not None: - del self.type.names[node.name] - node.is_inferred_def = False + self.strip_class_attr(node.name) node.def_var = None super().visit_member_expr(node) + def strip_class_attr(self, name: str) -> None: + if self.type is not None: + del self.type.names[name] + def is_duplicate_attribute_def(self, node: MemberExpr) -> bool: if not node.is_inferred_def: return False @@ -223,11 +220,17 @@ def strip_ref_expr(self, node: RefExpr) -> None: node.kind = None node.node = None node.fullname = None + node.is_new_def = False + node.is_inferred_def = False def visit_call_expr(self, node: CallExpr) -> None: node.analyzed = None super().visit_call_expr(node) + def visit_super_expr(self, node: SuperExpr) -> None: + node.info = None + super().visit_super_expr(node) + # TODO: handle more node types diff --git a/mypy/server/update.py b/mypy/server/update.py index 374569c4854f..d3261e529c8e 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -207,7 +207,7 @@ def update_single(self, module: str, path: str) -> Tuple[List[str], filtered = [trigger for trigger in triggered if not trigger.endswith('__>')] print('triggered:', sorted(filtered)) - self.triggered.extend(triggered) + self.triggered.extend(triggered | self.previous_targets_with_errors) update_dependencies({module: tree}, self.deps, graph, self.options) propagate_changes_using_dependencies(manager, graph, self.deps, triggered, {module}, diff --git a/mypy/tvar_scope.py b/mypy/tvar_scope.py index e9f264a3c097..e4931fd684bb 100644 --- a/mypy/tvar_scope.py +++ b/mypy/tvar_scope.py @@ -52,7 +52,7 @@ def class_frame(self) -> 'TypeVarScope': """A new scope frame for binding a class. Prohibits *this* class's tvars""" return TypeVarScope(self.get_function_scope(), True, self) - def bind(self, name: str, tvar_expr: TypeVarExpr) -> TypeVarDef: + def bind_new(self, name: str, tvar_expr: TypeVarExpr) -> TypeVarDef: if self.is_class_scope: self.class_id += 1 i = self.class_id @@ -70,6 +70,9 @@ def bind(self, name: str, tvar_expr: TypeVarExpr) -> TypeVarDef: self.scope[tvar_expr.fullname()] = tvar_def return tvar_def + def bind_existing(self, tvar_def: TypeVarDef) -> None: + self.scope[tvar_def.fullname] = tvar_def + def get_binding(self, item: Union[str, SymbolTableNode]) -> Optional[TypeVarDef]: fullname = item.fullname if isinstance(item, SymbolTableNode) else item assert fullname is not None diff --git a/mypy/typeanal.py b/mypy/typeanal.py index ba118e6b851d..2eb5366d1639 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -560,7 +560,7 @@ def bind_function_type_variables(self, assert var_node, "Binding for function type variable not found within function" var_expr = var_node.node assert isinstance(var_expr, TypeVarExpr) - self.tvar_scope.bind(var.name, var_expr) + self.tvar_scope.bind_new(var.name, var_expr) return fun_type.variables typevars = self.infer_type_variables(fun_type) # Do not define a new type variable if already defined in scope. @@ -570,7 +570,7 @@ def bind_function_type_variables(self, for name, tvar in typevars: if not self.tvar_scope.allow_binding(tvar.fullname()): self.fail("Type variable '{}' is bound by an outer class".format(name), defn) - self.tvar_scope.bind(name, tvar) + self.tvar_scope.bind_new(name, tvar) binding = self.tvar_scope.get_binding(tvar.fullname()) assert binding is not None defs.append(binding) diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index 84981559527d..4f2d13485047 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -549,3 +549,15 @@ class B: [builtins fixtures/property.pyi] [out] __main__.B.x + +[case testFunctionalEnum] +from enum import Enum +A = Enum('A', 'x') +B = Enum('B', 'x') +[file next.py] +from enum import Enum +A = Enum('A', 'x') +B = Enum('B', 'y') +[out] +__main__.B.x +__main__.B.y diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index c986cde2985d..357cb54ccc00 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -1025,8 +1025,8 @@ def f() -> Iterator[None]: [typing fixtures/typing-full.pyi] [builtins fixtures/list.pyi] [triggered] -2: -3: +2: , __main__ +3: , __main__, a [out] main:2: error: Revealed type is 'contextlib.GeneratorContextManager[builtins.None]' == @@ -1072,9 +1072,9 @@ def g() -> None: [typing fixtures/typing-full.pyi] [builtins fixtures/list.pyi] [triggered] -2: , -3: , -4: +2: , , a.g +3: , , a +4: a.g [out] a.py:11: error: Too many arguments for "h" == @@ -1347,6 +1347,141 @@ def g() -> str: pass [out] == +[case testMroSpecialCase] +import b +import a + +[file a.py] +class C: pass +class D(C): + 1() +class E(D): pass + +[file b.py] +import a + +[file a.py.2] +class C: pass +class D(C): + 1() +class E(D): pass + +[file b.py.2] +import a + +[triggered] +2: a, a +[out] +a.py:3: error: "int" not callable +== +a.py:3: error: "int" not callable + +[case testMetaclassDefinition_python2] +# flags: --py2 +import abc +import m +m.f() + +class A: + __metaclass__ = abc.ABCMeta +[file m.py] +def f(): pass +[file m.py.2] +def f(x=1): pass +[out] +== + +[case testRefreshGenericSubclass] +from typing import Generic, TypeVar +import m +m.x + +T = TypeVar('T') + +class C(Generic[T]): + def __init__(self, x: T) -> None: + pass + +class D(C[T]): + def __init__(self, x: T) -> None: + m.x + super(D, self).__init__(x) +[file m.py] +x = 0 +[file m.py.2] +x = '' +[out] +== + +[case testRefreshNamedTupleSubclass] +from typing import NamedTuple +import m +m.x + +N = NamedTuple('N', [('x', int)]) + +class C(N): + pass +[file m.py] +x = 0 +[file m.py.2] +x = '' +[out] +== + +[case testNewTypeRefresh] +import a + +[file a.py] +from typing import Dict, NewType + +N = NewType('N', int) + +a: Dict[N, int] + +def f(self, x: N) -> None: + a.get(x) + +[file a.py.2] +from typing import Dict, NewType # dummy change + +N = NewType('N', int) + +a: Dict[N, int] + +def f(self, x: N) -> None: + a.get(x) + +[builtins fixtures/dict.pyi] +[out] +== + +[case testRefreshFunctionalEnum] +import a + +[file a.py] +from typing import Dict +from enum import Enum + +N = Enum('N', 'x') +a: Dict[N, int] + +def f(self, x: N) -> None: + a.get(x) + +[file a.py.2] +from typing import Dict +from enum import Enum + +N = Enum('N', 'x') +a: Dict[N, int] + +def f(self, x: N) -> None: + a.get(x) +[builtins fixtures/dict.pyi] +[out] +== + [case testFineGrainedCallable] import a [file a.py]