From 7e75b0dacca1ec40611412c470212f3de15df24a Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Mon, 6 May 2019 16:40:43 +0300 Subject: [PATCH 1/4] Fix --- mypy/fastparse.py | 22 ++++++++++++++-------- mypy/fastparse2.py | 25 +++++++++++++++---------- test-data/unit/check-functions.test | 8 ++++++++ 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index adea026b112e..2a7d997b7154 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -7,7 +7,7 @@ MYPY = False if MYPY: import typing # for typing.Type, which conflicts with types.Type - from typing_extensions import Final + from typing_extensions import Final, Literal from mypy.sharedparse import ( special_function_elide_names, argument_elide_name, @@ -249,6 +249,8 @@ def __init__(self, is_stub: bool, errors: Errors) -> None: self.class_nesting = 0 + # 'C' for class, 'F' for function + self.class_and_function_stack = [] # type: List[Literal['C', 'F']] self.imports = [] # type: List[ImportBase] self.options = options @@ -382,8 +384,8 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret.append(OverloadedFuncDef(current_overload)) return ret - def in_class(self) -> bool: - return self.class_nesting > 0 + def in_method_scope(self) -> bool: + return self.class_and_function_stack[-2:] == ['C', 'F'] def translate_module_id(self, id: str) -> str: """Return the actual, internal module id for a source text id. @@ -424,6 +426,7 @@ def visit_AsyncFunctionDef(self, n: ast3.AsyncFunctionDef) -> Union[FuncDef, Dec def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], is_coroutine: bool = False) -> Union[FuncDef, Decorator]: """Helper shared between visit_FunctionDef and visit_AsyncFunctionDef.""" + self.class_and_function_stack.append('F') no_type_check = bool(n.decorator_list and any(is_no_type_check_decorator(d) for d in n.decorator_list)) @@ -465,7 +468,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], line=lineno).visit(func_type_ast.returns) # add implicit self type - if self.in_class() and len(arg_types) < len(args): + if self.in_method_scope() and len(arg_types) < len(args): arg_types.insert(0, AnyType(TypeOfAny.special_form)) except SyntaxError: self.fail(TYPE_COMMENT_SYNTAX_ERROR, lineno, n.col_offset) @@ -513,6 +516,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], func_type.definition = func_def func_type.line = lineno + retval: Union[FuncDef, Decorator] if n.decorator_list: if sys.version_info < (3, 8): # Before 3.8, [typed_]ast the line number points to the first decorator. @@ -529,11 +533,13 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], deco = Decorator(func_def, self.translate_expr_list(n.decorator_list), var) deco.set_line(n.decorator_list[0].lineno) - return deco + retval = deco else: # FuncDef overrides set_line -- can't use self.set_line func_def.set_line(lineno, n.col_offset) - return func_def + retval = func_def + self.class_and_function_stack.pop() + return retval def set_type_optional(self, type: Optional[Type], initializer: Optional[Expression]) -> None: if self.options.no_implicit_optional: @@ -614,7 +620,7 @@ def fail_arg(self, msg: str, arg: ast3.arg) -> None: # stmt* body, # expr* decorator_list) def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: - self.class_nesting += 1 + self.class_and_function_stack.append('C') keywords = [(kw.arg, self.visit(kw.value)) for kw in n.keywords if kw.arg] @@ -634,7 +640,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: cdef.column = n.col_offset else: self.set_line(cdef, n) - self.class_nesting -= 1 + self.class_and_function_stack.pop() return cdef # Return(expr? value) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 95f415c629cb..98fd9c481a9d 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -20,7 +20,7 @@ MYPY = False if MYPY: import typing # for typing.Type, which conflicts with types.Type - from typing_extensions import Final + from typing_extensions import Final, Literal from mypy.sharedparse import ( special_function_elide_names, argument_elide_name, @@ -134,7 +134,8 @@ class ASTConverter: def __init__(self, options: Options, errors: Errors) -> None: - self.class_nesting = 0 + # 'C' for class, 'F' for function + self.class_and_function_stack = [] # type: List[Literal['C', 'F']] self.imports = [] # type: List[ImportBase] self.options = options @@ -285,8 +286,8 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret.append(OverloadedFuncDef(current_overload)) return ret - def in_class(self) -> bool: - return self.class_nesting > 0 + def in_method_scope(self) -> bool: + return self.class_and_function_stack[-2:] == ['C', 'F'] def translate_module_id(self, id: str) -> str: """Return the actual, internal module id for a source text id. @@ -313,10 +314,11 @@ def visit_Module(self, mod: ast27.Module) -> MypyFile: # --- stmt --- # FunctionDef(identifier name, arguments args, - # stmt* body, expr* decorator_list, expr? returns, string? type_comment) + # stmt* body, expr* decorator_list, expr? returns, string? type_comment) # arguments = (arg* args, arg? vararg, arg* kwonlyargs, expr* kw_defaults, # arg? kwarg, expr* defaults) def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: + self.class_and_function_stack.append('F') lineno = n.lineno converter = TypeConverter(self.errors, line=lineno, assume_str_is_unicode=self.unicode_literals) @@ -353,7 +355,7 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: return_type = converter.visit(func_type_ast.returns) # add implicit self type - if self.in_class() and len(arg_types) < len(args): + if self.in_method_scope() and len(arg_types) < len(args): arg_types.insert(0, AnyType(TypeOfAny.special_form)) except SyntaxError: self.fail(TYPE_COMMENT_SYNTAX_ERROR, lineno, n.col_offset) @@ -397,6 +399,7 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: func_type.definition = func_def func_type.line = lineno + retval: Statement if n.decorator_list: var = Var(func_def.name()) var.is_ready = False @@ -407,11 +410,13 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: func_def.body.set_line(func_def.get_line()) dec = Decorator(func_def, self.translate_expr_list(n.decorator_list), var) dec.set_line(lineno, n.col_offset) - return dec + retval = dec else: # Overrides set_line -- can't use self.set_line func_def.set_line(lineno, n.col_offset) - return func_def + retval = func_def + self.class_and_function_stack.pop() + return retval def set_type_optional(self, type: Optional[Type], initializer: Optional[Expression]) -> None: if self.options.no_implicit_optional: @@ -515,7 +520,7 @@ def stringify_name(self, n: AST) -> str: # stmt* body, # expr* decorator_list) def visit_ClassDef(self, n: ast27.ClassDef) -> ClassDef: - self.class_nesting += 1 + self.class_and_function_stack.append('C') cdef = ClassDef(n.name, self.as_required_block(n.body, n.lineno), @@ -524,7 +529,7 @@ def visit_ClassDef(self, n: ast27.ClassDef) -> ClassDef: metaclass=None) cdef.decorators = self.translate_expr_list(n.decorator_list) self.set_line(cdef, n) - self.class_nesting -= 1 + self.class_and_function_stack.pop() return cdef # Return(expr? value) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index fea7b17b0ad6..55869f31aa23 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -737,6 +737,14 @@ class A: main:6: error: Incompatible types in assignment (expression has type "int", variable has type "A") main:8: error: Argument 1 to "g" has incompatible type "A"; expected "int" +[case testNestedFunctionInMethodWithTooFewArgumentsInTypeComment] +class A: + def f(self): + # type: () -> None + def g(x): # E: Type signature has too few arguments + # type: () -> None + pass + [case testMutuallyRecursiveNestedFunctions] def f() -> None: def g() -> None: From 4294f9954797005e9d7dd808bacc8e1e04ca6c5f Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Mon, 6 May 2019 17:33:48 +0300 Subject: [PATCH 2/4] Fix type annotations --- mypy/fastparse.py | 3 +-- mypy/fastparse2.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 2a7d997b7154..02c84be896bd 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -516,7 +516,6 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], func_type.definition = func_def func_type.line = lineno - retval: Union[FuncDef, Decorator] if n.decorator_list: if sys.version_info < (3, 8): # Before 3.8, [typed_]ast the line number points to the first decorator. @@ -533,7 +532,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef], deco = Decorator(func_def, self.translate_expr_list(n.decorator_list), var) deco.set_line(n.decorator_list[0].lineno) - retval = deco + retval = deco # type: Union[FuncDef, Decorator] else: # FuncDef overrides set_line -- can't use self.set_line func_def.set_line(lineno, n.col_offset) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 98fd9c481a9d..7ee56e8ac80e 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -399,7 +399,6 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: func_type.definition = func_def func_type.line = lineno - retval: Statement if n.decorator_list: var = Var(func_def.name()) var.is_ready = False @@ -410,7 +409,7 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement: func_def.body.set_line(func_def.get_line()) dec = Decorator(func_def, self.translate_expr_list(n.decorator_list), var) dec.set_line(lineno, n.col_offset) - retval = dec + retval = dec # type: Statement else: # Overrides set_line -- can't use self.set_line func_def.set_line(lineno, n.col_offset) From 1f3b8f03233a57499548ddc438b7eb99c5772579 Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Mon, 6 May 2019 17:53:54 +0300 Subject: [PATCH 3/4] Remove not used variable --- mypy/fastparse.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 02c84be896bd..825dac4417a5 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -248,7 +248,6 @@ def __init__(self, options: Options, is_stub: bool, errors: Errors) -> None: - self.class_nesting = 0 # 'C' for class, 'F' for function self.class_and_function_stack = [] # type: List[Literal['C', 'F']] self.imports = [] # type: List[ImportBase] From 547563a65084ddcda858625d9f9ffa10b0531082 Mon Sep 17 00:00:00 2001 From: Ekin Dursun Date: Mon, 6 May 2019 18:01:03 +0300 Subject: [PATCH 4/4] Add extra tests --- test-data/unit/check-functions.test | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 55869f31aa23..c3d6c08fe6c5 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -745,6 +745,27 @@ class A: # type: () -> None pass +[case testDeepNestedFunctionWithTooFewArgumentsInTypeComment] +class A: + def f(self): + # type: () -> None + class B: + def g(self): + # type: () -> None + def h(x): # E: Type signature has too few arguments + # type: () -> None + pass + +[case testDeepNestedMethodInTypeComment] +class A: + def f(self): + # type: () -> None + class B: + class C: + def g(self): + # type: () -> None + pass + [case testMutuallyRecursiveNestedFunctions] def f() -> None: def g() -> None: