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
::ops
::{Add, Sub}
;
28 use serialize
::{Encodable, Decodable, Encoder, Decoder}
;
31 // _____________________________________________________________________________
32 // Pos, BytePos, CharPos
36 fn from_usize(n
: usize) -> Self;
37 fn to_usize(&self) -> usize;
40 /// A byte offset. Keep this small (currently 32-bits), as AST contains
42 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)]
43 pub struct BytePos(pub u32);
45 /// A character offset. Because of multibyte utf8 characters, a byte offset
46 /// is not equivalent to a character offset. The CodeMap will convert BytePos
47 /// values to CharPos values as necessary.
48 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)]
49 pub struct CharPos(pub usize);
51 // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
52 // have been unsuccessful
54 impl Pos
for BytePos
{
55 fn from_usize(n
: usize) -> BytePos { BytePos(n as u32) }
56 fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize }
59 impl Add
for BytePos
{
60 type Output
= BytePos
;
62 fn add(self, rhs
: BytePos
) -> BytePos
{
63 BytePos((self.to_usize() + rhs
.to_usize()) as u32)
67 impl Sub
for BytePos
{
68 type Output
= BytePos
;
70 fn sub(self, rhs
: BytePos
) -> BytePos
{
71 BytePos((self.to_usize() - rhs
.to_usize()) as u32)
75 impl Encodable
for BytePos
{
76 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
81 impl Decodable
for BytePos
{
82 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<BytePos
, D
::Error
> {
83 Ok(BytePos(try
!{ d.read_u32() }
))
87 impl Pos
for CharPos
{
88 fn from_usize(n
: usize) -> CharPos { CharPos(n) }
89 fn to_usize(&self) -> usize { let CharPos(n) = *self; n }
92 impl Add
for CharPos
{
93 type Output
= CharPos
;
95 fn add(self, rhs
: CharPos
) -> CharPos
{
96 CharPos(self.to_usize() + rhs
.to_usize())
100 impl Sub
for CharPos
{
101 type Output
= CharPos
;
103 fn sub(self, rhs
: CharPos
) -> CharPos
{
104 CharPos(self.to_usize() - rhs
.to_usize())
108 // _____________________________________________________________________________
112 /// Spans represent a region of code, used for error reporting. Positions in spans
113 /// are *absolute* positions from the beginning of the codemap, not positions
114 /// relative to FileMaps. Methods on the CodeMap can be used to relate spans back
115 /// to the original source.
116 #[derive(Clone, Copy, Debug, Hash)]
120 /// Information about where the macro came from, if this piece of
121 /// code was created by a macro expansion.
125 pub const DUMMY_SP
: Span
= Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION }
;
127 // Generic span to be used for code originating from the command line
128 pub const COMMAND_LINE_SP
: Span
= Span
{ lo
: BytePos(0),
130 expn_id
: COMMAND_LINE_EXPN
};
132 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
133 pub struct Spanned
<T
> {
138 impl PartialEq
for Span
{
139 fn eq(&self, other
: &Span
) -> bool
{
140 return (*self).lo
== (*other
).lo
&& (*self).hi
== (*other
).hi
;
142 fn ne(&self, other
: &Span
) -> bool { !(*self).eq(other) }
147 impl Encodable
for Span
{
148 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
149 // Encode spans as a single u64 in order to cut down on tagging overhead
150 // added by the RBML metadata encoding. The should be solved differently
151 // altogether some time (FIXME #21482)
152 s
.emit_u64( (self.lo
.0 as u64) | ((self.hi
.0 as u64) << 32) )
156 impl Decodable
for Span
{
157 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<Span
, D
::Error
> {
158 let lo_hi
: u64 = try
! { d.read_u64() }
;
159 let lo
= BytePos(lo_hi
as u32);
160 let hi
= BytePos((lo_hi
>> 32) as u32);
165 pub fn spanned
<T
>(lo
: BytePos
, hi
: BytePos
, t
: T
) -> Spanned
<T
> {
166 respan(mk_sp(lo
, hi
), t
)
169 pub fn respan
<T
>(sp
: Span
, t
: T
) -> Spanned
<T
> {
170 Spanned {node: t, span: sp}
173 pub fn dummy_spanned
<T
>(t
: T
) -> Spanned
<T
> {
177 /* assuming that we're not in macro expansion */
178 pub fn mk_sp(lo
: BytePos
, hi
: BytePos
) -> Span
{
179 Span {lo: lo, hi: hi, expn_id: NO_EXPANSION}
182 /// Return the span itself if it doesn't come from a macro expansion,
183 /// otherwise return the call site span up to the `enclosing_sp` by
184 /// following the `expn_info` chain.
185 pub fn original_sp(cm
: &CodeMap
, sp
: Span
, enclosing_sp
: Span
) -> Span
{
186 let call_site1
= cm
.with_expn_info(sp
.expn_id
, |ei
| ei
.map(|ei
| ei
.call_site
));
187 let call_site2
= cm
.with_expn_info(enclosing_sp
.expn_id
, |ei
| ei
.map(|ei
| ei
.call_site
));
188 match (call_site1
, call_site2
) {
190 (Some(call_site1
), Some(call_site2
)) if call_site1
== call_site2
=> sp
,
191 (Some(call_site1
), _
) => original_sp(cm
, call_site1
, enclosing_sp
),
195 // _____________________________________________________________________________
196 // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos
199 /// A source code location used for error reporting
202 /// Information about the original source
203 pub file
: Rc
<FileMap
>,
204 /// The (1-based) line number
206 /// The (0-based) column offset
210 /// A source code location used as the result of lookup_char_pos_adj
211 // Actually, *none* of the clients use the filename *or* file field;
212 // perhaps they should just be removed.
214 pub struct LocWithOpt
{
215 pub filename
: FileName
,
218 pub file
: Option
<Rc
<FileMap
>>,
221 // used to be structural records. Better names, anyone?
223 pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize }
225 pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos }
228 // _____________________________________________________________________________
229 // ExpnFormat, NameAndSpan, ExpnInfo, ExpnId
232 /// The source of expansion.
233 #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)]
234 pub enum ExpnFormat
{
235 /// e.g. #[derive(...)] <item>
239 /// Syntax sugar expansion performed by the compiler (libsyntax::expand).
243 #[derive(Clone, Hash, Debug)]
244 pub struct NameAndSpan
{
245 /// The name of the macro that was invoked to create the thing
248 /// The format with which the macro was invoked.
249 pub format
: ExpnFormat
,
250 /// Whether the macro is allowed to use #[unstable]/feature-gated
251 /// features internally without forcing the whole crate to opt-in
253 pub allow_internal_unstable
: bool
,
254 /// The span of the macro definition itself. The macro may not
255 /// have a sensible definition span (e.g. something defined
256 /// completely inside libsyntax) in which case this is None.
257 pub span
: Option
<Span
>
260 /// Extra information for tracking spans of macro and syntax sugar expansion
261 #[derive(Hash, Debug)]
262 pub struct ExpnInfo
{
263 /// The location of the actual macro invocation or syntax sugar , e.g.
264 /// `let x = foo!();` or `if let Some(y) = x {}`
266 /// This may recursively refer to other macro invocations, e.g. if
267 /// `foo!()` invoked `bar!()` internally, and there was an
268 /// expression inside `bar!`; the call_site of the expression in
269 /// the expansion would point to the `bar!` invocation; that
270 /// call_site span would have its own ExpnInfo, with the call_site
271 /// pointing to the `foo!` invocation.
273 /// Information about the expansion.
274 pub callee
: NameAndSpan
277 #[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)]
278 pub struct ExpnId(u32);
280 pub const NO_EXPANSION
: ExpnId
= ExpnId(!0);
281 // For code appearing from the command line
282 pub const COMMAND_LINE_EXPN
: ExpnId
= ExpnId(!1);
285 pub fn from_u32(id
: u32) -> ExpnId
{
289 pub fn into_u32(self) -> u32 {
294 // _____________________________________________________________________________
295 // FileMap, MultiByteChar, FileName, FileLines
298 pub type FileName
= String
;
300 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
301 pub struct LineInfo
{
302 /// Index of line, starting from 0.
303 pub line_index
: usize,
305 /// Column in line where span begins, starting from 0.
306 pub start_col
: CharPos
,
308 /// Column in line where span ends, starting from 0, exclusive.
309 pub end_col
: CharPos
,
312 pub struct FileLines
{
313 pub file
: Rc
<FileMap
>,
314 pub lines
: Vec
<LineInfo
>
317 /// Identifies an offset of a multi-byte character in a FileMap
318 #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Eq, PartialEq)]
319 pub struct MultiByteChar
{
320 /// The absolute offset of the character in the CodeMap
322 /// The number of bytes, >=2
326 /// A single source in the CodeMap
328 /// The name of the file that the source came from, source that doesn't
329 /// originate from files has names between angle brackets by convention,
332 /// The complete source code
333 pub src
: Option
<Rc
<String
>>,
334 /// The start position of this source in the CodeMap
335 pub start_pos
: BytePos
,
336 /// The end position of this source in the CodeMap
337 pub end_pos
: BytePos
,
338 /// Locations of lines beginnings in the source code
339 pub lines
: RefCell
<Vec
<BytePos
>>,
340 /// Locations of multi-byte characters in the source code
341 pub multibyte_chars
: RefCell
<Vec
<MultiByteChar
>>,
344 impl Encodable
for FileMap
{
345 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
346 s
.emit_struct("FileMap", 5, |s
| {
347 try
! { s.emit_struct_field("name", 0, |s| self.name.encode(s)) }
;
348 try
! { s.emit_struct_field("start_pos", 1, |s| self.start_pos.encode(s)) }
;
349 try
! { s.emit_struct_field("end_pos", 2, |s| self.end_pos.encode(s)) }
;
350 try
! { s
.emit_struct_field("lines", 3, |s
| {
351 let lines
= self.lines
.borrow();
353 try
! { s.emit_u32(lines.len() as u32) }
;
355 if !lines
.is_empty() {
356 // In order to preserve some space, we exploit the fact that
357 // the lines list is sorted and individual lines are
358 // probably not that long. Because of that we can store lines
359 // as a difference list, using as little space as possible
360 // for the differences.
361 let max_line_length
= if lines
.len() == 1 {
365 .map(|w
| w
[1] - w
[0])
366 .map(|bp
| bp
.to_usize())
371 let bytes_per_diff
: u8 = match max_line_length
{
373 0x100 ... 0xFFFF => 2,
377 // Encode the number of bytes used per diff.
378 try
! { bytes_per_diff.encode(s) }
;
380 // Encode the first element.
381 try
! { lines[0].encode(s) }
;
383 let diff_iter
= (&lines
[..]).windows(2)
384 .map(|w
| (w
[1] - w
[0]));
386 match bytes_per_diff
{
387 1 => for diff
in diff_iter { try! { (diff.0 as u8).encode(s) }
},
388 2 => for diff
in diff_iter { try! { (diff.0 as u16).encode(s) }
},
389 4 => for diff
in diff_iter { try! { diff.0.encode(s) }
},
397 s
.emit_struct_field("multibyte_chars", 4, |s
| {
398 (*self.multibyte_chars
.borrow()).encode(s
)
404 impl Decodable
for FileMap
{
405 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<FileMap
, D
::Error
> {
407 d
.read_struct("FileMap", 5, |d
| {
408 let name
: String
= try
! {
409 d
.read_struct_field("name", 0, |d
| Decodable
::decode(d
))
411 let start_pos
: BytePos
= try
! {
412 d
.read_struct_field("start_pos", 1, |d
| Decodable
::decode(d
))
414 let end_pos
: BytePos
= try
! {
415 d
.read_struct_field("end_pos", 2, |d
| Decodable
::decode(d
))
417 let lines
: Vec
<BytePos
> = try
! {
418 d
.read_struct_field("lines", 3, |d
| {
419 let num_lines
: u32 = try
! { Decodable::decode(d) }
;
420 let mut lines
= Vec
::with_capacity(num_lines
as usize);
423 // Read the number of bytes used per diff.
424 let bytes_per_diff
: u8 = try
! { Decodable::decode(d) }
;
426 // Read the first element.
427 let mut line_start
: BytePos
= try
! { Decodable::decode(d) }
;
428 lines
.push(line_start
);
430 for _
in 1..num_lines
{
431 let diff
= match bytes_per_diff
{
432 1 => try
! { d.read_u8() }
as u32,
433 2 => try
! { d.read_u16() }
as u32,
434 4 => try
! { d.read_u32() }
,
438 line_start
= line_start
+ BytePos(diff
);
440 lines
.push(line_start
);
447 let multibyte_chars
: Vec
<MultiByteChar
> = try
! {
448 d
.read_struct_field("multibyte_chars", 4, |d
| Decodable
::decode(d
))
452 start_pos
: start_pos
,
455 lines
: RefCell
::new(lines
),
456 multibyte_chars
: RefCell
::new(multibyte_chars
)
462 impl fmt
::Debug
for FileMap
{
463 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
464 write
!(fmt
, "FileMap({})", self.name
)
469 /// EFFECT: register a start-of-line offset in the
470 /// table of line-beginnings.
471 /// UNCHECKED INVARIANT: these offsets must be added in the right
472 /// order and must be in the right places; there is shared knowledge
473 /// about what ends a line between this file and parse.rs
474 /// WARNING: pos param here is the offset relative to start of CodeMap,
475 /// and CodeMap will append a newline when adding a filemap without a newline at the end,
476 /// so the safe way to call this is with value calculated as
477 /// filemap.start_pos + newline_offset_relative_to_the_start_of_filemap.
478 pub fn next_line(&self, pos
: BytePos
) {
479 // the new charpos must be > the last one (or it's the first one).
480 let mut lines
= self.lines
.borrow_mut();
481 let line_len
= lines
.len();
482 assert
!(line_len
== 0 || ((*lines
)[line_len
- 1] < pos
));
486 /// get a line from the list of pre-computed line-beginnings.
487 /// line-number here is 0-based.
488 pub fn get_line(&self, line_number
: usize) -> Option
<&str> {
491 let lines
= self.lines
.borrow();
492 lines
.get(line_number
).map(|&line
| {
493 let begin
: BytePos
= line
- self.start_pos
;
494 let begin
= begin
.to_usize();
495 let slice
= &src
[begin
..];
496 match slice
.find('
\n'
) {
497 Some(e
) => &slice
[..e
],
506 pub fn record_multibyte_char(&self, pos
: BytePos
, bytes
: usize) {
507 assert
!(bytes
>=2 && bytes
<= 4);
508 let mbc
= MultiByteChar
{
512 self.multibyte_chars
.borrow_mut().push(mbc
);
515 pub fn is_real_file(&self) -> bool
{
516 !(self.name
.starts_with("<") &&
517 self.name
.ends_with(">"))
520 pub fn is_imported(&self) -> bool
{
526 // _____________________________________________________________________________
531 pub files
: RefCell
<Vec
<Rc
<FileMap
>>>,
532 expansions
: RefCell
<Vec
<ExpnInfo
>>
536 pub fn new() -> CodeMap
{
538 files
: RefCell
::new(Vec
::new()),
539 expansions
: RefCell
::new(Vec
::new()),
543 pub fn new_filemap(&self, filename
: FileName
, mut src
: String
) -> Rc
<FileMap
> {
544 let mut files
= self.files
.borrow_mut();
545 let start_pos
= match files
.last() {
547 Some(last
) => last
.end_pos
.to_usize(),
550 // Remove utf-8 BOM if any.
551 if src
.starts_with("\u{feff}") {
555 // Append '\n' in case it's not already there.
556 // This is a workaround to prevent CodeMap.lookup_filemap_idx from
557 // accidentally overflowing into the next filemap in case the last byte
558 // of span is also the last byte of filemap, which leads to incorrect
559 // results from CodeMap.span_to_*.
560 if !src
.is_empty() && !src
.ends_with("\n") {
564 let end_pos
= start_pos
+ src
.len();
566 let filemap
= Rc
::new(FileMap
{
568 src
: Some(Rc
::new(src
)),
569 start_pos
: Pos
::from_usize(start_pos
),
570 end_pos
: Pos
::from_usize(end_pos
),
571 lines
: RefCell
::new(Vec
::new()),
572 multibyte_chars
: RefCell
::new(Vec
::new()),
575 files
.push(filemap
.clone());
580 /// Allocates a new FileMap representing a source file from an external
581 /// crate. The source code of such an "imported filemap" is not available,
582 /// but we still know enough to generate accurate debuginfo location
583 /// information for things inlined from other crates.
584 pub fn new_imported_filemap(&self,
587 mut file_local_lines
: Vec
<BytePos
>,
588 mut file_local_multibyte_chars
: Vec
<MultiByteChar
>)
590 let mut files
= self.files
.borrow_mut();
591 let start_pos
= match files
.last() {
593 Some(last
) => last
.end_pos
.to_usize(),
596 let end_pos
= Pos
::from_usize(start_pos
+ source_len
);
597 let start_pos
= Pos
::from_usize(start_pos
);
599 for pos
in &mut file_local_lines
{
600 *pos
= *pos
+ start_pos
;
603 for mbc
in &mut file_local_multibyte_chars
{
604 mbc
.pos
= mbc
.pos
+ start_pos
;
607 let filemap
= Rc
::new(FileMap
{
610 start_pos
: start_pos
,
612 lines
: RefCell
::new(file_local_lines
),
613 multibyte_chars
: RefCell
::new(file_local_multibyte_chars
),
616 files
.push(filemap
.clone());
621 pub fn mk_substr_filename(&self, sp
: Span
) -> String
{
622 let pos
= self.lookup_char_pos(sp
.lo
);
623 (format
!("<{}:{}:{}>",
626 pos
.col
.to_usize() + 1)).to_string()
629 /// Lookup source information about a BytePos
630 pub fn lookup_char_pos(&self, pos
: BytePos
) -> Loc
{
631 let FileMapAndLine {fm: f, line: a}
= self.lookup_line(pos
);
632 let line
= a
+ 1; // Line numbers start at 1
633 let chpos
= self.bytepos_to_file_charpos(pos
);
634 let linebpos
= (*f
.lines
.borrow())[a
];
635 let linechpos
= self.bytepos_to_file_charpos(linebpos
);
636 debug
!("byte pos {:?} is on the line at byte pos {:?}",
638 debug
!("char pos {:?} is on the line at char pos {:?}",
640 debug
!("byte is on line: {}", line
);
641 assert
!(chpos
>= linechpos
);
645 col
: chpos
- linechpos
649 fn lookup_line(&self, pos
: BytePos
) -> FileMapAndLine
{
650 let idx
= self.lookup_filemap_idx(pos
);
652 let files
= self.files
.borrow();
653 let f
= (*files
)[idx
].clone();
656 let lines
= f
.lines
.borrow();
657 let mut b
= lines
.len();
660 if (*lines
)[m
] > pos { b = m; }
else { a = m; }
663 FileMapAndLine {fm: f, line: a}
666 pub fn lookup_char_pos_adj(&self, pos
: BytePos
) -> LocWithOpt
{
667 let loc
= self.lookup_char_pos(pos
);
669 filename
: loc
.file
.name
.to_string(),
676 pub fn span_to_string(&self, sp
: Span
) -> String
{
677 if self.files
.borrow().is_empty() && sp
== DUMMY_SP
{
678 return "no-location".to_string();
681 let lo
= self.lookup_char_pos_adj(sp
.lo
);
682 let hi
= self.lookup_char_pos_adj(sp
.hi
);
683 return (format
!("{}:{}:{}: {}:{}",
686 lo
.col
.to_usize() + 1,
688 hi
.col
.to_usize() + 1)).to_string()
691 pub fn span_to_filename(&self, sp
: Span
) -> FileName
{
692 self.lookup_char_pos(sp
.lo
).file
.name
.to_string()
695 pub fn span_to_lines(&self, sp
: Span
) -> FileLinesResult
{
697 return Err(SpanLinesError
::IllFormedSpan(sp
));
700 let lo
= self.lookup_char_pos(sp
.lo
);
701 let hi
= self.lookup_char_pos(sp
.hi
);
703 if lo
.file
.start_pos
!= hi
.file
.start_pos
{
704 return Err(SpanLinesError
::DistinctSources(DistinctSources
{
705 begin
: (lo
.file
.name
.clone(), lo
.file
.start_pos
),
706 end
: (hi
.file
.name
.clone(), hi
.file
.start_pos
),
709 assert
!(hi
.line
>= lo
.line
);
711 let mut lines
= Vec
::with_capacity(hi
.line
- lo
.line
+ 1);
713 // The span starts partway through the first line,
714 // but after that it starts from offset 0.
715 let mut start_col
= lo
.col
;
717 // For every line but the last, it extends from `start_col`
718 // and to the end of the line. Be careful because the line
719 // numbers in Loc are 1-based, so we subtract 1 to get 0-based
721 for line_index
in lo
.line
-1 .. hi
.line
-1 {
722 let line_len
= lo
.file
.get_line(line_index
).map(|s
| s
.len()).unwrap_or(0);
723 lines
.push(LineInfo
{ line_index
: line_index
,
724 start_col
: start_col
,
725 end_col
: CharPos
::from_usize(line_len
) });
726 start_col
= CharPos
::from_usize(0);
729 // For the last line, it extends from `start_col` to `hi.col`:
730 lines
.push(LineInfo
{ line_index
: hi
.line
- 1,
731 start_col
: start_col
,
734 Ok(FileLines {file: lo.file, lines: lines}
)
737 pub fn span_to_snippet(&self, sp
: Span
) -> Result
<String
, SpanSnippetError
> {
739 return Err(SpanSnippetError
::IllFormedSpan(sp
));
742 let local_begin
= self.lookup_byte_offset(sp
.lo
);
743 let local_end
= self.lookup_byte_offset(sp
.hi
);
745 if local_begin
.fm
.start_pos
!= local_end
.fm
.start_pos
{
746 return Err(SpanSnippetError
::DistinctSources(DistinctSources
{
747 begin
: (local_begin
.fm
.name
.clone(),
748 local_begin
.fm
.start_pos
),
749 end
: (local_end
.fm
.name
.clone(),
750 local_end
.fm
.start_pos
)
753 match local_begin
.fm
.src
{
755 let start_index
= local_begin
.pos
.to_usize();
756 let end_index
= local_end
.pos
.to_usize();
757 let source_len
= (local_begin
.fm
.end_pos
-
758 local_begin
.fm
.start_pos
).to_usize();
760 if start_index
> end_index
|| end_index
> source_len
{
761 return Err(SpanSnippetError
::MalformedForCodemap(
762 MalformedCodemapPositions
{
763 name
: local_begin
.fm
.name
.clone(),
764 source_len
: source_len
,
765 begin_pos
: local_begin
.pos
,
766 end_pos
: local_end
.pos
,
770 return Ok((&src
[start_index
..end_index
]).to_string())
773 return Err(SpanSnippetError
::SourceNotAvailable
{
774 filename
: local_begin
.fm
.name
.clone()
781 pub fn get_filemap(&self, filename
: &str) -> Rc
<FileMap
> {
782 for fm
in &*self.files
.borrow() {
783 if filename
== fm
.name
{
787 panic
!("asking for {} which we don't know about", filename
);
790 /// For a global BytePos compute the local offset within the containing FileMap
791 pub fn lookup_byte_offset(&self, bpos
: BytePos
) -> FileMapAndBytePos
{
792 let idx
= self.lookup_filemap_idx(bpos
);
793 let fm
= (*self.files
.borrow())[idx
].clone();
794 let offset
= bpos
- fm
.start_pos
;
795 FileMapAndBytePos {fm: fm, pos: offset}
798 /// Converts an absolute BytePos to a CharPos relative to the filemap and above.
799 pub fn bytepos_to_file_charpos(&self, bpos
: BytePos
) -> CharPos
{
800 let idx
= self.lookup_filemap_idx(bpos
);
801 let files
= self.files
.borrow();
802 let map
= &(*files
)[idx
];
804 // The number of extra bytes due to multibyte chars in the FileMap
805 let mut total_extra_bytes
= 0;
807 for mbc
in &*map
.multibyte_chars
.borrow() {
808 debug
!("{}-byte char at {:?}", mbc
.bytes
, mbc
.pos
);
810 // every character is at least one byte, so we only
811 // count the actual extra bytes.
812 total_extra_bytes
+= mbc
.bytes
- 1;
813 // We should never see a byte position in the middle of a
815 assert
!(bpos
.to_usize() >= mbc
.pos
.to_usize() + mbc
.bytes
);
821 assert
!(map
.start_pos
.to_usize() + total_extra_bytes
<= bpos
.to_usize());
822 CharPos(bpos
.to_usize() - map
.start_pos
.to_usize() - total_extra_bytes
)
825 fn lookup_filemap_idx(&self, pos
: BytePos
) -> usize {
826 let files
= self.files
.borrow();
828 let len
= files
.len();
833 if files
[m
].start_pos
> pos
{
839 // There can be filemaps with length 0. These have the same start_pos as
840 // the previous filemap, but are not the filemaps we want (because they
841 // are length 0, they cannot contain what we are looking for). So,
842 // rewind until we find a useful filemap.
844 let lines
= files
[a
].lines
.borrow();
846 if !lines
.is_empty() {
850 panic
!("position {} does not resolve to a source location",
856 panic
!("position {} does not resolve to a source location",
863 pub fn record_expansion(&self, expn_info
: ExpnInfo
) -> ExpnId
{
864 let mut expansions
= self.expansions
.borrow_mut();
865 expansions
.push(expn_info
);
866 let len
= expansions
.len();
867 if len
> u32::max_value() as usize {
868 panic
!("too many ExpnInfo's!");
870 ExpnId(len
as u32 - 1)
873 pub fn with_expn_info
<T
, F
>(&self, id
: ExpnId
, f
: F
) -> T
where
874 F
: FnOnce(Option
<&ExpnInfo
>) -> T
,
877 NO_EXPANSION
| COMMAND_LINE_EXPN
=> f(None
),
878 ExpnId(i
) => f(Some(&(*self.expansions
.borrow())[i
as usize]))
882 /// Check if a span is "internal" to a macro in which #[unstable]
883 /// items can be used (that is, a macro marked with
884 /// `#[allow_internal_unstable]`).
885 pub fn span_allows_unstable(&self, span
: Span
) -> bool
{
886 debug
!("span_allows_unstable(span = {:?})", span
);
887 let mut allows_unstable
= false;
888 let mut expn_id
= span
.expn_id
;
890 let quit
= self.with_expn_info(expn_id
, |expninfo
| {
891 debug
!("span_allows_unstable: expninfo = {:?}", expninfo
);
892 expninfo
.map_or(/* hit the top level */ true, |info
| {
894 let span_comes_from_this_expansion
=
895 info
.callee
.span
.map_or(span
== info
.call_site
, |mac_span
| {
896 mac_span
.lo
<= span
.lo
&& span
.hi
<= mac_span
.hi
899 debug
!("span_allows_unstable: from this expansion? {}, allows unstable? {}",
900 span_comes_from_this_expansion
,
901 info
.callee
.allow_internal_unstable
);
902 if span_comes_from_this_expansion
{
903 allows_unstable
= info
.callee
.allow_internal_unstable
;
904 // we've found the right place, stop looking
907 // not the right place, keep looking
908 expn_id
= info
.call_site
.expn_id
;
917 debug
!("span_allows_unstable? {}", allows_unstable
);
922 // _____________________________________________________________________________
923 // SpanLinesError, SpanSnippetError, DistinctSources, MalformedCodemapPositions
926 pub type FileLinesResult
= Result
<FileLines
, SpanLinesError
>;
928 #[derive(Clone, PartialEq, Eq, Debug)]
929 pub enum SpanLinesError
{
931 DistinctSources(DistinctSources
),
934 #[derive(Clone, PartialEq, Eq, Debug)]
935 pub enum SpanSnippetError
{
937 DistinctSources(DistinctSources
),
938 MalformedForCodemap(MalformedCodemapPositions
),
939 SourceNotAvailable { filename: String }
942 #[derive(Clone, PartialEq, Eq, Debug)]
943 pub struct DistinctSources
{
944 begin
: (String
, BytePos
),
945 end
: (String
, BytePos
)
948 #[derive(Clone, PartialEq, Eq, Debug)]
949 pub struct MalformedCodemapPositions
{
957 // _____________________________________________________________________________
968 let cm
= CodeMap
::new();
969 let fm
= cm
.new_filemap("blork.rs".to_string(),
970 "first line.\nsecond line".to_string());
971 fm
.next_line(BytePos(0));
972 assert_eq
!(fm
.get_line(0), Some("first line."));
973 // TESTING BROKEN BEHAVIOR:
974 fm
.next_line(BytePos(10));
975 assert_eq
!(fm
.get_line(1), Some("."));
981 let cm
= CodeMap
::new();
982 let fm
= cm
.new_filemap("blork.rs".to_string(),
983 "first line.\nsecond line".to_string());
984 // TESTING *REALLY* BROKEN BEHAVIOR:
985 fm
.next_line(BytePos(0));
986 fm
.next_line(BytePos(10));
987 fm
.next_line(BytePos(2));
990 fn init_code_map() -> CodeMap
{
991 let cm
= CodeMap
::new();
992 let fm1
= cm
.new_filemap("blork.rs".to_string(),
993 "first line.\nsecond line".to_string());
994 let fm2
= cm
.new_filemap("empty.rs".to_string(),
996 let fm3
= cm
.new_filemap("blork2.rs".to_string(),
997 "first line.\nsecond line".to_string());
999 fm1
.next_line(BytePos(0));
1000 fm1
.next_line(BytePos(12));
1001 fm2
.next_line(BytePos(24));
1002 fm3
.next_line(BytePos(24));
1003 fm3
.next_line(BytePos(34));
1010 // Test lookup_byte_offset
1011 let cm
= init_code_map();
1013 let fmabp1
= cm
.lookup_byte_offset(BytePos(22));
1014 assert_eq
!(fmabp1
.fm
.name
, "blork.rs");
1015 assert_eq
!(fmabp1
.pos
, BytePos(22));
1017 let fmabp2
= cm
.lookup_byte_offset(BytePos(24));
1018 assert_eq
!(fmabp2
.fm
.name
, "blork2.rs");
1019 assert_eq
!(fmabp2
.pos
, BytePos(0));
1024 // Test bytepos_to_file_charpos
1025 let cm
= init_code_map();
1027 let cp1
= cm
.bytepos_to_file_charpos(BytePos(22));
1028 assert_eq
!(cp1
, CharPos(22));
1030 let cp2
= cm
.bytepos_to_file_charpos(BytePos(24));
1031 assert_eq
!(cp2
, CharPos(0));
1036 // Test zero-length filemaps.
1037 let cm
= init_code_map();
1039 let loc1
= cm
.lookup_char_pos(BytePos(22));
1040 assert_eq
!(loc1
.file
.name
, "blork.rs");
1041 assert_eq
!(loc1
.line
, 2);
1042 assert_eq
!(loc1
.col
, CharPos(10));
1044 let loc2
= cm
.lookup_char_pos(BytePos(24));
1045 assert_eq
!(loc2
.file
.name
, "blork2.rs");
1046 assert_eq
!(loc2
.line
, 1);
1047 assert_eq
!(loc2
.col
, CharPos(0));
1050 fn init_code_map_mbc() -> CodeMap
{
1051 let cm
= CodeMap
::new();
1052 // € is a three byte utf8 char.
1054 cm
.new_filemap("blork.rs".to_string(),
1055 "fir€st €€€€ line.\nsecond line".to_string());
1056 let fm2
= cm
.new_filemap("blork2.rs".to_string(),
1057 "first line€€.\n€ second line".to_string());
1059 fm1
.next_line(BytePos(0));
1060 fm1
.next_line(BytePos(22));
1061 fm2
.next_line(BytePos(40));
1062 fm2
.next_line(BytePos(58));
1064 fm1
.record_multibyte_char(BytePos(3), 3);
1065 fm1
.record_multibyte_char(BytePos(9), 3);
1066 fm1
.record_multibyte_char(BytePos(12), 3);
1067 fm1
.record_multibyte_char(BytePos(15), 3);
1068 fm1
.record_multibyte_char(BytePos(18), 3);
1069 fm2
.record_multibyte_char(BytePos(50), 3);
1070 fm2
.record_multibyte_char(BytePos(53), 3);
1071 fm2
.record_multibyte_char(BytePos(58), 3);
1078 // Test bytepos_to_file_charpos in the presence of multi-byte chars
1079 let cm
= init_code_map_mbc();
1081 let cp1
= cm
.bytepos_to_file_charpos(BytePos(3));
1082 assert_eq
!(cp1
, CharPos(3));
1084 let cp2
= cm
.bytepos_to_file_charpos(BytePos(6));
1085 assert_eq
!(cp2
, CharPos(4));
1087 let cp3
= cm
.bytepos_to_file_charpos(BytePos(56));
1088 assert_eq
!(cp3
, CharPos(12));
1090 let cp4
= cm
.bytepos_to_file_charpos(BytePos(61));
1091 assert_eq
!(cp4
, CharPos(15));
1096 // Test span_to_lines for a span ending at the end of filemap
1097 let cm
= init_code_map();
1098 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1099 let file_lines
= cm
.span_to_lines(span
).unwrap();
1101 assert_eq
!(file_lines
.file
.name
, "blork.rs");
1102 assert_eq
!(file_lines
.lines
.len(), 1);
1103 assert_eq
!(file_lines
.lines
[0].line_index
, 1);
1106 /// Given a string like " ^~~~~~~~~~~~ ", produces a span
1107 /// coverting that range. The idea is that the string has the same
1108 /// length as the input, and we uncover the byte positions. Note
1109 /// that this can span lines and so on.
1110 fn span_from_selection(input
: &str, selection
: &str) -> Span
{
1111 assert_eq
!(input
.len(), selection
.len());
1112 let left_index
= selection
.find('
^').unwrap() as u32;
1113 let right_index
= selection
.rfind('
~'
).unwrap() as u32;
1114 Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
1117 fn new_filemap_and_lines(cm
: &CodeMap
, filename
: &str, input
: &str) -> Rc
<FileMap
> {
1118 let fm
= cm
.new_filemap(filename
.to_string(), input
.to_string());
1119 let mut byte_pos
: u32 = 0;
1120 for line
in input
.lines() {
1121 // register the start of this line
1122 fm
.next_line(BytePos(byte_pos
));
1124 // update byte_pos to include this line and the \n at the end
1125 byte_pos
+= line
.len() as u32 + 1;
1130 /// Test span_to_snippet and span_to_lines for a span coverting 3
1131 /// lines in the middle of a file.
1133 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1134 let cm
= CodeMap
::new();
1135 let inputtext
= "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1136 let selection
= " \n ^~\n~~~\n~~~~~ \n \n";
1137 new_filemap_and_lines(&cm
, "blork.rs", inputtext
);
1138 let span
= span_from_selection(inputtext
, selection
);
1140 // check that we are extracting the text we thought we were extracting
1141 assert_eq
!(&cm
.span_to_snippet(span
).unwrap(), "BB\nCCC\nDDDDD");
1143 // check that span_to_lines gives us the complete result with the lines/cols we expected
1144 let lines
= cm
.span_to_lines(span
).unwrap();
1145 let expected
= vec
![
1146 LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) }
,
1147 LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) }
,
1148 LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) }
1150 assert_eq
!(lines
.lines
, expected
);
1155 // Test span_to_snippet for a span ending at the end of filemap
1156 let cm
= init_code_map();
1157 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1158 let snippet
= cm
.span_to_snippet(span
);
1160 assert_eq
!(snippet
, Ok("second line".to_string()));
1165 // Test span_to_str for a span ending at the end of filemap
1166 let cm
= init_code_map();
1167 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1168 let sstr
= cm
.span_to_string(span
);
1170 assert_eq
!(sstr
, "blork.rs:2:1: 2:12");