use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct NodeId(pub i64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct FileId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ErrorCode(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceLoc {
pub offset: usize,
pub length: usize,
pub file_id: FileId,
}
impl SourceLoc {
pub fn parse(src: &str) -> Option<Self> {
let mut parts = src.split(':');
let offset = parts.next()?.parse::<usize>().ok()?;
let length = parts.next()?.parse::<usize>().ok()?;
let file_id = parts.next()?.parse::<u64>().ok()?;
if parts.next().is_some() {
return None;
}
Some(Self {
offset,
length,
file_id: FileId(file_id),
})
}
pub fn end(&self) -> usize {
self.offset + self.length
}
pub fn file_id_str(&self) -> SolcFileId {
SolcFileId::new(self.file_id.0.to_string())
}
}
impl std::fmt::Display for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::Display for FileId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<u32> for ErrorCode {
fn from(value: u32) -> Self {
Self(value)
}
}
impl std::borrow::Borrow<u32> for ErrorCode {
fn borrow(&self) -> &u32 {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct AbsPath(String);
impl AbsPath {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl std::fmt::Display for AbsPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for AbsPath {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for AbsPath {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<std::path::Path> for AbsPath {
fn as_ref(&self) -> &std::path::Path {
std::path::Path::new(&self.0)
}
}
impl From<String> for AbsPath {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for AbsPath {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::borrow::Borrow<str> for AbsPath {
fn borrow(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RelPath(String);
impl RelPath {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl std::fmt::Display for RelPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for RelPath {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for RelPath {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<String> for RelPath {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for RelPath {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::borrow::Borrow<str> for RelPath {
fn borrow(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SymbolName(String);
impl SymbolName {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for SymbolName {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::fmt::Display for SymbolName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for SymbolName {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for SymbolName {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::borrow::Borrow<str> for SymbolName {
fn borrow(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<String> for SymbolName {
fn borrow(&self) -> &String {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct TypeIdentifier(String);
impl TypeIdentifier {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for TypeIdentifier {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::fmt::Display for TypeIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for TypeIdentifier {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for TypeIdentifier {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::borrow::Borrow<str> for TypeIdentifier {
fn borrow(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<String> for TypeIdentifier {
fn borrow(&self) -> &String {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SolcFileId(String);
impl SolcFileId {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl std::fmt::Display for SolcFileId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for SolcFileId {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for SolcFileId {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<String> for SolcFileId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for SolcFileId {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::borrow::Borrow<str> for SolcFileId {
fn borrow(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SrcLocation(String);
impl SrcLocation {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
pub fn parse(&self) -> Option<SourceLoc> {
SourceLoc::parse(&self.0)
}
}
impl std::fmt::Display for SrcLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for SrcLocation {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for SrcLocation {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq<&str> for SrcLocation {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl PartialEq<SrcLocation> for &str {
fn eq(&self, other: &SrcLocation) -> bool {
*self == other.0
}
}
impl From<String> for SrcLocation {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for SrcLocation {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl std::borrow::Borrow<str> for SrcLocation {
fn borrow(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct DocumentUri(String);
impl DocumentUri {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl std::fmt::Display for DocumentUri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for DocumentUri {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::ops::Deref for DocumentUri {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<String> for DocumentUri {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for DocumentUri {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
impl From<&String> for DocumentUri {
fn from(s: &String) -> Self {
Self(s.clone())
}
}
impl std::borrow::Borrow<str> for DocumentUri {
fn borrow(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<String> for DocumentUri {
fn borrow(&self) -> &String {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FuncSelector(String);
impl FuncSelector {
pub fn new(hex: impl Into<String>) -> Self {
Self(hex.into())
}
pub fn as_hex(&self) -> &str {
&self.0
}
pub fn to_prefixed(&self) -> String {
format!("0x{}", self.0)
}
}
impl std::fmt::Display for FuncSelector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct EventSelector(String);
impl EventSelector {
pub fn new(hex: impl Into<String>) -> Self {
Self(hex.into())
}
pub fn as_hex(&self) -> &str {
&self.0
}
pub fn to_prefixed(&self) -> String {
format!("0x{}", self.0)
}
}
impl std::fmt::Display for EventSelector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Selector {
Func(FuncSelector),
Event(EventSelector),
}
impl Selector {
pub fn as_hex(&self) -> &str {
match self {
Selector::Func(s) => s.as_hex(),
Selector::Event(s) => s.as_hex(),
}
}
pub fn to_prefixed(&self) -> String {
match self {
Selector::Func(s) => s.to_prefixed(),
Selector::Event(s) => s.to_prefixed(),
}
}
}
impl std::fmt::Display for Selector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Selector::Func(s) => write!(f, "{s}"),
Selector::Event(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MethodId(String);
impl MethodId {
pub fn new(sig: impl Into<String>) -> Self {
Self(sig.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn name(&self) -> &str {
self.0.split('(').next().unwrap_or(&self.0)
}
}
impl std::fmt::Display for MethodId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone)]
pub struct PathInterner {
paths: Vec<String>,
path_to_id: HashMap<String, u64>,
}
impl PathInterner {
pub fn new() -> Self {
Self {
paths: Vec::new(),
path_to_id: HashMap::new(),
}
}
pub fn intern(&mut self, path: &str) -> FileId {
if let Some(&id) = self.path_to_id.get(path) {
return FileId(id);
}
let id = self.paths.len() as u64;
self.paths.push(path.to_owned());
self.path_to_id.insert(path.to_owned(), id);
FileId(id)
}
pub fn resolve(&self, id: FileId) -> Option<&str> {
self.paths.get(id.0 as usize).map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
pub fn build_remap(
&mut self,
solc_id_to_path: &HashMap<SolcFileId, String>,
) -> HashMap<u64, FileId> {
let mut remap = HashMap::with_capacity(solc_id_to_path.len());
for (solc_id, path) in solc_id_to_path {
let solc_num: u64 = solc_id.as_str().parse().unwrap_or(u64::MAX);
let canonical = self.intern(path);
remap.insert(solc_num, canonical);
}
remap
}
pub fn to_id_to_path_map(&self) -> HashMap<SolcFileId, String> {
self.paths
.iter()
.enumerate()
.map(|(i, path)| (SolcFileId::new(i.to_string()), path.clone()))
.collect()
}
}
impl Default for PathInterner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_source_loc_parse_valid() {
let loc = SourceLoc::parse("100:50:3").unwrap();
assert_eq!(loc.offset, 100);
assert_eq!(loc.length, 50);
assert_eq!(loc.file_id, FileId(3));
assert_eq!(loc.end(), 150);
assert_eq!(loc.file_id_str(), SolcFileId::new("3"));
}
#[test]
fn test_source_loc_parse_zero() {
let loc = SourceLoc::parse("0:0:0").unwrap();
assert_eq!(loc.offset, 0);
assert_eq!(loc.length, 0);
assert_eq!(loc.file_id, FileId(0));
}
#[test]
fn test_source_loc_parse_invalid_format() {
assert!(SourceLoc::parse("").is_none());
assert!(SourceLoc::parse("100").is_none());
assert!(SourceLoc::parse("100:50").is_none());
assert!(SourceLoc::parse("abc:50:3").is_none());
assert!(SourceLoc::parse("100:abc:3").is_none());
assert!(SourceLoc::parse("100:50:abc").is_none());
}
#[test]
fn test_source_loc_parse_rejects_extra_parts() {
assert!(SourceLoc::parse("100:50:3:extra").is_none());
}
#[test]
fn test_node_id_equality() {
assert_eq!(NodeId(42), NodeId(42));
assert_ne!(NodeId(42), NodeId(43));
}
#[test]
fn test_file_id_equality() {
assert_eq!(FileId(1), FileId(1));
assert_ne!(FileId(1), FileId(2));
}
#[test]
fn test_node_id_file_id_are_different_types() {
let _n: NodeId = NodeId(1);
let _f: FileId = FileId(1);
}
#[test]
fn test_func_selector_display() {
let sel = FuncSelector::new("f3cd914c");
assert_eq!(sel.as_hex(), "f3cd914c");
assert_eq!(sel.to_prefixed(), "0xf3cd914c");
assert_eq!(format!("{sel}"), "f3cd914c");
}
#[test]
fn test_func_selector_equality() {
assert_eq!(FuncSelector::new("f3cd914c"), FuncSelector::new("f3cd914c"));
assert_ne!(FuncSelector::new("f3cd914c"), FuncSelector::new("8da5cb5b"));
}
#[test]
fn test_event_selector_display() {
let sel =
EventSelector::new("8be0079c5114abcdef1234567890abcdef1234567890abcdef1234567890abcd");
assert_eq!(sel.as_hex().len(), 64);
assert!(sel.to_prefixed().starts_with("0x"));
}
#[test]
fn test_selector_enum_variants() {
let func = Selector::Func(FuncSelector::new("f3cd914c"));
let event = Selector::Event(EventSelector::new("a".repeat(64)));
assert_eq!(func.as_hex(), "f3cd914c");
assert_eq!(func.to_prefixed(), "0xf3cd914c");
assert_eq!(event.as_hex().len(), 64);
}
#[test]
fn test_method_id() {
let mid = MethodId::new(
"swap((address,address,uint24,int24,address),(bool,int256,uint160),bytes)",
);
assert_eq!(mid.name(), "swap");
assert!(mid.as_str().starts_with("swap("));
}
#[test]
fn test_method_id_no_params() {
let mid = MethodId::new("settle()");
assert_eq!(mid.name(), "settle");
}
#[test]
fn test_func_selector_hashmap_key() {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(FuncSelector::new("f3cd914c"), "swap");
map.insert(FuncSelector::new("8da5cb5b"), "owner");
assert_eq!(map.get(&FuncSelector::new("f3cd914c")), Some(&"swap"));
assert_eq!(map.get(&FuncSelector::new("8da5cb5b")), Some(&"owner"));
}
#[test]
fn test_path_interner_basic() {
let mut interner = PathInterner::new();
assert!(interner.is_empty());
let id_a = interner.intern("src/Foo.sol");
let id_b = interner.intern("src/Bar.sol");
assert_ne!(id_a, id_b);
assert_eq!(interner.len(), 2);
let id_a2 = interner.intern("src/Foo.sol");
assert_eq!(id_a, id_a2);
assert_eq!(interner.len(), 2);
}
#[test]
fn test_path_interner_resolve() {
let mut interner = PathInterner::new();
let id = interner.intern("/abs/src/Pool.sol");
assert_eq!(interner.resolve(id), Some("/abs/src/Pool.sol"));
assert_eq!(interner.resolve(FileId(999)), None);
}
#[test]
fn test_path_interner_monotonic_ids() {
let mut interner = PathInterner::new();
let a = interner.intern("a.sol");
let b = interner.intern("b.sol");
let c = interner.intern("c.sol");
assert_eq!(a, FileId(0));
assert_eq!(b, FileId(1));
assert_eq!(c, FileId(2));
}
#[test]
fn test_path_interner_build_remap() {
let mut interner = PathInterner::new();
interner.intern("/abs/src/Foo.sol");
let mut solc_map = HashMap::new();
solc_map.insert(SolcFileId::new("0"), "/abs/src/Bar.sol".to_string());
solc_map.insert(SolcFileId::new("1"), "/abs/src/Foo.sol".to_string());
let remap = interner.build_remap(&solc_map);
assert_eq!(remap[&1], FileId(0));
assert_eq!(remap[&0], FileId(1));
}
#[test]
fn test_path_interner_to_id_to_path_map() {
let mut interner = PathInterner::new();
interner.intern("src/A.sol");
interner.intern("src/B.sol");
let map = interner.to_id_to_path_map();
assert_eq!(map.get("0").map(|s| s.as_str()), Some("src/A.sol"));
assert_eq!(map.get("1").map(|s| s.as_str()), Some("src/B.sol"));
}
}