Skip to content

Commit 2d04835

Browse files
committed
fix: ignore deferred annotation code. #1908
1 parent e7c05fe commit 2d04835

File tree

4 files changed

+49
-1
lines changed

4 files changed

+49
-1
lines changed

CHANGES.rst

+7
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,19 @@ upgrading your version of coverage.py.
2323
Unreleased
2424
----------
2525

26+
- Fix: Python 3.14 `defers evaluation of annotations <pep649_>`_ by moving them
27+
into separate code objects. That code is rarely executed, so coverage.py
28+
would mark them as missing, as reported in `issue 1908`_. Now they are
29+
ignored by coverage automatically.
30+
2631
- Fixed an obscure and mysterious problem on PyPy 3.10 seemingly involving
2732
mocks, imports, and trace functions: `issue 1902`_. To be honest, I don't
2833
understand the problem or the solution, but ``git bisect`` helped find it,
2934
and now it's fixed.
3035

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

3340

3441
.. start-releases

coverage/env.py

+3
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ class PYBEHAVIOR:
100100
# https://github.com/python/cpython/issues/113728
101101
lasti_is_yield = (PYVERSION[:2] != (3, 13))
102102

103+
# PEP649 and PEP749: Deferred annotations
104+
deferred_annotations = (PYVERSION >= (3, 14))
105+
103106

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

coverage/parser.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,17 @@ def child_parsers(self) -> Iterable[ByteParser]:
413413
414414
The iteration includes `self` as its first value.
415415
416+
We skip code objects named `__annotate__` since they are deferred
417+
annotations that usually are never run. If there are errors in the
418+
annotations, they will be caught by type checkers or other tools that
419+
use annotations.
420+
416421
"""
417-
return (ByteParser(self.text, code=c) for c in code_objects(self.code))
422+
return (
423+
ByteParser(self.text, code=c)
424+
for c in code_objects(self.code)
425+
if c.co_name != "__annotate__"
426+
)
418427

419428
def _line_numbers(self) -> Iterable[TLineNo]:
420429
"""Yield the line numbers possible in this code object.

tests/test_coverage.py

+29
Original file line numberDiff line numberDiff line change
@@ -1290,6 +1290,35 @@ def foo(self):
12901290
)
12911291

12921292

1293+
class AnnotationTest(CoverageTest):
1294+
"""Tests specific to annotations."""
1295+
1296+
def test_attribute_annotation(self) -> None:
1297+
if env.PYBEHAVIOR.deferred_annotations:
1298+
lines = [1, 3]
1299+
else:
1300+
lines = [1, 2, 3]
1301+
self.check_coverage("""\
1302+
class X:
1303+
x: int
1304+
y = 1
1305+
""",
1306+
lines=lines,
1307+
missing="",
1308+
)
1309+
1310+
def test_attribute_annotation_from_future(self) -> None:
1311+
self.check_coverage("""\
1312+
from __future__ import annotations
1313+
class X:
1314+
x: int
1315+
y = 1
1316+
""",
1317+
lines=[1, 2, 3, 4],
1318+
missing="",
1319+
)
1320+
1321+
12931322
class ExcludeTest(CoverageTest):
12941323
"""Tests of the exclusion feature to mark lines as not covered."""
12951324

0 commit comments

Comments
 (0)