Skip to content

Commit 500104a

Browse files
committed
refactor: SourceMap
1 parent fb30758 commit 500104a

File tree

8 files changed

+621
-309
lines changed

8 files changed

+621
-309
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ itertools = "0.13"
4242
codspeed-criterion-compat = { version = "2.7.2", default-features = false, optional = true }
4343
static_assertions = "1.1.0"
4444
simd-json = "0.14.3"
45+
self_cell = "1.2.0"
4546

4647
[dev-dependencies]
4748
twox-hash = "2.1.0"

src/cached_source.rs

Lines changed: 109 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
stream_chunks_of_source_map, StreamChunks,
1414
},
1515
rope::Rope,
16-
MapOptions, Source, SourceMap,
16+
BoxSource, MapOptions, Source, SourceExt, SourceMap,
1717
};
1818

1919
/// It tries to reused cached results from other methods to avoid calculations,
@@ -49,36 +49,60 @@ use crate::{
4949
/// "Hello World\nconsole.log('test');\nconsole.log('test2');\nHello2\n"
5050
/// );
5151
/// ```
52-
pub struct CachedSource<T> {
53-
inner: Arc<T>,
54-
cached_hash: Arc<OnceLock<u64>>,
55-
cached_maps:
56-
Arc<DashMap<MapOptions, Option<SourceMap>, BuildHasherDefault<FxHasher>>>,
52+
53+
#[derive(Debug)]
54+
struct CachedSourceOwner {
55+
inner: BoxSource,
56+
cached_hash: OnceLock<u64>,
57+
}
58+
59+
#[derive(Debug)]
60+
struct CachedSourceDependent<'a> {
61+
cached_colomns_map: OnceLock<Option<SourceMap<'static>>>,
62+
cached_line_only_map: OnceLock<Option<SourceMap<'static>>>,
63+
phantom: std::marker::PhantomData<&'a ()>,
5764
}
5865

59-
impl<T> CachedSource<T> {
66+
self_cell::self_cell!(
67+
struct CachedSourceCell {
68+
owner: CachedSourceOwner,
69+
70+
#[covariant]
71+
dependent: CachedSourceDependent,
72+
}
73+
74+
impl { Debug }
75+
);
76+
77+
pub struct CachedSource(CachedSourceCell);
78+
79+
impl CachedSource {
6080
/// Create a [CachedSource] with the original [Source].
61-
pub fn new(inner: T) -> Self {
62-
Self {
63-
inner: Arc::new(inner),
64-
cached_hash: Default::default(),
65-
cached_maps: Default::default(),
66-
}
81+
pub fn new<T: SourceExt>(inner: T) -> Self {
82+
let owner = CachedSourceOwner {
83+
inner: inner.boxed(),
84+
cached_hash: OnceLock::new(),
85+
};
86+
Self(CachedSourceCell::new(owner, |_| CachedSourceDependent {
87+
cached_colomns_map: Default::default(),
88+
cached_line_only_map: Default::default(),
89+
phantom: std::marker::PhantomData,
90+
}))
6791
}
6892

6993
/// Get the original [Source].
70-
pub fn original(&self) -> &T {
71-
&self.inner
94+
pub fn original(&self) -> &BoxSource {
95+
&self.0.borrow_owner().inner
7296
}
7397
}
7498

75-
impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
99+
impl Source for CachedSource {
76100
fn source(&self) -> Cow<str> {
77-
self.inner.source()
101+
self.0.borrow_owner().inner.source()
78102
}
79103

80104
fn rope(&self) -> Rope<'_> {
81-
self.inner.rope()
105+
self.0.borrow_owner().inner.rope()
82106
}
83107

84108
fn buffer(&self) -> Cow<[u8]> {
@@ -88,101 +112,114 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
88112
}
89113

90114
fn size(&self) -> usize {
91-
self.inner.size()
115+
self.0.borrow_owner().inner.size()
92116
}
93117

94118
fn map(&self, options: &MapOptions) -> Option<SourceMap> {
95-
if let Some(map) = self.cached_maps.get(options) {
96-
map.clone()
119+
if options.columns {
120+
self.0.with_dependent(|owner, dependent| {
121+
dependent.cached_colomns_map.get_or_init(|| {
122+
let map = owner.inner.map(options);
123+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
124+
}).clone()
125+
})
97126
} else {
98-
let map = self.inner.map(options);
99-
self.cached_maps.insert(options.clone(), map.clone());
100-
map
127+
self.0.with_dependent(|owner, dependent| {
128+
dependent.cached_line_only_map.get_or_init(|| {
129+
let map = owner.inner.map(options);
130+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
131+
}).clone()
132+
})
101133
}
102134
}
103135

104136
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
105-
self.inner.to_writer(writer)
137+
self.0.borrow_owner().inner.to_writer(writer)
106138
}
107139
}
108140

109-
impl<T: Source + Hash + PartialEq + Eq + 'static> StreamChunks
110-
for CachedSource<T>
111-
{
141+
impl StreamChunks for CachedSource {
112142
fn stream_chunks<'a>(
113143
&'a self,
114144
options: &MapOptions,
115145
on_chunk: crate::helpers::OnChunk<'_, 'a>,
116146
on_source: crate::helpers::OnSource<'_, 'a>,
117147
on_name: crate::helpers::OnName<'_, 'a>,
118148
) -> crate::helpers::GeneratedInfo {
119-
let cached_map = self.cached_maps.entry(options.clone());
120-
match cached_map {
121-
Entry::Occupied(entry) => {
122-
let source = self.rope();
123-
if let Some(map) = entry.get() {
124-
#[allow(unsafe_code)]
125-
// SAFETY: We guarantee that once a `SourceMap` is stored in the cache, it will never be removed.
126-
// Therefore, even if we force its lifetime to be longer, the reference remains valid.
127-
// This is based on the following assumptions:
128-
// 1. `SourceMap` will be valid for the entire duration of the application.
129-
// 2. The cached `SourceMap` will not be manually removed or replaced, ensuring the reference's safety.
130-
let map =
131-
unsafe { std::mem::transmute::<&SourceMap, &'a SourceMap>(map) };
132-
stream_chunks_of_source_map(
133-
source, map, on_chunk, on_source, on_name, options,
134-
)
135-
} else {
136-
stream_chunks_of_raw_source(
137-
source, options, on_chunk, on_source, on_name,
138-
)
139-
}
149+
let cached = if options.columns {
150+
self.0.borrow_dependent().cached_colomns_map.get()
151+
} else {
152+
self.0.borrow_dependent().cached_line_only_map.get()
153+
};
154+
match cached {
155+
Some(Some(map)) => {
156+
let source = self.0.borrow_owner().inner.rope();
157+
stream_chunks_of_source_map(
158+
source, map, on_chunk, on_source, on_name, options,
159+
)
140160
}
141-
Entry::Vacant(entry) => {
142-
let (generated_info, map) = stream_and_get_source_and_map(
143-
&self.inner as &T,
161+
Some(None) => {
162+
let source = self.0.borrow_owner().inner.rope();
163+
stream_chunks_of_raw_source(
164+
source, options, on_chunk, on_source, on_name,
165+
)
166+
}
167+
None => {
168+
if options.columns {
169+
self.0.with_dependent(|owner, dependent| {
170+
let (generated_info, map) = stream_and_get_source_and_map(
171+
&owner.inner,
144172
options,
145173
on_chunk,
146174
on_source,
147175
on_name,
148176
);
149-
entry.insert(map);
177+
dependent.cached_colomns_map.get_or_init(|| {
178+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
179+
});
150180
generated_info
181+
})
182+
} else {
183+
self.0.with_dependent(|owner, dependent| {
184+
let (generated_info, map) = stream_and_get_source_and_map(
185+
&owner.inner,
186+
options,
187+
on_chunk,
188+
on_source,
189+
on_name,
190+
);
191+
dependent.cached_line_only_map.get_or_init(|| {
192+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
193+
});
194+
generated_info
195+
})
196+
}
151197
}
152198
}
153199
}
154200
}
155201

156-
impl<T> Clone for CachedSource<T> {
157-
fn clone(&self) -> Self {
158-
Self {
159-
inner: self.inner.clone(),
160-
cached_hash: self.cached_hash.clone(),
161-
cached_maps: self.cached_maps.clone(),
162-
}
163-
}
164-
}
165-
166-
impl<T: Source + Hash + PartialEq + Eq + 'static> Hash for CachedSource<T> {
202+
impl Hash for CachedSource {
167203
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
168-
(self.cached_hash.get_or_init(|| {
204+
let owner = self.0.borrow_owner();
205+
(owner.cached_hash.get_or_init(|| {
169206
let mut hasher = FxHasher::default();
170-
self.inner.hash(&mut hasher);
207+
owner.inner.hash(&mut hasher);
171208
hasher.finish()
172209
}))
173210
.hash(state);
174211
}
175212
}
176213

177-
impl<T: PartialEq> PartialEq for CachedSource<T> {
214+
impl PartialEq for CachedSource {
178215
fn eq(&self, other: &Self) -> bool {
179-
self.inner == other.inner
216+
&self.0.borrow_owner().inner == &other.0.borrow_owner().inner
180217
}
181218
}
182219

183-
impl<T: Eq> Eq for CachedSource<T> {}
220+
impl Eq for CachedSource {}
184221

185-
impl<T: std::fmt::Debug> std::fmt::Debug for CachedSource<T> {
222+
impl std::fmt::Debug for CachedSource {
186223
fn fmt(
187224
&self,
188225
f: &mut std::fmt::Formatter<'_>,
@@ -194,7 +231,7 @@ impl<T: std::fmt::Debug> std::fmt::Debug for CachedSource<T> {
194231
writeln!(
195232
f,
196233
"{indent_str}{:indent$?}",
197-
self.inner,
234+
self.0.borrow_owner().inner,
198235
indent = indent + 2
199236
)?;
200237
write!(f, "{indent_str}).boxed()")
@@ -218,7 +255,7 @@ mod tests {
218255
value: "\nconsole.log(1);\n".to_string(),
219256
name: "index.js".to_string(),
220257
source_map: SourceMap::new(
221-
";AACA",
258+
";AACA".into(),
222259
vec!["index.js".into()],
223260
vec!["// DELETE IT\nconsole.log(1)".into()],
224261
vec![],
@@ -230,25 +267,6 @@ mod tests {
230267
assert_eq!(map.mappings(), ";;AACA");
231268
}
232269

233-
#[test]
234-
fn should_allow_to_store_and_share_cached_data() {
235-
let original = OriginalSource::new("Hello World", "test.txt");
236-
let source = CachedSource::new(original);
237-
let clone = source.clone();
238-
239-
// fill up cache
240-
let map_options = MapOptions::default();
241-
source.source();
242-
source.buffer();
243-
source.size();
244-
source.map(&map_options);
245-
246-
assert_eq!(
247-
*clone.cached_maps.get(&map_options).unwrap().value(),
248-
source.map(&map_options)
249-
);
250-
}
251-
252270
#[test]
253271
fn should_return_the_correct_size_for_binary_files() {
254272
let source = OriginalSource::new(

0 commit comments

Comments
 (0)