1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! candor-scan — a STABLE-Rust effect scanner. Produces the same candor report JSON the nightly
//! `rustc_private` lint does, but with a purely syntactic backend: walk the crate's `.rs` files, parse
//! with `syn`, build a name-resolved-enough call graph, classify path-qualified calls with the shared
//! `candor-classify`, and propagate transitively. No nightly, no `rustc-dev`, no dylint — `cargo install`
//! and run anywhere.
//!
//! THE PRECISION TRADE vs the lint, stated plainly. This is syntactic, so it sees what's written, not
//! what's resolved. It CATCHES: path-qualified effect calls (`std::fs::read`, `reqwest::Client::execute`,
//! `Command::new`), including `use`-aliased prefixes; intra-crate calls (matched by name) for transitive
//! propagation; macro bodies; and local-type/local-trait method dispatch. It DISCLOSES `Unknown` where it
//! can see the boundary it can't see through: an invoked fn-value/callback (scan.rs), an FFI `extern`
//! call (scan.rs), an untrusted chained dep report (deps.rs). It MISSES (silently, by design — no
//! `Unknown`): effects reached only through external-trait dispatch or an uninferrable receiver,
//! desugared operators/`?`/`.await`/Drop-glue of external types, and cross-crate propagation by stable
//! identity. So on resolution-heavy code it under-reports relative to the lint. Use the lint when you
//! need the soundness contract; use this when you need zero-friction, stable, installable triage.
//! Shares the lint's classifier — one source of truth.
//!
//! CALL RESOLUTION. The local call graph is name-resolved, not type-resolved. A qualified `Type::method`
//! call (or an associated-fn call `RequestBuilder::new()`) is matched on its 2-segment tail, but ONLY when
//! that tail is UNAMBIGUOUS, which keeps same-named methods on different types distinct. A bare
//! free-function call falls back to a unique leaf. A `.method()` call whose receiver type is inferred to a
//! LOCAL type resolves through that type's `Type::method` tail (so `x.go()` reaches a local `S::go`); an
//! external or un-inferrable receiver leaves the bare `.method()` with no definite target, so it
//! under-reports rather than guess (this is what stops `range.start()` — on the external `FloatRange` —
//! linking to a unique local `Clipboard::start`). We deliberately do NOT link a many-way-ambiguous name:
//! on a real crate that would link every `.new()` to all 100+ `*::new` defs and smear one type's effect
//! across the whole graph. Under-reporting an ambiguous edge is the honest failure
//! mode; fabricating one is never ok. The shared resolver is `resolve_target`.
//!
//! Usage: candor-scan [<crate-dir>] [--out <prefix>] [--json] [--include-tests]
//! default dir = ".", default prefix = "<dir>/.candor/report"; writes <prefix>.<crate>.scan.json (+ a
//! callgraph sidecar so `cargo candor callers <fn>` works on the stable report too). `--json` prints
//! the report to stdout instead. By DEFAULT only the crate's library/binary source is scanned —
//! `tests/`, `benches/`, `examples/`, `test/`, the root `build.rs` (the Cargo build script — but NOT a
//! `src/build.rs` source module), and `#[cfg(test)]` modules are skipped, so
//! the report describes what the CRATE does, not what its harness does (`--include-tests` keeps them).
//! See eval/calibration for accuracy on 35 real crates.
pub use ;
pub use Path;
pub use ReportEntry;
pub use Visit;
// One flat crate namespace: main.rs was a single 7.9k-line file until 2026-07; the
// split into modules kept every item reachable exactly as before (byte-identical
// reports gated the move), so the modules re-export crate-wide.
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;