typedframes 0.6.0__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {typedframes-0.6.0 → typedframes-0.8.0}/PKG-INFO +13 -13
  2. {typedframes-0.6.0 → typedframes-0.8.0}/README.md +12 -12
  3. {typedframes-0.6.0 → typedframes-0.8.0}/pyproject.toml +1 -1
  4. {typedframes-0.6.0 → typedframes-0.8.0}/rust/Cargo.lock +1 -1
  5. {typedframes-0.6.0 → typedframes-0.8.0}/rust/Cargo.toml +1 -1
  6. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/ast_extract.rs +13 -0
  7. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/constants.rs +87 -0
  8. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/errors.rs +63 -0
  9. typedframes-0.8.0/rust/src/frame_ops.rs +1335 -0
  10. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/index.rs +733 -1
  11. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/lib.rs +1 -0
  12. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/linter.rs +4331 -305
  13. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/notebook.rs +116 -1
  14. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/pyapi.rs +6 -0
  15. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/__init__.py +1 -1
  16. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/cli.py +224 -24
  17. {typedframes-0.6.0 → typedframes-0.8.0}/rust/.cargo/config.toml +0 -0
  18. {typedframes-0.6.0 → typedframes-0.8.0}/rust/README.md +0 -0
  19. {typedframes-0.6.0 → typedframes-0.8.0}/rust/benches/parser_bench.rs +0 -0
  20. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/config.rs +0 -0
  21. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/contract.rs +0 -0
  22. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/main.rs +0 -0
  23. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/sql.rs +0 -0
  24. {typedframes-0.6.0 → typedframes-0.8.0}/rust/src/typo.rs +0 -0
  25. {typedframes-0.6.0 → typedframes-0.8.0}/rust/tests/integration_test.rs +0 -0
  26. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/_rust_checker.pyi +0 -0
  27. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/base_schema.py +0 -0
  28. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/column.py +0 -0
  29. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/column_group.py +0 -0
  30. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/column_group_error.py +0 -0
  31. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/column_set.py +0 -0
  32. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/missing_dependency_error.py +0 -0
  33. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/mypy.py +0 -0
  34. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/pandas.py +0 -0
  35. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/pandera.py +0 -0
  36. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/polars.py +0 -0
  37. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/py.typed +0 -0
  38. {typedframes-0.6.0 → typedframes-0.8.0}/src/typedframes/schema_algebra.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typedframes
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -42,7 +42,7 @@ Project-URL: Repository, https://github.com/w-martin/typedframes
42
42
 
43
43
  > ⚠️ **Project Status: Proof of Concept**
44
44
  >
45
- > `typedframes` (v0.5.0) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
45
+ > `typedframes` (v0.8.0) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
46
46
  > integrations work, but expect rough edges. The codebase prioritizes demonstrating the viability of static DataFrame
47
47
  > column checking over production-grade stability.
48
48
  >
@@ -504,7 +504,7 @@ repos:
504
504
  name: typedframes check
505
505
  entry: typedframes check . --strict
506
506
  language: python
507
- additional_dependencies: ["typedframes==0.5.0"]
507
+ additional_dependencies: ["typedframes==0.8.0"]
508
508
  types_or: [python, jupyter]
509
509
  pass_filenames: false
