fallow_types/trace_chain.rs
1//! Symbol-level call-chain output contracts.
2
3use std::path::PathBuf;
4
5use serde::Serialize;
6
7use crate::serde_path;
8
9/// Default chain depth when `--depth` is unset.
10pub const DEFAULT_TRACE_DEPTH: u32 = 2;
11
12/// Which directions to walk.
13#[derive(Debug, Clone, Copy)]
14pub struct TraceDirections {
15 /// Walk up to callers.
16 pub callers: bool,
17 /// Walk down to callees.
18 pub callees: bool,
19}
20
21/// The result of a symbol-level call-chain trace. Its own surface (`kind:
22/// "trace"`), NOT folded into the ranked brief.
23#[derive(Debug, Clone, Serialize)]
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25pub struct SymbolChainTrace {
26 /// The file containing the traced symbol (project-root-relative).
27 #[serde(serialize_with = "serde_path::serialize")]
28 pub file: PathBuf,
29 /// The traced symbol name.
30 pub symbol: String,
31 /// Whether the symbol's defining export was found in the graph. When
32 /// `false`, the chains are empty and `reason` explains why.
33 pub symbol_found: bool,
34 /// The chain depth applied to both directions.
35 pub depth: u32,
36 /// Whether this trace is best-effort (always `true`: symbol-level chains are
37 /// labeled best-effort, syntactic per ADR-001).
38 pub best_effort: bool,
39 /// Caller chain hops (UP). Present only when `--callers` was requested.
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub callers: Option<Vec<ChainHop>>,
42 /// Callee chain hops (DOWN) resolved to an import-symbol edge. Present only
43 /// when `--callees` was requested.
44 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub callees: Option<Vec<ChainHop>>,
46 /// Callees referenced at a call site in the symbol's module that the
47 /// syntactic walk could NOT resolve to an import-symbol edge (locals,
48 /// globals, dynamic dispatch, re-bound callees). Reported, never dropped.
49 /// Present only when `--callees` was requested.
50 #[serde(default, skip_serializing_if = "Option::is_none")]
51 pub unresolved_callees: Option<Vec<UnresolvedCallee>>,
52 /// A human-readable summary of the trace outcome.
53 pub reason: String,
54}
55
56/// One hop in a caller / callee chain.
57#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
58#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
59pub struct ChainHop {
60 /// The file at this hop (project-root-relative). For a caller hop this is
61 /// the importing module; for a callee hop the imported module.
62 #[serde(serialize_with = "serde_path::serialize")]
63 pub file: PathBuf,
64 /// The symbol name as imported across the edge (`default`, `*` for namespace,
65 /// the imported name otherwise).
66 pub imported_as: String,
67 /// The local binding name in the file at this hop.
68 pub local_name: String,
69 /// Whether the import edge is type-only (`import type { ... }`).
70 pub type_only: bool,
71 /// The hop's depth (1 = direct caller/callee of the symbol).
72 pub depth: u32,
73}
74
75/// A callee referenced at a call site that did not resolve to an import-symbol
76/// edge. Surfaced so a missing callee is never silently dropped.
77#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
78#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
79pub struct UnresolvedCallee {
80 /// The callee path as written at the call site (e.g. `helper`,
81 /// `obj.method`).
82 pub callee: String,
83 /// Why it is unresolved (best-effort classification).
84 pub reason: UnresolvedReason,
85}
86
87/// Best-effort classification of why a callee did not resolve to an edge.
88#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)]
89#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
90#[serde(rename_all = "kebab-case")]
91pub enum UnresolvedReason {
92 /// A bare identifier call with no matching import binding (a same-module
93 /// local function, a global, or a re-bound callee).
94 LocalOrGlobal,
95 /// A computed / member-expression callee (`obj.method`, dynamic dispatch).
96 MemberOrDynamic,
97}
98
99/// Target and traversal parameters for a symbol-chain trace.
100#[derive(Debug, Clone, Copy)]
101pub struct SymbolChainQuery<'a> {
102 /// File path of the target symbol, root-relative or absolute.
103 pub file: &'a str,
104 /// Exported symbol name to trace.
105 pub symbol: &'a str,
106 /// Maximum traversal depth in each direction.
107 pub depth: u32,
108 /// Which directions to walk.
109 pub directions: TraceDirections,
110}