]> git.lizzy.rs Git - rust.git/commitdiff
Rollup merge of #66427 - Mark-Simulacrum:errors-json, r=Centril
authorMazdak Farrokhzad <twingoow@gmail.com>
Fri, 15 Nov 2019 17:02:02 +0000 (18:02 +0100)
committerGitHub <noreply@github.com>
Fri, 15 Nov 2019 17:02:02 +0000 (18:02 +0100)
Move the JSON error emitter to librustc_errors

This is done both as a cleanup (it makes little sense for this emitter to be in libsyntax), but also as part of broader work to decouple Session from librustc itself.

Along the way, this also moves SourceMap to syntax_pos, which is also nice for the above reasons, as well as allowing dropping the SourceMapper trait from code. This had the unfortunate side-effect of moving `FatalError` to rustc_data_structures (it's needed in syntax_pos, due to SourceMap, but putting it there feels somehow worse).

20 files changed:
Cargo.lock
src/librustc/session/mod.rs
src/librustc_codegen_ssa/back/write.rs
src/librustc_errors/annotate_snippet_emitter_writer.rs
src/librustc_errors/emitter.rs
src/librustc_errors/json.rs [new file with mode: 0644]
src/librustc_errors/json/tests.rs [new file with mode: 0644]
src/librustc_errors/lib.rs
src/librustdoc/core.rs
src/libsyntax/json.rs [deleted file]
src/libsyntax/json/tests.rs [deleted file]
src/libsyntax/lib.rs
src/libsyntax/source_map.rs [deleted file]
src/libsyntax/source_map/tests.rs [deleted file]
src/libsyntax_pos/Cargo.toml
src/libsyntax_pos/fatal_error.rs [new file with mode: 0644]
src/libsyntax_pos/lib.rs
src/libsyntax_pos/source_map.rs [new file with mode: 0644]
src/libsyntax_pos/source_map/tests.rs [new file with mode: 0644]
src/tools/compiletest/src/json.rs

index bb2bd3c314caa03e060138c2cdfbe430def582b5..d3bc70c666f018b1eaad0863ee5b2d636c2f89c2 100644 (file)
@@ -4439,6 +4439,7 @@ version = "0.0.0"
 dependencies = [
  "arena",
  "cfg-if",
+ "log",
  "rustc_data_structures",
  "rustc_index",
  "rustc_macros",
index a69584cb90ad1c5d5c408fee4c928052867555af..4fbc8da9cbf026433f99e33b0172f8f9dab3e8bb 100644 (file)
@@ -22,7 +22,7 @@
 use errors::annotate_snippet_emitter_writer::{AnnotateSnippetEmitterWriter};
 use syntax::edition::Edition;
 use syntax::feature_gate::{self, AttributeType};
-use syntax::json::JsonEmitter;
+use errors::json::JsonEmitter;
 use syntax::source_map;
 use syntax::sess::{ParseSess, ProcessCfgMod};
 use syntax::symbol::Symbol;
index ed901fa064a4e3e7064fabb5908cff5240afeb83..f35a31d59fe62bd648b5a50e54459b0a03262022 100644 (file)
@@ -23,7 +23,8 @@
 use rustc_fs_util::link_or_copy;
 use rustc_data_structures::svh::Svh;
 use rustc_data_structures::sync::Lrc;
-use rustc_errors::{Handler, Level, FatalError, DiagnosticId, SourceMapperDyn};
+use rustc_errors::{Handler, Level, FatalError, DiagnosticId};
+use syntax_pos::source_map::SourceMap;
 use rustc_errors::emitter::{Emitter};
 use rustc_target::spec::MergeFunctions;
 use syntax::attr;
@@ -1679,7 +1680,7 @@ fn emit_diagnostic(&mut self, diag: &rustc_errors::Diagnostic) {
         }
         drop(self.sender.send(SharedEmitterMessage::AbortIfErrors));
     }
-    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
+    fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         None
     }
 }
index 491bc2aa6a2eb7034669b33c4ae7cbba214164e5..4c5d0178b2c64d049ffc17adb2b737b0b1536c93 100644 (file)
@@ -6,9 +6,10 @@
 //! [annotate_snippets]: https://docs.rs/crate/annotate-snippets/
 
 use syntax_pos::{SourceFile, MultiSpan, Loc};
+use syntax_pos::source_map::SourceMap;
 use crate::{
     Level, CodeSuggestion, Diagnostic, Emitter,
-    SourceMapperDyn, SubDiagnostic, DiagnosticId
+    SubDiagnostic, DiagnosticId
 };
 use crate::emitter::FileWithAnnotatedLines;
 use rustc_data_structures::sync::Lrc;
@@ -20,7 +21,7 @@
 
 /// Generates diagnostics using annotate-snippet
 pub struct AnnotateSnippetEmitterWriter {
-    source_map: Option<Lrc<SourceMapperDyn>>,
+    source_map: Option<Lrc<SourceMap>>,
     /// If true, hides the longer explanation text
     short_message: bool,
     /// If true, will normalize line numbers with `LL` to prevent noise in UI test diffs.
@@ -49,7 +50,7 @@ fn emit_diagnostic(&mut self, diag: &Diagnostic) {
                                    &suggestions);
     }
 
-    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
+    fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         self.source_map.as_ref()
     }
 