510
510
  ```
@@ -553,10 +553,10 @@ The action installs the PyPI wheel into a throwaway virtualenv and runs the chec
553
553
  | Input | Default | |
554
554
  |-------|---------|-|
555
555
  | `path` | `.` | File or directory to check |
556
- | `version` | `latest` | PyPI version to install, e.g. `"0.5.0"` |
556
+ | `version` | `latest` | PyPI version to install, e.g. `"0.8.0"` |
557
557
  | `strict` | `true` | Fail the step on errors — `typedframes check` exits 0 without it |
558
558
  | `coverage-fail-under` | *(unset)* | Minimum DataFrame schema coverage, e.g. `"90"` |
559
- | `coverage-detail` | `summary` | Or `term-missing` for the per-file breakdown |
559
+ | `coverage-detail` | `summary` | Or `term-missing` for the per-file breakdown, `explain` to diagnose a lower-than-expected total |
560
560
  | `no-warnings` | `false` | Suppress warning-level diagnostics |
561
561
  | `args` | *(empty)* | Escape hatch for `--no-index`, `--lenient-ingest`, `--no-info` |
562
562
 
@@ -667,17 +667,17 @@ plus the requested features, so both halves get checked normally. See
667
667
  Fast feedback reduces development time. The typedframes Rust binary provides near-instant column checking.
668
668
 
669
669
  **Benchmark results** (20 runs, 3 warmup, caches cleared between runs):
670
- *2026-08-18 · Darwin 25.6.0 · arm · CPython 3.14.4 · 64GiB RAM · Great Expectations pinned @ 1.20.0*
670
+ *2026-09-28 · Darwin 27.0.0 · arm · CPython 3.14.4 · 64GiB RAM · Great Expectations pinned @ 1.20.0*
671
671
 
672
672
  | Tool | Version | What it does | typedframes (13 files) | great_expectations (485 files) |
673
673
  |------|---------|--------------|------------------------|--------------------------------|
674
- | typedframes | 0.4.1 | DataFrame column checker | 51ms ±918µs (IQR 1ms) | 219ms ±2ms (IQR 4ms) |
675
- | ruff | 0.16.3 | Linter (no type checking) | 30ms ±764µs (IQR 922µs) | 233ms ±3ms (IQR 4ms) |
676
- | ty | 0.0.72 | Type checker | 73ms ±1ms (IQR 2ms) | 810ms ±10ms (IQR 13ms) |
677
- | pyrefly | 1.2.0 | Type checker | 104ms ±2ms (IQR 2ms) | 276ms ±9ms (IQR 14ms) |
678
- | mypy | 2.3.1 | Type checker (no plugin) | 3.07s ±17ms (IQR 25ms) | 4.56s ±29ms (IQR 57ms) |
679
- | mypy + typedframes | 2.3.1 | Type checker + column checker | 3.07s ±13ms (IQR 17ms) | 4.84s ±20ms (IQR 23ms) |
680
- | pyright | 1.1.411 | Type checker | 781ms ±5ms (IQR 6ms) | 3.46s ±25ms (IQR 41ms) |
674
+ | typedframes | 0.8.0 | DataFrame column checker | 49ms ±349µs (IQR 513µs) | 258ms ±2ms (IQR 2ms) |
675
+ | ruff | 0.16.3 | Linter (no type checking) | 30ms ±981µs (IQR 1ms) | 236ms ±4ms (IQR 6ms) |
676
+ | ty | 0.0.72 | Type checker | 72ms ±1ms (IQR 959µs) | 773ms ±8ms (IQR 14ms) |
677
+ | pyrefly | 1.2.0 | Type checker | 98ms ±1ms (IQR 1ms) | 273ms ±8ms (IQR 11ms) |
678
+ | mypy | 2.3.1 | Type checker (no plugin) | 2.79s ±93ms (IQR 61ms) | 4.14s ±41ms (IQR 52ms) |
679
+ | mypy + typedframes | 2.3.1 | Type checker + column checker | 2.78s ±13ms (IQR 14ms) | 4.50s ±26ms (IQR 33ms) |
680
+ | pyright | 1.1.411 | Type checker | 841ms ±35ms (IQR 46ms) | 3.30s ±24ms (IQR 32ms) |
681
681
 
682
682
  *Run `uv run python benchmarks/benchmark_checkers.py` to reproduce.*
683
683
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  > ⚠️ **Project Status: Proof of Concept**
10
10
  >
11
- > `typedframes` (v0.5.0) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
11
+ > `typedframes` (v0.8.0) is currently an experimental proof-of-concept. The core static analysis and mypy/Rust
12
12
  > integrations work, but expect rough edges. The codebase prioritizes demonstrating the viability of static DataFrame
13
13
  > column checking over production-grade stability.
14
14
  >
@@ -470,7 +470,7 @@ repos:
470
470
  name: typedframes check
471
471
  entry: typedframes check . --strict
472
472
  language: python
473
- additional_dependencies: ["typedframes==0.5.0"]
473
+ additional_dependencies: ["typedframes==0.8.0"]
474
474
  types_or: [python, jupyter]
475
475
  pass_filenames: false
476
476
  ```
@@ -519,10 +519,10 @@ The action installs the PyPI wheel into a throwaway virtualenv and runs the chec
519
519
  | Input | Default | |
520
520
  |-------|---------|-|
521
521
  | `path` | `.` | File or directory to check |
522
- | `version` | `latest` | PyPI version to install, e.g. `"0.5.0"` |
522
+ | `version` | `latest` | PyPI version to install, e.g. `"0.8.0"` |
523
523
  | `strict` | `true` | Fail the step on errors — `typedframes check` exits 0 without it |
524
524
  | `coverage-fail-under` | *(unset)* | Minimum DataFrame schema coverage, e.g. `"90"` |
