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
::{Cell, RefCell}
;
23 use std
::ops
::{Add, Sub}
;
28 use std
::io
::{self, Read}
;
30 use serialize
::{Encodable, Decodable, Encoder, Decoder}
;
33 // _____________________________________________________________________________
34 // Pos, BytePos, CharPos
38 fn from_usize(n
: usize) -> Self;
39 fn to_usize(&self) -> usize;
42 /// A byte offset. Keep this small (currently 32-bits), as AST contains
44 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)]
45 pub struct BytePos(pub u32);
47 /// A character offset. Because of multibyte utf8 characters, a byte offset
48 /// is not equivalent to a character offset. The CodeMap will convert BytePos
49 /// values to CharPos values as necessary.
50 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)]
51 pub struct CharPos(pub usize);
53 // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
54 // have been unsuccessful
56 impl Pos
for BytePos
{
57 fn from_usize(n
: usize) -> BytePos { BytePos(n as u32) }
58 fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize }
61 impl Add
for BytePos
{
62 type Output
= BytePos
;
64 fn add(self, rhs
: BytePos
) -> BytePos
{
65 BytePos((self.to_usize() + rhs
.to_usize()) as u32)
69 impl Sub
for BytePos
{
70 type Output
= BytePos
;
72 fn sub(self, rhs
: BytePos
) -> BytePos
{
73 BytePos((self.to_usize() - rhs
.to_usize()) as u32)
77 impl Encodable
for BytePos
{
78 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
83 impl Decodable
for BytePos
{
84 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<BytePos
, D
::Error
> {
85 Ok(BytePos(try
!{ d.read_u32() }
))
89 impl Pos
for CharPos
{
90 fn from_usize(n
: usize) -> CharPos { CharPos(n) }
91 fn to_usize(&self) -> usize { let CharPos(n) = *self; n }
94 impl Add
for CharPos
{
95 type Output
= CharPos
;
97 fn add(self, rhs
: CharPos
) -> CharPos
{
98 CharPos(self.to_usize() + rhs
.to_usize())
102 impl Sub
for CharPos
{
103 type Output
= CharPos
;
105 fn sub(self, rhs
: CharPos
) -> CharPos
{
106 CharPos(self.to_usize() - rhs
.to_usize())
110 // _____________________________________________________________________________
114 /// Spans represent a region of code, used for error reporting. Positions in spans
115 /// are *absolute* positions from the beginning of the codemap, not positions
116 /// relative to FileMaps. Methods on the CodeMap can be used to relate spans back
117 /// to the original source.
118 #[derive(Clone, Copy, Hash)]
122 /// Information about where the macro came from, if this piece of
123 /// code was created by a macro expansion.
127 pub const DUMMY_SP
: Span
= Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION }
;
129 // Generic span to be used for code originating from the command line
130 pub const COMMAND_LINE_SP
: Span
= Span
{ lo
: BytePos(0),
132 expn_id
: COMMAND_LINE_EXPN
};
134 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
135 pub struct Spanned
<T
> {
140 impl PartialEq
for Span
{
141 fn eq(&self, other
: &Span
) -> bool
{
142 return (*self).lo
== (*other
).lo
&& (*self).hi
== (*other
).hi
;
144 fn ne(&self, other
: &Span
) -> bool { !(*self).eq(other) }
149 impl Encodable
for Span
{
150 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
151 // Encode spans as a single u64 in order to cut down on tagging overhead
152 // added by the RBML metadata encoding. The should be solved differently
153 // altogether some time (FIXME #21482)
154 s
.emit_u64( (self.lo
.0 as u64) | ((self.hi
.0 as u64) << 32) )
158 impl Decodable
for Span
{
159 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<Span
, D
::Error
> {
160 let lo_hi
: u64 = try
! { d.read_u64() }
;
161 let lo
= BytePos(lo_hi
as u32);
162 let hi
= BytePos((lo_hi
>> 32) as u32);
167 fn default_span_debug(span
: Span
, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
168 write
!(f
, "Span {{ lo: {:?}, hi: {:?}, expn_id: {:?} }}",
169 span
.lo
, span
.hi
, span
.expn_id
)
172 thread_local
!(pub static SPAN_DEBUG
: Cell
<fn(Span
, &mut fmt
::Formatter
) -> fmt
::Result
> =
173 Cell
::new(default_span_debug
));
175 impl fmt
::Debug
for Span
{
176 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
177 SPAN_DEBUG
.with(|span_debug
| span_debug
.get()(*self, f
))
181 pub fn spanned
<T
>(lo
: BytePos
, hi
: BytePos
, t
: T
) -> Spanned
<T
> {
182 respan(mk_sp(lo
, hi
), t
)
185 pub fn respan
<T
>(sp
: Span
, t
: T
) -> Spanned
<T
> {
186 Spanned {node: t, span: sp}
189 pub fn dummy_spanned
<T
>(t
: T
) -> Spanned
<T
> {
193 /* assuming that we're not in macro expansion */
194 pub fn mk_sp(lo
: BytePos
, hi
: BytePos
) -> Span
{
195 Span {lo: lo, hi: hi, expn_id: NO_EXPANSION}
198 /// Return the span itself if it doesn't come from a macro expansion,
199 /// otherwise return the call site span up to the `enclosing_sp` by
200 /// following the `expn_info` chain.
201 pub fn original_sp(cm
: &CodeMap
, sp
: Span
, enclosing_sp
: Span
) -> Span
{
202 let call_site1
= cm
.with_expn_info(sp
.expn_id
, |ei
| ei
.map(|ei
| ei
.call_site
));
203 let call_site2
= cm
.with_expn_info(enclosing_sp
.expn_id
, |ei
| ei
.map(|ei
| ei
.call_site
));
204 match (call_site1
, call_site2
) {
206 (Some(call_site1
), Some(call_site2
)) if call_site1
== call_site2
=> sp
,
207 (Some(call_site1
), _
) => original_sp(cm
, call_site1
, enclosing_sp
),
211 // _____________________________________________________________________________
212 // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos
215 /// A source code location used for error reporting
218 /// Information about the original source
219 pub file
: Rc
<FileMap
>,
220 /// The (1-based) line number
222 /// The (0-based) column offset
226 /// A source code location used as the result of lookup_char_pos_adj
227 // Actually, *none* of the clients use the filename *or* file field;
228 // perhaps they should just be removed.
230 pub struct LocWithOpt
{
231 pub filename
: FileName
,
234 pub file
: Option
<Rc
<FileMap
>>,
237 // used to be structural records. Better names, anyone?
239 pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize }
241 pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos }
244 // _____________________________________________________________________________
245 // ExpnFormat, NameAndSpan, ExpnInfo, ExpnId
248 /// The source of expansion.
249 #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)]
250 pub enum ExpnFormat
{
251 /// e.g. #[derive(...)] <item>
255 /// Syntax sugar expansion performed by the compiler (libsyntax::expand).
259 #[derive(Clone, Hash, Debug)]
260 pub struct NameAndSpan
{
261 /// The name of the macro that was invoked to create the thing
264 /// The format with which the macro was invoked.
265 pub format
: ExpnFormat
,
266 /// Whether the macro is allowed to use #[unstable]/feature-gated
267 /// features internally without forcing the whole crate to opt-in
269 pub allow_internal_unstable
: bool
,
270 /// The span of the macro definition itself. The macro may not
271 /// have a sensible definition span (e.g. something defined
272 /// completely inside libsyntax) in which case this is None.
273 pub span
: Option
<Span
>
276 /// Extra information for tracking spans of macro and syntax sugar expansion
277 #[derive(Hash, Debug)]
278 pub struct ExpnInfo
{
279 /// The location of the actual macro invocation or syntax sugar , e.g.
280 /// `let x = foo!();` or `if let Some(y) = x {}`
282 /// This may recursively refer to other macro invocations, e.g. if
283 /// `foo!()` invoked `bar!()` internally, and there was an
284 /// expression inside `bar!`; the call_site of the expression in
285 /// the expansion would point to the `bar!` invocation; that
286 /// call_site span would have its own ExpnInfo, with the call_site
287 /// pointing to the `foo!` invocation.
289 /// Information about the expansion.
290 pub callee
: NameAndSpan
293 #[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)]
294 pub struct ExpnId(u32);
296 pub const NO_EXPANSION
: ExpnId
= ExpnId(!0);
297 // For code appearing from the command line
298 pub const COMMAND_LINE_EXPN
: ExpnId
= ExpnId(!1);
301 pub fn from_u32(id
: u32) -> ExpnId
{
305 pub fn into_u32(self) -> u32 {
310 // _____________________________________________________________________________
311 // FileMap, MultiByteChar, FileName, FileLines
314 pub type FileName
= String
;
316 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
317 pub struct LineInfo
{
318 /// Index of line, starting from 0.
319 pub line_index
: usize,
321 /// Column in line where span begins, starting from 0.
322 pub start_col
: CharPos
,
324 /// Column in line where span ends, starting from 0, exclusive.
325 pub end_col
: CharPos
,
328 pub struct FileLines
{
329 pub file
: Rc
<FileMap
>,
330 pub lines
: Vec
<LineInfo
>
333 /// Identifies an offset of a multi-byte character in a FileMap
334 #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Eq, PartialEq)]
335 pub struct MultiByteChar
{
336 /// The absolute offset of the character in the CodeMap
338 /// The number of bytes, >=2
342 /// A single source in the CodeMap
344 /// The name of the file that the source came from, source that doesn't
345 /// originate from files has names between angle brackets by convention,
348 /// The complete source code
349 pub src
: Option
<Rc
<String
>>,
350 /// The start position of this source in the CodeMap
351 pub start_pos
: BytePos
,
352 /// The end position of this source in the CodeMap
353 pub end_pos
: BytePos
,
354 /// Locations of lines beginnings in the source code
355 pub lines
: RefCell
<Vec
<BytePos
>>,
356 /// Locations of multi-byte characters in the source code
357 pub multibyte_chars
: RefCell
<Vec
<MultiByteChar
>>,
360 impl Encodable
for FileMap
{
361 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
362 s
.emit_struct("FileMap", 5, |s
| {
363 try
! { s.emit_struct_field("name", 0, |s| self.name.encode(s)) }
;
364 try
! { s.emit_struct_field("start_pos", 1, |s| self.start_pos.encode(s)) }
;
365 try
! { s.emit_struct_field("end_pos", 2, |s| self.end_pos.encode(s)) }
;
366 try
! { s
.emit_struct_field("lines", 3, |s
| {
367 let lines
= self.lines
.borrow();
369 try
! { s.emit_u32(lines.len() as u32) }
;
371 if !lines
.is_empty() {
372 // In order to preserve some space, we exploit the fact that
373 // the lines list is sorted and individual lines are
374 // probably not that long. Because of that we can store lines
375 // as a difference list, using as little space as possible
376 // for the differences.
377 let max_line_length
= if lines
.len() == 1 {
381 .map(|w
| w
[1] - w
[0])
382 .map(|bp
| bp
.to_usize())
387 let bytes_per_diff
: u8 = match max_line_length
{
389 0x100 ... 0xFFFF => 2,
393 // Encode the number of bytes used per diff.
394 try
! { bytes_per_diff.encode(s) }
;
396 // Encode the first element.
397 try
! { lines[0].encode(s) }
;
399 let diff_iter
= (&lines
[..]).windows(2)
400 .map(|w
| (w
[1] - w
[0]));
402 match bytes_per_diff
{
403 1 => for diff
in diff_iter { try! { (diff.0 as u8).encode(s) }
},
404 2 => for diff
in diff_iter { try! { (diff.0 as u16).encode(s) }
},
405 4 => for diff
in diff_iter { try! { diff.0.encode(s) }
},
413 s
.emit_struct_field("multibyte_chars", 4, |s
| {
414 (*self.multibyte_chars
.borrow()).encode(s
)
420 impl Decodable
for FileMap
{
421 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<FileMap
, D
::Error
> {
423 d
.read_struct("FileMap", 5, |d
| {
424 let name
: String
= try
! {
425 d
.read_struct_field("name", 0, |d
| Decodable
::decode(d
))
427 let start_pos
: BytePos
= try
! {
428 d
.read_struct_field("start_pos", 1, |d
| Decodable
::decode(d
))
430 let end_pos
: BytePos
= try
! {
431 d
.read_struct_field("end_pos", 2, |d
| Decodable
::decode(d
))
433 let lines
: Vec
<BytePos
> = try
! {
434 d
.read_struct_field("lines", 3, |d
| {
435 let num_lines
: u32 = try
! { Decodable::decode(d) }
;
436 let mut lines
= Vec
::with_capacity(num_lines
as usize);
439 // Read the number of bytes used per diff.
440 let bytes_per_diff
: u8 = try
! { Decodable::decode(d) }
;
442 // Read the first element.
443 let mut line_start
: BytePos
= try
! { Decodable::decode(d) }
;
444 lines
.push(line_start
);
446 for _
in 1..num_lines
{
447 let diff
= match bytes_per_diff
{
448 1 => try
! { d.read_u8() }
as u32,
449 2 => try
! { d.read_u16() }
as u32,
450 4 => try
! { d.read_u32() }
,
454 line_start
= line_start
+ BytePos(diff
);
456 lines
.push(line_start
);
463 let multibyte_chars
: Vec
<MultiByteChar
> = try
! {
464 d
.read_struct_field("multibyte_chars", 4, |d
| Decodable
::decode(d
))
468 start_pos
: start_pos
,
471 lines
: RefCell
::new(lines
),
472 multibyte_chars
: RefCell
::new(multibyte_chars
)
478 impl fmt
::Debug
for FileMap
{
479 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
480 write
!(fmt
, "FileMap({})", self.name
)
485 /// EFFECT: register a start-of-line offset in the
486 /// table of line-beginnings.
487 /// UNCHECKED INVARIANT: these offsets must be added in the right
488 /// order and must be in the right places; there is shared knowledge
489 /// about what ends a line between this file and parse.rs
490 /// WARNING: pos param here is the offset relative to start of CodeMap,
491 /// and CodeMap will append a newline when adding a filemap without a newline at the end,
492 /// so the safe way to call this is with value calculated as
493 /// filemap.start_pos + newline_offset_relative_to_the_start_of_filemap.
494 pub fn next_line(&self, pos
: BytePos
) {
495 // the new charpos must be > the last one (or it's the first one).
496 let mut lines
= self.lines
.borrow_mut();
497 let line_len
= lines
.len();
498 assert
!(line_len
== 0 || ((*lines
)[line_len
- 1] < pos
));
502 /// get a line from the list of pre-computed line-beginnings.
503 /// line-number here is 0-based.
504 pub fn get_line(&self, line_number
: usize) -> Option
<&str> {
507 let lines
= self.lines
.borrow();
508 lines
.get(line_number
).map(|&line
| {
509 let begin
: BytePos
= line
- self.start_pos
;
510 let begin
= begin
.to_usize();
511 let slice
= &src
[begin
..];
512 match slice
.find('
\n'
) {
513 Some(e
) => &slice
[..e
],
522 pub fn record_multibyte_char(&self, pos
: BytePos
, bytes
: usize) {
523 assert
!(bytes
>=2 && bytes
<= 4);
524 let mbc
= MultiByteChar
{
528 self.multibyte_chars
.borrow_mut().push(mbc
);
531 pub fn is_real_file(&self) -> bool
{
532 !(self.name
.starts_with("<") &&
533 self.name
.ends_with(">"))
536 pub fn is_imported(&self) -> bool
{
541 /// An abstraction over the fs operations used by the Parser.
542 pub trait FileLoader
{
543 /// Query the existence of a file.
544 fn file_exists(&self, path
: &Path
) -> bool
;
546 /// Read the contents of an UTF-8 file into memory.
547 fn read_file(&self, path
: &Path
) -> io
::Result
<String
>;
550 /// A FileLoader that uses std::fs to load real files.
551 pub struct RealFileLoader
;
553 impl FileLoader
for RealFileLoader
{
554 fn file_exists(&self, path
: &Path
) -> bool
{
555 fs
::metadata(path
).is_ok()
558 fn read_file(&self, path
: &Path
) -> io
::Result
<String
> {
559 let mut src
= String
::new();
560 try
!(try
!(fs
::File
::open(path
)).read_to_string(&mut src
));
565 // _____________________________________________________________________________
570 pub files
: RefCell
<Vec
<Rc
<FileMap
>>>,
571 expansions
: RefCell
<Vec
<ExpnInfo
>>,
572 file_loader
: Box
<FileLoader
>
576 pub fn new() -> CodeMap
{
578 files
: RefCell
::new(Vec
::new()),
579 expansions
: RefCell
::new(Vec
::new()),
580 file_loader
: Box
::new(RealFileLoader
)
584 pub fn with_file_loader(file_loader
: Box
<FileLoader
>) -> CodeMap
{
586 files
: RefCell
::new(Vec
::new()),
587 expansions
: RefCell
::new(Vec
::new()),
588 file_loader
: file_loader
592 pub fn file_exists(&self, path
: &Path
) -> bool
{
593 self.file_loader
.file_exists(path
)
596 pub fn load_file(&self, path
: &Path
) -> io
::Result
<Rc
<FileMap
>> {
597 let src
= try
!(self.file_loader
.read_file(path
));
598 Ok(self.new_filemap(path
.to_str().unwrap().to_string(), src
))
601 pub fn new_filemap(&self, filename
: FileName
, mut src
: String
) -> Rc
<FileMap
> {
602 let mut files
= self.files
.borrow_mut();
603 let start_pos
= match files
.last() {
605 Some(last
) => last
.end_pos
.to_usize(),
608 // Remove utf-8 BOM if any.
609 if src
.starts_with("\u{feff}") {
613 // Append '\n' in case it's not already there.
614 // This is a workaround to prevent CodeMap.lookup_filemap_idx from
615 // accidentally overflowing into the next filemap in case the last byte
616 // of span is also the last byte of filemap, which leads to incorrect
617 // results from CodeMap.span_to_*.
618 if !src
.is_empty() && !src
.ends_with("\n") {
622 let end_pos
= start_pos
+ src
.len();
624 let filemap
= Rc
::new(FileMap
{
626 src
: Some(Rc
::new(src
)),
627 start_pos
: Pos
::from_usize(start_pos
),
628 end_pos
: Pos
::from_usize(end_pos
),
629 lines
: RefCell
::new(Vec
::new()),
630 multibyte_chars
: RefCell
::new(Vec
::new()),
633 files
.push(filemap
.clone());
638 /// Allocates a new FileMap representing a source file from an external
639 /// crate. The source code of such an "imported filemap" is not available,
640 /// but we still know enough to generate accurate debuginfo location
641 /// information for things inlined from other crates.
642 pub fn new_imported_filemap(&self,
645 mut file_local_lines
: Vec
<BytePos
>,
646 mut file_local_multibyte_chars
: Vec
<MultiByteChar
>)
648 let mut files
= self.files
.borrow_mut();
649 let start_pos
= match files
.last() {
651 Some(last
) => last
.end_pos
.to_usize(),
654 let end_pos
= Pos
::from_usize(start_pos
+ source_len
);
655 let start_pos
= Pos
::from_usize(start_pos
);
657 for pos
in &mut file_local_lines
{
658 *pos
= *pos
+ start_pos
;
661 for mbc
in &mut file_local_multibyte_chars
{
662 mbc
.pos
= mbc
.pos
+ start_pos
;
665 let filemap
= Rc
::new(FileMap
{
668 start_pos
: start_pos
,
670 lines
: RefCell
::new(file_local_lines
),
671 multibyte_chars
: RefCell
::new(file_local_multibyte_chars
),
674 files
.push(filemap
.clone());
679 pub fn mk_substr_filename(&self, sp
: Span
) -> String
{
680 let pos
= self.lookup_char_pos(sp
.lo
);
681 (format
!("<{}:{}:{}>",
684 pos
.col
.to_usize() + 1)).to_string()
687 /// Lookup source information about a BytePos
688 pub fn lookup_char_pos(&self, pos
: BytePos
) -> Loc
{
689 let FileMapAndLine {fm: f, line: a}
= self.lookup_line(pos
);
690 let line
= a
+ 1; // Line numbers start at 1
691 let chpos
= self.bytepos_to_file_charpos(pos
);
692 let linebpos
= (*f
.lines
.borrow())[a
];
693 let linechpos
= self.bytepos_to_file_charpos(linebpos
);
694 debug
!("byte pos {:?} is on the line at byte pos {:?}",
696 debug
!("char pos {:?} is on the line at char pos {:?}",
698 debug
!("byte is on line: {}", line
);
699 assert
!(chpos
>= linechpos
);
703 col
: chpos
- linechpos
707 fn lookup_line(&self, pos
: BytePos
) -> FileMapAndLine
{
708 let idx
= self.lookup_filemap_idx(pos
);
710 let files
= self.files
.borrow();
711 let f
= (*files
)[idx
].clone();
714 let lines
= f
.lines
.borrow();
715 let mut b
= lines
.len();
718 if (*lines
)[m
] > pos { b = m; }
else { a = m; }
721 FileMapAndLine {fm: f, line: a}
724 pub fn lookup_char_pos_adj(&self, pos
: BytePos
) -> LocWithOpt
{
725 let loc
= self.lookup_char_pos(pos
);
727 filename
: loc
.file
.name
.to_string(),
734 pub fn span_to_string(&self, sp
: Span
) -> String
{
735 if self.files
.borrow().is_empty() && sp
== DUMMY_SP
{
736 return "no-location".to_string();
739 let lo
= self.lookup_char_pos_adj(sp
.lo
);
740 let hi
= self.lookup_char_pos_adj(sp
.hi
);
741 return (format
!("{}:{}:{}: {}:{}",
744 lo
.col
.to_usize() + 1,
746 hi
.col
.to_usize() + 1)).to_string()
749 pub fn span_to_filename(&self, sp
: Span
) -> FileName
{
750 self.lookup_char_pos(sp
.lo
).file
.name
.to_string()
753 pub fn span_to_lines(&self, sp
: Span
) -> FileLinesResult
{
755 return Err(SpanLinesError
::IllFormedSpan(sp
));
758 let lo
= self.lookup_char_pos(sp
.lo
);
759 let hi
= self.lookup_char_pos(sp
.hi
);
761 if lo
.file
.start_pos
!= hi
.file
.start_pos
{
762 return Err(SpanLinesError
::DistinctSources(DistinctSources
{
763 begin
: (lo
.file
.name
.clone(), lo
.file
.start_pos
),
764 end
: (hi
.file
.name
.clone(), hi
.file
.start_pos
),
767 assert
!(hi
.line
>= lo
.line
);
769 let mut lines
= Vec
::with_capacity(hi
.line
- lo
.line
+ 1);
771 // The span starts partway through the first line,
772 // but after that it starts from offset 0.
773 let mut start_col
= lo
.col
;
775 // For every line but the last, it extends from `start_col`
776 // and to the end of the line. Be careful because the line
777 // numbers in Loc are 1-based, so we subtract 1 to get 0-based
779 for line_index
in lo
.line
-1 .. hi
.line
-1 {
780 let line_len
= lo
.file
.get_line(line_index
).map(|s
| s
.len()).unwrap_or(0);
781 lines
.push(LineInfo
{ line_index
: line_index
,
782 start_col
: start_col
,
783 end_col
: CharPos
::from_usize(line_len
) });
784 start_col
= CharPos
::from_usize(0);
787 // For the last line, it extends from `start_col` to `hi.col`:
788 lines
.push(LineInfo
{ line_index
: hi
.line
- 1,
789 start_col
: start_col
,
792 Ok(FileLines {file: lo.file, lines: lines}
)
795 pub fn span_to_snippet(&self, sp
: Span
) -> Result
<String
, SpanSnippetError
> {
797 return Err(SpanSnippetError
::IllFormedSpan(sp
));
800 let local_begin
= self.lookup_byte_offset(sp
.lo
);
801 let local_end
= self.lookup_byte_offset(sp
.hi
);
803 if local_begin
.fm
.start_pos
!= local_end
.fm
.start_pos
{
804 return Err(SpanSnippetError
::DistinctSources(DistinctSources
{
805 begin
: (local_begin
.fm
.name
.clone(),
806 local_begin
.fm
.start_pos
),
807 end
: (local_end
.fm
.name
.clone(),
808 local_end
.fm
.start_pos
)
811 match local_begin
.fm
.src
{
813 let start_index
= local_begin
.pos
.to_usize();
814 let end_index
= local_end
.pos
.to_usize();
815 let source_len
= (local_begin
.fm
.end_pos
-
816 local_begin
.fm
.start_pos
).to_usize();
818 if start_index
> end_index
|| end_index
> source_len
{
819 return Err(SpanSnippetError
::MalformedForCodemap(
820 MalformedCodemapPositions
{
821 name
: local_begin
.fm
.name
.clone(),
822 source_len
: source_len
,
823 begin_pos
: local_begin
.pos
,
824 end_pos
: local_end
.pos
,
828 return Ok((&src
[start_index
..end_index
]).to_string())
831 return Err(SpanSnippetError
::SourceNotAvailable
{
832 filename
: local_begin
.fm
.name
.clone()
839 pub fn get_filemap(&self, filename
: &str) -> Rc
<FileMap
> {
840 for fm
in self.files
.borrow().iter() {
841 if filename
== fm
.name
{
845 panic
!("asking for {} which we don't know about", filename
);
848 /// For a global BytePos compute the local offset within the containing FileMap
849 pub fn lookup_byte_offset(&self, bpos
: BytePos
) -> FileMapAndBytePos
{
850 let idx
= self.lookup_filemap_idx(bpos
);
851 let fm
= (*self.files
.borrow())[idx
].clone();
852 let offset
= bpos
- fm
.start_pos
;
853 FileMapAndBytePos {fm: fm, pos: offset}
856 /// Converts an absolute BytePos to a CharPos relative to the filemap and above.
857 pub fn bytepos_to_file_charpos(&self, bpos
: BytePos
) -> CharPos
{
858 let idx
= self.lookup_filemap_idx(bpos
);
859 let files
= self.files
.borrow();
860 let map
= &(*files
)[idx
];
862 // The number of extra bytes due to multibyte chars in the FileMap
863 let mut total_extra_bytes
= 0;
865 for mbc
in map
.multibyte_chars
.borrow().iter() {
866 debug
!("{}-byte char at {:?}", mbc
.bytes
, mbc
.pos
);
868 // every character is at least one byte, so we only
869 // count the actual extra bytes.
870 total_extra_bytes
+= mbc
.bytes
- 1;
871 // We should never see a byte position in the middle of a
873 assert
!(bpos
.to_usize() >= mbc
.pos
.to_usize() + mbc
.bytes
);
879 assert
!(map
.start_pos
.to_usize() + total_extra_bytes
<= bpos
.to_usize());
880 CharPos(bpos
.to_usize() - map
.start_pos
.to_usize() - total_extra_bytes
)
883 fn lookup_filemap_idx(&self, pos
: BytePos
) -> usize {
884 let files
= self.files
.borrow();
886 let len
= files
.len();
891 if files
[m
].start_pos
> pos
{
897 // There can be filemaps with length 0. These have the same start_pos as
898 // the previous filemap, but are not the filemaps we want (because they
899 // are length 0, they cannot contain what we are looking for). So,
900 // rewind until we find a useful filemap.
902 let lines
= files
[a
].lines
.borrow();
904 if !lines
.is_empty() {
908 panic
!("position {} does not resolve to a source location",
914 panic
!("position {} does not resolve to a source location",
921 pub fn record_expansion(&self, expn_info
: ExpnInfo
) -> ExpnId
{
922 let mut expansions
= self.expansions
.borrow_mut();
923 expansions
.push(expn_info
);
924 let len
= expansions
.len();
925 if len
> u32::max_value() as usize {
926 panic
!("too many ExpnInfo's!");
928 ExpnId(len
as u32 - 1)
931 pub fn with_expn_info
<T
, F
>(&self, id
: ExpnId
, f
: F
) -> T
where
932 F
: FnOnce(Option
<&ExpnInfo
>) -> T
,
935 NO_EXPANSION
| COMMAND_LINE_EXPN
=> f(None
),
936 ExpnId(i
) => f(Some(&(*self.expansions
.borrow())[i
as usize]))
940 /// Check if a span is "internal" to a macro in which #[unstable]
941 /// items can be used (that is, a macro marked with
942 /// `#[allow_internal_unstable]`).
943 pub fn span_allows_unstable(&self, span
: Span
) -> bool
{
944 debug
!("span_allows_unstable(span = {:?})", span
);
945 let mut allows_unstable
= false;
946 let mut expn_id
= span
.expn_id
;
948 let quit
= self.with_expn_info(expn_id
, |expninfo
| {
949 debug
!("span_allows_unstable: expninfo = {:?}", expninfo
);
950 expninfo
.map_or(/* hit the top level */ true, |info
| {
952 let span_comes_from_this_expansion
=
953 info
.callee
.span
.map_or(span
== info
.call_site
, |mac_span
| {
954 mac_span
.lo
<= span
.lo
&& span
.hi
<= mac_span
.hi
957 debug
!("span_allows_unstable: from this expansion? {}, allows unstable? {}",
958 span_comes_from_this_expansion
,
959 info
.callee
.allow_internal_unstable
);
960 if span_comes_from_this_expansion
{
961 allows_unstable
= info
.callee
.allow_internal_unstable
;
962 // we've found the right place, stop looking
965 // not the right place, keep looking
966 expn_id
= info
.call_site
.expn_id
;
975 debug
!("span_allows_unstable? {}", allows_unstable
);
980 // _____________________________________________________________________________
981 // SpanLinesError, SpanSnippetError, DistinctSources, MalformedCodemapPositions
984 pub type FileLinesResult
= Result
<FileLines
, SpanLinesError
>;
986 #[derive(Clone, PartialEq, Eq, Debug)]
987 pub enum SpanLinesError
{
989 DistinctSources(DistinctSources
),
992 #[derive(Clone, PartialEq, Eq, Debug)]
993 pub enum SpanSnippetError
{
995 DistinctSources(DistinctSources
),
996 MalformedForCodemap(MalformedCodemapPositions
),
997 SourceNotAvailable { filename: String }
1000 #[derive(Clone, PartialEq, Eq, Debug)]
1001 pub struct DistinctSources
{
1002 begin
: (String
, BytePos
),
1003 end
: (String
, BytePos
)
1006 #[derive(Clone, PartialEq, Eq, Debug)]
1007 pub struct MalformedCodemapPositions
{
1015 // _____________________________________________________________________________
1026 let cm
= CodeMap
::new();
1027 let fm
= cm
.new_filemap("blork.rs".to_string(),
1028 "first line.\nsecond line".to_string());
1029 fm
.next_line(BytePos(0));
1030 assert_eq
!(fm
.get_line(0), Some("first line."));
1031 // TESTING BROKEN BEHAVIOR:
1032 fm
.next_line(BytePos(10));
1033 assert_eq
!(fm
.get_line(1), Some("."));
1039 let cm
= CodeMap
::new();
1040 let fm
= cm
.new_filemap("blork.rs".to_string(),
1041 "first line.\nsecond line".to_string());
1042 // TESTING *REALLY* BROKEN BEHAVIOR:
1043 fm
.next_line(BytePos(0));
1044 fm
.next_line(BytePos(10));
1045 fm
.next_line(BytePos(2));
1048 fn init_code_map() -> CodeMap
{
1049 let cm
= CodeMap
::new();
1050 let fm1
= cm
.new_filemap("blork.rs".to_string(),
1051 "first line.\nsecond line".to_string());
1052 let fm2
= cm
.new_filemap("empty.rs".to_string(),
1054 let fm3
= cm
.new_filemap("blork2.rs".to_string(),
1055 "first line.\nsecond line".to_string());
1057 fm1
.next_line(BytePos(0));
1058 fm1
.next_line(BytePos(12));
1059 fm2
.next_line(BytePos(24));
1060 fm3
.next_line(BytePos(24));
1061 fm3
.next_line(BytePos(34));
1068 // Test lookup_byte_offset
1069 let cm
= init_code_map();
1071 let fmabp1
= cm
.lookup_byte_offset(BytePos(22));
1072 assert_eq
!(fmabp1
.fm
.name
, "blork.rs");
1073 assert_eq
!(fmabp1
.pos
, BytePos(22));
1075 let fmabp2
= cm
.lookup_byte_offset(BytePos(24));
1076 assert_eq
!(fmabp2
.fm
.name
, "blork2.rs");
1077 assert_eq
!(fmabp2
.pos
, BytePos(0));
1082 // Test bytepos_to_file_charpos
1083 let cm
= init_code_map();
1085 let cp1
= cm
.bytepos_to_file_charpos(BytePos(22));
1086 assert_eq
!(cp1
, CharPos(22));
1088 let cp2
= cm
.bytepos_to_file_charpos(BytePos(24));
1089 assert_eq
!(cp2
, CharPos(0));
1094 // Test zero-length filemaps.
1095 let cm
= init_code_map();
1097 let loc1
= cm
.lookup_char_pos(BytePos(22));
1098 assert_eq
!(loc1
.file
.name
, "blork.rs");
1099 assert_eq
!(loc1
.line
, 2);
1100 assert_eq
!(loc1
.col
, CharPos(10));
1102 let loc2
= cm
.lookup_char_pos(BytePos(24));
1103 assert_eq
!(loc2
.file
.name
, "blork2.rs");
1104 assert_eq
!(loc2
.line
, 1);
1105 assert_eq
!(loc2
.col
, CharPos(0));
1108 fn init_code_map_mbc() -> CodeMap
{
1109 let cm
= CodeMap
::new();
1110 // € is a three byte utf8 char.
1112 cm
.new_filemap("blork.rs".to_string(),
1113 "fir€st €€€€ line.\nsecond line".to_string());
1114 let fm2
= cm
.new_filemap("blork2.rs".to_string(),
1115 "first line€€.\n€ second line".to_string());
1117 fm1
.next_line(BytePos(0));
1118 fm1
.next_line(BytePos(22));
1119 fm2
.next_line(BytePos(40));
1120 fm2
.next_line(BytePos(58));
1122 fm1
.record_multibyte_char(BytePos(3), 3);
1123 fm1
.record_multibyte_char(BytePos(9), 3);
1124 fm1
.record_multibyte_char(BytePos(12), 3);
1125 fm1
.record_multibyte_char(BytePos(15), 3);
1126 fm1
.record_multibyte_char(BytePos(18), 3);
1127 fm2
.record_multibyte_char(BytePos(50), 3);
1128 fm2
.record_multibyte_char(BytePos(53), 3);
1129 fm2
.record_multibyte_char(BytePos(58), 3);
1136 // Test bytepos_to_file_charpos in the presence of multi-byte chars
1137 let cm
= init_code_map_mbc();
1139 let cp1
= cm
.bytepos_to_file_charpos(BytePos(3));
1140 assert_eq
!(cp1
, CharPos(3));
1142 let cp2
= cm
.bytepos_to_file_charpos(BytePos(6));
1143 assert_eq
!(cp2
, CharPos(4));
1145 let cp3
= cm
.bytepos_to_file_charpos(BytePos(56));
1146 assert_eq
!(cp3
, CharPos(12));
1148 let cp4
= cm
.bytepos_to_file_charpos(BytePos(61));
1149 assert_eq
!(cp4
, CharPos(15));
1154 // Test span_to_lines for a span ending at the end of filemap
1155 let cm
= init_code_map();
1156 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1157 let file_lines
= cm
.span_to_lines(span
).unwrap();
1159 assert_eq
!(file_lines
.file
.name
, "blork.rs");
1160 assert_eq
!(file_lines
.lines
.len(), 1);
1161 assert_eq
!(file_lines
.lines
[0].line_index
, 1);
1164 /// Given a string like " ^~~~~~~~~~~~ ", produces a span
1165 /// coverting that range. The idea is that the string has the same
1166 /// length as the input, and we uncover the byte positions. Note
1167 /// that this can span lines and so on.
1168 fn span_from_selection(input
: &str, selection
: &str) -> Span
{
1169 assert_eq
!(input
.len(), selection
.len());
1170 let left_index
= selection
.find('
^').unwrap() as u32;
1171 let right_index
= selection
.rfind('
~'
).unwrap() as u32;
1172 Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
1175 fn new_filemap_and_lines(cm
: &CodeMap
, filename
: &str, input
: &str) -> Rc
<FileMap
> {
1176 let fm
= cm
.new_filemap(filename
.to_string(), input
.to_string());
1177 let mut byte_pos
: u32 = 0;
1178 for line
in input
.lines() {
1179 // register the start of this line
1180 fm
.next_line(BytePos(byte_pos
));
1182 // update byte_pos to include this line and the \n at the end
1183 byte_pos
+= line
.len() as u32 + 1;
1188 /// Test span_to_snippet and span_to_lines for a span coverting 3
1189 /// lines in the middle of a file.
1191 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1192 let cm
= CodeMap
::new();
1193 let inputtext
= "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1194 let selection
= " \n ^~\n~~~\n~~~~~ \n \n";
1195 new_filemap_and_lines(&cm
, "blork.rs", inputtext
);
1196 let span
= span_from_selection(inputtext
, selection
);
1198 // check that we are extracting the text we thought we were extracting
1199 assert_eq
!(&cm
.span_to_snippet(span
).unwrap(), "BB\nCCC\nDDDDD");
1201 // check that span_to_lines gives us the complete result with the lines/cols we expected
1202 let lines
= cm
.span_to_lines(span
).unwrap();
1203 let expected
= vec
![
1204 LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) }
,
1205 LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) }
,
1206 LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) }
1208 assert_eq
!(lines
.lines
, expected
);
1213 // Test span_to_snippet for a span ending at the end of filemap
1214 let cm
= init_code_map();
1215 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1216 let snippet
= cm
.span_to_snippet(span
);
1218 assert_eq
!(snippet
, Ok("second line".to_string()));
1223 // Test span_to_str for a span ending at the end of filemap
1224 let cm
= init_code_map();
1225 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1226 let sstr
= cm
.span_to_string(span
);
1228 assert_eq
!(sstr
, "blork.rs:2:1: 2:12");