Skip to main content

saf_core/
span.rs

1//! Source span and symbol types for optional metadata.
2//!
3//! These types support source-level debugging and error reporting.
4//! They are optional in AIR — frontends may or may not provide them.
5
6use serde::{Deserialize, Serialize};
7
8use crate::ids::FileId;
9
10/// Source location span within a file.
11///
12/// Byte offsets are absolute within the file; line/column are 1-based.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct Span {
15    /// Unique identifier for the source file.
16    pub file_id: FileId,
17
18    /// Byte offset of span start (0-based).
19    pub byte_start: u32,
20
21    /// Byte offset of span end (exclusive, 0-based).
22    pub byte_end: u32,
23
24    /// Line number of span start (1-based).
25    pub line_start: u32,
26
27    /// Column number of span start (1-based).
28    pub col_start: u32,
29
30    /// Line number of span end (1-based).
31    pub line_end: u32,
32
33    /// Column number of span end (1-based).
34    pub col_end: u32,
35}
36
37impl Span {
38    /// Create a new span.
39    #[must_use]
40    pub const fn new(
41        file_id: FileId,
42        byte_start: u32,
43        byte_end: u32,
44        line_start: u32,
45        col_start: u32,
46        line_end: u32,
47        col_end: u32,
48    ) -> Self {
49        Self {
50            file_id,
51            byte_start,
52            byte_end,
53            line_start,
54            col_start,
55            line_end,
56            col_end,
57        }
58    }
59
60    /// Create a point span (single character).
61    #[must_use]
62    pub const fn point(file_id: FileId, byte_offset: u32, line: u32, col: u32) -> Self {
63        Self {
64            file_id,
65            byte_start: byte_offset,
66            byte_end: byte_offset + 1,
67            line_start: line,
68            col_start: col,
69            line_end: line,
70            col_end: col + 1,
71        }
72    }
73}
74
75/// Symbol information for named entities.
76///
77/// Provides human-readable names and optional mangled/qualified names
78/// for debugging and demangling support.
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct Symbol {
81    /// Human-readable display name (e.g., `main`, `MyClass::method`).
82    pub display_name: String,
83
84    /// Mangled/linkage name if different from display name.
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub mangled_name: Option<String>,
87
88    /// Namespace path (e.g., `["std", "collections", "HashMap"]`).
89    #[serde(default, skip_serializing_if = "Vec::is_empty")]
90    pub namespace_path: Vec<String>,
91}
92
93impl Symbol {
94    /// Create a symbol with just a display name.
95    #[must_use]
96    pub fn simple(name: impl Into<String>) -> Self {
97        Self {
98            display_name: name.into(),
99            mangled_name: None,
100            namespace_path: Vec::new(),
101        }
102    }
103
104    /// Create a symbol with display and mangled names.
105    #[must_use]
106    pub fn with_mangled(display: impl Into<String>, mangled: impl Into<String>) -> Self {
107        Self {
108            display_name: display.into(),
109            mangled_name: Some(mangled.into()),
110            namespace_path: Vec::new(),
111        }
112    }
113
114    /// Create a fully qualified symbol.
115    #[must_use]
116    pub fn qualified(
117        display: impl Into<String>,
118        mangled: Option<String>,
119        namespace: Vec<String>,
120    ) -> Self {
121        Self {
122            display_name: display.into(),
123            mangled_name: mangled,
124            namespace_path: namespace,
125        }
126    }
127}
128
129/// Source file metadata.
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct SourceFile {
132    /// Unique identifier for this file.
133    pub id: FileId,
134
135    /// File path (may be absolute or relative).
136    pub path: String,
137
138    /// Optional checksum of file contents for verification.
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub checksum: Option<String>,
141}
142
143impl SourceFile {
144    /// Create a source file entry.
145    #[must_use]
146    pub fn new(id: FileId, path: impl Into<String>) -> Self {
147        Self {
148            id,
149            path: path.into(),
150            checksum: None,
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn span_serialization_roundtrip() {
161        let span = Span::new(FileId::new(42), 100, 150, 10, 5, 12, 20);
162        let json = serde_json::to_string(&span).expect("serialize");
163        let parsed: Span = serde_json::from_str(&json).expect("deserialize");
164        assert_eq!(span, parsed);
165    }
166
167    #[test]
168    fn symbol_simple() {
169        let sym = Symbol::simple("main");
170        assert_eq!(sym.display_name, "main");
171        assert!(sym.mangled_name.is_none());
172        assert!(sym.namespace_path.is_empty());
173    }
174
175    #[test]
176    fn symbol_with_mangled_serialization() {
177        let sym = Symbol::with_mangled("MyClass::method", "_ZN7MyClass6methodEv");
178        let json = serde_json::to_string(&sym).expect("serialize");
179        assert!(json.contains("_ZN7MyClass6methodEv"));
180        let parsed: Symbol = serde_json::from_str(&json).expect("deserialize");
181        assert_eq!(sym, parsed);
182    }
183
184    #[test]
185    fn symbol_namespace_path() {
186        let sym = Symbol::qualified(
187            "HashMap",
188            None,
189            vec!["std".to_string(), "collections".to_string()],
190        );
191        assert_eq!(sym.namespace_path.len(), 2);
192    }
193}