]>
Commit | Line | Data |
---|---|---|
223e47cc LB |
1 | // Copyright 2012 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
1a4d82fc JJ |
11 | //! The CodeMap tracks all the source code used within a single crate, mapping |
12 | //! from integer byte positions to the original source code location. Each bit | |
13 | //! of source parsed during crate parsing (typically files, in-memory strings, | |
14 | //! or various bits of macro expansion) cover a continuous range of bytes in the | |
15 | //! CodeMap and are represented by FileMaps. Byte positions are stored in | |
16 | //! `spans` and used pervasively in the compiler. They are absolute positions | |
17 | //! within the CodeMap, which upon request can be converted to line and column | |
18 | //! information, source code snippets, etc. | |
223e47cc | 19 | |
d9579d0f | 20 | pub use self::ExpnFormat::*; |
223e47cc | 21 | |
3157f602 XL |
22 | use std::cell::RefCell; |
23 | use std::path::{Path,PathBuf}; | |
1a4d82fc | 24 | use std::rc::Rc; |
223e47cc | 25 | |
3157f602 XL |
26 | use std::env; |
27 | use std::fs; | |
62682a34 | 28 | use std::io::{self, Read}; |
3157f602 XL |
29 | pub use syntax_pos::*; |
30 | use errors::CodeMapper; | |
223e47cc | 31 | |
e9174d1e | 32 | use ast::Name; |
c34b1796 | 33 | |
1a4d82fc JJ |
34 | /// Return the span itself if it doesn't come from a macro expansion, |
35 | /// otherwise return the call site span up to the `enclosing_sp` by | |
36 | /// following the `expn_info` chain. | |
37 | pub fn original_sp(cm: &CodeMap, sp: Span, enclosing_sp: Span) -> Span { | |
38 | let call_site1 = cm.with_expn_info(sp.expn_id, |ei| ei.map(|ei| ei.call_site)); | |
39 | let call_site2 = cm.with_expn_info(enclosing_sp.expn_id, |ei| ei.map(|ei| ei.call_site)); | |
40 | match (call_site1, call_site2) { | |
41 | (None, _) => sp, | |
42 | (Some(call_site1), Some(call_site2)) if call_site1 == call_site2 => sp, | |
43 | (Some(call_site1), _) => original_sp(cm, call_site1, enclosing_sp), | |
44 | } | |
45 | } | |
223e47cc | 46 | |
3157f602 XL |
47 | /// The source of expansion. |
48 | #[derive(Clone, Hash, Debug, PartialEq, Eq)] | |
49 | pub enum ExpnFormat { | |
50 | /// e.g. #[derive(...)] <item> | |
51 | MacroAttribute(Name), | |
52 | /// e.g. `format!()` | |
53 | MacroBang(Name), | |
7453a54e SL |
54 | } |
55 | ||
3157f602 XL |
56 | #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)] |
57 | pub struct Spanned<T> { | |
58 | pub node: T, | |
59 | pub span: Span, | |
7453a54e SL |
60 | } |
61 | ||
3157f602 XL |
62 | pub fn spanned<T>(lo: BytePos, hi: BytePos, t: T) -> Spanned<T> { |
63 | respan(mk_sp(lo, hi), t) | |
223e47cc LB |
64 | } |
65 | ||
3157f602 XL |
66 | pub fn respan<T>(sp: Span, t: T) -> Spanned<T> { |
67 | Spanned {node: t, span: sp} | |
223e47cc LB |
68 | } |
69 | ||
3157f602 XL |
70 | pub fn dummy_spanned<T>(t: T) -> Spanned<T> { |
71 | respan(DUMMY_SP, t) | |
1a4d82fc JJ |
72 | } |
73 | ||
5bcae85e SL |
74 | /// Build a span that covers the two provided spans. |
75 | pub fn combine_spans(sp1: Span, sp2: Span) -> Span { | |
76 | if sp1 == DUMMY_SP && sp2 == DUMMY_SP { | |
77 | DUMMY_SP | |
78 | } else if sp1 == DUMMY_SP { | |
79 | sp2 | |
80 | } else if sp2 == DUMMY_SP { | |
81 | sp1 | |
82 | } else { | |
83 | Span { | |
84 | lo: if sp1.lo < sp2.lo { sp1.lo } else { sp2.lo }, | |
85 | hi: if sp1.hi > sp2.hi { sp1.hi } else { sp2.hi }, | |
86 | expn_id: if sp1.expn_id == sp2.expn_id { sp1.expn_id } else { NO_EXPANSION }, | |
87 | } | |
88 | } | |
89 | } | |
90 | ||
85aaf69f | 91 | #[derive(Clone, Hash, Debug)] |
1a4d82fc | 92 | pub struct NameAndSpan { |
1a4d82fc | 93 | /// The format with which the macro was invoked. |
d9579d0f | 94 | pub format: ExpnFormat, |
c34b1796 AL |
95 | /// Whether the macro is allowed to use #[unstable]/feature-gated |
96 | /// features internally without forcing the whole crate to opt-in | |
97 | /// to them. | |
98 | pub allow_internal_unstable: bool, | |
1a4d82fc JJ |
99 | /// The span of the macro definition itself. The macro may not |
100 | /// have a sensible definition span (e.g. something defined | |
101 | /// completely inside libsyntax) in which case this is None. | |
102 | pub span: Option<Span> | |
223e47cc LB |
103 | } |
104 | ||
e9174d1e SL |
105 | impl NameAndSpan { |
106 | pub fn name(&self) -> Name { | |
107 | match self.format { | |
108 | ExpnFormat::MacroAttribute(s) => s, | |
109 | ExpnFormat::MacroBang(s) => s, | |
e9174d1e SL |
110 | } |
111 | } | |
112 | } | |
113 | ||
d9579d0f | 114 | /// Extra information for tracking spans of macro and syntax sugar expansion |
85aaf69f | 115 | #[derive(Hash, Debug)] |
1a4d82fc | 116 | pub struct ExpnInfo { |
d9579d0f AL |
117 | /// The location of the actual macro invocation or syntax sugar , e.g. |
118 | /// `let x = foo!();` or `if let Some(y) = x {}` | |
1a4d82fc JJ |
119 | /// |
120 | /// This may recursively refer to other macro invocations, e.g. if | |
121 | /// `foo!()` invoked `bar!()` internally, and there was an | |
122 | /// expression inside `bar!`; the call_site of the expression in | |
123 | /// the expansion would point to the `bar!` invocation; that | |
124 | /// call_site span would have its own ExpnInfo, with the call_site | |
125 | /// pointing to the `foo!` invocation. | |
126 | pub call_site: Span, | |
d9579d0f | 127 | /// Information about the expansion. |
1a4d82fc | 128 | pub callee: NameAndSpan |
223e47cc LB |
129 | } |
130 | ||
c34b1796 AL |
131 | // _____________________________________________________________________________ |
132 | // FileMap, MultiByteChar, FileName, FileLines | |
133 | // | |
134 | ||
62682a34 SL |
135 | /// An abstraction over the fs operations used by the Parser. |
136 | pub trait FileLoader { | |
137 | /// Query the existence of a file. | |
138 | fn file_exists(&self, path: &Path) -> bool; | |
139 | ||
3157f602 XL |
140 | /// Return an absolute path to a file, if possible. |
141 | fn abs_path(&self, path: &Path) -> Option<PathBuf>; | |
142 | ||
62682a34 SL |
143 | /// Read the contents of an UTF-8 file into memory. |
144 | fn read_file(&self, path: &Path) -> io::Result<String>; | |
145 | } | |
146 | ||
147 | /// A FileLoader that uses std::fs to load real files. | |
148 | pub struct RealFileLoader; | |
149 | ||
150 | impl FileLoader for RealFileLoader { | |
151 | fn file_exists(&self, path: &Path) -> bool { | |
152 | fs::metadata(path).is_ok() | |
153 | } | |
154 | ||
3157f602 XL |
155 | fn abs_path(&self, path: &Path) -> Option<PathBuf> { |
156 | if path.is_absolute() { | |
157 | Some(path.to_path_buf()) | |
158 | } else { | |
159 | env::current_dir() | |
160 | .ok() | |
161 | .map(|cwd| cwd.join(path)) | |
162 | } | |
163 | } | |
164 | ||
62682a34 SL |
165 | fn read_file(&self, path: &Path) -> io::Result<String> { |
166 | let mut src = String::new(); | |
54a0048b | 167 | fs::File::open(path)?.read_to_string(&mut src)?; |
62682a34 SL |
168 | Ok(src) |
169 | } | |
170 | } | |
c34b1796 AL |
171 | |
172 | // _____________________________________________________________________________ | |
173 | // CodeMap | |
174 | // | |
175 | ||
223e47cc | 176 | pub struct CodeMap { |
1a4d82fc | 177 | pub files: RefCell<Vec<Rc<FileMap>>>, |
62682a34 SL |
178 | expansions: RefCell<Vec<ExpnInfo>>, |
179 | file_loader: Box<FileLoader> | |
223e47cc LB |
180 | } |
181 | ||
970d7e83 | 182 | impl CodeMap { |
223e47cc LB |
183 | pub fn new() -> CodeMap { |
184 | CodeMap { | |
1a4d82fc JJ |
185 | files: RefCell::new(Vec::new()), |
186 | expansions: RefCell::new(Vec::new()), | |
62682a34 SL |
187 | file_loader: Box::new(RealFileLoader) |
188 | } | |
189 | } | |
190 | ||
191 | pub fn with_file_loader(file_loader: Box<FileLoader>) -> CodeMap { | |
192 | CodeMap { | |
193 | files: RefCell::new(Vec::new()), | |
194 | expansions: RefCell::new(Vec::new()), | |
195 | file_loader: file_loader | |
223e47cc LB |
196 | } |
197 | } | |
198 | ||
62682a34 SL |
199 | pub fn file_exists(&self, path: &Path) -> bool { |
200 | self.file_loader.file_exists(path) | |
201 | } | |
202 | ||
203 | pub fn load_file(&self, path: &Path) -> io::Result<Rc<FileMap>> { | |
54a0048b | 204 | let src = self.file_loader.read_file(path)?; |
3157f602 XL |
205 | let abs_path = self.file_loader.abs_path(path).map(|p| p.to_str().unwrap().to_string()); |
206 | Ok(self.new_filemap(path.to_str().unwrap().to_string(), abs_path, src)) | |
62682a34 SL |
207 | } |
208 | ||
c1a9b12d SL |
209 | fn next_start_pos(&self) -> usize { |
210 | let files = self.files.borrow(); | |
211 | match files.last() { | |
212 | None => 0, | |
213 | // Add one so there is some space between files. This lets us distinguish | |
214 | // positions in the codemap, even in the presence of zero-length files. | |
215 | Some(last) => last.end_pos.to_usize() + 1, | |
216 | } | |
217 | } | |
218 | ||
219 | /// Creates a new filemap without setting its line information. If you don't | |
220 | /// intend to set the line information yourself, you should use new_filemap_and_lines. | |
3157f602 XL |
221 | pub fn new_filemap(&self, filename: FileName, abs_path: Option<FileName>, |
222 | mut src: String) -> Rc<FileMap> { | |
c1a9b12d | 223 | let start_pos = self.next_start_pos(); |
1a4d82fc | 224 | let mut files = self.files.borrow_mut(); |
223e47cc | 225 | |
1a4d82fc | 226 | // Remove utf-8 BOM if any. |
d9579d0f AL |
227 | if src.starts_with("\u{feff}") { |
228 | src.drain(..3); | |
229 | } | |
223e47cc | 230 | |
c34b1796 AL |
231 | let end_pos = start_pos + src.len(); |
232 | ||
1a4d82fc JJ |
233 | let filemap = Rc::new(FileMap { |
234 | name: filename, | |
3157f602 | 235 | abs_path: abs_path, |
c34b1796 | 236 | src: Some(Rc::new(src)), |
85aaf69f | 237 | start_pos: Pos::from_usize(start_pos), |
c34b1796 | 238 | end_pos: Pos::from_usize(end_pos), |
1a4d82fc JJ |
239 | lines: RefCell::new(Vec::new()), |
240 | multibyte_chars: RefCell::new(Vec::new()), | |
241 | }); | |
223e47cc | 242 | |
1a4d82fc JJ |
243 | files.push(filemap.clone()); |
244 | ||
245 | filemap | |
223e47cc LB |
246 | } |
247 | ||
c1a9b12d | 248 | /// Creates a new filemap and sets its line information. |
3157f602 XL |
249 | pub fn new_filemap_and_lines(&self, filename: &str, abs_path: Option<&str>, |
250 | src: &str) -> Rc<FileMap> { | |
251 | let fm = self.new_filemap(filename.to_string(), | |
252 | abs_path.map(|s| s.to_owned()), | |
253 | src.to_owned()); | |
a7813a04 | 254 | let mut byte_pos: u32 = fm.start_pos.0; |
c1a9b12d SL |
255 | for line in src.lines() { |
256 | // register the start of this line | |
257 | fm.next_line(BytePos(byte_pos)); | |
258 | ||
259 | // update byte_pos to include this line and the \n at the end | |
260 | byte_pos += line.len() as u32 + 1; | |
261 | } | |
262 | fm | |
263 | } | |
264 | ||
265 | ||
c34b1796 AL |
266 | /// Allocates a new FileMap representing a source file from an external |
267 | /// crate. The source code of such an "imported filemap" is not available, | |
268 | /// but we still know enough to generate accurate debuginfo location | |
269 | /// information for things inlined from other crates. | |
270 | pub fn new_imported_filemap(&self, | |
271 | filename: FileName, | |
3157f602 | 272 | abs_path: Option<FileName>, |
c34b1796 | 273 | source_len: usize, |
d9579d0f AL |
274 | mut file_local_lines: Vec<BytePos>, |
275 | mut file_local_multibyte_chars: Vec<MultiByteChar>) | |
c34b1796 | 276 | -> Rc<FileMap> { |
c1a9b12d | 277 | let start_pos = self.next_start_pos(); |
c34b1796 | 278 | let mut files = self.files.borrow_mut(); |
c34b1796 AL |
279 | |
280 | let end_pos = Pos::from_usize(start_pos + source_len); | |
281 | let start_pos = Pos::from_usize(start_pos); | |
282 | ||
d9579d0f AL |
283 | for pos in &mut file_local_lines { |
284 | *pos = *pos + start_pos; | |
285 | } | |
286 | ||
287 | for mbc in &mut file_local_multibyte_chars { | |
288 | mbc.pos = mbc.pos + start_pos; | |
289 | } | |
c34b1796 AL |
290 | |
291 | let filemap = Rc::new(FileMap { | |
292 | name: filename, | |
3157f602 | 293 | abs_path: abs_path, |
c34b1796 AL |
294 | src: None, |
295 | start_pos: start_pos, | |
296 | end_pos: end_pos, | |
d9579d0f AL |
297 | lines: RefCell::new(file_local_lines), |
298 | multibyte_chars: RefCell::new(file_local_multibyte_chars), | |
c34b1796 AL |
299 | }); |
300 | ||
301 | files.push(filemap.clone()); | |
302 | ||
303 | filemap | |
304 | } | |
305 | ||
1a4d82fc | 306 | pub fn mk_substr_filename(&self, sp: Span) -> String { |
223e47cc | 307 | let pos = self.lookup_char_pos(sp.lo); |
1a4d82fc JJ |
308 | (format!("<{}:{}:{}>", |
309 | pos.file.name, | |
310 | pos.line, | |
85aaf69f | 311 | pos.col.to_usize() + 1)).to_string() |
223e47cc LB |
312 | } |
313 | ||
314 | /// Lookup source information about a BytePos | |
970d7e83 | 315 | pub fn lookup_char_pos(&self, pos: BytePos) -> Loc { |
d9579d0f | 316 | let chpos = self.bytepos_to_file_charpos(pos); |
c1a9b12d SL |
317 | match self.lookup_line(pos) { |
318 | Ok(FileMapAndLine { fm: f, line: a }) => { | |
319 | let line = a + 1; // Line numbers start at 1 | |
320 | let linebpos = (*f.lines.borrow())[a]; | |
321 | let linechpos = self.bytepos_to_file_charpos(linebpos); | |
322 | debug!("byte pos {:?} is on the line at byte pos {:?}", | |
323 | pos, linebpos); | |
324 | debug!("char pos {:?} is on the line at char pos {:?}", | |
325 | chpos, linechpos); | |
326 | debug!("byte is on line: {}", line); | |
327 | assert!(chpos >= linechpos); | |
328 | Loc { | |
329 | file: f, | |
330 | line: line, | |
331 | col: chpos - linechpos, | |
332 | } | |
333 | } | |
334 | Err(f) => { | |
335 | Loc { | |
336 | file: f, | |
337 | line: 0, | |
338 | col: chpos, | |
339 | } | |
340 | } | |
d9579d0f AL |
341 | } |
342 | } | |
343 | ||
c1a9b12d SL |
344 | // If the relevant filemap is empty, we don't return a line number. |
345 | fn lookup_line(&self, pos: BytePos) -> Result<FileMapAndLine, Rc<FileMap>> { | |
d9579d0f AL |
346 | let idx = self.lookup_filemap_idx(pos); |
347 | ||
348 | let files = self.files.borrow(); | |
349 | let f = (*files)[idx].clone(); | |
c1a9b12d | 350 | |
9e0c209e SL |
351 | match f.lookup_line(pos) { |
352 | Some(line) => Ok(FileMapAndLine { fm: f, line: line }), | |
353 | None => Err(f) | |
c1a9b12d | 354 | } |
223e47cc LB |
355 | } |
356 | ||
970d7e83 | 357 | pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt { |
223e47cc | 358 | let loc = self.lookup_char_pos(pos); |
1a4d82fc JJ |
359 | LocWithOpt { |
360 | filename: loc.file.name.to_string(), | |
361 | line: loc.line, | |
362 | col: loc.col, | |
363 | file: Some(loc.file) | |
223e47cc LB |
364 | } |
365 | } | |
366 | ||
9e0c209e SL |
367 | /// Returns `Some(span)`, a union of the lhs and rhs span. The lhs must precede the rhs. If |
368 | /// there are gaps between lhs and rhs, the resulting union will cross these gaps. | |
369 | /// For this to work, the spans have to be: | |
370 | /// * the expn_id of both spans much match | |
371 | /// * the lhs span needs to end on the same line the rhs span begins | |
372 | /// * the lhs span must start at or before the rhs span | |
373 | pub fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> { | |
374 | use std::cmp; | |
375 | ||
376 | // make sure we're at the same expansion id | |
377 | if sp_lhs.expn_id != sp_rhs.expn_id { | |
378 | return None; | |
379 | } | |
380 | ||
381 | let lhs_end = match self.lookup_line(sp_lhs.hi) { | |
382 | Ok(x) => x, | |
383 | Err(_) => return None | |
384 | }; | |
385 | let rhs_begin = match self.lookup_line(sp_rhs.lo) { | |
386 | Ok(x) => x, | |
387 | Err(_) => return None | |
388 | }; | |
389 | ||
390 | // if we must cross lines to merge, don't merge | |
391 | if lhs_end.line != rhs_begin.line { | |
392 | return None; | |
393 | } | |
394 | ||
395 | // ensure these follow the expected order and we don't overlap | |
396 | if (sp_lhs.lo <= sp_rhs.lo) && (sp_lhs.hi <= sp_rhs.lo) { | |
397 | Some(Span { | |
398 | lo: cmp::min(sp_lhs.lo, sp_rhs.lo), | |
399 | hi: cmp::max(sp_lhs.hi, sp_rhs.hi), | |
400 | expn_id: sp_lhs.expn_id, | |
401 | }) | |
402 | } else { | |
403 | None | |
404 | } | |
405 | } | |
406 | ||
1a4d82fc | 407 | pub fn span_to_string(&self, sp: Span) -> String { |
a7813a04 XL |
408 | if sp == COMMAND_LINE_SP { |
409 | return "<command line option>".to_string(); | |
410 | } | |
411 | ||
7453a54e | 412 | if self.files.borrow().is_empty() && sp.source_equal(&DUMMY_SP) { |
1a4d82fc | 413 | return "no-location".to_string(); |
223e47cc LB |
414 | } |
415 | ||
416 | let lo = self.lookup_char_pos_adj(sp.lo); | |
417 | let hi = self.lookup_char_pos_adj(sp.hi); | |
1a4d82fc JJ |
418 | return (format!("{}:{}:{}: {}:{}", |
419 | lo.filename, | |
420 | lo.line, | |
85aaf69f | 421 | lo.col.to_usize() + 1, |
1a4d82fc | 422 | hi.line, |
85aaf69f | 423 | hi.col.to_usize() + 1)).to_string() |
223e47cc LB |
424 | } |
425 | ||
92a42be0 SL |
426 | // Returns true if two spans have the same callee |
427 | // (Assumes the same ExpnFormat implies same callee) | |
428 | fn match_callees(&self, sp_a: &Span, sp_b: &Span) -> bool { | |
429 | let fmt_a = self | |
430 | .with_expn_info(sp_a.expn_id, | |
431 | |ei| ei.map(|ei| ei.callee.format.clone())); | |
432 | ||
433 | let fmt_b = self | |
434 | .with_expn_info(sp_b.expn_id, | |
435 | |ei| ei.map(|ei| ei.callee.format.clone())); | |
436 | fmt_a == fmt_b | |
437 | } | |
438 | ||
439 | /// Returns a formatted string showing the expansion chain of a span | |
440 | /// | |
441 | /// Spans are printed in the following format: | |
442 | /// | |
443 | /// filename:start_line:col: end_line:col | |
444 | /// snippet | |
445 | /// Callee: | |
446 | /// Callee span | |
447 | /// Callsite: | |
448 | /// Callsite span | |
449 | /// | |
450 | /// Callees and callsites are printed recursively (if available, otherwise header | |
451 | /// and span is omitted), expanding into their own callee/callsite spans. | |
452 | /// Each layer of recursion has an increased indent, and snippets are truncated | |
453 | /// to at most 50 characters. Finally, recursive calls to the same macro are squashed, | |
454 | /// with '...' used to represent any number of recursive calls. | |
455 | pub fn span_to_expanded_string(&self, sp: Span) -> String { | |
456 | self.span_to_expanded_string_internal(sp, "") | |
457 | } | |
458 | ||
459 | fn span_to_expanded_string_internal(&self, sp:Span, indent: &str) -> String { | |
460 | let mut indent = indent.to_owned(); | |
461 | let mut output = "".to_owned(); | |
462 | let span_str = self.span_to_string(sp); | |
463 | let mut span_snip = self.span_to_snippet(sp) | |
464 | .unwrap_or("Snippet unavailable".to_owned()); | |
7453a54e SL |
465 | |
466 | // Truncate by code points - in worst case this will be more than 50 characters, | |
467 | // but ensures at least 50 characters and respects byte boundaries. | |
468 | let char_vec: Vec<(usize, char)> = span_snip.char_indices().collect(); | |
469 | if char_vec.len() > 50 { | |
470 | span_snip.truncate(char_vec[49].0); | |
92a42be0 SL |
471 | span_snip.push_str("..."); |
472 | } | |
7453a54e | 473 | |
92a42be0 SL |
474 | output.push_str(&format!("{}{}\n{}`{}`\n", indent, span_str, indent, span_snip)); |
475 | ||
476 | if sp.expn_id == NO_EXPANSION || sp.expn_id == COMMAND_LINE_EXPN { | |
477 | return output; | |
478 | } | |
479 | ||
480 | let mut callee = self.with_expn_info(sp.expn_id, | |
481 | |ei| ei.and_then(|ei| ei.callee.span.clone())); | |
482 | let mut callsite = self.with_expn_info(sp.expn_id, | |
483 | |ei| ei.map(|ei| ei.call_site.clone())); | |
484 | ||
485 | indent.push_str(" "); | |
486 | let mut is_recursive = false; | |
487 | ||
488 | while callee.is_some() && self.match_callees(&sp, &callee.unwrap()) { | |
489 | callee = self.with_expn_info(callee.unwrap().expn_id, | |
490 | |ei| ei.and_then(|ei| ei.callee.span.clone())); | |
491 | is_recursive = true; | |
492 | } | |
493 | if let Some(span) = callee { | |
494 | output.push_str(&indent); | |
495 | output.push_str("Callee:\n"); | |
496 | if is_recursive { | |
497 | output.push_str(&indent); | |
498 | output.push_str("...\n"); | |
499 | } | |
500 | output.push_str(&(self.span_to_expanded_string_internal(span, &indent))); | |
501 | } | |
502 | ||
503 | is_recursive = false; | |
504 | while callsite.is_some() && self.match_callees(&sp, &callsite.unwrap()) { | |
505 | callsite = self.with_expn_info(callsite.unwrap().expn_id, | |
506 | |ei| ei.map(|ei| ei.call_site.clone())); | |
507 | is_recursive = true; | |
508 | } | |
509 | if let Some(span) = callsite { | |
510 | output.push_str(&indent); | |
511 | output.push_str("Callsite:\n"); | |
512 | if is_recursive { | |
513 | output.push_str(&indent); | |
514 | output.push_str("...\n"); | |
515 | } | |
516 | output.push_str(&(self.span_to_expanded_string_internal(span, &indent))); | |
517 | } | |
518 | output | |
519 | } | |
520 | ||
7453a54e SL |
521 | /// Return the source span - this is either the supplied span, or the span for |
522 | /// the macro callsite that expanded to it. | |
523 | pub fn source_callsite(&self, sp: Span) -> Span { | |
524 | let mut span = sp; | |
525 | // Special case - if a macro is parsed as an argument to another macro, the source | |
526 | // callsite is the first callsite, which is also source-equivalent to the span. | |
527 | let mut first = true; | |
528 | while span.expn_id != NO_EXPANSION && span.expn_id != COMMAND_LINE_EXPN { | |
529 | if let Some(callsite) = self.with_expn_info(span.expn_id, | |
530 | |ei| ei.map(|ei| ei.call_site.clone())) { | |
531 | if first && span.source_equal(&callsite) { | |
532 | if self.lookup_char_pos(span.lo).file.is_real_file() { | |
533 | return Span { expn_id: NO_EXPANSION, .. span }; | |
534 | } | |
535 | } | |
536 | first = false; | |
537 | span = callsite; | |
538 | } | |
539 | else { | |
540 | break; | |
541 | } | |
542 | } | |
543 | span | |
544 | } | |
545 | ||
546 | /// Return the source callee. | |
547 | /// | |
548 | /// Returns None if the supplied span has no expansion trace, | |
549 | /// else returns the NameAndSpan for the macro definition | |
550 | /// corresponding to the source callsite. | |
551 | pub fn source_callee(&self, sp: Span) -> Option<NameAndSpan> { | |
552 | let mut span = sp; | |
553 | // Special case - if a macro is parsed as an argument to another macro, the source | |
554 | // callsite is source-equivalent to the span, and the source callee is the first callee. | |
555 | let mut first = true; | |
556 | while let Some(callsite) = self.with_expn_info(span.expn_id, | |
557 | |ei| ei.map(|ei| ei.call_site.clone())) { | |
558 | if first && span.source_equal(&callsite) { | |
559 | if self.lookup_char_pos(span.lo).file.is_real_file() { | |
560 | return self.with_expn_info(span.expn_id, | |
561 | |ei| ei.map(|ei| ei.callee.clone())); | |
562 | } | |
563 | } | |
564 | first = false; | |
565 | if let Some(_) = self.with_expn_info(callsite.expn_id, | |
566 | |ei| ei.map(|ei| ei.call_site.clone())) { | |
567 | span = callsite; | |
568 | } | |
569 | else { | |
570 | return self.with_expn_info(span.expn_id, | |
571 | |ei| ei.map(|ei| ei.callee.clone())); | |
572 | } | |
573 | } | |
574 | None | |
575 | } | |
576 | ||
1a4d82fc JJ |
577 | pub fn span_to_filename(&self, sp: Span) -> FileName { |
578 | self.lookup_char_pos(sp.lo).file.name.to_string() | |
223e47cc LB |
579 | } |
580 | ||
d9579d0f | 581 | pub fn span_to_lines(&self, sp: Span) -> FileLinesResult { |
a7813a04 XL |
582 | debug!("span_to_lines(sp={:?})", sp); |
583 | ||
d9579d0f AL |
584 | if sp.lo > sp.hi { |
585 | return Err(SpanLinesError::IllFormedSpan(sp)); | |
586 | } | |
587 | ||
223e47cc | 588 | let lo = self.lookup_char_pos(sp.lo); |
a7813a04 | 589 | debug!("span_to_lines: lo={:?}", lo); |
223e47cc | 590 | let hi = self.lookup_char_pos(sp.hi); |
a7813a04 | 591 | debug!("span_to_lines: hi={:?}", hi); |
d9579d0f AL |
592 | |
593 | if lo.file.start_pos != hi.file.start_pos { | |
594 | return Err(SpanLinesError::DistinctSources(DistinctSources { | |
595 | begin: (lo.file.name.clone(), lo.file.start_pos), | |
596 | end: (hi.file.name.clone(), hi.file.start_pos), | |
597 | })); | |
598 | } | |
599 | assert!(hi.line >= lo.line); | |
600 | ||
9346a6ac AL |
601 | let mut lines = Vec::with_capacity(hi.line - lo.line + 1); |
602 | ||
603 | // The span starts partway through the first line, | |
604 | // but after that it starts from offset 0. | |
605 | let mut start_col = lo.col; | |
606 | ||
607 | // For every line but the last, it extends from `start_col` | |
608 | // and to the end of the line. Be careful because the line | |
609 | // numbers in Loc are 1-based, so we subtract 1 to get 0-based | |
610 | // lines. | |
611 | for line_index in lo.line-1 .. hi.line-1 { | |
a7813a04 XL |
612 | let line_len = lo.file.get_line(line_index) |
613 | .map(|s| s.chars().count()) | |
614 | .unwrap_or(0); | |
9346a6ac AL |
615 | lines.push(LineInfo { line_index: line_index, |
616 | start_col: start_col, | |
617 | end_col: CharPos::from_usize(line_len) }); | |
618 | start_col = CharPos::from_usize(0); | |
619 | } | |
620 | ||
621 | // For the last line, it extends from `start_col` to `hi.col`: | |
622 | lines.push(LineInfo { line_index: hi.line - 1, | |
623 | start_col: start_col, | |
624 | end_col: hi.col }); | |
625 | ||
d9579d0f | 626 | Ok(FileLines {file: lo.file, lines: lines}) |
223e47cc LB |
627 | } |
628 | ||
85aaf69f SL |
629 | pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> { |
630 | if sp.lo > sp.hi { | |
631 | return Err(SpanSnippetError::IllFormedSpan(sp)); | |
632 | } | |
633 | ||
c34b1796 AL |
634 | let local_begin = self.lookup_byte_offset(sp.lo); |
635 | let local_end = self.lookup_byte_offset(sp.hi); | |
1a4d82fc | 636 | |
c34b1796 | 637 | if local_begin.fm.start_pos != local_end.fm.start_pos { |
85aaf69f | 638 | return Err(SpanSnippetError::DistinctSources(DistinctSources { |
c34b1796 AL |
639 | begin: (local_begin.fm.name.clone(), |
640 | local_begin.fm.start_pos), | |
641 | end: (local_end.fm.name.clone(), | |
642 | local_end.fm.start_pos) | |
85aaf69f | 643 | })); |
1a4d82fc | 644 | } else { |
c34b1796 AL |
645 | match local_begin.fm.src { |
646 | Some(ref src) => { | |
647 | let start_index = local_begin.pos.to_usize(); | |
648 | let end_index = local_end.pos.to_usize(); | |
649 | let source_len = (local_begin.fm.end_pos - | |
650 | local_begin.fm.start_pos).to_usize(); | |
651 | ||
652 | if start_index > end_index || end_index > source_len { | |
653 | return Err(SpanSnippetError::MalformedForCodemap( | |
654 | MalformedCodemapPositions { | |
655 | name: local_begin.fm.name.clone(), | |
656 | source_len: source_len, | |
657 | begin_pos: local_begin.pos, | |
658 | end_pos: local_end.pos, | |
659 | })); | |
660 | } | |
85aaf69f | 661 | |
c34b1796 AL |
662 | return Ok((&src[start_index..end_index]).to_string()) |
663 | } | |
664 | None => { | |
665 | return Err(SpanSnippetError::SourceNotAvailable { | |
666 | filename: local_begin.fm.name.clone() | |
667 | }); | |
668 | } | |
669 | } | |
1a4d82fc | 670 | } |
223e47cc LB |
671 | } |
672 | ||
3157f602 | 673 | pub fn get_filemap(&self, filename: &str) -> Option<Rc<FileMap>> { |
62682a34 | 674 | for fm in self.files.borrow().iter() { |
1a4d82fc | 675 | if filename == fm.name { |
3157f602 | 676 | return Some(fm.clone()); |
1a4d82fc JJ |
677 | } |
678 | } | |
3157f602 | 679 | None |
1a4d82fc JJ |
680 | } |
681 | ||
c34b1796 | 682 | /// For a global BytePos compute the local offset within the containing FileMap |
1a4d82fc JJ |
683 | pub fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos { |
684 | let idx = self.lookup_filemap_idx(bpos); | |
685 | let fm = (*self.files.borrow())[idx].clone(); | |
686 | let offset = bpos - fm.start_pos; | |
687 | FileMapAndBytePos {fm: fm, pos: offset} | |
688 | } | |
689 | ||
c1a9b12d | 690 | /// Converts an absolute BytePos to a CharPos relative to the filemap. |
1a4d82fc JJ |
691 | pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos { |
692 | let idx = self.lookup_filemap_idx(bpos); | |
693 | let files = self.files.borrow(); | |
694 | let map = &(*files)[idx]; | |
695 | ||
696 | // The number of extra bytes due to multibyte chars in the FileMap | |
697 | let mut total_extra_bytes = 0; | |
698 | ||
62682a34 | 699 | for mbc in map.multibyte_chars.borrow().iter() { |
1a4d82fc JJ |
700 | debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos); |
701 | if mbc.pos < bpos { | |
702 | // every character is at least one byte, so we only | |
703 | // count the actual extra bytes. | |
704 | total_extra_bytes += mbc.bytes - 1; | |
705 | // We should never see a byte position in the middle of a | |
706 | // character | |
85aaf69f | 707 | assert!(bpos.to_usize() >= mbc.pos.to_usize() + mbc.bytes); |
1a4d82fc JJ |
708 | } else { |
709 | break; | |
710 | } | |
711 | } | |
712 | ||
85aaf69f SL |
713 | assert!(map.start_pos.to_usize() + total_extra_bytes <= bpos.to_usize()); |
714 | CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes) | |
223e47cc | 715 | } |
223e47cc | 716 | |
c1a9b12d | 717 | // Return the index of the filemap (in self.files) which contains pos. |
9e0c209e | 718 | pub fn lookup_filemap_idx(&self, pos: BytePos) -> usize { |
1a4d82fc JJ |
719 | let files = self.files.borrow(); |
720 | let files = &*files; | |
c1a9b12d SL |
721 | let count = files.len(); |
722 | ||
723 | // Binary search for the filemap. | |
85aaf69f | 724 | let mut a = 0; |
c1a9b12d | 725 | let mut b = count; |
85aaf69f SL |
726 | while b - a > 1 { |
727 | let m = (a + b) / 2; | |
1a4d82fc | 728 | if files[m].start_pos > pos { |
223e47cc LB |
729 | b = m; |
730 | } else { | |
731 | a = m; | |
732 | } | |
733 | } | |
c1a9b12d SL |
734 | |
735 | assert!(a < count, "position {} does not resolve to a source location", pos.to_usize()); | |
223e47cc LB |
736 | |
737 | return a; | |
738 | } | |
739 | ||
1a4d82fc JJ |
740 | pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId { |
741 | let mut expansions = self.expansions.borrow_mut(); | |
742 | expansions.push(expn_info); | |
9346a6ac AL |
743 | let len = expansions.len(); |
744 | if len > u32::max_value() as usize { | |
745 | panic!("too many ExpnInfo's!"); | |
746 | } | |
747 | ExpnId(len as u32 - 1) | |
223e47cc LB |
748 | } |
749 | ||
1a4d82fc JJ |
750 | pub fn with_expn_info<T, F>(&self, id: ExpnId, f: F) -> T where |
751 | F: FnOnce(Option<&ExpnInfo>) -> T, | |
752 | { | |
753 | match id { | |
9346a6ac | 754 | NO_EXPANSION | COMMAND_LINE_EXPN => f(None), |
85aaf69f | 755 | ExpnId(i) => f(Some(&(*self.expansions.borrow())[i as usize])) |
1a4d82fc | 756 | } |
223e47cc LB |
757 | } |
758 | ||
c34b1796 AL |
759 | /// Check if a span is "internal" to a macro in which #[unstable] |
760 | /// items can be used (that is, a macro marked with | |
761 | /// `#[allow_internal_unstable]`). | |
762 | pub fn span_allows_unstable(&self, span: Span) -> bool { | |
763 | debug!("span_allows_unstable(span = {:?})", span); | |
764 | let mut allows_unstable = false; | |
765 | let mut expn_id = span.expn_id; | |
766 | loop { | |
767 | let quit = self.with_expn_info(expn_id, |expninfo| { | |
768 | debug!("span_allows_unstable: expninfo = {:?}", expninfo); | |
769 | expninfo.map_or(/* hit the top level */ true, |info| { | |
770 | ||
771 | let span_comes_from_this_expansion = | |
7453a54e | 772 | info.callee.span.map_or(span.source_equal(&info.call_site), |mac_span| { |
b039eaaf | 773 | mac_span.contains(span) |
c34b1796 AL |
774 | }); |
775 | ||
c1a9b12d SL |
776 | debug!("span_allows_unstable: span: {:?} call_site: {:?} callee: {:?}", |
777 | (span.lo, span.hi), | |
778 | (info.call_site.lo, info.call_site.hi), | |
779 | info.callee.span.map(|x| (x.lo, x.hi))); | |
c34b1796 AL |
780 | debug!("span_allows_unstable: from this expansion? {}, allows unstable? {}", |
781 | span_comes_from_this_expansion, | |
782 | info.callee.allow_internal_unstable); | |
783 | if span_comes_from_this_expansion { | |
784 | allows_unstable = info.callee.allow_internal_unstable; | |
785 | // we've found the right place, stop looking | |
786 | true | |
1a4d82fc | 787 | } else { |
c34b1796 AL |
788 | // not the right place, keep looking |
789 | expn_id = info.call_site.expn_id; | |
790 | false | |
1a4d82fc | 791 | } |
c34b1796 AL |
792 | }) |
793 | }); | |
794 | if quit { | |
795 | break | |
223e47cc | 796 | } |
c34b1796 AL |
797 | } |
798 | debug!("span_allows_unstable? {}", allows_unstable); | |
799 | allows_unstable | |
223e47cc | 800 | } |
92a42be0 SL |
801 | |
802 | pub fn count_lines(&self) -> usize { | |
803 | self.files.borrow().iter().fold(0, |a, f| a + f.count_lines()) | |
804 | } | |
a7813a04 XL |
805 | |
806 | pub fn macro_backtrace(&self, span: Span) -> Vec<MacroBacktrace> { | |
807 | let mut last_span = DUMMY_SP; | |
808 | let mut span = span; | |
809 | let mut result = vec![]; | |
810 | loop { | |
811 | let span_name_span = self.with_expn_info(span.expn_id, |expn_info| { | |
812 | expn_info.map(|ei| { | |
813 | let (pre, post) = match ei.callee.format { | |
814 | MacroAttribute(..) => ("#[", "]"), | |
815 | MacroBang(..) => ("", "!"), | |
816 | }; | |
817 | let macro_decl_name = format!("{}{}{}", | |
818 | pre, | |
819 | ei.callee.name(), | |
820 | post); | |
821 | let def_site_span = ei.callee.span; | |
822 | (ei.call_site, macro_decl_name, def_site_span) | |
823 | }) | |
824 | }); | |
825 | ||
826 | match span_name_span { | |
827 | None => break, | |
828 | Some((call_site, macro_decl_name, def_site_span)) => { | |
829 | // Don't print recursive invocations | |
830 | if !call_site.source_equal(&last_span) { | |
831 | result.push(MacroBacktrace { | |
832 | call_site: call_site, | |
833 | macro_decl_name: macro_decl_name, | |
834 | def_site_span: def_site_span, | |
835 | }); | |
836 | } | |
837 | last_span = span; | |
838 | span = call_site; | |
839 | } | |
840 | } | |
841 | } | |
842 | result | |
843 | } | |
844 | } | |
845 | ||
3157f602 XL |
846 | impl CodeMapper for CodeMap { |
847 | fn lookup_char_pos(&self, pos: BytePos) -> Loc { | |
848 | self.lookup_char_pos(pos) | |
849 | } | |
850 | fn span_to_lines(&self, sp: Span) -> FileLinesResult { | |
851 | self.span_to_lines(sp) | |
852 | } | |
853 | fn span_to_string(&self, sp: Span) -> String { | |
854 | self.span_to_string(sp) | |
855 | } | |
856 | fn span_to_filename(&self, sp: Span) -> FileName { | |
857 | self.span_to_filename(sp) | |
858 | } | |
859 | fn macro_backtrace(&self, span: Span) -> Vec<MacroBacktrace> { | |
860 | self.macro_backtrace(span) | |
861 | } | |
9e0c209e SL |
862 | fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> { |
863 | self.merge_spans(sp_lhs, sp_rhs) | |
864 | } | |
85aaf69f SL |
865 | } |
866 | ||
c34b1796 AL |
867 | // _____________________________________________________________________________ |
868 | // Tests | |
869 | // | |
870 | ||
223e47cc | 871 | #[cfg(test)] |
d9579d0f | 872 | mod tests { |
223e47cc | 873 | use super::*; |
3157f602 | 874 | use std::rc::Rc; |
223e47cc LB |
875 | |
876 | #[test] | |
877 | fn t1 () { | |
878 | let cm = CodeMap::new(); | |
1a4d82fc | 879 | let fm = cm.new_filemap("blork.rs".to_string(), |
3157f602 | 880 | None, |
1a4d82fc | 881 | "first line.\nsecond line".to_string()); |
223e47cc | 882 | fm.next_line(BytePos(0)); |
c1a9b12d | 883 | // Test we can get lines with partial line info. |
9346a6ac | 884 | assert_eq!(fm.get_line(0), Some("first line.")); |
c1a9b12d | 885 | // TESTING BROKEN BEHAVIOR: line break declared before actual line break. |
223e47cc | 886 | fm.next_line(BytePos(10)); |
9346a6ac | 887 | assert_eq!(fm.get_line(1), Some(".")); |
c1a9b12d SL |
888 | fm.next_line(BytePos(12)); |
889 | assert_eq!(fm.get_line(2), Some("second line")); | |
223e47cc LB |
890 | } |
891 | ||
892 | #[test] | |
c34b1796 | 893 | #[should_panic] |
223e47cc LB |
894 | fn t2 () { |
895 | let cm = CodeMap::new(); | |
1a4d82fc | 896 | let fm = cm.new_filemap("blork.rs".to_string(), |
3157f602 | 897 | None, |
1a4d82fc | 898 | "first line.\nsecond line".to_string()); |
223e47cc LB |
899 | // TESTING *REALLY* BROKEN BEHAVIOR: |
900 | fm.next_line(BytePos(0)); | |
901 | fm.next_line(BytePos(10)); | |
902 | fm.next_line(BytePos(2)); | |
903 | } | |
1a4d82fc JJ |
904 | |
905 | fn init_code_map() -> CodeMap { | |
906 | let cm = CodeMap::new(); | |
907 | let fm1 = cm.new_filemap("blork.rs".to_string(), | |
3157f602 | 908 | None, |
1a4d82fc JJ |
909 | "first line.\nsecond line".to_string()); |
910 | let fm2 = cm.new_filemap("empty.rs".to_string(), | |
3157f602 | 911 | None, |
1a4d82fc JJ |
912 | "".to_string()); |
913 | let fm3 = cm.new_filemap("blork2.rs".to_string(), | |
3157f602 | 914 | None, |
1a4d82fc JJ |
915 | "first line.\nsecond line".to_string()); |
916 | ||
917 | fm1.next_line(BytePos(0)); | |
918 | fm1.next_line(BytePos(12)); | |
c1a9b12d SL |
919 | fm2.next_line(fm2.start_pos); |
920 | fm3.next_line(fm3.start_pos); | |
921 | fm3.next_line(fm3.start_pos + BytePos(12)); | |
1a4d82fc JJ |
922 | |
923 | cm | |
924 | } | |
925 | ||
926 | #[test] | |
927 | fn t3() { | |
928 | // Test lookup_byte_offset | |
929 | let cm = init_code_map(); | |
930 | ||
c1a9b12d | 931 | let fmabp1 = cm.lookup_byte_offset(BytePos(23)); |
1a4d82fc | 932 | assert_eq!(fmabp1.fm.name, "blork.rs"); |
c1a9b12d SL |
933 | assert_eq!(fmabp1.pos, BytePos(23)); |
934 | ||
935 | let fmabp1 = cm.lookup_byte_offset(BytePos(24)); | |
936 | assert_eq!(fmabp1.fm.name, "empty.rs"); | |
937 | assert_eq!(fmabp1.pos, BytePos(0)); | |
1a4d82fc | 938 | |
c1a9b12d | 939 | let fmabp2 = cm.lookup_byte_offset(BytePos(25)); |
1a4d82fc JJ |
940 | assert_eq!(fmabp2.fm.name, "blork2.rs"); |
941 | assert_eq!(fmabp2.pos, BytePos(0)); | |
942 | } | |
943 | ||
944 | #[test] | |
945 | fn t4() { | |
946 | // Test bytepos_to_file_charpos | |
947 | let cm = init_code_map(); | |
948 | ||
949 | let cp1 = cm.bytepos_to_file_charpos(BytePos(22)); | |
950 | assert_eq!(cp1, CharPos(22)); | |
951 | ||
c1a9b12d | 952 | let cp2 = cm.bytepos_to_file_charpos(BytePos(25)); |
1a4d82fc JJ |
953 | assert_eq!(cp2, CharPos(0)); |
954 | } | |
955 | ||
956 | #[test] | |
957 | fn t5() { | |
958 | // Test zero-length filemaps. | |
959 | let cm = init_code_map(); | |
960 | ||
961 | let loc1 = cm.lookup_char_pos(BytePos(22)); | |
962 | assert_eq!(loc1.file.name, "blork.rs"); | |
963 | assert_eq!(loc1.line, 2); | |
964 | assert_eq!(loc1.col, CharPos(10)); | |
965 | ||
c1a9b12d | 966 | let loc2 = cm.lookup_char_pos(BytePos(25)); |
1a4d82fc JJ |
967 | assert_eq!(loc2.file.name, "blork2.rs"); |
968 | assert_eq!(loc2.line, 1); | |
969 | assert_eq!(loc2.col, CharPos(0)); | |
970 | } | |
971 | ||
972 | fn init_code_map_mbc() -> CodeMap { | |
973 | let cm = CodeMap::new(); | |
974 | // € is a three byte utf8 char. | |
975 | let fm1 = | |
976 | cm.new_filemap("blork.rs".to_string(), | |
3157f602 | 977 | None, |
1a4d82fc JJ |
978 | "fir€st €€€€ line.\nsecond line".to_string()); |
979 | let fm2 = cm.new_filemap("blork2.rs".to_string(), | |
3157f602 | 980 | None, |
1a4d82fc JJ |
981 | "first line€€.\n€ second line".to_string()); |
982 | ||
983 | fm1.next_line(BytePos(0)); | |
c1a9b12d SL |
984 | fm1.next_line(BytePos(28)); |
985 | fm2.next_line(fm2.start_pos); | |
986 | fm2.next_line(fm2.start_pos + BytePos(20)); | |
1a4d82fc JJ |
987 | |
988 | fm1.record_multibyte_char(BytePos(3), 3); | |
989 | fm1.record_multibyte_char(BytePos(9), 3); | |
990 | fm1.record_multibyte_char(BytePos(12), 3); | |
991 | fm1.record_multibyte_char(BytePos(15), 3); | |
992 | fm1.record_multibyte_char(BytePos(18), 3); | |
c1a9b12d SL |
993 | fm2.record_multibyte_char(fm2.start_pos + BytePos(10), 3); |
994 | fm2.record_multibyte_char(fm2.start_pos + BytePos(13), 3); | |
995 | fm2.record_multibyte_char(fm2.start_pos + BytePos(18), 3); | |
1a4d82fc JJ |
996 | |
997 | cm | |
998 | } | |
999 | ||
1000 | #[test] | |
1001 | fn t6() { | |
1002 | // Test bytepos_to_file_charpos in the presence of multi-byte chars | |
1003 | let cm = init_code_map_mbc(); | |
1004 | ||
1005 | let cp1 = cm.bytepos_to_file_charpos(BytePos(3)); | |
1006 | assert_eq!(cp1, CharPos(3)); | |
1007 | ||
1008 | let cp2 = cm.bytepos_to_file_charpos(BytePos(6)); | |
1009 | assert_eq!(cp2, CharPos(4)); | |
1010 | ||
1011 | let cp3 = cm.bytepos_to_file_charpos(BytePos(56)); | |
1012 | assert_eq!(cp3, CharPos(12)); | |
1013 | ||
1014 | let cp4 = cm.bytepos_to_file_charpos(BytePos(61)); | |
1015 | assert_eq!(cp4, CharPos(15)); | |
1016 | } | |
1017 | ||
1018 | #[test] | |
1019 | fn t7() { | |
1020 | // Test span_to_lines for a span ending at the end of filemap | |
1021 | let cm = init_code_map(); | |
1022 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
d9579d0f | 1023 | let file_lines = cm.span_to_lines(span).unwrap(); |
1a4d82fc JJ |
1024 | |
1025 | assert_eq!(file_lines.file.name, "blork.rs"); | |
1026 | assert_eq!(file_lines.lines.len(), 1); | |
9346a6ac AL |
1027 | assert_eq!(file_lines.lines[0].line_index, 1); |
1028 | } | |
1029 | ||
a7813a04 | 1030 | /// Given a string like " ~~~~~~~~~~~~ ", produces a span |
9346a6ac AL |
1031 | /// coverting that range. The idea is that the string has the same |
1032 | /// length as the input, and we uncover the byte positions. Note | |
1033 | /// that this can span lines and so on. | |
1034 | fn span_from_selection(input: &str, selection: &str) -> Span { | |
1035 | assert_eq!(input.len(), selection.len()); | |
a7813a04 | 1036 | let left_index = selection.find('~').unwrap() as u32; |
7453a54e | 1037 | let right_index = selection.rfind('~').map(|x|x as u32).unwrap_or(left_index); |
9346a6ac AL |
1038 | Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION } |
1039 | } | |
1040 | ||
9346a6ac AL |
1041 | /// Test span_to_snippet and span_to_lines for a span coverting 3 |
1042 | /// lines in the middle of a file. | |
1043 | #[test] | |
1044 | fn span_to_snippet_and_lines_spanning_multiple_lines() { | |
1045 | let cm = CodeMap::new(); | |
1046 | let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n"; | |
a7813a04 | 1047 | let selection = " \n ~~\n~~~\n~~~~~ \n \n"; |
3157f602 | 1048 | cm.new_filemap_and_lines("blork.rs", None, inputtext); |
9346a6ac AL |
1049 | let span = span_from_selection(inputtext, selection); |
1050 | ||
1051 | // check that we are extracting the text we thought we were extracting | |
1052 | assert_eq!(&cm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD"); | |
1053 | ||
1054 | // check that span_to_lines gives us the complete result with the lines/cols we expected | |
d9579d0f | 1055 | let lines = cm.span_to_lines(span).unwrap(); |
9346a6ac AL |
1056 | let expected = vec![ |
1057 | LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) }, | |
1058 | LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) }, | |
1059 | LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) } | |
1060 | ]; | |
1061 | assert_eq!(lines.lines, expected); | |
1a4d82fc JJ |
1062 | } |
1063 | ||
1064 | #[test] | |
1065 | fn t8() { | |
1066 | // Test span_to_snippet for a span ending at the end of filemap | |
1067 | let cm = init_code_map(); | |
1068 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
1069 | let snippet = cm.span_to_snippet(span); | |
1070 | ||
85aaf69f | 1071 | assert_eq!(snippet, Ok("second line".to_string())); |
1a4d82fc JJ |
1072 | } |
1073 | ||
1074 | #[test] | |
1075 | fn t9() { | |
1076 | // Test span_to_str for a span ending at the end of filemap | |
1077 | let cm = init_code_map(); | |
1078 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
1079 | let sstr = cm.span_to_string(span); | |
1080 | ||
1081 | assert_eq!(sstr, "blork.rs:2:1: 2:12"); | |
1082 | } | |
92a42be0 SL |
1083 | |
1084 | #[test] | |
1085 | fn t10() { | |
1086 | // Test span_to_expanded_string works in base case (no expansion) | |
1087 | let cm = init_code_map(); | |
1088 | let span = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION }; | |
1089 | let sstr = cm.span_to_expanded_string(span); | |
1090 | assert_eq!(sstr, "blork.rs:1:1: 1:12\n`first line.`\n"); | |
1091 | ||
1092 | let span = Span { lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION }; | |
1093 | let sstr = cm.span_to_expanded_string(span); | |
1094 | assert_eq!(sstr, "blork.rs:2:1: 2:12\n`second line`\n"); | |
1095 | } | |
1096 | ||
1097 | #[test] | |
1098 | fn t11() { | |
1099 | // Test span_to_expanded_string works with expansion | |
1100 | use ast::Name; | |
1101 | let cm = init_code_map(); | |
1102 | let root = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION }; | |
1103 | let format = ExpnFormat::MacroBang(Name(0u32)); | |
1104 | let callee = NameAndSpan { format: format, | |
1105 | allow_internal_unstable: false, | |
1106 | span: None }; | |
1107 | ||
1108 | let info = ExpnInfo { call_site: root, callee: callee }; | |
1109 | let id = cm.record_expansion(info); | |
1110 | let sp = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id }; | |
1111 | ||
1112 | let sstr = cm.span_to_expanded_string(sp); | |
1113 | assert_eq!(sstr, | |
1114 | "blork.rs:2:1: 2:12\n`second line`\n Callsite:\n \ | |
1115 | blork.rs:1:1: 1:12\n `first line.`\n"); | |
1116 | } | |
1117 | ||
9e0c209e SL |
1118 | /// Test merging two spans on the same line |
1119 | #[test] | |
1120 | fn span_merging() { | |
1121 | let cm = CodeMap::new(); | |
1122 | let inputtext = "bbbb BB bb CCC\n"; | |
1123 | let selection1 = " ~~ \n"; | |
1124 | let selection2 = " ~~~\n"; | |
1125 | cm.new_filemap_and_lines("blork.rs", None, inputtext); | |
1126 | let span1 = span_from_selection(inputtext, selection1); | |
1127 | let span2 = span_from_selection(inputtext, selection2); | |
1128 | ||
1129 | if let Some(sp) = cm.merge_spans(span1, span2) { | |
1130 | let sstr = cm.span_to_expanded_string(sp); | |
1131 | assert_eq!(sstr, "blork.rs:1:6: 1:15\n`BB bb CCC`\n"); | |
1132 | } | |
1133 | else { | |
1134 | assert!(false); | |
1135 | } | |
1136 | } | |
1137 | ||
1138 | /// Test failing to merge two spans on different lines | |
1139 | #[test] | |
1140 | fn span_merging_fail() { | |
1141 | let cm = CodeMap::new(); | |
1142 | let inputtext = "bbbb BB\ncc CCC\n"; | |
1143 | let selection1 = " ~~\n \n"; | |
1144 | let selection2 = " \n ~~~\n"; | |
1145 | cm.new_filemap_and_lines("blork.rs", None, inputtext); | |
1146 | let span1 = span_from_selection(inputtext, selection1); | |
1147 | let span2 = span_from_selection(inputtext, selection2); | |
1148 | ||
1149 | assert!(cm.merge_spans(span1, span2).is_none()); | |
1150 | } | |
1151 | ||
3157f602 XL |
1152 | /// Returns the span corresponding to the `n`th occurrence of |
1153 | /// `substring` in `source_text`. | |
1154 | trait CodeMapExtension { | |
1155 | fn span_substr(&self, | |
1156 | file: &Rc<FileMap>, | |
1157 | source_text: &str, | |
1158 | substring: &str, | |
1159 | n: usize) | |
1160 | -> Span; | |
1161 | } | |
1162 | ||
1163 | impl CodeMapExtension for CodeMap { | |
1164 | fn span_substr(&self, | |
1165 | file: &Rc<FileMap>, | |
1166 | source_text: &str, | |
1167 | substring: &str, | |
1168 | n: usize) | |
1169 | -> Span | |
1170 | { | |
1171 | println!("span_substr(file={:?}/{:?}, substring={:?}, n={})", | |
1172 | file.name, file.start_pos, substring, n); | |
1173 | let mut i = 0; | |
1174 | let mut hi = 0; | |
1175 | loop { | |
1176 | let offset = source_text[hi..].find(substring).unwrap_or_else(|| { | |
1177 | panic!("source_text `{}` does not have {} occurrences of `{}`, only {}", | |
1178 | source_text, n, substring, i); | |
1179 | }); | |
1180 | let lo = hi + offset; | |
1181 | hi = lo + substring.len(); | |
1182 | if i == n { | |
1183 | let span = Span { | |
1184 | lo: BytePos(lo as u32 + file.start_pos.0), | |
1185 | hi: BytePos(hi as u32 + file.start_pos.0), | |
1186 | expn_id: NO_EXPANSION, | |
1187 | }; | |
1188 | assert_eq!(&self.span_to_snippet(span).unwrap()[..], | |
1189 | substring); | |
1190 | return span; | |
1191 | } | |
1192 | i += 1; | |
1193 | } | |
1194 | } | |
1195 | } | |
1196 | ||
92a42be0 SL |
1197 | fn init_expansion_chain(cm: &CodeMap) -> Span { |
1198 | // Creates an expansion chain containing two recursive calls | |
1199 | // root -> expA -> expA -> expB -> expB -> end | |
1200 | use ast::Name; | |
1201 | ||
1202 | let root = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION }; | |
1203 | ||
1204 | let format_root = ExpnFormat::MacroBang(Name(0u32)); | |
1205 | let callee_root = NameAndSpan { format: format_root, | |
1206 | allow_internal_unstable: false, | |
1207 | span: Some(root) }; | |
1208 | ||
1209 | let info_a1 = ExpnInfo { call_site: root, callee: callee_root }; | |
1210 | let id_a1 = cm.record_expansion(info_a1); | |
1211 | let span_a1 = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a1 }; | |
1212 | ||
1213 | let format_a = ExpnFormat::MacroBang(Name(1u32)); | |
1214 | let callee_a = NameAndSpan { format: format_a, | |
1215 | allow_internal_unstable: false, | |
1216 | span: Some(span_a1) }; | |
1217 | ||
1218 | let info_a2 = ExpnInfo { call_site: span_a1, callee: callee_a.clone() }; | |
1219 | let id_a2 = cm.record_expansion(info_a2); | |
1220 | let span_a2 = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a2 }; | |
1221 | ||
1222 | let info_b1 = ExpnInfo { call_site: span_a2, callee: callee_a }; | |
1223 | let id_b1 = cm.record_expansion(info_b1); | |
1224 | let span_b1 = Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b1 }; | |
1225 | ||
1226 | let format_b = ExpnFormat::MacroBang(Name(2u32)); | |
1227 | let callee_b = NameAndSpan { format: format_b, | |
1228 | allow_internal_unstable: false, | |
1229 | span: None }; | |
1230 | ||
1231 | let info_b2 = ExpnInfo { call_site: span_b1, callee: callee_b.clone() }; | |
1232 | let id_b2 = cm.record_expansion(info_b2); | |
1233 | let span_b2 = Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b2 }; | |
1234 | ||
1235 | let info_end = ExpnInfo { call_site: span_b2, callee: callee_b }; | |
1236 | let id_end = cm.record_expansion(info_end); | |
1237 | Span { lo: BytePos(37), hi: BytePos(48), expn_id: id_end } | |
1238 | } | |
1239 | ||
1240 | #[test] | |
1241 | fn t12() { | |
1242 | // Test span_to_expanded_string collapses recursive macros and handles | |
1243 | // recursive callsite and callee expansions | |
1244 | let cm = init_code_map(); | |
1245 | let end = init_expansion_chain(&cm); | |
1246 | let sstr = cm.span_to_expanded_string(end); | |
1247 | let res_str = | |
1248 | r"blork2.rs:2:1: 2:12 | |
1249 | `second line` | |
1250 | Callsite: | |
1251 | ... | |
1252 | blork2.rs:1:1: 1:12 | |
1253 | `first line.` | |
1254 | Callee: | |
1255 | blork.rs:2:1: 2:12 | |
1256 | `second line` | |
1257 | Callee: | |
1258 | blork.rs:1:1: 1:12 | |
1259 | `first line.` | |
1260 | Callsite: | |
1261 | blork.rs:1:1: 1:12 | |
1262 | `first line.` | |
1263 | Callsite: | |
1264 | ... | |
1265 | blork.rs:2:1: 2:12 | |
1266 | `second line` | |
1267 | Callee: | |
1268 | blork.rs:1:1: 1:12 | |
1269 | `first line.` | |
1270 | Callsite: | |
1271 | blork.rs:1:1: 1:12 | |
1272 | `first line.` | |
1273 | "; | |
1274 | assert_eq!(sstr, res_str); | |
1275 | } | |
223e47cc | 1276 | } |