@@ -61,7 +62,7 @@ fn should_show_explain(&self) -> bool {
 /// Collects all the data needed to generate the data structures needed for the
 /// `annotate-snippets` library.
 struct DiagnosticConverter<'a> {
-    source_map: Option<Lrc<SourceMapperDyn>>,
+    source_map: Option<Lrc<SourceMap>>,
     level: Level,
     message: String,
     code: Option<DiagnosticId>,
@@ -168,7 +169,7 @@ fn annotation_type_for_level(level: Level) -> AnnotationType {
 
 impl AnnotateSnippetEmitterWriter {
     pub fn new(
-        source_map: Option<Lrc<SourceMapperDyn>>,
+        source_map: Option<Lrc<SourceMap>>,
         short_message: bool,
         external_macro_backtrace: bool,
     ) -> Self {
index 291920f17f66d7fa1d94ab3c5c9a5fec0e934e15..ea779982ba961b4de8387eba92e7edcd1a3eadb7 100644 (file)
 use Destination::*;
 
 use syntax_pos::{SourceFile, Span, MultiSpan};
+use syntax_pos::source_map::SourceMap;
 
 use crate::{
     Level, CodeSuggestion, Diagnostic, SubDiagnostic, pluralize,
-    SuggestionStyle, SourceMapper, SourceMapperDyn, DiagnosticId,
+    SuggestionStyle, DiagnosticId,
 };
 use crate::Level::Error;
 use crate::snippet::{Annotation, AnnotationType, Line, MultilineAnnotation, StyledString, Style};
@@ -49,7 +50,7 @@ pub fn unzip(self) -> (bool, ColorConfig) {
     pub fn new_emitter(
         self,
         dst: Box<dyn Write + Send>,
-        source_map: Option<Lrc<SourceMapperDyn>>,
+        source_map: Option<Lrc<SourceMap>>,
         teach: bool,
         terminal_width: Option<usize>,
         external_macro_backtrace: bool,
@@ -192,7 +193,7 @@ fn should_show_explain(&self) -> bool {
         true
     }
 
-    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>>;
+    fn source_map(&self) -> Option<&Lrc<SourceMap>>;
 
     /// Formats the substitutions of the primary_span
     ///
@@ -271,7 +272,7 @@ fn primary_span_formatted<'a>(
     // point directly at <*macros>. Since these are often difficult to read, this
     // will change the span to point at the use site.
     fn fix_multispans_in_std_macros(&self,
-                                    source_map: &Option<Lrc<SourceMapperDyn>>,
+                                    source_map: &Option<Lrc<SourceMap>>,
                                     span: &mut MultiSpan,
                                     children: &mut Vec<SubDiagnostic>,
                                     level: &Level,
@@ -311,7 +312,7 @@ fn fix_multispans_in_std_macros(&self,
     // <*macros>. Since these locations are often difficult to read, we move these Spans from
     // <*macros> to their corresponding use site.
     fn fix_multispan_in_std_macros(&self,
-                                   source_map: &Option<Lrc<SourceMapperDyn>>,
+                                   source_map: &Option<Lrc<SourceMap>>,
                                    span: &mut MultiSpan,
                                    always_backtrace: bool) -> bool {
         let sm = match source_map {
@@ -397,7 +398,7 @@ fn fix_multispan_in_std_macros(&self,
 }
 
 impl Emitter for EmitterWriter {
-    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
+    fn source_map(&self) -> Option<&Lrc<SourceMap>> {
         self.sm.as_ref()
     }
 
@@ -428,7 +429,7 @@ fn should_show_explain(&self) -> bool {
 pub struct SilentEmitter;
 
 impl Emitter for SilentEmitter {
-    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> { None }
+    fn source_map(&self) -> Option<&Lrc<SourceMap>> { None }
     fn emit_diagnostic(&mut self, _: &Diagnostic) {}
 }
 
@@ -476,7 +477,7 @@ fn suggests_using_colors(self) -> bool {
 /// Handles the writing of `HumanReadableErrorType::Default` and `HumanReadableErrorType::Short`
 pub struct EmitterWriter {
     dst: Destination,
-    sm: Option<Lrc<SourceMapperDyn>>,
+    sm: Option<Lrc<SourceMap>>,
     short_message: bool,
     teach: bool,
     ui_testing: bool,
@@ -495,7 +496,7 @@ pub struct FileWithAnnotatedLines {
 impl EmitterWriter {
     pub fn stderr(
         color_config: ColorConfig,
-        source_map: Option<Lrc<SourceMapperDyn>>,
+        source_map: Option<Lrc<SourceMap>>,
         short_message: bool,
         teach: bool,
         terminal_width: Option<usize>,
@@ -515,7 +516,7 @@ pub fn stderr(
 
     pub fn new(
         dst: Box<dyn Write + Send>,
-        source_map: Option<Lrc<SourceMapperDyn>>,
+        source_map: Option<Lrc<SourceMap>>,
         short_message: bool,
         teach: bool,
         colored: bool,
@@ -1685,7 +1686,7 @@ impl FileWithAnnotatedLines {
     /// This helps us quickly iterate over the whole message (including secondary file spans)
     pub fn collect_annotations(
         msp: &MultiSpan,
-        source_map: &Option<Lrc<SourceMapperDyn>>
+        source_map: &Option<Lrc<SourceMap>>
     ) -> Vec<FileWithAnnotatedLines> {
         fn add_annotation_to_file(file_vec: &mut Vec<FileWithAnnotatedLines>,
                                   file: Lrc<SourceFile>,
@@ -2067,7 +2068,7 @@ fn drop(&mut self) {
 }
 
 /// Whether the original and suggested code are visually similar enough to warrant extra wording.
-pub fn is_case_difference(sm: &dyn SourceMapper, suggested: &str, sp: Span) -> bool {
+pub fn is_case_difference(sm: &SourceMap, suggested: &str, sp: Span) -> bool {
     // FIXME: this should probably be extended to also account for `FO0` → `FOO` and unicode.
     let found = sm.span_to_snippet(sp).unwrap();
     let ascii_confusables = &['c', 'f', 'i', 'k', 'o', 's', 'u', 'v', 'w', 'x', 'y', 'z'];
diff --git a/src/librustc_errors/json.rs b/src/librustc_errors/json.rs
new file mode 100644 (file)
index 0000000..ebbd49b
--- /dev/null
@@ -0,0 +1,434 @@
+//! A JSON emitter for errors.
+//!
+//! This works by converting errors to a simplified structural format (see the
+//! structs at the start of the file) and then serializing them. These should
+//! contain as much information about the error as possible.
+//!
+//! The format of the JSON output should be considered *unstable*. For now the
+//! structs at the end of this file (Diagnostic*) specify the error format.
+
+// FIXME: spec the JSON output properly.
+
+use syntax_pos::source_map::{SourceMap, FilePathMapping};
+
+use crate::registry::Registry;
+use crate::{SubDiagnostic, CodeSuggestion};
+use crate::{DiagnosticId, Applicability};
+use crate::emitter::{Emitter, HumanReadableErrorType};
+
+use syntax_pos::{MacroBacktrace, Span, SpanLabel, MultiSpan};
+use rustc_data_structures::sync::Lrc;
+use std::io::{self, Write};
+use std::path::Path;
+use std::vec;
+use std::sync::{Arc, Mutex};
+
+use rustc_serialize::json::{as_json, as_pretty_json};
+
+#[cfg(test)]
+mod tests;
+
+pub struct JsonEmitter {
+    dst: Box<dyn Write + Send>,
+    registry: Option<Registry>,
+    sm: Lrc<SourceMap>,
+    pretty: bool,
+    ui_testing: bool,
+    json_rendered: HumanReadableErrorType,
+    external_macro_backtrace: bool,
+}
+
+impl JsonEmitter {
+    pub fn stderr(
+        registry: Option<Registry>,
+        source_map: Lrc<SourceMap>,
+        pretty: bool,
+        json_rendered: HumanReadableErrorType,
+        external_macro_backtrace: bool,
+    ) -> JsonEmitter {
+        JsonEmitter {
+            dst: Box::new(io::stderr()),
+            registry,
+            sm: source_map,
+            pretty,
+            ui_testing: false,
+            json_rendered,
+            external_macro_backtrace,
+        }
+    }
+
+    pub fn basic(
+        pretty: bool,
+        json_rendered: HumanReadableErrorType,
+        external_macro_backtrace: bool,
+    ) -> JsonEmitter {
+        let file_path_mapping = FilePathMapping::empty();
+        JsonEmitter::stderr(None, Lrc::new(SourceMap::new(file_path_mapping)),
+                            pretty, json_rendered, external_macro_backtrace)
+    }
+
+    pub fn new(
+        dst: Box<dyn Write + Send>,
+        registry: Option<Registry>,
+        source_map: Lrc<SourceMap>,
+        pretty: bool,
+        json_rendered: HumanReadableErrorType,
+        external_macro_backtrace: bool,
+    ) -> JsonEmitter {
+        JsonEmitter {
+            dst,
+            registry,
+            sm: source_map,
+            pretty,
+            ui_testing: false,
+            json_rendered,
+            external_macro_backtrace,
+        }
+    }
+
+    pub fn ui_testing(self, ui_testing: bool) -> Self {
+        Self { ui_testing, ..self }
+    }
+}
+
+impl Emitter for JsonEmitter {
+    fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
+        let data = Diagnostic::from_errors_diagnostic(diag, self);
+        let result = if self.pretty {
+            writeln!(&mut self.dst, "{}", as_pretty_json(&data))
+        } else {
+            writeln!(&mut self.dst, "{}", as_json(&data))
+        };
+        if let Err(e) = result {
+            panic!("failed to print diagnostics: {:?}", e);
+        }
+    }
+
+    fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
+        let data = ArtifactNotification { artifact: path, emit: artifact_type };
+        let result = if self.pretty {
+            writeln!(&mut self.dst, "{}", as_pretty_json(&data))
+        } else {
+            writeln!(&mut self.dst, "{}", as_json(&data))
+        };
+        if let Err(e) = result {
+            panic!("failed to print notification: {:?}", e);
+        }
+    }
+
+    fn source_map(&self) -> Option<&Lrc<SourceMap>> {
+        Some(&self.sm)
+    }
+
+    fn should_show_explain(&self) -> bool {
+        match self.json_rendered {
+            HumanReadableErrorType::Short(_) => false,
+            _ => true,
+        }
+    }
+}
+
+// The following data types are provided just for serialisation.
+
+#[derive(RustcEncodable)]
+struct Diagnostic {
+    /// The primary error message.
+    message: String,
+    code: Option<DiagnosticCode>,
+    /// "error: internal compiler error", "error", "warning", "note", "help".
+    level: &'static str,
+    spans: Vec<DiagnosticSpan>,
+    /// Associated diagnostic messages.
+    children: Vec<Diagnostic>,
+    /// The message as rustc would render it.
+    rendered: Option<String>,
+}
+
+#[derive(RustcEncodable)]
+struct DiagnosticSpan {
+    file_name: String,
+    byte_start: u32,
+    byte_end: u32,
+    /// 1-based.
+    line_start: usize,
+    line_end: usize,
+    /// 1-based, character offset.
+    column_start: usize,
+    column_end: usize,
+    /// Is this a "primary" span -- meaning the point, or one of the points,
+    /// where the error occurred?
+    is_primary: bool,
+    /// Source text from the start of line_start to the end of line_end.
+    text: Vec<DiagnosticSpanLine>,
+    /// Label that should be placed at this location (if any)
+    label: Option<String>,
+    /// If we are suggesting a replacement, this will contain text
+    /// that should be sliced in atop this span.
+    suggested_replacement: Option<String>,
+    /// If the suggestion is approximate
+    suggestion_applicability: Option<Applicability>,
+    /// Macro invocations that created the code at this span, if any.
+    expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
+}
+
+#[derive(RustcEncodable)]
+struct DiagnosticSpanLine {
+    text: String,
+
+    /// 1-based, character offset in self.text.
+    highlight_start: usize,
+
+    highlight_end: usize,
+}
+
+#[derive(RustcEncodable)]
+struct DiagnosticSpanMacroExpansion {
+    /// span where macro was applied to generate this code; note that
+    /// this may itself derive from a macro (if
+    /// `span.expansion.is_some()`)
+    span: DiagnosticSpan,
+
+    /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
+    macro_decl_name: String,
+
+    /// span where macro was defined (if known)
+    def_site_span: DiagnosticSpan,
+}
+
+#[derive(RustcEncodable)]
+struct DiagnosticCode {
+    /// The code itself.
+    code: String,
+    /// An explanation for the code.
+    explanation: Option<&'static str>,
+}
+
+#[derive(RustcEncodable)]
+struct ArtifactNotification<'a> {
+    /// The path of the artifact.
+    artifact: &'a Path,
+    /// What kind of artifact we're emitting.
+    emit: &'a str,
+}
+
+impl Diagnostic {
+    fn from_errors_diagnostic(diag: &crate::Diagnostic,
+                               je: &JsonEmitter)
+                               -> Diagnostic {
+        let sugg = diag.suggestions.iter().map(|sugg| {
+            Diagnostic {
+                message: sugg.msg.clone(),
+                code: None,
+                level: "help",
+                spans: DiagnosticSpan::from_suggestion(sugg, je),
+                children: vec![],
+                rendered: None,
+            }
+        });
+
+        // generate regular command line output and store it in the json
+
+        // A threadsafe buffer for writing.
+        #[derive(Default, Clone)]
+        struct BufWriter(Arc<Mutex<Vec<u8>>>);
+
+        impl Write for BufWriter {
+            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+                self.0.lock().unwrap().write(buf)
+            }
+            fn flush(&mut self) -> io::Result<()> {
+                self.0.lock().unwrap().flush()
+            }
+        }
+        let buf = BufWriter::default();
+        let output = buf.clone();
+        je.json_rendered.new_emitter(
+            Box::new(buf), Some(je.sm.clone()), false, None, je.external_macro_backtrace
+        ).ui_testing(je.ui_testing).emit_diagnostic(diag);
+        let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
+        let output = String::from_utf8(output).unwrap();
+
+        Diagnostic {
+            message: diag.message(),
+            code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
+            level: diag.level.to_str(),
+            spans: DiagnosticSpan::from_multispan(&diag.span, je),
+            children: diag.children.iter().map(|c| {
+                Diagnostic::from_sub_diagnostic(c, je)
+            }).chain(sugg).collect(),
+            rendered: Some(output),
+        }
+    }
+
+    fn from_sub_diagnostic(diag: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
+        Diagnostic {
+            message: diag.message(),
+            code: None,
+            level: diag.level.to_str(),
+            spans: diag.render_span.as_ref()
+                     .map(|sp| DiagnosticSpan::from_multispan(sp, je))
+                     .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
+            children: vec![],
+            rendered: None,
+        }
+    }
+}
+
+impl DiagnosticSpan {
+    fn from_span_label(span: SpanLabel,
+                       suggestion: Option<(&String, Applicability)>,
+                       je: &JsonEmitter)
+                       -> DiagnosticSpan {
+        Self::from_span_etc(span.span,
+                            span.is_primary,
+                            span.label,
+                            suggestion,
+                            je)
+    }
+
+    fn from_span_etc(span: Span,
+                     is_primary: bool,
+                     label: Option<String>,
+                     suggestion: Option<(&String, Applicability)>,
+                     je: &JsonEmitter)
+                     -> DiagnosticSpan {
+        // obtain the full backtrace from the `macro_backtrace`
+        // helper; in some ways, it'd be better to expand the
+        // backtrace ourselves, but the `macro_backtrace` helper makes
+        // some decision, such as dropping some frames, and I don't
+        // want to duplicate that logic here.
+        let backtrace = span.macro_backtrace().into_iter();
+        DiagnosticSpan::from_span_full(span,
+                                       is_primary,
+                                       label,
+                                       suggestion,
+                                       backtrace,
+                                       je)
+    }
+
+    fn from_span_full(span: Span,
+                      is_primary: bool,
+                      label: Option<String>,
+                      suggestion: Option<(&String, Applicability)>,
+                      mut backtrace: vec::IntoIter<MacroBacktrace>,
+                      je: &JsonEmitter)
+                      -> DiagnosticSpan {
+        let start = je.sm.lookup_char_pos(span.lo());
+        let end = je.sm.lookup_char_pos(span.hi());
+        let backtrace_step = backtrace.next().map(|bt| {
+            let call_site =
+                Self::from_span_full(bt.call_site,
+                                     false,
+                                     None,
+                                     None,
+                                     backtrace,
+                                     je);
+            let def_site_span =
+                Self::from_span_full(bt.def_site_span,
+                                     false,
+                                     None,
+                                     None,
+                                     vec![].into_iter(),
+                                     je);
+            Box::new(DiagnosticSpanMacroExpansion {
+                span: call_site,
+                macro_decl_name: bt.macro_decl_name,
+                def_site_span,
+            })
+        });
+
+        DiagnosticSpan {
+            file_name: start.file.name.to_string(),
+            byte_start: start.file.original_relative_byte_pos(span.lo()).0,
+            byte_end: start.file.original_relative_byte_pos(span.hi()).0,
+            line_start: start.line,
+            line_end: end.line,
+            column_start: start.col.0 + 1,
+            column_end: end.col.0 + 1,
+            is_primary,
+            text: DiagnosticSpanLine::from_span(span, je),
+            suggested_replacement: suggestion.map(|x| x.0.clone()),
+            suggestion_applicability: suggestion.map(|x| x.1),
+            expansion: backtrace_step,
+            label,
+        }
+    }
+
+    fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
+        msp.span_labels()
+           .into_iter()
+           .map(|span_str| Self::from_span_label(span_str, None, je))
+           .collect()
+    }
+
+    fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
+                       -> Vec<DiagnosticSpan> {
+        suggestion.substitutions
+                      .iter()
+                      .flat_map(|substitution| {
+                          substitution.parts.iter().map(move |suggestion_inner| {
+                              let span_label = SpanLabel {
+                                  span: suggestion_inner.span,
+                                  is_primary: true,
+                                  label: None,
+                              };
+                              DiagnosticSpan::from_span_label(span_label,
+                                                              Some((&suggestion_inner.snippet,
+                                                                   suggestion.applicability)),
+                                                              je)
+                          })
+                      })
+                      .collect()
+    }
+}
+
+impl DiagnosticSpanLine {
+    fn line_from_source_file(fm: &syntax_pos::SourceFile,
+                         index: usize,
+                         h_start: usize,
+                         h_end: usize)
+                         -> DiagnosticSpanLine {
+        DiagnosticSpanLine {
+            text: fm.get_line(index).map_or(String::new(), |l| l.into_owned()),
+            highlight_start: h_start,
+            highlight_end: h_end,
+        }
+    }
+
+    /// Creates a list of DiagnosticSpanLines from span - each line with any part
+    /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
+    /// `span` within the line.
+    fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
+        je.sm.span_to_lines(span)
+            .map(|lines| {
+                let fm = &*lines.file;
+                lines.lines
+                    .iter()
+                    .map(|line| DiagnosticSpanLine::line_from_source_file(
+                        fm,
+                        line.line_index,
+                        line.start_col.0 + 1,
+                        line.end_col.0 + 1,
+                    )).collect()
+            }).unwrap_or_else(|_| vec![])
+    }
+}
+
+impl DiagnosticCode {
+    fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
+        s.map(|s| {
+            let s = match s {
+                DiagnosticId::Error(s) => s,
+                DiagnosticId::Lint(s) => s,
+            };
+            let explanation = je.registry
+                                .as_ref()
+                                .and_then(|registry| registry.find_description(&s));
+
+            DiagnosticCode {
+                code: s,
+                explanation,
+            }
+        })
+    }
+}
diff --git a/src/librustc_errors/json/tests.rs b/src/librustc_errors/json/tests.rs
new file mode 100644 (file)
index 0000000..4ab5cd2
--- /dev/null
@@ -0,0 +1,205 @@
+use super::*;
+
+use crate::json::JsonEmitter;
+use syntax_pos::source_map::{FilePathMapping, SourceMap};
+
+use crate::emitter::{ColorConfig, HumanReadableErrorType};
+use crate::Handler;
+use rustc_serialize::json::decode;
+use syntax_pos::{BytePos, Span};
+
+use std::str;
+
+#[derive(RustcDecodable, Debug, PartialEq, Eq)]
+struct TestData {
+    spans: Vec<SpanTestData>,
+}
+
+#[derive(RustcDecodable, Debug, PartialEq, Eq)]
+struct SpanTestData {
+    pub byte_start: u32,
+    pub byte_end: u32,
+    pub line_start: u32,
+    pub column_start: u32,
+    pub line_end: u32,
+    pub column_end: u32,
+}
+
+struct Shared<T> {
+    data: Arc<Mutex<T>>,
+}
+
+impl<T: Write> Write for Shared<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.data.lock().unwrap().write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.data.lock().unwrap().flush()
+    }
+}
+
+fn with_default_globals(f: impl FnOnce()) {
+    let globals = syntax_pos::Globals::new(syntax_pos::edition::DEFAULT_EDITION);
+    syntax_pos::GLOBALS.set(&globals, || {
+        syntax_pos::GLOBALS.set(&globals, f)
+    })
+}
+
+/// Test the span yields correct positions in JSON.
+fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
+    let expected_output = TestData { spans: vec![expected_output] };
+
+    with_default_globals(|| {
+        let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+        sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned());
+
+        let output = Arc::new(Mutex::new(Vec::new()));
+        let je = JsonEmitter::new(
+            Box::new(Shared { data: output.clone() }),
+            None,
+            sm,
+            true,
+            HumanReadableErrorType::Short(ColorConfig::Never),
+            false,
+        );
+
+        let span = Span::with_root_ctxt(BytePos(span.0), BytePos(span.1));
+        let handler = Handler::with_emitter(true, None, Box::new(je));
+        handler.span_err(span, "foo");
+
+        let bytes = output.lock().unwrap();
+        let actual_output = str::from_utf8(&bytes).unwrap();
+        let actual_output: TestData = decode(actual_output).unwrap();
+
+        assert_eq!(expected_output, actual_output)
+    })
+}
+
+#[test]
+fn empty() {
+    test_positions(
+        " ",
+        (0, 1),
+        SpanTestData {
+            byte_start: 0,
+            byte_end: 1,
+            line_start: 1,
+            column_start: 1,
+            line_end: 1,
+            column_end: 2,
+        },
+    )
+}
+
+#[test]
+fn bom() {
+    test_positions(
+        "\u{feff} ",
+        (0, 1),
+        SpanTestData {
+            byte_start: 3,
+            byte_end: 4,
+            line_start: 1,
+            column_start: 1,
+            line_end: 1,
+            column_end: 2,
+        },
+    )
+}
+
+#[test]
+fn lf_newlines() {
+    test_positions(
+        "\nmod foo;\nmod bar;\n",
+        (5, 12),
+        SpanTestData {
+            byte_start: 5,
+            byte_end: 12,
+            line_start: 2,
+            column_start: 5,
+            line_end: 3,
+            column_end: 3,
+        },
+    )
+}
+
+#[test]
+fn crlf_newlines() {
+    test_positions(
+        "\r\nmod foo;\r\nmod bar;\r\n",
+        (5, 12),
+        SpanTestData {
+            byte_start: 6,
+            byte_end: 14,
+            line_start: 2,
+            column_start: 5,
+            line_end: 3,
+            column_end: 3,
+        },
+    )
+}
+
+#[test]
+fn crlf_newlines_with_bom() {
+    test_positions(
+        "\u{feff}\r\nmod foo;\r\nmod bar;\r\n",
+        (5, 12),
+        SpanTestData {
+            byte_start: 9,
+            byte_end: 17,
+            line_start: 2,
+            column_start: 5,
+            line_end: 3,
+            column_end: 3,
+        },
+    )
+}
+
+#[test]
+fn span_before_crlf() {
+    test_positions(
+        "foo\r\nbar",
+        (2, 3),
+        SpanTestData {
+            byte_start: 2,
+            byte_end: 3,
+            line_start: 1,
+            column_start: 3,
+            line_end: 1,
+            column_end: 4,
+        },
+    )
+}
+
+#[test]
+fn span_on_crlf() {
+    test_positions(
+        "foo\r\nbar",
+        (3, 4),
+        SpanTestData {
+            byte_start: 3,
+            byte_end: 5,
+            line_start: 1,
+            column_start: 4,
+            line_end: 2,
+            column_end: 1,
+        },
+    )
+}
+
+#[test]
+fn span_after_crlf() {
+    test_positions(
+        "foo\r\nbar",
+        (4, 5),
+        SpanTestData {
+            byte_start: 5,
+            byte_end: 6,
+            line_start: 2,
+            column_start: 1,
+            line_end: 2,
+            column_end: 2,
+        },
+    )
+}
index 8ee28875c625931bd277ad2cb8814aa854cc686d..17765ef9deefa25df550aebe9041fe0c312e400e 100644 (file)
@@ -18,6 +18,8 @@
 use rustc_data_structures::sync::{self, Lrc, Lock};
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
 use rustc_data_structures::stable_hasher::StableHasher;
+use syntax_pos::source_map::SourceMap;
+use syntax_pos::{Loc, Span, MultiSpan};
 
 use std::borrow::Cow;
 use std::cell::Cell;
 pub mod registry;
 mod styled_buffer;
 mod lock;
-
-use syntax_pos::{
-    BytePos,
-    FileLinesResult,
-    FileName,
-    Loc,
-    MultiSpan,
-    SourceFile,
-    Span,
-    SpanSnippetError,
-};
+pub mod json;
 
 pub type PResult<'a, T> = Result<T, DiagnosticBuilder<'a>>;
 
@@ -150,26 +142,12 @@ pub struct SubstitutionPart {
     pub snippet: String,
 }
 
-pub type SourceMapperDyn = dyn SourceMapper + sync::Send + sync::Sync;
-
-pub trait SourceMapper {
-    fn lookup_char_pos(&self, pos: BytePos) -> Loc;
-    fn span_to_lines(&self, sp: Span) -> FileLinesResult;
-    fn span_to_string(&self, sp: Span) -> String;
-    fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError>;
-    fn span_to_filename(&self, sp: Span) -> FileName;
-    fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span>;
-    fn call_span_if_macro(&self, sp: Span) -> Span;
-    fn ensure_source_file_source_present(&self, source_file: Lrc<SourceFile>) -> bool;
-    fn doctest_offset_line(&self, file: &FileName, line: usize) -> usize;
-}
-
 impl CodeSuggestion {
     /// Returns the assembled code suggestions, whether they should be shown with an underline
     /// and whether the substitution only differs in capitalization.
     pub fn splice_lines(
         &self,
-        cm: &SourceMapperDyn,
+        cm: &SourceMap,
     ) -> Vec<(String, Vec<SubstitutionPart>, bool)> {
         use syntax_pos::{CharPos, Pos};
 
@@ -259,36 +237,7 @@ fn push_trailing(buf: &mut String,
     }
 }
 
-/// Used as a return value to signify a fatal error occurred. (It is also
-/// used as the argument to panic at the moment, but that will eventually
-/// not be true.)
-#[derive(Copy, Clone, Debug)]
-#[must_use]
-pub struct FatalError;
-
-pub struct FatalErrorMarker;
-
-// Don't implement Send on FatalError. This makes it impossible to panic!(FatalError).
-// We don't want to invoke the panic handler and print a backtrace for fatal errors.
-impl !Send for FatalError {}
-
-impl FatalError {
-    pub fn raise(self) -> ! {
-        panic::resume_unwind(Box::new(FatalErrorMarker))
-    }
-}
-
-impl fmt::Display for FatalError {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "parser fatal error")
-    }
-}
-
-impl error::Error for FatalError {
-    fn description(&self) -> &str {
-        "The parser has encountered a fatal error"
-    }
-}
+pub use syntax_pos::fatal_error::{FatalError, FatalErrorMarker};
 
 /// Signifies that the compiler died with an explicit call to `.bug`
 /// or `.span_bug` rather than a failed assertion, etc.
@@ -405,7 +354,7 @@ pub fn with_tty_emitter(
         color_config: ColorConfig,
         can_emit_warnings: bool,
         treat_err_as_bug: Option<usize>,
-        cm: Option<Lrc<SourceMapperDyn>>,
+        cm: Option<Lrc<SourceMap>>,
     ) -> Self {
         Self::with_tty_emitter_and_flags(
             color_config,
@@ -420,7 +369,7 @@ pub fn with_tty_emitter(
 
     pub fn with_tty_emitter_and_flags(
         color_config: ColorConfig,
-        cm: Option<Lrc<SourceMapperDyn>>,
+        cm: Option<Lrc<SourceMap>>,
         flags: HandlerFlags,
     ) -> Self {
         let emitter = Box::new(EmitterWriter::stderr(
index a40325448b10b8b20675a29a5f3138cdc128348b..507732a9107fb166476651f796975f16a0e0d7d5 100644 (file)
@@ -18,7 +18,7 @@
 use syntax::source_map;
 use syntax::attr;
 use syntax::feature_gate::UnstableFeatures;
-use syntax::json::JsonEmitter;
+use errors::json::JsonEmitter;
 use syntax::symbol::sym;
 use syntax_pos::DUMMY_SP;
 use errors;
diff --git a/src/libsyntax/json.rs b/src/libsyntax/json.rs
deleted file mode 100644 (file)
index 0b15793..0000000
+++ /dev/null
@@ -1,434 +0,0 @@
-//! A JSON emitter for errors.
-//!
-//! This works by converting errors to a simplified structural format (see the
-//! structs at the start of the file) and then serializing them. These should
-//! contain as much information about the error as possible.
-//!
-//! The format of the JSON output should be considered *unstable*. For now the
-//! structs at the end of this file (Diagnostic*) specify the error format.
-
-// FIXME: spec the JSON output properly.
-
-use crate::source_map::{SourceMap, FilePathMapping};
-
-use errors::registry::Registry;
-use errors::{SubDiagnostic, CodeSuggestion, SourceMapper, SourceMapperDyn};
-use errors::{DiagnosticId, Applicability};
-use errors::emitter::{Emitter, HumanReadableErrorType};
-
-use syntax_pos::{MacroBacktrace, Span, SpanLabel, MultiSpan};
-use rustc_data_structures::sync::{self, Lrc};
-use std::io::{self, Write};
-use std::path::Path;
-use std::vec;
-use std::sync::{Arc, Mutex};
-
-use rustc_serialize::json::{as_json, as_pretty_json};
-
-#[cfg(test)]
-mod tests;
-
-pub struct JsonEmitter {
-    dst: Box<dyn Write + Send>,
-    registry: Option<Registry>,
-    sm: Lrc<dyn SourceMapper + sync::Send + sync::Sync>,
-    pretty: bool,
-    ui_testing: bool,
-    json_rendered: HumanReadableErrorType,
-    external_macro_backtrace: bool,
-}
-
-impl JsonEmitter {
-    pub fn stderr(
-        registry: Option<Registry>,
-        source_map: Lrc<SourceMap>,
-        pretty: bool,
-        json_rendered: HumanReadableErrorType,
-        external_macro_backtrace: bool,
-    ) -> JsonEmitter {
-        JsonEmitter {
-            dst: Box::new(io::stderr()),
-            registry,
-            sm: source_map,
-            pretty,
-            ui_testing: false,
-            json_rendered,
-            external_macro_backtrace,
-        }
-    }
-
-    pub fn basic(
-        pretty: bool,
-        json_rendered: HumanReadableErrorType,
-        external_macro_backtrace: bool,
-    ) -> JsonEmitter {
-        let file_path_mapping = FilePathMapping::empty();
-        JsonEmitter::stderr(None, Lrc::new(SourceMap::new(file_path_mapping)),
-                            pretty, json_rendered, external_macro_backtrace)
-    }
-
-    pub fn new(
-        dst: Box<dyn Write + Send>,
-        registry: Option<Registry>,
-        source_map: Lrc<SourceMap>,
-        pretty: bool,
-        json_rendered: HumanReadableErrorType,
-        external_macro_backtrace: bool,
-    ) -> JsonEmitter {
-        JsonEmitter {
-            dst,
-            registry,
-            sm: source_map,
-            pretty,
-            ui_testing: false,
-            json_rendered,
-            external_macro_backtrace,
-        }
-    }
-
-    pub fn ui_testing(self, ui_testing: bool) -> Self {
-        Self { ui_testing, ..self }
-    }
-}
-
-impl Emitter for JsonEmitter {
-    fn emit_diagnostic(&mut self, diag: &errors::Diagnostic) {
-        let data = Diagnostic::from_errors_diagnostic(diag, self);
-        let result = if self.pretty {
-            writeln!(&mut self.dst, "{}", as_pretty_json(&data))
-        } else {
-            writeln!(&mut self.dst, "{}", as_json(&data))
-        };
-        if let Err(e) = result {
-            panic!("failed to print diagnostics: {:?}", e);
-        }
-    }
-
-    fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
-        let data = ArtifactNotification { artifact: path, emit: artifact_type };
-        let result = if self.pretty {
-            writeln!(&mut self.dst, "{}", as_pretty_json(&data))
-        } else {
-            writeln!(&mut self.dst, "{}", as_json(&data))
-        };
-        if let Err(e) = result {
-            panic!("failed to print notification: {:?}", e);
-        }
-    }
-
-    fn source_map(&self) -> Option<&Lrc<SourceMapperDyn>> {
-        Some(&self.sm)
-    }
-
-    fn should_show_explain(&self) -> bool {
-        match self.json_rendered {
-            HumanReadableErrorType::Short(_) => false,
-            _ => true,
-        }
-    }
-}
-
-// The following data types are provided just for serialisation.
-
-#[derive(RustcEncodable)]
-struct Diagnostic {
-    /// The primary error message.
-    message: String,
-    code: Option<DiagnosticCode>,
-    /// "error: internal compiler error", "error", "warning", "note", "help".
-    level: &'static str,
-    spans: Vec<DiagnosticSpan>,
-    /// Associated diagnostic messages.
-    children: Vec<Diagnostic>,
-    /// The message as rustc would render it.
-    rendered: Option<String>,
-}
-
-#[derive(RustcEncodable)]
-struct DiagnosticSpan {
-    file_name: String,
-    byte_start: u32,
-    byte_end: u32,
-    /// 1-based.
-    line_start: usize,
-    line_end: usize,
-    /// 1-based, character offset.
-    column_start: usize,
-    column_end: usize,
-    /// Is this a "primary" span -- meaning the point, or one of the points,
-    /// where the error occurred?
-    is_primary: bool,
-    /// Source text from the start of line_start to the end of line_end.
-    text: Vec<DiagnosticSpanLine>,
-    /// Label that should be placed at this location (if any)
-    label: Option<String>,
-    /// If we are suggesting a replacement, this will contain text
-    /// that should be sliced in atop this span.
-    suggested_replacement: Option<String>,
-    /// If the suggestion is approximate
-    suggestion_applicability: Option<Applicability>,
-    /// Macro invocations that created the code at this span, if any.
-    expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
-}
-
-#[derive(RustcEncodable)]
-struct DiagnosticSpanLine {
-    text: String,
-
-    /// 1-based, character offset in self.text.
-    highlight_start: usize,
-
-    highlight_end: usize,
-}
-
-#[derive(RustcEncodable)]
-struct DiagnosticSpanMacroExpansion {
-    /// span where macro was applied to generate this code; note that
-    /// this may itself derive from a macro (if
-    /// `span.expansion.is_some()`)
-    span: DiagnosticSpan,
-
-    /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
-    macro_decl_name: String,
-
-    /// span where macro was defined (if known)
-    def_site_span: DiagnosticSpan,
-}
-
-#[derive(RustcEncodable)]
-struct DiagnosticCode {
-    /// The code itself.
-    code: String,
-    /// An explanation for the code.
-    explanation: Option<&'static str>,
-}
-
-#[derive(RustcEncodable)]
-struct ArtifactNotification<'a> {
-    /// The path of the artifact.
-    artifact: &'a Path,
-    /// What kind of artifact we're emitting.
-    emit: &'a str,
-}
-
-impl Diagnostic {
-    fn from_errors_diagnostic(diag: &errors::Diagnostic,
-                               je: &JsonEmitter)
-                               -> Diagnostic {
-        let sugg = diag.suggestions.iter().map(|sugg| {
-            Diagnostic {
-                message: sugg.msg.clone(),
-                code: None,
-                level: "help",
-                spans: DiagnosticSpan::from_suggestion(sugg, je),
-                children: vec![],
-                rendered: None,
-            }
-        });
-
-        // generate regular command line output and store it in the json
-
-        // A threadsafe buffer for writing.
-        #[derive(Default, Clone)]
-        struct BufWriter(Arc<Mutex<Vec<u8>>>);
-
-        impl Write for BufWriter {
-            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-                self.0.lock().unwrap().write(buf)
-            }
-            fn flush(&mut self) -> io::Result<()> {
-                self.0.lock().unwrap().flush()
-            }
-        }
-        let buf = BufWriter::default();
-        let output = buf.clone();
-        je.json_rendered.new_emitter(
-            Box::new(buf), Some(je.sm.clone()), false, None, je.external_macro_backtrace
-        ).ui_testing(je.ui_testing).emit_diagnostic(diag);
-        let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
-        let output = String::from_utf8(output).unwrap();
-
-        Diagnostic {
-            message: diag.message(),
-            code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
-            level: diag.level.to_str(),
-            spans: DiagnosticSpan::from_multispan(&diag.span, je),
-            children: diag.children.iter().map(|c| {
-                Diagnostic::from_sub_diagnostic(c, je)
-            }).chain(sugg).collect(),
-            rendered: Some(output),
-        }
-    }
-
-    fn from_sub_diagnostic(diag: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
-        Diagnostic {
-            message: diag.message(),
-            code: None,
-            level: diag.level.to_str(),
-            spans: diag.render_span.as_ref()
-                     .map(|sp| DiagnosticSpan::from_multispan(sp, je))
-                     .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
-            children: vec![],
-            rendered: None,
-        }
-    }
-}
-
-impl DiagnosticSpan {
-    fn from_span_label(span: SpanLabel,
-                       suggestion: Option<(&String, Applicability)>,
-                       je: &JsonEmitter)
-                       -> DiagnosticSpan {
-        Self::from_span_etc(span.span,
-                            span.is_primary,
-                            span.label,
-                            suggestion,
-                            je)
-    }
-
-    fn from_span_etc(span: Span,
-                     is_primary: bool,
-                     label: Option<String>,
-                     suggestion: Option<(&String, Applicability)>,
-                     je: &JsonEmitter)
-                     -> DiagnosticSpan {
-        // obtain the full backtrace from the `macro_backtrace`
-        // helper; in some ways, it'd be better to expand the
-        // backtrace ourselves, but the `macro_backtrace` helper makes
-        // some decision, such as dropping some frames, and I don't
-        // want to duplicate that logic here.
-        let backtrace = span.macro_backtrace().into_iter();
-        DiagnosticSpan::from_span_full(span,
-                                       is_primary,
-                                       label,
-                                       suggestion,
-                                       backtrace,
-                                       je)
-    }
-
-    fn from_span_full(span: Span,
-                      is_primary: bool,
-                      label: Option<String>,
-                      suggestion: Option<(&String, Applicability)>,
-                      mut backtrace: vec::IntoIter<MacroBacktrace>,
-                      je: &JsonEmitter)
-                      -> DiagnosticSpan {
-        let start = je.sm.lookup_char_pos(span.lo());
-        let end = je.sm.lookup_char_pos(span.hi());
-        let backtrace_step = backtrace.next().map(|bt| {
-            let call_site =
-                Self::from_span_full(bt.call_site,
-                                     false,
-                                     None,
-                                     None,
-                                     backtrace,
-                                     je);
-            let def_site_span =
-                Self::from_span_full(bt.def_site_span,
-                                     false,
-                                     None,
-                                     None,
-                                     vec![].into_iter(),
-                                     je);
-            Box::new(DiagnosticSpanMacroExpansion {
-                span: call_site,
-                macro_decl_name: bt.macro_decl_name,
-                def_site_span,
-            })
-        });
-
-        DiagnosticSpan {
-            file_name: start.file.name.to_string(),
-            byte_start: start.file.original_relative_byte_pos(span.lo()).0,
-            byte_end: start.file.original_relative_byte_pos(span.hi()).0,
-            line_start: start.line,
-            line_end: end.line,
-            column_start: start.col.0 + 1,
-            column_end: end.col.0 + 1,
-            is_primary,
-            text: DiagnosticSpanLine::from_span(span, je),
-            suggested_replacement: suggestion.map(|x| x.0.clone()),
-            suggestion_applicability: suggestion.map(|x| x.1),
-            expansion: backtrace_step,
-            label,
-        }
-    }
-
-    fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
-        msp.span_labels()
-           .into_iter()
-           .map(|span_str| Self::from_span_label(span_str, None, je))
-           .collect()
-    }
-
-    fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter)
-                       -> Vec<DiagnosticSpan> {
-        suggestion.substitutions
-                      .iter()
-                      .flat_map(|substitution| {
-                          substitution.parts.iter().map(move |suggestion_inner| {
-                              let span_label = SpanLabel {
-                                  span: suggestion_inner.span,
-                                  is_primary: true,
-                                  label: None,
-                              };
-                              DiagnosticSpan::from_span_label(span_label,
-                                                              Some((&suggestion_inner.snippet,
-                                                                   suggestion.applicability)),
-                                                              je)
-                          })
-                      })
-                      .collect()
-    }
-}
-
-impl DiagnosticSpanLine {
-    fn line_from_source_file(fm: &syntax_pos::SourceFile,
-                         index: usize,
-                         h_start: usize,
-                         h_end: usize)
-                         -> DiagnosticSpanLine {
-        DiagnosticSpanLine {
-            text: fm.get_line(index).map_or(String::new(), |l| l.into_owned()),
-            highlight_start: h_start,
-            highlight_end: h_end,
-        }
-    }
-
-    /// Creates a list of DiagnosticSpanLines from span - each line with any part
-    /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
-    /// `span` within the line.
-    fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
-        je.sm.span_to_lines(span)
-            .map(|lines| {
-                let fm = &*lines.file;
-                lines.lines
-                    .iter()
-                    .map(|line| DiagnosticSpanLine::line_from_source_file(
-                        fm,
-                        line.line_index,
-                        line.start_col.0 + 1,
-                        line.end_col.0 + 1,
-                    )).collect()
-            }).unwrap_or_else(|_| vec![])
-    }
-}
-
-impl DiagnosticCode {
-    fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
-        s.map(|s| {
-            let s = match s {
-                DiagnosticId::Error(s) => s,
-                DiagnosticId::Lint(s) => s,
-            };
-            let explanation = je.registry
-                                .as_ref()
-                                .and_then(|registry| registry.find_description(&s));
-
-            DiagnosticCode {
-                code: s,
-                explanation,
-            }
-        })
-    }
-}
diff --git a/src/libsyntax/json/tests.rs b/src/libsyntax/json/tests.rs
deleted file mode 100644 (file)
index 1edefd5..0000000
+++ /dev/null
@@ -1,199 +0,0 @@
-use super::*;
-
-use crate::json::JsonEmitter;
-use crate::source_map::{FilePathMapping, SourceMap};
-use crate::with_default_globals;
-
-use errors::emitter::{ColorConfig, HumanReadableErrorType};
-use errors::Handler;
-use rustc_serialize::json::decode;
-use syntax_pos::{BytePos, Span};
-
-use std::str;
-
-#[derive(RustcDecodable, Debug, PartialEq, Eq)]
-struct TestData {
-    spans: Vec<SpanTestData>,
-}
-
-#[derive(RustcDecodable, Debug, PartialEq, Eq)]
-struct SpanTestData {
-    pub byte_start: u32,
-    pub byte_end: u32,
-    pub line_start: u32,
-    pub column_start: u32,
-    pub line_end: u32,
-    pub column_end: u32,
-}
-
-struct Shared<T> {
-    data: Arc<Mutex<T>>,
-}
-
-impl<T: Write> Write for Shared<T> {
-    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        self.data.lock().unwrap().write(buf)
-    }
-
-    fn flush(&mut self) -> io::Result<()> {
-        self.data.lock().unwrap().flush()
-    }
-}
-
-/// Test the span yields correct positions in JSON.
-fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
-    let expected_output = TestData { spans: vec![expected_output] };
-
-    with_default_globals(|| {
-        let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
-        sm.new_source_file(Path::new("test.rs").to_owned().into(), code.to_owned());
-
-        let output = Arc::new(Mutex::new(Vec::new()));
-        let je = JsonEmitter::new(
-            Box::new(Shared { data: output.clone() }),
-            None,
-            sm,
-            true,
-            HumanReadableErrorType::Short(ColorConfig::Never),
-            false,
-        );
-
-        let span = Span::with_root_ctxt(BytePos(span.0), BytePos(span.1));
-        let handler = Handler::with_emitter(true, None, Box::new(je));
-        handler.span_err(span, "foo");
-
-        let bytes = output.lock().unwrap();
-        let actual_output = str::from_utf8(&bytes).unwrap();
-        let actual_output: TestData = decode(actual_output).unwrap();
-
-        assert_eq!(expected_output, actual_output)
-    })
-}
-
-#[test]
-fn empty() {
-    test_positions(
-        " ",
-        (0, 1),
-        SpanTestData {
-            byte_start: 0,
-            byte_end: 1,
-            line_start: 1,
-            column_start: 1,
-            line_end: 1,
-            column_end: 2,
-        },
-    )
-}
-
-#[test]
-fn bom() {
-    test_positions(
-        "\u{feff} ",
-        (0, 1),
-        SpanTestData {
-            byte_start: 3,
-            byte_end: 4,
-            line_start: 1,
-            column_start: 1,
-            line_end: 1,
-            column_end: 2,
-        },
-    )
-}
-
-#[test]
-fn lf_newlines() {
-    test_positions(
-        "\nmod foo;\nmod bar;\n",
-        (5, 12),
-        SpanTestData {
-            byte_start: 5,
-            byte_end: 12,
-            line_start: 2,
-            column_start: 5,
-            line_end: 3,
-            column_end: 3,
-        },
-    )
-}
-
-#[test]
-fn crlf_newlines() {
-    test_positions(
-        "\r\nmod foo;\r\nmod bar;\r\n",
-        (5, 12),
-        SpanTestData {
-            byte_start: 6,
-            byte_end: 14,
-            line_start: 2,
-            column_start: 5,
-            line_end: 3,
-            column_end: 3,
-        },
-    )
-}
-
-#[test]
-fn crlf_newlines_with_bom() {
-    test_positions(
-        "\u{feff}\r\nmod foo;\r\nmod bar;\r\n",
-        (5, 12),
-        SpanTestData {
-            byte_start: 9,
-            byte_end: 17,
-            line_start: 2,
-            column_start: 5,
-            line_end: 3,
-            column_end: 3,
-        },
-    )
-}
-
-#[test]
-fn span_before_crlf() {
-    test_positions(
-        "foo\r\nbar",
-        (2, 3),
-        SpanTestData {
-            byte_start: 2,
-            byte_end: 3,
-            line_start: 1,
-            column_start: 3,
-            line_end: 1,
-            column_end: 4,
-        },
-    )
-}
-
-#[test]
-fn span_on_crlf() {
-    test_positions(
-        "foo\r\nbar",
-        (3, 4),
-        SpanTestData {
-            byte_start: 3,
-            byte_end: 5,
-            line_start: 1,
-            column_start: 4,
-            line_end: 2,
-            column_end: 1,
-        },
-    )
-}
-
-#[test]
-fn span_after_crlf() {
-    test_positions(
-        "foo\r\nbar",
-        (4, 5),
-        SpanTestData {
-            byte_start: 5,
-            byte_end: 6,
-            line_start: 2,
-            column_start: 1,
-            line_end: 2,
-            column_end: 2,
-        },
-    )
-}
index 6290b2137ea9a42ee55a7b4262fcbcd01933a8d0..e3eca75dfe7e70951b26fdb7a238759bfad360a3 100644 (file)
@@ -87,12 +87,10 @@ pub mod util {
     pub mod map_in_place;
 }
 
-pub mod json;
-
 pub mod ast;
 pub mod attr;
 pub mod expand;
-pub mod source_map;
+pub use syntax_pos::source_map;
 pub mod entry;
 pub mod feature_gate;
 pub mod mut_visit;
diff --git a/src/libsyntax/source_map.rs b/src/libsyntax/source_map.rs
deleted file mode 100644 (file)
index d9f6186..0000000
+++ /dev/null
@@ -1,1035 +0,0 @@
-//! The `SourceMap` tracks all the source code used within a single crate, mapping
-//! from integer byte positions to the original source code location. Each bit
-//! of source parsed during crate parsing (typically files, in-memory strings,
-//! or various bits of macro expansion) cover a continuous range of bytes in the
-//! `SourceMap` and are represented by `SourceFile`s. Byte positions are stored in
-//! `Span` and used pervasively in the compiler. They are absolute positions
-//! within the `SourceMap`, which upon request can be converted to line and column
-//! information, source code snippets, etc.
-
-pub use syntax_pos::*;
-pub use syntax_pos::hygiene::{ExpnKind, ExpnData};
-
-use rustc_data_structures::fx::FxHashMap;
-use rustc_data_structures::stable_hasher::StableHasher;
-use rustc_data_structures::sync::{Lrc, Lock, LockGuard, MappedLockGuard};
-use std::cmp;
-use std::hash::Hash;
-use std::path::{Path, PathBuf};
-
-use std::env;
-use std::fs;
-use std::io;
-use log::debug;
-
-use errors::SourceMapper;
-
-#[cfg(test)]
-mod tests;
-
-/// Returns the span itself if it doesn't come from a macro expansion,
-/// otherwise return the call site span up to the `enclosing_sp` by
-/// following the `expn_data` chain.
-pub fn original_sp(sp: Span, enclosing_sp: Span) -> Span {
-    let expn_data1 = sp.ctxt().outer_expn_data();
-    let expn_data2 = enclosing_sp.ctxt().outer_expn_data();
-    if expn_data1.is_root() ||
-       !expn_data2.is_root() && expn_data1.call_site == expn_data2.call_site {
-        sp
-    } else {
-        original_sp(expn_data1.call_site, enclosing_sp)
-    }
-}
-
-#[derive(Clone, RustcEncodable, RustcDecodable, Debug, Copy)]
-pub struct Spanned<T> {
-    pub node: T,
-    pub span: Span,
-}
-
-pub fn respan<T>(sp: Span, t: T) -> Spanned<T> {
-    Spanned {node: t, span: sp}
-}
-
-pub fn dummy_spanned<T>(t: T) -> Spanned<T> {
-    respan(DUMMY_SP, t)
-}
-
-// _____________________________________________________________________________
-// SourceFile, MultiByteChar, FileName, FileLines
-//
-
-/// An abstraction over the fs operations used by the Parser.
-pub trait FileLoader {
-    /// Query the existence of a file.
-    fn file_exists(&self, path: &Path) -> bool;
-
-    /// Returns an absolute path to a file, if possible.
-    fn abs_path(&self, path: &Path) -> Option<PathBuf>;
-
-    /// Read the contents of an UTF-8 file into memory.
-    fn read_file(&self, path: &Path) -> io::Result<String>;
-}
-
-/// A FileLoader that uses std::fs to load real files.
-pub struct RealFileLoader;
-
-impl FileLoader for RealFileLoader {
-    fn file_exists(&self, path: &Path) -> bool {
-        fs::metadata(path).is_ok()
-    }
-
-    fn abs_path(&self, path: &Path) -> Option<PathBuf> {
-        if path.is_absolute() {
-            Some(path.to_path_buf())
-        } else {
-            env::current_dir()
-                .ok()
-                .map(|cwd| cwd.join(path))
-        }
-    }
-
-    fn read_file(&self, path: &Path) -> io::Result<String> {
-        fs::read_to_string(path)
-    }
-}
-
-// This is a `SourceFile` identifier that is used to correlate `SourceFile`s between
-// subsequent compilation sessions (which is something we need to do during
-// incremental compilation).
-#[derive(Copy, Clone, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable, Debug)]
-pub struct StableSourceFileId(u128);
-
-impl StableSourceFileId {
-    pub fn new(source_file: &SourceFile) -> StableSourceFileId {
-        StableSourceFileId::new_from_pieces(&source_file.name,
-                                            source_file.name_was_remapped,
-                                            source_file.unmapped_path.as_ref())
-    }
-
-    pub fn new_from_pieces(name: &FileName,
-                           name_was_remapped: bool,
-                           unmapped_path: Option<&FileName>) -> StableSourceFileId {
-        let mut hasher = StableHasher::new();
-
-        name.hash(&mut hasher);
-        name_was_remapped.hash(&mut hasher);
-        unmapped_path.hash(&mut hasher);
-
-        StableSourceFileId(hasher.finish())
-    }
-}
-
-// _____________________________________________________________________________
-// SourceMap
-//
-
-#[derive(Default)]
-pub(super) struct SourceMapFiles {
-    source_files: Vec<Lrc<SourceFile>>,
-    stable_id_to_source_file: FxHashMap<StableSourceFileId, Lrc<SourceFile>>
-}
-
-pub struct SourceMap {
-    files: Lock<SourceMapFiles>,
-    file_loader: Box<dyn FileLoader + Sync + Send>,
-    // This is used to apply the file path remapping as specified via
-    // `--remap-path-prefix` to all `SourceFile`s allocated within this `SourceMap`.
-    path_mapping: FilePathMapping,
-}
-
-impl SourceMap {
-    pub fn new(path_mapping: FilePathMapping) -> SourceMap {
-        SourceMap {
-            files: Default::default(),
-            file_loader: Box::new(RealFileLoader),
-            path_mapping,
-        }
-    }
-
-    pub fn with_file_loader(file_loader: Box<dyn FileLoader + Sync + Send>,
-                            path_mapping: FilePathMapping)
-                            -> SourceMap {
-        SourceMap {
-            files: Default::default(),
-            file_loader,
-            path_mapping,
-        }
-    }
-
-    pub fn path_mapping(&self) -> &FilePathMapping {
-        &self.path_mapping
-    }
-
-    pub fn file_exists(&self, path: &Path) -> bool {
-        self.file_loader.file_exists(path)
-    }
-
-    pub fn load_file(&self, path: &Path) -> io::Result<Lrc<SourceFile>> {
-        let src = self.file_loader.read_file(path)?;
-        let filename = path.to_owned().into();
-        Ok(self.new_source_file(filename, src))
-    }
-
-    /// Loads source file as a binary blob.
-    ///
-    /// Unlike `load_file`, guarantees that no normalization like BOM-removal
-    /// takes place.
-    pub fn load_binary_file(&self, path: &Path) -> io::Result<Vec<u8>> {
-        // Ideally, this should use `self.file_loader`, but it can't
-        // deal with binary files yet.
-        let bytes = fs::read(path)?;
-
-        // We need to add file to the `SourceMap`, so that it is present
-        // in dep-info. There's also an edge case that file might be both
-        // loaded as a binary via `include_bytes!` and as proper `SourceFile`
-        // via `mod`, so we try to use real file contents and not just an
-        // empty string.
-        let text = std::str::from_utf8(&bytes).unwrap_or("")
-            .to_string();
-        self.new_source_file(path.to_owned().into(), text);
-        Ok(bytes)
-    }
-
-    pub fn files(&self) -> MappedLockGuard<'_, Vec<Lrc<SourceFile>>> {
-        LockGuard::map(self.files.borrow(), |files| &mut files.source_files)
-    }
-
-    pub fn source_file_by_stable_id(&self, stable_id: StableSourceFileId) ->
-    Option<Lrc<SourceFile>> {
-        self.files.borrow().stable_id_to_source_file.get(&stable_id).map(|sf| sf.clone())
-    }
-
-    fn next_start_pos(&self) -> usize {
-        match self.files.borrow().source_files.last() {
-            None => 0,
-            // Add one so there is some space between files. This lets us distinguish
-            // positions in the `SourceMap`, even in the presence of zero-length files.
-            Some(last) => last.end_pos.to_usize() + 1,
-        }
-    }
-
-    /// Creates a new `SourceFile`.
-    /// If a file already exists in the `SourceMap` with the same ID, that file is returned
-    /// unmodified.
-    pub fn new_source_file(&self, filename: FileName, src: String) -> Lrc<SourceFile> {
-        self.try_new_source_file(filename, src)
-            .unwrap_or_else(|OffsetOverflowError| {
-                eprintln!("fatal error: rustc does not support files larger than 4GB");
-                errors::FatalError.raise()
-            })
-    }
-
-    fn try_new_source_file(
-        &self,
-        filename: FileName,
-        src: String
-    ) -> Result<Lrc<SourceFile>, OffsetOverflowError> {
-        let start_pos = self.next_start_pos();
-
-        // The path is used to determine the directory for loading submodules and
-        // include files, so it must be before remapping.
-        // Note that filename may not be a valid path, eg it may be `<anon>` etc,
-        // but this is okay because the directory determined by `path.pop()` will
-        // be empty, so the working directory will be used.
-        let unmapped_path = filename.clone();
-
-        let (filename, was_remapped) = match filename {
-            FileName::Real(filename) => {
-                let (filename, was_remapped) = self.path_mapping.map_prefix(filename);
-                (FileName::Real(filename), was_remapped)
-            },
-            other => (other, false),
-        };
-
-        let file_id = StableSourceFileId::new_from_pieces(&filename,
-                                                       was_remapped,
-                                                       Some(&unmapped_path));
-
-        let lrc_sf = match self.source_file_by_stable_id(file_id) {
-            Some(lrc_sf) => lrc_sf,
-            None => {
-                let source_file = Lrc::new(SourceFile::new(
-                    filename,
-                    was_remapped,
-                    unmapped_path,
-                    src,
-                    Pos::from_usize(start_pos),
-                )?);
-
-                let mut files = self.files.borrow_mut();
-
-                files.source_files.push(source_file.clone());
-                files.stable_id_to_source_file.insert(file_id, source_file.clone());
-
-                source_file
-            }
-        };
-        Ok(lrc_sf)
-    }
-
-    /// Allocates a new `SourceFile` representing a source file from an external
-    /// crate. The source code of such an "imported `SourceFile`" is not available,
-    /// but we still know enough to generate accurate debuginfo location
-    /// information for things inlined from other crates.
-    pub fn new_imported_source_file(
-        &self,
-        filename: FileName,
-        name_was_remapped: bool,
-        crate_of_origin: u32,
-        src_hash: u128,
-        name_hash: u128,
-        source_len: usize,
-        mut file_local_lines: Vec<BytePos>,
-        mut file_local_multibyte_chars: Vec<MultiByteChar>,
-        mut file_local_non_narrow_chars: Vec<NonNarrowChar>,
-        mut file_local_normalized_pos: Vec<NormalizedPos>,
-    ) -> Lrc<SourceFile> {
-        let start_pos = self.next_start_pos();
-
-        let end_pos = Pos::from_usize(start_pos + source_len);
-        let start_pos = Pos::from_usize(start_pos);
-
-        for pos in &mut file_local_lines {
-            *pos = *pos + start_pos;
-        }
-
-        for mbc in &mut file_local_multibyte_chars {
-            mbc.pos = mbc.pos + start_pos;
-        }
-
-        for swc in &mut file_local_non_narrow_chars {
-            *swc = *swc + start_pos;
-        }
-
-        for nc in &mut file_local_normalized_pos {
-            nc.pos = nc.pos + start_pos;
-        }
-
-        let source_file = Lrc::new(SourceFile {
-            name: filename,
-            name_was_remapped,
-            unmapped_path: None,
-            crate_of_origin,
-            src: None,
-            src_hash,
-            external_src: Lock::new(ExternalSource::AbsentOk),
-            start_pos,
-            end_pos,
-            lines: file_local_lines,
-            multibyte_chars: file_local_multibyte_chars,
-            non_narrow_chars: file_local_non_narrow_chars,
-            normalized_pos: file_local_normalized_pos,
-            name_hash,
-        });
-
-        let mut files = self.files.borrow_mut();
-
-        files.source_files.push(source_file.clone());
-        files.stable_id_to_source_file.insert(StableSourceFileId::new(&source_file),
-                                              source_file.clone());
-
-        source_file
-    }
-
-    pub fn mk_substr_filename(&self, sp: Span) -> String {
-        let pos = self.lookup_char_pos(sp.lo());
-        format!("<{}:{}:{}>",
-                 pos.file.name,
-                 pos.line,
-                 pos.col.to_usize() + 1)
-    }
-
-    // If there is a doctest offset, applies it to the line.
-    pub fn doctest_offset_line(&self, file: &FileName, orig: usize) -> usize {
-        return match file {
-            FileName::DocTest(_, offset) => {
-                return if *offset >= 0 {
-                    orig + *offset as usize
-                } else {
-                    orig - (-(*offset)) as usize
-                }
-            },
-            _ => orig
-        }
-    }
-
-    /// Looks up source information about a `BytePos`.
-    pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
-        let chpos = self.bytepos_to_file_charpos(pos);
-        match self.lookup_line(pos) {
-            Ok(SourceFileAndLine { sf: f, line: a }) => {
-                let line = a + 1; // Line numbers start at 1
-                let linebpos = f.lines[a];
-                let linechpos = self.bytepos_to_file_charpos(linebpos);
-                let col = chpos - linechpos;
-
-                let col_display = {
-                    let start_width_idx = f
-                        .non_narrow_chars
-                        .binary_search_by_key(&linebpos, |x| x.pos())
-                        .unwrap_or_else(|x| x);
-                    let end_width_idx = f
-                        .non_narrow_chars
-                        .binary_search_by_key(&pos, |x| x.pos())
-                        .unwrap_or_else(|x| x);
-                    let special_chars = end_width_idx - start_width_idx;
-                    let non_narrow: usize = f
-                        .non_narrow_chars[start_width_idx..end_width_idx]
-                        .into_iter()
-                        .map(|x| x.width())
-                        .sum();
-                    col.0 - special_chars + non_narrow
-                };
-                debug!("byte pos {:?} is on the line at byte pos {:?}",
-                       pos, linebpos);
-                debug!("char pos {:?} is on the line at char pos {:?}",
-                       chpos, linechpos);
-                debug!("byte is on line: {}", line);
-                assert!(chpos >= linechpos);
-                Loc {
-                    file: f,
-                    line,
-                    col,
-                    col_display,
-                }
-            }
-            Err(f) => {
-                let col_display = {
-                    let end_width_idx = f
-                        .non_narrow_chars
-                        .binary_search_by_key(&pos, |x| x.pos())
-                        .unwrap_or_else(|x| x);
-                    let non_narrow: usize = f
-                        .non_narrow_chars[0..end_width_idx]
-                        .into_iter()
-                        .map(|x| x.width())
-                        .sum();
-                    chpos.0 - end_width_idx + non_narrow
-                };
-                Loc {
-                    file: f,
-                    line: 0,
-                    col: chpos,
-                    col_display,
-                }
-            }
-        }
-    }
-
-    // If the corresponding `SourceFile` is empty, does not return a line number.
-    pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
-        let idx = self.lookup_source_file_idx(pos);
-
-        let f = (*self.files.borrow().source_files)[idx].clone();
-
-        match f.lookup_line(pos) {
-            Some(line) => Ok(SourceFileAndLine { sf: f, line }),
-            None => Err(f)
-        }
-    }
-
-    /// Returns `Some(span)`, a union of the LHS and RHS span. The LHS must precede the RHS. If
-    /// there are gaps between LHS and RHS, the resulting union will cross these gaps.
-    /// For this to work,
-    ///
-    ///    * the syntax contexts of both spans much match,
-    ///    * the LHS span needs to end on the same line the RHS span begins,
-    ///    * the LHS span must start at or before the RHS span.
-    pub fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
-        // Ensure we're at the same expansion ID.
-        if sp_lhs.ctxt() != sp_rhs.ctxt() {
-            return None;
-        }
-
-        let lhs_end = match self.lookup_line(sp_lhs.hi()) {
-            Ok(x) => x,
-            Err(_) => return None
-        };
-        let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
-            Ok(x) => x,
-            Err(_) => return None
-        };
-
-        // If we must cross lines to merge, don't merge.
-        if lhs_end.line != rhs_begin.line {
-            return None;
-        }
-
-        // Ensure these follow the expected order and that we don't overlap.
-        if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
-            Some(sp_lhs.to(sp_rhs))
-        } else {
-            None
-        }
-    }
-
-    pub fn span_to_string(&self, sp: Span) -> String {
-        if self.files.borrow().source_files.is_empty() && sp.is_dummy() {
-            return "no-location".to_string();
-        }
-
-        let lo = self.lookup_char_pos(sp.lo());
-        let hi = self.lookup_char_pos(sp.hi());
-        format!("{}:{}:{}: {}:{}",
-            lo.file.name,
-            lo.line,
-            lo.col.to_usize() + 1,
-            hi.line,
-            hi.col.to_usize() + 1,
-        )
-    }
-
-    pub fn span_to_filename(&self, sp: Span) -> FileName {
-        self.lookup_char_pos(sp.lo()).file.name.clone()
-    }
-
-    pub fn span_to_unmapped_path(&self, sp: Span) -> FileName {
-        self.lookup_char_pos(sp.lo()).file.unmapped_path.clone()
-            .expect("`SourceMap::span_to_unmapped_path` called for imported `SourceFile`?")
-    }
-
-    pub fn is_multiline(&self, sp: Span) -> bool {
-        let lo = self.lookup_char_pos(sp.lo());
-        let hi = self.lookup_char_pos(sp.hi());
-        lo.line != hi.line
-    }
-
-    pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
-        debug!("span_to_lines(sp={:?})", sp);
-
-        let lo = self.lookup_char_pos(sp.lo());
-        debug!("span_to_lines: lo={:?}", lo);
-        let hi = self.lookup_char_pos(sp.hi());
-        debug!("span_to_lines: hi={:?}", hi);
-
-        if lo.file.start_pos != hi.file.start_pos {
-            return Err(SpanLinesError::DistinctSources(DistinctSources {
-                begin: (lo.file.name.clone(), lo.file.start_pos),
-                end: (hi.file.name.clone(), hi.file.start_pos),
-            }));
-        }
-        assert!(hi.line >= lo.line);
-
-        let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
-
-        // The span starts partway through the first line,
-        // but after that it starts from offset 0.
-        let mut start_col = lo.col;
-
-        // For every line but the last, it extends from `start_col`
-        // and to the end of the line. Be careful because the line
-        // numbers in Loc are 1-based, so we subtract 1 to get 0-based
-        // lines.
-        for line_index in lo.line-1 .. hi.line-1 {
-            let line_len = lo.file.get_line(line_index)
-                                  .map(|s| s.chars().count())
-                                  .unwrap_or(0);
-            lines.push(LineInfo { line_index,
-                                  start_col,
-                                  end_col: CharPos::from_usize(line_len) });
-            start_col = CharPos::from_usize(0);
-        }
-
-        // For the last line, it extends from `start_col` to `hi.col`:
-        lines.push(LineInfo { line_index: hi.line - 1,
-                              start_col,
-                              end_col: hi.col });
-
-        Ok(FileLines {file: lo.file, lines})
-    }
-
-    /// Extracts the source surrounding the given `Span` using the `extract_source` function. The
-    /// extract function takes three arguments: a string slice containing the source, an index in
-    /// the slice for the beginning of the span and an index in the slice for the end of the span.
-    fn span_to_source<F>(&self, sp: Span, extract_source: F) -> Result<String, SpanSnippetError>
-        where F: Fn(&str, usize, usize) -> Result<String, SpanSnippetError>
-    {
-        let local_begin = self.lookup_byte_offset(sp.lo());
-        let local_end = self.lookup_byte_offset(sp.hi());
-
-        if local_begin.sf.start_pos != local_end.sf.start_pos {
-            return Err(SpanSnippetError::DistinctSources(DistinctSources {
-                begin: (local_begin.sf.name.clone(),
-                        local_begin.sf.start_pos),
-                end: (local_end.sf.name.clone(),
-                      local_end.sf.start_pos)
-            }));
-        } else {
-            self.ensure_source_file_source_present(local_begin.sf.clone());
-
-            let start_index = local_begin.pos.to_usize();
-            let end_index = local_end.pos.to_usize();
-            let source_len = (local_begin.sf.end_pos -
-                              local_begin.sf.start_pos).to_usize();
-
-            if start_index > end_index || end_index > source_len {
-                return Err(SpanSnippetError::MalformedForSourcemap(
-                    MalformedSourceMapPositions {
-                        name: local_begin.sf.name.clone(),
-                        source_len,
-                        begin_pos: local_begin.pos,
-                        end_pos: local_end.pos,
-                    }));
-            }
-
-            if let Some(ref src) = local_begin.sf.src {
-                return extract_source(src, start_index, end_index);
-            } else if let Some(src) = local_begin.sf.external_src.borrow().get_source() {
-                return extract_source(src, start_index, end_index);
-            } else {
-                return Err(SpanSnippetError::SourceNotAvailable {
-                    filename: local_begin.sf.name.clone()
-                });
-            }
-        }
-    }
-
-    /// Returns the source snippet as `String` corresponding to the given `Span`.
-    pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> {
-        self.span_to_source(sp, |src, start_index, end_index| src.get(start_index..end_index)
-            .map(|s| s.to_string())
-            .ok_or_else(|| SpanSnippetError::IllFormedSpan(sp)))
-    }
-
-    pub fn span_to_margin(&self, sp: Span) -> Option<usize> {
-        match self.span_to_prev_source(sp) {
-            Err(_) => None,
-            Ok(source) => source.split('\n').last().map(|last_line| {
-                last_line.len() - last_line.trim_start().len()
-            })
-        }
-    }
-
-    /// Returns the source snippet as `String` before the given `Span`.
-    pub fn span_to_prev_source(&self, sp: Span) -> Result<String, SpanSnippetError> {
-        self.span_to_source(sp, |src, start_index, _| src.get(..start_index)
-            .map(|s| s.to_string())
-            .ok_or_else(|| SpanSnippetError::IllFormedSpan(sp)))
-    }
-
-    /// Extends the given `Span` to just after the previous occurrence of `c`. Return the same span
-    /// if no character could be found or if an error occurred while retrieving the code snippet.
-    pub fn span_extend_to_prev_char(&self, sp: Span, c: char) -> Span {
-        if let Ok(prev_source) = self.span_to_prev_source(sp) {
-            let prev_source = prev_source.rsplit(c).nth(0).unwrap_or("").trim_start();
-            if !prev_source.is_empty() && !prev_source.contains('\n') {
-                return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
-            }
-        }
-
-        sp
-    }
-
-    /// Extends the given `Span` to just after the previous occurrence of `pat` when surrounded by
-    /// whitespace. Returns the same span if no character could be found or if an error occurred
-    /// while retrieving the code snippet.
-    pub fn span_extend_to_prev_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
-        // assure that the pattern is delimited, to avoid the following
-        //     fn my_fn()
-        //           ^^^^ returned span without the check
-        //     ---------- correct span
-        for ws in &[" ", "\t", "\n"] {
-            let pat = pat.to_owned() + ws;
-            if let Ok(prev_source) = self.span_to_prev_source(sp) {
-                let prev_source = prev_source.rsplit(&pat).nth(0).unwrap_or("").trim_start();
-                if !prev_source.is_empty() && (!prev_source.contains('\n') || accept_newlines) {
-                    return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
-                }
-            }
-        }
-
-        sp
-    }
-
-    /// Given a `Span`, tries to get a shorter span ending before the first occurrence of `char`
-    /// `c`.
-    pub fn span_until_char(&self, sp: Span, c: char) -> Span {
-        match self.span_to_snippet(sp) {
-            Ok(snippet) => {
-                let snippet = snippet.split(c).nth(0).unwrap_or("").trim_end();
-                if !snippet.is_empty() && !snippet.contains('\n') {
-                    sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
-                } else {
-                    sp
-                }
-            }
-            _ => sp,
-        }
-    }
-
-    /// Given a `Span`, tries to get a shorter span ending just after the first occurrence of `char`
-    /// `c`.
-    pub fn span_through_char(&self, sp: Span, c: char) -> Span {
-        if let Ok(snippet) = self.span_to_snippet(sp) {
-            if let Some(offset) = snippet.find(c) {
-                return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
-            }
-        }
-        sp
-    }
-
-    /// Given a `Span`, gets a new `Span` covering the first token and all its trailing whitespace
-    /// or the original `Span`.
-    ///
-    /// If `sp` points to `"let mut x"`, then a span pointing at `"let "` will be returned.
-    pub fn span_until_non_whitespace(&self, sp: Span) -> Span {
-        let mut whitespace_found = false;
-
-        self.span_take_while(sp, |c| {
-            if !whitespace_found && c.is_whitespace() {
-                whitespace_found = true;
-            }
-
-            if whitespace_found && !c.is_whitespace() {
-                false
-            } else {
-                true
-            }
-        })
-    }
-
-    /// Given a `Span`, gets a new `Span` covering the first token without its trailing whitespace
-    /// or the original `Span` in case of error.
-    ///
-    /// If `sp` points to `"let mut x"`, then a span pointing at `"let"` will be returned.
-    pub fn span_until_whitespace(&self, sp: Span) -> Span {
-        self.span_take_while(sp, |c| !c.is_whitespace())
-    }
-
-    /// Given a `Span`, gets a shorter one until `predicate` yields `false`.
-    pub fn span_take_while<P>(&self, sp: Span, predicate: P) -> Span
-        where P: for <'r> FnMut(&'r char) -> bool
-    {
-        if let Ok(snippet) = self.span_to_snippet(sp) {
-            let offset = snippet.chars()
-                .take_while(predicate)
-                .map(|c| c.len_utf8())
-                .sum::<usize>();
-
-            sp.with_hi(BytePos(sp.lo().0 + (offset as u32)))
-        } else {
-            sp
-        }
-    }
-
-    pub fn def_span(&self, sp: Span) -> Span {
-        self.span_until_char(sp, '{')
-    }
-
-    /// Returns a new span representing just the start point of this span.
-    pub fn start_point(&self, sp: Span) -> Span {
-        let pos = sp.lo().0;
-        let width = self.find_width_of_character_at_span(sp, false);
-        let corrected_start_position = pos.checked_add(width).unwrap_or(pos);
-        let end_point = BytePos(cmp::max(corrected_start_position, sp.lo().0));
-        sp.with_hi(end_point)
-    }
-
-    /// Returns a new span representing just the end point of this span.
-    pub fn end_point(&self, sp: Span) -> Span {
-        let pos = sp.hi().0;
-
-        let width = self.find_width_of_character_at_span(sp, false);
-        let corrected_end_position = pos.checked_sub(width).unwrap_or(pos);
-
-        let end_point = BytePos(cmp::max(corrected_end_position, sp.lo().0));
-        sp.with_lo(end_point)
-    }
-
-    /// Returns a new span representing the next character after the end-point of this span.
-    pub fn next_point(&self, sp: Span) -> Span {
-        let start_of_next_point = sp.hi().0;
-
-        let width = self.find_width_of_character_at_span(sp, true);
-        // If the width is 1, then the next span should point to the same `lo` and `hi`. However,
-        // in the case of a multibyte character, where the width != 1, the next span should
-        // span multiple bytes to include the whole character.
-        let end_of_next_point = start_of_next_point.checked_add(
-            width - 1).unwrap_or(start_of_next_point);
-
-        let end_of_next_point = BytePos(cmp::max(sp.lo().0 + 1, end_of_next_point));
-        Span::new(BytePos(start_of_next_point), end_of_next_point, sp.ctxt())
-    }
-
-    /// Finds the width of a character, either before or after the provided span.
-    fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
-        let sp = sp.data();
-        if sp.lo == sp.hi {
-            debug!("find_width_of_character_at_span: early return empty span");
-            return 1;
-        }
-
-        let local_begin = self.lookup_byte_offset(sp.lo);
-        let local_end = self.lookup_byte_offset(sp.hi);
-        debug!("find_width_of_character_at_span: local_begin=`{:?}`, local_end=`{:?}`",
-               local_begin, local_end);
-
-        if local_begin.sf.start_pos != local_end.sf.start_pos {
-            debug!("find_width_of_character_at_span: begin and end are in different files");
-            return 1;
-        }
-
-        let start_index = local_begin.pos.to_usize();
-        let end_index = local_end.pos.to_usize();
-        debug!("find_width_of_character_at_span: start_index=`{:?}`, end_index=`{:?}`",
-               start_index, end_index);
-
-        // Disregard indexes that are at the start or end of their spans, they can't fit bigger
-        // characters.
-        if (!forwards && end_index == usize::min_value()) ||
-            (forwards && start_index == usize::max_value()) {
-            debug!("find_width_of_character_at_span: start or end of span, cannot be multibyte");
-            return 1;
-        }
-
-        let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
-        debug!("find_width_of_character_at_span: source_len=`{:?}`", source_len);
-        // Ensure indexes are also not malformed.
-        if start_index > end_index || end_index > source_len {
-            debug!("find_width_of_character_at_span: source indexes are malformed");
-            return 1;
-        }
-
-        let src = local_begin.sf.external_src.borrow();
-
-        // We need to extend the snippet to the end of the src rather than to end_index so when
-        // searching forwards for boundaries we've got somewhere to search.
-        let snippet = if let Some(ref src) = local_begin.sf.src {
-            let len = src.len();
-            (&src[start_index..len])
-        } else if let Some(src) = src.get_source() {
-            let len = src.len();
-            (&src[start_index..len])
-        } else {
-            return 1;
-        };
-        debug!("find_width_of_character_at_span: snippet=`{:?}`", snippet);
-
-        let mut target = if forwards { end_index + 1 } else { end_index - 1 };
-        debug!("find_width_of_character_at_span: initial target=`{:?}`", target);
-
-        while !snippet.is_char_boundary(target - start_index) && target < source_len {
-            target = if forwards {
-                target + 1
-            } else {
-                match target.checked_sub(1) {
-                    Some(target) => target,
-                    None => {
-                        break;
-                    }
-                }
-            };
-            debug!("find_width_of_character_at_span: target=`{:?}`", target);
-        }
-        debug!("find_width_of_character_at_span: final target=`{:?}`", target);
-
-        if forwards {
-            (target - end_index) as u32
-        } else {
-            (end_index - target) as u32
-        }
-    }
-
-    pub fn get_source_file(&self, filename: &FileName) -> Option<Lrc<SourceFile>> {
-        for sf in self.files.borrow().source_files.iter() {
-            if *filename == sf.name {
-                return Some(sf.clone());
-            }
-        }
-        None
-    }
-
-    /// For a global `BytePos`, computes the local offset within the containing `SourceFile`.
-    pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
-        let idx = self.lookup_source_file_idx(bpos);
-        let sf = (*self.files.borrow().source_files)[idx].clone();
-        let offset = bpos - sf.start_pos;
-        SourceFileAndBytePos {sf, pos: offset}
-    }
-
-    /// Converts an absolute `BytePos` to a `CharPos` relative to the `SourceFile`.
-    pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
-        let idx = self.lookup_source_file_idx(bpos);
-        let map = &(*self.files.borrow().source_files)[idx];
-
-        // The number of extra bytes due to multibyte chars in the `SourceFile`.
-        let mut total_extra_bytes = 0;
-
-        for mbc in map.multibyte_chars.iter() {
-            debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
-            if mbc.pos < bpos {
-                // Every character is at least one byte, so we only
-                // count the actual extra bytes.
-                total_extra_bytes += mbc.bytes as u32 - 1;
-                // We should never see a byte position in the middle of a
-                // character.
-                assert!(bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32);
-            } else {
-                break;
-            }
-        }
-
-        assert!(map.start_pos.to_u32() + total_extra_bytes <= bpos.to_u32());
-        CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes as usize)
-    }
-
-    // Returns the index of the `SourceFile` (in `self.files`) that contains `pos`.
-    pub fn lookup_source_file_idx(&self, pos: BytePos) -> usize {
-        self.files.borrow().source_files.binary_search_by_key(&pos, |key| key.start_pos)
-            .unwrap_or_else(|p| p - 1)
-    }
-
-    pub fn count_lines(&self) -> usize {
-        self.files().iter().fold(0, |a, f| a + f.count_lines())
-    }
-
-
-    pub fn generate_fn_name_span(&self, span: Span) -> Option<Span> {
-        let prev_span = self.span_extend_to_prev_str(span, "fn", true);
-        self.span_to_snippet(prev_span).map(|snippet| {
-            let len = snippet.find(|c: char| !c.is_alphanumeric() && c != '_')
-                .expect("no label after fn");
-            prev_span.with_hi(BytePos(prev_span.lo().0 + len as u32))
-        }).ok()
-    }
-
-    /// Takes the span of a type parameter in a function signature and try to generate a span for
-    /// the function name (with generics) and a new snippet for this span with the pointed type
-    /// parameter as a new local type parameter.
-    ///
-    /// For instance:
-    /// ```rust,ignore (pseudo-Rust)
-    /// // Given span
-    /// fn my_function(param: T)
-    /// //                    ^ Original span
-    ///
-    /// // Result
-    /// fn my_function(param: T)
-    /// // ^^^^^^^^^^^ Generated span with snippet `my_function<T>`
-    /// ```
-    ///
-    /// Attention: The method used is very fragile since it essentially duplicates the work of the
-    /// parser. If you need to use this function or something similar, please consider updating the
-    /// `SourceMap` functions and this function to something more robust.
-    pub fn generate_local_type_param_snippet(&self, span: Span) -> Option<(Span, String)> {
-        // Try to extend the span to the previous "fn" keyword to retrieve the function
-        // signature.
-        let sugg_span = self.span_extend_to_prev_str(span, "fn", false);
-        if sugg_span != span {
-            if let Ok(snippet) = self.span_to_snippet(sugg_span) {
-                // Consume the function name.
-                let mut offset = snippet.find(|c: char| !c.is_alphanumeric() && c != '_')
-                    .expect("no label after fn");
-
-                // Consume the generics part of the function signature.
-                let mut bracket_counter = 0;
-                let mut last_char = None;
-                for c in snippet[offset..].chars() {
-                    match c {
-                        '<' => bracket_counter += 1,
-                        '>' => bracket_counter -= 1,
-                        '(' => if bracket_counter == 0 { break; }
-                        _ => {}
-                    }
-                    offset += c.len_utf8();
-                    last_char = Some(c);
-                }
-
-                // Adjust the suggestion span to encompass the function name with its generics.
-                let sugg_span = sugg_span.with_hi(BytePos(sugg_span.lo().0 + offset as u32));
-
-                // Prepare the new suggested snippet to append the type parameter that triggered
-                // the error in the generics of the function signature.
-                let mut new_snippet = if last_char == Some('>') {
-                    format!("{}, ", &snippet[..(offset - '>'.len_utf8())])
-                } else {
-                    format!("{}<", &snippet[..offset])
-                };
-                new_snippet.push_str(
-                    &self.span_to_snippet(span).unwrap_or_else(|_| "T".to_string()));
-                new_snippet.push('>');
-
-                return Some((sugg_span, new_snippet));
-            }
-        }
-
-        None
-    }
-}
-
-impl SourceMapper for SourceMap {
-    fn lookup_char_pos(&self, pos: BytePos) -> Loc {
-        self.lookup_char_pos(pos)
-    }
-    fn span_to_lines(&self, sp: Span) -> FileLinesResult {
-        self.span_to_lines(sp)
-    }
-    fn span_to_string(&self, sp: Span) -> String {
-        self.span_to_string(sp)
-    }
-    fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> {
-        self.span_to_snippet(sp)
-    }
-    fn span_to_filename(&self, sp: Span) -> FileName {
-        self.span_to_filename(sp)
-    }
-    fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
-        self.merge_spans(sp_lhs, sp_rhs)
-    }
-    fn call_span_if_macro(&self, sp: Span) -> Span {
-        if self.span_to_filename(sp.clone()).is_macros() {
-            let v = sp.macro_backtrace();
-            if let Some(use_site) = v.last() {
-                return use_site.call_site;
-            }
-        }
-        sp
-    }
-    fn ensure_source_file_source_present(&self, source_file: Lrc<SourceFile>) -> bool {
-        source_file.add_external_src(
-            || match source_file.name {
-                FileName::Real(ref name) => self.file_loader.read_file(name).ok(),
-                _ => None,
-            }
-        )
-    }
-    fn doctest_offset_line(&self, file: &FileName, line: usize) -> usize {
-        self.doctest_offset_line(file, line)
-    }
-}
-
-#[derive(Clone)]
-pub struct FilePathMapping {
-    mapping: Vec<(PathBuf, PathBuf)>,
-}
-
-impl FilePathMapping {
-    pub fn empty() -> FilePathMapping {
-        FilePathMapping {
-            mapping: vec![]
-        }
-    }
-
-    pub fn new(mapping: Vec<(PathBuf, PathBuf)>) -> FilePathMapping {
-        FilePathMapping {
-            mapping,
-        }
-    }
-
-    /// Applies any path prefix substitution as defined by the mapping.
-    /// The return value is the remapped path and a boolean indicating whether
-    /// the path was affected by the mapping.
-    pub fn map_prefix(&self, path: PathBuf) -> (PathBuf, bool) {
-        // NOTE: We are iterating over the mapping entries from last to first
-        //       because entries specified later on the command line should
-        //       take precedence.
-        for &(ref from, ref to) in self.mapping.iter().rev() {
-            if let Ok(rest) = path.strip_prefix(from) {
-                return (to.join(rest), true);
-            }
-        }
-
-        (path, false)
-    }
-}
diff --git a/src/libsyntax/source_map/tests.rs b/src/libsyntax/source_map/tests.rs
deleted file mode 100644 (file)
index 1525433..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-use super::*;
-
-use rustc_data_structures::sync::Lrc;
-
-fn init_source_map() -> SourceMap {
-    let sm = SourceMap::new(FilePathMapping::empty());
-    sm.new_source_file(
-        PathBuf::from("blork.rs").into(),
-        "first line.\nsecond line".to_string(),
-    );
-    sm.new_source_file(
-        PathBuf::from("empty.rs").into(),
-        String::new(),
-    );
-    sm.new_source_file(
-        PathBuf::from("blork2.rs").into(),
-        "first line.\nsecond line".to_string(),
-    );
-    sm
-}
-
-/// Tests `lookup_byte_offset`.
-#[test]
-fn t3() {
-    let sm = init_source_map();
-
-    let srcfbp1 = sm.lookup_byte_offset(BytePos(23));
-    assert_eq!(srcfbp1.sf.name, PathBuf::from("blork.rs").into());
-    assert_eq!(srcfbp1.pos, BytePos(23));
-
-    let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
-    assert_eq!(srcfbp1.sf.name, PathBuf::from("empty.rs").into());
-    assert_eq!(srcfbp1.pos, BytePos(0));
-
-    let srcfbp2 = sm.lookup_byte_offset(BytePos(25));
-    assert_eq!(srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
-    assert_eq!(srcfbp2.pos, BytePos(0));
-}
-
-/// Tests `bytepos_to_file_charpos`.
-#[test]
-fn t4() {
-    let sm = init_source_map();
-
-    let cp1 = sm.bytepos_to_file_charpos(BytePos(22));
-    assert_eq!(cp1, CharPos(22));
-
-    let cp2 = sm.bytepos_to_file_charpos(BytePos(25));
-    assert_eq!(cp2, CharPos(0));
-}
-
-/// Tests zero-length `SourceFile`s.
-#[test]
-fn t5() {
-    let sm = init_source_map();
-
-    let loc1 = sm.lookup_char_pos(BytePos(22));
-    assert_eq!(loc1.file.name, PathBuf::from("blork.rs").into());
-    assert_eq!(loc1.line, 2);
-    assert_eq!(loc1.col, CharPos(10));
-
-    let loc2 = sm.lookup_char_pos(BytePos(25));
-    assert_eq!(loc2.file.name, PathBuf::from("blork2.rs").into());
-    assert_eq!(loc2.line, 1);
-    assert_eq!(loc2.col, CharPos(0));
-}
-
-fn init_source_map_mbc() -> SourceMap {
-    let sm = SourceMap::new(FilePathMapping::empty());
-    // "€" is a three-byte UTF8 char.
-    sm.new_source_file(PathBuf::from("blork.rs").into(),
-                    "fir€st €€€€ line.\nsecond line".to_string());
-    sm.new_source_file(PathBuf::from("blork2.rs").into(),
-                    "first line€€.\n€ second line".to_string());
-    sm
-}
-
-/// Tests `bytepos_to_file_charpos` in the presence of multi-byte chars.
-#[test]
-fn t6() {
-    let sm = init_source_map_mbc();
-
-    let cp1 = sm.bytepos_to_file_charpos(BytePos(3));
-    assert_eq!(cp1, CharPos(3));
-
-    let cp2 = sm.bytepos_to_file_charpos(BytePos(6));
-    assert_eq!(cp2, CharPos(4));
-
-    let cp3 = sm.bytepos_to_file_charpos(BytePos(56));
-    assert_eq!(cp3, CharPos(12));
-
-    let cp4 = sm.bytepos_to_file_charpos(BytePos(61));
-    assert_eq!(cp4, CharPos(15));
-}
-
-/// Test `span_to_lines` for a span ending at the end of a `SourceFile`.
-#[test]
-fn t7() {
-    let sm = init_source_map();
-    let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
-    let file_lines = sm.span_to_lines(span).unwrap();
-
-    assert_eq!(file_lines.file.name, PathBuf::from("blork.rs").into());
-    assert_eq!(file_lines.lines.len(), 1);
-    assert_eq!(file_lines.lines[0].line_index, 1);
-}
-
-/// Given a string like " ~~~~~~~~~~~~ ", produces a span
-/// converting that range. The idea is that the string has the same
-/// length as the input, and we uncover the byte positions. Note
-/// that this can span lines and so on.
-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('~').map(|x|x as u32).unwrap_or(left_index);
-    Span::with_root_ctxt(BytePos(left_index), BytePos(right_index + 1))
-}
-
-/// Tests `span_to_snippet` and `span_to_lines` for a span converting 3
-/// lines in the middle of a file.
-#[test]
-fn span_to_snippet_and_lines_spanning_multiple_lines() {
-    let sm = SourceMap::new(FilePathMapping::empty());
-    let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
-    let selection = "     \n    ~~\n~~~\n~~~~~     \n   \n";
-    sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_string());
-    let span = span_from_selection(inputtext, selection);
-
-    // Check that we are extracting the text we thought we were extracting.
-    assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
-
-    // Check that span_to_lines gives us the complete result with the lines/cols we expected.
-    let lines = sm.span_to_lines(span).unwrap();
-    let expected = vec![
-        LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) },
-        LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) },
-        LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) }
-        ];
-    assert_eq!(lines.lines, expected);
-}
-
-/// Test span_to_snippet for a span ending at the end of a `SourceFile`.
-#[test]
-fn t8() {
-    let sm = init_source_map();
-    let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
-    let snippet = sm.span_to_snippet(span);
-
-    assert_eq!(snippet, Ok("second line".to_string()));
-}
-
-/// Test `span_to_str` for a span ending at the end of a `SourceFile`.
-#[test]
-fn t9() {
-    let sm = init_source_map();
-    let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
-    let sstr =  sm.span_to_string(span);
-
-    assert_eq!(sstr, "blork.rs:2:1: 2:12");
-}
-
-/// Tests failing to merge two spans on different lines.
-#[test]
-fn span_merging_fail() {
-    let sm = SourceMap::new(FilePathMapping::empty());
-    let inputtext  = "bbbb BB\ncc CCC\n";
-    let selection1 = "     ~~\n      \n";
-    let selection2 = "       \n   ~~~\n";
-    sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_owned());
-    let span1 = span_from_selection(inputtext, selection1);
-    let span2 = span_from_selection(inputtext, selection2);
-
-    assert!(sm.merge_spans(span1, span2).is_none());
-}
-
-/// Returns the span corresponding to the `n`th occurrence of `substring` in `source_text`.
-trait SourceMapExtension {
-    fn span_substr(
-        &self,
-        file: &Lrc<SourceFile>,
-        source_text: &str,
-        substring: &str,
-        n: usize,
-    ) -> Span;
-}
-
-impl SourceMapExtension for SourceMap {
-    fn span_substr(
-        &self,
-        file: &Lrc<SourceFile>,
-        source_text: &str,
-        substring: &str,
-        n: usize,
-    ) -> Span {
-        println!(
-            "span_substr(file={:?}/{:?}, substring={:?}, n={})",
-            file.name, file.start_pos, substring, n
-        );
-        let mut i = 0;
-        let mut hi = 0;
-        loop {
-            let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
-                panic!(
-                    "source_text `{}` does not have {} occurrences of `{}`, only {}",
-                    source_text, n, substring, i
-                );
-            });
-            let lo = hi + offset;
-            hi = lo + substring.len();
-            if i == n {
-                let span = Span::with_root_ctxt(
-                    BytePos(lo as u32 + file.start_pos.0),
-                    BytePos(hi as u32 + file.start_pos.0),
-                );
-                assert_eq!(&self.span_to_snippet(span).unwrap()[..], substring);
-                return span;
-            }
-            i += 1;
-        }
-    }
-}
index 378f7a955a36fb6edd0ae80ca3cea99f18e8688f..2cac76085d297c645a864c44f54e4c05358f1a2b 100644 (file)
@@ -18,3 +18,4 @@ arena = { path = "../libarena" }
 scoped-tls = "1.0"
 unicode-width = "0.1.4"
 cfg-if = "0.1.2"
+log = "0.4"
diff --git a/src/libsyntax_pos/fatal_error.rs b/src/libsyntax_pos/fatal_error.rs
new file mode 100644 (file)
index 0000000..cf7c677
--- /dev/null
@@ -0,0 +1,30 @@
+/// Used as a return value to signify a fatal error occurred. (It is also
+/// used as the argument to panic at the moment, but that will eventually
+/// not be true.)
+#[derive(Copy, Clone, Debug)]
+#[must_use]
+pub struct FatalError;
+
+pub struct FatalErrorMarker;
+
+// Don't implement Send on FatalError. This makes it impossible to panic!(FatalError).
+// We don't want to invoke the panic handler and print a backtrace for fatal errors.
+impl !Send for FatalError {}
+
+impl FatalError {
+    pub fn raise(self) -> ! {
+        std::panic::resume_unwind(Box::new(FatalErrorMarker))
+    }
+}
+
+impl std::fmt::Display for FatalError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "parser fatal error")
+    }
+}
+
+impl std::error::Error for FatalError {
+    fn description(&self) -> &str {
+        "The parser has encountered a fatal error"
+    }
+}
index a762d8af49a21149d10c03e7bfc7dae70c7081d4..b88d6dbc3f3790434d0168bee1c99c2ae2a928f2 100644 (file)
@@ -16,6 +16,8 @@
 
 use rustc_serialize::{Encodable, Decodable, Encoder, Decoder};
 