525
- | `coverage-detail` | `summary` | Or `term-missing` for the per-file breakdown |
525
+ | `coverage-detail` | `summary` | Or `term-missing` for the per-file breakdown, `explain` to diagnose a lower-than-expected total |
526
526
  | `no-warnings` | `false` | Suppress warning-level diagnostics |
527
527
  | `args` | *(empty)* | Escape hatch for `--no-index`, `--lenient-ingest`, `--no-info` |
528
528
 
@@ -633,17 +633,17 @@ plus the requested features, so both halves get checked normally. See
633
633
  Fast feedback reduces development time. The typedframes Rust binary provides near-instant column checking.
634
634
 
635
635
  **Benchmark results** (20 runs, 3 warmup, caches cleared between runs):
636
- *2026-08-18 · Darwin 25.6.0 · arm · CPython 3.14.4 · 64GiB RAM · Great Expectations pinned @ 1.20.0*
636
+ *2026-09-28 · Darwin 27.0.0 · arm · CPython 3.14.4 · 64GiB RAM · Great Expectations pinned @ 1.20.0*
637
637
 
638
638
  | Tool | Version | What it does | typedframes (13 files) | great_expectations (485 files) |
639
639
  |------|---------|--------------|------------------------|--------------------------------|
640
- | typedframes | 0.4.1 | DataFrame column checker | 51ms ±918µs (IQR 1ms) | 219ms ±2ms (IQR 4ms) |
641
- | ruff | 0.16.3 | Linter (no type checking) | 30ms ±764µs (IQR 922µs) | 233ms ±3ms (IQR 4ms) |
642
- | ty | 0.0.72 | Type checker | 73ms ±1ms (IQR 2ms) | 810ms ±10ms (IQR 13ms) |
643
- | pyrefly | 1.2.0 | Type checker | 104ms ±2ms (IQR 2ms) | 276ms ±9ms (IQR 14ms) |
644
- | mypy | 2.3.1 | Type checker (no plugin) | 3.07s ±17ms (IQR 25ms) | 4.56s ±29ms (IQR 57ms) |
645
- | mypy + typedframes | 2.3.1 | Type checker + column checker | 3.07s ±13ms (IQR 17ms) | 4.84s ±20ms (IQR 23ms) |
646
- | pyright | 1.1.411 | Type checker | 781ms ±5ms (IQR 6ms) | 3.46s ±25ms (IQR 41ms) |
640
+ | typedframes | 0.8.0 | DataFrame column checker | 49ms ±349µs (IQR 513µs) | 258ms ±2ms (IQR 2ms) |
641
+ | ruff | 0.16.3 | Linter (no type checking) | 30ms ±981µs (IQR 1ms) | 236ms ±4ms (IQR 6ms) |
642
+ | ty | 0.0.72 | Type checker | 72ms ±1ms (IQR 959µs) | 773ms ±8ms (IQR 14ms) |
643
+ | pyrefly | 1.2.0 | Type checker | 98ms ±1ms (IQR 1ms) | 273ms ±8ms (IQR 11ms) |
644
+ | mypy | 2.3.1 | Type checker (no plugin) | 2.79s ±93ms (IQR 61ms) | 4.14s ±41ms (IQR 52ms) |
645
+ | mypy + typedframes | 2.3.1 | Type checker + column checker | 2.78s ±13ms (IQR 14ms) | 4.50s ±26ms (IQR 33ms) |
646
+ | pyright | 1.1.411 | Type checker | 841ms ±35ms (IQR 46ms) | 3.30s ±24ms (IQR 32ms) |
647
647
 
648
648
  *Run `uv run python benchmarks/benchmark_checkers.py` to reproduce.*
649
649
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "typedframes"
7
- version = "0.6.0"
7
+ version = "0.8.0"
8
8
  description = "Static analysis for pandas and polars DataFrames. Catch column errors at lint-time, not runtime."
9
9
  keywords = ["pandas", "polars", "type-checking", "static-analysis", "dataframe", "linter", "mypy-plugin"]
10
10
  readme = "README.md"
@@ -1347,7 +1347,7 @@ checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
1347
1347
 
1348
1348
  [[package]]
1349
1349
  name = "typedframes_checker"
1350
- version = "0.6.0"
1350
+ version = "0.8.0"
1351
1351
  dependencies = [
1352
1352
  "anyhow",
1353
1353
  "criterion",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "typedframes_checker"
3
- version = "0.6.0"
3
+ version = "0.8.0"
4
4
  edition = "2021"
5
5
  description = "A high-performance Rust-based checker for typedframes DataFrame column schemas"
6
6
  license = "MIT"
@@ -16,6 +16,19 @@ pub(crate) fn is_schema_base(name: &str) -> bool {
16
16
  )
17
17
  }
