From ae81832ad7ecc453790152b83405783329c6331f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Miedzi=C5=84ski?= Date: Wed, 17 May 2017 01:05:09 +0200 Subject: [PATCH 1/3] Fix crash when annotating callable definition location --- mypy/messages.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index f6cadf0267885..ad37591fa4714 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -612,13 +612,10 @@ def unexpected_keyword_argument(self, callee: CallableType, name: str, if callee.name: msg += ' for {}'.format(callee.name) self.fail(msg, context) - if callee.definition: - fullname = callee.definition.fullname() - if fullname is not None and '.' in fullname: - module_name = fullname.rsplit('.', 1)[0] - path = self.modules[module_name].path - self.note('{} defined here'.format(callee.name), callee.definition, - file=path, origin=context) + module = find_defining_module(self.modules, callee) + if module: + self.note('{} defined here'.format(callee.name), callee.definition, + file=module.path, origin=context) def duplicate_argument_value(self, callee: CallableType, index: int, context: Context) -> None: @@ -946,6 +943,21 @@ def callable_name(type: CallableType) -> str: return 'function' +def find_defining_module(modules: Dict[str, MypyFile], typ: CallableType) -> MypyFile: + if not typ.definition: + return None + fullname = typ.definition.fullname() + if fullname is not None and '.' in fullname: + for i in range(1, fullname.count('.') + 1): + module_name = fullname.rsplit('.', i)[0] + try: + return modules[module_name] + except KeyError: + pass + assert False, "Couldn't determine module from CallableType" + return None + + def temp_message_builder() -> MessageBuilder: """Return a message builder usable for throwaway errors (which may not format properly).""" return MessageBuilder(Errors(), {}) From f77dbf331b3410c0d78e740134137814498b5755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Miedzi=C5=84ski?= Date: Wed, 17 May 2017 20:44:40 +0200 Subject: [PATCH 2/3] Use more friendly range args --- mypy/messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index ad37591fa4714..ccfc70a8051fb 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -948,8 +948,8 @@ def find_defining_module(modules: Dict[str, MypyFile], typ: CallableType) -> Myp return None fullname = typ.definition.fullname() if fullname is not None and '.' in fullname: - for i in range(1, fullname.count('.') + 1): - module_name = fullname.rsplit('.', i)[0] + for i in range(fullname.count('.')): + module_name = fullname.rsplit('.', i + 1)[0] try: return modules[module_name] except KeyError: From dfa07b3d84534dd9920119b568f7ecdb5c0b110b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Miedzi=C5=84ski?= Date: Mon, 22 May 2017 01:21:27 +0200 Subject: [PATCH 3/3] Set methods' fullname during 2nd pass --- mypy/semanal.py | 2 ++ test-data/unit/check-functions.test | 4 ++-- test-data/unit/check-kwargs.test | 17 ++++++++++++++++- test-data/unit/semanal-classes.test | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 95a7bf6e3630e..3bde5cadf21ce 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -328,6 +328,8 @@ def visit_func_def(self, defn: FuncDef) -> None: self.function_stack.append(defn) # First phase of analysis for function. self.errors.push_function(defn.name()) + if not defn._fullname: + defn._fullname = self.qualified_name(defn.name()) if defn.type: assert isinstance(defn.type, CallableType) self.update_function_type_variables(defn.type, defn) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index c978c0e83353b..1688d61b8c221 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1932,7 +1932,7 @@ main:3: note: "f" defined here [case testMagicMethodPositionalOnlyArg] class A(object): - def __eq__(self, other) -> bool: return True # We are all equal. + def __eq__(self, other) -> bool: return True # We are all equal. # N: "__eq__" of "A" defined here a = A() a.__eq__(a) @@ -1944,7 +1944,7 @@ a.__eq__(other=a) # E: Unexpected keyword argument "other" for "__eq__" of "A" class A(object): - def __eq__(self, other) -> bool: return True # We are all equal. + def __eq__(self, other) -> bool: return True # We are all equal. # N: "__eq__" of "A" defined here a = A() a.__eq__(a) diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 61c0a5a0d5d72..de229c76e6319 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -320,7 +320,7 @@ f(y=0) # E: Unexpected keyword argument "y" for "f" [case testKeywordArgumentAndCommentSignature2] import typing class A: - def f(self, x): # type: (int) -> str + def f(self, x): # type: (int) -> str # N: "f" of "A" defined here pass A().f(x='') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "int" A().f(x=0) @@ -359,3 +359,18 @@ f(**c) # E: Keywords must be strings def f(**k): pass f(*(1, 2)) # E: Too many arguments for "f" [builtins fixtures/dict.pyi] + +[case testUnexpectedMethodKwargInNestedClass] +class A: + class B: + def __init__(self) -> None: # N: "B" defined here + pass +A.B(x=1) # E: Unexpected keyword argument "x" for "B" + +[case testUnexpectedMethodKwargFromOtherModule] +import m +m.A(x=1) # E: Unexpected keyword argument "x" for "A" +[file m.py] +class A: + def __init__(self) -> None: # N: "A" defined here + pass diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index bb79b4264106e..96c6ffe32523a 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -270,7 +270,7 @@ MypyFile:1( PassStmt:2())) AssignmentStmt:3( NameExpr(g* [m]) - NameExpr(f [m])))) + NameExpr(f [__main__.A.f])))) [case testIfStatementInClassBody] class A: