Skip to main content

sealmap_model/
symbol.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::flow::Flow;
6use crate::path::SourcePath;
7
8/// The canonical, fully qualified identity of a symbol, e.g.
9/// `my_crate::net::Client` or `my_crate::net::Client::connect`.
10///
11/// Ids use `::` as the separator for every language so projections can treat
12/// them uniformly. They are the join key between the model, the rendered
13/// diagrams and the JSON metadata, so an agent can follow an id from a node in
14/// a diagram straight back to its definition.
15///
16/// Symbols that live outside the analysed codebase (standard library, third
17/// party crates, or anything the frontend could not resolve) still get an id;
18/// they are simply absent from [`Codebase::symbols`](crate::Codebase::symbols)
19/// and relations pointing at them carry [`Confidence::External`].
20#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
21#[serde(transparent)]
22pub struct SymbolId(String);
23
24impl SymbolId {
25    /// Wrap a fully qualified path.
26    pub fn new(path: impl Into<String>) -> Self {
27        Self(path.into())
28    }
29
30    /// The id as a string.
31    pub fn as_str(&self) -> &str {
32        &self.0
33    }
34
35    /// The last `::` segment (the short name).
36    ///
37    /// ```
38    /// # use sealmap_model::SymbolId;
39    /// assert_eq!(SymbolId::new("a::b::C").name(), "C");
40    /// ```
41    pub fn name(&self) -> &str {
42        self.0.rsplit("::").next().unwrap_or(&self.0)
43    }
44
45    /// The id with the last segment removed, if there is more than one.
46    ///
47    /// ```
48    /// # use sealmap_model::SymbolId;
49    /// assert_eq!(SymbolId::new("a::b::C").parent().unwrap().as_str(), "a::b");
50    /// assert!(SymbolId::new("a").parent().is_none());
51    /// ```
52    pub fn parent(&self) -> Option<SymbolId> {
53        self.0.rfind("::").map(|i| SymbolId(self.0[..i].to_owned()))
54    }
55
56    /// Append a segment: `a::b` + `c` → `a::b::c`.
57    pub fn child(&self, segment: &str) -> SymbolId {
58        SymbolId(format!("{}::{}", self.0, segment))
59    }
60}
61
62impl fmt::Display for SymbolId {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.write_str(&self.0)
65    }
66}
67
68/// What kind of thing a [`Symbol`] is.
69///
70/// The set is a superset of what common languages need; frontends map their
71/// own constructs onto the closest variant (a TypeScript `interface` would be
72/// a [`SymbolKind::Trait`], a Python `class` a [`SymbolKind::Struct`]).
73#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
74#[serde(rename_all = "snake_case")]
75pub enum SymbolKind {
76    /// A module / namespace. Every source file has one.
77    Module,
78    /// A product type with named or positional fields.
79    Struct,
80    /// A sum type.
81    Enum,
82    /// An untagged union.
83    Union,
84    /// An interface / trait / protocol.
85    Trait,
86    /// A type alias.
87    TypeAlias,
88    /// A free function.
89    Function,
90    /// A function attached to a type (inherent or trait method, including
91    /// associated functions without `self`).
92    Method,
93    /// A constant.
94    Const,
95    /// A static / global variable.
96    Static,
97    /// A macro definition.
98    Macro,
99}
100
101impl SymbolKind {
102    /// `true` for kinds that are rendered as classes in a structure diagram.
103    pub fn is_type(self) -> bool {
104        matches!(self, Self::Struct | Self::Enum | Self::Union | Self::Trait | Self::TypeAlias)
105    }
106
107    /// `true` for kinds that have a body and therefore may carry a [`Flow`].
108    pub fn is_callable(self) -> bool {
109        matches!(self, Self::Function | Self::Method)
110    }
111
112    /// Lower-case keyword used in diagrams and metadata (`struct`, `fn`, ...).
113    pub fn keyword(self) -> &'static str {
114        match self {
115            Self::Module => "mod",
116            Self::Struct => "struct",
117            Self::Enum => "enum",
118            Self::Union => "union",
119            Self::Trait => "trait",
120            Self::TypeAlias => "type",
121            Self::Function => "fn",
122            Self::Method => "method",
123            Self::Const => "const",
124            Self::Static => "static",
125            Self::Macro => "macro",
126        }
127    }
128}
129
130/// Visibility of a symbol or member.
131#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
132#[serde(rename_all = "snake_case")]
133pub enum Visibility {
134    /// Visible to everyone (`pub`).
135    Public,
136    /// Visible within the package / crate (`pub(crate)`).
137    Crate,
138    /// Visible within a named scope (`pub(in path)`, `pub(super)`).
139    Restricted(String),
140    /// Private to the defining scope.
141    Private,
142}
143
144impl Visibility {
145    /// UML-style marker used by Mermaid class diagrams: `+`, `~`, `#` or `-`.
146    pub fn uml_marker(&self) -> char {
147        match self {
148            Self::Public => '+',
149            Self::Crate => '~',
150            Self::Restricted(_) => '#',
151            Self::Private => '-',
152        }
153    }
154}
155
156/// A source range. Lines and columns are **1-based**; `end` is inclusive of
157/// the last line containing the item.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
159pub struct Span {
160    /// First line (1-based).
161    pub start_line: u32,
162    /// Column on the first line (1-based).
163    pub start_col: u32,
164    /// Last line (1-based, inclusive).
165    pub end_line: u32,
166    /// Column on the last line (1-based).
167    pub end_col: u32,
168}
169
170impl Span {
171    /// Construct a span from line/column pairs.
172    pub fn new(start_line: u32, start_col: u32, end_line: u32, end_col: u32) -> Self {
173        Self { start_line, start_col, end_line, end_col }
174    }
175
176    /// `L<start>-L<end>` (or `L<start>` for single-line spans), the compact
177    /// form used in diagram metadata.
178    ///
179    /// ```
180    /// # use sealmap_model::Span;
181    /// assert_eq!(Span::new(3, 1, 9, 2).compact(), "L3-L9");
182    /// assert_eq!(Span::new(4, 1, 4, 9).compact(), "L4");
183    /// ```
184    pub fn compact(&self) -> String {
185        if self.start_line == self.end_line {
186            format!("L{}", self.start_line)
187        } else {
188            format!("L{}-L{}", self.start_line, self.end_line)
189        }
190    }
191}
192
193/// What a [`Member`] is.
194#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
195#[serde(rename_all = "snake_case")]
196pub enum MemberKind {
197    /// A named or positional field.
198    Field,
199    /// An enum variant.
200    Variant,
201    /// An associated constant.
202    AssocConst,
203    /// An associated type.
204    AssocType,
205    /// A required (bodiless) trait method signature.
206    RequiredMethod,
207}
208
209/// A field, variant or associated item listed *inside* a type in structure
210/// diagrams. Methods with bodies are full [`Symbol`]s instead, because they
211/// carry their own [`Flow`] and relations.
212#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
213pub struct Member {
214    /// Member name (`0`, `1`, ... for positional fields).
215    pub name: String,
216    /// What the member is.
217    pub kind: MemberKind,
218    /// Type or signature as written, normalised to single spaces
219    /// (e.g. `Vec<String>`, `(u32, u32)`, `fn(&self) -> u8`).
220    pub ty: Option<String>,
221    /// Member visibility.
222    pub visibility: Visibility,
223    /// Where it is defined.
224    pub span: Span,
225    /// Codebase-relevant types this member's type mentions, resolved to
226    /// canonical ids (primitives and std prelude types are omitted).
227    #[serde(default, skip_serializing_if = "Vec::is_empty")]
228    pub refs: Vec<SymbolId>,
229}
230
231/// A named definition in the codebase.
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub struct Symbol {
234    /// Canonical id.
235    pub id: SymbolId,
236    /// Short name.
237    pub name: String,
238    /// Kind of definition.
239    pub kind: SymbolKind,
240    /// Visibility.
241    pub visibility: Visibility,
242    /// File the definition lives in.
243    pub file: SourcePath,
244    /// Where in the file.
245    pub span: Span,
246    /// Enclosing symbol (the type for a method, the module for an item).
247    pub parent: Option<SymbolId>,
248    /// One-line signature, e.g. `pub fn connect(&self, addr: &str) -> Result<Conn>`.
249    pub signature: Option<String>,
250    /// First sentence of the doc comment, if any. Kept short on purpose: it is
251    /// context for an agent, not a copy of the docs.
252    pub doc: Option<String>,
253    /// Generic parameters as written (`T: Clone`, `'a`, `const N: usize`).
254    pub generics: Vec<String>,
255    /// Notable attributes / modifiers: derives (`derive(Debug)`), `async`,
256    /// `unsafe`, `const`, `test`, `cfg(...)`, trait name for trait impls, etc.
257    pub tags: Vec<String>,
258    /// Fields, variants and associated items.
259    pub members: Vec<Member>,
260    /// Ordered control/call flow of the body, for callables.
261    pub flow: Option<Flow>,
262}
263
264impl Symbol {
265    /// A symbol with only the required fields set; everything else empty /
266    /// private. Frontends fill in the rest.
267    pub fn new(id: SymbolId, name: impl Into<String>, kind: SymbolKind, file: SourcePath) -> Self {
268        Self {
269            parent: id.parent(),
270            id,
271            name: name.into(),
272            kind,
273            visibility: Visibility::Private,
274            file,
275            span: Span::default(),
276            signature: None,
277            doc: None,
278            generics: Vec::new(),
279            tags: Vec::new(),
280            members: Vec::new(),
281            flow: None,
282        }
283    }
284}
285
286/// The kind of a directed edge between two symbols.
287#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
288#[serde(rename_all = "snake_case")]
289pub enum RelationKind {
290    /// `from` lexically contains `to` (module → item, type → method).
291    Contains,
292    /// `from` implements trait `to`.
293    Implements,
294    /// Trait `from` has supertrait `to`.
295    Extends,
296    /// `from` has a field whose type mentions `to` (composition / aggregation).
297    FieldType,
298    /// `from`'s signature mentions `to` (parameters, return, bounds).
299    Uses,
300    /// Module `from` imports `to`.
301    Imports,
302    /// Callable `from` calls `to` (aggregated from its [`Flow`]).
303    Calls,
304}
305
306impl RelationKind {
307    /// Lower-case label used in diagrams and metadata.
308    pub fn label(self) -> &'static str {
309        match self {
310            Self::Contains => "contains",
311            Self::Implements => "implements",
312            Self::Extends => "extends",
313            Self::FieldType => "field",
314            Self::Uses => "uses",
315            Self::Imports => "imports",
316            Self::Calls => "calls",
317        }
318    }
319}
320
321/// How sure the frontend is that a relation's target is correct.
322///
323/// Static analysis without type inference cannot resolve everything (method
324/// calls on values of unknown type are the classic case). Rather than drop
325/// such edges or present them as fact, sealmap labels them, so an agent can
326/// weigh them accordingly.
327#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
328#[serde(rename_all = "snake_case")]
329pub enum Confidence {
330    /// Resolved through explicit paths, imports or declared types to a symbol
331    /// defined in the codebase.
332    Exact,
333    /// Matched by name or local inference; probably right, not proven.
334    Inferred,
335    /// Points outside the codebase (std, dependencies) or was not resolved.
336    External,
337}
338
339impl Confidence {
340    /// Single-character marker used in compact metadata (`=`, `~`, `?`).
341    pub fn marker(self) -> char {
342        match self {
343            Self::Exact => '=',
344            Self::Inferred => '~',
345            Self::External => '?',
346        }
347    }
348}
349
350/// A directed, typed edge between two symbols.
351///
352/// Ordering is derived field by field, which is what makes relation sets in a
353/// [`Codebase`](crate::Codebase) iterate deterministically.
354#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
355pub struct Relation {
356    /// Source symbol.
357    pub from: SymbolId,
358    /// Target symbol (may be external).
359    pub to: SymbolId,
360    /// Edge kind.
361    pub kind: RelationKind,
362    /// Resolution confidence.
363    pub confidence: Confidence,
364}
365
366impl Relation {
367    /// Construct a relation.
368    pub fn new(from: SymbolId, to: SymbolId, kind: RelationKind, confidence: Confidence) -> Self {
369        Self { from, to, kind, confidence }
370    }
371}