diff --git a/mypy/report.py b/mypy/report.py index 3e49ed29a47c..b5d37b128b37 100644 --- a/mypy/report.py +++ b/mypy/report.py @@ -295,7 +295,20 @@ def indentation_level(self, line_number: int) -> Optional[int]: def visit_func_def(self, defn: FuncDef) -> None: start_line = defn.get_line() - 1 - start_indent = self.indentation_level(start_line) + start_indent = None + # When a function is decorated, sometimes the start line will point to + # whitespace or comments between the decorator and the function, so + # we have to look for the start. + while start_line < len(self.source): + start_indent = self.indentation_level(start_line) + if start_indent is not None: + break + start_line += 1 + # If we can't find the function give up and don't annotate anything. + # Our line numbers are not reliable enough to be asserting on. + if start_indent is None: + return + cur_line = start_line + 1 end_line = cur_line # After this loop, function body will be lines [start_line, end_line) @@ -315,8 +328,12 @@ def visit_func_def(self, defn: FuncDef) -> None: is_typed = defn.type is not None for line in range(start_line, end_line): old_indent, _ = self.lines_covered[line] - assert start_indent is not None and start_indent > old_indent - self.lines_covered[line] = (start_indent, is_typed) + # If there was an old indent level for this line, and the new + # level isn't increasing the indentation, ignore it. + # This is to be defensive against funniness in our line numbers, + # which are not always reliable. + if old_indent <= start_indent: + self.lines_covered[line] = (start_indent, is_typed) # Visit the body, in case there are nested functions super().visit_func_def(defn) diff --git a/test-data/unit/reports.test b/test-data/unit/reports.test index bb0f4f55c7c9..11a4dc060880 100644 --- a/test-data/unit/reports.test +++ b/test-data/unit/reports.test @@ -337,3 +337,23 @@ def foo(): n 0 0 0 0 0 0 0 ----------------------------------------------------------------------------------------------------------------- Total 0 0 0 0 0 0 0 +[case testTrickyCoverage] +# cmd: mypy --linecoverage-report=report n.py +[file n.py] +import attr + +def blah(x): return x + +@blah + +def f(x: int) -> None: pass + +class Foo: + @blah + #hi + def f(self, x: int) -> None: + pass + +@attr.s +class Z(object): + pass