Skip to content

refactor(checker): declare DocTarget, DocComment and Docstring rows ahead of the documentation epic #1699

Closed

Description

@dekobon

Objective

Declare DocTarget, DocComment, Docstring, Comment, DocRunOpener, DocRunDirective and HoldsDocInBody per language, so the documentation epic (#1654–#1682) lands its adapters on rows plus a DocBinding rather than a sixth per-language copy of kind membership. Keep every existing comment predicate and DLOC projection unchanged.

Umbrella: R00: #1690 · Planning ID: R09: #1699
Completion prerequisites: R03: #1693, R06: #1696 (both closed at the start of implementation). Must complete before #1655 freezes its contract; does not wait for R07: #1697, which is used only as an evidence-method source.

Why this exists

#1654's packages D07–D22 add documentation extraction across all 23 source variants. Each needs "which declaration kinds take a doc comment" — today is_doc_target in checker/*.rs, a hand-written matches! per language (Java's java_declaration names thirteen kinds) that already overlaps is_func, is_func_space and get_space_kind. If the epic ships before this exists it restates that list again, in a new file per language, and the next "doc comment on X is not attached" issue has the same trail shape as every other.

The scope is deliberately narrow, per the design review: rows remove the target-list copy only. Candidate selection (source vs legacy views — #1654 contract 2, with the Java empty-/**/ counter-example), profile composition, owner normalisation and text decoding are the epic's own work and are not kind membership. DocBinding (lang_helpers/doc_comment.rs: kept, skips, forward, gathers) stays as it is: its four callback contracts and every verdict are unchanged; where a callback's body restates a row's whole kind list (Kotlin's and PHP's forward), that membership read becomes the row's kind table and the callback's extra kinds stay with their exemption.

Docstring is a separate role from DocComment, because Python's docstring is a string expression, not a comment. Neither Python nor Elixir overrides is_doc_comment — both inherit the trait default false (checker.rs) — and Python's DLOC is computed by python_documentation_string_run in lang_helpers/python.rs, not through that predicate; Elixir's @doc attributes are not legacy DLOC at all. #1654 contract 11 keeps the broad discarded-string classification distinct from docstring recognition. So Docstring rows feed the Python LOC helper and, later, the epic's extraction; they never touch is_comment or is_doc_comment.

Premises corrected (2026-10-31, before implementation)

Re-verified against main at fdd12ad4 per AGENTS.md "Re-verify a deferred issue's premises". Each item below is a departure from the original wording of this issue; the reworded steps, tests and criteria follow.

  • Doxygen run openers / directives. checker/doxygen_doc_binding.rs holds DOC_BINDING and is_doc_target only. is_doc_run_opener is Lua and Ruby; is_doc_run_directive is Go and Ruby. The C family declares DocRunOpener / DocRunDirective absent (Doxygen attaches every block to an entry; none documents by its own content).
  • Python assignment docs. Python has no is_doc_target, so its DocTarget row is absent and the parity exception is moot. The assignment-owner recognition an adapter needs already exists in lang_helpers/python.rs (Attributes::documented_by, assignment_target) and is named in the design(ast): define documentation association and extraction contracts #1655 touchpoint edit as a reusable helper outside this package's rows.
  • is_comment reads lists_kind, not owns. Checker::is_comment(&Node) has no bytes and no chain, so it reads the ungated kind tables comment::lists_kind ∪ doc_comment::lists_kind — the DSL's documented legacy spelling (roles/mod.rs). owns with an invented empty chain would be the fix(ops): ops walk misses source-aware function spaces #1130 trap.
  • Rust's DocComment is child-gated, not text-gated. checker/rust.rs reads the doc_comment child the grammar marks and never the bytes; the row says gated_by has_doc_marker_child.
  • Comment is partitioned from DocComment. A kind that is documentation by kind (Groovy groovydoc_comment, Perl pod_statement) sits only on DocComment; Comment holds the plain comment kinds. "Any comment" is the union of the two rows' kind tables, which is what makes is_doc_comment ⇒ is_comment structural and testable. The role-set table in roles/mod.rs is reworded to match.
  • HoldsDocInBody is the residual set. The row declares only the kinds a language's Checker names beyond is_func ∨ is_closure (Java StaticInitializer / Block / SwitchBlock, Groovy StaticInitializer, Kotlin Getter / Setter / AnonymousInitializer, C# IndexerDeclaration / PropertyDeclaration); the full answer is func_opener::is_callable ∪ closure::owns ∪ holds_doc_in_body::owns, then the language's body-child test. The JS family declares it absent: its override is the body-child test alone on the openers and closures, and feat(mi): stop charging documentation in the maintainability index #1597's inner-symbol policy is DOC_COMMENTS_IN_FUNCTION_BODIES, unchanged. The role-set table is reworded to match.
  • DocTarget rows are candidates under local gates; parent/scope context stays in the Checker. A row owns a kind under gates that read the node itself (a declarator binds a name, a specifier has a body, a Go declaration is outside a function body). A clause that reads a different node's context — TypeScript's export / ambient parent and its inner-declaration sub-query, Ruby's then / modifier unwrapping and YARD scope on the original node, Rust's any-named-kind tuple-struct slot, Doxygen's ERROR-holds-an-entry — stays a named fn in checker/<lang>.rs with a role-bypass: allow(DocTarget) — context marker, and is named per language in the design(ast): define documentation association and extraction contracts #1655 touchpoint edit so an adapter composes it rather than re-deriving it. Where that makes the row over-approximate the Checker (a TS declaration under export), a test pins it.
  • Lua. Lua never overrides is_doc_target; its documentable-item list is consulted only after the --- opener and content checks inside is_doc_run_opener. Lua's DocTarget row is read by that refinement only; the Checker's is_doc_target stays the default false, because forwarding it would let plain -- runs document functions (a DLOC widening). Lua's DocRunOpener row is the opener candidate where LDoc reads; the next-dependent content decision stays in the Checker.
  • Doxygen's doxygen_entry is a second query. It is "entry without conditionals", read by conditional_declares (post-order memo) and no_entry_inside; doxygen_target is entry ∨ declaring-conditional. The migration keeps that projection (doxygen_entry::<T> tests preproc_conditional before reading the row, so the memo walk is never re-entered) and deletes doxygen_target. It matches node.kind() strings today, so the rows enumerate every alias id and a test asserts the id set equals the old name set exactly.
  • Error is not a row owner. utils/role_tables.py strips the generated ERROR sentinel, so Doxygen's error clause stays in the helper.
  • Acceptance criterion 1 is reworded below from "no kind matched outside a row" to "no membership list restated", because twelve sites inside the boundary still compare a row-owned kind for a non-membership reason (a parent test, a scan terminator, a policy subset, a body-child test), each carrying an allow naming the role.

Implementation steps

  • DocTarget rows; is_doc_target reads owns_DocTarget with the existing ancestry gates (Java's package rule, fix(metrics/loc): six LOC fixes from the 2026-09-10 residue #1446) as named fns on the row where the gate reads the node's own chain, and in the Checker where it reads another node's context (see "Premises corrected").
  • Comment and DocComment rows for comment-kind languages, text-gated where the tool reads the marker from bytes (Java /**, Doxygen's forms, KDoc, PHPDoc, C# XML docs, JSDoc / TSDoc, Perl POD) and child-gated for Rust /// / //!; is_comment reads lists_kind_Comment ∪ lists_kind_DocComment so is_doc_comment ⇒ is_comment is structural — for comment kinds only.
  • Docstring rows for Python (position-gated string expression, one owner per docstring — the statement value, never a fragment under concatenated_string), read by python_documentation_string_run at the point its backward scan already runs; is_doc_comment and is_comment for Python stay at their current verdicts (both unchanged — the default false for is_doc_comment). Elixir's @doc/@moduledoc recognition is left to the epic (D19, feat(ast): extract Elixir documentation attributes #1676); declare absent(Docstring, "extraction owned by #1676") for now.
  • DocRunOpener / DocRunDirective / HoldsDocInBody rows where a language has them (Lua and Ruby openers, Go and Ruby directives; Java StaticInitializer / Block / SwitchBlock and the other residual kinds listed above); their is_* impls read the rows, composed with the retained content / body-child refinements.
  • Note in roles/mod.rs for the epic: an adapter's recognition set is its rows, composed with the named context fns in checker/<lang>.rs; its DocBinding, selection and text work are not rows. Edit design(ast): define documentation association and extraction contracts #1655's "Implementation touchpoints" to name big-code-analysis-ast/src/roles/<lang>.rs as the recognition source, link this issue, and name per language the reusable entry points (rows, context fns, doc_comment.rs text tests, doxygen.rs mechanism, python.rs run and assignment helpers).

Tests and edge cases

  • src/metrics/loc/dloc.rs and the DLOC corpus snapshots byte-identical — DLOC must not widen (feat(ast): expose declaration documentation fragments and text across all languages #1654 contract 11).
  • Derived parity per language: owns_DocTarget ⊆ owns_FuncOpener ∪ owns_Container ∪ owns_Member evaluated on the same node and real chain, or an exception names it — as excludes [K "why"] on the space-tree row where that row's author genuinely rejected the kind (Java EnumConstant on Member), else in a test-side (language, kind, reason) table; both directions asserted so a stale entry fails. The Rust tuple-struct slot and Lua's default-false Checker are non-row facts pinned by adapter tests.
  • Python: is_comment and is_doc_comment verdicts on a docstring fixture unchanged (both false, beside a # comment answering true); docstring::owns answered directly on primary, additional, attribute, discarded, concatenated and non-str strings; python_documentation_string_run's exact credited row sequence unchanged.
  • Zero drift; both snapshot forms; plain run.

Acceptance criteria

Verification and completion evidence

Completion record (2026-11-01)

Landed as two commits on refactor/1699-doc-roles (S0 tooling e809a3d3; the migration). Counts on the final tree against fdd12ad4: corpus 0 of 46,592, fixtures 0 of 952, recovery 0 of 107,106, C-family per-node is_doc_target oracle 7,675,318 nodes byte-identical. Census 0 pending. #1655's touchpoints edited. Commands and the per-slice record are in the completion comment below.

Activity

  1. added
    refactorCode maintainability / tech-debt cleanup (no behaviour change)
    on Oct 23, 2026
  2. dekobon commented on Oct 23, 2026

    @dekobon
    OwnerAuthor

    Tracking is ready

    The body now links the master issue and the work packages it references. Native parent and blocked-by relationships match the listed completion prerequisites. Implementation and acceptance checkboxes remain open; prerequisite closure and the R04: #1694 go decision govern execution order.

  3. dekobon commented on Nov 1, 2026

    @dekobon
    OwnerAuthor

    Premises re-verified; body amended before implementation

    Implementation starts on refactor/1699-doc-roles from main at fdd12ad4. Per AGENTS.md "Re-verify a deferred issue's premises", the body was checked clause by clause against that tree before the first edit, and the departures are now recorded in a Premises corrected block in the body — not only here — because #1655 freezes against whatever this body says.

    Prerequisites: R03 (#1693) and R06 (#1696) are closed. R07 (#1697) is not a dependency; its oracle / recovery / perturbation drivers are reused as evidence method.

    Two stale premises:

    1. Step 4 placed Doxygen run openers and directives in checker/doxygen_doc_binding.rs. That file holds DOC_BINDING and is_doc_target only; is_doc_run_opener is Lua + Ruby and is_doc_run_directive is Go + Ruby. The C family's two rows are absent with the reason.
    2. The parity exception "Python assignment docs" assumes a Python is_doc_target. There is none, so Python's DocTarget is absent. The assignment-owner helpers an adapter needs (Attributes::documented_by, assignment_target in lang_helpers/python.rs) are named in the design(ast): define documentation association and extraction contracts #1655 edit instead.

    Six readings the body now states explicitly, each argued from the signature or the DSL rather than preferred:

    Reading Why
    is_comment reads lists_kind, not owns Checker::is_comment(&Node) has no bytes or chain; lists_kind is the DSL's documented legacy spelling (roles/mod.rs). owns(node, &[], known(&[])) would be the #1130 shape.
    Rust DocComment is child-gated checker/rust.rs:57-64 reads the doc_comment child, never the bytes.
    Comment ∖ DocComment partition Groovy groovydoc_comment and Perl pod_statement are documentation by kind; putting them only on DocComment is what lets a test fail when the union is dropped from is_comment. Empty /**/ and a POD =begin comment region stay (is_comment, is_doc_comment) = (true, false) via the ungated kind table.
    HoldsDocInBody is residual Every Checker answers is_func ∨ is_closure ∨ <extras> then a body-child test; the row owns the extras, so no kind is declared on two rows. The JS family's override is the body-child test alone, so its row is absent; #1597's policy is the unchanged DOC_COMMENTS_IN_FUNCTION_BODIES.
    DocTarget = candidate + local gates; context in the Checker A row gate reads (node, code, ancestors) of the node asked about. TypeScript's export sub-query, Ruby's unwrapped core, Rust's any-kind tuple slot and Doxygen's ERROR clause each read a different node's context; those stay named fns with allow(DocTarget) — context, named per language in the #1655 edit.
    Lua No is_doc_target override exists; forwarding one would let -- runs document (DLOC widening). The row is read by the next refinement of is_doc_run_opener only.

    Two mechanism facts the body now carries: doxygen_entry is a separate "entry without conditionals" query with a post-order memo, kept as a projection over the row that tests preproc_conditional first; and Error cannot own a row (role_tables.py strips the sentinel).

    Acceptance criterion 1 is reworded from "no kind matched inside the boundary outside a row" to "no membership list restated; every remaining compare carries an allow naming the role" — twelve sites compare a row-owned kind as a parent test, scan terminator, policy subset or body-child test, and a row projection for each would be a second declaration with one reader.

    Plan of record: seven slices (comment-only languages → marker languages → positional languages → Doxygen family → JS family → Python → cross-cutting), reviewed at three checkpoints; zero-drift oracle at base and every slice; final history ≤ 3 commits.

  4. dekobon commented on Nov 1, 2026

    @dekobon
    OwnerAuthor

    Progress: S1 + S2 landed on refactor/1699-doc-roles

    S1 (8e997a98) — Bash, Tcl, iRules, Ccomment, Preproc, Elixir: Comment + six absent rows; impl_comment_from_rows! reads comment::lists_kind ∪ doc_comment::lists_kind; roles/doc_tests.rs per-node table. CP1 reviews (astra, sol, algorithmic-cost audit): 5 findings, all applied — absent reasons that claimed no tool exists (shdoc, Ruff!) reworded to "convention not recognised"; the Elixir @doc negatives anchored so the fixture cannot lose them; a dloc mention removed from a row comment; the union's observability qualified; the mod.rs allow rationale updated. Cost audit: release IR shows one kind_id FFI call + one icmp per is_comment, strictly cheaper than the From<u16> round-trip it replaced.

    S2 (edc1cf67) — Java, Groovy, Kotlin, C#, PHP, Rust, Perl: Comment, DocComment (text-gated; Rust child-gated), DocTarget, HoldsDocInBody where the Checker names residual kinds. Java's package rule is a row gate reading its own lists_kind; Rust's tuple slot stays a Checker clause; Kotlin/PHP forward read the DocTarget kind table; Groovy groovydoc_comment and Perl pod_statement sit on DocComment alone (the partition the union exists for). 11 pre-existing role-bypass exemptions gained DocTarget / Comment where a newly claimed kind is compared for a non-membership reason.

    Zero drift (S2 tip 900176c8, bca 68c230d210e8 vs base fdd12ad4 3adf2747833b): oracle.py over tests/repositories (four submodules at the SHAs fdd12ad4 records) + the R07 fixtures + the new doc fixtures, seven metrics option variants + ops / dump / find / count + the verbatim FuncSpace export: 0 of 46,592 corpus outputs and 0 of 588 fixture outputs differ.

    Perturbations (S2): 22 single-edit mutants of a row kind, a gate's condition, or a Checker composition. 19 caught by the named doc_tests test; 2 (Kotlin / PHP forward dropping the lists_kind read) caught by the root loc:: suite; 1 (C# reading the row after the callable test) unobservable in any verdict — a cost fact, pinned by the loc/csharp-accessor-docs probe — and the test comment now says so.

    Gates: role-census 144 → 72 pending (all R09), role-coverage remove-only, role-bypass OK; cargo nextest --workspace --all-features 6631 passed; 0 .snap.new, 0 .pending-snap.

    Next: S3 (Go, Lua, Ruby — DocRunOpener / DocRunDirective), then S4 Doxygen (CP2).

  5. dekobon commented on Nov 1, 2026

    @dekobon
    OwnerAuthor

    Progress: S3 landed (39df4cd2)

    Go, Lua, Ruby: Comment, DocTarget under the tools' scope and shape gates, DocRunDirective (go/doc directives, Ruby magic comments), DocRunOpener (LDoc --- block, RDoc/YARD ## / @! block where read); DocComment absent. Lua's Checker::is_doc_target stays the trait default — its row is read only by the opener's content decision (the fixture-decay guard is a row-true / Checker-false table). Ruby's wrappers and YARD scope stay as Checker context over the original node.

    Zero drift: 0 of 46,592 corpus outputs, 0 of 630 fixture outputs (bca b17964b4d69f vs base 3adf2747833b). Perturbations: 20 single-edit mutants, every one caught by a named doc_tests test (one needed a ##-in-a-def fixture row to pin the opener's scope term; added). Gates: census 72 → 54 pending; coverage remove-only; bypass OK after 11 consumer sites gained DocTarget / Comment in their existing allow; 6636 workspace tests; 0 pending snapshots.

    Next: S4 Doxygen (CP2). A per-node is_doc_target oracle over every C-family node of the corpus and six Doxygen fixtures (entries per dialect, an Objective-C header through C / C++ / Mozcpp, error recovery, a sole conditional child of an error) is captured at base: 7,674,389 nodes, 354,274 targets, to be compared line-for-line after the string-keyed doxygen_entry becomes id-keyed rows.

  6. dekobon commented on Nov 1, 2026

    @dekobon
    OwnerAuthor

    Completion and evidence record

    Branch refactor/1699-doc-roles, two commits on fdd12ad4 (origin/main): e809a3d3 (S0, the bypass-gate self-test case) and the migration. Final tree identical to the pre-squash tip (backup/1699-before-squash, tree hash compared).

    What landed

    • Seven documentation rows in every roles/<lang>.rs (Comment, DocComment, Docstring, DocTarget, DocRunOpener, DocRunDirective, HoldsDocInBody); fragments c_family_doc_entries!, cpp_family_doc_entries!, c_family_doc_conditionals!, objc_doc_conditionals! (roles/c_family.rs), js_family_doc_targets!, ts_family_doc_targets! (roles/js_family.rs); gate emitters c_family_doc_gates!, js_family_doc_gates!, js_doc_gates!, ts_doc_gates!.
    • Checker forwards in checker.rs: impl_comment_from_rows! (reads comment::lists_kind ∪ doc_comment::lists_kind), impl_doc_comment_from_row!, impl_doc_run_directive_from_row! (bytes, Ancestors::known(&[])), holds_doc_in_body_kind! (&[] under const assert!(!TEXT_GATED)).
    • Retained Checker context, each under role-bypass: allow(DocTarget …) — <reason>: tsdoc_target (declaration under export / declare), Ruby then wrapper + ruby_yard_reads scope, Rust tuple-struct slot, Doxygen ERROR holding an entry (lang_helpers::doxygen::doxygen_target), Lua's run-content decision.
    • lang_helpers::doxygen: doxygen_target is deleted as a string-keyed list and reborn as the row ∪ ERROR composition; doxygen_entry::<T: DoxygenRow> reads the row directly, preproc_conditional first (pinned by count: 12 reads vs 4,095 on the other order, doxygen_entry_never_reenters_the_conditional_walk); conditional_declares hands each child a chain ending in the conditional.
    • lang_helpers::python: python_is_documentation_string is the Docstring gate; python_documentation_string_run shares its one scan (one_scan_per_run_in_release, counter docstring_runs_scanned). New lang_helpers/ruby.rs (YARD scope fold, call forms). roles/doc_tests/{mod,doxygen,parity}.rs.
    • roles/mod.rs: role-set table reworded; "For the documentation epic" note. design(ast): define documentation association and extraction contracts #1655 "Implementation touchpoints" edited (rows, context fns, reusable helpers per file) and its coordination section updated.

    Zero drift (final tree vs fdd12ad4)

    Driver: oracle.py (R07's, with .h → objc added to the recovery populations) over tests/repositories (DeepSpeech aa1d2853, pdf.js 65c4a4b3f, serde d6de9118, big-code-analysis-output af5c6222), the R07 fixtures and 26 doc fixtures (one per language spelling every row owner and both sides of every gate; six Doxygen fixtures incl. an Objective-C header read as C / C++ / Mozcpp, recovery, a sole conditional child of an error). Seven metrics option variants + ops / dump / find / count + the verbatim FuncSpace export; compare.py on each side's capture.

    instrument outputs differing
    corpus (3,328 files, 25 populations) 46,592 0
    fixtures (63 files) 952 0
    recovery sweep (every line / word / operator cut, both fixture roots) 107,106 0
    C-family per-node Checker::is_doc_target with the real chain, error-recovered trees included (doxygen-oracle, four dialects, every .h through all four) 7,675,318 node lines byte-identical

    Per slice (S1–S6) the same corpus compare read 0 of 46,592 each time; fixtures 0/588 → 0/952 as they grew; recovery 0/49,188 (S4), 0/40,104 (S5), 0/107,106 (S6).

    One migration defect, caught before any capture saw it (astra + the cost audit at CP3, independently): the TypeScript export gate read its declaration child through lists_kind, gates aside, so /** d */ export const {x} = o; documented (base dloc 0 → S5 tree 1; no corpus file has a doc-commented destructuring export). Fixed by owns(&inner, …) with the export's chain; pinned as NONE rows in the TS and TSX tables and in checker_composes_the_row_with_the_wrapper_context. The row-level Python tuple over-approximation ("a", "b" after an assignment) was likewise invisible to the LOC walk and is now is_bare_statement inside documentation_run.

    Tests and perturbations

    45 tests in roles/doc_tests/: a per-node answer table per language (seven rows + is_comment / is_doc_comment, exact walk order, every declared owner met, defensive aliases declared-and-unseen), checker_composes_* where context composes (Java, Kotlin, C#, PHP, Rust, Lua, Ruby, TS, TSX), four exact alias guards (old_names_are_the_row per Doxygen dialect — the row equals the id set the deleted name list denoted), the ERROR clause on recovery fixtures, and three cross-language pins: doc_targets_outside_the_space_tree_are_each_named (declared lists, both directions), doc_targets_the_space_tree_rejects_by_gate_are_each_named (gated nodes with the real chain; C#'s bodied property / indexer are the named shapes; non-vacuity count per language), doc_comment_implies_comment_and_docstring_implies_neither (the Checker on nodes; a dropped DocComment union fails on Groovy).

    Perturbation sweeps (in-memory original, restore in finally, old != new and count == 1 asserted per edit): S1 3, S2 22, S3 20, S4 12 + 5 (CP2), S5 14, S6 5, CP3 6 — every mutant of a row kind list, a gate's condition or a Checker composition fails a named test; the two cost facts no verdict can see (C# branch order, Python one scan per run) are pinned by observation counters (mutants 3 vs 0 searches; 2× scans).

    Gates (final tree)

    make pre-commit → BCA_GATE: pass (gate=pre-commit) (also in a fresh worktree after make worktree-setup); make role-census 0 pending (144 → 0), make role-coverage remove-only, make role-bypass OK (4 baselined sites, unchanged); make check-test-lang-gates-compare COMPARE_REF=origin/main OK; make chain-audit 613 passed; make bench-scaling 99/99, doc-path probes 0.99–1.05; cargo llvm-cov per ci.yml + utils/patch-coverage.py … fdd12ad4: patch 99.49 % vs project 98.79 %; 0 .snap.new / .pending-snap; -p big-code-analysis-ast --no-default-features --features {go,cpp,python,ruby} each green with differing filtered-out counts.

    Reviews

    CP1 (S1), CP2 (S2–S4), CP3 (tip): astra, sol, an algorithmic-cost audit at each; at the tip also code-review --effort max (correctness, cleanup), a least-reviewed pass, and two closing reviews (contract alignment: 1–8, 10 MET, 9 MET after the final re-capture; architecture). All findings applied or recorded as follow-ups on #1700 / #1701.

  7. dekobon commented on Nov 1, 2026

    @dekobon
    OwnerAuthor

    Post-review update (tip f4d35453 on e809a3d3)

    After the completion comment above, three skills and seven review rounds were applied and squashed into the migration commit. The work is unchanged in shape; what moved:

    Found and fixed in review

    • A cost regression introduced mid-review: conditional_declares handed each conditional's children a copy of the walk's chain, O(depth) per conditional and quadratic over independent conditionals at growing depths (astra 1.85, cost audit 1.47 vs base 0.97 at depth 16k; the existing nest-axis probe loc/c-nested-conditional-docs could not see it). Now a one-entry context (chain_below, no allocation); new probe loc/c-conditional-per-struct-depth reads 1.50 with the copy, 1.04 without. no_entry_inside hands an ERROR's child the same context (lesson 103), pinned by error_child_is_asked_with_the_error_as_its_parent.
    • The Ruby analogue: a wrapper's core is asked with its own chain, copied only on a wrapper hit (the first version copied eagerly — loc/ruby-nested-doc-constants 1.61 at 4k/8k/16k — and off by one). core_is_asked_with_its_own_chain holds the chain's parent to the tree's for every wrapper shape; mutants of both defects fail it.
    • The doc_tests/mod.rs harness lost c/cpp/mozcpp/objc from its cfg unions to a --fix that read only mod.rs (doxygen.rs reaches it via use super::*); every C-family single-language test build broke and lang-go (ast) clippy failed on an unused closure. Fixed with full unions + hand-written(c, cpp, mozcpp, objc) markers, c_entries cfg-gated, and a check-test-lang-gates-test.py case for the blind spot.
    • Audit-tests: 10 fixture gaps — both sides of every gate per dialect (bodiless union/enum/struct, #ifdef EMPTY, #elifdef, Go type_elem outside an interface, JS using, TS/TSX computed_property_name / module / var / generator / shorthand, TSX --> html); reachable kinds out of defensive; TSX now pins the whole TS table on its own ids; declaration_children held to the grammar's declaration supertype from node-types.json; a dead OWNERS.len() assert dropped; check-role-bypass.py validates the role of an exempted no-scope kind() == "…" site.
    • Parity test: owned_in dialect lists derived from each dialect's space rows; a completeness pin against roles::doc_target_rows() (itself held to LANG), filtered by is_enabled() so single-language legs pass.
    • A tenth Answers slot reads Checker::is_doc_run_directive beside its row — the last byte-less forward without a chain-dimension pin (mutant: Go + Ruby tables fail).

    Consolidation: impl_doc_comment_rows_in_bodies!, impl_doc_target_from_row!, impl_byte_gated_row_predicate!, roles::byte_gate!; c_family_doc_entries! is the 11 shared kinds plus c_doc_declarations! / cpp_doc_entries!; DoxygenRow::doc_target_row is an associated fn with no Checker supertrait; YardScope::{DefBody, Namespace}; owners_covered serves all four harnesses (closing space_tree_tests' weaker inline check); the Python debug_assert_eq! double scan removed (one_scan_per_run pins runs flat); the two use crate::Cpp; let _ = … gate-steering hacks removed.

    Pre-existing, filed: #1767 — the loc walk is quadratic in C++ namespace nesting depth on main (found validating the probe; the probe nests structs to avoid it).

    Evidence on the final tree (6c564d84 ≡ f4d35453) vs fdd12ad4, same drivers and populations as above: corpus 0 / 46,592; fixtures (68 files) 0 / 952; recovery sweep 0 / 107,106, 0 failed; per-node C-family is_doc_target oracle byte-identical (7,675,318 lines). make pre-commit BCA_GATE: pass in a fresh worktree after make worktree-setup, 0 pending snapshots; chain-audit 617; bench-scaling 100/100; check-test-lang-gates-compare COMPARE_REF=origin/main OK; -p big-code-analysis-ast --no-default-features --features {objc,go,ruby} clippy clean, --features go tests 258 passed. Commands and captures: /var/tmp/bca/1699/PROVENANCE.md (tier 2; the counts are the record).

    Deferred to #1701 from the reviews: CHAIN_GATED for the signature-less forwards (now the only unpinned axis), the four shape helpers in checker.rs to lang_helpers/, whether lang_helpers/ and roles/ join the bypass BOUNDARY (eleven markers moved there are prose today), a fn-pointer port replacing DoxygenRow, HoldsDocInBody naming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactorCode maintainability / tech-debt cleanup (no behaviour change)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions