Skip to main content

solidity_language_server/
types.rs

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