Skip to main content

backhopper_xref_graph/
loc.rs

1// Copyright (C) 2026 Michael S. Klishin and Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5//! Source locations.
6//!
7//! Locations are spans, not points, so multi-line call sites can be
8//! reported verbatim. Paths are interned per [`CallGraph`](crate::graph::CallGraph)
9//! to avoid storing thousands of duplicate `PathBuf`s on the edge set.
10
11use std::collections::HashMap;
12use std::path::{Path, PathBuf};
13
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
17#[serde(transparent)]
18pub struct PathId(u32);
19
20impl PathId {
21    pub const fn new(id: u32) -> Self {
22        Self(id)
23    }
24
25    pub const fn as_u32(self) -> u32 {
26        self.0
27    }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
31pub struct Position {
32    pub line: u32,
33    pub column: u32,
34    pub byte_offset: u32,
35}
36
37impl Position {
38    pub const fn new(line: u32, column: u32, byte_offset: u32) -> Self {
39        Self {
40            line,
41            column,
42            byte_offset,
43        }
44    }
45
46    /// `(1, 1, 0)`: the canonical "start of file" position.
47    pub const fn zero() -> Self {
48        Self::new(1, 1, 0)
49    }
50}
51
52impl Default for Position {
53    fn default() -> Self {
54        Self::zero()
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
59pub struct Loc {
60    pub path: PathId,
61    pub start: Position,
62    pub end: Position,
63}
64
65impl Loc {
66    pub fn point(path: PathId, pos: Position) -> Self {
67        Self {
68            path,
69            start: pos,
70            end: pos,
71        }
72    }
73
74    pub fn span(path: PathId, start: Position, end: Position) -> Self {
75        Self { path, start, end }
76    }
77}
78
79#[derive(Debug, Default, Clone)]
80pub struct PathInterner {
81    paths: Vec<PathBuf>,
82    index: HashMap<PathBuf, PathId>,
83}
84
85impl PathInterner {
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    pub fn intern(&mut self, p: &Path) -> PathId {
91        if let Some(id) = self.index.get(p) {
92            return *id;
93        }
94        let id = PathId::new(u32::try_from(self.paths.len()).expect("path id overflow"));
95        let owned = p.to_path_buf();
96        self.paths.push(owned.clone());
97        self.index.insert(owned, id);
98        id
99    }
100
101    pub fn get(&self, id: PathId) -> Option<&Path> {
102        self.paths.get(id.as_u32() as usize).map(PathBuf::as_path)
103    }
104
105    pub fn len(&self) -> usize {
106        self.paths.len()
107    }
108
109    pub fn is_empty(&self) -> bool {
110        self.paths.is_empty()
111    }
112}