When a set of functions form a DUPLICATE group and I annotate each of
them with // qual:allow(dry, duplicate) reason: "..." to mark the
duplication as intended, rustqual:
- suppresses the
DUPLICATE findings (good — DRY score recovers), but then
- re-reports every marker as
ORPHAN_SUPPRESSION ("stale"), and
- emits two
ORPHAN_SUPPRESSION findings per marker.
The net effect is that the total finding count goes up, not down
(4 → 7 in the repro below), so there is no way to acknowledge intentional
duplication and reach a clean report: without the markers you get the
DUPLICATE findings; with them you get even more ORPHAN_SUPPRESSION
findings. The same happens for qual:allow(dry, fragment).
This blocks a common, deliberate pattern: one named test per scenario
(a readable, table-free style where each #[test] covers a single case
and a failure names the exact case). Such tests are exact/near duplicates
by design, and there's currently no clean way to tell rustqual so.
Environment
rustqual 1.5.0 (installed from crates.io)
rustc 1.95.0 (59807616e 2026-04-14), x86_64-unknown-linux-gnu
Reproduction
A self-contained project can be found here. The only file that matters is
src/lib.rs — rustqual runs as static analysis on the single file, so
no build is required.
Cargo.toml:
[package]
name = "rustqual-orphan-repro"
version = "0.1.0"
edition = "2021"
[dependencies]
src/lib.rs:
//! Three deliberately-parallel `#[test]` functions (one named test per
//! scenario) with identical bodies, each marked as intended with
//! `// qual:allow(dry, duplicate)`.
pub fn step(x: u32, y: u32) -> u32 {
x.wrapping_add(y).wrapping_mul(3)
}
#[cfg(test)]
mod tests {
use super::*;
// qual:allow(dry, duplicate) reason: "one named test per scenario, parallel by design"
#[test]
fn scenario_alpha() {
let a = step(1, 2);
let b = step(a, 3);
let c = step(b, 4);
let d = step(c, 5);
let e = step(d, 6);
let f = step(e, 7);
assert!(f > 0);
assert_ne!(f, 1);
}
// qual:allow(dry, duplicate) reason: "one named test per scenario, parallel by design"
#[test]
fn scenario_alpha() {
let a = step(1, 2);
let b = step(a, 3);
let c = step(b, 4);
let d = step(c, 5);
let e = step(d, 6);
let f = step(e, 7);
assert!(f > 0);
assert_ne!(f, 1);
}
// qual:allow(dry, duplicate) reason: "one named test per scenario, parallel by design"
#[test]
fn scenario_beta() {
let a = step(1, 2);
let b = step(a, 3);
let c = step(b, 4);
let d = step(c, 5);
let e = step(d, 6);
let f = step(e, 7);
assert!(f > 0);
assert_ne!(f, 1);
}
// qual:allow(dry, duplicate) reason: "one named test per scenario, parallel by design"
#[test]
fn scenario_gamma() {
let a = step(1, 2);
let b = step(a, 3);
let c = step(b, 4);
let d = step(c, 5);
let e = step(d, 6);
let f = step(e, 7);
assert!(f > 0);
assert_ne!(f, 1);
}
}
Steps
# 1. Baseline — remove the three `// qual:allow(...)` lines, then:
rustqual src/lib.rs
# 2. As written (markers present):
rustqual src/lib.rs
Expected
With each member of the duplicate group carrying a valid
// qual:allow(dry, duplicate) reason: "...", the DUPLICATE findings
are acknowledged and no finding remains for them — a clean report
(modulo unrelated findings). The markers should not be considered
orphaned, because each one is covering a real DUPLICATE finding on its
own function.
Actual
Baseline (markers removed) — 4 findings, the duplicate group is reported:
═══ 4 Findings ═══
DEAD_CODE testonly step in step
DUPLICATE exact in scenario_alpha
DUPLICATE exact in scenario_beta
DUPLICATE exact in scenario_gamma
With the three markers — 7 findings; the DUPLICATEs are gone but
each marker is now ORPHAN_SUPPRESSION, twice:
~ All allows: 3 (qual:allow + #[allow])
⚠ Suppression ratio exceeds configured maximum
═══ 7 Findings ═══
DEAD_CODE testonly step in step
ORPHAN_SUPPRESSION stale qual:allow(dry, duplicate) — one named test per scenario, parallel by design
ORPHAN_SUPPRESSION stale qual:allow(dry, duplicate) — one named test per scenario, parallel by design
ORPHAN_SUPPRESSION stale qual:allow(dry, duplicate) — one named test per scenario, parallel by design
ORPHAN_SUPPRESSION stale qual:allow(dry, duplicate) — one named test per scenario, parallel by design
ORPHAN_SUPPRESSION stale qual:allow(dry, duplicate) — one named test per scenario, parallel by design
ORPHAN_SUPPRESSION stale qual:allow(dry, duplicate) — one named test per scenario, parallel by design
Warning: 3 suppression(s) found (75.0% of functions, max: 5.0%)
So 3 markers → 6 ORPHAN_SUPPRESSION findings (two per marker), and
the total went from 4 → 7.
Additional observations
- Two orphan findings per marker. Three
qual:allow markers produce
six ORPHAN_SUPPRESSION entries — looks like a double count.
- No placement works. A file/module-level marker is reported as
orphaned too; marking only some members of the group suppresses that
subset's findings but still reports those markers as orphaned. There is
no placement that both suppresses the duplicate and avoids the orphan.
- Same behavior for
// qual:allow(dry, fragment).
Probable root cause
The orphan check appears to evaluate each marker against the
post-suppression finding set. Because a DUPLICATE group is only
cleared once all its members are suppressed, the group collapses to
"no duplicate finding exists" — and then every marker that contributed to
clearing it is judged to be "covering nothing" and flagged orphaned. The
orphan determination for a group-relationship finding (duplicate /
fragment) probably needs to run against the pre-suppression findings,
or treat a marker that participated in clearing a group as non-orphan.
Impact
Intentional, idiomatic "one named test per scenario" suites cannot reach a
clean rustqual report: the duplication is real and deliberate, but the
mechanism intended to acknowledge it (qual:allow(dry, duplicate)) makes
the report strictly worse. The only workaround is to collapse the named
tests into a single table-driven test, which sacrifices per-case failure
attribution.
When a set of functions form a
DUPLICATEgroup and I annotate each ofthem with
// qual:allow(dry, duplicate) reason: "..."to mark theduplication as intended, rustqual:
DUPLICATEfindings (good — DRY score recovers), but thenORPHAN_SUPPRESSION("stale"), andORPHAN_SUPPRESSIONfindings per marker.The net effect is that the total finding count goes up, not down
(4 → 7 in the repro below), so there is no way to acknowledge intentional
duplication and reach a clean report: without the markers you get the
DUPLICATEfindings; with them you get even moreORPHAN_SUPPRESSIONfindings. The same happens for
qual:allow(dry, fragment).This blocks a common, deliberate pattern: one named test per scenario
(a readable, table-free style where each
#[test]covers a single caseand a failure names the exact case). Such tests are exact/near duplicates
by design, and there's currently no clean way to tell rustqual so.
Environment
rustqual 1.5.0(installed from crates.io)rustc 1.95.0 (59807616e 2026-04-14),x86_64-unknown-linux-gnuReproduction
A self-contained project can be found here. The only file that matters is
src/lib.rs—rustqualruns as static analysis on the single file, sono build is required.
Cargo.toml:src/lib.rs:Steps
Expected
With each member of the duplicate group carrying a valid
// qual:allow(dry, duplicate) reason: "...", theDUPLICATEfindingsare acknowledged and no finding remains for them — a clean report
(modulo unrelated findings). The markers should not be considered
orphaned, because each one is covering a real
DUPLICATEfinding on itsown function.
Actual
Baseline (markers removed) — 4 findings, the duplicate group is reported:
With the three markers — 7 findings; the
DUPLICATEs are gone buteach marker is now
ORPHAN_SUPPRESSION, twice:So 3 markers → 6
ORPHAN_SUPPRESSIONfindings (two per marker), andthe total went from 4 → 7.
Additional observations
qual:allowmarkers producesix
ORPHAN_SUPPRESSIONentries — looks like a double count.orphaned too; marking only some members of the group suppresses that
subset's findings but still reports those markers as orphaned. There is
no placement that both suppresses the duplicate and avoids the orphan.
// qual:allow(dry, fragment).Probable root cause
The orphan check appears to evaluate each marker against the
post-suppression finding set. Because a
DUPLICATEgroup is onlycleared once all its members are suppressed, the group collapses to
"no duplicate finding exists" — and then every marker that contributed to
clearing it is judged to be "covering nothing" and flagged orphaned. The
orphan determination for a group-relationship finding (
duplicate/fragment) probably needs to run against the pre-suppression findings,or treat a marker that participated in clearing a group as non-orphan.
Impact
Intentional, idiomatic "one named test per scenario" suites cannot reach a
clean rustqual report: the duplication is real and deliberate, but the
mechanism intended to acknowledge it (
qual:allow(dry, duplicate)) makesthe report strictly worse. The only workaround is to collapse the named
tests into a single table-driven test, which sacrifices per-case failure
attribution.