pedant_core/ir/facts.rs
1use std::fmt;
2use std::rc::Rc;
3use std::sync::Arc;
4
5use super::sites::{DefinitionSite, ModuleDeclarationSite, ModuleScope, ReferenceSite};
6
7pub use super::dataflow::{DataFlowFact, DataFlowKind};
8
9/// Normalized item visibility, for `item-visibility-policy`.
10///
11/// [`fmt::Display`] renders the canonical Rust spelling (`private`, `pub`,
12/// `pub(crate)`, `pub(super)`, `pub(in <path>)`), which is how policies are
13/// written in configuration and compared.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum Visibility {
16 /// No modifier: private to the enclosing module.
17 Private,
18 /// `pub`.
19 Public,
20 /// `pub(crate)`.
21 Crate,
22 /// `pub(super)`.
23 Super,
24 /// `pub(in <path>)` (or `pub(self)`), carrying the restriction path.
25 Restricted(Box<str>),
26}
27
28impl fmt::Display for Visibility {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::Private => f.write_str("private"),
32 Self::Public => f.write_str("pub"),
33 Self::Crate => f.write_str("pub(crate)"),
34 Self::Super => f.write_str("pub(super)"),
35 Self::Restricted(path) => write!(f, "pub(in {path})"),
36 }
37 }
38}
39
40/// Source position extracted from `syn` spans.
41///
42/// Line is 1-based. Column is 0-based from syn, adjusted to 1-based at report time.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct IrSpan {
45 /// 1-based.
46 pub line: usize,
47 /// 0-based from syn; adjusted to 1-based at report time.
48 pub column: usize,
49}
50
51/// All facts extracted from a single source file's AST in one pass.
52#[derive(Debug)]
53pub struct FileIr {
54 /// Absolute path used for violation reporting.
55 pub file_path: Arc<str>,
56 /// Physical line count of the source file, for `large-source-file`.
57 /// `0` when the IR is built directly from a syntax tree without source.
58 pub source_line_count: usize,
59 /// Function and method definitions with body metadata.
60 pub functions: Box<[FnFact]>,
61 /// Struct, enum, and trait definitions with type-relationship edges.
62 pub type_defs: Box<[TypeDefFact]>,
63 /// Free type aliases with target and generic-argument relationships.
64 pub type_aliases: Box<[TypeAliasFact]>,
65 /// Inherent and trait impl blocks.
66 pub impl_blocks: Box<[ImplFact]>,
67 /// Flattened `use` paths for capability detection.
68 pub use_paths: Box<[UsePathFact]>,
69 /// Nesting-tracked control flow constructs.
70 pub control_flow: Box<[ControlFlowFact]>,
71 /// Let bindings with ownership and type metadata.
72 pub bindings: Box<[BindingFact]>,
73 /// Type references classified by position (return, param, field, body).
74 pub type_refs: Box<[TypeRefFact]>,
75 /// Method calls with receiver tracking for clone-in-loop analysis.
76 pub method_calls: Box<[MethodCallFact]>,
77 /// Macro invocations for forbidden-macro checks.
78 pub macro_invocations: Box<[MacroFact]>,
79 /// Item attributes for forbidden-attribute checks.
80 pub attributes: Box<[AttributeFact]>,
81 /// String literals for credential/endpoint detection.
82 pub string_literals: Box<[StringLitFact]>,
83 /// Unsafe blocks, functions, and impls.
84 pub unsafe_sites: Box<[UnsafeFact]>,
85 /// Extern block declarations for FFI detection.
86 pub extern_blocks: Box<[ExternBlockFact]>,
87 /// Module declarations for inline-test detection.
88 pub modules: Box<[ModuleFact]>,
89 /// Populated only by semantic enrichment; empty otherwise.
90 /// `Arc<[T]>` because semantic enrichment shares the cached analysis's
91 /// flow slice — no deep copy. Non-semantic paths use an empty Arc.
92 pub data_flows: std::sync::Arc<[DataFlowFact]>,
93 /// Lexical module scopes; index zero is the source itself.
94 pub module_scopes: Box<[ModuleScope]>,
95 /// Every `mod` item, which the module closure reads instead of walking the
96 /// syntax tree again.
97 pub module_declarations: Box<[ModuleDeclarationSite]>,
98 /// The authoritative definition sites a resolution report may name.
99 pub definition_sites: Box<[DefinitionSite]>,
100 /// The authoritative reference sites, one per source occurrence.
101 pub reference_sites: Box<[ReferenceSite]>,
102}
103
104/// Extracted metadata for a function or method definition.
105#[derive(Debug)]
106pub struct FnFact {
107 /// Identifier of the function.
108 pub name: Box<str>,
109 /// Location of the `fn` keyword.
110 pub span: IrSpan,
111 /// Marked `unsafe fn`.
112 pub is_unsafe: bool,
113 /// Declared parameters.
114 pub params: Box<[ParamFact]>,
115 /// Explicit return type, if present (excludes implicit `()`).
116 pub return_type: Option<TypeInfo>,
117 /// Unique type names from parameters and return type, for mixed-concerns edges.
118 pub signature_type_names: Box<[Rc<str>]>,
119 /// Nesting depth of the item in the module tree.
120 pub item_depth: usize,
121 /// Whether the body contains arithmetic operators.
122 pub has_arithmetic: bool,
123 /// Pairwise edges from body-referenced types (for mixed-concerns analysis).
124 pub body_type_edges: Box<[(Rc<str>, Rc<str>)]>,
125 /// Physical source lines spanned by the body block `{ … }`, inclusive of
126 /// both braces. `0` when the function has no body (e.g. a trait method
127 /// declaration without a default).
128 pub body_line_count: usize,
129 /// `true` for methods and associated functions (inside an `impl` or trait);
130 /// `false` for free `fn` items.
131 pub is_associated: bool,
132 /// `Some(self_type)` when this function is defined in an inherent `impl`
133 /// block (`impl Type`); `None` for free functions, trait methods, and
134 /// trait-impl methods (`impl Trait for Type`).
135 pub inherent_method_of: Option<Rc<str>>,
136 /// `true` when the body is exactly a single delegating call of the form
137 /// `self.<field>.<method>(<args>)`, allowing a trailing `?` and/or `.await`.
138 /// Such pure forwarders carry no responsibility of their own.
139 pub is_pure_forwarder: bool,
140 /// Declared visibility of the item (`private` for trait methods).
141 pub visibility: Visibility,
142 /// Feature names of the `#[cfg(feature = "…")]` gates enclosing this item
143 /// (its own and every ancestor module/impl), for `ungated-test-api`.
144 pub cfg_feature_gates: Box<[Rc<str>]>,
145 /// Rendered `#[cfg(…)]` predicates — of any kind, not just `feature` —
146 /// on this item and every ancestor module/impl within the file. Empty
147 /// means the method is in every build of the file.
148 pub cfg_predicates: Box<[Rc<str>]>,
149}
150
151/// Extracted metadata for a function parameter.
152#[derive(Debug)]
153pub struct ParamFact {
154 /// Identifier or `self`.
155 pub name: Box<str>,
156 /// Rendered type text for pattern matching.
157 pub type_text: Box<str>,
158}
159
160/// Rendered type text with dispatch classification.
161#[derive(Debug)]
162pub struct TypeInfo {
163 /// Normalized type text for pattern matching.
164 pub text: Box<str>,
165 /// Contains `dyn Trait` at any depth.
166 pub involves_dyn: bool,
167}
168
169/// Else-branch metadata attached to `If` control flow nodes.
170#[derive(Debug, Clone, Copy)]
171pub struct ElseInfo {
172 /// Total branches in the if/else-if chain, when chained.
173 pub chain_len: Option<usize>,
174 /// Location of the `else` keyword, for `forbid_else` reporting.
175 pub span: Option<IrSpan>,
176}
177
178/// A control flow construct with nesting context.
179#[derive(Debug)]
180pub struct ControlFlowFact {
181 /// Discriminant: if, match, loop variant, or closure.
182 pub kind: ControlFlowKind,
183 /// Location of the keyword.
184 pub span: IrSpan,
185 /// Nesting depth within the function body (for max-depth check).
186 pub depth: usize,
187 /// Enclosing loop count (for clone-in-loop suppression).
188 pub loop_depth: usize,
189 /// Set when nested inside an if or match arm.
190 pub parent_branch: Option<BranchContext>,
191 /// Present only for `If` nodes.
192 pub else_info: Option<ElseInfo>,
193 /// Index of the function containing this construct.
194 pub containing_fn: Option<usize>,
195}
196
197/// Discriminant for control flow constructs.
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199pub enum ControlFlowKind {
200 /// `if` expression.
201 If,
202 /// `match` expression.
203 Match,
204 /// `for .. in` loop.
205 ForLoop,
206 /// `while` loop.
207 WhileLoop,
208 /// Bare `loop` (infinite).
209 Loop,
210 /// Closure expression (counts as nesting).
211 Closure,
212}
213
214/// Which branch kind encloses a nested control flow node.
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub enum BranchContext {
217 /// Nested inside an `if` branch.
218 If,
219 /// Nested inside a `match` arm.
220 Match,
221}
222
223/// A `let` binding with ownership and context metadata.
224#[derive(Debug)]
225pub struct BindingFact {
226 /// Identifier (or `_` for wildcard).
227 pub name: Box<str>,
228 /// `None` for compiler-desugared bindings without source spans.
229 pub span: Option<IrSpan>,
230 /// Enclosing loop count for clone-in-loop analysis.
231 pub loop_depth: usize,
232 /// `true` when the declared type is `Rc<_>` or `Arc<_>`.
233 pub is_refcounted: bool,
234 /// `true` when the pattern is `_` (wildcard discard).
235 pub is_wildcard: bool,
236 /// `true` when an initializer expression is present.
237 pub has_init: bool,
238 /// `true` when the initializer is `write!`/`writeln!` into a `String` (infallible).
239 pub init_is_write_macro: bool,
240 /// Index into `FileIr::functions`; links binding to its enclosing function.
241 pub containing_fn: Option<usize>,
242 /// Present when the binding has an explicit `: Type` annotation.
243 pub type_annotation_span: Option<IrSpan>,
244 /// Filled by semantic enrichment; canonical type after alias resolution.
245 pub resolved_type: Option<Box<str>>,
246}
247
248/// A type reference with dispatch and hasher classification.
249#[derive(Debug)]
250pub struct TypeRefFact {
251 /// Normalized type text for pattern matching.
252 pub text: Box<str>,
253 /// Location of the type in source.
254 pub span: IrSpan,
255 /// Contains `dyn Trait` at any depth.
256 pub involves_dyn: bool,
257 /// Matches `Vec<Box<dyn ...>>` pattern.
258 pub is_vec_box_dyn: bool,
259 /// `HashMap`/`HashSet` without explicit hasher parameter.
260 pub is_default_hasher: bool,
261 /// Index into `FileIr::functions`; links to enclosing function.
262 pub containing_fn: Option<usize>,
263 /// Positional context: return, param, field, or body.
264 pub context: TypeRefContext,
265}
266
267/// Positional context of a type reference, determining which checks apply.
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum TypeRefContext {
270 /// In a function return type.
271 Return,
272 /// In a function parameter.
273 Param,
274 /// In a struct or enum field.
275 Field,
276 /// Inside a function body.
277 Body,
278}
279
280/// Struct, enum, or trait definition with type-relationship edges.
281#[derive(Debug)]
282pub struct TypeDefFact {
283 /// Identifier of the defined type.
284 pub name: Rc<str>,
285 /// Location of the definition keyword.
286 pub span: IrSpan,
287 /// Struct, enum, or trait.
288 pub kind: TypeDefKind,
289 /// Declared visibility of the type.
290 pub visibility: Visibility,
291 /// Feature names of the `#[cfg(feature = "…")]` gates enclosing this type.
292 pub cfg_feature_gates: Box<[Rc<str>]>,
293 /// Pairwise type-relationship edges for mixed-concerns graph analysis.
294 pub edges: Box<[(Rc<str>, Rc<str>)]>,
295}
296
297/// A free type alias and the relationships its target states.
298#[derive(Debug)]
299pub struct TypeAliasFact {
300 /// Identifier of the declared alias.
301 pub name: Rc<str>,
302 /// Edges from the alias to its target and generic argument types.
303 pub edges: Box<[(Rc<str>, Rc<str>)]>,
304}
305
306/// Discriminant for type definitions.
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308pub enum TypeDefKind {
309 /// `struct` definition.
310 Struct,
311 /// `enum` definition.
312 Enum,
313 /// `trait` definition.
314 Trait,
315 /// `union` definition.
316 Union,
317}
318
319/// An inherent or trait impl block with type-relationship edges.
320#[derive(Debug)]
321pub struct ImplFact {
322 /// The type being implemented on.
323 pub self_type: Rc<str>,
324 /// `Some` for `impl Trait for Type`, `None` for inherent impls.
325 pub trait_name: Option<Box<str>>,
326 /// Location of the `impl` keyword.
327 pub span: IrSpan,
328 /// Rendered `#[cfg(…)]` predicates on this block and every ancestor module
329 /// within the file. Empty means the block is in every build of the file.
330 pub cfg_predicates: Box<[Rc<str>]>,
331 /// Pairwise type-relationship edges for mixed-concerns graph analysis.
332 pub edges: Box<[(Rc<str>, Rc<str>)]>,
333}
334
335/// A flattened `use` import path for capability detection.
336#[derive(Debug)]
337pub struct UsePathFact {
338 /// Fully qualified path (e.g., `std::collections::HashMap`).
339 pub path: Box<str>,
340 /// Location of the `use` statement.
341 pub span: IrSpan,
342}
343
344/// A method call expression with receiver and loop context.
345#[derive(Debug)]
346pub struct MethodCallFact {
347 /// Method identifier (e.g., `clone`, `unwrap`).
348 pub method_name: Box<str>,
349 /// Full rendered expression for pattern matching.
350 pub text: Box<str>,
351 /// Location of the method call.
352 pub span: IrSpan,
353 /// Simple identifier receiver, when not a complex expression.
354 pub receiver_ident: Option<Box<str>>,
355 /// Location of the receiver for diagnostic pointing.
356 pub receiver_span: IrSpan,
357 /// Enclosing loop count for clone-in-loop analysis.
358 pub loop_depth: usize,
359 /// Index into `FileIr::functions`; links to enclosing function.
360 pub containing_fn: Option<usize>,
361 /// Filled by semantic enrichment; canonical receiver type.
362 /// `Arc<str>` so multiple calls on the same binding share one allocation.
363 pub receiver_type: Option<Arc<str>>,
364 /// Filled by semantic enrichment; suppresses clone-in-loop for `Copy` types.
365 pub is_copy_receiver: bool,
366}
367
368/// A macro invocation for forbidden-macro checks.
369#[derive(Debug)]
370pub struct MacroFact {
371 /// Rendered macro text (e.g., `println!`) for pattern matching.
372 pub text: Box<str>,
373 /// Location of the macro call.
374 pub span: IrSpan,
375}
376
377/// An item attribute for forbidden-attribute and capability checks.
378#[derive(Debug)]
379pub struct AttributeFact {
380 /// Rendered inner text (e.g., `allow(dead_code)`) for pattern matching.
381 pub text: Box<str>,
382 /// Location of the `#[` token.
383 pub span: IrSpan,
384 /// Top-level attribute name (e.g., `derive`, `cfg`, `link`).
385 pub name: Box<str>,
386}
387
388/// A string literal for credential and endpoint detection.
389#[derive(Debug)]
390pub struct StringLitFact {
391 /// Unescaped content of the literal.
392 pub value: Box<str>,
393 /// Location of the opening quote.
394 pub span: IrSpan,
395}
396
397/// An unsafe block, function, or impl for safety auditing.
398#[derive(Debug)]
399pub struct UnsafeFact {
400 /// Block, function, or impl.
401 pub kind: UnsafeKind,
402 /// Location of the `unsafe` keyword.
403 pub span: IrSpan,
404 /// Snippet of the unsafe code for evidence reporting.
405 pub evidence: Box<str>,
406}
407
408/// Discriminant for unsafe constructs.
409#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410pub enum UnsafeKind {
411 /// `unsafe { }` block.
412 Block,
413 /// `unsafe fn` declaration.
414 Fn,
415 /// `unsafe impl` block.
416 Impl,
417}
418
419/// An `extern` block declaration for FFI capability detection.
420#[derive(Debug)]
421pub struct ExternBlockFact {
422 /// Location of the `extern` keyword.
423 pub span: IrSpan,
424}
425
426/// Structural fingerprint for a function, used for duplicate detection.
427///
428/// Two functions with identical structure (same control flow, same method call
429/// count, same binding count) but different names produce the same `skeleton_hash`.
430/// `exact_hash` additionally includes method and type reference names.
431#[derive(Debug)]
432pub struct FnFingerprint {
433 /// Index into `FileIr::functions`.
434 pub fn_index: usize,
435 /// Function name.
436 pub name: Box<str>,
437 /// Location of the function definition.
438 pub span: IrSpan,
439 /// Hash of structural shape only (param count, control flow sequence, counts).
440 pub skeleton_hash: u64,
441 /// Hash of skeleton components plus method names and type reference texts.
442 pub exact_hash: u64,
443 /// Total number of facts (method calls + bindings + type refs + control flow).
444 pub fact_count: usize,
445}
446
447/// A `mod` declaration for inline-test detection.
448#[derive(Debug)]
449pub struct ModuleFact {
450 /// Module identifier.
451 pub name: Box<str>,
452 /// Location of the `mod` keyword.
453 pub span: IrSpan,
454 /// `true` when annotated with `#[cfg(test)]`.
455 pub is_cfg_test: bool,
456 /// Rendered `#[cfg(…)]` predicates guarding this declaration. For a file
457 /// module (`mod x;`) they guard the whole of `x.rs` / `x/`, which is the
458 /// only way that gate is visible from inside those files.
459 pub cfg_predicates: Box<[Rc<str>]>,
460}