18
18
 
19
+ // The bare name of a decorator expression: `staticmethod` for `@staticmethod`,
20
+ // `wraps` for both `@wraps(f)` and `@functools.wraps(f)`. `None` for anything else
21
+ // (an attribute chain deeper than one level, a subscript, a call with no simple
22
+ // name, ...) -- callers treat that the same as an unrecognized name.
23
+ pub(crate) fn decorator_name(expr: &Expr) -> Option<&str> {
24
+ match expr {
25
+ Expr::Name(name) => Some(name.id.as_str()),
26
+ Expr::Attribute(attr) => Some(attr.attr.as_str()),
27
+ Expr::Call(call) => decorator_name(&call.func),
28
+ _ => None,
29
+ }
30
+ }
31
+
19
32
  pub(crate) fn extract_string_literal(expr: &Expr) -> Option<&str> {
20
33
  if let Expr::StringLiteral(s) = expr {
21
34
  Some(s.value.to_str())
@@ -182,6 +182,9 @@ pub(crate) const LOAD_FUNCTIONS: &[&str] = &[
182
182
  "read_database",
183
183
  "read_database_uri",
184
184
  "read_gbq",
185
+ // Not an I/O read, but shares the same usecols=/columns=/dtype=/schema= recognition
186
+ // and bare-load-without-a-hint fallback as every function above it.
187
+ "DataFrame",
185
188
  ];
186
189
 
187
190
  pub(crate) const LOAD_MODULES: &[&str] = &["pd", "pandas", "pl", "polars"];
@@ -241,6 +244,90 @@ pub(crate) const SQL_PRODUCING_METHODS: &[&str] = &["query", "sql"];
241
244
  // checker's existing "empty string = none" sentinel convention for the same fields.
242
245
  pub(crate) const OPEN_FRAME_MARKER: &str = "__typedframes_open_frame__";
243
246
 
247
+ // Methods that return a frame with the same columns as their receiver, beyond
248
+ // `ROW_PASSTHROUGH_METHODS` (which the assignment dispatch already propagates
249
+ // into a new binding). Consulted only by `frame_ops::classify_rhs`, to tell a
250
+ // reassignment that leaves a tracked frame's schema intact (`df = df.copy()`)
251
+ // apart from one that may change it. Deliberately conservative: anything not
252
+ // listed here is treated as possibly schema-changing.
253
+ pub(crate) const SCHEMA_PRESERVING_METHODS: &[&str] = &[
254
+ "copy",
255
+ "drop_duplicates",
256
+ "astype",
257
+ "replace",
258
+ "round",
259
+ "abs",
260
+ "clip",
261
+ "where",
262
+ "mask",
263
+ "sort_index",
264
+ "interpolate",
265
+ "shift",
266
+ "convert_dtypes",
267
+ "infer_objects",
268
+ "explode",
269
+ "isna",
270
+ "isnull",
271
+ "notna",
272
+ "notnull",
273
+ "clone",
274
+ "unique",
275
+ "drop_nulls",
276
+ "fill_null",
277
+ "fill_nan",
278
+ "slice",
279
+ "limit",
280
+ "reverse",
281
+ "lazy",
282
+ "collect",
283
+ "to_pandas",
284
+ "to_polars",
285
+ ];
286
+
287
+ // Methods whose result is a Series, scalar, list, dict, or a serialisation --
288
+ // never a frame -- so a name reassigned from one is no longer the tracked frame.
289
+ pub(crate) const NON_FRAME_METHODS: &[&str] = &[
290
+ "sum",
291
+ "mean",
292
+ "median",
293
+ "std",
294
+ "var",
295
+ "min",
296
+ "max",
297
+ "count",
298
+ "nunique",
299
+ "idxmax",
300
+ "idxmin",
301
+ "any",
302
+ "all",
303
+ "prod",
304
+ "value_counts",
305
+ "to_dict",
306
+ "to_list",
307
+ "tolist",
308
+ "to_numpy",
309
+ "to_csv",
310
+ "to_parquet",
311
+ "to_json",
312
+ "to_string",
313
+ "to_html",
314
+ "to_markdown",
315
+ "to_latex",
316
+ "to_excel",
317
+ "to_sql",
318
+ "to_records",
319
+ "memory_usage",
320
+ "iterrows",
321
+ "itertuples",
322
+ "items",
323
+ "pop",
324
+ "get",
325
+ "squeeze",
326
+ "item",
327
+ "keys",
328
+ "insert",
329
+ ];
330
+
244
331
  pub(crate) const ROW_PASSTHROUGH_METHODS: &[&str] = &[
245
332
  "filter",
246
333
  "query",
@@ -63,6 +63,17 @@ pub struct FileStats {
63
63
  pub dataframes_total: usize,
64
64
  pub dataframes_typed: usize,
65
65
  pub untyped_sites: Vec<UntypedSite>,
66
+ pub typed_sites: Vec<TypedSite>,
67
+ /// Every `<load module>.<load function>(...)`-shaped call found anywhere in the
68
+ /// file by an unconditional, position-independent scan (see
69
+ /// `linter::LoadCallSiteCollector`) -- populated regardless of whether the site
70
+ /// was actually recognized as an origin by the targeted (`Assign`/`AnnAssign`-only)
71
+ /// counting logic. Powers `--coverage-detail=explain`: cross-referencing this list
72
+ /// against `typed_sites`/`untyped_sites` by line is what tells a reader whether a
73
+ /// given call was counted at all, not just whether it was typed.
74
+ pub all_dataframe_calls: Vec<DataFrameCallSite>,
75
+ /// Every place a tracked frame stopped carrying its column set -- see [`LegEvent`].
76
+ pub leg_events: Vec<LegEvent>,
66
77
  }
67
78
 
68
79
  /// One DataFrame origin the linter recognized but could not resolve columns for.
@@ -85,6 +96,58 @@ pub struct UntypedSite {
85
96
  pub var: String,
86
97
  }
87
98
 
99
+ /// A recognized DataFrame origin that DID resolve to a concrete column set --
100
+ /// the `dataframes_typed` counterpart of [`UntypedSite`]. Line and column are
101
+ /// 1-indexed, matching [`LintError`].
102
+ #[derive(Debug, Serialize)]
103
+ pub struct TypedSite {
104
+ pub line: usize,
105
+ pub col: usize,
106
+ /// The assigned variable name where one was available, else a generic stand-in.
107
+ pub var: String,
108
+ /// A human-readable rendering of the resolved schema, from
109
+ /// `Linter::schema_display`: the schema's class name and column set for a named
110
+ /// `BaseSchema`, or `inferred column set {...}` (plus an origin, where known)
111
+ /// for one inferred from `usecols=`/a dict literal/etc.
112
+ pub schema: String,
113
+ }
114
+
115
+ /// A statement after which a tracked DataFrame no longer has a column set the checker
116
+ /// can vouch for. Powers `--coverage-detail=explain`'s "tracking ended" listing, so a
117
+ /// run can say exactly which line lost a frame and why.
118
+ ///
119
+ /// `outcome` is `"unresolved"` (the frame is still a DataFrame, but its columns are
120
+ /// unknown from here) or `"untracked"` (the name no longer holds that frame, or the
121
+ /// checker can make no claim about it). Line and column are 1-indexed, matching
122
+ /// [`LintError`].
123
+ #[derive(Debug, Serialize)]
124
+ pub struct LegEvent {
125
+ pub line: usize,
126
+ pub col: usize,
127
+ pub var: String,
128
+ pub outcome: String,
129
+ pub reason: String,
130
+ }
131
+
132
+ /// One `<load module>.<load function>(...)`-shaped call site found anywhere in a
133
+ /// file by [`crate::linter::LoadCallSiteCollector`]'s unconditional scan -- see
134
+ /// [`FileStats::all_dataframe_calls`]. Line and column are 1-indexed and point at
135
+ /// the call expression itself (e.g. the `pd` in `pd.DataFrame(...)`), NOT at an
136
+ /// enclosing assignment statement the way [`UntypedSite`]/[`TypedSite`] do -- the two
137
+ /// coordinate systems are cross-referenced by LINE only (see
138
+ /// `--coverage-detail=explain` in the CLI), not by column.
139
+ #[derive(Debug, Serialize)]
140
+ pub struct DataFrameCallSite {
141
+ pub line: usize,
142
+ pub col: usize,
143
+ /// E.g. `"pd.DataFrame"`, `"pl.read_csv"`.
144
+ pub label: String,
145
+ /// Human-readable description of the syntactic position this call was found in
146
+ /// (direct assignment, return value, call argument, collection element, ...) --
147
+ /// see `linter::CallContext::describe`.
148
+ pub context: String,
149
+ }
150
+
88
151
  /// The JSON payload returned by the `check_file` entry point: the diagnostics plus
89
152
  /// coverage stats.
90
153
  #[derive(Debug, Serialize)]