]> git.proxmox.com Git - rustc.git/blame - src/libsyntax/codemap.rs
Imported Upstream version 1.1.0+dfsg1
[rustc.git] / src / libsyntax / codemap.rs
CommitLineData
223e47cc
LB
1// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
1a4d82fc
JJ
11//! The CodeMap tracks all the source code used within a single crate, mapping
12//! from integer byte positions to the original source code location. Each bit
13//! of source parsed during crate parsing (typically files, in-memory strings,
14//! or various bits of macro expansion) cover a continuous range of bytes in the
15//! CodeMap and are represented by FileMaps. Byte positions are stored in
16//! `spans` and used pervasively in the compiler. They are absolute positions
17//! within the CodeMap, which upon request can be converted to line and column
18//! information, source code snippets, etc.
223e47cc 19
d9579d0f 20pub use self::ExpnFormat::*;
223e47cc 21
1a4d82fc 22use std::cell::RefCell;
1a4d82fc
JJ
23use std::ops::{Add, Sub};
24use std::rc::Rc;
223e47cc 25
9346a6ac
AL
26use std::fmt;
27
1a4d82fc 28use serialize::{Encodable, Decodable, Encoder, Decoder};
223e47cc 29
c34b1796
AL
30
31// _____________________________________________________________________________
32// Pos, BytePos, CharPos
33//
34
223e47cc 35pub trait Pos {
85aaf69f
SL
36 fn from_usize(n: usize) -> Self;
37 fn to_usize(&self) -> usize;
223e47cc
LB
38}
39
1a4d82fc
JJ
40/// A byte offset. Keep this small (currently 32-bits), as AST contains
41/// a lot of them.
85aaf69f 42#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)]
1a4d82fc
JJ
43pub struct BytePos(pub u32);
44
223e47cc
LB
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.
9346a6ac 48#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)]
85aaf69f 49pub struct CharPos(pub usize);
223e47cc 50
1a4d82fc 51// FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
223e47cc
LB
52// have been unsuccessful
53
54impl Pos for BytePos {
85aaf69f
SL
55 fn from_usize(n: usize) -> BytePos { BytePos(n as u32) }
56 fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize }
223e47cc
LB
57}
58
1a4d82fc
JJ
59impl Add for BytePos {
60 type Output = BytePos;
223e47cc 61
1a4d82fc 62 fn add(self, rhs: BytePos) -> BytePos {
85aaf69f 63 BytePos((self.to_usize() + rhs.to_usize()) as u32)
223e47cc
LB
64 }
65}
66
1a4d82fc
JJ
67impl Sub for BytePos {
68 type Output = BytePos;
69
70 fn sub(self, rhs: BytePos) -> BytePos {
85aaf69f 71 BytePos((self.to_usize() - rhs.to_usize()) as u32)
223e47cc
LB
72 }
73}
74
c34b1796
AL
75impl Encodable for BytePos {
76 fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
77 s.emit_u32(self.0)
78 }
79}
80
81impl Decodable for BytePos {
82 fn decode<D: Decoder>(d: &mut D) -> Result<BytePos, D::Error> {
83 Ok(BytePos(try!{ d.read_u32() }))
84 }
85}
86
223e47cc 87impl Pos for CharPos {
85aaf69f
SL
88 fn from_usize(n: usize) -> CharPos { CharPos(n) }
89 fn to_usize(&self) -> usize { let CharPos(n) = *self; n }
223e47cc
LB
90}
91
1a4d82fc
JJ
92impl Add for CharPos {
93 type Output = CharPos;
223e47cc 94
1a4d82fc 95 fn add(self, rhs: CharPos) -> CharPos {
85aaf69f 96 CharPos(self.to_usize() + rhs.to_usize())
223e47cc
LB
97 }
98}
99
1a4d82fc
JJ
100impl Sub for CharPos {
101 type Output = CharPos;
102
103 fn sub(self, rhs: CharPos) -> CharPos {
85aaf69f 104 CharPos(self.to_usize() - rhs.to_usize())
223e47cc
LB
105 }
106}
107
c34b1796
AL
108// _____________________________________________________________________________
109// Span, Spanned
110//
111
1a4d82fc
JJ
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.
85aaf69f 116#[derive(Clone, Copy, Debug, Hash)]
1a4d82fc
JJ
117pub struct Span {
118 pub lo: BytePos,
119 pub hi: BytePos,
120 /// Information about where the macro came from, if this piece of
121 /// code was created by a macro expansion.
122 pub expn_id: ExpnId
223e47cc
LB
123}
124
1a4d82fc
JJ
125pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION };
126
127// Generic span to be used for code originating from the command line
128pub const COMMAND_LINE_SP: Span = Span { lo: BytePos(0),
129 hi: BytePos(0),
130 expn_id: COMMAND_LINE_EXPN };
131
85aaf69f 132#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
1a4d82fc
JJ
133pub struct Spanned<T> {
134 pub node: T,
135 pub span: Span,
136}
223e47cc 137
1a4d82fc
JJ
138impl PartialEq for Span {
139 fn eq(&self, other: &Span) -> bool {
223e47cc
LB
140 return (*self).lo == (*other).lo && (*self).hi == (*other).hi;
141 }
1a4d82fc 142 fn ne(&self, other: &Span) -> bool { !(*self).eq(other) }
223e47cc
LB
143}
144
1a4d82fc
JJ
145impl Eq for Span {}
146
147impl Encodable for Span {
1a4d82fc 148 fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
c34b1796
AL
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) )
970d7e83 153 }
223e47cc
LB
154}
155
1a4d82fc 156impl Decodable for Span {
c34b1796
AL
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);
161 Ok(mk_sp(lo, hi))
223e47cc
LB
162 }
163}
164
1a4d82fc 165pub fn spanned<T>(lo: BytePos, hi: BytePos, t: T) -> Spanned<T> {
223e47cc
LB
166 respan(mk_sp(lo, hi), t)
167}
168
1a4d82fc
JJ
169pub fn respan<T>(sp: Span, t: T) -> Spanned<T> {
170 Spanned {node: t, span: sp}
223e47cc
LB
171}
172
1a4d82fc
JJ
173pub fn dummy_spanned<T>(t: T) -> Spanned<T> {
174 respan(DUMMY_SP, t)
223e47cc
LB
175}
176
177/* assuming that we're not in macro expansion */
1a4d82fc
JJ
178pub fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
179 Span {lo: lo, hi: hi, expn_id: NO_EXPANSION}
223e47cc
LB
180}
181
1a4d82fc
JJ
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.
185pub 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) {
189 (None, _) => sp,
190 (Some(call_site1), Some(call_site2)) if call_site1 == call_site2 => sp,
191 (Some(call_site1), _) => original_sp(cm, call_site1, enclosing_sp),
192 }
193}
223e47cc 194
c34b1796
AL
195// _____________________________________________________________________________
196// Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos
197//
198
223e47cc 199/// A source code location used for error reporting
9346a6ac 200#[derive(Debug)]
223e47cc
LB
201pub struct Loc {
202 /// Information about the original source
1a4d82fc 203 pub file: Rc<FileMap>,
223e47cc 204 /// The (1-based) line number
85aaf69f 205 pub line: usize,
223e47cc 206 /// The (0-based) column offset
1a4d82fc 207 pub col: CharPos
223e47cc
LB
208}
209
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.
9346a6ac 213#[derive(Debug)]
223e47cc 214pub struct LocWithOpt {
1a4d82fc 215 pub filename: FileName,
85aaf69f 216 pub line: usize,
1a4d82fc
JJ
217 pub col: CharPos,
218 pub file: Option<Rc<FileMap>>,
223e47cc
LB
219}
220
221// used to be structural records. Better names, anyone?
9346a6ac 222#[derive(Debug)]
85aaf69f 223pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize }
9346a6ac 224#[derive(Debug)]
1a4d82fc
JJ
225pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos }
226
c34b1796
AL
227
228// _____________________________________________________________________________
d9579d0f 229// ExpnFormat, NameAndSpan, ExpnInfo, ExpnId
c34b1796
AL
230//
231
d9579d0f
AL
232/// The source of expansion.
233#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)]
234pub enum ExpnFormat {
1a4d82fc
JJ
235 /// e.g. #[derive(...)] <item>
236 MacroAttribute,
237 /// e.g. `format!()`
d9579d0f
AL
238 MacroBang,
239 /// Syntax sugar expansion performed by the compiler (libsyntax::expand).
240 CompilerExpansion,
1a4d82fc
JJ
241}
242
85aaf69f 243#[derive(Clone, Hash, Debug)]
1a4d82fc
JJ
244pub struct NameAndSpan {
245 /// The name of the macro that was invoked to create the thing
246 /// with this Span.
247 pub name: String,
248 /// The format with which the macro was invoked.
d9579d0f 249 pub format: ExpnFormat,
c34b1796
AL
250 /// Whether the macro is allowed to use #[unstable]/feature-gated
251 /// features internally without forcing the whole crate to opt-in
252 /// to them.
253 pub allow_internal_unstable: bool,
1a4d82fc
JJ
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>
223e47cc
LB
258}
259
d9579d0f 260/// Extra information for tracking spans of macro and syntax sugar expansion
85aaf69f 261#[derive(Hash, Debug)]
1a4d82fc 262pub struct ExpnInfo {
d9579d0f
AL
263 /// The location of the actual macro invocation or syntax sugar , e.g.
264 /// `let x = foo!();` or `if let Some(y) = x {}`
1a4d82fc
JJ
265 ///
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.
272 pub call_site: Span,
d9579d0f 273 /// Information about the expansion.
1a4d82fc 274 pub callee: NameAndSpan
223e47cc
LB
275}
276
85aaf69f 277#[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)]
1a4d82fc 278pub struct ExpnId(u32);
223e47cc 279
c34b1796 280pub const NO_EXPANSION: ExpnId = ExpnId(!0);
1a4d82fc 281// For code appearing from the command line
c34b1796 282pub const COMMAND_LINE_EXPN: ExpnId = ExpnId(!1);
1a4d82fc
JJ
283
284impl ExpnId {
d9579d0f
AL
285 pub fn from_u32(id: u32) -> ExpnId {
286 ExpnId(id)
1a4d82fc
JJ
287 }
288
d9579d0f
AL
289 pub fn into_u32(self) -> u32 {
290 self.0
1a4d82fc 291 }
223e47cc
LB
292}
293
c34b1796
AL
294// _____________________________________________________________________________
295// FileMap, MultiByteChar, FileName, FileLines
296//
297
1a4d82fc
JJ
298pub type FileName = String;
299
9346a6ac
AL
300#[derive(Copy, Clone, Debug, PartialEq, Eq)]
301pub struct LineInfo {
302 /// Index of line, starting from 0.
303 pub line_index: usize,
304
305 /// Column in line where span begins, starting from 0.
306 pub start_col: CharPos,
307
308 /// Column in line where span ends, starting from 0, exclusive.
309 pub end_col: CharPos,
310}
311
1a4d82fc
JJ
312pub struct FileLines {
313 pub file: Rc<FileMap>,
9346a6ac 314 pub lines: Vec<LineInfo>
223e47cc
LB
315}
316
317/// Identifies an offset of a multi-byte character in a FileMap
c34b1796 318#[derive(Copy, Clone, RustcEncodable, RustcDecodable, Eq, PartialEq)]
223e47cc
LB
319pub struct MultiByteChar {
320 /// The absolute offset of the character in the CodeMap
1a4d82fc 321 pub pos: BytePos,
223e47cc 322 /// The number of bytes, >=2
85aaf69f 323 pub bytes: usize,
223e47cc
LB
324}
325
326/// A single source in the CodeMap
327pub struct FileMap {
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,
330 /// e.g. `<anon>`
1a4d82fc 331 pub name: FileName,
223e47cc 332 /// The complete source code
c34b1796 333 pub src: Option<Rc<String>>,
223e47cc 334 /// The start position of this source in the CodeMap
1a4d82fc 335 pub start_pos: BytePos,
c34b1796
AL
336 /// The end position of this source in the CodeMap
337 pub end_pos: BytePos,
223e47cc 338 /// Locations of lines beginnings in the source code
c34b1796 339 pub lines: RefCell<Vec<BytePos>>,
223e47cc 340 /// Locations of multi-byte characters in the source code
c34b1796
AL
341 pub multibyte_chars: RefCell<Vec<MultiByteChar>>,
342}
343
344impl 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();
352 // store the length
353 try! { s.emit_u32(lines.len() as u32) };
354
9346a6ac 355 if !lines.is_empty() {
c34b1796
AL
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 {
362 0
363 } else {
364 lines.windows(2)
365 .map(|w| w[1] - w[0])
366 .map(|bp| bp.to_usize())
367 .max()
368 .unwrap()
369 };
370
371 let bytes_per_diff: u8 = match max_line_length {
372 0 ... 0xFF => 1,
373 0x100 ... 0xFFFF => 2,
374 _ => 4
375 };
376
377 // Encode the number of bytes used per diff.
378 try! { bytes_per_diff.encode(s) };
379
380 // Encode the first element.
381 try! { lines[0].encode(s) };
382
383 let diff_iter = (&lines[..]).windows(2)
384 .map(|w| (w[1] - w[0]));
385
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) } },
390 _ => unreachable!()
391 }
392 }
393
394 Ok(())
395 })
396 };
397 s.emit_struct_field("multibyte_chars", 4, |s| {
398 (*self.multibyte_chars.borrow()).encode(s)
399 })
400 })
401 }
402}
403
404impl Decodable for FileMap {
405 fn decode<D: Decoder>(d: &mut D) -> Result<FileMap, D::Error> {
406
407 d.read_struct("FileMap", 5, |d| {
408 let name: String = try! {
409 d.read_struct_field("name", 0, |d| Decodable::decode(d))
410 };
411 let start_pos: BytePos = try! {
412 d.read_struct_field("start_pos", 1, |d| Decodable::decode(d))
413 };
414 let end_pos: BytePos = try! {
415 d.read_struct_field("end_pos", 2, |d| Decodable::decode(d))
416 };
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);
421
422 if num_lines > 0 {
423 // Read the number of bytes used per diff.
424 let bytes_per_diff: u8 = try! { Decodable::decode(d) };
425
426 // Read the first element.
427 let mut line_start: BytePos = try! { Decodable::decode(d) };
428 lines.push(line_start);
429
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() },
435 _ => unreachable!()
436 };
437
438 line_start = line_start + BytePos(diff);
439
440 lines.push(line_start);
441 }
442 }
443
444 Ok(lines)
445 })
446 };
447 let multibyte_chars: Vec<MultiByteChar> = try! {
448 d.read_struct_field("multibyte_chars", 4, |d| Decodable::decode(d))
449 };
450 Ok(FileMap {
451 name: name,
452 start_pos: start_pos,
453 end_pos: end_pos,
454 src: None,
455 lines: RefCell::new(lines),
456 multibyte_chars: RefCell::new(multibyte_chars)
457 })
458 })
459 }
223e47cc
LB
460}
461
9346a6ac
AL
462impl fmt::Debug for FileMap {
463 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
464 write!(fmt, "FileMap({})", self.name)
465 }
466}
467
970d7e83 468impl FileMap {
1a4d82fc
JJ
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.
970d7e83 478 pub fn next_line(&self, pos: BytePos) {
223e47cc 479 // the new charpos must be > the last one (or it's the first one).
1a4d82fc
JJ
480 let mut lines = self.lines.borrow_mut();
481 let line_len = lines.len();
482 assert!(line_len == 0 || ((*lines)[line_len - 1] < pos));
970d7e83 483 lines.push(pos);
223e47cc
LB
484 }
485
9346a6ac
AL
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> {
c34b1796
AL
489 match self.src {
490 Some(ref src) => {
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],
498 None => slice
9346a6ac 499 }
c34b1796
AL
500 })
501 }
502 None => None
503 }
223e47cc
LB
504 }
505
85aaf69f 506 pub fn record_multibyte_char(&self, pos: BytePos, bytes: usize) {
223e47cc
LB
507 assert!(bytes >=2 && bytes <= 4);
508 let mbc = MultiByteChar {
509 pos: pos,
510 bytes: bytes,
511 };
1a4d82fc
JJ
512 self.multibyte_chars.borrow_mut().push(mbc);
513 }
514
515 pub fn is_real_file(&self) -> bool {
516 !(self.name.starts_with("<") &&
517 self.name.ends_with(">"))
223e47cc 518 }
c34b1796
AL
519
520 pub fn is_imported(&self) -> bool {
521 self.src.is_none()
522 }
223e47cc
LB
523}
524
c34b1796
AL
525
526// _____________________________________________________________________________
527// CodeMap
528//
529
223e47cc 530pub struct CodeMap {
1a4d82fc
JJ
531 pub files: RefCell<Vec<Rc<FileMap>>>,
532 expansions: RefCell<Vec<ExpnInfo>>
223e47cc
LB
533}
534
970d7e83 535impl CodeMap {
223e47cc
LB
536 pub fn new() -> CodeMap {
537 CodeMap {
1a4d82fc
JJ
538 files: RefCell::new(Vec::new()),
539 expansions: RefCell::new(Vec::new()),
223e47cc
LB
540 }
541 }
542
d9579d0f 543 pub fn new_filemap(&self, filename: FileName, mut src: String) -> Rc<FileMap> {
1a4d82fc
JJ
544 let mut files = self.files.borrow_mut();
545 let start_pos = match files.last() {
546 None => 0,
c34b1796 547 Some(last) => last.end_pos.to_usize(),
1a4d82fc 548 };
223e47cc 549
1a4d82fc 550 // Remove utf-8 BOM if any.
d9579d0f
AL
551 if src.starts_with("\u{feff}") {
552 src.drain(..3);
553 }
223e47cc 554
1a4d82fc 555 // Append '\n' in case it's not already there.
85aaf69f
SL
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_*.
9346a6ac 560 if !src.is_empty() && !src.ends_with("\n") {
1a4d82fc
JJ
561 src.push('\n');
562 }
223e47cc 563
c34b1796
AL
564 let end_pos = start_pos + src.len();
565
1a4d82fc
JJ
566 let filemap = Rc::new(FileMap {
567 name: filename,
c34b1796 568 src: Some(Rc::new(src)),
85aaf69f 569 start_pos: Pos::from_usize(start_pos),
c34b1796 570 end_pos: Pos::from_usize(end_pos),
1a4d82fc
JJ
571 lines: RefCell::new(Vec::new()),
572 multibyte_chars: RefCell::new(Vec::new()),
573 });
223e47cc 574
1a4d82fc
JJ
575 files.push(filemap.clone());
576
577 filemap
223e47cc
LB
578 }
579
c34b1796
AL
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,
585 filename: FileName,
586 source_len: usize,
d9579d0f
AL
587 mut file_local_lines: Vec<BytePos>,
588 mut file_local_multibyte_chars: Vec<MultiByteChar>)
c34b1796
AL
589 -> Rc<FileMap> {
590 let mut files = self.files.borrow_mut();
591 let start_pos = match files.last() {
592 None => 0,
593 Some(last) => last.end_pos.to_usize(),
594 };
595
596 let end_pos = Pos::from_usize(start_pos + source_len);
597 let start_pos = Pos::from_usize(start_pos);
598
d9579d0f
AL
599 for pos in &mut file_local_lines {
600 *pos = *pos + start_pos;
601 }
602
603 for mbc in &mut file_local_multibyte_chars {
604 mbc.pos = mbc.pos + start_pos;
605 }
c34b1796
AL
606
607 let filemap = Rc::new(FileMap {
608 name: filename,
609 src: None,
610 start_pos: start_pos,
611 end_pos: end_pos,
d9579d0f
AL
612 lines: RefCell::new(file_local_lines),
613 multibyte_chars: RefCell::new(file_local_multibyte_chars),
c34b1796
AL
614 });
615
616 files.push(filemap.clone());
617
618 filemap
619 }
620
1a4d82fc 621 pub fn mk_substr_filename(&self, sp: Span) -> String {
223e47cc 622 let pos = self.lookup_char_pos(sp.lo);
1a4d82fc
JJ
623 (format!("<{}:{}:{}>",
624 pos.file.name,
625 pos.line,
85aaf69f 626 pos.col.to_usize() + 1)).to_string()
223e47cc
LB
627 }
628
629 /// Lookup source information about a BytePos
970d7e83 630 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
d9579d0f
AL
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 {:?}",
637 pos, linebpos);
638 debug!("char pos {:?} is on the line at char pos {:?}",
639 chpos, linechpos);
640 debug!("byte is on line: {}", line);
641 assert!(chpos >= linechpos);
642 Loc {
643 file: f,
644 line: line,
645 col: chpos - linechpos
646 }
647 }
648
649 fn lookup_line(&self, pos: BytePos) -> FileMapAndLine {
650 let idx = self.lookup_filemap_idx(pos);
651
652 let files = self.files.borrow();
653 let f = (*files)[idx].clone();
654 let mut a = 0;
655 {
656 let lines = f.lines.borrow();
657 let mut b = lines.len();
658 while b - a > 1 {
659 let m = (a + b) / 2;
660 if (*lines)[m] > pos { b = m; } else { a = m; }
661 }
662 }
663 FileMapAndLine {fm: f, line: a}
223e47cc
LB
664 }
665
970d7e83 666 pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
223e47cc 667 let loc = self.lookup_char_pos(pos);
1a4d82fc
JJ
668 LocWithOpt {
669 filename: loc.file.name.to_string(),
670 line: loc.line,
671 col: loc.col,
672 file: Some(loc.file)
223e47cc
LB
673 }
674 }
675
1a4d82fc 676 pub fn span_to_string(&self, sp: Span) -> String {
9346a6ac 677 if self.files.borrow().is_empty() && sp == DUMMY_SP {
1a4d82fc 678 return "no-location".to_string();
223e47cc
LB
679 }
680
681 let lo = self.lookup_char_pos_adj(sp.lo);
682 let hi = self.lookup_char_pos_adj(sp.hi);
1a4d82fc
JJ
683 return (format!("{}:{}:{}: {}:{}",
684 lo.filename,
685 lo.line,
85aaf69f 686 lo.col.to_usize() + 1,
1a4d82fc 687 hi.line,
85aaf69f 688 hi.col.to_usize() + 1)).to_string()
223e47cc
LB
689 }
690
1a4d82fc
JJ
691 pub fn span_to_filename(&self, sp: Span) -> FileName {
692 self.lookup_char_pos(sp.lo).file.name.to_string()
223e47cc
LB
693 }
694
d9579d0f
AL
695 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
696 if sp.lo > sp.hi {
697 return Err(SpanLinesError::IllFormedSpan(sp));
698 }
699
223e47cc
LB
700 let lo = self.lookup_char_pos(sp.lo);
701 let hi = self.lookup_char_pos(sp.hi);
d9579d0f
AL
702
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),
707 }));
708 }
709 assert!(hi.line >= lo.line);
710
9346a6ac
AL
711 let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
712
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;
716
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
720 // lines.
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);
727 }
728
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,
732 end_col: hi.col });
733
d9579d0f 734 Ok(FileLines {file: lo.file, lines: lines})
223e47cc
LB
735 }
736
85aaf69f
SL
737 pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> {
738 if sp.lo > sp.hi {
739 return Err(SpanSnippetError::IllFormedSpan(sp));
740 }
741
c34b1796
AL
742 let local_begin = self.lookup_byte_offset(sp.lo);
743 let local_end = self.lookup_byte_offset(sp.hi);
1a4d82fc 744
c34b1796 745 if local_begin.fm.start_pos != local_end.fm.start_pos {
85aaf69f 746 return Err(SpanSnippetError::DistinctSources(DistinctSources {
c34b1796
AL
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)
85aaf69f 751 }));
1a4d82fc 752 } else {
c34b1796
AL
753 match local_begin.fm.src {
754 Some(ref 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();
759
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,
767 }));
768 }
85aaf69f 769
c34b1796
AL
770 return Ok((&src[start_index..end_index]).to_string())
771 }
772 None => {
773 return Err(SpanSnippetError::SourceNotAvailable {
774 filename: local_begin.fm.name.clone()
775 });
776 }
777 }
1a4d82fc 778 }
223e47cc
LB
779 }
780
1a4d82fc 781 pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> {
85aaf69f 782 for fm in &*self.files.borrow() {
1a4d82fc
JJ
783 if filename == fm.name {
784 return fm.clone();
785 }
786 }
787 panic!("asking for {} which we don't know about", filename);
788 }
789
c34b1796 790 /// For a global BytePos compute the local offset within the containing FileMap
1a4d82fc
JJ
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}
796 }
797
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];
803
804 // The number of extra bytes due to multibyte chars in the FileMap
805 let mut total_extra_bytes = 0;
806
85aaf69f 807 for mbc in &*map.multibyte_chars.borrow() {
1a4d82fc
JJ
808 debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
809 if mbc.pos < bpos {
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
814 // character
85aaf69f 815 assert!(bpos.to_usize() >= mbc.pos.to_usize() + mbc.bytes);
1a4d82fc
JJ
816 } else {
817 break;
818 }
819 }
820
85aaf69f
SL
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)
223e47cc 823 }
223e47cc 824
85aaf69f 825 fn lookup_filemap_idx(&self, pos: BytePos) -> usize {
1a4d82fc
JJ
826 let files = self.files.borrow();
827 let files = &*files;
223e47cc 828 let len = files.len();
85aaf69f 829 let mut a = 0;
223e47cc 830 let mut b = len;
85aaf69f
SL
831 while b - a > 1 {
832 let m = (a + b) / 2;
1a4d82fc 833 if files[m].start_pos > pos {
223e47cc
LB
834 b = m;
835 } else {
836 a = m;
837 }
838 }
1a4d82fc
JJ
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.
843 loop {
844 let lines = files[a].lines.borrow();
845 let lines = lines;
9346a6ac 846 if !lines.is_empty() {
1a4d82fc
JJ
847 break;
848 }
849 if a == 0 {
850 panic!("position {} does not resolve to a source location",
85aaf69f 851 pos.to_usize());
1a4d82fc
JJ
852 }
853 a -= 1;
854 }
855 if a >= len {
856 panic!("position {} does not resolve to a source location",
85aaf69f 857 pos.to_usize())
223e47cc
LB
858 }
859
860 return a;
861 }
862
1a4d82fc
JJ
863 pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId {
864 let mut expansions = self.expansions.borrow_mut();
865 expansions.push(expn_info);
9346a6ac
AL
866 let len = expansions.len();
867 if len > u32::max_value() as usize {
868 panic!("too many ExpnInfo's!");
869 }
870 ExpnId(len as u32 - 1)
223e47cc
LB
871 }
872
1a4d82fc
JJ
873 pub fn with_expn_info<T, F>(&self, id: ExpnId, f: F) -> T where
874 F: FnOnce(Option<&ExpnInfo>) -> T,
875 {
876 match id {
9346a6ac 877 NO_EXPANSION | COMMAND_LINE_EXPN => f(None),
85aaf69f 878 ExpnId(i) => f(Some(&(*self.expansions.borrow())[i as usize]))
1a4d82fc 879 }
223e47cc
LB
880 }
881
c34b1796
AL
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;
889 loop {
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| {
893
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
897 });
898
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
905 true
1a4d82fc 906 } else {
c34b1796
AL
907 // not the right place, keep looking
908 expn_id = info.call_site.expn_id;
909 false
1a4d82fc 910 }
c34b1796
AL
911 })
912 });
913 if quit {
914 break
223e47cc 915 }
c34b1796
AL
916 }
917 debug!("span_allows_unstable? {}", allows_unstable);
918 allows_unstable
223e47cc
LB
919 }
920}
921
c34b1796 922// _____________________________________________________________________________
d9579d0f 923// SpanLinesError, SpanSnippetError, DistinctSources, MalformedCodemapPositions
c34b1796
AL
924//
925
d9579d0f
AL
926pub type FileLinesResult = Result<FileLines, SpanLinesError>;
927
928#[derive(Clone, PartialEq, Eq, Debug)]
929pub enum SpanLinesError {
930 IllFormedSpan(Span),
931 DistinctSources(DistinctSources),
932}
933
85aaf69f
SL
934#[derive(Clone, PartialEq, Eq, Debug)]
935pub enum SpanSnippetError {
936 IllFormedSpan(Span),
937 DistinctSources(DistinctSources),
938 MalformedForCodemap(MalformedCodemapPositions),
c34b1796 939 SourceNotAvailable { filename: String }
85aaf69f
SL
940}
941
942#[derive(Clone, PartialEq, Eq, Debug)]
943pub struct DistinctSources {
944 begin: (String, BytePos),
945 end: (String, BytePos)
946}
947
948#[derive(Clone, PartialEq, Eq, Debug)]
949pub struct MalformedCodemapPositions {
950 name: String,
951 source_len: usize,
952 begin_pos: BytePos,
953 end_pos: BytePos
954}
955
c34b1796
AL
956
957// _____________________________________________________________________________
958// Tests
959//
960
223e47cc 961#[cfg(test)]
d9579d0f 962mod tests {
223e47cc 963 use super::*;
9346a6ac 964 use std::rc::Rc;
223e47cc
LB
965
966 #[test]
967 fn t1 () {
968 let cm = CodeMap::new();
1a4d82fc
JJ
969 let fm = cm.new_filemap("blork.rs".to_string(),
970 "first line.\nsecond line".to_string());
223e47cc 971 fm.next_line(BytePos(0));
9346a6ac 972 assert_eq!(fm.get_line(0), Some("first line."));
223e47cc
LB
973 // TESTING BROKEN BEHAVIOR:
974 fm.next_line(BytePos(10));
9346a6ac 975 assert_eq!(fm.get_line(1), Some("."));
223e47cc
LB
976 }
977
978 #[test]
c34b1796 979 #[should_panic]
223e47cc
LB
980 fn t2 () {
981 let cm = CodeMap::new();
1a4d82fc
JJ
982 let fm = cm.new_filemap("blork.rs".to_string(),
983 "first line.\nsecond line".to_string());
223e47cc
LB
984 // TESTING *REALLY* BROKEN BEHAVIOR:
985 fm.next_line(BytePos(0));
986 fm.next_line(BytePos(10));
987 fm.next_line(BytePos(2));
988 }
1a4d82fc
JJ
989
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(),
995 "".to_string());
996 let fm3 = cm.new_filemap("blork2.rs".to_string(),
997 "first line.\nsecond line".to_string());
998
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));
1004
1005 cm
1006 }
1007
1008 #[test]
1009 fn t3() {
1010 // Test lookup_byte_offset
1011 let cm = init_code_map();
1012
1013 let fmabp1 = cm.lookup_byte_offset(BytePos(22));
1014 assert_eq!(fmabp1.fm.name, "blork.rs");
1015 assert_eq!(fmabp1.pos, BytePos(22));
1016
1017 let fmabp2 = cm.lookup_byte_offset(BytePos(24));
1018 assert_eq!(fmabp2.fm.name, "blork2.rs");
1019 assert_eq!(fmabp2.pos, BytePos(0));
1020 }
1021
1022 #[test]
1023 fn t4() {
1024 // Test bytepos_to_file_charpos
1025 let cm = init_code_map();
1026
1027 let cp1 = cm.bytepos_to_file_charpos(BytePos(22));
1028 assert_eq!(cp1, CharPos(22));
1029
1030 let cp2 = cm.bytepos_to_file_charpos(BytePos(24));
1031 assert_eq!(cp2, CharPos(0));
1032 }
1033
1034 #[test]
1035 fn t5() {
1036 // Test zero-length filemaps.
1037 let cm = init_code_map();
1038
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));
1043
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));
1048 }
1049
1050 fn init_code_map_mbc() -> CodeMap {
1051 let cm = CodeMap::new();
1052 // € is a three byte utf8 char.
1053 let fm1 =
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());
1058
1059 fm1.next_line(BytePos(0));
1060 fm1.next_line(BytePos(22));
1061 fm2.next_line(BytePos(40));
1062 fm2.next_line(BytePos(58));
1063
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);
1072
1073 cm
1074 }
1075
1076 #[test]
1077 fn t6() {
1078 // Test bytepos_to_file_charpos in the presence of multi-byte chars
1079 let cm = init_code_map_mbc();
1080
1081 let cp1 = cm.bytepos_to_file_charpos(BytePos(3));
1082 assert_eq!(cp1, CharPos(3));
1083
1084 let cp2 = cm.bytepos_to_file_charpos(BytePos(6));
1085 assert_eq!(cp2, CharPos(4));
1086
1087 let cp3 = cm.bytepos_to_file_charpos(BytePos(56));
1088 assert_eq!(cp3, CharPos(12));
1089
1090 let cp4 = cm.bytepos_to_file_charpos(BytePos(61));
1091 assert_eq!(cp4, CharPos(15));
1092 }
1093
1094 #[test]
1095 fn t7() {
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};
d9579d0f 1099 let file_lines = cm.span_to_lines(span).unwrap();
1a4d82fc
JJ
1100
1101 assert_eq!(file_lines.file.name, "blork.rs");
1102 assert_eq!(file_lines.lines.len(), 1);
9346a6ac
AL
1103 assert_eq!(file_lines.lines[0].line_index, 1);
1104 }
1105
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 }
1115 }
1116
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));
1123
1124 // update byte_pos to include this line and the \n at the end
1125 byte_pos += line.len() as u32 + 1;
1126 }
1127 fm
1128 }
1129
1130 /// Test span_to_snippet and span_to_lines for a span coverting 3
1131 /// lines in the middle of a file.
1132 #[test]
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);
1139
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");
1142
1143 // check that span_to_lines gives us the complete result with the lines/cols we expected
d9579d0f 1144 let lines = cm.span_to_lines(span).unwrap();
9346a6ac
AL
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) }
1149 ];
1150 assert_eq!(lines.lines, expected);
1a4d82fc
JJ
1151 }
1152
1153 #[test]
1154 fn t8() {
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);
1159
85aaf69f 1160 assert_eq!(snippet, Ok("second line".to_string()));
1a4d82fc
JJ
1161 }
1162
1163 #[test]
1164 fn t9() {
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);
1169
1170 assert_eq!(sstr, "blork.rs:2:1: 2:12");
1171 }
223e47cc 1172}