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.
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.
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.
20 pub use self::ExpnFormat::*;
22 use std::cell::RefCell;
23 use std::path::{Path,PathBuf};
28 use std::io::{self, Read};
29 pub use syntax_pos::*;
30 use errors::CodeMapper;
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) {
42 (Some(call_site1), Some(call_site2)) if call_site1 == call_site2 => sp,
43 (Some(call_site1), _) => original_sp(cm, call_site1, enclosing_sp),
47 /// The source of expansion.
48 #[derive(Clone, Hash, Debug, PartialEq, Eq)]
50 /// e.g. #[derive(...)] <item>
56 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
57 pub struct Spanned<T> {
62 pub fn spanned<T>(lo: BytePos, hi: BytePos, t: T) -> Spanned<T> {
63 respan(mk_sp(lo, hi), t)
66 pub fn respan<T>(sp: Span, t: T) -> Spanned<T> {
67 Spanned {node: t, span: sp}
70 pub fn dummy_spanned<T>(t: T) -> Spanned<T> {
74 #[derive(Clone, Hash, Debug)]
75 pub struct NameAndSpan {
76 /// The format with which the macro was invoked.
77 pub format: ExpnFormat,
78 /// Whether the macro is allowed to use #[unstable]/feature-gated
79 /// features internally without forcing the whole crate to opt-in
81 pub allow_internal_unstable: bool,
82 /// The span of the macro definition itself. The macro may not
83 /// have a sensible definition span (e.g. something defined
84 /// completely inside libsyntax) in which case this is None.
85 pub span: Option<Span>
89 pub fn name(&self) -> Name {
91 ExpnFormat::MacroAttribute(s) => s,
92 ExpnFormat::MacroBang(s) => s,
97 /// Extra information for tracking spans of macro and syntax sugar expansion
98 #[derive(Hash, Debug)]
100 /// The location of the actual macro invocation or syntax sugar , e.g.
101 /// `let x = foo!();` or `if let Some(y) = x {}`
103 /// This may recursively refer to other macro invocations, e.g. if
104 /// `foo!()` invoked `bar!()` internally, and there was an
105 /// expression inside `bar!`; the call_site of the expression in
106 /// the expansion would point to the `bar!` invocation; that
107 /// call_site span would have its own ExpnInfo, with the call_site
108 /// pointing to the `foo!` invocation.
110 /// Information about the expansion.
111 pub callee: NameAndSpan
114 // _____________________________________________________________________________
115 // FileMap, MultiByteChar, FileName, FileLines
118 /// An abstraction over the fs operations used by the Parser.
119 pub trait FileLoader {
120 /// Query the existence of a file.
121 fn file_exists(&self, path: &Path) -> bool;
123 /// Return an absolute path to a file, if possible.
124 fn abs_path(&self, path: &Path) -> Option<PathBuf>;
126 /// Read the contents of an UTF-8 file into memory.
127 fn read_file(&self, path: &Path) -> io::Result<String>;
130 /// A FileLoader that uses std::fs to load real files.
131 pub struct RealFileLoader;
133 impl FileLoader for RealFileLoader {
134 fn file_exists(&self, path: &Path) -> bool {
135 fs::metadata(path).is_ok()
138 fn abs_path(&self, path: &Path) -> Option<PathBuf> {
139 if path.is_absolute() {
140 Some(path.to_path_buf())
144 .map(|cwd| cwd.join(path))
148 fn read_file(&self, path: &Path) -> io::Result<String> {
149 let mut src = String::new();
150 fs::File::open(path)?.read_to_string(&mut src)?;
155 // _____________________________________________________________________________
160 pub files: RefCell<Vec<Rc<FileMap>>>,
161 expansions: RefCell<Vec<ExpnInfo>>,
162 file_loader: Box<FileLoader>
166 pub fn new() -> CodeMap {
168 files: RefCell::new(Vec::new()),
169 expansions: RefCell::new(Vec::new()),
170 file_loader: Box::new(RealFileLoader)
174 pub fn with_file_loader(file_loader: Box<FileLoader>) -> CodeMap {
176 files: RefCell::new(Vec::new()),
177 expansions: RefCell::new(Vec::new()),
178 file_loader: file_loader
182 pub fn file_exists(&self, path: &Path) -> bool {
183 self.file_loader.file_exists(path)
186 pub fn load_file(&self, path: &Path) -> io::Result<Rc<FileMap>> {
187 let src = self.file_loader.read_file(path)?;
188 let abs_path = self.file_loader.abs_path(path).map(|p| p.to_str().unwrap().to_string());
189 Ok(self.new_filemap(path.to_str().unwrap().to_string(), abs_path, src))
192 fn next_start_pos(&self) -> usize {
193 let files = self.files.borrow();
196 // Add one so there is some space between files. This lets us distinguish
197 // positions in the codemap, even in the presence of zero-length files.
198 Some(last) => last.end_pos.to_usize() + 1,
202 /// Creates a new filemap without setting its line information. If you don't
203 /// intend to set the line information yourself, you should use new_filemap_and_lines.
204 pub fn new_filemap(&self, filename: FileName, abs_path: Option<FileName>,
205 mut src: String) -> Rc<FileMap> {
206 let start_pos = self.next_start_pos();
207 let mut files = self.files.borrow_mut();
209 // Remove utf-8 BOM if any.
210 if src.starts_with("\u{feff}") {
214 let end_pos = start_pos + src.len();
216 let filemap = Rc::new(FileMap {
219 src: Some(Rc::new(src)),
220 start_pos: Pos::from_usize(start_pos),
221 end_pos: Pos::from_usize(end_pos),
222 lines: RefCell::new(Vec::new()),
223 multibyte_chars: RefCell::new(Vec::new()),
226 files.push(filemap.clone());
231 /// Creates a new filemap and sets its line information.
232 pub fn new_filemap_and_lines(&self, filename: &str, abs_path: Option<&str>,
233 src: &str) -> Rc<FileMap> {
234 let fm = self.new_filemap(filename.to_string(),
235 abs_path.map(|s| s.to_owned()),
237 let mut byte_pos: u32 = fm.start_pos.0;
238 for line in src.lines() {
239 // register the start of this line
240 fm.next_line(BytePos(byte_pos));
242 // update byte_pos to include this line and the \n at the end
243 byte_pos += line.len() as u32 + 1;
249 /// Allocates a new FileMap representing a source file from an external
250 /// crate. The source code of such an "imported filemap" is not available,
251 /// but we still know enough to generate accurate debuginfo location
252 /// information for things inlined from other crates.
253 pub fn new_imported_filemap(&self,
255 abs_path: Option<FileName>,
257 mut file_local_lines: Vec<BytePos>,
258 mut file_local_multibyte_chars: Vec<MultiByteChar>)
260 let start_pos = self.next_start_pos();
261 let mut files = self.files.borrow_mut();
263 let end_pos = Pos::from_usize(start_pos + source_len);
264 let start_pos = Pos::from_usize(start_pos);
266 for pos in &mut file_local_lines {
267 *pos = *pos + start_pos;
270 for mbc in &mut file_local_multibyte_chars {
271 mbc.pos = mbc.pos + start_pos;
274 let filemap = Rc::new(FileMap {
278 start_pos: start_pos,
280 lines: RefCell::new(file_local_lines),
281 multibyte_chars: RefCell::new(file_local_multibyte_chars),
284 files.push(filemap.clone());
289 pub fn mk_substr_filename(&self, sp: Span) -> String {
290 let pos = self.lookup_char_pos(sp.lo);
291 (format!("<{}:{}:{}>",
294 pos.col.to_usize() + 1)).to_string()
297 /// Lookup source information about a BytePos
298 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
299 let chpos = self.bytepos_to_file_charpos(pos);
300 match self.lookup_line(pos) {
301 Ok(FileMapAndLine { fm: f, line: a }) => {
302 let line = a + 1; // Line numbers start at 1
303 let linebpos = (*f.lines.borrow())[a];
304 let linechpos = self.bytepos_to_file_charpos(linebpos);
305 debug!("byte pos {:?} is on the line at byte pos {:?}",
307 debug!("char pos {:?} is on the line at char pos {:?}",
309 debug!("byte is on line: {}", line);
310 assert!(chpos >= linechpos);
314 col: chpos - linechpos,
327 // If the relevant filemap is empty, we don't return a line number.
328 fn lookup_line(&self, pos: BytePos) -> Result<FileMapAndLine, Rc<FileMap>> {
329 let idx = self.lookup_filemap_idx(pos);
331 let files = self.files.borrow();
332 let f = (*files)[idx].clone();
334 let len = f.lines.borrow().len();
341 let lines = f.lines.borrow();
342 let mut b = lines.len();
345 if (*lines)[m] > pos {
351 assert!(a <= lines.len());
353 Ok(FileMapAndLine { fm: f, line: a })
356 pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
357 let loc = self.lookup_char_pos(pos);
359 filename: loc.file.name.to_string(),
366 pub fn span_to_string(&self, sp: Span) -> String {
367 if sp == COMMAND_LINE_SP {
368 return "<command line option>".to_string();
371 if self.files.borrow().is_empty() && sp.source_equal(&DUMMY_SP) {
372 return "no-location".to_string();
375 let lo = self.lookup_char_pos_adj(sp.lo);
376 let hi = self.lookup_char_pos_adj(sp.hi);
377 return (format!("{}:{}:{}: {}:{}",
380 lo.col.to_usize() + 1,
382 hi.col.to_usize() + 1)).to_string()
385 // Returns true if two spans have the same callee
386 // (Assumes the same ExpnFormat implies same callee)
387 fn match_callees(&self, sp_a: &Span, sp_b: &Span) -> bool {
389 .with_expn_info(sp_a.expn_id,
390 |ei| ei.map(|ei| ei.callee.format.clone()));
393 .with_expn_info(sp_b.expn_id,
394 |ei| ei.map(|ei| ei.callee.format.clone()));
398 /// Returns a formatted string showing the expansion chain of a span
400 /// Spans are printed in the following format:
402 /// filename:start_line:col: end_line:col
409 /// Callees and callsites are printed recursively (if available, otherwise header
410 /// and span is omitted), expanding into their own callee/callsite spans.
411 /// Each layer of recursion has an increased indent, and snippets are truncated
412 /// to at most 50 characters. Finally, recursive calls to the same macro are squashed,
413 /// with '...' used to represent any number of recursive calls.
414 pub fn span_to_expanded_string(&self, sp: Span) -> String {
415 self.span_to_expanded_string_internal(sp, "")
418 fn span_to_expanded_string_internal(&self, sp:Span, indent: &str) -> String {
419 let mut indent = indent.to_owned();
420 let mut output = "".to_owned();
421 let span_str = self.span_to_string(sp);
422 let mut span_snip = self.span_to_snippet(sp)
423 .unwrap_or("Snippet unavailable".to_owned());
425 // Truncate by code points - in worst case this will be more than 50 characters,
426 // but ensures at least 50 characters and respects byte boundaries.
427 let char_vec: Vec<(usize, char)> = span_snip.char_indices().collect();
428 if char_vec.len() > 50 {
429 span_snip.truncate(char_vec[49].0);
430 span_snip.push_str("...");
433 output.push_str(&format!("{}{}\n{}`{}`\n", indent, span_str, indent, span_snip));
435 if sp.expn_id == NO_EXPANSION || sp.expn_id == COMMAND_LINE_EXPN {
439 let mut callee = self.with_expn_info(sp.expn_id,
440 |ei| ei.and_then(|ei| ei.callee.span.clone()));
441 let mut callsite = self.with_expn_info(sp.expn_id,
442 |ei| ei.map(|ei| ei.call_site.clone()));
444 indent.push_str(" ");
445 let mut is_recursive = false;
447 while callee.is_some() && self.match_callees(&sp, &callee.unwrap()) {
448 callee = self.with_expn_info(callee.unwrap().expn_id,
449 |ei| ei.and_then(|ei| ei.callee.span.clone()));
452 if let Some(span) = callee {
453 output.push_str(&indent);
454 output.push_str("Callee:\n");
456 output.push_str(&indent);
457 output.push_str("...\n");
459 output.push_str(&(self.span_to_expanded_string_internal(span, &indent)));
462 is_recursive = false;
463 while callsite.is_some() && self.match_callees(&sp, &callsite.unwrap()) {
464 callsite = self.with_expn_info(callsite.unwrap().expn_id,
465 |ei| ei.map(|ei| ei.call_site.clone()));
468 if let Some(span) = callsite {
469 output.push_str(&indent);
470 output.push_str("Callsite:\n");
472 output.push_str(&indent);
473 output.push_str("...\n");
475 output.push_str(&(self.span_to_expanded_string_internal(span, &indent)));
480 /// Return the source span - this is either the supplied span, or the span for
481 /// the macro callsite that expanded to it.
482 pub fn source_callsite(&self, sp: Span) -> Span {
484 // Special case - if a macro is parsed as an argument to another macro, the source
485 // callsite is the first callsite, which is also source-equivalent to the span.
486 let mut first = true;
487 while span.expn_id != NO_EXPANSION && span.expn_id != COMMAND_LINE_EXPN {
488 if let Some(callsite) = self.with_expn_info(span.expn_id,
489 |ei| ei.map(|ei| ei.call_site.clone())) {
490 if first && span.source_equal(&callsite) {
491 if self.lookup_char_pos(span.lo).file.is_real_file() {
492 return Span { expn_id: NO_EXPANSION, .. span };
505 /// Return the source callee.
507 /// Returns None if the supplied span has no expansion trace,
508 /// else returns the NameAndSpan for the macro definition
509 /// corresponding to the source callsite.
510 pub fn source_callee(&self, sp: Span) -> Option<NameAndSpan> {
512 // Special case - if a macro is parsed as an argument to another macro, the source
513 // callsite is source-equivalent to the span, and the source callee is the first callee.
514 let mut first = true;
515 while let Some(callsite) = self.with_expn_info(span.expn_id,
516 |ei| ei.map(|ei| ei.call_site.clone())) {
517 if first && span.source_equal(&callsite) {
518 if self.lookup_char_pos(span.lo).file.is_real_file() {
519 return self.with_expn_info(span.expn_id,
520 |ei| ei.map(|ei| ei.callee.clone()));
524 if let Some(_) = self.with_expn_info(callsite.expn_id,
525 |ei| ei.map(|ei| ei.call_site.clone())) {
529 return self.with_expn_info(span.expn_id,
530 |ei| ei.map(|ei| ei.callee.clone()));
536 pub fn span_to_filename(&self, sp: Span) -> FileName {
537 self.lookup_char_pos(sp.lo).file.name.to_string()
540 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
541 debug!("span_to_lines(sp={:?})", sp);
544 return Err(SpanLinesError::IllFormedSpan(sp));
547 let lo = self.lookup_char_pos(sp.lo);
548 debug!("span_to_lines: lo={:?}", lo);
549 let hi = self.lookup_char_pos(sp.hi);
550 debug!("span_to_lines: hi={:?}", hi);
552 if lo.file.start_pos != hi.file.start_pos {
553 return Err(SpanLinesError::DistinctSources(DistinctSources {
554 begin: (lo.file.name.clone(), lo.file.start_pos),
555 end: (hi.file.name.clone(), hi.file.start_pos),
558 assert!(hi.line >= lo.line);
560 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
562 // The span starts partway through the first line,
563 // but after that it starts from offset 0.
564 let mut start_col = lo.col;
566 // For every line but the last, it extends from `start_col`
567 // and to the end of the line. Be careful because the line
568 // numbers in Loc are 1-based, so we subtract 1 to get 0-based
570 for line_index in lo.line-1 .. hi.line-1 {
571 let line_len = lo.file.get_line(line_index)
572 .map(|s| s.chars().count())
574 lines.push(LineInfo { line_index: line_index,
575 start_col: start_col,
576 end_col: CharPos::from_usize(line_len) });
577 start_col = CharPos::from_usize(0);
580 // For the last line, it extends from `start_col` to `hi.col`:
581 lines.push(LineInfo { line_index: hi.line - 1,
582 start_col: start_col,
585 Ok(FileLines {file: lo.file, lines: lines})
588 pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> {
590 return Err(SpanSnippetError::IllFormedSpan(sp));
593 let local_begin = self.lookup_byte_offset(sp.lo);
594 let local_end = self.lookup_byte_offset(sp.hi);
596 if local_begin.fm.start_pos != local_end.fm.start_pos {
597 return Err(SpanSnippetError::DistinctSources(DistinctSources {
598 begin: (local_begin.fm.name.clone(),
599 local_begin.fm.start_pos),
600 end: (local_end.fm.name.clone(),
601 local_end.fm.start_pos)
604 match local_begin.fm.src {
606 let start_index = local_begin.pos.to_usize();
607 let end_index = local_end.pos.to_usize();
608 let source_len = (local_begin.fm.end_pos -
609 local_begin.fm.start_pos).to_usize();
611 if start_index > end_index || end_index > source_len {
612 return Err(SpanSnippetError::MalformedForCodemap(
613 MalformedCodemapPositions {
614 name: local_begin.fm.name.clone(),
615 source_len: source_len,
616 begin_pos: local_begin.pos,
617 end_pos: local_end.pos,
621 return Ok((&src[start_index..end_index]).to_string())
624 return Err(SpanSnippetError::SourceNotAvailable {
625 filename: local_begin.fm.name.clone()
632 pub fn get_filemap(&self, filename: &str) -> Option<Rc<FileMap>> {
633 for fm in self.files.borrow().iter() {
634 if filename == fm.name {
635 return Some(fm.clone());
641 /// For a global BytePos compute the local offset within the containing FileMap
642 pub fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos {
643 let idx = self.lookup_filemap_idx(bpos);
644 let fm = (*self.files.borrow())[idx].clone();
645 let offset = bpos - fm.start_pos;
646 FileMapAndBytePos {fm: fm, pos: offset}
649 /// Converts an absolute BytePos to a CharPos relative to the filemap.
650 pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
651 let idx = self.lookup_filemap_idx(bpos);
652 let files = self.files.borrow();
653 let map = &(*files)[idx];
655 // The number of extra bytes due to multibyte chars in the FileMap
656 let mut total_extra_bytes = 0;
658 for mbc in map.multibyte_chars.borrow().iter() {
659 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
661 // every character is at least one byte, so we only
662 // count the actual extra bytes.
663 total_extra_bytes += mbc.bytes - 1;
664 // We should never see a byte position in the middle of a
666 assert!(bpos.to_usize() >= mbc.pos.to_usize() + mbc.bytes);
672 assert!(map.start_pos.to_usize() + total_extra_bytes <= bpos.to_usize());
673 CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes)
676 // Return the index of the filemap (in self.files) which contains pos.
677 fn lookup_filemap_idx(&self, pos: BytePos) -> usize {
678 let files = self.files.borrow();
680 let count = files.len();
682 // Binary search for the filemap.
687 if files[m].start_pos > pos {
694 assert!(a < count, "position {} does not resolve to a source location", pos.to_usize());
699 pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId {
700 let mut expansions = self.expansions.borrow_mut();
701 expansions.push(expn_info);
702 let len = expansions.len();
703 if len > u32::max_value() as usize {
704 panic!("too many ExpnInfo's!");
706 ExpnId(len as u32 - 1)
709 pub fn with_expn_info<T, F>(&self, id: ExpnId, f: F) -> T where
710 F: FnOnce(Option<&ExpnInfo>) -> T,
713 NO_EXPANSION | COMMAND_LINE_EXPN => f(None),
714 ExpnId(i) => f(Some(&(*self.expansions.borrow())[i as usize]))
718 /// Check if a span is "internal" to a macro in which #[unstable]
719 /// items can be used (that is, a macro marked with
720 /// `#[allow_internal_unstable]`).
721 pub fn span_allows_unstable(&self, span: Span) -> bool {
722 debug!("span_allows_unstable(span = {:?})", span);
723 let mut allows_unstable = false;
724 let mut expn_id = span.expn_id;
726 let quit = self.with_expn_info(expn_id, |expninfo| {
727 debug!("span_allows_unstable: expninfo = {:?}", expninfo);
728 expninfo.map_or(/* hit the top level */ true, |info| {
730 let span_comes_from_this_expansion =
731 info.callee.span.map_or(span.source_equal(&info.call_site), |mac_span| {
732 mac_span.contains(span)
735 debug!("span_allows_unstable: span: {:?} call_site: {:?} callee: {:?}",
737 (info.call_site.lo, info.call_site.hi),
738 info.callee.span.map(|x| (x.lo, x.hi)));
739 debug!("span_allows_unstable: from this expansion? {}, allows unstable? {}",
740 span_comes_from_this_expansion,
741 info.callee.allow_internal_unstable);
742 if span_comes_from_this_expansion {
743 allows_unstable = info.callee.allow_internal_unstable;
744 // we've found the right place, stop looking
747 // not the right place, keep looking
748 expn_id = info.call_site.expn_id;
757 debug!("span_allows_unstable? {}", allows_unstable);
761 pub fn count_lines(&self) -> usize {
762 self.files.borrow().iter().fold(0, |a, f| a + f.count_lines())
765 pub fn macro_backtrace(&self, span: Span) -> Vec<MacroBacktrace> {
766 let mut last_span = DUMMY_SP;
768 let mut result = vec![];
770 let span_name_span = self.with_expn_info(span.expn_id, |expn_info| {
772 let (pre, post) = match ei.callee.format {
773 MacroAttribute(..) => ("#[", "]"),
774 MacroBang(..) => ("", "!"),
776 let macro_decl_name = format!("{}{}{}",
780 let def_site_span = ei.callee.span;
781 (ei.call_site, macro_decl_name, def_site_span)
785 match span_name_span {
787 Some((call_site, macro_decl_name, def_site_span)) => {
788 // Don't print recursive invocations
789 if !call_site.source_equal(&last_span) {
790 result.push(MacroBacktrace {
791 call_site: call_site,
792 macro_decl_name: macro_decl_name,
793 def_site_span: def_site_span,
805 impl CodeMapper for CodeMap {
806 fn lookup_char_pos(&self, pos: BytePos) -> Loc {
807 self.lookup_char_pos(pos)
809 fn span_to_lines(&self, sp: Span) -> FileLinesResult {
810 self.span_to_lines(sp)
812 fn span_to_string(&self, sp: Span) -> String {
813 self.span_to_string(sp)
815 fn span_to_filename(&self, sp: Span) -> FileName {
816 self.span_to_filename(sp)
818 fn macro_backtrace(&self, span: Span) -> Vec<MacroBacktrace> {
819 self.macro_backtrace(span)
823 // _____________________________________________________________________________
834 let cm = CodeMap::new();
835 let fm = cm.new_filemap("blork.rs".to_string(),
837 "first line.\nsecond line".to_string());
838 fm.next_line(BytePos(0));
839 // Test we can get lines with partial line info.
840 assert_eq!(fm.get_line(0), Some("first line."));
841 // TESTING BROKEN BEHAVIOR: line break declared before actual line break.
842 fm.next_line(BytePos(10));
843 assert_eq!(fm.get_line(1), Some("."));
844 fm.next_line(BytePos(12));
845 assert_eq!(fm.get_line(2), Some("second line"));
851 let cm = CodeMap::new();
852 let fm = cm.new_filemap("blork.rs".to_string(),
854 "first line.\nsecond line".to_string());
855 // TESTING *REALLY* BROKEN BEHAVIOR:
856 fm.next_line(BytePos(0));
857 fm.next_line(BytePos(10));
858 fm.next_line(BytePos(2));
861 fn init_code_map() -> CodeMap {
862 let cm = CodeMap::new();
863 let fm1 = cm.new_filemap("blork.rs".to_string(),
865 "first line.\nsecond line".to_string());
866 let fm2 = cm.new_filemap("empty.rs".to_string(),
869 let fm3 = cm.new_filemap("blork2.rs".to_string(),
871 "first line.\nsecond line".to_string());
873 fm1.next_line(BytePos(0));
874 fm1.next_line(BytePos(12));
875 fm2.next_line(fm2.start_pos);
876 fm3.next_line(fm3.start_pos);
877 fm3.next_line(fm3.start_pos + BytePos(12));
884 // Test lookup_byte_offset
885 let cm = init_code_map();
887 let fmabp1 = cm.lookup_byte_offset(BytePos(23));
888 assert_eq!(fmabp1.fm.name, "blork.rs");
889 assert_eq!(fmabp1.pos, BytePos(23));
891 let fmabp1 = cm.lookup_byte_offset(BytePos(24));
892 assert_eq!(fmabp1.fm.name, "empty.rs");
893 assert_eq!(fmabp1.pos, BytePos(0));
895 let fmabp2 = cm.lookup_byte_offset(BytePos(25));
896 assert_eq!(fmabp2.fm.name, "blork2.rs");
897 assert_eq!(fmabp2.pos, BytePos(0));
902 // Test bytepos_to_file_charpos
903 let cm = init_code_map();
905 let cp1 = cm.bytepos_to_file_charpos(BytePos(22));
906 assert_eq!(cp1, CharPos(22));
908 let cp2 = cm.bytepos_to_file_charpos(BytePos(25));
909 assert_eq!(cp2, CharPos(0));
914 // Test zero-length filemaps.
915 let cm = init_code_map();
917 let loc1 = cm.lookup_char_pos(BytePos(22));
918 assert_eq!(loc1.file.name, "blork.rs");
919 assert_eq!(loc1.line, 2);
920 assert_eq!(loc1.col, CharPos(10));
922 let loc2 = cm.lookup_char_pos(BytePos(25));
923 assert_eq!(loc2.file.name, "blork2.rs");
924 assert_eq!(loc2.line, 1);
925 assert_eq!(loc2.col, CharPos(0));
928 fn init_code_map_mbc() -> CodeMap {
929 let cm = CodeMap::new();
930 // € is a three byte utf8 char.
932 cm.new_filemap("blork.rs".to_string(),
934 "fir€st €€€€ line.\nsecond line".to_string());
935 let fm2 = cm.new_filemap("blork2.rs".to_string(),
937 "first line€€.\n€ second line".to_string());
939 fm1.next_line(BytePos(0));
940 fm1.next_line(BytePos(28));
941 fm2.next_line(fm2.start_pos);
942 fm2.next_line(fm2.start_pos + BytePos(20));
944 fm1.record_multibyte_char(BytePos(3), 3);
945 fm1.record_multibyte_char(BytePos(9), 3);
946 fm1.record_multibyte_char(BytePos(12), 3);
947 fm1.record_multibyte_char(BytePos(15), 3);
948 fm1.record_multibyte_char(BytePos(18), 3);
949 fm2.record_multibyte_char(fm2.start_pos + BytePos(10), 3);
950 fm2.record_multibyte_char(fm2.start_pos + BytePos(13), 3);
951 fm2.record_multibyte_char(fm2.start_pos + BytePos(18), 3);
958 // Test bytepos_to_file_charpos in the presence of multi-byte chars
959 let cm = init_code_map_mbc();
961 let cp1 = cm.bytepos_to_file_charpos(BytePos(3));
962 assert_eq!(cp1, CharPos(3));
964 let cp2 = cm.bytepos_to_file_charpos(BytePos(6));
965 assert_eq!(cp2, CharPos(4));
967 let cp3 = cm.bytepos_to_file_charpos(BytePos(56));
968 assert_eq!(cp3, CharPos(12));
970 let cp4 = cm.bytepos_to_file_charpos(BytePos(61));
971 assert_eq!(cp4, CharPos(15));
976 // Test span_to_lines for a span ending at the end of filemap
977 let cm = init_code_map();
978 let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION};
979 let file_lines = cm.span_to_lines(span).unwrap();
981 assert_eq!(file_lines.file.name, "blork.rs");
982 assert_eq!(file_lines.lines.len(), 1);
983 assert_eq!(file_lines.lines[0].line_index, 1);
986 /// Given a string like " ~~~~~~~~~~~~ ", produces a span
987 /// coverting that range. The idea is that the string has the same
988 /// length as the input, and we uncover the byte positions. Note
989 /// that this can span lines and so on.
990 fn span_from_selection(input: &str, selection: &str) -> Span {
991 assert_eq!(input.len(), selection.len());
992 let left_index = selection.find('~').unwrap() as u32;
993 let right_index = selection.rfind('~').map(|x|x as u32).unwrap_or(left_index);
994 Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
997 /// Test span_to_snippet and span_to_lines for a span coverting 3
998 /// lines in the middle of a file.
1000 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1001 let cm = CodeMap::new();
1002 let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1003 let selection = " \n ~~\n~~~\n~~~~~ \n \n";
1004 cm.new_filemap_and_lines("blork.rs", None, inputtext);
1005 let span = span_from_selection(inputtext, selection);
1007 // check that we are extracting the text we thought we were extracting
1008 assert_eq!(&cm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
1010 // check that span_to_lines gives us the complete result with the lines/cols we expected
1011 let lines = cm.span_to_lines(span).unwrap();
1012 let expected = vec![
1013 LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) },
1014 LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) },
1015 LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) }
1017 assert_eq!(lines.lines, expected);
1022 // Test span_to_snippet for a span ending at the end of filemap
1023 let cm = init_code_map();
1024 let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION};
1025 let snippet = cm.span_to_snippet(span);
1027 assert_eq!(snippet, Ok("second line".to_string()));
1032 // Test span_to_str for a span ending at the end of filemap
1033 let cm = init_code_map();
1034 let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION};
1035 let sstr = cm.span_to_string(span);
1037 assert_eq!(sstr, "blork.rs:2:1: 2:12");
1042 // Test span_to_expanded_string works in base case (no expansion)
1043 let cm = init_code_map();
1044 let span = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION };
1045 let sstr = cm.span_to_expanded_string(span);
1046 assert_eq!(sstr, "blork.rs:1:1: 1:12\n`first line.`\n");
1048 let span = Span { lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION };
1049 let sstr = cm.span_to_expanded_string(span);
1050 assert_eq!(sstr, "blork.rs:2:1: 2:12\n`second line`\n");
1055 // Test span_to_expanded_string works with expansion
1057 let cm = init_code_map();
1058 let root = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION };
1059 let format = ExpnFormat::MacroBang(Name(0u32));
1060 let callee = NameAndSpan { format: format,
1061 allow_internal_unstable: false,
1064 let info = ExpnInfo { call_site: root, callee: callee };
1065 let id = cm.record_expansion(info);
1066 let sp = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id };
1068 let sstr = cm.span_to_expanded_string(sp);
1070 "blork.rs:2:1: 2:12\n`second line`\n Callsite:\n \
1071 blork.rs:1:1: 1:12\n `first line.`\n");
1074 /// Returns the span corresponding to the `n`th occurrence of
1075 /// `substring` in `source_text`.
1076 trait CodeMapExtension {
1077 fn span_substr(&self,
1085 impl CodeMapExtension for CodeMap {
1086 fn span_substr(&self,
1093 println!("span_substr(file={:?}/{:?}, substring={:?}, n={})",
1094 file.name, file.start_pos, substring, n);
1098 let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
1099 panic!("source_text `{}` does not have {} occurrences of `{}`, only {}",
1100 source_text, n, substring, i);
1102 let lo = hi + offset;
1103 hi = lo + substring.len();
1106 lo: BytePos(lo as u32 + file.start_pos.0),
1107 hi: BytePos(hi as u32 + file.start_pos.0),
1108 expn_id: NO_EXPANSION,
1110 assert_eq!(&self.span_to_snippet(span).unwrap()[..],
1119 fn init_expansion_chain(cm: &CodeMap) -> Span {
1120 // Creates an expansion chain containing two recursive calls
1121 // root -> expA -> expA -> expB -> expB -> end
1124 let root = Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION };
1126 let format_root = ExpnFormat::MacroBang(Name(0u32));
1127 let callee_root = NameAndSpan { format: format_root,
1128 allow_internal_unstable: false,
1131 let info_a1 = ExpnInfo { call_site: root, callee: callee_root };
1132 let id_a1 = cm.record_expansion(info_a1);
1133 let span_a1 = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a1 };
1135 let format_a = ExpnFormat::MacroBang(Name(1u32));
1136 let callee_a = NameAndSpan { format: format_a,
1137 allow_internal_unstable: false,
1138 span: Some(span_a1) };
1140 let info_a2 = ExpnInfo { call_site: span_a1, callee: callee_a.clone() };
1141 let id_a2 = cm.record_expansion(info_a2);
1142 let span_a2 = Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a2 };
1144 let info_b1 = ExpnInfo { call_site: span_a2, callee: callee_a };
1145 let id_b1 = cm.record_expansion(info_b1);
1146 let span_b1 = Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b1 };
1148 let format_b = ExpnFormat::MacroBang(Name(2u32));
1149 let callee_b = NameAndSpan { format: format_b,
1150 allow_internal_unstable: false,
1153 let info_b2 = ExpnInfo { call_site: span_b1, callee: callee_b.clone() };
1154 let id_b2 = cm.record_expansion(info_b2);
1155 let span_b2 = Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b2 };
1157 let info_end = ExpnInfo { call_site: span_b2, callee: callee_b };
1158 let id_end = cm.record_expansion(info_end);
1159 Span { lo: BytePos(37), hi: BytePos(48), expn_id: id_end }
1164 // Test span_to_expanded_string collapses recursive macros and handles
1165 // recursive callsite and callee expansions
1166 let cm = init_code_map();
1167 let end = init_expansion_chain(&cm);
1168 let sstr = cm.span_to_expanded_string(end);
1170 r"blork2.rs:2:1: 2:12
1196 assert_eq!(sstr, res_str);