]>
Commit | Line | Data |
---|---|---|
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. | |
1a4d82fc JJ |
10 | // |
11 | // ignore-lexer-test FIXME #15679 | |
223e47cc | 12 | |
1a4d82fc JJ |
13 | //! The CodeMap tracks all the source code used within a single crate, mapping |
14 | //! from integer byte positions to the original source code location. Each bit | |
15 | //! of source parsed during crate parsing (typically files, in-memory strings, | |
16 | //! or various bits of macro expansion) cover a continuous range of bytes in the | |
17 | //! CodeMap and are represented by FileMaps. Byte positions are stored in | |
18 | //! `spans` and used pervasively in the compiler. They are absolute positions | |
19 | //! within the CodeMap, which upon request can be converted to line and column | |
20 | //! information, source code snippets, etc. | |
223e47cc | 21 | |
1a4d82fc | 22 | pub use self::MacroFormat::*; |
223e47cc | 23 | |
1a4d82fc JJ |
24 | use std::cell::RefCell; |
25 | use std::num::ToPrimitive; | |
26 | use std::ops::{Add, Sub}; | |
27 | use std::rc::Rc; | |
223e47cc | 28 | |
1a4d82fc JJ |
29 | use libc::c_uint; |
30 | use serialize::{Encodable, Decodable, Encoder, Decoder}; | |
223e47cc | 31 | |
c34b1796 AL |
32 | |
33 | // _____________________________________________________________________________ | |
34 | // Pos, BytePos, CharPos | |
35 | // | |
36 | ||
223e47cc | 37 | pub trait Pos { |
85aaf69f SL |
38 | fn from_usize(n: usize) -> Self; |
39 | fn to_usize(&self) -> usize; | |
223e47cc LB |
40 | } |
41 | ||
1a4d82fc JJ |
42 | /// A byte offset. Keep this small (currently 32-bits), as AST contains |
43 | /// a lot of them. | |
85aaf69f | 44 | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)] |
1a4d82fc JJ |
45 | pub struct BytePos(pub u32); |
46 | ||
223e47cc LB |
47 | /// A character offset. Because of multibyte utf8 characters, a byte offset |
48 | /// is not equivalent to a character offset. The CodeMap will convert BytePos | |
49 | /// values to CharPos values as necessary. | |
c34b1796 | 50 | #[derive(Copy, Clone, PartialEq, Hash, PartialOrd, Debug)] |
85aaf69f | 51 | pub struct CharPos(pub usize); |
223e47cc | 52 | |
1a4d82fc | 53 | // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix |
223e47cc LB |
54 | // have been unsuccessful |
55 | ||
56 | impl Pos for BytePos { | |
85aaf69f SL |
57 | fn from_usize(n: usize) -> BytePos { BytePos(n as u32) } |
58 | fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize } | |
223e47cc LB |
59 | } |
60 | ||
1a4d82fc JJ |
61 | impl Add for BytePos { |
62 | type Output = BytePos; | |
223e47cc | 63 | |
1a4d82fc | 64 | fn add(self, rhs: BytePos) -> BytePos { |
85aaf69f | 65 | BytePos((self.to_usize() + rhs.to_usize()) as u32) |
223e47cc LB |
66 | } |
67 | } | |
68 | ||
1a4d82fc JJ |
69 | impl Sub for BytePos { |
70 | type Output = BytePos; | |
71 | ||
72 | fn sub(self, rhs: BytePos) -> BytePos { | |
85aaf69f | 73 | BytePos((self.to_usize() - rhs.to_usize()) as u32) |
223e47cc LB |
74 | } |
75 | } | |
76 | ||
c34b1796 AL |
77 | impl Encodable for BytePos { |
78 | fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> { | |
79 | s.emit_u32(self.0) | |
80 | } | |
81 | } | |
82 | ||
83 | impl Decodable for BytePos { | |
84 | fn decode<D: Decoder>(d: &mut D) -> Result<BytePos, D::Error> { | |
85 | Ok(BytePos(try!{ d.read_u32() })) | |
86 | } | |
87 | } | |
88 | ||
223e47cc | 89 | impl Pos for CharPos { |
85aaf69f SL |
90 | fn from_usize(n: usize) -> CharPos { CharPos(n) } |
91 | fn to_usize(&self) -> usize { let CharPos(n) = *self; n } | |
223e47cc LB |
92 | } |
93 | ||
1a4d82fc JJ |
94 | impl Add for CharPos { |
95 | type Output = CharPos; | |
223e47cc | 96 | |
1a4d82fc | 97 | fn add(self, rhs: CharPos) -> CharPos { |
85aaf69f | 98 | CharPos(self.to_usize() + rhs.to_usize()) |
223e47cc LB |
99 | } |
100 | } | |
101 | ||
1a4d82fc JJ |
102 | impl Sub for CharPos { |
103 | type Output = CharPos; | |
104 | ||
105 | fn sub(self, rhs: CharPos) -> CharPos { | |
85aaf69f | 106 | CharPos(self.to_usize() - rhs.to_usize()) |
223e47cc LB |
107 | } |
108 | } | |
109 | ||
c34b1796 AL |
110 | // _____________________________________________________________________________ |
111 | // Span, Spanned | |
112 | // | |
113 | ||
1a4d82fc JJ |
114 | /// Spans represent a region of code, used for error reporting. Positions in spans |
115 | /// are *absolute* positions from the beginning of the codemap, not positions | |
116 | /// relative to FileMaps. Methods on the CodeMap can be used to relate spans back | |
117 | /// to the original source. | |
85aaf69f | 118 | #[derive(Clone, Copy, Debug, Hash)] |
1a4d82fc JJ |
119 | pub struct Span { |
120 | pub lo: BytePos, | |
121 | pub hi: BytePos, | |
122 | /// Information about where the macro came from, if this piece of | |
123 | /// code was created by a macro expansion. | |
124 | pub expn_id: ExpnId | |
223e47cc LB |
125 | } |
126 | ||
1a4d82fc JJ |
127 | pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION }; |
128 | ||
129 | // Generic span to be used for code originating from the command line | |
130 | pub const COMMAND_LINE_SP: Span = Span { lo: BytePos(0), | |
131 | hi: BytePos(0), | |
132 | expn_id: COMMAND_LINE_EXPN }; | |
133 | ||
85aaf69f | 134 | #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)] |
1a4d82fc JJ |
135 | pub struct Spanned<T> { |
136 | pub node: T, | |
137 | pub span: Span, | |
138 | } | |
223e47cc | 139 | |
1a4d82fc JJ |
140 | impl PartialEq for Span { |
141 | fn eq(&self, other: &Span) -> bool { | |
223e47cc LB |
142 | return (*self).lo == (*other).lo && (*self).hi == (*other).hi; |
143 | } | |
1a4d82fc | 144 | fn ne(&self, other: &Span) -> bool { !(*self).eq(other) } |
223e47cc LB |
145 | } |
146 | ||
1a4d82fc JJ |
147 | impl Eq for Span {} |
148 | ||
149 | impl Encodable for Span { | |
1a4d82fc | 150 | fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> { |
c34b1796 AL |
151 | // Encode spans as a single u64 in order to cut down on tagging overhead |
152 | // added by the RBML metadata encoding. The should be solved differently | |
153 | // altogether some time (FIXME #21482) | |
154 | s.emit_u64( (self.lo.0 as u64) | ((self.hi.0 as u64) << 32) ) | |
970d7e83 | 155 | } |
223e47cc LB |
156 | } |
157 | ||
1a4d82fc | 158 | impl Decodable for Span { |
c34b1796 AL |
159 | fn decode<D: Decoder>(d: &mut D) -> Result<Span, D::Error> { |
160 | let lo_hi: u64 = try! { d.read_u64() }; | |
161 | let lo = BytePos(lo_hi as u32); | |
162 | let hi = BytePos((lo_hi >> 32) as u32); | |
163 | Ok(mk_sp(lo, hi)) | |
223e47cc LB |
164 | } |
165 | } | |
166 | ||
1a4d82fc | 167 | pub fn spanned<T>(lo: BytePos, hi: BytePos, t: T) -> Spanned<T> { |
223e47cc LB |
168 | respan(mk_sp(lo, hi), t) |
169 | } | |
170 | ||
1a4d82fc JJ |
171 | pub fn respan<T>(sp: Span, t: T) -> Spanned<T> { |
172 | Spanned {node: t, span: sp} | |
223e47cc LB |
173 | } |
174 | ||
1a4d82fc JJ |
175 | pub fn dummy_spanned<T>(t: T) -> Spanned<T> { |
176 | respan(DUMMY_SP, t) | |
223e47cc LB |
177 | } |
178 | ||
179 | /* assuming that we're not in macro expansion */ | |
1a4d82fc JJ |
180 | pub fn mk_sp(lo: BytePos, hi: BytePos) -> Span { |
181 | Span {lo: lo, hi: hi, expn_id: NO_EXPANSION} | |
223e47cc LB |
182 | } |
183 | ||
1a4d82fc JJ |
184 | /// Return the span itself if it doesn't come from a macro expansion, |
185 | /// otherwise return the call site span up to the `enclosing_sp` by | |
186 | /// following the `expn_info` chain. | |
187 | pub fn original_sp(cm: &CodeMap, sp: Span, enclosing_sp: Span) -> Span { | |
188 | let call_site1 = cm.with_expn_info(sp.expn_id, |ei| ei.map(|ei| ei.call_site)); | |
189 | let call_site2 = cm.with_expn_info(enclosing_sp.expn_id, |ei| ei.map(|ei| ei.call_site)); | |
190 | match (call_site1, call_site2) { | |
191 | (None, _) => sp, | |
192 | (Some(call_site1), Some(call_site2)) if call_site1 == call_site2 => sp, | |
193 | (Some(call_site1), _) => original_sp(cm, call_site1, enclosing_sp), | |
194 | } | |
195 | } | |
223e47cc | 196 | |
c34b1796 AL |
197 | // _____________________________________________________________________________ |
198 | // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos | |
199 | // | |
200 | ||
223e47cc LB |
201 | /// A source code location used for error reporting |
202 | pub struct Loc { | |
203 | /// Information about the original source | |
1a4d82fc | 204 | pub file: Rc<FileMap>, |
223e47cc | 205 | /// The (1-based) line number |
85aaf69f | 206 | pub line: usize, |
223e47cc | 207 | /// The (0-based) column offset |
1a4d82fc | 208 | pub col: CharPos |
223e47cc LB |
209 | } |
210 | ||
211 | /// A source code location used as the result of lookup_char_pos_adj | |
212 | // Actually, *none* of the clients use the filename *or* file field; | |
213 | // perhaps they should just be removed. | |
214 | pub 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? | |
85aaf69f | 222 | pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize } |
1a4d82fc JJ |
223 | pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos } |
224 | ||
c34b1796 AL |
225 | |
226 | // _____________________________________________________________________________ | |
227 | // MacroFormat, NameAndSpan, ExpnInfo, ExpnId | |
228 | // | |
229 | ||
1a4d82fc | 230 | /// The syntax with which a macro was invoked. |
85aaf69f | 231 | #[derive(Clone, Copy, Hash, Debug)] |
1a4d82fc JJ |
232 | pub enum MacroFormat { |
233 | /// e.g. #[derive(...)] <item> | |
234 | MacroAttribute, | |
235 | /// e.g. `format!()` | |
236 | MacroBang | |
237 | } | |
238 | ||
85aaf69f | 239 | #[derive(Clone, Hash, Debug)] |
1a4d82fc JJ |
240 | pub struct NameAndSpan { |
241 | /// The name of the macro that was invoked to create the thing | |
242 | /// with this Span. | |
243 | pub name: String, | |
244 | /// The format with which the macro was invoked. | |
245 | pub format: MacroFormat, | |
c34b1796 AL |
246 | /// Whether the macro is allowed to use #[unstable]/feature-gated |
247 | /// features internally without forcing the whole crate to opt-in | |
248 | /// to them. | |
249 | pub allow_internal_unstable: bool, | |
1a4d82fc JJ |
250 | /// The span of the macro definition itself. The macro may not |
251 | /// have a sensible definition span (e.g. something defined | |
252 | /// completely inside libsyntax) in which case this is None. | |
253 | pub span: Option<Span> | |
223e47cc LB |
254 | } |
255 | ||
256 | /// Extra information for tracking macro expansion of spans | |
85aaf69f | 257 | #[derive(Hash, Debug)] |
1a4d82fc JJ |
258 | pub struct ExpnInfo { |
259 | /// The location of the actual macro invocation, e.g. `let x = | |
260 | /// foo!();` | |
261 | /// | |
262 | /// This may recursively refer to other macro invocations, e.g. if | |
263 | /// `foo!()` invoked `bar!()` internally, and there was an | |
264 | /// expression inside `bar!`; the call_site of the expression in | |
265 | /// the expansion would point to the `bar!` invocation; that | |
266 | /// call_site span would have its own ExpnInfo, with the call_site | |
267 | /// pointing to the `foo!` invocation. | |
268 | pub call_site: Span, | |
269 | /// Information about the macro and its definition. | |
270 | /// | |
271 | /// The `callee` of the inner expression in the `call_site` | |
272 | /// example would point to the `macro_rules! bar { ... }` and that | |
273 | /// of the `bar!()` invocation would point to the `macro_rules! | |
274 | /// foo { ... }`. | |
275 | pub callee: NameAndSpan | |
223e47cc LB |
276 | } |
277 | ||
85aaf69f | 278 | #[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)] |
1a4d82fc | 279 | pub struct ExpnId(u32); |
223e47cc | 280 | |
c34b1796 | 281 | pub const NO_EXPANSION: ExpnId = ExpnId(!0); |
1a4d82fc | 282 | // For code appearing from the command line |
c34b1796 | 283 | pub const COMMAND_LINE_EXPN: ExpnId = ExpnId(!1); |
1a4d82fc JJ |
284 | |
285 | impl ExpnId { | |
286 | pub fn from_llvm_cookie(cookie: c_uint) -> ExpnId { | |
c34b1796 | 287 | ExpnId(cookie) |
1a4d82fc JJ |
288 | } |
289 | ||
290 | pub fn to_llvm_cookie(self) -> i32 { | |
291 | let ExpnId(cookie) = self; | |
292 | cookie as i32 | |
293 | } | |
223e47cc LB |
294 | } |
295 | ||
c34b1796 AL |
296 | // _____________________________________________________________________________ |
297 | // FileMap, MultiByteChar, FileName, FileLines | |
298 | // | |
299 | ||
1a4d82fc JJ |
300 | pub type FileName = String; |
301 | ||
302 | pub struct FileLines { | |
303 | pub file: Rc<FileMap>, | |
85aaf69f | 304 | pub lines: Vec<usize> |
223e47cc LB |
305 | } |
306 | ||
307 | /// Identifies an offset of a multi-byte character in a FileMap | |
c34b1796 | 308 | #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Eq, PartialEq)] |
223e47cc LB |
309 | pub struct MultiByteChar { |
310 | /// The absolute offset of the character in the CodeMap | |
1a4d82fc | 311 | pub pos: BytePos, |
223e47cc | 312 | /// The number of bytes, >=2 |
85aaf69f | 313 | pub bytes: usize, |
223e47cc LB |
314 | } |
315 | ||
316 | /// A single source in the CodeMap | |
317 | pub struct FileMap { | |
318 | /// The name of the file that the source came from, source that doesn't | |
319 | /// originate from files has names between angle brackets by convention, | |
320 | /// e.g. `<anon>` | |
1a4d82fc | 321 | pub name: FileName, |
223e47cc | 322 | /// The complete source code |
c34b1796 | 323 | pub src: Option<Rc<String>>, |
223e47cc | 324 | /// The start position of this source in the CodeMap |
1a4d82fc | 325 | pub start_pos: BytePos, |
c34b1796 AL |
326 | /// The end position of this source in the CodeMap |
327 | pub end_pos: BytePos, | |
223e47cc | 328 | /// Locations of lines beginnings in the source code |
c34b1796 | 329 | pub lines: RefCell<Vec<BytePos>>, |
223e47cc | 330 | /// Locations of multi-byte characters in the source code |
c34b1796 AL |
331 | pub multibyte_chars: RefCell<Vec<MultiByteChar>>, |
332 | } | |
333 | ||
334 | impl Encodable for FileMap { | |
335 | fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> { | |
336 | s.emit_struct("FileMap", 5, |s| { | |
337 | try! { s.emit_struct_field("name", 0, |s| self.name.encode(s)) }; | |
338 | try! { s.emit_struct_field("start_pos", 1, |s| self.start_pos.encode(s)) }; | |
339 | try! { s.emit_struct_field("end_pos", 2, |s| self.end_pos.encode(s)) }; | |
340 | try! { s.emit_struct_field("lines", 3, |s| { | |
341 | let lines = self.lines.borrow(); | |
342 | // store the length | |
343 | try! { s.emit_u32(lines.len() as u32) }; | |
344 | ||
345 | if lines.len() > 0 { | |
346 | // In order to preserve some space, we exploit the fact that | |
347 | // the lines list is sorted and individual lines are | |
348 | // probably not that long. Because of that we can store lines | |
349 | // as a difference list, using as little space as possible | |
350 | // for the differences. | |
351 | let max_line_length = if lines.len() == 1 { | |
352 | 0 | |
353 | } else { | |
354 | lines.windows(2) | |
355 | .map(|w| w[1] - w[0]) | |
356 | .map(|bp| bp.to_usize()) | |
357 | .max() | |
358 | .unwrap() | |
359 | }; | |
360 | ||
361 | let bytes_per_diff: u8 = match max_line_length { | |
362 | 0 ... 0xFF => 1, | |
363 | 0x100 ... 0xFFFF => 2, | |
364 | _ => 4 | |
365 | }; | |
366 | ||
367 | // Encode the number of bytes used per diff. | |
368 | try! { bytes_per_diff.encode(s) }; | |
369 | ||
370 | // Encode the first element. | |
371 | try! { lines[0].encode(s) }; | |
372 | ||
373 | let diff_iter = (&lines[..]).windows(2) | |
374 | .map(|w| (w[1] - w[0])); | |
375 | ||
376 | match bytes_per_diff { | |
377 | 1 => for diff in diff_iter { try! { (diff.0 as u8).encode(s) } }, | |
378 | 2 => for diff in diff_iter { try! { (diff.0 as u16).encode(s) } }, | |
379 | 4 => for diff in diff_iter { try! { diff.0.encode(s) } }, | |
380 | _ => unreachable!() | |
381 | } | |
382 | } | |
383 | ||
384 | Ok(()) | |
385 | }) | |
386 | }; | |
387 | s.emit_struct_field("multibyte_chars", 4, |s| { | |
388 | (*self.multibyte_chars.borrow()).encode(s) | |
389 | }) | |
390 | }) | |
391 | } | |
392 | } | |
393 | ||
394 | impl Decodable for FileMap { | |
395 | fn decode<D: Decoder>(d: &mut D) -> Result<FileMap, D::Error> { | |
396 | ||
397 | d.read_struct("FileMap", 5, |d| { | |
398 | let name: String = try! { | |
399 | d.read_struct_field("name", 0, |d| Decodable::decode(d)) | |
400 | }; | |
401 | let start_pos: BytePos = try! { | |
402 | d.read_struct_field("start_pos", 1, |d| Decodable::decode(d)) | |
403 | }; | |
404 | let end_pos: BytePos = try! { | |
405 | d.read_struct_field("end_pos", 2, |d| Decodable::decode(d)) | |
406 | }; | |
407 | let lines: Vec<BytePos> = try! { | |
408 | d.read_struct_field("lines", 3, |d| { | |
409 | let num_lines: u32 = try! { Decodable::decode(d) }; | |
410 | let mut lines = Vec::with_capacity(num_lines as usize); | |
411 | ||
412 | if num_lines > 0 { | |
413 | // Read the number of bytes used per diff. | |
414 | let bytes_per_diff: u8 = try! { Decodable::decode(d) }; | |
415 | ||
416 | // Read the first element. | |
417 | let mut line_start: BytePos = try! { Decodable::decode(d) }; | |
418 | lines.push(line_start); | |
419 | ||
420 | for _ in 1..num_lines { | |
421 | let diff = match bytes_per_diff { | |
422 | 1 => try! { d.read_u8() } as u32, | |
423 | 2 => try! { d.read_u16() } as u32, | |
424 | 4 => try! { d.read_u32() }, | |
425 | _ => unreachable!() | |
426 | }; | |
427 | ||
428 | line_start = line_start + BytePos(diff); | |
429 | ||
430 | lines.push(line_start); | |
431 | } | |
432 | } | |
433 | ||
434 | Ok(lines) | |
435 | }) | |
436 | }; | |
437 | let multibyte_chars: Vec<MultiByteChar> = try! { | |
438 | d.read_struct_field("multibyte_chars", 4, |d| Decodable::decode(d)) | |
439 | }; | |
440 | Ok(FileMap { | |
441 | name: name, | |
442 | start_pos: start_pos, | |
443 | end_pos: end_pos, | |
444 | src: None, | |
445 | lines: RefCell::new(lines), | |
446 | multibyte_chars: RefCell::new(multibyte_chars) | |
447 | }) | |
448 | }) | |
449 | } | |
223e47cc LB |
450 | } |
451 | ||
970d7e83 | 452 | impl FileMap { |
1a4d82fc JJ |
453 | /// EFFECT: register a start-of-line offset in the |
454 | /// table of line-beginnings. | |
455 | /// UNCHECKED INVARIANT: these offsets must be added in the right | |
456 | /// order and must be in the right places; there is shared knowledge | |
457 | /// about what ends a line between this file and parse.rs | |
458 | /// WARNING: pos param here is the offset relative to start of CodeMap, | |
459 | /// and CodeMap will append a newline when adding a filemap without a newline at the end, | |
460 | /// so the safe way to call this is with value calculated as | |
461 | /// filemap.start_pos + newline_offset_relative_to_the_start_of_filemap. | |
970d7e83 | 462 | pub fn next_line(&self, pos: BytePos) { |
223e47cc | 463 | // the new charpos must be > the last one (or it's the first one). |
1a4d82fc JJ |
464 | let mut lines = self.lines.borrow_mut(); |
465 | let line_len = lines.len(); | |
466 | assert!(line_len == 0 || ((*lines)[line_len - 1] < pos)); | |
970d7e83 | 467 | lines.push(pos); |
223e47cc LB |
468 | } |
469 | ||
1a4d82fc JJ |
470 | /// get a line from the list of pre-computed line-beginnings |
471 | /// | |
85aaf69f | 472 | pub fn get_line(&self, line_number: usize) -> Option<String> { |
c34b1796 AL |
473 | match self.src { |
474 | Some(ref src) => { | |
475 | let lines = self.lines.borrow(); | |
476 | lines.get(line_number).map(|&line| { | |
477 | let begin: BytePos = line - self.start_pos; | |
478 | let begin = begin.to_usize(); | |
479 | let slice = &src[begin..]; | |
480 | match slice.find('\n') { | |
481 | Some(e) => &slice[..e], | |
482 | None => slice | |
483 | }.to_string() | |
484 | }) | |
485 | } | |
486 | None => None | |
487 | } | |
223e47cc LB |
488 | } |
489 | ||
85aaf69f | 490 | pub fn record_multibyte_char(&self, pos: BytePos, bytes: usize) { |
223e47cc LB |
491 | assert!(bytes >=2 && bytes <= 4); |
492 | let mbc = MultiByteChar { | |
493 | pos: pos, | |
494 | bytes: bytes, | |
495 | }; | |
1a4d82fc JJ |
496 | self.multibyte_chars.borrow_mut().push(mbc); |
497 | } | |
498 | ||
499 | pub fn is_real_file(&self) -> bool { | |
500 | !(self.name.starts_with("<") && | |
501 | self.name.ends_with(">")) | |
223e47cc | 502 | } |
c34b1796 AL |
503 | |
504 | pub fn is_imported(&self) -> bool { | |
505 | self.src.is_none() | |
506 | } | |
223e47cc LB |
507 | } |
508 | ||
c34b1796 AL |
509 | |
510 | // _____________________________________________________________________________ | |
511 | // CodeMap | |
512 | // | |
513 | ||
223e47cc | 514 | pub struct CodeMap { |
1a4d82fc JJ |
515 | pub files: RefCell<Vec<Rc<FileMap>>>, |
516 | expansions: RefCell<Vec<ExpnInfo>> | |
223e47cc LB |
517 | } |
518 | ||
970d7e83 | 519 | impl CodeMap { |
223e47cc LB |
520 | pub fn new() -> CodeMap { |
521 | CodeMap { | |
1a4d82fc JJ |
522 | files: RefCell::new(Vec::new()), |
523 | expansions: RefCell::new(Vec::new()), | |
223e47cc LB |
524 | } |
525 | } | |
526 | ||
1a4d82fc JJ |
527 | pub fn new_filemap(&self, filename: FileName, src: String) -> Rc<FileMap> { |
528 | let mut files = self.files.borrow_mut(); | |
529 | let start_pos = match files.last() { | |
530 | None => 0, | |
c34b1796 | 531 | Some(last) => last.end_pos.to_usize(), |
1a4d82fc | 532 | }; |
223e47cc | 533 | |
1a4d82fc JJ |
534 | // Remove utf-8 BOM if any. |
535 | // FIXME #12884: no efficient/safe way to remove from the start of a string | |
536 | // and reuse the allocation. | |
537 | let mut src = if src.starts_with("\u{feff}") { | |
538 | String::from_str(&src[3..]) | |
223e47cc | 539 | } else { |
85aaf69f | 540 | String::from_str(&src[..]) |
223e47cc LB |
541 | }; |
542 | ||
1a4d82fc | 543 | // Append '\n' in case it's not already there. |
85aaf69f SL |
544 | // This is a workaround to prevent CodeMap.lookup_filemap_idx from |
545 | // accidentally overflowing into the next filemap in case the last byte | |
546 | // of span is also the last byte of filemap, which leads to incorrect | |
547 | // results from CodeMap.span_to_*. | |
1a4d82fc JJ |
548 | if src.len() > 0 && !src.ends_with("\n") { |
549 | src.push('\n'); | |
550 | } | |
223e47cc | 551 | |
c34b1796 AL |
552 | let end_pos = start_pos + src.len(); |
553 | ||
1a4d82fc JJ |
554 | let filemap = Rc::new(FileMap { |
555 | name: filename, | |
c34b1796 | 556 | src: Some(Rc::new(src)), |
85aaf69f | 557 | start_pos: Pos::from_usize(start_pos), |
c34b1796 | 558 | end_pos: Pos::from_usize(end_pos), |
1a4d82fc JJ |
559 | lines: RefCell::new(Vec::new()), |
560 | multibyte_chars: RefCell::new(Vec::new()), | |
561 | }); | |
223e47cc | 562 | |
1a4d82fc JJ |
563 | files.push(filemap.clone()); |
564 | ||
565 | filemap | |
223e47cc LB |
566 | } |
567 | ||
c34b1796 AL |
568 | /// Allocates a new FileMap representing a source file from an external |
569 | /// crate. The source code of such an "imported filemap" is not available, | |
570 | /// but we still know enough to generate accurate debuginfo location | |
571 | /// information for things inlined from other crates. | |
572 | pub fn new_imported_filemap(&self, | |
573 | filename: FileName, | |
574 | source_len: usize, | |
575 | file_local_lines: Vec<BytePos>, | |
576 | file_local_multibyte_chars: Vec<MultiByteChar>) | |
577 | -> Rc<FileMap> { | |
578 | let mut files = self.files.borrow_mut(); | |
579 | let start_pos = match files.last() { | |
580 | None => 0, | |
581 | Some(last) => last.end_pos.to_usize(), | |
582 | }; | |
583 | ||
584 | let end_pos = Pos::from_usize(start_pos + source_len); | |
585 | let start_pos = Pos::from_usize(start_pos); | |
586 | ||
587 | let lines = file_local_lines.map_in_place(|pos| pos + start_pos); | |
588 | let multibyte_chars = file_local_multibyte_chars.map_in_place(|mbc| MultiByteChar { | |
589 | pos: mbc.pos + start_pos, | |
590 | bytes: mbc.bytes | |
591 | }); | |
592 | ||
593 | let filemap = Rc::new(FileMap { | |
594 | name: filename, | |
595 | src: None, | |
596 | start_pos: start_pos, | |
597 | end_pos: end_pos, | |
598 | lines: RefCell::new(lines), | |
599 | multibyte_chars: RefCell::new(multibyte_chars), | |
600 | }); | |
601 | ||
602 | files.push(filemap.clone()); | |
603 | ||
604 | filemap | |
605 | } | |
606 | ||
1a4d82fc | 607 | pub fn mk_substr_filename(&self, sp: Span) -> String { |
223e47cc | 608 | let pos = self.lookup_char_pos(sp.lo); |
1a4d82fc JJ |
609 | (format!("<{}:{}:{}>", |
610 | pos.file.name, | |
611 | pos.line, | |
85aaf69f | 612 | pos.col.to_usize() + 1)).to_string() |
223e47cc LB |
613 | } |
614 | ||
615 | /// Lookup source information about a BytePos | |
970d7e83 | 616 | pub fn lookup_char_pos(&self, pos: BytePos) -> Loc { |
1a4d82fc | 617 | self.lookup_pos(pos) |
223e47cc LB |
618 | } |
619 | ||
970d7e83 | 620 | pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt { |
223e47cc | 621 | let loc = self.lookup_char_pos(pos); |
1a4d82fc JJ |
622 | LocWithOpt { |
623 | filename: loc.file.name.to_string(), | |
624 | line: loc.line, | |
625 | col: loc.col, | |
626 | file: Some(loc.file) | |
223e47cc LB |
627 | } |
628 | } | |
629 | ||
1a4d82fc JJ |
630 | pub fn span_to_string(&self, sp: Span) -> String { |
631 | if self.files.borrow().len() == 0 && sp == DUMMY_SP { | |
632 | return "no-location".to_string(); | |
223e47cc LB |
633 | } |
634 | ||
635 | let lo = self.lookup_char_pos_adj(sp.lo); | |
636 | let hi = self.lookup_char_pos_adj(sp.hi); | |
1a4d82fc JJ |
637 | return (format!("{}:{}:{}: {}:{}", |
638 | lo.filename, | |
639 | lo.line, | |
85aaf69f | 640 | lo.col.to_usize() + 1, |
1a4d82fc | 641 | hi.line, |
85aaf69f | 642 | hi.col.to_usize() + 1)).to_string() |
223e47cc LB |
643 | } |
644 | ||
1a4d82fc JJ |
645 | pub fn span_to_filename(&self, sp: Span) -> FileName { |
646 | self.lookup_char_pos(sp.lo).file.name.to_string() | |
223e47cc LB |
647 | } |
648 | ||
1a4d82fc | 649 | pub fn span_to_lines(&self, sp: Span) -> FileLines { |
223e47cc LB |
650 | let lo = self.lookup_char_pos(sp.lo); |
651 | let hi = self.lookup_char_pos(sp.hi); | |
1a4d82fc | 652 | let mut lines = Vec::new(); |
c34b1796 | 653 | for i in lo.line - 1..hi.line { |
223e47cc LB |
654 | lines.push(i); |
655 | }; | |
1a4d82fc | 656 | FileLines {file: lo.file, lines: lines} |
223e47cc LB |
657 | } |
658 | ||
85aaf69f SL |
659 | pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> { |
660 | if sp.lo > sp.hi { | |
661 | return Err(SpanSnippetError::IllFormedSpan(sp)); | |
662 | } | |
663 | ||
c34b1796 AL |
664 | let local_begin = self.lookup_byte_offset(sp.lo); |
665 | let local_end = self.lookup_byte_offset(sp.hi); | |
1a4d82fc | 666 | |
c34b1796 | 667 | if local_begin.fm.start_pos != local_end.fm.start_pos { |
85aaf69f | 668 | return Err(SpanSnippetError::DistinctSources(DistinctSources { |
c34b1796 AL |
669 | begin: (local_begin.fm.name.clone(), |
670 | local_begin.fm.start_pos), | |
671 | end: (local_end.fm.name.clone(), | |
672 | local_end.fm.start_pos) | |
85aaf69f | 673 | })); |
1a4d82fc | 674 | } else { |
c34b1796 AL |
675 | match local_begin.fm.src { |
676 | Some(ref src) => { | |
677 | let start_index = local_begin.pos.to_usize(); | |
678 | let end_index = local_end.pos.to_usize(); | |
679 | let source_len = (local_begin.fm.end_pos - | |
680 | local_begin.fm.start_pos).to_usize(); | |
681 | ||
682 | if start_index > end_index || end_index > source_len { | |
683 | return Err(SpanSnippetError::MalformedForCodemap( | |
684 | MalformedCodemapPositions { | |
685 | name: local_begin.fm.name.clone(), | |
686 | source_len: source_len, | |
687 | begin_pos: local_begin.pos, | |
688 | end_pos: local_end.pos, | |
689 | })); | |
690 | } | |
85aaf69f | 691 | |
c34b1796 AL |
692 | return Ok((&src[start_index..end_index]).to_string()) |
693 | } | |
694 | None => { | |
695 | return Err(SpanSnippetError::SourceNotAvailable { | |
696 | filename: local_begin.fm.name.clone() | |
697 | }); | |
698 | } | |
699 | } | |
1a4d82fc | 700 | } |
223e47cc LB |
701 | } |
702 | ||
1a4d82fc | 703 | pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> { |
85aaf69f | 704 | for fm in &*self.files.borrow() { |
1a4d82fc JJ |
705 | if filename == fm.name { |
706 | return fm.clone(); | |
707 | } | |
708 | } | |
709 | panic!("asking for {} which we don't know about", filename); | |
710 | } | |
711 | ||
c34b1796 | 712 | /// For a global BytePos compute the local offset within the containing FileMap |
1a4d82fc JJ |
713 | pub fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos { |
714 | let idx = self.lookup_filemap_idx(bpos); | |
715 | let fm = (*self.files.borrow())[idx].clone(); | |
716 | let offset = bpos - fm.start_pos; | |
717 | FileMapAndBytePos {fm: fm, pos: offset} | |
718 | } | |
719 | ||
720 | /// Converts an absolute BytePos to a CharPos relative to the filemap and above. | |
721 | pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos { | |
722 | let idx = self.lookup_filemap_idx(bpos); | |
723 | let files = self.files.borrow(); | |
724 | let map = &(*files)[idx]; | |
725 | ||
726 | // The number of extra bytes due to multibyte chars in the FileMap | |
727 | let mut total_extra_bytes = 0; | |
728 | ||
85aaf69f | 729 | for mbc in &*map.multibyte_chars.borrow() { |
1a4d82fc JJ |
730 | debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos); |
731 | if mbc.pos < bpos { | |
732 | // every character is at least one byte, so we only | |
733 | // count the actual extra bytes. | |
734 | total_extra_bytes += mbc.bytes - 1; | |
735 | // We should never see a byte position in the middle of a | |
736 | // character | |
85aaf69f | 737 | assert!(bpos.to_usize() >= mbc.pos.to_usize() + mbc.bytes); |
1a4d82fc JJ |
738 | } else { |
739 | break; | |
740 | } | |
741 | } | |
742 | ||
85aaf69f SL |
743 | assert!(map.start_pos.to_usize() + total_extra_bytes <= bpos.to_usize()); |
744 | CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes) | |
223e47cc | 745 | } |
223e47cc | 746 | |
85aaf69f | 747 | fn lookup_filemap_idx(&self, pos: BytePos) -> usize { |
1a4d82fc JJ |
748 | let files = self.files.borrow(); |
749 | let files = &*files; | |
223e47cc | 750 | let len = files.len(); |
85aaf69f | 751 | let mut a = 0; |
223e47cc | 752 | let mut b = len; |
85aaf69f SL |
753 | while b - a > 1 { |
754 | let m = (a + b) / 2; | |
1a4d82fc | 755 | if files[m].start_pos > pos { |
223e47cc LB |
756 | b = m; |
757 | } else { | |
758 | a = m; | |
759 | } | |
760 | } | |
1a4d82fc JJ |
761 | // There can be filemaps with length 0. These have the same start_pos as |
762 | // the previous filemap, but are not the filemaps we want (because they | |
763 | // are length 0, they cannot contain what we are looking for). So, | |
764 | // rewind until we find a useful filemap. | |
765 | loop { | |
766 | let lines = files[a].lines.borrow(); | |
767 | let lines = lines; | |
768 | if lines.len() > 0 { | |
769 | break; | |
770 | } | |
771 | if a == 0 { | |
772 | panic!("position {} does not resolve to a source location", | |
85aaf69f | 773 | pos.to_usize()); |
1a4d82fc JJ |
774 | } |
775 | a -= 1; | |
776 | } | |
777 | if a >= len { | |
778 | panic!("position {} does not resolve to a source location", | |
85aaf69f | 779 | pos.to_usize()) |
223e47cc LB |
780 | } |
781 | ||
782 | return a; | |
783 | } | |
784 | ||
1a4d82fc | 785 | fn lookup_line(&self, pos: BytePos) -> FileMapAndLine { |
223e47cc | 786 | let idx = self.lookup_filemap_idx(pos); |
1a4d82fc JJ |
787 | |
788 | let files = self.files.borrow(); | |
789 | let f = (*files)[idx].clone(); | |
85aaf69f | 790 | let mut a = 0; |
1a4d82fc JJ |
791 | { |
792 | let lines = f.lines.borrow(); | |
793 | let mut b = lines.len(); | |
85aaf69f SL |
794 | while b - a > 1 { |
795 | let m = (a + b) / 2; | |
1a4d82fc JJ |
796 | if (*lines)[m] > pos { b = m; } else { a = m; } |
797 | } | |
223e47cc | 798 | } |
1a4d82fc | 799 | FileMapAndLine {fm: f, line: a} |
223e47cc LB |
800 | } |
801 | ||
970d7e83 | 802 | fn lookup_pos(&self, pos: BytePos) -> Loc { |
223e47cc | 803 | let FileMapAndLine {fm: f, line: a} = self.lookup_line(pos); |
85aaf69f | 804 | let line = a + 1; // Line numbers start at 1 |
1a4d82fc JJ |
805 | let chpos = self.bytepos_to_file_charpos(pos); |
806 | let linebpos = (*f.lines.borrow())[a]; | |
807 | let linechpos = self.bytepos_to_file_charpos(linebpos); | |
808 | debug!("byte pos {:?} is on the line at byte pos {:?}", | |
223e47cc | 809 | pos, linebpos); |
1a4d82fc | 810 | debug!("char pos {:?} is on the line at char pos {:?}", |
223e47cc | 811 | chpos, linechpos); |
1a4d82fc | 812 | debug!("byte is on line: {}", line); |
223e47cc | 813 | assert!(chpos >= linechpos); |
1a4d82fc | 814 | Loc { |
223e47cc LB |
815 | file: f, |
816 | line: line, | |
817 | col: chpos - linechpos | |
1a4d82fc | 818 | } |
223e47cc LB |
819 | } |
820 | ||
1a4d82fc JJ |
821 | pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId { |
822 | let mut expansions = self.expansions.borrow_mut(); | |
823 | expansions.push(expn_info); | |
824 | ExpnId(expansions.len().to_u32().expect("too many ExpnInfo's!") - 1) | |
223e47cc LB |
825 | } |
826 | ||
1a4d82fc JJ |
827 | pub fn with_expn_info<T, F>(&self, id: ExpnId, f: F) -> T where |
828 | F: FnOnce(Option<&ExpnInfo>) -> T, | |
829 | { | |
830 | match id { | |
831 | NO_EXPANSION => f(None), | |
85aaf69f | 832 | ExpnId(i) => f(Some(&(*self.expansions.borrow())[i as usize])) |
1a4d82fc | 833 | } |
223e47cc LB |
834 | } |
835 | ||
c34b1796 AL |
836 | /// Check if a span is "internal" to a macro in which #[unstable] |
837 | /// items can be used (that is, a macro marked with | |
838 | /// `#[allow_internal_unstable]`). | |
839 | pub fn span_allows_unstable(&self, span: Span) -> bool { | |
840 | debug!("span_allows_unstable(span = {:?})", span); | |
841 | let mut allows_unstable = false; | |
842 | let mut expn_id = span.expn_id; | |
843 | loop { | |
844 | let quit = self.with_expn_info(expn_id, |expninfo| { | |
845 | debug!("span_allows_unstable: expninfo = {:?}", expninfo); | |
846 | expninfo.map_or(/* hit the top level */ true, |info| { | |
847 | ||
848 | let span_comes_from_this_expansion = | |
849 | info.callee.span.map_or(span == info.call_site, |mac_span| { | |
850 | mac_span.lo <= span.lo && span.hi <= mac_span.hi | |
851 | }); | |
852 | ||
853 | debug!("span_allows_unstable: from this expansion? {}, allows unstable? {}", | |
854 | span_comes_from_this_expansion, | |
855 | info.callee.allow_internal_unstable); | |
856 | if span_comes_from_this_expansion { | |
857 | allows_unstable = info.callee.allow_internal_unstable; | |
858 | // we've found the right place, stop looking | |
859 | true | |
1a4d82fc | 860 | } else { |
c34b1796 AL |
861 | // not the right place, keep looking |
862 | expn_id = info.call_site.expn_id; | |
863 | false | |
1a4d82fc | 864 | } |
c34b1796 AL |
865 | }) |
866 | }); | |
867 | if quit { | |
868 | break | |
223e47cc | 869 | } |
c34b1796 AL |
870 | } |
871 | debug!("span_allows_unstable? {}", allows_unstable); | |
872 | allows_unstable | |
223e47cc LB |
873 | } |
874 | } | |
875 | ||
c34b1796 AL |
876 | // _____________________________________________________________________________ |
877 | // SpanSnippetError, DistinctSources, MalformedCodemapPositions | |
878 | // | |
879 | ||
85aaf69f SL |
880 | #[derive(Clone, PartialEq, Eq, Debug)] |
881 | pub enum SpanSnippetError { | |
882 | IllFormedSpan(Span), | |
883 | DistinctSources(DistinctSources), | |
884 | MalformedForCodemap(MalformedCodemapPositions), | |
c34b1796 | 885 | SourceNotAvailable { filename: String } |
85aaf69f SL |
886 | } |
887 | ||
888 | #[derive(Clone, PartialEq, Eq, Debug)] | |
889 | pub struct DistinctSources { | |
890 | begin: (String, BytePos), | |
891 | end: (String, BytePos) | |
892 | } | |
893 | ||
894 | #[derive(Clone, PartialEq, Eq, Debug)] | |
895 | pub struct MalformedCodemapPositions { | |
896 | name: String, | |
897 | source_len: usize, | |
898 | begin_pos: BytePos, | |
899 | end_pos: BytePos | |
900 | } | |
901 | ||
c34b1796 AL |
902 | |
903 | // _____________________________________________________________________________ | |
904 | // Tests | |
905 | // | |
906 | ||
223e47cc LB |
907 | #[cfg(test)] |
908 | mod test { | |
909 | use super::*; | |
910 | ||
911 | #[test] | |
912 | fn t1 () { | |
913 | let cm = CodeMap::new(); | |
1a4d82fc JJ |
914 | let fm = cm.new_filemap("blork.rs".to_string(), |
915 | "first line.\nsecond line".to_string()); | |
223e47cc | 916 | fm.next_line(BytePos(0)); |
1a4d82fc | 917 | assert_eq!(fm.get_line(0), Some("first line.".to_string())); |
223e47cc LB |
918 | // TESTING BROKEN BEHAVIOR: |
919 | fm.next_line(BytePos(10)); | |
1a4d82fc | 920 | assert_eq!(fm.get_line(1), Some(".".to_string())); |
223e47cc LB |
921 | } |
922 | ||
923 | #[test] | |
c34b1796 | 924 | #[should_panic] |
223e47cc LB |
925 | fn t2 () { |
926 | let cm = CodeMap::new(); | |
1a4d82fc JJ |
927 | let fm = cm.new_filemap("blork.rs".to_string(), |
928 | "first line.\nsecond line".to_string()); | |
223e47cc LB |
929 | // TESTING *REALLY* BROKEN BEHAVIOR: |
930 | fm.next_line(BytePos(0)); | |
931 | fm.next_line(BytePos(10)); | |
932 | fm.next_line(BytePos(2)); | |
933 | } | |
1a4d82fc JJ |
934 | |
935 | fn init_code_map() -> CodeMap { | |
936 | let cm = CodeMap::new(); | |
937 | let fm1 = cm.new_filemap("blork.rs".to_string(), | |
938 | "first line.\nsecond line".to_string()); | |
939 | let fm2 = cm.new_filemap("empty.rs".to_string(), | |
940 | "".to_string()); | |
941 | let fm3 = cm.new_filemap("blork2.rs".to_string(), | |
942 | "first line.\nsecond line".to_string()); | |
943 | ||
944 | fm1.next_line(BytePos(0)); | |
945 | fm1.next_line(BytePos(12)); | |
946 | fm2.next_line(BytePos(24)); | |
947 | fm3.next_line(BytePos(24)); | |
948 | fm3.next_line(BytePos(34)); | |
949 | ||
950 | cm | |
951 | } | |
952 | ||
953 | #[test] | |
954 | fn t3() { | |
955 | // Test lookup_byte_offset | |
956 | let cm = init_code_map(); | |
957 | ||
958 | let fmabp1 = cm.lookup_byte_offset(BytePos(22)); | |
959 | assert_eq!(fmabp1.fm.name, "blork.rs"); | |
960 | assert_eq!(fmabp1.pos, BytePos(22)); | |
961 | ||
962 | let fmabp2 = cm.lookup_byte_offset(BytePos(24)); | |
963 | assert_eq!(fmabp2.fm.name, "blork2.rs"); | |
964 | assert_eq!(fmabp2.pos, BytePos(0)); | |
965 | } | |
966 | ||
967 | #[test] | |
968 | fn t4() { | |
969 | // Test bytepos_to_file_charpos | |
970 | let cm = init_code_map(); | |
971 | ||
972 | let cp1 = cm.bytepos_to_file_charpos(BytePos(22)); | |
973 | assert_eq!(cp1, CharPos(22)); | |
974 | ||
975 | let cp2 = cm.bytepos_to_file_charpos(BytePos(24)); | |
976 | assert_eq!(cp2, CharPos(0)); | |
977 | } | |
978 | ||
979 | #[test] | |
980 | fn t5() { | |
981 | // Test zero-length filemaps. | |
982 | let cm = init_code_map(); | |
983 | ||
984 | let loc1 = cm.lookup_char_pos(BytePos(22)); | |
985 | assert_eq!(loc1.file.name, "blork.rs"); | |
986 | assert_eq!(loc1.line, 2); | |
987 | assert_eq!(loc1.col, CharPos(10)); | |
988 | ||
989 | let loc2 = cm.lookup_char_pos(BytePos(24)); | |
990 | assert_eq!(loc2.file.name, "blork2.rs"); | |
991 | assert_eq!(loc2.line, 1); | |
992 | assert_eq!(loc2.col, CharPos(0)); | |
993 | } | |
994 | ||
995 | fn init_code_map_mbc() -> CodeMap { | |
996 | let cm = CodeMap::new(); | |
997 | // € is a three byte utf8 char. | |
998 | let fm1 = | |
999 | cm.new_filemap("blork.rs".to_string(), | |
1000 | "fir€st €€€€ line.\nsecond line".to_string()); | |
1001 | let fm2 = cm.new_filemap("blork2.rs".to_string(), | |
1002 | "first line€€.\n€ second line".to_string()); | |
1003 | ||
1004 | fm1.next_line(BytePos(0)); | |
1005 | fm1.next_line(BytePos(22)); | |
1006 | fm2.next_line(BytePos(40)); | |
1007 | fm2.next_line(BytePos(58)); | |
1008 | ||
1009 | fm1.record_multibyte_char(BytePos(3), 3); | |
1010 | fm1.record_multibyte_char(BytePos(9), 3); | |
1011 | fm1.record_multibyte_char(BytePos(12), 3); | |
1012 | fm1.record_multibyte_char(BytePos(15), 3); | |
1013 | fm1.record_multibyte_char(BytePos(18), 3); | |
1014 | fm2.record_multibyte_char(BytePos(50), 3); | |
1015 | fm2.record_multibyte_char(BytePos(53), 3); | |
1016 | fm2.record_multibyte_char(BytePos(58), 3); | |
1017 | ||
1018 | cm | |
1019 | } | |
1020 | ||
1021 | #[test] | |
1022 | fn t6() { | |
1023 | // Test bytepos_to_file_charpos in the presence of multi-byte chars | |
1024 | let cm = init_code_map_mbc(); | |
1025 | ||
1026 | let cp1 = cm.bytepos_to_file_charpos(BytePos(3)); | |
1027 | assert_eq!(cp1, CharPos(3)); | |
1028 | ||
1029 | let cp2 = cm.bytepos_to_file_charpos(BytePos(6)); | |
1030 | assert_eq!(cp2, CharPos(4)); | |
1031 | ||
1032 | let cp3 = cm.bytepos_to_file_charpos(BytePos(56)); | |
1033 | assert_eq!(cp3, CharPos(12)); | |
1034 | ||
1035 | let cp4 = cm.bytepos_to_file_charpos(BytePos(61)); | |
1036 | assert_eq!(cp4, CharPos(15)); | |
1037 | } | |
1038 | ||
1039 | #[test] | |
1040 | fn t7() { | |
1041 | // Test span_to_lines for a span ending at the end of filemap | |
1042 | let cm = init_code_map(); | |
1043 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
1044 | let file_lines = cm.span_to_lines(span); | |
1045 | ||
1046 | assert_eq!(file_lines.file.name, "blork.rs"); | |
1047 | assert_eq!(file_lines.lines.len(), 1); | |
85aaf69f | 1048 | assert_eq!(file_lines.lines[0], 1); |
1a4d82fc JJ |
1049 | } |
1050 | ||
1051 | #[test] | |
1052 | fn t8() { | |
1053 | // Test span_to_snippet for a span ending at the end of filemap | |
1054 | let cm = init_code_map(); | |
1055 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
1056 | let snippet = cm.span_to_snippet(span); | |
1057 | ||
85aaf69f | 1058 | assert_eq!(snippet, Ok("second line".to_string())); |
1a4d82fc JJ |
1059 | } |
1060 | ||
1061 | #[test] | |
1062 | fn t9() { | |
1063 | // Test span_to_str for a span ending at the end of filemap | |
1064 | let cm = init_code_map(); | |
1065 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
1066 | let sstr = cm.span_to_string(span); | |
1067 | ||
1068 | assert_eq!(sstr, "blork.rs:2:1: 2:12"); | |
1069 | } | |
223e47cc | 1070 | } |