Skip to main content

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}