1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! Virtual span registry for error reporting across multiple source documents.
//!
//! When deserializing a merged ConfigValue tree, we need to report errors with
//! spans that point back to the original source (CLI args, env vars, or config files).
//! Since facet-format expects a single linear span space, we assign virtual spans
//! to each value and maintain a registry that maps virtual spans back to their
//! real source locations.
use crate::config_value::{ConfigValue, ConfigValueVisitorMut};
use crate::path::Path;
use crate::provenance::Provenance;
use facet_reflect::Span;
/// Entry in the span registry mapping virtual span to real location.
#[derive(Debug, Clone)]
pub struct SpanEntry {
/// The real span within the source document.
pub real_span: Span,
/// Which source document this span refers to.
pub provenance: Provenance,
}
/// Registry that maps virtual spans to real source locations.
#[derive(Debug, Default)]
pub struct SpanRegistry {
/// Entries indexed by virtual span offset.
/// Each entry represents one value in the ConfigValue tree.
entries: Vec<SpanEntry>,
/// Next virtual offset to assign.
next_offset: u32,
}
impl SpanRegistry {
/// Create a new empty registry.
pub fn new() -> Self {
Self::default()
}
/// Register a span and return its virtual span.
///
/// The virtual span has a monotonically increasing offset that can be
/// used to look up the real span later.
pub fn register(&mut self, real_span: Span, provenance: Provenance) -> Span {
let virtual_offset = self.next_offset;
// Use the real length so error highlighting works correctly
let virtual_span = Span::new(virtual_offset as usize, real_span.len as usize);
self.entries.push(SpanEntry {
real_span,
provenance,
});
// Increment by at least 1 to ensure unique offsets
self.next_offset += real_span.len.max(1);
virtual_span
}
/// Look up a virtual span to get the real span and provenance.
///
/// Returns None if the virtual span doesn't match any registered entry.
#[allow(dead_code)]
pub fn lookup(&self, virtual_span: Span) -> Option<&SpanEntry> {
// Find the entry whose virtual offset range contains this span
let mut current_offset: u32 = 0;
for entry in &self.entries {
let entry_len = entry.real_span.len.max(1);
if virtual_span.offset >= current_offset
&& virtual_span.offset < current_offset + entry_len
{
return Some(entry);
}
current_offset += entry_len;
}
None
}
/// Look up by just the offset (for facet-format errors that only give offset).
pub fn lookup_by_offset(&self, offset: usize) -> Option<&SpanEntry> {
let offset = offset as u32;
let mut current_offset: u32 = 0;
for entry in &self.entries {
let entry_len = entry.real_span.len.max(1);
if offset >= current_offset && offset < current_offset + entry_len {
return Some(entry);
}
current_offset += entry_len;
}
None
}
}
/// Walk a ConfigValue tree and assign virtual spans, mutating the tree in place.
/// Returns the registry for looking up real spans.
pub fn assign_virtual_spans(value: &mut ConfigValue) -> SpanRegistry {
let mut visitor = VirtualSpanVisitor {
registry: SpanRegistry::new(),
};
let mut path = Path::new();
value.visit_mut(&mut visitor, &mut path);
visitor.registry
}
/// Visitor that assigns virtual spans to all values with real spans.
struct VirtualSpanVisitor {
registry: SpanRegistry,
}
impl ConfigValueVisitorMut for VirtualSpanVisitor {
fn visit_value(&mut self, _path: &Path, value: &mut ConfigValue) {
if let Some(real_span) = value.span() {
let prov = value.provenance().cloned().unwrap_or(Provenance::Default);
let virtual_span = self.registry.register(real_span, prov);
*value.span_mut() = Some(virtual_span);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_register_and_lookup() {
let mut registry = SpanRegistry::new();
let real1 = Span::new(10, 5);
let prov1 = Provenance::cli("--foo", "bar");
let virtual1 = registry.register(real1, prov1.clone());
let real2 = Span::new(100, 10);
let prov2 = Provenance::env("FOO", "baz");
let virtual2 = registry.register(real2, prov2.clone());
// Virtual spans should be distinct
assert_ne!(virtual1.offset, virtual2.offset);
// Lookup should return correct entries
let entry1 = registry.lookup(virtual1).unwrap();
assert_eq!(entry1.real_span.offset, 10);
assert_eq!(entry1.real_span.len, 5);
let entry2 = registry.lookup(virtual2).unwrap();
assert_eq!(entry2.real_span.offset, 100);
assert_eq!(entry2.real_span.len, 10);
}
#[test]
fn test_lookup_by_offset() {
let mut registry = SpanRegistry::new();
let real = Span::new(50, 8);
let prov = Provenance::cli("--test", "value");
let virtual_span = registry.register(real, prov);
// Should find by the start offset
let entry = registry
.lookup_by_offset(virtual_span.offset as usize)
.unwrap();
assert_eq!(entry.real_span.offset, 50);
}
}