use std::collections::HashMap;
use std::sync::OnceLock;
use crate::json::JsonLocation;
pub struct Location {
pub file: &'static str,
pub line: u32,
pub column: u32,
}
static LOCATIONS: OnceLock<crate::lib_on::MetaRwLock<HashMap<&'static str, &'static Location>>> =
OnceLock::new();
#[doc(hidden)]
pub fn register_location(name: &'static str, location: &'static Location) {
let map = LOCATIONS.get_or_init(|| crate::lib_on::meta_rw_lock!("locations", HashMap::new()));
if let Ok(mut w) = map.write() {
w.entry(name).or_insert(location);
}
}
pub(crate) fn register_caller_location(
key: &'static str,
caller: &'static std::panic::Location<'static>,
) {
let map = LOCATIONS.get_or_init(|| crate::lib_on::meta_rw_lock!("locations", HashMap::new()));
if let Ok(mut w) = map.write() {
w.entry(key).or_insert_with(|| {
&*Box::leak(Box::new(Location {
file: caller.file(),
line: caller.line(),
column: caller.column(),
}))
});
}
}
pub(crate) fn lookup_location(name: &str) -> Option<JsonLocation> {
let map = LOCATIONS.get()?;
let location = map.read().ok()?.get(name).copied()?;
Some(JsonLocation {
file: normalize_file_path(location.file),
line: location.line,
column: location.column,
})
}
pub(crate) fn location_for_key(key: &str) -> JsonLocation {
lookup_location(key).unwrap_or_else(|| {
let mut parts = key.rsplitn(3, ':');
match (
parts.next().and_then(|c| c.parse().ok()),
parts.next().and_then(|l| l.parse().ok()),
parts.next(),
) {
(Some(column), Some(line), Some(file)) => JsonLocation {
file: normalize_file_path(file),
line,
column,
},
_ => JsonLocation {
file: key.to_string(),
line: 0,
column: 0,
},
}
})
}
pub(crate) fn any_relative_file() -> Option<&'static str> {
let map = LOCATIONS.get()?;
let guard = map.read().ok()?;
guard
.values()
.map(|location| location.file)
.find(|file| !is_absolute_path(file))
}
fn normalize_file_path(file: &str) -> String {
if !is_absolute_path(file) {
return file.to_string();
}
for marker in ["/registry/src/", "\\registry\\src\\"] {
if let Some(pos) = file.find(marker) {
let rest = &file[pos + marker.len()..];
let separator = if marker.starts_with('/') { '/' } else { '\\' };
if let Some((_index, crate_path)) = rest.split_once(separator) {
if !crate_path.is_empty() {
return format!("<external>/{}", crate_path.replace('\\', "/"));
}
}
}
}
file.to_string()
}
fn is_absolute_path(file: &str) -> bool {
file.starts_with('/') || file.starts_with('\\') || file.as_bytes().get(1) == Some(&b':')
}
#[cfg(test)]
mod tests {
use crate::lib_on::locations::{
lookup_location, normalize_file_path, register_location, Location,
};
#[test]
fn relative_paths_pass_through() {
assert_eq!(
normalize_file_path("crates/hotpath/src/lib.rs"),
"crates/hotpath/src/lib.rs"
);
}
#[test]
fn registry_paths_rewrite_to_external() {
assert_eq!(
normalize_file_path(
"/home/u/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.47.1/src/lib.rs"
),
"<external>/tokio-1.47.1/src/lib.rs"
);
assert_eq!(
normalize_file_path(
"C:\\Users\\u\\.cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\tokio-1.47.1\\src\\lib.rs"
),
"<external>/tokio-1.47.1/src/lib.rs"
);
}
#[test]
fn other_absolute_paths_pass_through() {
assert_eq!(
normalize_file_path("/home/u/project/src/main.rs"),
"/home/u/project/src/main.rs"
);
assert_eq!(
normalize_file_path("C:\\project\\src\\main.rs"),
"C:\\project\\src\\main.rs"
);
}
#[test]
fn first_registration_wins() {
static FIRST: Location = Location {
file: "src/a.rs",
line: 1,
column: 2,
};
static SECOND: Location = Location {
file: "src/b.rs",
line: 3,
column: 4,
};
register_location("locations_test_first_wins", &FIRST);
register_location("locations_test_first_wins", &SECOND);
let found = lookup_location("locations_test_first_wins").unwrap();
assert_eq!(found.file, "src/a.rs");
assert_eq!(found.line, 1);
assert_eq!(found.column, 2);
assert!(lookup_location("locations_test_unknown").is_none());
}
}