]> git.lizzy.rs Git - rust.git/blobdiff - src/libsyntax/codemap.rs
Only retain external static symbols across LTO
[rust.git] / src / libsyntax / codemap.rs
index 432c1688536bb1d28f998fa48d280b31bb7d07cc..74302e2f6a0fdb7b0f32cfaa9fe5bd0ebadfccdb 100644 (file)
@@ -23,6 +23,7 @@
 use std::ops::{Add, Sub};
 use std::path::Path;
 use std::rc::Rc;
+use std::cmp;
 
 use std::{fmt, fs};
 use std::io::{self, Read};
@@ -31,6 +32,8 @@
 
 use ast::Name;
 
+use errors::emitter::MAX_HIGHLIGHT_LINES;
+
 // _____________________________________________________________________________
 // Pos, BytePos, CharPos
 //
@@ -42,7 +45,7 @@ pub trait Pos {
 
 /// A byte offset. Keep this small (currently 32-bits), as AST contains
 /// a lot of them.
-#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
 pub struct BytePos(pub u32);
 
 /// A character offset. Because of multibyte utf8 characters, a byte offset
@@ -109,7 +112,7 @@ fn sub(self, rhs: CharPos) -> CharPos {
 }
 
 // _____________________________________________________________________________
-// Span, Spanned
+// Span, MultiSpan, Spanned
 //
 
 /// Spans represent a region of code, used for error reporting. Positions in spans
@@ -120,7 +123,7 @@ fn sub(self, rhs: CharPos) -> CharPos {
 /// able to use many of the functions on spans in codemap and you cannot assume
 /// that the length of the span = hi - lo; there may be space in the BytePos
 /// range between files.
-#[derive(Clone, Copy, Hash)]
+#[derive(Clone, Copy, Hash, PartialEq, Eq)]
 pub struct Span {
     pub lo: BytePos,
     pub hi: BytePos,
@@ -129,6 +132,15 @@ pub struct Span {
     pub expn_id: ExpnId
 }
 
+/// Spans are converted to MultiSpans just before error reporting, either automatically,
+/// generated by line grouping, or manually constructed.
+/// In the latter case care should be taken to ensure that spans are ordered, disjoint,
+/// and point into the same FileMap.
+#[derive(Clone)]
+pub struct MultiSpan {
+    pub spans: Vec<Span>
+}
+
 pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION };
 
 // Generic span to be used for code originating from the command line
@@ -139,12 +151,47 @@ pub struct Span {
 impl Span {
     /// Returns `self` if `self` is not the dummy span, and `other` otherwise.
     pub fn substitute_dummy(self, other: Span) -> Span {
-        if self == DUMMY_SP { other } else { self }
+        if self.source_equal(&DUMMY_SP) { other } else { self }
     }
 
     pub fn contains(self, other: Span) -> bool {
         self.lo <= other.lo && other.hi <= self.hi
     }
+
+    /// Return true if the spans are equal with regards to the source text.
+    ///
+    /// Use this instead of `==` when either span could be generated code,
+    /// and you only care that they point to the same bytes of source text.
+    pub fn source_equal(&self, other: &Span) -> bool {
+        self.lo == other.lo && self.hi == other.hi
+    }
+
+    /// Returns `Some(span)`, a union of `self` and `other`, on overlap.
+    pub fn merge(self, other: Span) -> Option<Span> {
+        if self.expn_id != other.expn_id {
+            return None;
+        }
+
+        if (self.lo <= other.lo && self.hi > other.lo) ||
+           (self.lo >= other.lo && self.lo < other.hi) {
+            Some(Span {
+                lo: cmp::min(self.lo, other.lo),
+                hi: cmp::max(self.hi, other.hi),
+                expn_id: self.expn_id,
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Returns `Some(span)`, where the start is trimmed by the end of `other`
+    pub fn trim_start(self, other: Span) -> Option<Span> {
+        if self.hi > other.hi {
+            Some(Span { lo: cmp::max(self.lo, other.hi), .. self })
+        } else {
+            None
+        }
+    }
 }
 
 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
@@ -153,15 +200,6 @@ pub struct Spanned<T> {
     pub span: Span,
 }
 
-impl PartialEq for Span {
-    fn eq(&self, other: &Span) -> bool {
-        return (*self).lo == (*other).lo && (*self).hi == (*other).hi;
-    }
-    fn ne(&self, other: &Span) -> bool { !(*self).eq(other) }
-}
-
-impl Eq for Span {}
-
 impl Encodable for Span {
     fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
         s.emit_struct("Span", 2, |s| {
@@ -236,6 +274,102 @@ pub fn original_sp(cm: &CodeMap, sp: Span, enclosing_sp: Span) -> Span {
     }
 }
 
+impl MultiSpan {
+    pub fn new() -> MultiSpan {
+        MultiSpan { spans: Vec::new() }
+    }
+
+    pub fn to_span_bounds(&self) -> Span {
+        assert!(!self.spans.is_empty());
+        let Span { lo, expn_id, .. } = *self.spans.first().unwrap();
+        let Span { hi, .. } = *self.spans.last().unwrap();
+        Span { lo: lo, hi: hi, expn_id: expn_id }
+    }
+
+    /// Merges or inserts the given span into itself.
+    pub fn push_merge(&mut self, mut sp: Span) {
+        let mut idx_merged = None;
+
+        for idx in 0.. {
+            let cur = match self.spans.get(idx) {
+                Some(s) => *s,
+                None => break,
+            };
+            // Try to merge with a contained Span
+            if let Some(union) = cur.merge(sp) {
+                self.spans[idx] = union;
+                sp = union;
+                idx_merged = Some(idx);
+                break;
+            }
+            // Or insert into the first sorted position
+            if sp.hi <= cur.lo {
+                self.spans.insert(idx, sp);
+                idx_merged = Some(idx);
+                break;
+            }
+        }
+        if let Some(idx) = idx_merged {
+            // Merge with spans trailing the insertion/merging position
+            while (idx + 1) < self.spans.len() {
+                if let Some(union) = self.spans[idx + 1].merge(sp) {
+                    self.spans[idx] = union;
+                    self.spans.remove(idx + 1);
+                } else {
+                    break;
+                }
+            }
+        } else {
+            self.spans.push(sp);
+        }
+    }
+
+    /// Inserts the given span into itself, for use with `end_highlight_lines`.
+    pub fn push_trim(&mut self, mut sp: Span) {
+        let mut prev = mk_sp(BytePos(0), BytePos(0));
+
+        if let Some(first) = self.spans.get_mut(0) {
+            if first.lo > sp.lo {
+                // Prevent us here from spanning fewer lines
+                // because of trimming the start of the span
+                // (this should not be visible, because this method ought
+                // to not be used in conjunction with `highlight_lines`)
+                first.lo = sp.lo;
+            }
+        }
+
+        for idx in 0.. {
+            if let Some(sp_trim) = sp.trim_start(prev) {
+                // Implies `sp.hi > prev.hi`
+                let cur = match self.spans.as_slice().get(idx) {
+                    Some(s) => *s,
+                    None => {
+                        sp = sp_trim;
+                        break;
+                    }
+                };
+                // `cur` may overlap with `sp_trim`
+                if let Some(cur_trim) = cur.trim_start(sp_trim) {
+                    // Implies `sp.hi < cur.hi`
+                    self.spans.insert(idx, sp_trim);
+                    self.spans[idx + 1] = cur_trim;
+                    return;
+                } else if sp.hi == cur.hi {
+                    return;
+                }
+                prev = cur;
+            }
+        }
+        self.spans.push(sp);
+    }
+}
+
+impl From<Span> for MultiSpan {
+    fn from(span: Span) -> MultiSpan {
+        MultiSpan { spans: vec![span] }
+    }
+}
+
 // _____________________________________________________________________________
 // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos
 //
@@ -805,7 +939,7 @@ pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
     }
 
     pub fn span_to_string(&self, sp: Span) -> String {
-        if self.files.borrow().is_empty() && sp == DUMMY_SP {
+        if self.files.borrow().is_empty() && sp.source_equal(&DUMMY_SP) {
             return "no-location".to_string();
         }
 
@@ -918,9 +1052,18 @@ fn span_to_expanded_string_internal(&self, sp:Span, indent: &str) -> String {
     /// the macro callsite that expanded to it.
     pub fn source_callsite(&self, sp: Span) -> Span {
         let mut span = sp;
+        // Special case - if a macro is parsed as an argument to another macro, the source
+        // callsite is the first callsite, which is also source-equivalent to the span.
+        let mut first = true;
         while span.expn_id != NO_EXPANSION && span.expn_id != COMMAND_LINE_EXPN {
             if let Some(callsite) = self.with_expn_info(span.expn_id,
                                                |ei| ei.map(|ei| ei.call_site.clone())) {
+                if first && span.source_equal(&callsite) {
+                    if self.lookup_char_pos(span.lo).file.is_real_file() {
+                        return Span { expn_id: NO_EXPANSION, .. span };
+                    }
+                }
+                first = false;
                 span = callsite;
             }
             else {
@@ -930,6 +1073,37 @@ pub fn source_callsite(&self, sp: Span) -> Span {
         span
     }
 
+    /// Return the source callee.
+    ///
+    /// Returns None if the supplied span has no expansion trace,
+    /// else returns the NameAndSpan for the macro definition
+    /// corresponding to the source callsite.
+    pub fn source_callee(&self, sp: Span) -> Option<NameAndSpan> {
+        let mut span = sp;
+        // Special case - if a macro is parsed as an argument to another macro, the source
+        // callsite is source-equivalent to the span, and the source callee is the first callee.
+        let mut first = true;
+        while let Some(callsite) = self.with_expn_info(span.expn_id,
+                                            |ei| ei.map(|ei| ei.call_site.clone())) {
+            if first && span.source_equal(&callsite) {
+                if self.lookup_char_pos(span.lo).file.is_real_file() {
+                    return self.with_expn_info(span.expn_id,
+                                               |ei| ei.map(|ei| ei.callee.clone()));
+                }
+            }
+            first = false;
+            if let Some(_) = self.with_expn_info(callsite.expn_id,
+                                                 |ei| ei.map(|ei| ei.call_site.clone())) {
+                span = callsite;
+            }
+            else {
+                return self.with_expn_info(span.expn_id,
+                                           |ei| ei.map(|ei| ei.callee.clone()));
+            }
+        }
+        None
+    }
+
     pub fn span_to_filename(&self, sp: Span) -> FileName {
         self.lookup_char_pos(sp.lo).file.name.to_string()
     }
@@ -1020,6 +1194,59 @@ pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> {
         }
     }
 
+    /// Groups and sorts spans by lines into `MultiSpan`s, where `push` adds them to their group,
+    /// specifying the unification behaviour for overlapping spans.
+    /// Spans overflowing a line are put into their own one-element-group.
+    pub fn custom_group_spans<F>(&self, mut spans: Vec<Span>, push: F) -> Vec<MultiSpan>
+        where F: Fn(&mut MultiSpan, Span)
+    {
+        spans.sort_by(|a, b| a.lo.cmp(&b.lo));
+        let mut groups = Vec::<MultiSpan>::new();
+        let mut overflowing = vec![];
+        let mut prev_expn = ExpnId(!2u32);
+        let mut prev_file = !0usize;
+        let mut prev_line = !0usize;
+        let mut err_size = 0;
+
+        for sp in spans {
+            let line = self.lookup_char_pos(sp.lo).line;
+            let line_hi = self.lookup_char_pos(sp.hi).line;
+            if line != line_hi {
+                overflowing.push(sp.into());
+                continue
+            }
+            let file = self.lookup_filemap_idx(sp.lo);
+
+            if err_size < MAX_HIGHLIGHT_LINES && sp.expn_id == prev_expn && file == prev_file {
+                // `push` takes care of sorting, trimming, and merging
+                push(&mut groups.last_mut().unwrap(), sp);
+                if line != prev_line {
+                    err_size += 1;
+                }
+            } else {
+                groups.push(sp.into());
+                err_size = 1;
+            }
+            prev_expn = sp.expn_id;
+            prev_file = file;
+            prev_line = line;
+        }
+        groups.extend(overflowing);
+        groups
+    }
+
+    /// Groups and sorts spans by lines into `MultiSpan`s, merging overlapping spans.
+    /// Spans overflowing a line are put into their own one-element-group.
+    pub fn group_spans(&self, spans: Vec<Span>) -> Vec<MultiSpan> {
+        self.custom_group_spans(spans, |msp, sp| msp.push_merge(sp))
+    }
+
+    /// Like `group_spans`, but trims overlapping spans instead of
+    /// merging them (for use with `end_highlight_lines`)
+    pub fn end_group_spans(&self, spans: Vec<Span>) -> Vec<MultiSpan> {
+        self.custom_group_spans(spans, |msp, sp| msp.push_trim(sp))
+    }
+
     pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> {
         for fm in self.files.borrow().iter() {
             if filename == fm.name {
@@ -1119,7 +1346,7 @@ pub fn span_allows_unstable(&self, span: Span) -> bool {
                 expninfo.map_or(/* hit the top level */ true, |info| {
 
                     let span_comes_from_this_expansion =
-                        info.callee.span.map_or(span == info.call_site, |mac_span| {
+                        info.callee.span.map_or(span.source_equal(&info.call_site), |mac_span| {
                             mac_span.contains(span)
                         });
 
@@ -1351,7 +1578,7 @@ fn t7() {
     fn span_from_selection(input: &str, selection: &str) -> Span {
         assert_eq!(input.len(), selection.len());
         let left_index = selection.find('^').unwrap() as u32;
-        let right_index = selection.rfind('~').unwrap() as u32;
+        let right_index = selection.rfind('~').map(|x|x as u32).unwrap_or(left_index);
         Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION }
     }
 
@@ -1511,4 +1738,73 @@ fn t12() {
 ";
         assert_eq!(sstr, res_str);
     }
+
+    #[test]
+    fn t13() {
+        // Test that collecting multiple spans into line-groups works correctly
+        let cm = CodeMap::new();
+        let inp  =      "_aaaaa__bbb\nvv\nw\nx\ny\nz\ncccccc__ddddee__";
+        let sp1  =      " ^~~~~     \n  \n \n \n \n \n                ";
+        let sp2  =      "           \n  \n \n \n \n^\n                ";
+        let sp3  =      "        ^~~\n~~\n \n \n \n \n                ";
+        let sp4  =      "           \n  \n \n \n \n \n^~~~~~          ";
+        let sp5  =      "           \n  \n \n \n \n \n        ^~~~    ";
+        let sp6  =      "           \n  \n \n \n \n \n          ^~~~  ";
+        let sp_trim =   "           \n  \n \n \n \n \n            ^~  ";
+        let sp_merge =  "           \n  \n \n \n \n \n        ^~~~~~  ";
+        let sp7  =      "           \n ^\n \n \n \n \n                ";
+        let sp8  =      "           \n  \n^\n \n \n \n                ";
+        let sp9  =      "           \n  \n \n^\n \n \n                ";
+        let sp10 =      "           \n  \n \n \n^\n \n                ";
+
+        let span = |sp, expected| {
+            let sp = span_from_selection(inp, sp);
+            assert_eq!(&cm.span_to_snippet(sp).unwrap(), expected);
+            sp
+        };
+
+        cm.new_filemap_and_lines("blork.rs", inp);
+        let sp1 = span(sp1, "aaaaa");
+        let sp2 = span(sp2, "z");
+        let sp3 = span(sp3, "bbb\nvv");
+        let sp4 = span(sp4, "cccccc");
+        let sp5 = span(sp5, "dddd");
+        let sp6 = span(sp6, "ddee");
+        let sp7 = span(sp7, "v");
+        let sp8 = span(sp8, "w");
+        let sp9 = span(sp9, "x");
+        let sp10 = span(sp10, "y");
+        let sp_trim = span(sp_trim, "ee");
+        let sp_merge = span(sp_merge, "ddddee");
+
+        let spans = vec![sp5, sp2, sp4, sp9, sp10, sp7, sp3, sp8, sp1, sp6];
+
+        macro_rules! check_next {
+            ($groups: expr, $expected: expr) => ({
+                let actual = $groups.next().map(|g|&g.spans[..]);
+                let expected = $expected;
+                println!("actual:\n{:?}\n", actual);
+                println!("expected:\n{:?}\n", expected);
+                assert_eq!(actual, expected.as_ref().map(|x|&x[..]));
+            });
+        }
+
+        let _groups = cm.group_spans(spans.clone());
+        let it = &mut _groups.iter();
+
+        check_next!(it, Some([sp1, sp7, sp8, sp9, sp10, sp2]));
+        // New group because we're exceeding MAX_HIGHLIGHT_LINES
+        check_next!(it, Some([sp4, sp_merge]));
+        check_next!(it, Some([sp3]));
+        check_next!(it, None::<[Span; 0]>);
+
+        let _groups = cm.end_group_spans(spans);
+        let it = &mut _groups.iter();
+
+        check_next!(it, Some([sp1, sp7, sp8, sp9, sp10, sp2]));
+        // New group because we're exceeding MAX_HIGHLIGHT_LINES
+        check_next!(it, Some([sp4, sp5, sp_trim]));
+        check_next!(it, Some([sp3]));
+        check_next!(it, None::<[Span; 0]>);
+    }
 }