+pub mod source_map;
+
 pub mod edition;
 use edition::Edition;
 pub mod hygiene;
@@ -29,6 +31,7 @@
 pub use symbol::{Symbol, sym};
 
 mod analyze_source_file;
+pub mod fatal_error;
 
 use rustc_data_structures::stable_hasher::StableHasher;
 use rustc_data_structures::sync::{Lrc, Lock};
diff --git a/src/libsyntax_pos/source_map.rs b/src/libsyntax_pos/source_map.rs
new file mode 100644 (file)
index 0000000..77d9807
--- /dev/null
@@ -0,0 +1,1009 @@
+//! The `SourceMap` tracks all the source code used within a single crate, mapping
+//! from integer byte positions to the original source code location. Each bit
+//! of source parsed during crate parsing (typically files, in-memory strings,
+//! or various bits of macro expansion) cover a continuous range of bytes in the
+//! `SourceMap` and are represented by `SourceFile`s. Byte positions are stored in
+//! `Span` and used pervasively in the compiler. They are absolute positions
+//! within the `SourceMap`, which upon request can be converted to line and column
+//! information, source code snippets, etc.
+
+pub use crate::*;
+pub use crate::hygiene::{ExpnKind, ExpnData};
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_data_structures::stable_hasher::StableHasher;
+use rustc_data_structures::sync::{Lrc, Lock, LockGuard, MappedLockGuard};
+use std::cmp;
+use std::hash::Hash;
+use std::path::{Path, PathBuf};
+
+use std::env;
+use std::fs;
+use std::io;
+use log::debug;
+
+#[cfg(test)]
+mod tests;
+
+/// Returns the span itself if it doesn't come from a macro expansion,
+/// otherwise return the call site span up to the `enclosing_sp` by
+/// following the `expn_data` chain.
+pub fn original_sp(sp: Span, enclosing_sp: Span) -> Span {
+    let expn_data1 = sp.ctxt().outer_expn_data();
+    let expn_data2 = enclosing_sp.ctxt().outer_expn_data();
+    if expn_data1.is_root() ||
+       !expn_data2.is_root() && expn_data1.call_site == expn_data2.call_site {
+        sp
+    } else {
+        original_sp(expn_data1.call_site, enclosing_sp)
+    }
+}
+
+#[derive(Clone, RustcEncodable, RustcDecodable, Debug, Copy)]
+pub struct Spanned<T> {
+    pub node: T,
+    pub span: Span,
+}
+
+pub fn respan<T>(sp: Span, t: T) -> Spanned<T> {
+    Spanned {node: t, span: sp}
+}
+
+pub fn dummy_spanned<T>(t: T) -> Spanned<T> {
+    respan(DUMMY_SP, t)
+}
+
+// _____________________________________________________________________________
+// SourceFile, MultiByteChar, FileName, FileLines
+//
+
+/// An abstraction over the fs operations used by the Parser.
+pub trait FileLoader {
+    /// Query the existence of a file.
+    fn file_exists(&self, path: &Path) -> bool;
+
+    /// Returns an absolute path to a file, if possible.
+    fn abs_path(&self, path: &Path) -> Option<PathBuf>;
+
+    /// Read the contents of an UTF-8 file into memory.
+    fn read_file(&self, path: &Path) -> io::Result<String>;
+}
+
+/// A FileLoader that uses std::fs to load real files.
+pub struct RealFileLoader;
+
+impl FileLoader for RealFileLoader {
+    fn file_exists(&self, path: &Path) -> bool {
+        fs::metadata(path).is_ok()
+    }
+
+    fn abs_path(&self, path: &Path) -> Option<PathBuf> {
+        if path.is_absolute() {
+            Some(path.to_path_buf())
+        } else {
+            env::current_dir()
+                .ok()
+                .map(|cwd| cwd.join(path))
+        }
+    }
+
+    fn read_file(&self, path: &Path) -> io::Result<String> {
+        fs::read_to_string(path)
+    }
+}
+
+// This is a `SourceFile` identifier that is used to correlate `SourceFile`s between
+// subsequent compilation sessions (which is something we need to do during
+// incremental compilation).
+#[derive(Copy, Clone, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable, Debug)]
+pub struct StableSourceFileId(u128);
+
+impl StableSourceFileId {
+    pub fn new(source_file: &SourceFile) -> StableSourceFileId {
+        StableSourceFileId::new_from_pieces(&source_file.name,
+                                            source_file.name_was_remapped,
+                                            source_file.unmapped_path.as_ref())
+    }
+
+    pub fn new_from_pieces(name: &FileName,
+                           name_was_remapped: bool,
+                           unmapped_path: Option<&FileName>) -> StableSourceFileId {
+        let mut hasher = StableHasher::new();
+
+        name.hash(&mut hasher);
+        name_was_remapped.hash(&mut hasher);
+        unmapped_path.hash(&mut hasher);
+
+        StableSourceFileId(hasher.finish())
+    }
+}
+
+// _____________________________________________________________________________
+// SourceMap
+//
+
+#[derive(Default)]
+pub(super) struct SourceMapFiles {
+    source_files: Vec<Lrc<SourceFile>>,
+    stable_id_to_source_file: FxHashMap<StableSourceFileId, Lrc<SourceFile>>
+}
+
+pub struct SourceMap {
+    files: Lock<SourceMapFiles>,
+    file_loader: Box<dyn FileLoader + Sync + Send>,
+    // This is used to apply the file path remapping as specified via
+    // `--remap-path-prefix` to all `SourceFile`s allocated within this `SourceMap`.
+    path_mapping: FilePathMapping,
+}
+
+impl SourceMap {
+    pub fn new(path_mapping: FilePathMapping) -> SourceMap {
+        SourceMap {
+            files: Default::default(),
+            file_loader: Box::new(RealFileLoader),
+            path_mapping,
+        }
+    }
+
+    pub fn with_file_loader(file_loader: Box<dyn FileLoader + Sync + Send>,
+                            path_mapping: FilePathMapping)
+                            -> SourceMap {
+        SourceMap {
+            files: Default::default(),
+            file_loader,
+            path_mapping,
+        }
+    }
+
+    pub fn path_mapping(&self) -> &FilePathMapping {
+        &self.path_mapping
+    }
+
+    pub fn file_exists(&self, path: &Path) -> bool {
+        self.file_loader.file_exists(path)
+    }
+
+    pub fn load_file(&self, path: &Path) -> io::Result<Lrc<SourceFile>> {
+        let src = self.file_loader.read_file(path)?;
+        let filename = path.to_owned().into();
+        Ok(self.new_source_file(filename, src))
+    }
+
+    /// Loads source file as a binary blob.
+    ///
+    /// Unlike `load_file`, guarantees that no normalization like BOM-removal
+    /// takes place.
+    pub fn load_binary_file(&self, path: &Path) -> io::Result<Vec<u8>> {
+        // Ideally, this should use `self.file_loader`, but it can't
+        // deal with binary files yet.
+        let bytes = fs::read(path)?;
+
+        // We need to add file to the `SourceMap`, so that it is present
+        // in dep-info. There's also an edge case that file might be both
+        // loaded as a binary via `include_bytes!` and as proper `SourceFile`
+        // via `mod`, so we try to use real file contents and not just an
+        // empty string.
+        let text = std::str::from_utf8(&bytes).unwrap_or("")
+            .to_string();
+        self.new_source_file(path.to_owned().into(), text);
+        Ok(bytes)
+    }
+
+    pub fn files(&self) -> MappedLockGuard<'_, Vec<Lrc<SourceFile>>> {
+        LockGuard::map(self.files.borrow(), |files| &mut files.source_files)
+    }
+
+    pub fn source_file_by_stable_id(&self, stable_id: StableSourceFileId) ->
+    Option<Lrc<SourceFile>> {
+        self.files.borrow().stable_id_to_source_file.get(&stable_id).map(|sf| sf.clone())
+    }
+
+    fn next_start_pos(&self) -> usize {
+        match self.files.borrow().source_files.last() {
+            None => 0,
+            // Add one so there is some space between files. This lets us distinguish
+            // positions in the `SourceMap`, even in the presence of zero-length files.
+            Some(last) => last.end_pos.to_usize() + 1,
+        }
+    }
+
+    /// Creates a new `SourceFile`.
+    /// If a file already exists in the `SourceMap` with the same ID, that file is returned
+    /// unmodified.
+    pub fn new_source_file(&self, filename: FileName, src: String) -> Lrc<SourceFile> {
+        self.try_new_source_file(filename, src)
+            .unwrap_or_else(|OffsetOverflowError| {
+                eprintln!("fatal error: rustc does not support files larger than 4GB");
+                crate::fatal_error::FatalError.raise()
+            })
+    }
+
+    fn try_new_source_file(
+        &self,
+        filename: FileName,
+        src: String
+    ) -> Result<Lrc<SourceFile>, OffsetOverflowError> {
+        let start_pos = self.next_start_pos();
+
+        // The path is used to determine the directory for loading submodules and
+        // include files, so it must be before remapping.
+        // Note that filename may not be a valid path, eg it may be `<anon>` etc,
+        // but this is okay because the directory determined by `path.pop()` will
+        // be empty, so the working directory will be used.
+        let unmapped_path = filename.clone();
+
+        let (filename, was_remapped) = match filename {
+            FileName::Real(filename) => {
+                let (filename, was_remapped) = self.path_mapping.map_prefix(filename);
+                (FileName::Real(filename), was_remapped)
+            },
+            other => (other, false),
+        };
+
+        let file_id = StableSourceFileId::new_from_pieces(&filename,
+                                                       was_remapped,
+                                                       Some(&unmapped_path));
+
+        let lrc_sf = match self.source_file_by_stable_id(file_id) {
+            Some(lrc_sf) => lrc_sf,
+            None => {
+                let source_file = Lrc::new(SourceFile::new(
+                    filename,
+                    was_remapped,
+                    unmapped_path,
+                    src,
+                    Pos::from_usize(start_pos),
+                )?);
+
+                let mut files = self.files.borrow_mut();
+
+                files.source_files.push(source_file.clone());
+                files.stable_id_to_source_file.insert(file_id, source_file.clone());
+
+                source_file
+            }
+        };
+        Ok(lrc_sf)
+    }
+
+    /// Allocates a new `SourceFile` representing a source file from an external
+    /// crate. The source code of such an "imported `SourceFile`" is not available,
+    /// but we still know enough to generate accurate debuginfo location
+    /// information for things inlined from other crates.
+    pub fn new_imported_source_file(
+        &self,
+        filename: FileName,
+        name_was_remapped: bool,
+        crate_of_origin: u32,
+        src_hash: u128,
+        name_hash: u128,
+        source_len: usize,
+        mut file_local_lines: Vec<BytePos>,
+        mut file_local_multibyte_chars: Vec<MultiByteChar>,
+        mut file_local_non_narrow_chars: Vec<NonNarrowChar>,
+        mut file_local_normalized_pos: Vec<NormalizedPos>,
+    ) -> Lrc<SourceFile> {
+        let start_pos = self.next_start_pos();
+
+        let end_pos = Pos::from_usize(start_pos + source_len);
+        let start_pos = Pos::from_usize(start_pos);
+
+        for pos in &mut file_local_lines {
+            *pos = *pos + start_pos;
+        }
+
+        for mbc in &mut file_local_multibyte_chars {
+            mbc.pos = mbc.pos + start_pos;
+        }
+
+        for swc in &mut file_local_non_narrow_chars {
+            *swc = *swc + start_pos;
+        }
+
+        for nc in &mut file_local_normalized_pos {
+            nc.pos = nc.pos + start_pos;
+        }
+
+        let source_file = Lrc::new(SourceFile {
+            name: filename,
+            name_was_remapped,
+            unmapped_path: None,
+            crate_of_origin,
+            src: None,
+            src_hash,
+            external_src: Lock::new(ExternalSource::AbsentOk),
+            start_pos,
+            end_pos,
+            lines: file_local_lines,
+            multibyte_chars: file_local_multibyte_chars,
+            non_narrow_chars: file_local_non_narrow_chars,
+            normalized_pos: file_local_normalized_pos,
+            name_hash,
+        });
+
+        let mut files = self.files.borrow_mut();
+
+        files.source_files.push(source_file.clone());
+        files.stable_id_to_source_file.insert(StableSourceFileId::new(&source_file),
+                                              source_file.clone());
+
+        source_file
+    }
+
+    pub fn mk_substr_filename(&self, sp: Span) -> String {
+        let pos = self.lookup_char_pos(sp.lo());
+        format!("<{}:{}:{}>",
+                 pos.file.name,
+                 pos.line,
+                 pos.col.to_usize() + 1)
+    }
+
+    // If there is a doctest offset, applies it to the line.
+    pub fn doctest_offset_line(&self, file: &FileName, orig: usize) -> usize {
+        return match file {
+            FileName::DocTest(_, offset) => {
+                return if *offset >= 0 {
+                    orig + *offset as usize
+                } else {
+                    orig - (-(*offset)) as usize
+                }
+            },
+            _ => orig
+        }
+    }
+
+    /// Looks up source information about a `BytePos`.
+    pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
+        let chpos = self.bytepos_to_file_charpos(pos);
+        match self.lookup_line(pos) {
+            Ok(SourceFileAndLine { sf: f, line: a }) => {
+                let line = a + 1; // Line numbers start at 1
+                let linebpos = f.lines[a];
+                let linechpos = self.bytepos_to_file_charpos(linebpos);
+                let col = chpos - linechpos;
+
+                let col_display = {
+                    let start_width_idx = f
+                        .non_narrow_chars
+                        .binary_search_by_key(&linebpos, |x| x.pos())
+                        .unwrap_or_else(|x| x);
+                    let end_width_idx = f
+                        .non_narrow_chars
+                        .binary_search_by_key(&pos, |x| x.pos())
+                        .unwrap_or_else(|x| x);
+                    let special_chars = end_width_idx - start_width_idx;
+                    let non_narrow: usize = f
+                        .non_narrow_chars[start_width_idx..end_width_idx]
+                        .into_iter()
+                        .map(|x| x.width())
+                        .sum();
+                    col.0 - special_chars + non_narrow
+                };
+                debug!("byte pos {:?} is on the line at byte pos {:?}",
+                       pos, linebpos);
+                debug!("char pos {:?} is on the line at char pos {:?}",
+                       chpos, linechpos);
+                debug!("byte is on line: {}", line);
+                assert!(chpos >= linechpos);
+                Loc {
+                    file: f,
+                    line,
+                    col,
+                    col_display,
+                }
+            }
+            Err(f) => {
+                let col_display = {
+                    let end_width_idx = f
+                        .non_narrow_chars
+                        .binary_search_by_key(&pos, |x| x.pos())
+                        .unwrap_or_else(|x| x);
+                    let non_narrow: usize = f
+                        .non_narrow_chars[0..end_width_idx]
+                        .into_iter()
+                        .map(|x| x.width())
+                        .sum();
+                    chpos.0 - end_width_idx + non_narrow
+                };
+                Loc {
+                    file: f,
+                    line: 0,
+                    col: chpos,
+                    col_display,
+                }
+            }
+        }
+    }
+
+    // If the corresponding `SourceFile` is empty, does not return a line number.
+    pub fn lookup_line(&self, pos: BytePos) -> Result<SourceFileAndLine, Lrc<SourceFile>> {
+        let idx = self.lookup_source_file_idx(pos);
+
+        let f = (*self.files.borrow().source_files)[idx].clone();
+
+        match f.lookup_line(pos) {
+            Some(line) => Ok(SourceFileAndLine { sf: f, line }),
+            None => Err(f)
+        }
+    }
+
+    /// Returns `Some(span)`, a union of the LHS and RHS span. The LHS must precede the RHS. If
+    /// there are gaps between LHS and RHS, the resulting union will cross these gaps.
+    /// For this to work,
+    ///
+    ///    * the syntax contexts of both spans much match,
+    ///    * the LHS span needs to end on the same line the RHS span begins,
+    ///    * the LHS span must start at or before the RHS span.
+    pub fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span> {
+        // Ensure we're at the same expansion ID.
+        if sp_lhs.ctxt() != sp_rhs.ctxt() {
+            return None;
+        }
+
+        let lhs_end = match self.lookup_line(sp_lhs.hi()) {
+            Ok(x) => x,
+            Err(_) => return None
+        };
+        let rhs_begin = match self.lookup_line(sp_rhs.lo()) {
+            Ok(x) => x,
+            Err(_) => return None
+        };
+
+        // If we must cross lines to merge, don't merge.
+        if lhs_end.line != rhs_begin.line {
+            return None;
+        }
+
+        // Ensure these follow the expected order and that we don't overlap.
+        if (sp_lhs.lo() <= sp_rhs.lo()) && (sp_lhs.hi() <= sp_rhs.lo()) {
+            Some(sp_lhs.to(sp_rhs))
+        } else {
+            None
+        }
+    }
+
+    pub fn span_to_string(&self, sp: Span) -> String {
+        if self.files.borrow().source_files.is_empty() && sp.is_dummy() {
+            return "no-location".to_string();
+        }
+
+        let lo = self.lookup_char_pos(sp.lo());
+        let hi = self.lookup_char_pos(sp.hi());
+        format!("{}:{}:{}: {}:{}",
+            lo.file.name,
+            lo.line,
+            lo.col.to_usize() + 1,
+            hi.line,
+            hi.col.to_usize() + 1,
+        )
+    }
+
+    pub fn span_to_filename(&self, sp: Span) -> FileName {
+        self.lookup_char_pos(sp.lo()).file.name.clone()
+    }
+
+    pub fn span_to_unmapped_path(&self, sp: Span) -> FileName {
+        self.lookup_char_pos(sp.lo()).file.unmapped_path.clone()
+            .expect("`SourceMap::span_to_unmapped_path` called for imported `SourceFile`?")
+    }
+
+    pub fn is_multiline(&self, sp: Span) -> bool {
+        let lo = self.lookup_char_pos(sp.lo());
+        let hi = self.lookup_char_pos(sp.hi());
+        lo.line != hi.line
+    }
+
+    pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
+        debug!("span_to_lines(sp={:?})", sp);
+
+        let lo = self.lookup_char_pos(sp.lo());
+        debug!("span_to_lines: lo={:?}", lo);
+        let hi = self.lookup_char_pos(sp.hi());
+        debug!("span_to_lines: hi={:?}", hi);
+
+        if lo.file.start_pos != hi.file.start_pos {
+            return Err(SpanLinesError::DistinctSources(DistinctSources {
+                begin: (lo.file.name.clone(), lo.file.start_pos),
+                end: (hi.file.name.clone(), hi.file.start_pos),
+            }));
+        }
+        assert!(hi.line >= lo.line);
+
+        let mut lines = Vec::with_capacity(hi.line - lo.line + 1);
+
+        // The span starts partway through the first line,
+        // but after that it starts from offset 0.
+        let mut start_col = lo.col;
+
+        // For every line but the last, it extends from `start_col`
+        // and to the end of the line. Be careful because the line
+        // numbers in Loc are 1-based, so we subtract 1 to get 0-based
+        // lines.
+        for line_index in lo.line-1 .. hi.line-1 {
+            let line_len = lo.file.get_line(line_index)
+                                  .map(|s| s.chars().count())
+                                  .unwrap_or(0);
+            lines.push(LineInfo { line_index,
+                                  start_col,
+                                  end_col: CharPos::from_usize(line_len) });
+            start_col = CharPos::from_usize(0);
+        }
+
+        // For the last line, it extends from `start_col` to `hi.col`:
+        lines.push(LineInfo { line_index: hi.line - 1,
+                              start_col,
+                              end_col: hi.col });
+
+        Ok(FileLines {file: lo.file, lines})
+    }
+
+    /// Extracts the source surrounding the given `Span` using the `extract_source` function. The
+    /// extract function takes three arguments: a string slice containing the source, an index in
+    /// the slice for the beginning of the span and an index in the slice for the end of the span.
+    fn span_to_source<F>(&self, sp: Span, extract_source: F) -> Result<String, SpanSnippetError>
+        where F: Fn(&str, usize, usize) -> Result<String, SpanSnippetError>
+    {
+        let local_begin = self.lookup_byte_offset(sp.lo());
+        let local_end = self.lookup_byte_offset(sp.hi());
+
+        if local_begin.sf.start_pos != local_end.sf.start_pos {
+            return Err(SpanSnippetError::DistinctSources(DistinctSources {
+                begin: (local_begin.sf.name.clone(),
+                        local_begin.sf.start_pos),
+                end: (local_end.sf.name.clone(),
+                      local_end.sf.start_pos)
+            }));
+        } else {
+            self.ensure_source_file_source_present(local_begin.sf.clone());
+
+            let start_index = local_begin.pos.to_usize();
+            let end_index = local_end.pos.to_usize();
+            let source_len = (local_begin.sf.end_pos -
+                              local_begin.sf.start_pos).to_usize();
+
+            if start_index > end_index || end_index > source_len {
+                return Err(SpanSnippetError::MalformedForSourcemap(
+                    MalformedSourceMapPositions {
+                        name: local_begin.sf.name.clone(),
+                        source_len,
+                        begin_pos: local_begin.pos,
+                        end_pos: local_end.pos,
+                    }));
+            }
+
+            if let Some(ref src) = local_begin.sf.src {
+                return extract_source(src, start_index, end_index);
+            } else if let Some(src) = local_begin.sf.external_src.borrow().get_source() {
+                return extract_source(src, start_index, end_index);
+            } else {
+                return Err(SpanSnippetError::SourceNotAvailable {
+                    filename: local_begin.sf.name.clone()
+                });
+            }
+        }
+    }
+
+    /// Returns the source snippet as `String` corresponding to the given `Span`.
+    pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> {
+        self.span_to_source(sp, |src, start_index, end_index| src.get(start_index..end_index)
+            .map(|s| s.to_string())
+            .ok_or_else(|| SpanSnippetError::IllFormedSpan(sp)))
+    }
+
+    pub fn span_to_margin(&self, sp: Span) -> Option<usize> {
+        match self.span_to_prev_source(sp) {
+            Err(_) => None,
+            Ok(source) => source.split('\n').last().map(|last_line| {
+                last_line.len() - last_line.trim_start().len()
+            })
+        }
+    }
+
+    /// Returns the source snippet as `String` before the given `Span`.
+    pub fn span_to_prev_source(&self, sp: Span) -> Result<String, SpanSnippetError> {
+        self.span_to_source(sp, |src, start_index, _| src.get(..start_index)
+            .map(|s| s.to_string())
+            .ok_or_else(|| SpanSnippetError::IllFormedSpan(sp)))
+    }
+
+    /// Extends the given `Span` to just after the previous occurrence of `c`. Return the same span
+    /// if no character could be found or if an error occurred while retrieving the code snippet.
+    pub fn span_extend_to_prev_char(&self, sp: Span, c: char) -> Span {
+        if let Ok(prev_source) = self.span_to_prev_source(sp) {
+            let prev_source = prev_source.rsplit(c).nth(0).unwrap_or("").trim_start();
+            if !prev_source.is_empty() && !prev_source.contains('\n') {
+                return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
+            }
+        }
+
+        sp
+    }
+
+    /// Extends the given `Span` to just after the previous occurrence of `pat` when surrounded by
+    /// whitespace. Returns the same span if no character could be found or if an error occurred
+    /// while retrieving the code snippet.
+    pub fn span_extend_to_prev_str(&self, sp: Span, pat: &str, accept_newlines: bool) -> Span {
+        // assure that the pattern is delimited, to avoid the following
+        //     fn my_fn()
+        //           ^^^^ returned span without the check
+        //     ---------- correct span
+        for ws in &[" ", "\t", "\n"] {
+            let pat = pat.to_owned() + ws;
+            if let Ok(prev_source) = self.span_to_prev_source(sp) {
+                let prev_source = prev_source.rsplit(&pat).nth(0).unwrap_or("").trim_start();
+                if !prev_source.is_empty() && (!prev_source.contains('\n') || accept_newlines) {
+                    return sp.with_lo(BytePos(sp.lo().0 - prev_source.len() as u32));
+                }
+            }
+        }
+
+        sp
+    }
+
+    /// Given a `Span`, tries to get a shorter span ending before the first occurrence of `char`
+    /// `c`.
+    pub fn span_until_char(&self, sp: Span, c: char) -> Span {
+        match self.span_to_snippet(sp) {
+            Ok(snippet) => {
+                let snippet = snippet.split(c).nth(0).unwrap_or("").trim_end();
+                if !snippet.is_empty() && !snippet.contains('\n') {
+                    sp.with_hi(BytePos(sp.lo().0 + snippet.len() as u32))
+                } else {
+                    sp
+                }
+            }
+            _ => sp,
+        }
+    }
+
+    /// Given a `Span`, tries to get a shorter span ending just after the first occurrence of `char`
+    /// `c`.
+    pub fn span_through_char(&self, sp: Span, c: char) -> Span {
+        if let Ok(snippet) = self.span_to_snippet(sp) {
+            if let Some(offset) = snippet.find(c) {
+                return sp.with_hi(BytePos(sp.lo().0 + (offset + c.len_utf8()) as u32));
+            }
+        }
+        sp
+    }
+
+    /// Given a `Span`, gets a new `Span` covering the first token and all its trailing whitespace
+    /// or the original `Span`.
+    ///
+    /// If `sp` points to `"let mut x"`, then a span pointing at `"let "` will be returned.
+    pub fn span_until_non_whitespace(&self, sp: Span) -> Span {
+        let mut whitespace_found = false;
+
+        self.span_take_while(sp, |c| {
+            if !whitespace_found && c.is_whitespace() {
+                whitespace_found = true;
+            }
+
+            if whitespace_found && !c.is_whitespace() {
+                false
+            } else {
+                true
+            }
+        })
+    }
+
+    /// Given a `Span`, gets a new `Span` covering the first token without its trailing whitespace
+    /// or the original `Span` in case of error.
+    ///
+    /// If `sp` points to `"let mut x"`, then a span pointing at `"let"` will be returned.
+    pub fn span_until_whitespace(&self, sp: Span) -> Span {
+        self.span_take_while(sp, |c| !c.is_whitespace())
+    }
+
+    /// Given a `Span`, gets a shorter one until `predicate` yields `false`.
+    pub fn span_take_while<P>(&self, sp: Span, predicate: P) -> Span
+        where P: for <'r> FnMut(&'r char) -> bool
+    {
+        if let Ok(snippet) = self.span_to_snippet(sp) {
+            let offset = snippet.chars()
+                .take_while(predicate)
+                .map(|c| c.len_utf8())
+                .sum::<usize>();
+
+            sp.with_hi(BytePos(sp.lo().0 + (offset as u32)))
+        } else {
+            sp
+        }
+    }
+
+    pub fn def_span(&self, sp: Span) -> Span {
+        self.span_until_char(sp, '{')
+    }
+
+    /// Returns a new span representing just the start point of this span.
+    pub fn start_point(&self, sp: Span) -> Span {
+        let pos = sp.lo().0;
+        let width = self.find_width_of_character_at_span(sp, false);
+        let corrected_start_position = pos.checked_add(width).unwrap_or(pos);
+        let end_point = BytePos(cmp::max(corrected_start_position, sp.lo().0));
+        sp.with_hi(end_point)
+    }
+
+    /// Returns a new span representing just the end point of this span.
+    pub fn end_point(&self, sp: Span) -> Span {
+        let pos = sp.hi().0;
+
+        let width = self.find_width_of_character_at_span(sp, false);
+        let corrected_end_position = pos.checked_sub(width).unwrap_or(pos);
+
+        let end_point = BytePos(cmp::max(corrected_end_position, sp.lo().0));
+        sp.with_lo(end_point)
+    }
+
+    /// Returns a new span representing the next character after the end-point of this span.
+    pub fn next_point(&self, sp: Span) -> Span {
+        let start_of_next_point = sp.hi().0;
+
+        let width = self.find_width_of_character_at_span(sp, true);
+        // If the width is 1, then the next span should point to the same `lo` and `hi`. However,
+        // in the case of a multibyte character, where the width != 1, the next span should
+        // span multiple bytes to include the whole character.
+        let end_of_next_point = start_of_next_point.checked_add(
+            width - 1).unwrap_or(start_of_next_point);
+
+        let end_of_next_point = BytePos(cmp::max(sp.lo().0 + 1, end_of_next_point));
+        Span::new(BytePos(start_of_next_point), end_of_next_point, sp.ctxt())
+    }
+
+    /// Finds the width of a character, either before or after the provided span.
+    fn find_width_of_character_at_span(&self, sp: Span, forwards: bool) -> u32 {
+        let sp = sp.data();
+        if sp.lo == sp.hi {
+            debug!("find_width_of_character_at_span: early return empty span");
+            return 1;
+        }
+
+        let local_begin = self.lookup_byte_offset(sp.lo);
+        let local_end = self.lookup_byte_offset(sp.hi);
+        debug!("find_width_of_character_at_span: local_begin=`{:?}`, local_end=`{:?}`",
+               local_begin, local_end);
+
+        if local_begin.sf.start_pos != local_end.sf.start_pos {
+            debug!("find_width_of_character_at_span: begin and end are in different files");
+            return 1;
+        }
+
+        let start_index = local_begin.pos.to_usize();
+        let end_index = local_end.pos.to_usize();
+        debug!("find_width_of_character_at_span: start_index=`{:?}`, end_index=`{:?}`",
+               start_index, end_index);
+
+        // Disregard indexes that are at the start or end of their spans, they can't fit bigger
+        // characters.
+        if (!forwards && end_index == usize::min_value()) ||
+            (forwards && start_index == usize::max_value()) {
+            debug!("find_width_of_character_at_span: start or end of span, cannot be multibyte");
+            return 1;
+        }
+
+        let source_len = (local_begin.sf.end_pos - local_begin.sf.start_pos).to_usize();
+        debug!("find_width_of_character_at_span: source_len=`{:?}`", source_len);
+        // Ensure indexes are also not malformed.
+        if start_index > end_index || end_index > source_len {
+            debug!("find_width_of_character_at_span: source indexes are malformed");
+            return 1;
+        }
+
+        let src = local_begin.sf.external_src.borrow();
+
+        // We need to extend the snippet to the end of the src rather than to end_index so when
+        // searching forwards for boundaries we've got somewhere to search.
+        let snippet = if let Some(ref src) = local_begin.sf.src {
+            let len = src.len();
+            (&src[start_index..len])
+        } else if let Some(src) = src.get_source() {
+            let len = src.len();
+            (&src[start_index..len])
+        } else {
+            return 1;
+        };
+        debug!("find_width_of_character_at_span: snippet=`{:?}`", snippet);
+
+        let mut target = if forwards { end_index + 1 } else { end_index - 1 };
+        debug!("find_width_of_character_at_span: initial target=`{:?}`", target);
+
+        while !snippet.is_char_boundary(target - start_index) && target < source_len {
+            target = if forwards {
+                target + 1
+            } else {
+                match target.checked_sub(1) {
+                    Some(target) => target,
+                    None => {
+                        break;
+                    }
+                }
+            };
+            debug!("find_width_of_character_at_span: target=`{:?}`", target);
+        }
+        debug!("find_width_of_character_at_span: final target=`{:?}`", target);
+
+        if forwards {
+            (target - end_index) as u32
+        } else {
+            (end_index - target) as u32
+        }
+    }
+
+    pub fn get_source_file(&self, filename: &FileName) -> Option<Lrc<SourceFile>> {
+        for sf in self.files.borrow().source_files.iter() {
+            if *filename == sf.name {
+                return Some(sf.clone());
+            }
+        }
+        None
+    }
+
+    /// For a global `BytePos`, computes the local offset within the containing `SourceFile`.
+    pub fn lookup_byte_offset(&self, bpos: BytePos) -> SourceFileAndBytePos {
+        let idx = self.lookup_source_file_idx(bpos);
+        let sf = (*self.files.borrow().source_files)[idx].clone();
+        let offset = bpos - sf.start_pos;
+        SourceFileAndBytePos {sf, pos: offset}
+    }
+
+    /// Converts an absolute `BytePos` to a `CharPos` relative to the `SourceFile`.
+    pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos {
+        let idx = self.lookup_source_file_idx(bpos);
+        let map = &(*self.files.borrow().source_files)[idx];
+
+        // The number of extra bytes due to multibyte chars in the `SourceFile`.
+        let mut total_extra_bytes = 0;
+
+        for mbc in map.multibyte_chars.iter() {
+            debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos);
+            if mbc.pos < bpos {
+                // Every character is at least one byte, so we only
+                // count the actual extra bytes.
+                total_extra_bytes += mbc.bytes as u32 - 1;
+                // We should never see a byte position in the middle of a
+                // character.
+                assert!(bpos.to_u32() >= mbc.pos.to_u32() + mbc.bytes as u32);
+            } else {
+                break;
+            }
+        }
+
+        assert!(map.start_pos.to_u32() + total_extra_bytes <= bpos.to_u32());
+        CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes as usize)
+    }
+
+    // Returns the index of the `SourceFile` (in `self.files`) that contains `pos`.
+    pub fn lookup_source_file_idx(&self, pos: BytePos) -> usize {
+        self.files.borrow().source_files.binary_search_by_key(&pos, |key| key.start_pos)
+            .unwrap_or_else(|p| p - 1)
+    }
+
+    pub fn count_lines(&self) -> usize {
+        self.files().iter().fold(0, |a, f| a + f.count_lines())
+    }
+
+
+    pub fn generate_fn_name_span(&self, span: Span) -> Option<Span> {
+        let prev_span = self.span_extend_to_prev_str(span, "fn", true);
+        self.span_to_snippet(prev_span).map(|snippet| {
+            let len = snippet.find(|c: char| !c.is_alphanumeric() && c != '_')
+                .expect("no label after fn");
+            prev_span.with_hi(BytePos(prev_span.lo().0 + len as u32))
+        }).ok()
+    }
+
+    /// Takes the span of a type parameter in a function signature and try to generate a span for
+    /// the function name (with generics) and a new snippet for this span with the pointed type
+    /// parameter as a new local type parameter.
+    ///
+    /// For instance:
+    /// ```rust,ignore (pseudo-Rust)
+    /// // Given span
+    /// fn my_function(param: T)
+    /// //                    ^ Original span
+    ///
+    /// // Result
+    /// fn my_function(param: T)
+    /// // ^^^^^^^^^^^ Generated span with snippet `my_function<T>`
+    /// ```
+    ///
+    /// Attention: The method used is very fragile since it essentially duplicates the work of the
+    /// parser. If you need to use this function or something similar, please consider updating the
+    /// `SourceMap` functions and this function to something more robust.
+    pub fn generate_local_type_param_snippet(&self, span: Span) -> Option<(Span, String)> {
+        // Try to extend the span to the previous "fn" keyword to retrieve the function
+        // signature.
+        let sugg_span = self.span_extend_to_prev_str(span, "fn", false);
+        if sugg_span != span {
+            if let Ok(snippet) = self.span_to_snippet(sugg_span) {
+                // Consume the function name.
+                let mut offset = snippet.find(|c: char| !c.is_alphanumeric() && c != '_')
+                    .expect("no label after fn");
+
+                // Consume the generics part of the function signature.
+                let mut bracket_counter = 0;
+                let mut last_char = None;
+                for c in snippet[offset..].chars() {
+                    match c {
+                        '<' => bracket_counter += 1,
+                        '>' => bracket_counter -= 1,
+                        '(' => if bracket_counter == 0 { break; }
+                        _ => {}
+                    }
+                    offset += c.len_utf8();
+                    last_char = Some(c);
+                }
+
+                // Adjust the suggestion span to encompass the function name with its generics.
+                let sugg_span = sugg_span.with_hi(BytePos(sugg_span.lo().0 + offset as u32));
+
+                // Prepare the new suggested snippet to append the type parameter that triggered
+                // the error in the generics of the function signature.
+                let mut new_snippet = if last_char == Some('>') {
+                    format!("{}, ", &snippet[..(offset - '>'.len_utf8())])
+                } else {
+                    format!("{}<", &snippet[..offset])
+                };
+                new_snippet.push_str(
+                    &self.span_to_snippet(span).unwrap_or_else(|_| "T".to_string()));
+                new_snippet.push('>');
+
+                return Some((sugg_span, new_snippet));
+            }
+        }
+
+        None
+    }
+    pub fn ensure_source_file_source_present(&self, source_file: Lrc<SourceFile>) -> bool {
+        source_file.add_external_src(
+            || match source_file.name {
+                FileName::Real(ref name) => self.file_loader.read_file(name).ok(),
+                _ => None,
+            }
+        )
+    }
+    pub fn call_span_if_macro(&self, sp: Span) -> Span {
+        if self.span_to_filename(sp.clone()).is_macros() {
+            let v = sp.macro_backtrace();
+            if let Some(use_site) = v.last() {
+                return use_site.call_site;
+            }
+        }
+        sp
+    }
+}
+
+#[derive(Clone)]
+pub struct FilePathMapping {
+    mapping: Vec<(PathBuf, PathBuf)>,
+}
+
+impl FilePathMapping {
+    pub fn empty() -> FilePathMapping {
+        FilePathMapping {
+            mapping: vec![]
+        }
+    }
+
+    pub fn new(mapping: Vec<(PathBuf, PathBuf)>) -> FilePathMapping {
+        FilePathMapping {
+            mapping,
+        }
+    }
+
+    /// Applies any path prefix substitution as defined by the mapping.
+    /// The return value is the remapped path and a boolean indicating whether
+    /// the path was affected by the mapping.
+    pub fn map_prefix(&self, path: PathBuf) -> (PathBuf, bool) {
+        // NOTE: We are iterating over the mapping entries from last to first
+        //       because entries specified later on the command line should
+        //       take precedence.
+        for &(ref from, ref to) in self.mapping.iter().rev() {
+            if let Ok(rest) = path.strip_prefix(from) {
+                return (to.join(rest), true);
+            }
+        }
+
+        (path, false)
+    }
+}
diff --git a/src/libsyntax_pos/source_map/tests.rs b/src/libsyntax_pos/source_map/tests.rs
new file mode 100644 (file)
index 0000000..1525433
--- /dev/null
@@ -0,0 +1,221 @@
+use super::*;
+
+use rustc_data_structures::sync::Lrc;
+
+fn init_source_map() -> SourceMap {
+    let sm = SourceMap::new(FilePathMapping::empty());
+    sm.new_source_file(
+        PathBuf::from("blork.rs").into(),
+        "first line.\nsecond line".to_string(),
+    );
+    sm.new_source_file(
+        PathBuf::from("empty.rs").into(),
+        String::new(),
+    );
+    sm.new_source_file(
+        PathBuf::from("blork2.rs").into(),
+        "first line.\nsecond line".to_string(),
+    );
+    sm
+}
+
+/// Tests `lookup_byte_offset`.
+#[test]
+fn t3() {
+    let sm = init_source_map();
+
+    let srcfbp1 = sm.lookup_byte_offset(BytePos(23));
+    assert_eq!(srcfbp1.sf.name, PathBuf::from("blork.rs").into());
+    assert_eq!(srcfbp1.pos, BytePos(23));
+
+    let srcfbp1 = sm.lookup_byte_offset(BytePos(24));
+    assert_eq!(srcfbp1.sf.name, PathBuf::from("empty.rs").into());
+    assert_eq!(srcfbp1.pos, BytePos(0));
+
+    let srcfbp2 = sm.lookup_byte_offset(BytePos(25));
+    assert_eq!(srcfbp2.sf.name, PathBuf::from("blork2.rs").into());
+    assert_eq!(srcfbp2.pos, BytePos(0));
+}
+
+/// Tests `bytepos_to_file_charpos`.
+#[test]
+fn t4() {
+    let sm = init_source_map();
+
+    let cp1 = sm.bytepos_to_file_charpos(BytePos(22));
+    assert_eq!(cp1, CharPos(22));
+
+    let cp2 = sm.bytepos_to_file_charpos(BytePos(25));
+    assert_eq!(cp2, CharPos(0));
+}
+
+/// Tests zero-length `SourceFile`s.
+#[test]
+fn t5() {
+    let sm = init_source_map();
+
+    let loc1 = sm.lookup_char_pos(BytePos(22));
+    assert_eq!(loc1.file.name, PathBuf::from("blork.rs").into());
+    assert_eq!(loc1.line, 2);
+    assert_eq!(loc1.col, CharPos(10));
+
+    let loc2 = sm.lookup_char_pos(BytePos(25));
+    assert_eq!(loc2.file.name, PathBuf::from("blork2.rs").into());
+    assert_eq!(loc2.line, 1);
+    assert_eq!(loc2.col, CharPos(0));
+}
+
+fn init_source_map_mbc() -> SourceMap {
+    let sm = SourceMap::new(FilePathMapping::empty());
+    // "€" is a three-byte UTF8 char.
+    sm.new_source_file(PathBuf::from("blork.rs").into(),
+                    "fir€st €€€€ line.\nsecond line".to_string());
+    sm.new_source_file(PathBuf::from("blork2.rs").into(),
+                    "first line€€.\n€ second line".to_string());
+    sm
+}
+
+/// Tests `bytepos_to_file_charpos` in the presence of multi-byte chars.
+#[test]
+fn t6() {
+    let sm = init_source_map_mbc();
+
+    let cp1 = sm.bytepos_to_file_charpos(BytePos(3));
+    assert_eq!(cp1, CharPos(3));
+
+    let cp2 = sm.bytepos_to_file_charpos(BytePos(6));
+    assert_eq!(cp2, CharPos(4));
+
+    let cp3 = sm.bytepos_to_file_charpos(BytePos(56));
+    assert_eq!(cp3, CharPos(12));
+
+    let cp4 = sm.bytepos_to_file_charpos(BytePos(61));
+    assert_eq!(cp4, CharPos(15));
+}
+
+/// Test `span_to_lines` for a span ending at the end of a `SourceFile`.
+#[test]
+fn t7() {
+    let sm = init_source_map();
+    let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
+    let file_lines = sm.span_to_lines(span).unwrap();
+
+    assert_eq!(file_lines.file.name, PathBuf::from("blork.rs").into());
+    assert_eq!(file_lines.lines.len(), 1);
+    assert_eq!(file_lines.lines[0].line_index, 1);
+}
+
+/// Given a string like " ~~~~~~~~~~~~ ", produces a span
+/// converting that range. The idea is that the string has the same
+/// length as the input, and we uncover the byte positions. Note
+/// that this can span lines and so on.
+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('~').map(|x|x as u32).unwrap_or(left_index);
+    Span::with_root_ctxt(BytePos(left_index), BytePos(right_index + 1))
+}
+
+/// Tests `span_to_snippet` and `span_to_lines` for a span converting 3
+/// lines in the middle of a file.
+#[test]
+fn span_to_snippet_and_lines_spanning_multiple_lines() {
+    let sm = SourceMap::new(FilePathMapping::empty());
+    let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n";
+    let selection = "     \n    ~~\n~~~\n~~~~~     \n   \n";
+    sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_string());
+    let span = span_from_selection(inputtext, selection);
+
+    // Check that we are extracting the text we thought we were extracting.
+    assert_eq!(&sm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD");
+
+    // Check that span_to_lines gives us the complete result with the lines/cols we expected.
+    let lines = sm.span_to_lines(span).unwrap();
+    let expected = vec![
+        LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) },
+        LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) },
+        LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) }
+        ];
+    assert_eq!(lines.lines, expected);
+}
+
+/// Test span_to_snippet for a span ending at the end of a `SourceFile`.
+#[test]
+fn t8() {
+    let sm = init_source_map();
+    let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
+    let snippet = sm.span_to_snippet(span);
+
+    assert_eq!(snippet, Ok("second line".to_string()));
+}
+
+/// Test `span_to_str` for a span ending at the end of a `SourceFile`.
+#[test]
+fn t9() {
+    let sm = init_source_map();
+    let span = Span::with_root_ctxt(BytePos(12), BytePos(23));
+    let sstr =  sm.span_to_string(span);
+
+    assert_eq!(sstr, "blork.rs:2:1: 2:12");
+}
+
+/// Tests failing to merge two spans on different lines.
+#[test]
+fn span_merging_fail() {
+    let sm = SourceMap::new(FilePathMapping::empty());
+    let inputtext  = "bbbb BB\ncc CCC\n";
+    let selection1 = "     ~~\n      \n";
+    let selection2 = "       \n   ~~~\n";
+    sm.new_source_file(Path::new("blork.rs").to_owned().into(), inputtext.to_owned());
+    let span1 = span_from_selection(inputtext, selection1);
+    let span2 = span_from_selection(inputtext, selection2);
+
+    assert!(sm.merge_spans(span1, span2).is_none());
+}
+
+/// Returns the span corresponding to the `n`th occurrence of `substring` in `source_text`.
+trait SourceMapExtension {
+    fn span_substr(
+        &self,
+        file: &Lrc<SourceFile>,
+        source_text: &str,
+        substring: &str,
+        n: usize,
+    ) -> Span;
+}
+
+impl SourceMapExtension for SourceMap {
+    fn span_substr(
+        &self,
+        file: &Lrc<SourceFile>,
+        source_text: &str,
+        substring: &str,
+        n: usize,
+    ) -> Span {
+        println!(
+            "span_substr(file={:?}/{:?}, substring={:?}, n={})",
+            file.name, file.start_pos, substring, n
+        );
+        let mut i = 0;
+        let mut hi = 0;
+        loop {
+            let offset = source_text[hi..].find(substring).unwrap_or_else(|| {
+                panic!(
+                    "source_text `{}` does not have {} occurrences of `{}`, only {}",
+                    source_text, n, substring, i
+                );
+            });
+            let lo = hi + offset;
+            hi = lo + substring.len();
+            if i == n {
+                let span = Span::with_root_ctxt(
+                    BytePos(lo as u32 + file.start_pos.0),
+                    BytePos(hi as u32 + file.start_pos.0),
+                );
+                assert_eq!(&self.span_to_snippet(span).unwrap()[..], substring);
+                return span;
+            }
+            i += 1;
+        }
+    }
+}
index 02b09e21ff0226e5bf9d3c27a80eecba9d0980ef..7930d1249e7dc7da088317734e90abe877215ae8 100644 (file)
@@ -1,4 +1,4 @@
-//! These structs are a subset of the ones found in `syntax::json`.
+//! These structs are a subset of the ones found in `rustc_errors::json`.
 //! They are only used for deserialization of JSON output provided by libtest.
 
 use crate::errors::{Error, ErrorKind};