Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions coverage/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import re

from typing import (
Any, Callable, Union,
Any, Callable, Final, Union,
)
from collections.abc import Iterable

Expand Down Expand Up @@ -360,7 +360,8 @@ def copy(self) -> CoverageConfig:
"""Return a copy of the configuration."""
return copy.deepcopy(self)

CONCURRENCY_CHOICES = {"thread", "gevent", "greenlet", "eventlet", "multiprocessing"}
CONCURRENCY_CHOICES: Final[set[str]] = {
"thread", "gevent", "greenlet", "eventlet", "multiprocessing"}

CONFIG_FILE_OPTIONS = [
# These are *args for _set_attr_from_config_option:
Expand Down
6 changes: 3 additions & 3 deletions coverage/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from typing import (
overload,
Any, Callable, IO,
Any, Callable, Final, IO,
)
from collections.abc import Iterable, Iterator, Mapping

Expand Down Expand Up @@ -467,8 +467,8 @@ def get_one(
# a process-wide singleton. So stash it in sys.modules instead of
# on a class attribute. Yes, this is aggressively gross.

SYS_MOD_NAME = "$coverage.debug.DebugOutputFile.the_one"
SINGLETON_ATTR = "the_one_and_is_interim"
SYS_MOD_NAME: Final[str] = "$coverage.debug.DebugOutputFile.the_one"
SINGLETON_ATTR: Final[str] = "the_one_and_is_interim"

@classmethod
def _set_singleton_data(cls, the_one: DebugOutputFile, interim: bool) -> None:
Expand Down
9 changes: 3 additions & 6 deletions coverage/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import platform
import sys

from typing import Any
from typing import Any, Final
from collections.abc import Iterable

# debug_info() at the bottom wants to show all the globals, but not imports.
Expand Down Expand Up @@ -53,10 +53,7 @@ class PYBEHAVIOR:

# Is "if not __debug__" optimized away? The exact details have changed
# across versions.
if pep626:
optimize_if_not_debug = 1
else:
optimize_if_not_debug = 2
optimize_if_not_debug = 1 if pep626 else 2
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coverage/env.py:56: error: Unsupported statement in class body

In general Mypyc doesn't things being defined in multiple places. Using if/else means the class var is defined in two places, while using conditional statement means it is defined in just one place.

A more dramatic example is in files.py, where the function actual_path is defined conditionally:

if env.WINDOWS:

    _ACTUAL_PATH_CACHE: dict[str, str] = {}
    _ACTUAL_PATH_LIST_CACHE: dict[str, list[str]] = {}

    def actual_path(path: str) -> str:
        """Get the actual path of `path`, including the correct case."""
	...

else:
    def actual_path(path: str) -> str:
        """The actual path for non-Windows platforms."""
        return path


# 3.7 changed how functions with only docstrings are numbered.
docstring_only_function = (not PYPY) and (PYVERSION <= (3, 10))
Expand Down Expand Up @@ -148,7 +145,7 @@ class PYBEHAVIOR:
soft_keywords = (PYVERSION >= (3, 10))

# PEP669 Low Impact Monitoring: https://peps.python.org/pep-0669/
pep669 = bool(getattr(sys, "monitoring", None))
pep669: Final[bool] = bool(getattr(sys, "monitoring", None))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually, all of PYBEHAVIOR should be Final. How come only this attribute needed Final?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. I think it has to do with the file exclusions I mentioned above. Probably they would all require Final if everything were included.


# Where does frame.f_lasti point when yielding from a generator?
# It used to point at the YIELD, in 3.13 it points at the RESUME,
Expand Down
2 changes: 1 addition & 1 deletion coverage/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ def _line_numbers(self) -> Iterable[TLineNo]:
byte_increments = self.code.co_lnotab[0::2]
line_increments = self.code.co_lnotab[1::2]

last_line_num = None
last_line_num: TLineNo | None = None
line_num = self.code.co_firstlineno
byte_num = 0
for byte_incr, line_incr in zip(byte_increments, line_increments):
Expand Down
2 changes: 1 addition & 1 deletion coverage/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ def _line_ranges(
lines = sorted(lines)

pairs = []
start = None
start: TLineNo | None = None
lidx = 0
for stmt in statements:
if lidx >= len(lines):
Expand Down
Loading