refactor(cognitive): thread Nesting end to end - #1104
Merged
Conversation
`increase_nesting` was the last bare-positional-`usize` triple in the cognitive walk, spelled `(stats, &mut nesting, depth, lambda)` at 43 call sites across the 19 per-language modules and the `js_cognitive!` macro. It now takes `&mut Nesting`, and every `compute` body holds the struct end to end rather than destructuring it into three same-typed locals and rebuilding it on the way out. Adds `Nesting::total()` for the `conditional + function_depth + lambda` sum, which was open-coded at its two sites. No behaviour change: the full workspace suite is bit-identical. Ruby's `halstead.effort` baseline drops to match the smaller module. Two tests pin what the new shape makes newly expressible — bumping the wrong channel, and dropping one from the sum. Both were verified by perturbing the production line; a `function_depth += 1` slip fails the new test plus the five nested-function tests that observe it downstream, where a function boundary resets `conditional` alone. Also fixed, in files this already touches: the Python comprehension test covered only a trailing-`for` fixture, leaving the `last_clause_is_if` term of the returned element depth untested (deleting it left the test passing); the `increase_nesting` test asserted the `boolean_seq` reset and `increment`'s accumulation from zeroed state, where neither could fail; and `elixir.rs`'s header comment contradicted its own code on both `try` nesting and the deliberate absence of a function-depth bump. Fixes #1086
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1104 +/- ##
==========================================
+ Coverage 98.16% 98.18% +0.01%
==========================================
Files 272 272
Lines 68295 68206 -89
Branches 67865 67776 -89
==========================================
- Hits 67045 66968 -77
+ Misses 858 848 -10
+ Partials 392 390 -2
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
An assertion that a function resets or accumulates something proves nothing from a default-constructed fixture: `Default::default()` is the state a reset produces and the value `+=` and `=` agree on, so the assertion holds whether or not the line under test exists. Both instances cited shipped in the same test during #1086 and were measured rather than guessed — deleting the production `reset()` call failed zero of 3,130 lib tests.
A comment inside a match pattern — before `=>` or between `|` alternatives — makes rustfmt emit the entire enclosing item verbatim. There is no diagnostic and `cargo fmt --check` still exits 0, so a bulk or regex edit across sibling modules lands misformatted in exactly the files the gate cannot see. This happened during #1086. Affects seven modules under src/metrics/cognitive/. Records the perturbation procedure for testing a file, including the stderr trap that makes a `mod`-declaring file look like it bailed when rustfmt actually failed to resolve the module tree.
Two factual errors in the rule as first written, both found by review and confirmed by experiment: `macro_rules!` bodies are not exempt from formatting and are not a separate cause. rustfmt formats them by default (`format_macro_bodies`), and `js_cognitive!` in cognitive.rs bails for exactly the in-pattern comment reason — removing its `Else /* else-if also */` arm comment makes rustfmt reformat the whole body. The rule previously told readers the opposite, excluding the clearest example of the hazard. The affected set is eight sites, not seven. The bail is scoped to the enclosing match, not the whole item. Statements after the match still format, which is what made an earlier check on an unrelated function in the same file read as "unaffected". Also replaces the hardcoded module list with a snippet that regenerates it, and records the two measurement traps that produced the wrong answer the first time.
Both arms measured zero coverage. iRules had nine cognitive tests but none exercising `catch`, and mozcpp had no cognitive tests at all despite the fork being expected to stay metric-equivalent to upstream C++. Neither gap was introduced by the Nesting refactor — patch coverage surfaced them because that change rewrote every line in these modules, which pulled pre-existing untested arms into the diff. Expected values were derived before running, then confirmed by perturbation: gutting the `Catch` arm scores 0 instead of 1, and dropping mozcpp's lambda increment scores 1 instead of 2.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1086.
increase_nestingwas the last bare-positional-usizetriple in thecognitive walk —
(stats, &mut nesting, depth, lambda)at 43 call sitesacross the 19 per-language modules and the
js_cognitive!macro. It nowtakes
&mut Nesting, and everycomputebody holds the struct end toend instead of destructuring it into three same-typed locals and
rebuilding it on the way out. Adds
Nesting::total()for theconditional + function_depth + lambdasum, which was open-coded at twosites.
Net −120 lines across the cognitive modules. No behaviour change:
cargo test --workspace --all-featuresis bit-identical.Corrections to the issue's framing
The issue was wrong that a field transposition here is unobservable,
and that mattered — it was the stated justification for landing with no
test. Perturbing
nesting.conditional += 1tonesting.function_depth += 1fails six tests: the new unit testplus
java_nested_method_resets_nesting_and_adds_depth,cpp_nested_function_resets_nesting_and_adds_depth,groovy_nested_method_resets_nesting_and_adds_depth,php_nested_function_resets_nesting_775, andcsharp_local_function_in_if_does_not_inherit_nesting.The charge itself is symmetric so the mistake is invisible there, but a
function boundary resets
conditionalalone, so a misplacedincrement survives into
function_depthand moves the score. This isthe opposite of #1070's case, where every transposition genuinely was
unobservable; the two should not be cited together.
Two smaller corrections: the call sites span 19 modules plus the
macro (not "21 of 23"), and
total()replaced two open-coded sums(not three).
Tests
Two new unit tests pin what the new shape makes newly expressible —
bumping the wrong channel, and dropping one from the sum. Both were
verified by perturbing the production line per
.claude/rules/testing.md, not merely by passing.Reviewing the diff surfaced adjacent gaps in files this already touches,
fixed here rather than deferred — each also verified by perturbation:
python_comprehension_clauses_carry_inherited_depth_and_lambdacovered only a trailing-
forfixture, so the+ usize::from(last_clause_is_if)term of the returned element depthcontributed 0 and was untested — deleting it left the test green.
Added a trailing-
iffixture ([x for a in xs for b in ys if b],element depth 3).
increase_nestingtest first asserted theboolean_seqresetfrom
Stats::default(), where it cannot fail; deletingstats.boolean_seq.reset()was measured at zero failuressuite-wide. Now seeded. Same for
increment's+=vs=, which wasindistinguishable from a zeroed
structural.elixir.rs's header comment contradicted its own code twice: itdocumented
tryas adding+nestingon the container (deliberatelyexcluded) and
defas bumping the function depth (deliberately not —every
definside adefmodulehas aCallancestor, which wouldfalsely raise it).
As a sanity check on the suite itself, gutting
increase_nestingto anear-no-op fails 263 of 3,130 lib tests.
Gates
make pre-commitgreen: clippy (both feature flavours), fmt,cargo doc -D warnings, udeps, workspace tests with zero failures and no.snap.newdrift, both self-scan tiers, and the--no-default-features/--features rust,typescriptCI matrix legs..bca-baseline.tomlneeded one refresh:RubyCode::compute'shalstead.effortdropped 53802 → 47981, a tightening consistent withthe smaller module.
Known trade-off
Threading the struct removes the compile-time guard that
bash.rs,c.rs,irules.rs, andtcl.rsgot from bindinglambdaimmutably —a stray
nesting.lambda += 1there now compiles. The residual risk islow and untestable in a meaningful way: those four languages have no
lambda or closure construct, so there is no arm that could plausibly
grow such a write, and the real protection was always "the grammar has
no such node" rather than the binding.
Follow-up
#1103 — the
nesting.conditional = 0;+increment_function_depth(...)pair now repeats verbatim in 17 of 23modules and wants an
enter_function_boundaryhelper (~−55 lines). Keptout of this PR so the diff stays atomic and bit-identical by inspection.
Toolchain gotcha for reviewers
cargo fmt --checkexits 0 over misformatted code in these modules.Any comment inside a match pattern — a block comment before
=>(
Else /* else-if also */ =>) or//lines between|alternatives(
perl.rs) — makes rustfmt emit the enclosing match verbatim.Eight sites are affected: the modules
c.rs,cpp.rs,java.rs,mozcpp.rs,objc.rs,perl.rs,rust.rs, plus thejs_cognitive!macro body in
cognitive.rs.macro_rules!bodies are not exempt —rustfmt formats them by default, so the macro bails for the same reason
as the rest.
This bit the PR: a regex rewrite of 43 call sites left hanging-argument
calls and over-length lines that
cargo fmt --all -- --checkreportedas clean, all found by reading the diff. Written up with a
list-regeneration snippet in
.claude/rules/formatting.md.(Two earlier revisions of this section got the count and the cause
wrong — first eight-with-the-wrong-reason, then seven. The commit
cda402f1records the corrected version and the two measurement trapsthat produced the wrong answers.)