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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Best-effort recovery of what a macro's opaque token stream references.
//!
//! `syn`'s visitor treats a macro body as an opaque [`proc_macro2::TokenStream`],
//! so calls, identifiers, and `self` uses inside `vec![…]`, `format!(…)`,
//! `rsx!{…}` etc. are invisible to every analyzer that only walks the AST.
//! This module is the single place that re-derives that information, so the
//! call-graph / cohesion / structural sites don't each reinvent a weaker copy
//! (the historical bug: ~7 independent `Punctuated<Expr, Comma>` parses, each
//! blind to the `;`-repeat and block-bodied forms).
//!
//! Two levels, matching the two safe directions:
//! - [`recover_exprs`] — *structured*: returns real [`syn::Expr`]s, so callers
//! can run their normal expr visitors. Safe for everyone (no false positives,
//! only best-effort recall). The architecture `forbid_*` matchers use ONLY
//! this — a raw over-collecting fallback there would manufacture false
//! forbidden-call violations.
//! - [`idents_in_call_position`] — *raw, positional*: for reachability consumers
//! (dead-code, TQ) where a recovered call/component reference only ever
//! *suppresses* a finding, never raises a false one. It harvests idents in
//! call/construction position only, so DSL prop keys and locals don't pollute
//! the call set.
//! - [`tokens_reference_ident`] — *raw, presence*: SLM's yes/no "does this body
//! reference `self`" check.
use ;
/// Best-effort extraction of the expressions embedded in a macro token stream.
/// Most macros accept comma-separated exprs (`assert!(a, b)`, `format!("{}", x)`),
/// but block-like bodies (`tokio::select! { … }`) and separator-`;` variants
/// (`vec![x; n]`) don't. Three strategies in order:
/// 1. Comma-separated `syn::Expr` list (covers ~90% of macro calls).
/// 2. Brace-wrapped parse as a `syn::Block` — extracts every statement
/// expression, covering block-bodied and `;`-separated forms.
/// 3. Single `syn::Expr` — for macros whose argument is one expression.
///
/// Silent-skips (returns empty) on total parse failure (extern-DSL macros with
/// custom grammar) — recover their calls via [`idents_in_call_position`] where
/// the consumer can safely over-collect.
/// Operation: parser dispatch over fallback strategies, no own calls.
/// The expressions carried by a block statement (zero or more): a bare
/// expression; a `let`'s initialiser **plus** its `else { … }` diverging block
/// (let-else), so a call reachable only through the diverging branch is not
/// dropped; or a trailing-`;` macro statement (`format!(…);`) lifted back to an
/// `Expr::Macro` so nested macro calls inside a `;`-separated body survive.
/// `Stmt::Item` (e.g. a `const X = f();` nested in a macro body) is deliberately
/// dropped — an uncommon shape; its calls are recovered by the reachability
/// consumers' positional fallback. Operation: stmt-shape match, no own calls.
/// Identifiers in `tokens` that sit in *call or construction position*,
/// recursing into nested groups (order unspecified):
/// - an `Ident` followed by a `(…)` group — a call (`helper(…)`), any case;
/// - an **UpperCamelCase** `Ident` followed by a `{…}` group — a struct literal
/// or DSL component render (`Component { … }`).
///
/// The UpperCamelCase gate on brace-groups is what separates a component/struct
/// render from a lowercase DSL element tag (`div { … }`, `button { … }`): a
/// lowercase tag is never a Rust function/struct, so harvesting it could only
/// mask an unused `fn div()` — excluding it keeps the over-collection tight. A
/// real lowercase function is still captured via its `(…)` call form.
///
/// This is the precise raw fallback for DSL macro bodies (`rsx!{ Component {
/// prop: x } }`) that [`recover_exprs`] cannot parse: it captures the
/// component/function reference while EXCLUDING prop keys, field names, locals,
/// type-path segments, element tags, and string contents — so a prop named like
/// a production function (`Widget { helper: "x" }`) does NOT spuriously mark
/// `helper` as called. The consumer intersects the result against known
/// function/component names. Operation: positional token-tree walk, no own calls.
/// Whether `id`, given the token immediately after it, is in call/construction
/// position: a `(…)` group is a call (any case); a `{…}` group counts only when
/// `id` is UpperCamelCase (a component/struct, not a lowercase element tag).
/// Operation: delimiter + casing predicate, no own calls.
/// True if `name` appears as an identifier anywhere in `tokens` (recursing into
/// groups). Never fails — for yes/no presence checks like SLM's "does this macro
/// body reference `self`", where parsing the body as exprs is both unnecessary
/// and fragile (e.g. `matches!(self, Variant(_))` won't parse as an expr list).
/// A spurious hit only errs toward "reference present", the safe direction for a
/// self-less-method check. Note: idents inside string literals (capture
/// interpolation like `format!("{self}")`) are NOT seen — `proc_macro2` lexes a
/// string as one `Literal`, not idents. Operation: token-tree presence scan.