]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/codemap.rs
Doc says to avoid mixing allocator instead of forbiding it
[rust.git] / src / libsyntax / codemap.rs
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 //
11 // ignore-lexer-test FIXME #15679
12
13 /*!
14
15 The CodeMap tracks all the source code used within a single crate, mapping
16 from integer byte positions to the original source code location. Each bit of
17 source parsed during crate parsing (typically files, in-memory strings, or
18 various bits of macro expansion) cover a continuous range of bytes in the
19 CodeMap and are represented by FileMaps. Byte positions are stored in `spans`
20 and used pervasively in the compiler. They are absolute positions within the
21 CodeMap, which upon request can be converted to line and column information,
22 source code snippets, etc.
23
24 */
25
26 use serialize::{Encodable, Decodable, Encoder, Decoder};
27 use std::cell::RefCell;
28 use std::gc::Gc;
29 use std::rc::Rc;
30
31 pub trait Pos {
32     fn from_uint(n: uint) -> Self;
33     fn to_uint(&self) -> uint;
34 }
35
36 /// A byte offset. Keep this small (currently 32-bits), as AST contains
37 /// a lot of them.
38 #[deriving(Clone, PartialEq, Eq, Hash, PartialOrd, Show)]
39 pub struct BytePos(pub u32);
40
41 /// A character offset. Because of multibyte utf8 characters, a byte offset
42 /// is not equivalent to a character offset. The CodeMap will convert BytePos
43 /// values to CharPos values as necessary.
44 #[deriving(PartialEq, Hash, PartialOrd, Show)]
45 pub struct CharPos(pub uint);
46
47 // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
48 // have been unsuccessful
49
50 impl Pos for BytePos {
51     fn from_uint(n: uint) -> BytePos { BytePos(n as u32) }
52     fn to_uint(&self) -> uint { let BytePos(n) = *self; n as uint }
53 }
54
55 impl Add<BytePos, BytePos> for BytePos {
56     fn add(&self, rhs: &BytePos) -> BytePos {
57         BytePos((self.to_uint() + rhs.to_uint()) as u32)
58     }
59 }
60
61 impl Sub<BytePos, BytePos> for BytePos {
62     fn sub(&self, rhs: &BytePos) -> BytePos {
63         BytePos((self.to_uint() - rhs.to_uint()) as u32)
64     }
65 }
66
67 impl Pos for CharPos {
68     fn from_uint(n: uint) -> CharPos { CharPos(n) }
69     fn to_uint(&self) -> uint { let CharPos(n) = *self; n }
70 }
71
72 impl Add<CharPos,CharPos> for CharPos {
73     fn add(&self, rhs: &CharPos) -> CharPos {
74         CharPos(self.to_uint() + rhs.to_uint())
75     }
76 }
77
78 impl Sub<CharPos,CharPos> for CharPos {
79     fn sub(&self, rhs: &CharPos) -> CharPos {
80         CharPos(self.to_uint() - rhs.to_uint())
81     }
82 }
83
84 /**
85 Spans represent a region of code, used for error reporting. Positions in spans
86 are *absolute* positions from the beginning of the codemap, not positions
87 relative to FileMaps. Methods on the CodeMap can be used to relate spans back
88 to the original source.
89 */
90 #[deriving(Clone, Show, Hash)]
91 pub struct Span {
92     pub lo: BytePos,
93     pub hi: BytePos,
94     /// Information about where the macro came from, if this piece of
95     /// code was created by a macro expansion.
96     pub expn_info: Option<Gc<ExpnInfo>>
97 }
98
99 pub static DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_info: None };
100
101 #[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash, Show)]
102 pub struct Spanned<T> {
103     pub node: T,
104     pub span: Span,
105 }
106
107 impl PartialEq for Span {
108     fn eq(&self, other: &Span) -> bool {
109         return (*self).lo == (*other).lo && (*self).hi == (*other).hi;
110     }
111     fn ne(&self, other: &Span) -> bool { !(*self).eq(other) }
112 }
113
114 impl Eq for Span {}
115
116 impl<S:Encoder<E>, E> Encodable<S, E> for Span {
117     /* Note #1972 -- spans are encoded but not decoded */
118     fn encode(&self, s: &mut S) -> Result<(), E> {
119         s.emit_nil()
120     }
121 }
122
123 impl<D:Decoder<E>, E> Decodable<D, E> for Span {
124     fn decode(_d: &mut D) -> Result<Span, E> {
125         Ok(DUMMY_SP)
126     }
127 }
128
129 pub fn spanned<T>(lo: BytePos, hi: BytePos, t: T) -> Spanned<T> {
130     respan(mk_sp(lo, hi), t)
131 }
132
133 pub fn respan<T>(sp: Span, t: T) -> Spanned<T> {
134     Spanned {node: t, span: sp}
135 }
136
137 pub fn dummy_spanned<T>(t: T) -> Spanned<T> {
138     respan(DUMMY_SP, t)
139 }
140
141 /* assuming that we're not in macro expansion */
142 pub fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
143     Span {lo: lo, hi: hi, expn_info: None}
144 }
145
146 /// Return the span itself if it doesn't come from a macro expansion,
147 /// otherwise return the call site span up to the `enclosing_sp` by
148 /// following the `expn_info` chain.
149 pub fn original_sp(sp: Span, enclosing_sp: Span) -> Span {
150     match (sp.expn_info, enclosing_sp.expn_info) {
151         (None, _) => sp,
152         (Some(expn1), Some(expn2)) if expn1.call_site == expn2.call_site => sp,
153         (Some(expn1), _) => original_sp(expn1.call_site, enclosing_sp),
154     }
155 }
156
157 /// A source code location used for error reporting
158 pub struct Loc {
159     /// Information about the original source
160     pub file: Rc<FileMap>,
161     /// The (1-based) line number
162     pub line: uint,
163     /// The (0-based) column offset
164     pub col: CharPos
165 }
166
167 /// A source code location used as the result of lookup_char_pos_adj
168 // Actually, *none* of the clients use the filename *or* file field;
169 // perhaps they should just be removed.
170 pub struct LocWithOpt {
171     pub filename: FileName,
172     pub line: uint,
173     pub col: CharPos,
174     pub file: Option<Rc<FileMap>>,
175 }
176
177 // used to be structural records. Better names, anyone?
178 pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: uint }
179 pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos }
180
181 /// The syntax with which a macro was invoked.
182 #[deriving(Clone, Hash, Show)]
183 pub enum MacroFormat {
184     /// e.g. #[deriving(...)] <item>
185     MacroAttribute,
186     /// e.g. `format!()`
187     MacroBang
188 }
189
190 #[deriving(Clone, Hash, Show)]
191 pub struct NameAndSpan {
192     /// The name of the macro that was invoked to create the thing
193     /// with this Span.
194     pub name: String,
195     /// The format with which the macro was invoked.
196     pub format: MacroFormat,
197     /// The span of the macro definition itself. The macro may not
198     /// have a sensible definition span (e.g. something defined
199     /// completely inside libsyntax) in which case this is None.
200     pub span: Option<Span>
201 }
202
203 /// Extra information for tracking macro expansion of spans
204 #[deriving(Hash, Show)]
205 pub struct ExpnInfo {
206     /// The location of the actual macro invocation, e.g. `let x =
207     /// foo!();`
208     ///
209     /// This may recursively refer to other macro invocations, e.g. if
210     /// `foo!()` invoked `bar!()` internally, and there was an
211     /// expression inside `bar!`; the call_site of the expression in
212     /// the expansion would point to the `bar!` invocation; that
213     /// call_site span would have its own ExpnInfo, with the call_site
214     /// pointing to the `foo!` invocation.
215     pub call_site: Span,
216     /// Information about the macro and its definition.
217     ///
218     /// The `callee` of the inner expression in the `call_site`
219     /// example would point to the `macro_rules! bar { ... }` and that
220     /// of the `bar!()` invocation would point to the `macro_rules!
221     /// foo { ... }`.
222     pub callee: NameAndSpan
223 }
224
225 pub type FileName = String;
226
227 pub struct FileLines {
228     pub file: Rc<FileMap>,
229     pub lines: Vec<uint>
230 }
231
232 /// Identifies an offset of a multi-byte character in a FileMap
233 pub struct MultiByteChar {
234     /// The absolute offset of the character in the CodeMap
235     pub pos: BytePos,
236     /// The number of bytes, >=2
237     pub bytes: uint,
238 }
239
240 /// A single source in the CodeMap
241 pub struct FileMap {
242     /// The name of the file that the source came from, source that doesn't
243     /// originate from files has names between angle brackets by convention,
244     /// e.g. `<anon>`
245     pub name: FileName,
246     /// The complete source code
247     pub src: String,
248     /// The start position of this source in the CodeMap
249     pub start_pos: BytePos,
250     /// Locations of lines beginnings in the source code
251     pub lines: RefCell<Vec<BytePos> >,
252     /// Locations of multi-byte characters in the source code
253     pub multibyte_chars: RefCell<Vec<MultiByteChar> >,
254 }
255
256 impl FileMap {
257     /// EFFECT: register a start-of-line offset in the
258     /// table of line-beginnings.
259     /// UNCHECKED INVARIANT: these offsets must be added in the right
260     /// order and must be in the right places; there is shared knowledge
261     /// about what ends a line between this file and parse.rs
262     /// WARNING: pos param here is the offset relative to start of CodeMap,
263     /// and CodeMap will append a newline when adding a filemap without a newline at the end,
264     /// so the safe way to call this is with value calculated as
265     /// filemap.start_pos + newline_offset_relative_to_the_start_of_filemap.
266     pub fn next_line(&self, pos: BytePos) {
267         // the new charpos must be > the last one (or it's the first one).
268         let mut lines = self.lines.borrow_mut();;
269         let line_len = lines.len();
270         assert!(line_len == 0 || (*lines.get(line_len - 1) < pos))
271         lines.push(pos);
272     }
273
274     /// get a line from the list of pre-computed line-beginnings
275     pub fn get_line(&self, line: int) -> String {
276         let mut lines = self.lines.borrow_mut();
277         let begin: BytePos = *lines.get(line as uint) - self.start_pos;
278         let begin = begin.to_uint();
279         let slice = self.src.as_slice().slice_from(begin);
280         match slice.find('\n') {
281             Some(e) => slice.slice_to(e).to_string(),
282             None => slice.to_string()
283         }
284     }
285
286     pub fn record_multibyte_char(&self, pos: BytePos, bytes: uint) {
287         assert!(bytes >=2 && bytes <= 4);
288         let mbc = MultiByteChar {
289             pos: pos,
290             bytes: bytes,
291         };
292         self.multibyte_chars.borrow_mut().push(mbc);
293     }
294
295     pub fn is_real_file(&self) -> bool {
296         !(self.name.as_slice().starts_with("<") &&
297           self.name.as_slice().ends_with(">"))
298     }
299 }
300
301 pub struct CodeMap {
302     pub files: RefCell<Vec<Rc<FileMap>>>
303 }
304
305 impl CodeMap {
306     pub fn new() -> CodeMap {
307         CodeMap {
308             files: RefCell::new(Vec::new()),
309         }
310     }
311
312     pub fn new_filemap(&self, filename: FileName, src: String) -> Rc<FileMap> {
313         let mut files = self.files.borrow_mut();
314         let start_pos = match files.last() {
315             None => 0,
316             Some(last) => last.start_pos.to_uint() + last.src.len(),
317         };
318
319         // Remove utf-8 BOM if any.
320         // FIXME #12884: no efficient/safe way to remove from the start of a string
321         // and reuse the allocation.
322         let mut src = if src.as_slice().starts_with("\ufeff") {
323             String::from_str(src.as_slice().slice_from(3))
324         } else {
325             String::from_str(src.as_slice())
326         };
327
328         // Append '\n' in case it's not already there.
329         // This is a workaround to prevent CodeMap.lookup_filemap_idx from accidentally
330         // overflowing into the next filemap in case the last byte of span is also the last
331         // byte of filemap, which leads to incorrect results from CodeMap.span_to_*.
332         if src.len() > 0 && !src.as_slice().ends_with("\n") {
333             src.push_char('\n');
334         }
335
336         let filemap = Rc::new(FileMap {
337             name: filename,
338             src: src.to_string(),
339             start_pos: Pos::from_uint(start_pos),
340             lines: RefCell::new(Vec::new()),
341             multibyte_chars: RefCell::new(Vec::new()),
342         });
343
344         files.push(filemap.clone());
345
346         filemap
347     }
348
349     pub fn mk_substr_filename(&self, sp: Span) -> String {
350         let pos = self.lookup_char_pos(sp.lo);
351         (format!("<{}:{}:{}>",
352                  pos.file.name,
353                  pos.line,
354                  pos.col.to_uint() + 1)).to_string()
355     }
356
357     /// Lookup source information about a BytePos
358     pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
359         self.lookup_pos(pos)
360     }
361
362     pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
363         let loc = self.lookup_char_pos(pos);
364         LocWithOpt {
365             filename: loc.file.name.to_string(),
366             line: loc.line,
367             col: loc.col,
368             file: Some(loc.file)
369         }
370     }
371
372     pub fn span_to_string(&self, sp: Span) -> String {
373         if self.files.borrow().len() == 0 && sp == DUMMY_SP {
374             return "no-location".to_string();
375         }
376
377         let lo = self.lookup_char_pos_adj(sp.lo);
378         let hi = self.lookup_char_pos_adj(sp.hi);
379         return (format!("{}:{}:{}: {}:{}",
380                         lo.filename,
381                         lo.line,
382                         lo.col.to_uint() + 1,
383                         hi.line,
384                         hi.col.to_uint() + 1)).to_string()
385     }
386
387     pub fn span_to_filename(&self, sp: Span) -> FileName {
388         self.lookup_char_pos(sp.lo).file.name.to_string()
389     }
390
391     pub fn span_to_lines(&self, sp: Span) -> FileLines {
392         let lo = self.lookup_char_pos(sp.lo);
393         let hi = self.lookup_char_pos(sp.hi);
394         let mut lines = Vec::new();
395         for i in range(lo.line - 1u, hi.line as uint) {
396             lines.push(i);
397         };
398         FileLines {file: lo.file, lines: lines}
399     }
400
401     pub fn span_to_snippet(&self, sp: Span) -> Option<String> {
402         let begin = self.lookup_byte_offset(sp.lo);
403         let end = self.lookup_byte_offset(sp.hi);
404
405         // FIXME #8256: this used to be an assert but whatever precondition
406         // it's testing isn't true for all spans in the AST, so to allow the
407         // caller to not have to fail (and it can't catch it since the CodeMap
408         // isn't sendable), return None
409         if begin.fm.start_pos != end.fm.start_pos {
410             None
411         } else {
412             Some(begin.fm.src.as_slice().slice(begin.pos.to_uint(),
413                                                end.pos.to_uint()).to_string())
414         }
415     }
416
417     pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> {
418         for fm in self.files.borrow().iter() {
419             if filename == fm.name.as_slice() {
420                 return fm.clone();
421             }
422         }
423         fail!("asking for {} which we don't know about", filename);
424     }
425
426     pub fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos {
427         let idx = self.lookup_filemap_idx(bpos);
428         let fm = self.files.borrow().get(idx).clone();
429         let offset = bpos - fm.start_pos;
430         FileMapAndBytePos {fm: fm, pos: offset}
431     }
432
433     /// Converts an absolute BytePos to a CharPos relative to the filemap and above.
434     pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
435         let idx = self.lookup_filemap_idx(bpos);
436         let files = self.files.borrow();
437         let map = files.get(idx);
438
439         // The number of extra bytes due to multibyte chars in the FileMap
440         let mut total_extra_bytes = 0;
441
442         for mbc in map.multibyte_chars.borrow().iter() {
443             debug!("{}-byte char at {}", mbc.bytes, mbc.pos);
444             if mbc.pos < bpos {
445                 // every character is at least one byte, so we only
446                 // count the actual extra bytes.
447                 total_extra_bytes += mbc.bytes - 1;
448                 // We should never see a byte position in the middle of a
449                 // character
450                 assert!(bpos.to_uint() >= mbc.pos.to_uint() + mbc.bytes);
451             } else {
452                 break;
453             }
454         }
455
456         assert!(map.start_pos.to_uint() + total_extra_bytes <= bpos.to_uint());
457         CharPos(bpos.to_uint() - map.start_pos.to_uint() - total_extra_bytes)
458     }
459
460     fn lookup_filemap_idx(&self, pos: BytePos) -> uint {
461         let files = self.files.borrow();
462         let files = files;
463         let len = files.len();
464         let mut a = 0u;
465         let mut b = len;
466         while b - a > 1u {
467             let m = (a + b) / 2u;
468             if files.get(m).start_pos > pos {
469                 b = m;
470             } else {
471                 a = m;
472             }
473         }
474         // There can be filemaps with length 0. These have the same start_pos as the previous
475         // filemap, but are not the filemaps we want (because they are length 0, they cannot
476         // contain what we are looking for). So, rewind until we find a useful filemap.
477         loop {
478             let lines = files.get(a).lines.borrow();
479             let lines = lines;
480             if lines.len() > 0 {
481                 break;
482             }
483             if a == 0 {
484                 fail!("position {} does not resolve to a source location", pos.to_uint());
485             }
486             a -= 1;
487         }
488         if a >= len {
489             fail!("position {} does not resolve to a source location", pos.to_uint())
490         }
491
492         return a;
493     }
494
495     fn lookup_line(&self, pos: BytePos) -> FileMapAndLine {
496         let idx = self.lookup_filemap_idx(pos);
497
498         let files = self.files.borrow();
499         let f = files.get(idx).clone();
500         let mut a = 0u;
501         {
502             let mut lines = f.lines.borrow_mut();
503             let mut b = lines.len();
504             while b - a > 1u {
505                 let m = (a + b) / 2u;
506                 if *lines.get(m) > pos { b = m; } else { a = m; }
507             }
508         }
509         FileMapAndLine {fm: f, line: a}
510     }
511
512     fn lookup_pos(&self, pos: BytePos) -> Loc {
513         let FileMapAndLine {fm: f, line: a} = self.lookup_line(pos);
514         let line = a + 1u; // Line numbers start at 1
515         let chpos = self.bytepos_to_file_charpos(pos);
516         let linebpos = *f.lines.borrow().get(a);
517         let linechpos = self.bytepos_to_file_charpos(linebpos);
518         debug!("byte pos {} is on the line at byte pos {}",
519                pos, linebpos);
520         debug!("char pos {} is on the line at char pos {}",
521                chpos, linechpos);
522         debug!("byte is on line: {}", line);
523         assert!(chpos >= linechpos);
524         Loc {
525             file: f,
526             line: line,
527             col: chpos - linechpos
528         }
529     }
530 }
531
532 #[cfg(test)]
533 mod test {
534     use super::*;
535
536     #[test]
537     fn t1 () {
538         let cm = CodeMap::new();
539         let fm = cm.new_filemap("blork.rs".to_string(),
540                                 "first line.\nsecond line".to_string());
541         fm.next_line(BytePos(0));
542         assert_eq!(&fm.get_line(0),&"first line.".to_string());
543         // TESTING BROKEN BEHAVIOR:
544         fm.next_line(BytePos(10));
545         assert_eq!(&fm.get_line(1), &".".to_string());
546     }
547
548     #[test]
549     #[should_fail]
550     fn t2 () {
551         let cm = CodeMap::new();
552         let fm = cm.new_filemap("blork.rs".to_string(),
553                                 "first line.\nsecond line".to_string());
554         // TESTING *REALLY* BROKEN BEHAVIOR:
555         fm.next_line(BytePos(0));
556         fm.next_line(BytePos(10));
557         fm.next_line(BytePos(2));
558     }
559
560     fn init_code_map() -> CodeMap {
561         let cm = CodeMap::new();
562         let fm1 = cm.new_filemap("blork.rs".to_string(),
563                                  "first line.\nsecond line".to_string());
564         let fm2 = cm.new_filemap("empty.rs".to_string(),
565                                  "".to_string());
566         let fm3 = cm.new_filemap("blork2.rs".to_string(),
567                                  "first line.\nsecond line".to_string());
568
569         fm1.next_line(BytePos(0));
570         fm1.next_line(BytePos(12));
571         fm2.next_line(BytePos(24));
572         fm3.next_line(BytePos(24));
573         fm3.next_line(BytePos(34));
574
575         cm
576     }
577
578     #[test]
579     fn t3() {
580         // Test lookup_byte_offset
581         let cm = init_code_map();
582
583         let fmabp1 = cm.lookup_byte_offset(BytePos(22));
584         assert_eq!(fmabp1.fm.name, "blork.rs".to_string());
585         assert_eq!(fmabp1.pos, BytePos(22));
586
587         let fmabp2 = cm.lookup_byte_offset(BytePos(24));
588         assert_eq!(fmabp2.fm.name, "blork2.rs".to_string());
589         assert_eq!(fmabp2.pos, BytePos(0));
590     }
591
592     #[test]
593     fn t4() {
594         // Test bytepos_to_file_charpos
595         let cm = init_code_map();
596
597         let cp1 = cm.bytepos_to_file_charpos(BytePos(22));
598         assert_eq!(cp1, CharPos(22));
599
600         let cp2 = cm.bytepos_to_file_charpos(BytePos(24));
601         assert_eq!(cp2, CharPos(0));
602     }
603
604     #[test]
605     fn t5() {
606         // Test zero-length filemaps.
607         let cm = init_code_map();
608
609         let loc1 = cm.lookup_char_pos(BytePos(22));
610         assert_eq!(loc1.file.name, "blork.rs".to_string());
611         assert_eq!(loc1.line, 2);
612         assert_eq!(loc1.col, CharPos(10));
613
614         let loc2 = cm.lookup_char_pos(BytePos(24));
615         assert_eq!(loc2.file.name, "blork2.rs".to_string());
616         assert_eq!(loc2.line, 1);
617         assert_eq!(loc2.col, CharPos(0));
618     }
619
620     fn init_code_map_mbc() -> CodeMap {
621         let cm = CodeMap::new();
622         // € is a three byte utf8 char.
623         let fm1 =
624             cm.new_filemap("blork.rs".to_string(),
625                            "fir€st €€€€ line.\nsecond line".to_string());
626         let fm2 = cm.new_filemap("blork2.rs".to_string(),
627                                  "first line€€.\n€ second line".to_string());
628
629         fm1.next_line(BytePos(0));
630         fm1.next_line(BytePos(22));
631         fm2.next_line(BytePos(40));
632         fm2.next_line(BytePos(58));
633
634         fm1.record_multibyte_char(BytePos(3), 3);
635         fm1.record_multibyte_char(BytePos(9), 3);
636         fm1.record_multibyte_char(BytePos(12), 3);
637         fm1.record_multibyte_char(BytePos(15), 3);
638         fm1.record_multibyte_char(BytePos(18), 3);
639         fm2.record_multibyte_char(BytePos(50), 3);
640         fm2.record_multibyte_char(BytePos(53), 3);
641         fm2.record_multibyte_char(BytePos(58), 3);
642
643         cm
644     }
645
646     #[test]
647     fn t6() {
648         // Test bytepos_to_file_charpos in the presence of multi-byte chars
649         let cm = init_code_map_mbc();
650
651         let cp1 = cm.bytepos_to_file_charpos(BytePos(3));
652         assert_eq!(cp1, CharPos(3));
653
654         let cp2 = cm.bytepos_to_file_charpos(BytePos(6));
655         assert_eq!(cp2, CharPos(4));
656
657         let cp3 = cm.bytepos_to_file_charpos(BytePos(56));
658         assert_eq!(cp3, CharPos(12));
659
660         let cp4 = cm.bytepos_to_file_charpos(BytePos(61));
661         assert_eq!(cp4, CharPos(15));
662     }
663
664     #[test]
665     fn t7() {
666         // Test span_to_lines for a span ending at the end of filemap
667         let cm = init_code_map();
668         let span = Span {lo: BytePos(12), hi: BytePos(23), expn_info: None};
669         let file_lines = cm.span_to_lines(span);
670
671         assert_eq!(file_lines.file.name, "blork.rs".to_string());
672         assert_eq!(file_lines.lines.len(), 1);
673         assert_eq!(*file_lines.lines.get(0), 1u);
674     }
675
676     #[test]
677     fn t8() {
678         // Test span_to_snippet for a span ending at the end of filemap
679         let cm = init_code_map();
680         let span = Span {lo: BytePos(12), hi: BytePos(23), expn_info: None};
681         let snippet = cm.span_to_snippet(span);
682
683         assert_eq!(snippet, Some("second line".to_string()));
684     }
685
686     #[test]
687     fn t9() {
688         // Test span_to_str for a span ending at the end of filemap
689         let cm = init_code_map();
690         let span = Span {lo: BytePos(12), hi: BytePos(23), expn_info: None};
691         let sstr =  cm.span_to_string(span);
692
693         assert_eq!(sstr, "blork.rs:2:1: 2:12".to_string());
694     }
695 }