Skip to main content

solidity_language_server/
types.rs

1/// Type wrapper for AST node IDs.
2///
3/// Every node in the Solidity compiler's JSON AST has a unique numeric `id`.
4/// Wrapping it prevents accidental mixups with [`FileId`] or plain integers.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct NodeId(pub u64);
7
8/// Newtype wrapper for source file IDs.
9///
10/// The compiler assigns each input file a numeric ID that appears in `src`
11/// strings (`"offset:length:fileId"`). Wrapping it prevents accidental
12/// mixups with [`NodeId`] or plain integers.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub struct FileId(pub u64);
15
16/// A parsed `"offset:length:fileId"` source location from the AST.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct SourceLoc {
19    /// Byte offset in the source file.
20    pub offset: usize,
21    /// Byte length of the source range.
22    pub length: usize,
23    /// ID of the source file this location belongs to.
24    pub file_id: FileId,
25}
26
27impl SourceLoc {
28    /// Parse a `"offset:length:fileId"` string.
29    ///
30    /// Returns `None` if the format is invalid or any component fails to parse.
31    pub fn parse(src: &str) -> Option<Self> {
32        let mut parts = src.split(':');
33        let offset = parts.next()?.parse::<usize>().ok()?;
34        let length = parts.next()?.parse::<usize>().ok()?;
35        let file_id = parts.next()?.parse::<u64>().ok()?;
36        // Reject if there are extra parts
37        if parts.next().is_some() {
38            return None;
39        }
40        Some(Self {
41            offset,
42            length,
43            file_id: FileId(file_id),
44        })
45    }
46
47    /// End byte offset (`offset + length`).
48    pub fn end(&self) -> usize {
49        self.offset + self.length
50    }
51
52    /// The file ID as a string, for use as a HashMap key when interacting
53    /// with the `source_id_to_path` map (which uses string keys).
54    pub fn file_id_str(&self) -> String {
55        self.file_id.0.to_string()
56    }
57}
58
59impl std::fmt::Display for NodeId {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(f, "{}", self.0)
62    }
63}
64
65impl std::fmt::Display for FileId {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "{}", self.0)
68    }
69}
70
71// ── Selector types ─────────────────────────────────────────────────────────
72
73/// 4-byte function selector (`keccak256(signature)[0..4]`).
74///
75/// Used for external/public functions, public state variable getters,
76/// and custom errors. Stored as 8-char lowercase hex without `0x` prefix,
77/// matching the format solc uses in AST `functionSelector` / `errorSelector`
78/// fields and in `evm.methodIdentifiers` values.
79///
80/// # Examples
81/// ```ignore
82/// FuncSelector::new("f3cd914c")   // PoolManager.swap
83/// FuncSelector::new("8da5cb5b")   // Ownable.owner
84/// FuncSelector::new("0d89438e")   // DelegateCallNotAllowed error
85/// ```
86#[derive(Debug, Clone, PartialEq, Eq, Hash)]
87pub struct FuncSelector(String);
88
89impl FuncSelector {
90    /// Wrap a raw 8-char hex string (no `0x` prefix).
91    pub fn new(hex: impl Into<String>) -> Self {
92        Self(hex.into())
93    }
94
95    /// The raw hex string (no `0x` prefix), e.g. `"f3cd914c"`.
96    pub fn as_hex(&self) -> &str {
97        &self.0
98    }
99
100    /// Display with `0x` prefix, e.g. `"0xf3cd914c"`.
101    pub fn to_prefixed(&self) -> String {
102        format!("0x{}", self.0)
103    }
104}
105
106impl std::fmt::Display for FuncSelector {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(f, "{}", self.0)
109    }
110}
111
112/// 32-byte event topic (`keccak256(signature)`).
113///
114/// Used for events. Stored as 64-char lowercase hex without `0x` prefix,
115/// matching the format solc uses in the AST `eventSelector` field.
116///
117/// # Examples
118/// ```ignore
119/// EventSelector::new("8be0079c...") // OwnershipTransferred
120/// ```
121#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122pub struct EventSelector(String);
123
124impl EventSelector {
125    /// Wrap a raw 64-char hex string (no `0x` prefix).
126    pub fn new(hex: impl Into<String>) -> Self {
127        Self(hex.into())
128    }
129
130    /// The raw hex string (no `0x` prefix).
131    pub fn as_hex(&self) -> &str {
132        &self.0
133    }
134
135    /// Display with `0x` prefix.
136    pub fn to_prefixed(&self) -> String {
137        format!("0x{}", self.0)
138    }
139}
140
141impl std::fmt::Display for EventSelector {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(f, "{}", self.0)
144    }
145}
146
147/// A selector extracted from an AST declaration node.
148///
149/// Unifies [`FuncSelector`] (functions, errors, public variables) and
150/// [`EventSelector`] (events) into a single enum so callers can handle
151/// both with one match.
152#[derive(Debug, Clone, PartialEq, Eq, Hash)]
153pub enum Selector {
154    /// 4-byte selector for functions, public variables, and errors.
155    Func(FuncSelector),
156    /// 32-byte topic hash for events.
157    Event(EventSelector),
158}
159
160impl Selector {
161    /// The raw hex string (no `0x` prefix).
162    pub fn as_hex(&self) -> &str {
163        match self {
164            Selector::Func(s) => s.as_hex(),
165            Selector::Event(s) => s.as_hex(),
166        }
167    }
168
169    /// Display with `0x` prefix.
170    pub fn to_prefixed(&self) -> String {
171        match self {
172            Selector::Func(s) => s.to_prefixed(),
173            Selector::Event(s) => s.to_prefixed(),
174        }
175    }
176}
177
178impl std::fmt::Display for Selector {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        match self {
181            Selector::Func(s) => write!(f, "{s}"),
182            Selector::Event(s) => write!(f, "{s}"),
183        }
184    }
185}
186
187/// Canonical ABI method signature from `evm.methodIdentifiers`.
188///
189/// This is the full ABI-encoded signature string like
190/// `"swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes)"`.
191/// Unlike Solidity source signatures (which use struct names like `PoolKey`),
192/// these use fully-expanded tuple types. They are also the keys used in
193/// solc's `userdoc` and `devdoc` output.
194///
195/// Paired with a [`FuncSelector`] via `evm.methodIdentifiers`:
196/// `{ "swap(...)": "f3cd914c" }`.
197#[derive(Debug, Clone, PartialEq, Eq, Hash)]
198pub struct MethodId(String);
199
200impl MethodId {
201    /// Wrap a canonical ABI signature string.
202    pub fn new(sig: impl Into<String>) -> Self {
203        Self(sig.into())
204    }
205
206    /// The canonical signature, e.g. `"swap((address,...),bytes)"`.
207    pub fn as_str(&self) -> &str {
208        &self.0
209    }
210
211    /// The function/error name (text before the first `(`).
212    pub fn name(&self) -> &str {
213        self.0.split('(').next().unwrap_or(&self.0)
214    }
215}
216
217impl std::fmt::Display for MethodId {
218    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
219        write!(f, "{}", self.0)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn test_source_loc_parse_valid() {
229        let loc = SourceLoc::parse("100:50:3").unwrap();
230        assert_eq!(loc.offset, 100);
231        assert_eq!(loc.length, 50);
232        assert_eq!(loc.file_id, FileId(3));
233        assert_eq!(loc.end(), 150);
234        assert_eq!(loc.file_id_str(), "3");
235    }
236
237    #[test]
238    fn test_source_loc_parse_zero() {
239        let loc = SourceLoc::parse("0:0:0").unwrap();
240        assert_eq!(loc.offset, 0);
241        assert_eq!(loc.length, 0);
242        assert_eq!(loc.file_id, FileId(0));
243    }
244
245    #[test]
246    fn test_source_loc_parse_invalid_format() {
247        assert!(SourceLoc::parse("").is_none());
248        assert!(SourceLoc::parse("100").is_none());
249        assert!(SourceLoc::parse("100:50").is_none());
250        assert!(SourceLoc::parse("abc:50:3").is_none());
251        assert!(SourceLoc::parse("100:abc:3").is_none());
252        assert!(SourceLoc::parse("100:50:abc").is_none());
253    }
254
255    #[test]
256    fn test_source_loc_parse_rejects_extra_parts() {
257        assert!(SourceLoc::parse("100:50:3:extra").is_none());
258    }
259
260    #[test]
261    fn test_node_id_equality() {
262        assert_eq!(NodeId(42), NodeId(42));
263        assert_ne!(NodeId(42), NodeId(43));
264    }
265
266    #[test]
267    fn test_file_id_equality() {
268        assert_eq!(FileId(1), FileId(1));
269        assert_ne!(FileId(1), FileId(2));
270    }
271
272    #[test]
273    fn test_node_id_file_id_are_different_types() {
274        // This test documents the compile-time guarantee.
275        // NodeId(1) and FileId(1) are different types — they cannot be
276        // compared or used interchangeably.
277        let _n: NodeId = NodeId(1);
278        let _f: FileId = FileId(1);
279        // If you uncomment the following line, it won't compile:
280        // assert_ne!(_n, _f);
281    }
282
283    // ── Selector type tests ────────────────────────────────────────────
284
285    #[test]
286    fn test_func_selector_display() {
287        let sel = FuncSelector::new("f3cd914c");
288        assert_eq!(sel.as_hex(), "f3cd914c");
289        assert_eq!(sel.to_prefixed(), "0xf3cd914c");
290        assert_eq!(format!("{sel}"), "f3cd914c");
291    }
292
293    #[test]
294    fn test_func_selector_equality() {
295        assert_eq!(FuncSelector::new("f3cd914c"), FuncSelector::new("f3cd914c"));
296        assert_ne!(FuncSelector::new("f3cd914c"), FuncSelector::new("8da5cb5b"));
297    }
298
299    #[test]
300    fn test_event_selector_display() {
301        let sel =
302            EventSelector::new("8be0079c5114abcdef1234567890abcdef1234567890abcdef1234567890abcd");
303        assert_eq!(sel.as_hex().len(), 64);
304        assert!(sel.to_prefixed().starts_with("0x"));
305    }
306
307    #[test]
308    fn test_selector_enum_variants() {
309        let func = Selector::Func(FuncSelector::new("f3cd914c"));
310        let event = Selector::Event(EventSelector::new("a".repeat(64)));
311
312        assert_eq!(func.as_hex(), "f3cd914c");
313        assert_eq!(func.to_prefixed(), "0xf3cd914c");
314        assert_eq!(event.as_hex().len(), 64);
315    }
316
317    #[test]
318    fn test_method_id() {
319        let mid = MethodId::new(
320            "swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes)",
321        );
322        assert_eq!(mid.name(), "swap");
323        assert!(mid.as_str().starts_with("swap("));
324    }
325
326    #[test]
327    fn test_method_id_no_params() {
328        let mid = MethodId::new("settle()");
329        assert_eq!(mid.name(), "settle");
330    }
331
332    #[test]
333    fn test_func_selector_hashmap_key() {
334        use std::collections::HashMap;
335        let mut map = HashMap::new();
336        map.insert(FuncSelector::new("f3cd914c"), "swap");
337        map.insert(FuncSelector::new("8da5cb5b"), "owner");
338        assert_eq!(map.get(&FuncSelector::new("f3cd914c")), Some(&"swap"));
339        assert_eq!(map.get(&FuncSelector::new("8da5cb5b")), Some(&"owner"));
340    }
341}