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}
;
34 // _____________________________________________________________________________
35 // Pos, BytePos, CharPos
39 fn from_usize(n
: usize) -> Self;
40 fn to_usize(&self) -> usize;
43 /// A byte offset. Keep this small (currently 32-bits), as AST contains
45 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)]
46 pub struct BytePos(pub u32);
48 /// A character offset. Because of multibyte utf8 characters, a byte offset
49 /// is not equivalent to a character offset. The CodeMap will convert BytePos
50 /// values to CharPos values as necessary.
51 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)]
52 pub struct CharPos(pub usize);
54 // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
55 // have been unsuccessful
57 impl Pos
for BytePos
{
58 fn from_usize(n
: usize) -> BytePos { BytePos(n as u32) }
59 fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize }
62 impl Add
for BytePos
{
63 type Output
= BytePos
;
65 fn add(self, rhs
: BytePos
) -> BytePos
{
66 BytePos((self.to_usize() + rhs
.to_usize()) as u32)
70 impl Sub
for BytePos
{
71 type Output
= BytePos
;
73 fn sub(self, rhs
: BytePos
) -> BytePos
{
74 BytePos((self.to_usize() - rhs
.to_usize()) as u32)
78 impl Encodable
for BytePos
{
79 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
84 impl Decodable
for BytePos
{
85 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<BytePos
, D
::Error
> {
86 Ok(BytePos(try
!{ d.read_u32() }
))
90 impl Pos
for CharPos
{
91 fn from_usize(n
: usize) -> CharPos { CharPos(n) }
92 fn to_usize(&self) -> usize { let CharPos(n) = *self; n }
95 impl Add
for CharPos
{
96 type Output
= CharPos
;
98 fn add(self, rhs
: CharPos
) -> CharPos
{
99 CharPos(self.to_usize() + rhs
.to_usize())
103 impl Sub
for CharPos
{
104 type Output
= CharPos
;
106 fn sub(self, rhs
: CharPos
) -> CharPos
{
107 CharPos(self.to_usize() - rhs
.to_usize())
111 // _____________________________________________________________________________
115 /// Spans represent a region of code, used for error reporting. Positions in spans
116 /// are *absolute* positions from the beginning of the codemap, not positions
117 /// relative to FileMaps. Methods on the CodeMap can be used to relate spans back
118 /// to the original source.
119 /// You must be careful if the span crosses more than one file - you will not be
120 /// able to use many of the functions on spans in codemap and you cannot assume
121 /// that the length of the span = hi - lo; there may be space in the BytePos
122 /// range between files.
123 #[derive(Clone, Copy, Hash)]
127 /// Information about where the macro came from, if this piece of
128 /// code was created by a macro expansion.
132 pub const DUMMY_SP
: Span
= Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION }
;
134 // Generic span to be used for code originating from the command line
135 pub const COMMAND_LINE_SP
: Span
= Span
{ lo
: BytePos(0),
137 expn_id
: COMMAND_LINE_EXPN
};
140 /// Returns `self` if `self` is not the dummy span, and `other` otherwise.
141 pub fn substitute_dummy(self, other
: Span
) -> Span
{
142 if self == DUMMY_SP { other }
else { self }
145 pub fn contains(self, other
: Span
) -> bool
{
146 self.lo
<= other
.lo
&& other
.hi
<= self.hi
150 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
151 pub struct Spanned
<T
> {
156 impl PartialEq
for Span
{
157 fn eq(&self, other
: &Span
) -> bool
{
158 return (*self).lo
== (*other
).lo
&& (*self).hi
== (*other
).hi
;
160 fn ne(&self, other
: &Span
) -> bool { !(*self).eq(other) }
165 impl Encodable
for Span
{
166 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
167 // Encode spans as a single u64 in order to cut down on tagging overhead
168 // added by the RBML metadata encoding. The should be solved differently
169 // altogether some time (FIXME #21482)
170 s
.emit_u64( (self.lo
.0 as u64) | ((self.hi
.0 as u64) << 32) )
174 impl Decodable
for Span
{
175 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<Span
, D
::Error
> {
176 let lo_hi
: u64 = try
! { d.read_u64() }
;
177 let lo
= BytePos(lo_hi
as u32);
178 let hi
= BytePos((lo_hi
>> 32) as u32);
183 fn default_span_debug(span
: Span
, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
184 write
!(f
, "Span {{ lo: {:?}, hi: {:?}, expn_id: {:?} }}",
185 span
.lo
, span
.hi
, span
.expn_id
)
188 thread_local
!(pub static SPAN_DEBUG
: Cell
<fn(Span
, &mut fmt
::Formatter
) -> fmt
::Result
> =
189 Cell
::new(default_span_debug
));
191 impl fmt
::Debug
for Span
{
192 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
193 SPAN_DEBUG
.with(|span_debug
| span_debug
.get()(*self, f
))
197 pub fn spanned
<T
>(lo
: BytePos
, hi
: BytePos
, t
: T
) -> Spanned
<T
> {
198 respan(mk_sp(lo
, hi
), t
)
201 pub fn respan
<T
>(sp
: Span
, t
: T
) -> Spanned
<T
> {
202 Spanned {node: t, span: sp}
205 pub fn dummy_spanned
<T
>(t
: T
) -> Spanned
<T
> {
209 /* assuming that we're not in macro expansion */
210 pub fn mk_sp(lo
: BytePos
, hi
: BytePos
) -> Span
{
211 Span {lo: lo, hi: hi, expn_id: NO_EXPANSION}
214 /// Return the span itself if it doesn't come from a macro expansion,
215 /// otherwise return the call site span up to the `enclosing_sp` by
216 /// following the `expn_info` chain.
217 pub fn original_sp(cm
: &CodeMap
, sp
: Span
, enclosing_sp
: Span
) -> Span
{
218 let call_site1
= cm
.with_expn_info(sp
.expn_id
, |ei
| ei
.map(|ei
| ei
.call_site
));
219 let call_site2
= cm
.with_expn_info(enclosing_sp
.expn_id
, |ei
| ei
.map(|ei
| ei
.call_site
));
220 match (call_site1
, call_site2
) {
222 (Some(call_site1
), Some(call_site2
)) if call_site1
== call_site2
=> sp
,
223 (Some(call_site1
), _
) => original_sp(cm
, call_site1
, enclosing_sp
),
227 // _____________________________________________________________________________
228 // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos
231 /// A source code location used for error reporting
234 /// Information about the original source
235 pub file
: Rc
<FileMap
>,
236 /// The (1-based) line number
238 /// The (0-based) column offset
242 /// A source code location used as the result of lookup_char_pos_adj
243 // Actually, *none* of the clients use the filename *or* file field;
244 // perhaps they should just be removed.
246 pub struct LocWithOpt
{
247 pub filename
: FileName
,
250 pub file
: Option
<Rc
<FileMap
>>,
253 // used to be structural records. Better names, anyone?
255 pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize }
257 pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos }
260 // _____________________________________________________________________________
261 // ExpnFormat, NameAndSpan, ExpnInfo, ExpnId
264 /// The source of expansion.
265 #[derive(Clone, Hash, Debug, PartialEq, Eq)]
266 pub enum ExpnFormat
{
267 /// e.g. #[derive(...)] <item>
268 MacroAttribute(Name
),
273 #[derive(Clone, Hash, Debug)]
274 pub struct NameAndSpan
{
275 /// The format with which the macro was invoked.
276 pub format
: ExpnFormat
,
277 /// Whether the macro is allowed to use #[unstable]/feature-gated
278 /// features internally without forcing the whole crate to opt-in
280 pub allow_internal_unstable
: bool
,
281 /// The span of the macro definition itself. The macro may not
282 /// have a sensible definition span (e.g. something defined
283 /// completely inside libsyntax) in which case this is None.
284 pub span
: Option
<Span
>
288 pub fn name(&self) -> Name
{
290 ExpnFormat
::MacroAttribute(s
) => s
,
291 ExpnFormat
::MacroBang(s
) => s
,
296 /// Extra information for tracking spans of macro and syntax sugar expansion
297 #[derive(Hash, Debug)]
298 pub struct ExpnInfo
{
299 /// The location of the actual macro invocation or syntax sugar , e.g.
300 /// `let x = foo!();` or `if let Some(y) = x {}`
302 /// This may recursively refer to other macro invocations, e.g. if
303 /// `foo!()` invoked `bar!()` internally, and there was an
304 /// expression inside `bar!`; the call_site of the expression in
305 /// the expansion would point to the `bar!` invocation; that
306 /// call_site span would have its own ExpnInfo, with the call_site
307 /// pointing to the `foo!` invocation.
309 /// Information about the expansion.
310 pub callee
: NameAndSpan
313 #[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)]
314 pub struct ExpnId(u32);
316 pub const NO_EXPANSION
: ExpnId
= ExpnId(!0);
317 // For code appearing from the command line
318 pub const COMMAND_LINE_EXPN
: ExpnId
= ExpnId(!1);
321 pub fn from_u32(id
: u32) -> ExpnId
{
325 pub fn into_u32(self) -> u32 {
330 // _____________________________________________________________________________
331 // FileMap, MultiByteChar, FileName, FileLines
334 pub type FileName
= String
;
336 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
337 pub struct LineInfo
{
338 /// Index of line, starting from 0.
339 pub line_index
: usize,
341 /// Column in line where span begins, starting from 0.
342 pub start_col
: CharPos
,
344 /// Column in line where span ends, starting from 0, exclusive.
345 pub end_col
: CharPos
,
348 pub struct FileLines
{
349 pub file
: Rc
<FileMap
>,
350 pub lines
: Vec
<LineInfo
>
353 /// Identifies an offset of a multi-byte character in a FileMap
354 #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Eq, PartialEq)]
355 pub struct MultiByteChar
{
356 /// The absolute offset of the character in the CodeMap
358 /// The number of bytes, >=2
362 /// A single source in the CodeMap.
364 /// The name of the file that the source came from, source that doesn't
365 /// originate from files has names between angle brackets by convention,
368 /// The complete source code
369 pub src
: Option
<Rc
<String
>>,
370 /// The start position of this source in the CodeMap
371 pub start_pos
: BytePos
,
372 /// The end position of this source in the CodeMap
373 pub end_pos
: BytePos
,
374 /// Locations of lines beginnings in the source code
375 pub lines
: RefCell
<Vec
<BytePos
>>,
376 /// Locations of multi-byte characters in the source code
377 pub multibyte_chars
: RefCell
<Vec
<MultiByteChar
>>,
380 impl Encodable
for FileMap
{
381 fn encode
<S
: Encoder
>(&self, s
: &mut S
) -> Result
<(), S
::Error
> {
382 s
.emit_struct("FileMap", 5, |s
| {
383 try
! { s.emit_struct_field("name", 0, |s| self.name.encode(s)) }
;
384 try
! { s.emit_struct_field("start_pos", 1, |s| self.start_pos.encode(s)) }
;
385 try
! { s.emit_struct_field("end_pos", 2, |s| self.end_pos.encode(s)) }
;
386 try
! { s
.emit_struct_field("lines", 3, |s
| {
387 let lines
= self.lines
.borrow();
389 try
! { s.emit_u32(lines.len() as u32) }
;
391 if !lines
.is_empty() {
392 // In order to preserve some space, we exploit the fact that
393 // the lines list is sorted and individual lines are
394 // probably not that long. Because of that we can store lines
395 // as a difference list, using as little space as possible
396 // for the differences.
397 let max_line_length
= if lines
.len() == 1 {
401 .map(|w
| w
[1] - w
[0])
402 .map(|bp
| bp
.to_usize())
407 let bytes_per_diff
: u8 = match max_line_length
{
409 0x100 ... 0xFFFF => 2,
413 // Encode the number of bytes used per diff.
414 try
! { bytes_per_diff.encode(s) }
;
416 // Encode the first element.
417 try
! { lines[0].encode(s) }
;
419 let diff_iter
= (&lines
[..]).windows(2)
420 .map(|w
| (w
[1] - w
[0]));
422 match bytes_per_diff
{
423 1 => for diff
in diff_iter { try! { (diff.0 as u8).encode(s) }
},
424 2 => for diff
in diff_iter { try! { (diff.0 as u16).encode(s) }
},
425 4 => for diff
in diff_iter { try! { diff.0.encode(s) }
},
433 s
.emit_struct_field("multibyte_chars", 4, |s
| {
434 (*self.multibyte_chars
.borrow()).encode(s
)
440 impl Decodable
for FileMap
{
441 fn decode
<D
: Decoder
>(d
: &mut D
) -> Result
<FileMap
, D
::Error
> {
443 d
.read_struct("FileMap", 5, |d
| {
444 let name
: String
= try
! {
445 d
.read_struct_field("name", 0, |d
| Decodable
::decode(d
))
447 let start_pos
: BytePos
= try
! {
448 d
.read_struct_field("start_pos", 1, |d
| Decodable
::decode(d
))
450 let end_pos
: BytePos
= try
! {
451 d
.read_struct_field("end_pos", 2, |d
| Decodable
::decode(d
))
453 let lines
: Vec
<BytePos
> = try
! {
454 d
.read_struct_field("lines", 3, |d
| {
455 let num_lines
: u32 = try
! { Decodable::decode(d) }
;
456 let mut lines
= Vec
::with_capacity(num_lines
as usize);
459 // Read the number of bytes used per diff.
460 let bytes_per_diff
: u8 = try
! { Decodable::decode(d) }
;
462 // Read the first element.
463 let mut line_start
: BytePos
= try
! { Decodable::decode(d) }
;
464 lines
.push(line_start
);
466 for _
in 1..num_lines
{
467 let diff
= match bytes_per_diff
{
468 1 => try
! { d.read_u8() }
as u32,
469 2 => try
! { d.read_u16() }
as u32,
470 4 => try
! { d.read_u32() }
,
474 line_start
= line_start
+ BytePos(diff
);
476 lines
.push(line_start
);
483 let multibyte_chars
: Vec
<MultiByteChar
> = try
! {
484 d
.read_struct_field("multibyte_chars", 4, |d
| Decodable
::decode(d
))
488 start_pos
: start_pos
,
491 lines
: RefCell
::new(lines
),
492 multibyte_chars
: RefCell
::new(multibyte_chars
)
498 impl fmt
::Debug
for FileMap
{
499 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
500 write
!(fmt
, "FileMap({})", self.name
)
505 /// EFFECT: register a start-of-line offset in the
506 /// table of line-beginnings.
507 /// UNCHECKED INVARIANT: these offsets must be added in the right
508 /// order and must be in the right places; there is shared knowledge
509 /// about what ends a line between this file and parse.rs
510 /// WARNING: pos param here is the offset relative to start of CodeMap,
511 /// and CodeMap will append a newline when adding a filemap without a newline at the end,
512 /// so the safe way to call this is with value calculated as
513 /// filemap.start_pos + newline_offset_relative_to_the_start_of_filemap.
514 pub fn next_line(&self, pos
: BytePos
) {
515 // the new charpos must be > the last one (or it's the first one).
516 let mut lines
= self.lines
.borrow_mut();
517 let line_len
= lines
.len();
518 assert
!(line_len
== 0 || ((*lines
)[line_len
- 1] < pos
));
522 /// get a line from the list of pre-computed line-beginnings.
523 /// line-number here is 0-based.
524 pub fn get_line(&self, line_number
: usize) -> Option
<&str> {
527 let lines
= self.lines
.borrow();
528 lines
.get(line_number
).map(|&line
| {
529 let begin
: BytePos
= line
- self.start_pos
;
530 let begin
= begin
.to_usize();
531 // We can't use `lines.get(line_number+1)` because we might
532 // be parsing when we call this function and thus the current
533 // line is the last one we have line info for.
534 let slice
= &src
[begin
..];
535 match slice
.find('
\n'
) {
536 Some(e
) => &slice
[..e
],
545 pub fn record_multibyte_char(&self, pos
: BytePos
, bytes
: usize) {
546 assert
!(bytes
>=2 && bytes
<= 4);
547 let mbc
= MultiByteChar
{
551 self.multibyte_chars
.borrow_mut().push(mbc
);
554 pub fn is_real_file(&self) -> bool
{
555 !(self.name
.starts_with("<") &&
556 self.name
.ends_with(">"))
559 pub fn is_imported(&self) -> bool
{
563 fn count_lines(&self) -> usize {
564 self.lines
.borrow().len()
568 /// An abstraction over the fs operations used by the Parser.
569 pub trait FileLoader
{
570 /// Query the existence of a file.
571 fn file_exists(&self, path
: &Path
) -> bool
;
573 /// Read the contents of an UTF-8 file into memory.
574 fn read_file(&self, path
: &Path
) -> io
::Result
<String
>;
577 /// A FileLoader that uses std::fs to load real files.
578 pub struct RealFileLoader
;
580 impl FileLoader
for RealFileLoader
{
581 fn file_exists(&self, path
: &Path
) -> bool
{
582 fs
::metadata(path
).is_ok()
585 fn read_file(&self, path
: &Path
) -> io
::Result
<String
> {
586 let mut src
= String
::new();
587 try
!(try
!(fs
::File
::open(path
)).read_to_string(&mut src
));
592 // _____________________________________________________________________________
597 pub files
: RefCell
<Vec
<Rc
<FileMap
>>>,
598 expansions
: RefCell
<Vec
<ExpnInfo
>>,
599 file_loader
: Box
<FileLoader
>
603 pub fn new() -> CodeMap
{
605 files
: RefCell
::new(Vec
::new()),
606 expansions
: RefCell
::new(Vec
::new()),
607 file_loader
: Box
::new(RealFileLoader
)
611 pub fn with_file_loader(file_loader
: Box
<FileLoader
>) -> CodeMap
{
613 files
: RefCell
::new(Vec
::new()),
614 expansions
: RefCell
::new(Vec
::new()),
615 file_loader
: file_loader
619 pub fn file_exists(&self, path
: &Path
) -> bool
{
620 self.file_loader
.file_exists(path
)
623 pub fn load_file(&self, path
: &Path
) -> io
::Result
<Rc
<FileMap
>> {
624 let src
= try
!(self.file_loader
.read_file(path
));
625 Ok(self.new_filemap(path
.to_str().unwrap().to_string(), src
))
628 fn next_start_pos(&self) -> usize {
629 let files
= self.files
.borrow();
632 // Add one so there is some space between files. This lets us distinguish
633 // positions in the codemap, even in the presence of zero-length files.
634 Some(last
) => last
.end_pos
.to_usize() + 1,
638 /// Creates a new filemap without setting its line information. If you don't
639 /// intend to set the line information yourself, you should use new_filemap_and_lines.
640 pub fn new_filemap(&self, filename
: FileName
, mut src
: String
) -> Rc
<FileMap
> {
641 let start_pos
= self.next_start_pos();
642 let mut files
= self.files
.borrow_mut();
644 // Remove utf-8 BOM if any.
645 if src
.starts_with("\u{feff}") {
649 let end_pos
= start_pos
+ src
.len();
651 let filemap
= Rc
::new(FileMap
{
653 src
: Some(Rc
::new(src
)),
654 start_pos
: Pos
::from_usize(start_pos
),
655 end_pos
: Pos
::from_usize(end_pos
),
656 lines
: RefCell
::new(Vec
::new()),
657 multibyte_chars
: RefCell
::new(Vec
::new()),
660 files
.push(filemap
.clone());
665 /// Creates a new filemap and sets its line information.
666 pub fn new_filemap_and_lines(&self, filename
: &str, src
: &str) -> Rc
<FileMap
> {
667 let fm
= self.new_filemap(filename
.to_string(), src
.to_owned());
668 let mut byte_pos
: u32 = 0;
669 for line
in src
.lines() {
670 // register the start of this line
671 fm
.next_line(BytePos(byte_pos
));
673 // update byte_pos to include this line and the \n at the end
674 byte_pos
+= line
.len() as u32 + 1;
680 /// Allocates a new FileMap representing a source file from an external
681 /// crate. The source code of such an "imported filemap" is not available,
682 /// but we still know enough to generate accurate debuginfo location
683 /// information for things inlined from other crates.
684 pub fn new_imported_filemap(&self,
687 mut file_local_lines
: Vec
<BytePos
>,
688 mut file_local_multibyte_chars
: Vec
<MultiByteChar
>)
690 let start_pos
= self.next_start_pos();
691 let mut files
= self.files
.borrow_mut();
693 let end_pos
= Pos
::from_usize(start_pos
+ source_len
);
694 let start_pos
= Pos
::from_usize(start_pos
);
696 for pos
in &mut file_local_lines
{
697 *pos
= *pos
+ start_pos
;
700 for mbc
in &mut file_local_multibyte_chars
{
701 mbc
.pos
= mbc
.pos
+ start_pos
;
704 let filemap
= Rc
::new(FileMap
{
707 start_pos
: start_pos
,
709 lines
: RefCell
::new(file_local_lines
),
710 multibyte_chars
: RefCell
::new(file_local_multibyte_chars
),
713 files
.push(filemap
.clone());
718 pub fn mk_substr_filename(&self, sp
: Span
) -> String
{
719 let pos
= self.lookup_char_pos(sp
.lo
);
720 (format
!("<{}:{}:{}>",
723 pos
.col
.to_usize() + 1)).to_string()
726 /// Lookup source information about a BytePos
727 pub fn lookup_char_pos(&self, pos
: BytePos
) -> Loc
{
728 let chpos
= self.bytepos_to_file_charpos(pos
);
729 match self.lookup_line(pos
) {
730 Ok(FileMapAndLine { fm: f, line: a }
) => {
731 let line
= a
+ 1; // Line numbers start at 1
732 let linebpos
= (*f
.lines
.borrow())[a
];
733 let linechpos
= self.bytepos_to_file_charpos(linebpos
);
734 debug
!("byte pos {:?} is on the line at byte pos {:?}",
736 debug
!("char pos {:?} is on the line at char pos {:?}",
738 debug
!("byte is on line: {}", line
);
739 assert
!(chpos
>= linechpos
);
743 col
: chpos
- linechpos
,
756 // If the relevant filemap is empty, we don't return a line number.
757 fn lookup_line(&self, pos
: BytePos
) -> Result
<FileMapAndLine
, Rc
<FileMap
>> {
758 let idx
= self.lookup_filemap_idx(pos
);
760 let files
= self.files
.borrow();
761 let f
= (*files
)[idx
].clone();
763 let len
= f
.lines
.borrow().len();
770 let lines
= f
.lines
.borrow();
771 let mut b
= lines
.len();
774 if (*lines
)[m
] > pos
{
780 assert
!(a
<= lines
.len());
782 Ok(FileMapAndLine { fm: f, line: a }
)
785 pub fn lookup_char_pos_adj(&self, pos
: BytePos
) -> LocWithOpt
{
786 let loc
= self.lookup_char_pos(pos
);
788 filename
: loc
.file
.name
.to_string(),
795 pub fn span_to_string(&self, sp
: Span
) -> String
{
796 if self.files
.borrow().is_empty() && sp
== DUMMY_SP
{
797 return "no-location".to_string();
800 let lo
= self.lookup_char_pos_adj(sp
.lo
);
801 let hi
= self.lookup_char_pos_adj(sp
.hi
);
802 return (format
!("{}:{}:{}: {}:{}",
805 lo
.col
.to_usize() + 1,
807 hi
.col
.to_usize() + 1)).to_string()
810 // Returns true if two spans have the same callee
811 // (Assumes the same ExpnFormat implies same callee)
812 fn match_callees(&self, sp_a
: &Span
, sp_b
: &Span
) -> bool
{
814 .with_expn_info(sp_a
.expn_id
,
815 |ei
| ei
.map(|ei
| ei
.callee
.format
.clone()));
818 .with_expn_info(sp_b
.expn_id
,
819 |ei
| ei
.map(|ei
| ei
.callee
.format
.clone()));
823 /// Returns a formatted string showing the expansion chain of a span
825 /// Spans are printed in the following format:
827 /// filename:start_line:col: end_line:col
834 /// Callees and callsites are printed recursively (if available, otherwise header
835 /// and span is omitted), expanding into their own callee/callsite spans.
836 /// Each layer of recursion has an increased indent, and snippets are truncated
837 /// to at most 50 characters. Finally, recursive calls to the same macro are squashed,
838 /// with '...' used to represent any number of recursive calls.
839 pub fn span_to_expanded_string(&self, sp
: Span
) -> String
{
840 self.span_to_expanded_string_internal(sp
, "")
843 fn span_to_expanded_string_internal(&self, sp
:Span
, indent
: &str) -> String
{
844 let mut indent
= indent
.to_owned();
845 let mut output
= "".to_owned();
846 let span_str
= self.span_to_string(sp
);
847 let mut span_snip
= self.span_to_snippet(sp
)
848 .unwrap_or("Snippet unavailable".to_owned());
849 if span_snip
.len() > 50 {
850 span_snip
.truncate(50);
851 span_snip
.push_str("...");
853 output
.push_str(&format
!("{}{}\n{}`{}`\n", indent
, span_str
, indent
, span_snip
));
855 if sp
.expn_id
== NO_EXPANSION
|| sp
.expn_id
== COMMAND_LINE_EXPN
{
859 let mut callee
= self.with_expn_info(sp
.expn_id
,
860 |ei
| ei
.and_then(|ei
| ei
.callee
.span
.clone()));
861 let mut callsite
= self.with_expn_info(sp
.expn_id
,
862 |ei
| ei
.map(|ei
| ei
.call_site
.clone()));
864 indent
.push_str(" ");
865 let mut is_recursive
= false;
867 while callee
.is_some() && self.match_callees(&sp
, &callee
.unwrap()) {
868 callee
= self.with_expn_info(callee
.unwrap().expn_id
,
869 |ei
| ei
.and_then(|ei
| ei
.callee
.span
.clone()));
872 if let Some(span
) = callee
{
873 output
.push_str(&indent
);
874 output
.push_str("Callee:\n");
876 output
.push_str(&indent
);
877 output
.push_str("...\n");
879 output
.push_str(&(self.span_to_expanded_string_internal(span
, &indent
)));
882 is_recursive
= false;
883 while callsite
.is_some() && self.match_callees(&sp
, &callsite
.unwrap()) {
884 callsite
= self.with_expn_info(callsite
.unwrap().expn_id
,
885 |ei
| ei
.map(|ei
| ei
.call_site
.clone()));
888 if let Some(span
) = callsite
{
889 output
.push_str(&indent
);
890 output
.push_str("Callsite:\n");
892 output
.push_str(&indent
);
893 output
.push_str("...\n");
895 output
.push_str(&(self.span_to_expanded_string_internal(span
, &indent
)));
900 pub fn span_to_filename(&self, sp
: Span
) -> FileName
{
901 self.lookup_char_pos(sp
.lo
).file
.name
.to_string()
904 pub fn span_to_lines(&self, sp
: Span
) -> FileLinesResult
{
906 return Err(SpanLinesError
::IllFormedSpan(sp
));
909 let lo
= self.lookup_char_pos(sp
.lo
);
910 let hi
= self.lookup_char_pos(sp
.hi
);
912 if lo
.file
.start_pos
!= hi
.file
.start_pos
{
913 return Err(SpanLinesError
::DistinctSources(DistinctSources
{
914 begin
: (lo
.file
.name
.clone(), lo
.file
.start_pos
),
915 end
: (hi
.file
.name
.clone(), hi
.file
.start_pos
),
918 assert
!(hi
.line
>= lo
.line
);
920 let mut lines
= Vec
::with_capacity(hi
.line
- lo
.line
+ 1);
922 // The span starts partway through the first line,
923 // but after that it starts from offset 0.
924 let mut start_col
= lo
.col
;
926 // For every line but the last, it extends from `start_col`
927 // and to the end of the line. Be careful because the line
928 // numbers in Loc are 1-based, so we subtract 1 to get 0-based
930 for line_index
in lo
.line
-1 .. hi
.line
-1 {
931 let line_len
= lo
.file
.get_line(line_index
).map(|s
| s
.len()).unwrap_or(0);
932 lines
.push(LineInfo
{ line_index
: line_index
,
933 start_col
: start_col
,
934 end_col
: CharPos
::from_usize(line_len
) });
935 start_col
= CharPos
::from_usize(0);
938 // For the last line, it extends from `start_col` to `hi.col`:
939 lines
.push(LineInfo
{ line_index
: hi
.line
- 1,
940 start_col
: start_col
,
943 Ok(FileLines {file: lo.file, lines: lines}
)
946 pub fn span_to_snippet(&self, sp
: Span
) -> Result
<String
, SpanSnippetError
> {
948 return Err(SpanSnippetError
::IllFormedSpan(sp
));
951 let local_begin
= self.lookup_byte_offset(sp
.lo
);
952 let local_end
= self.lookup_byte_offset(sp
.hi
);
954 if local_begin
.fm
.start_pos
!= local_end
.fm
.start_pos
{
955 return Err(SpanSnippetError
::DistinctSources(DistinctSources
{
956 begin
: (local_begin
.fm
.name
.clone(),
957 local_begin
.fm
.start_pos
),
958 end
: (local_end
.fm
.name
.clone(),
959 local_end
.fm
.start_pos
)
962 match local_begin
.fm
.src
{
964 let start_index
= local_begin
.pos
.to_usize();
965 let end_index
= local_end
.pos
.to_usize();
966 let source_len
= (local_begin
.fm
.end_pos
-
967 local_begin
.fm
.start_pos
).to_usize();
969 if start_index
> end_index
|| end_index
> source_len
{
970 return Err(SpanSnippetError
::MalformedForCodemap(
971 MalformedCodemapPositions
{
972 name
: local_begin
.fm
.name
.clone(),
973 source_len
: source_len
,
974 begin_pos
: local_begin
.pos
,
975 end_pos
: local_end
.pos
,
979 return Ok((&src
[start_index
..end_index
]).to_string())
982 return Err(SpanSnippetError
::SourceNotAvailable
{
983 filename
: local_begin
.fm
.name
.clone()
990 pub fn get_filemap(&self, filename
: &str) -> Rc
<FileMap
> {
991 for fm
in self.files
.borrow().iter() {
992 if filename
== fm
.name
{
996 panic
!("asking for {} which we don't know about", filename
);
999 /// For a global BytePos compute the local offset within the containing FileMap
1000 pub fn lookup_byte_offset(&self, bpos
: BytePos
) -> FileMapAndBytePos
{
1001 let idx
= self.lookup_filemap_idx(bpos
);
1002 let fm
= (*self.files
.borrow())[idx
].clone();
1003 let offset
= bpos
- fm
.start_pos
;
1004 FileMapAndBytePos {fm: fm, pos: offset}
1007 /// Converts an absolute BytePos to a CharPos relative to the filemap.
1008 pub fn bytepos_to_file_charpos(&self, bpos
: BytePos
) -> CharPos
{
1009 let idx
= self.lookup_filemap_idx(bpos
);
1010 let files
= self.files
.borrow();
1011 let map
= &(*files
)[idx
];
1013 // The number of extra bytes due to multibyte chars in the FileMap
1014 let mut total_extra_bytes
= 0;
1016 for mbc
in map
.multibyte_chars
.borrow().iter() {
1017 debug
!("{}-byte char at {:?}", mbc
.bytes
, mbc
.pos
);
1019 // every character is at least one byte, so we only
1020 // count the actual extra bytes.
1021 total_extra_bytes
+= mbc
.bytes
- 1;
1022 // We should never see a byte position in the middle of a
1024 assert
!(bpos
.to_usize() >= mbc
.pos
.to_usize() + mbc
.bytes
);
1030 assert
!(map
.start_pos
.to_usize() + total_extra_bytes
<= bpos
.to_usize());
1031 CharPos(bpos
.to_usize() - map
.start_pos
.to_usize() - total_extra_bytes
)
1034 // Return the index of the filemap (in self.files) which contains pos.
1035 fn lookup_filemap_idx(&self, pos
: BytePos
) -> usize {
1036 let files
= self.files
.borrow();
1037 let files
= &*files
;
1038 let count
= files
.len();
1040 // Binary search for the filemap.
1044 let m
= (a
+ b
) / 2;
1045 if files
[m
].start_pos
> pos
{
1052 assert
!(a
< count
, "position {} does not resolve to a source location", pos
.to_usize());
1057 pub fn record_expansion(&self, expn_info
: ExpnInfo
) -> ExpnId
{
1058 let mut expansions
= self.expansions
.borrow_mut();
1059 expansions
.push(expn_info
);
1060 let len
= expansions
.len();
1061 if len
> u32::max_value() as usize {
1062 panic
!("too many ExpnInfo's!");
1064 ExpnId(len
as u32 - 1)
1067 pub fn with_expn_info
<T
, F
>(&self, id
: ExpnId
, f
: F
) -> T
where
1068 F
: FnOnce(Option
<&ExpnInfo
>) -> T
,
1071 NO_EXPANSION
| COMMAND_LINE_EXPN
=> f(None
),
1072 ExpnId(i
) => f(Some(&(*self.expansions
.borrow())[i
as usize]))
1076 /// Check if a span is "internal" to a macro in which #[unstable]
1077 /// items can be used (that is, a macro marked with
1078 /// `#[allow_internal_unstable]`).
1079 pub fn span_allows_unstable(&self, span
: Span
) -> bool
{
1080 debug
!("span_allows_unstable(span = {:?})", span
);
1081 let mut allows_unstable
= false;
1082 let mut expn_id
= span
.expn_id
;
1084 let quit
= self.with_expn_info(expn_id
, |expninfo
| {
1085 debug
!("span_allows_unstable: expninfo = {:?}", expninfo
);
1086 expninfo
.map_or(/* hit the top level */ true, |info
| {
1088 let span_comes_from_this_expansion
=
1089 info
.callee
.span
.map_or(span
== info
.call_site
, |mac_span
| {
1090 mac_span
.contains(span
)
1093 debug
!("span_allows_unstable: span: {:?} call_site: {:?} callee: {:?}",
1095 (info
.call_site
.lo
, info
.call_site
.hi
),
1096 info
.callee
.span
.map(|x
| (x
.lo
, x
.hi
)));
1097 debug
!("span_allows_unstable: from this expansion? {}, allows unstable? {}",
1098 span_comes_from_this_expansion
,
1099 info
.callee
.allow_internal_unstable
);
1100 if span_comes_from_this_expansion
{
1101 allows_unstable
= info
.callee
.allow_internal_unstable
;
1102 // we've found the right place, stop looking
1105 // not the right place, keep looking
1106 expn_id
= info
.call_site
.expn_id
;
1115 debug
!("span_allows_unstable? {}", allows_unstable
);
1119 pub fn count_lines(&self) -> usize {
1120 self.files
.borrow().iter().fold(0, |a
, f
| a
+ f
.count_lines())
1124 // _____________________________________________________________________________
1125 // SpanLinesError, SpanSnippetError, DistinctSources, MalformedCodemapPositions
1128 pub type FileLinesResult
= Result
<FileLines
, SpanLinesError
>;
1130 #[derive(Clone, PartialEq, Eq, Debug)]
1131 pub enum SpanLinesError
{
1132 IllFormedSpan(Span
),
1133 DistinctSources(DistinctSources
),
1136 #[derive(Clone, PartialEq, Eq, Debug)]
1137 pub enum SpanSnippetError
{
1138 IllFormedSpan(Span
),
1139 DistinctSources(DistinctSources
),
1140 MalformedForCodemap(MalformedCodemapPositions
),
1141 SourceNotAvailable { filename: String }
1144 #[derive(Clone, PartialEq, Eq, Debug)]
1145 pub struct DistinctSources
{
1146 begin
: (String
, BytePos
),
1147 end
: (String
, BytePos
)
1150 #[derive(Clone, PartialEq, Eq, Debug)]
1151 pub struct MalformedCodemapPositions
{
1159 // _____________________________________________________________________________
1169 let cm
= CodeMap
::new();
1170 let fm
= cm
.new_filemap("blork.rs".to_string(),
1171 "first line.\nsecond line".to_string());
1172 fm
.next_line(BytePos(0));
1173 // Test we can get lines with partial line info.
1174 assert_eq
!(fm
.get_line(0), Some("first line."));
1175 // TESTING BROKEN BEHAVIOR: line break declared before actual line break.
1176 fm
.next_line(BytePos(10));
1177 assert_eq
!(fm
.get_line(1), Some("."));
1178 fm
.next_line(BytePos(12));
1179 assert_eq
!(fm
.get_line(2), Some("second line"));
1185 let cm
= CodeMap
::new();
1186 let fm
= cm
.new_filemap("blork.rs".to_string(),
1187 "first line.\nsecond line".to_string());
1188 // TESTING *REALLY* BROKEN BEHAVIOR:
1189 fm
.next_line(BytePos(0));
1190 fm
.next_line(BytePos(10));
1191 fm
.next_line(BytePos(2));
1194 fn init_code_map() -> CodeMap
{
1195 let cm
= CodeMap
::new();
1196 let fm1
= cm
.new_filemap("blork.rs".to_string(),
1197 "first line.\nsecond line".to_string());
1198 let fm2
= cm
.new_filemap("empty.rs".to_string(),
1200 let fm3
= cm
.new_filemap("blork2.rs".to_string(),
1201 "first line.\nsecond line".to_string());
1203 fm1
.next_line(BytePos(0));
1204 fm1
.next_line(BytePos(12));
1205 fm2
.next_line(fm2
.start_pos
);
1206 fm3
.next_line(fm3
.start_pos
);
1207 fm3
.next_line(fm3
.start_pos
+ BytePos(12));
1214 // Test lookup_byte_offset
1215 let cm
= init_code_map();
1217 let fmabp1
= cm
.lookup_byte_offset(BytePos(23));
1218 assert_eq
!(fmabp1
.fm
.name
, "blork.rs");
1219 assert_eq
!(fmabp1
.pos
, BytePos(23));
1221 let fmabp1
= cm
.lookup_byte_offset(BytePos(24));
1222 assert_eq
!(fmabp1
.fm
.name
, "empty.rs");
1223 assert_eq
!(fmabp1
.pos
, BytePos(0));
1225 let fmabp2
= cm
.lookup_byte_offset(BytePos(25));
1226 assert_eq
!(fmabp2
.fm
.name
, "blork2.rs");
1227 assert_eq
!(fmabp2
.pos
, BytePos(0));
1232 // Test bytepos_to_file_charpos
1233 let cm
= init_code_map();
1235 let cp1
= cm
.bytepos_to_file_charpos(BytePos(22));
1236 assert_eq
!(cp1
, CharPos(22));
1238 let cp2
= cm
.bytepos_to_file_charpos(BytePos(25));
1239 assert_eq
!(cp2
, CharPos(0));
1244 // Test zero-length filemaps.
1245 let cm
= init_code_map();
1247 let loc1
= cm
.lookup_char_pos(BytePos(22));
1248 assert_eq
!(loc1
.file
.name
, "blork.rs");
1249 assert_eq
!(loc1
.line
, 2);
1250 assert_eq
!(loc1
.col
, CharPos(10));
1252 let loc2
= cm
.lookup_char_pos(BytePos(25));
1253 assert_eq
!(loc2
.file
.name
, "blork2.rs");
1254 assert_eq
!(loc2
.line
, 1);
1255 assert_eq
!(loc2
.col
, CharPos(0));
1258 fn init_code_map_mbc() -> CodeMap
{
1259 let cm
= CodeMap
::new();
1260 // € is a three byte utf8 char.
1262 cm
.new_filemap("blork.rs".to_string(),
1263 "fir€st €€€€ line.\nsecond line".to_string());
1264 let fm2
= cm
.new_filemap("blork2.rs".to_string(),
1265 "first line€€.\n€ second line".to_string());
1267 fm1
.next_line(BytePos(0));
1268 fm1
.next_line(BytePos(28));
1269 fm2
.next_line(fm2
.start_pos
);
1270 fm2
.next_line(fm2
.start_pos
+ BytePos(20));
1272 fm1
.record_multibyte_char(BytePos(3), 3);
1273 fm1
.record_multibyte_char(BytePos(9), 3);
1274 fm1
.record_multibyte_char(BytePos(12), 3);
1275 fm1
.record_multibyte_char(BytePos(15), 3);
1276 fm1
.record_multibyte_char(BytePos(18), 3);
1277 fm2
.record_multibyte_char(fm2
.start_pos
+ BytePos(10), 3);
1278 fm2
.record_multibyte_char(fm2
.start_pos
+ BytePos(13), 3);
1279 fm2
.record_multibyte_char(fm2
.start_pos
+ BytePos(18), 3);
1286 // Test bytepos_to_file_charpos in the presence of multi-byte chars
1287 let cm
= init_code_map_mbc();
1289 let cp1
= cm
.bytepos_to_file_charpos(BytePos(3));
1290 assert_eq
!(cp1
, CharPos(3));
1292 let cp2
= cm
.bytepos_to_file_charpos(BytePos(6));
1293 assert_eq
!(cp2
, CharPos(4));
1295 let cp3
= cm
.bytepos_to_file_charpos(BytePos(56));
1296 assert_eq
!(cp3
, CharPos(12));
1298 let cp4
= cm
.bytepos_to_file_charpos(BytePos(61));
1299 assert_eq
!(cp4
, CharPos(15));
1304 // Test span_to_lines for a span ending at the end of filemap
1305 let cm
= init_code_map();
1306 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1307 let file_lines
= cm
.span_to_lines(span
).unwrap();
1309 assert_eq
!(file_lines
.file
.name
, "blork.rs");
1310 assert_eq
!(file_lines
.lines
.len(), 1);
1311 assert_eq
!(file_lines
.lines
[0].line_index
, 1);
1314 /// Given a string like " ^~~~~~~~~~~~ ", produces a span
1315 /// coverting that range. The idea is that the string has the same
1316 /// length as the input, and we uncover the byte positions. Note
1317 /// that this can span lines and so on.
1318 fn span_from_selection(input
: &str, selection
: &str) -> Span
{
1319 assert_eq
!(input
.len(), selection
.len());
1320 let left_index
= selection
.find('
^').unwrap() as u32;
1321 let right_index
= selection
.rfind('
~'
).unwrap() as u32;
1322 Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
1325 /// Test span_to_snippet and span_to_lines for a span coverting 3
1326 /// lines in the middle of a file.
1328 fn span_to_snippet_and_lines_spanning_multiple_lines() {
1329 let cm
= CodeMap
::new();
1330 let inputtext
= "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
1331 let selection
= " \n ^~\n~~~\n~~~~~ \n \n";
1332 cm
.new_filemap_and_lines("blork.rs", inputtext
);
1333 let span
= span_from_selection(inputtext
, selection
);
1335 // check that we are extracting the text we thought we were extracting
1336 assert_eq
!(&cm
.span_to_snippet(span
).unwrap(), "BB\nCCC\nDDDDD");
1338 // check that span_to_lines gives us the complete result with the lines/cols we expected
1339 let lines
= cm
.span_to_lines(span
).unwrap();
1340 let expected
= vec
![
1341 LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) }
,
1342 LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) }
,
1343 LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) }
1345 assert_eq
!(lines
.lines
, expected
);
1350 // Test span_to_snippet for a span ending at the end of filemap
1351 let cm
= init_code_map();
1352 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1353 let snippet
= cm
.span_to_snippet(span
);
1355 assert_eq
!(snippet
, Ok("second line".to_string()));
1360 // Test span_to_str for a span ending at the end of filemap
1361 let cm
= init_code_map();
1362 let span
= Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}
;
1363 let sstr
= cm
.span_to_string(span
);
1365 assert_eq
!(sstr
, "blork.rs:2:1: 2:12");
1370 // Test span_to_expanded_string works in base case (no expansion)
1371 let cm
= init_code_map();
1372 let span
= Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION }
;
1373 let sstr
= cm
.span_to_expanded_string(span
);
1374 assert_eq
!(sstr
, "blork.rs:1:1: 1:12\n`first line.`\n");
1376 let span
= Span { lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION }
;
1377 let sstr
= cm
.span_to_expanded_string(span
);
1378 assert_eq
!(sstr
, "blork.rs:2:1: 2:12\n`second line`\n");
1383 // Test span_to_expanded_string works with expansion
1385 let cm
= init_code_map();
1386 let root
= Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION }
;
1387 let format
= ExpnFormat
::MacroBang(Name(0u32));
1388 let callee
= NameAndSpan
{ format
: format
,
1389 allow_internal_unstable
: false,
1392 let info
= ExpnInfo { call_site: root, callee: callee }
;
1393 let id
= cm
.record_expansion(info
);
1394 let sp
= Span { lo: BytePos(12), hi: BytePos(23), expn_id: id }
;
1396 let sstr
= cm
.span_to_expanded_string(sp
);
1398 "blork.rs:2:1: 2:12\n`second line`\n Callsite:\n \
1399 blork.rs:1:1: 1:12\n `first line.`\n");
1402 fn init_expansion_chain(cm
: &CodeMap
) -> Span
{
1403 // Creates an expansion chain containing two recursive calls
1404 // root -> expA -> expA -> expB -> expB -> end
1407 let root
= Span { lo: BytePos(0), hi: BytePos(11), expn_id: NO_EXPANSION }
;
1409 let format_root
= ExpnFormat
::MacroBang(Name(0u32));
1410 let callee_root
= NameAndSpan
{ format
: format_root
,
1411 allow_internal_unstable
: false,
1414 let info_a1
= ExpnInfo { call_site: root, callee: callee_root }
;
1415 let id_a1
= cm
.record_expansion(info_a1
);
1416 let span_a1
= Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a1 }
;
1418 let format_a
= ExpnFormat
::MacroBang(Name(1u32));
1419 let callee_a
= NameAndSpan
{ format
: format_a
,
1420 allow_internal_unstable
: false,
1421 span
: Some(span_a1
) };
1423 let info_a2
= ExpnInfo { call_site: span_a1, callee: callee_a.clone() }
;
1424 let id_a2
= cm
.record_expansion(info_a2
);
1425 let span_a2
= Span { lo: BytePos(12), hi: BytePos(23), expn_id: id_a2 }
;
1427 let info_b1
= ExpnInfo { call_site: span_a2, callee: callee_a }
;
1428 let id_b1
= cm
.record_expansion(info_b1
);
1429 let span_b1
= Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b1 }
;
1431 let format_b
= ExpnFormat
::MacroBang(Name(2u32));
1432 let callee_b
= NameAndSpan
{ format
: format_b
,
1433 allow_internal_unstable
: false,
1436 let info_b2
= ExpnInfo { call_site: span_b1, callee: callee_b.clone() }
;
1437 let id_b2
= cm
.record_expansion(info_b2
);
1438 let span_b2
= Span { lo: BytePos(25), hi: BytePos(36), expn_id: id_b2 }
;
1440 let info_end
= ExpnInfo { call_site: span_b2, callee: callee_b }
;
1441 let id_end
= cm
.record_expansion(info_end
);
1442 Span { lo: BytePos(37), hi: BytePos(48), expn_id: id_end }
1447 // Test span_to_expanded_string collapses recursive macros and handles
1448 // recursive callsite and callee expansions
1449 let cm
= init_code_map();
1450 let end
= init_expansion_chain(&cm
);
1451 let sstr
= cm
.span_to_expanded_string(end
);
1453 r
"blork2.rs:2:1: 2:12
1479 assert_eq
!(sstr
, res_str
);