Skip to content

Commit ba1c0cd

Browse files
Merge pull request #367 from oscarbenjamin/pr_typing
Full test and typing coverage
2 parents e6977fd + 39ba35c commit ba1c0cd

60 files changed

Lines changed: 5962 additions & 536 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bin/build_dependencies_unix.sh

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ SKIP_MPFR=no
2222
USE_GMP=gmp
2323
PATCH_GMP_ARM64=no
2424
BUILD_ARB=no
25+
USE_GMP_GITHUB_MIRROR=no
2526

2627
while [[ $# -gt 0 ]]
2728
do
@@ -124,15 +125,15 @@ cd src
124125
# #
125126
# ------------------------------------------------------------------------- #
126127

127-
if [ $USE_GMP = "gmp" ]; then
128+
if [ "$USE_GMP" = "gmp" ]; then
128129

129130
# ----------------------------------------------------------------------- #
130131
# #
131132
# GMP #
132133
# #
133134
# ----------------------------------------------------------------------- #
134135

135-
if [ $SKIP_GMP = "yes" ]; then
136+
if [ "$SKIP_GMP" = "yes" ]; then
136137
echo
137138
echo --------------------------------------------
138139
echo " skipping GMP"
@@ -145,7 +146,7 @@ if [ $USE_GMP = "gmp" ]; then
145146
echo --------------------------------------------
146147
echo
147148

148-
if [ $USE_GMP_GITHUB_MIRROR = "yes" ]; then
149+
if [ "$USE_GMP_GITHUB_MIRROR" = "yes" ]; then
149150
# Needed in GitHub Actions because it is blocked from gmplib.org
150151
git clone https://github.com/oscarbenjamin/gmp_mirror.git
151152
cp gmp_mirror/gmp-$GMPVER.tar.xz .
@@ -163,7 +164,7 @@ if [ $USE_GMP = "gmp" ]; then
163164
# from the GMP repo but was applied after the release of GMP 6.2.1.
164165
# This patch is no longer needed for GMP 6.3.0.
165166
#
166-
if [ $PATCH_GMP_ARM64 = "yes" ]; then
167+
if [ "$PATCH_GMP_ARM64" = "yes" ]; then
167168
echo
168169
echo --------------------------------------------
169170
echo " patching GMP"
@@ -260,7 +261,7 @@ fi
260261
# #
261262
# ------------------------------------------------------------------------- #
262263

263-
if [ $SKIP_MPFR = "yes" ]; then
264+
if [ "$SKIP_MPFR" = "yes" ]; then
264265
echo
265266
echo --------------------------------------------
266267
echo " skipping MPFR"
@@ -326,7 +327,7 @@ cd ..
326327
# #
327328
# ------------------------------------------------------------------------- #
328329

329-
if [ $BUILD_ARB = "yes" ]; then
330+
if [ "$BUILD_ARB" = "yes" ]; then
330331

331332
echo
332333
echo --------------------------------------------
@@ -369,7 +370,7 @@ echo $PREFIX
369370
echo
370371
echo Versions:
371372

372-
if [ $SKIP_GMP = "yes" ]; then
373+
if [ "$SKIP_GMP" = "yes" ]; then
373374
echo GMP: skipped
374375
else
375376
if [[ $USE_GMP = "gmp" ]]; then
@@ -379,15 +380,15 @@ else
379380
fi
380381
fi
381382

382-
if [ $SKIP_MPFR = "yes" ]; then
383+
if [ "$SKIP_MPFR" = "yes" ]; then
383384
echo MPFR: skipped
384385
else
385386
echo MPFR: $MPFRVER
386387
fi
387388

388389
echo Flint: $FLINTVER
389390

390-
if [ $BUILD_ARB = "yes" ]; then
391+
if [ "$BUILD_ARB" = "yes" ]; then
391392
echo Arb: $ARBVER
392393
fi
393394
echo

coverage_plugin.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def get_cython_build_rules():
6262

6363

6464
@cache
65-
def parse_all_cfile_lines():
65+
def parse_all_cfile_lines(excluded_line_patterns=()):
6666
"""Parse all generated C files from the build directory."""
6767
#
6868
# Each .c file can include code generated from multiple Cython files (e.g.
@@ -77,6 +77,14 @@ def parse_all_cfile_lines():
7777
# expensive.
7878
#
7979
all_code_lines = {}
80+
all_excluded_lines = defaultdict(set)
81+
if excluded_line_patterns:
82+
pattern = re.compile("|".join([f"(?:{regex})" for regex in excluded_line_patterns]))
83+
line_is_excluded = pattern.search
84+
else:
85+
line_is_excluded = lambda line: False
86+
87+
source_cache = {}
8088

8189
for c_file, _ in get_cython_build_rules():
8290

@@ -85,13 +93,33 @@ def parse_all_cfile_lines():
8593
for cython_file, line_map in cfile_lines.items():
8694
if cython_file == '(tree fragment)':
8795
continue
88-
elif cython_file in all_code_lines:
96+
src_lines = source_cache.get(cython_file)
97+
if src_lines is None:
98+
src_path = src_dir / cython_file
99+
if src_path.exists():
100+
with open(src_path, encoding="utf8") as src:
101+
src_lines = src.read().splitlines()
102+
else:
103+
src_lines = []
104+
source_cache[cython_file] = src_lines
105+
106+
excluded = set()
107+
filtered_line_map = {}
108+
for lineno, line in line_map.items():
109+
source_line = src_lines[lineno - 1] if 0 < lineno <= len(src_lines) else ""
110+
if line_is_excluded(source_line) or line_is_excluded(line):
111+
excluded.add(lineno)
112+
else:
113+
filtered_line_map[lineno] = line
114+
if cython_file in all_code_lines:
89115
# Possibly need to merge the lines?
90-
assert all_code_lines[cython_file] == line_map
116+
assert all_code_lines[cython_file] == filtered_line_map
117+
all_excluded_lines[cython_file].update(excluded)
91118
else:
92-
all_code_lines[cython_file] = line_map
119+
all_code_lines[cython_file] = filtered_line_map
120+
all_excluded_lines[cython_file] = excluded
93121

94-
return all_code_lines
122+
return all_code_lines, all_excluded_lines
95123

96124

97125
def parse_cfile_lines(c_file):
@@ -102,6 +130,11 @@ def parse_cfile_lines(c_file):
102130

103131
class Plugin(CoveragePlugin):
104132
"""A coverage plugin for a spin/meson project with Cython code."""
133+
_excluded_line_patterns = ()
134+
135+
def configure(self, config):
136+
# Match Cython's plugin behavior and respect coverage's exclusion regexes.
137+
self._excluded_line_patterns = tuple(config.get_option("report:exclude_lines"))
105138

106139
def file_tracer(self, filename):
107140
"""Find a tracer for filename to handle trace events."""
@@ -121,7 +154,7 @@ def file_tracer(self, filename):
121154
def file_reporter(self, filename):
122155
"""Return a file reporter for filename."""
123156
srcfile = Path(filename).relative_to(src_dir)
124-
return CyFileReporter(srcfile)
157+
return CyFileReporter(srcfile, self._excluded_line_patterns)
125158

126159

127160
class CyFileTracer(FileTracer):
@@ -157,14 +190,15 @@ def get_source_filename(filename):
157190
class CyFileReporter(FileReporter):
158191
"""File reporter for Cython or Python files (.pyx,.pxd,.py)."""
159192

160-
def __init__(self, srcpath):
193+
def __init__(self, srcpath, excluded_line_patterns):
161194
abspath = (src_dir / srcpath)
162195
assert abspath.exists()
163196

164197
# filepath here needs to match dynamic_source_filename
165198
super().__init__(str(abspath))
166199

167200
self.srcpath = srcpath
201+
self.excluded_line_patterns = excluded_line_patterns
168202

169203
def relative_filename(self):
170204
"""Path displayed in the coverage reports."""
@@ -173,10 +207,15 @@ def relative_filename(self):
173207
def lines(self):
174208
"""Set of line numbers for possibly traceable lines."""
175209
srcpath = str(self.srcpath)
176-
all_line_maps = parse_all_cfile_lines()
210+
all_line_maps, _ = parse_all_cfile_lines(self.excluded_line_patterns)
177211
line_map = all_line_maps[srcpath]
178212
return set(line_map)
179213

214+
def excluded_lines(self):
215+
srcpath = str(self.srcpath)
216+
_, all_excluded_lines = parse_all_cfile_lines(self.excluded_line_patterns)
217+
return set(all_excluded_lines.get(srcpath, ()))
218+
180219

181220
def coverage_init(reg, options):
182221
plugin = Plugin()

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ package = "flint"
8787
"spin.cmds.meson.run",
8888
]
8989

90+
[tool.mypy]
91+
files = ["src"]
92+
9093
[tool.pytest.ini_options]
9194
addopts = "--import-mode=importlib"
9295

src/flint/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,23 @@
5050

5151
__version__ = "0.8.0"
5252

53+
54+
def _flint_version_at_least(major: int, minor: int) -> bool:
55+
version_parts = __FLINT_VERSION__.split(".")
56+
if len(version_parts) < 2:
57+
return False
58+
try:
59+
current_major = int(version_parts[0])
60+
current_minor = int(version_parts[1])
61+
except ValueError:
62+
return False
63+
return (current_major, current_minor) >= (major, minor)
64+
65+
66+
def _has_acb_theta() -> bool:
67+
return _flint_version_at_least(3, 1)
68+
69+
5370
__all__ = [
5471
"ctx",
5572
"fmpz",

src/flint/flint_base/flint_base.pyi

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,23 @@ class flint_poly(flint_elem, Generic[Telem]):
8080
def is_one(self) -> bool: ...
8181
def is_constant(self) -> bool: ...
8282
def is_gen(self) -> bool: ...
83-
def roots(self) -> list[tuple[Telem, int]]: ...
83+
def roots(self) -> list[tuple[Telem, int]] | list[Telem]: ...
8484
# Should be list[arb]:
8585
def real_roots(self) -> list[Any]: ...
8686
# Should be list[acb]:
8787
def complex_roots(self) -> list[Any]: ...
8888
def derivative(self) -> Self: ...
8989

90+
class flint_mat(flint_elem, Generic[Telem]):
91+
def nrows(self) -> int: ...
92+
def ncols(self) -> int: ...
93+
def __getitem__(self, index: tuple[int, int], /) -> Telem: ...
94+
def __setitem__(self, index: tuple[int, int], value: Telem | int, /) -> None: ...
95+
def entries(self) -> list[Telem]: ...
96+
def table(self) -> list[list[Telem]]: ...
97+
def tolist(self) -> list[list[Telem]]: ...
98+
def __iter__(self) -> Iterator[Telem]: ...
99+
90100
class Ordering(enum.Enum):
91101
lex = "lex"
92102
deglex = "deglex"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from __future__ import annotations
2+
3+
from typing import Callable, ParamSpec, TypeVar
4+
5+
6+
_P = ParamSpec("_P")
7+
_R = TypeVar("_R")
8+
9+
10+
class FlintContext:
11+
pretty: bool
12+
unicode: bool
13+
14+
def __init__(self) -> None: ...
15+
def default(self) -> None: ...
16+
17+
@property
18+
def prec(self) -> int: ...
19+
@prec.setter
20+
def prec(self, prec: int) -> None: ...
21+
22+
@property
23+
def dps(self) -> int: ...
24+
@dps.setter
25+
def dps(self, prec: int) -> None: ...
26+
27+
@property
28+
def cap(self) -> int: ...
29+
@cap.setter
30+
def cap(self, cap: int) -> None: ...
31+
32+
@property
33+
def threads(self) -> int: ...
34+
@threads.setter
35+
def threads(self, num: int) -> None: ...
36+
37+
def extraprec(self, n: int) -> PrecisionManager: ...
38+
def extradps(self, n: int) -> PrecisionManager: ...
39+
def workprec(self, n: int) -> PrecisionManager: ...
40+
def workdps(self, n: int) -> PrecisionManager: ...
41+
42+
def __repr__(self) -> str: ...
43+
def cleanup(self) -> None: ...
44+
45+
46+
class PrecisionManager:
47+
def __init__(self, ctx: FlintContext, eprec: int = -1, edps: int = -1) -> None: ...
48+
def __call__(self, func: Callable[_P, _R]) -> Callable[_P, _R]: ...
49+
def __enter__(self) -> None: ...
50+
def __exit__(self, exc_type: object, value: object, traceback: object) -> None: ...

src/flint/flint_base/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pkgdir = 'flint/flint_base'
33
pyfiles = [
44
'__init__.py',
55
'flint_base.pyi',
6+
'flint_context.pyi',
67
]
78

89
exts = [

src/flint/flintlib/functions/arf.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ from flint.flintlib.types.fmpq cimport fmpq_t
1616
# .. macro:: ARF_PREC_EXACT
1717

1818
cdef extern from "flint/arf.h":
19+
cdef const slong ARF_PREC_EXACT
1920
void arf_init(arf_t x)
2021
void arf_clear(arf_t x)
2122
slong arf_allocated_bytes(const arf_t x)

src/flint/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pyfiles = [
44
'__init__.py',
55
'typing.py',
66
'py.typed',
7+
'pyflint.pyi',
78
]
89

910
exts = [

src/flint/pyflint.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .flint_base.flint_context import FlintContext
2+
3+
4+
ctx: FlintContext

0 commit comments

Comments
 (0)