forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcaching_source_map_view.rs
More file actions
170 lines (152 loc) · 6.75 KB
/
caching_source_map_view.rs
File metadata and controls
170 lines (152 loc) · 6.75 KB
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
163
164
165
166
167
168
169
170
use std::ops::Range;
use std::sync::Arc;
use crate::source_map::SourceMap;
use crate::{BytePos, Pos, RelativeBytePos, SourceFile, SpanData};
/// A `SourceMap` wrapper that caches info about a single recent code position. This gives a good
/// speedup when hashing spans, because often multiple spans within a single line are hashed in
/// succession, and this avoids expensive `SourceMap` lookups each time the cache is hit. We used
/// to cache multiple code positions, but caching a single position ended up being simpler and
/// faster.
pub struct CachingSourceMapView<'sm> {
source_map: &'sm SourceMap,
file: Arc<SourceFile>,
// The line's byte position range in the `SourceMap`. This range will fail to contain a valid
// position in certain edge cases. Spans often start/end one past something, and when that
// something is the last character of a file (this can happen when a file doesn't end in a
// newline, for example), we'd still like for the position to be considered within the last
// line. However, it isn't according to the exclusive upper bound of this range. We cannot
// change the upper bound to be inclusive, because for most lines, the upper bound is the same
// as the lower bound of the next line, so there would be an ambiguity.
//
// Since the containment aspect of this range is only used to see whether or not the cache
// entry contains a position, the only ramification of the above is that we will get cache
// misses for these rare positions. A line lookup for the position via `SourceMap::lookup_line`
// after a cache miss will produce the last line number, as desired.
line_bounds: Range<BytePos>,
line_number: usize,
}
impl<'sm> CachingSourceMapView<'sm> {
pub fn new(source_map: &'sm SourceMap) -> CachingSourceMapView<'sm> {
let files = source_map.files();
let first_file = Arc::clone(&files[0]);
CachingSourceMapView {
source_map,
file: first_file,
line_bounds: BytePos(0)..BytePos(0),
line_number: 0,
}
}
#[inline]
fn pos_to_line(&self, pos: BytePos) -> (Range<BytePos>, usize) {
let pos = self.file.relative_position(pos);
let line_index = self.file.lookup_line(pos).unwrap();
let line_bounds = self.file.line_bounds(line_index);
let line_number = line_index + 1;
(line_bounds, line_number)
}
#[inline]
fn update(&mut self, new_file: Option<Arc<SourceFile>>, pos: BytePos) {
if let Some(file) = new_file {
self.file = file;
}
(self.line_bounds, self.line_number) = self.pos_to_line(pos);
}
pub fn byte_pos_to_line_and_col(
&mut self,
pos: BytePos,
) -> Option<(Arc<SourceFile>, usize, RelativeBytePos)> {
if self.line_bounds.contains(&pos) {
// Cache hit: do nothing.
} else {
// Cache miss. If the entry doesn't point to the correct file, get the new file and
// index.
let new_file = if !file_contains(&self.file, pos) {
Some(self.file_for_position(pos)?)
} else {
None
};
self.update(new_file, pos);
};
let col = RelativeBytePos(pos.to_u32() - self.line_bounds.start.to_u32());
Some((Arc::clone(&self.file), self.line_number, col))
}
pub fn span_data_to_lines_and_cols(
&mut self,
span_data: &SpanData,
) -> Option<(&SourceFile, usize, BytePos, usize, BytePos)> {
let lo_hit = self.line_bounds.contains(&span_data.lo);
let hi_hit = self.line_bounds.contains(&span_data.hi);
if lo_hit && hi_hit {
// span_data.lo and span_data.hi are cached (i.e. both in the same line).
return Some((
&self.file,
self.line_number,
span_data.lo - self.line_bounds.start,
self.line_number,
span_data.hi - self.line_bounds.start,
));
}
// If the cached file is wrong, update it. Return early if the span lo and hi are in
// different files.
let new_file = if !file_contains(&self.file, span_data.lo) {
let new_file = self.file_for_position(span_data.lo)?;
if !file_contains(&new_file, span_data.hi) {
return None;
}
Some(new_file)
} else {
if !file_contains(&self.file, span_data.hi) {
return None;
}
None
};
// If we reach here, lo and hi are in the same file.
if !lo_hit {
// We cache the lo information.
self.update(new_file, span_data.lo);
}
let lo_line_bounds = &self.line_bounds;
let lo_line_number = self.line_number.clone();
let (hi_line_bounds, hi_line_number) = if !self.line_bounds.contains(&span_data.hi) {
// hi and lo are in different lines. We compute but don't cache the hi information.
self.pos_to_line(span_data.hi)
} else {
// hi and lo are in the same line.
(self.line_bounds.clone(), self.line_number)
};
// Span lo and hi may equal line end when last line doesn't
// end in newline, hence the inclusive upper bounds below.
assert!(span_data.lo >= lo_line_bounds.start);
assert!(span_data.lo <= lo_line_bounds.end);
assert!(span_data.hi >= hi_line_bounds.start);
assert!(span_data.hi <= hi_line_bounds.end);
assert!(self.file.contains(span_data.lo));
assert!(self.file.contains(span_data.hi));
Some((
&self.file,
lo_line_number,
span_data.lo - lo_line_bounds.start,
hi_line_number,
span_data.hi - hi_line_bounds.start,
))
}
fn file_for_position(&self, pos: BytePos) -> Option<Arc<SourceFile>> {
if !self.source_map.files().is_empty() {
let file_idx = self.source_map.lookup_source_file_idx(pos);
let file = &self.source_map.files()[file_idx];
if file_contains(file, pos) {
return Some(Arc::clone(file));
}
}
None
}
}
#[inline]
fn file_contains(file: &SourceFile, pos: BytePos) -> bool {
// `SourceMap::lookup_source_file_idx` and `SourceFile::contains` both consider the position
// one past the end of a file to belong to it. Normally, that's what we want. But for the
// purposes of converting a byte position to a line and column number, we can't come up with a
// line and column number if the file is empty, because an empty file doesn't contain any
// lines. So for our purposes, we don't consider empty files to contain any byte position.
file.contains(pos) && !file.is_empty()
}