Skip to content

Commit

Permalink
fix: ignore deferred annotation code. #1908
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Dec 22, 2024
1 parent e7c05fe commit 2d04835
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 1 deletion.
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ upgrading your version of coverage.py.
Unreleased
----------

- Fix: Python 3.14 `defers evaluation of annotations <pep649_>`_ by moving them
into separate code objects. That code is rarely executed, so coverage.py
would mark them as missing, as reported in `issue 1908`_. Now they are
ignored by coverage automatically.

- Fixed an obscure and mysterious problem on PyPy 3.10 seemingly involving
mocks, imports, and trace functions: `issue 1902`_. To be honest, I don't
understand the problem or the solution, but ``git bisect`` helped find it,
and now it's fixed.

.. _issue 1902: https://github.com/nedbat/coveragepy/issues/1902
.. _issue 1908: https://github.com/nedbat/coveragepy/issues/1908
.. _pep649: https://docs.python.org/3.14/whatsnew/3.14.html#pep-649-deferred-evaluation-of-annotations


.. start-releases
Expand Down
3 changes: 3 additions & 0 deletions coverage/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ class PYBEHAVIOR:
# https://github.com/python/cpython/issues/113728
lasti_is_yield = (PYVERSION[:2] != (3, 13))

# PEP649 and PEP749: Deferred annotations
deferred_annotations = (PYVERSION >= (3, 14))


# Coverage.py specifics, about testing scenarios. See tests/testenv.py also.

Expand Down
11 changes: 10 additions & 1 deletion coverage/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,8 +413,17 @@ def child_parsers(self) -> Iterable[ByteParser]:
The iteration includes `self` as its first value.
We skip code objects named `__annotate__` since they are deferred
annotations that usually are never run. If there are errors in the
annotations, they will be caught by type checkers or other tools that
use annotations.
"""
return (ByteParser(self.text, code=c) for c in code_objects(self.code))
return (
ByteParser(self.text, code=c)
for c in code_objects(self.code)
if c.co_name != "__annotate__"
)

def _line_numbers(self) -> Iterable[TLineNo]:
"""Yield the line numbers possible in this code object.
Expand Down
29 changes: 29 additions & 0 deletions tests/test_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,35 @@ def foo(self):
)


class AnnotationTest(CoverageTest):
"""Tests specific to annotations."""

def test_attribute_annotation(self) -> None:
if env.PYBEHAVIOR.deferred_annotations:
lines = [1, 3]
else:
lines = [1, 2, 3]
self.check_coverage("""\
class X:
x: int
y = 1
""",
lines=lines,
missing="",
)

def test_attribute_annotation_from_future(self) -> None:
self.check_coverage("""\
from __future__ import annotations
class X:
x: int
y = 1
""",
lines=[1, 2, 3, 4],
missing="",
)


class ExcludeTest(CoverageTest):
"""Tests of the exclusion feature to mark lines as not covered."""

Expand Down

0 comments on commit 2d04835

Please sign in to comment.