use std::ops::Range; use codespan_reporting::diagnostic::{Diagnostic, Label}; /// A source location, for use in pointing users towards warnings and errors. /// /// Internally, locations are very tied to the `codespan_reporting` library, /// and the primary use of them is to serve as anchors within that library. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Location { file_idx: usize, location: Range, } impl Location { /// Generate a new `Location` from a file index and an offset from the /// start of the file. /// /// The file index is based on the file database being used. See the /// `codespan_reporting::files::SimpleFiles::add` function, which is /// normally where we get this index. pub fn new(file_idx: usize, location: Range) -> Self { Location { file_idx, location } } /// Generate a `Location` for a completely manufactured bit of code. /// /// Ideally, this is used only in testing, as any code we generate as /// part of the compiler should, theoretically, be tied to some actual /// location in the source code. That being said, this can be used in /// a pinch ... just maybe try to avoid it if you can. pub fn manufactured() -> Self { Location { file_idx: 0, location: 0..0, } } /// Generate a primary label for a [`Diagnostic`], based on this source /// location. /// /// Note, this is just the [`Label`], you'll want to fill in the [`Diagnostic`] /// with a lot more information. /// /// Primary labels are the things that are they key cause of the message. /// If, for example, it was an error to bind a variable named "x", and /// then have another binding of a variable named "x", the second one /// would likely be the primary label (because that's where the error /// actually happened), but you'd probably want to make the first location /// the secondary label to help users find it. pub fn primary_label(&self) -> Label { Label::primary(self.file_idx, self.location.clone()) } /// Generate a secondary label for a [`Diagnostic`], based on this source /// location. /// /// Note, this is just the [`Label`], you'll want to fill in the [`Diagnostic`] /// with a lot more information. /// /// Secondary labels are the things that are involved in the message, but /// aren't necessarily a problem in and of themselves. If, for example, it /// was an error to bind a variable named "x", and then have another binding /// of a variable named "x", the second one would likely be the primary /// label (because that's where the error actually happened), but you'd /// probably want to make the first location the secondary label to help /// users find it. pub fn secondary_label(&self) -> Label { Label::secondary(self.file_idx, self.location.clone()) } /// Return an error diagnostic centered at this location. /// /// Note that this [`Diagnostic`] will have no information associated with /// it other than that (a) there is an error, and (b) that the error is at /// this particular location. You'll need to extend it with actually useful /// information, like what kind of error it is. pub fn error(&self) -> Diagnostic { Diagnostic::error().with_labels(vec![Label::primary(self.file_idx, self.location.clone())]) } /// Return an error diagnostic centered at this location, with the given message. /// /// This is much more useful than [`Self::error`], because it actually provides /// the user with some guidance. That being said, you still might want to add /// even more information to ut, using [`Diagnostic::with_labels`], /// [`Diagnostic::with_notes`], or [`Diagnostic::with_code`]. pub fn labelled_error>(&self, msg: T) -> Diagnostic { Diagnostic::error().with_labels(vec![ Label::primary(self.file_idx, self.location.clone()).with_message(msg.as_ref()) ]) } /// Merge two locations into a single location spanning the whole range between /// them. /// /// This function returns None if the locations are from different files; this /// can happen if one of the locations is manufactured, for example. pub fn merge(&self, other: &Self) -> Option { if self.file_idx != other.file_idx { None } else { let start = if self.location.start <= other.location.start { self.location.start } else { other.location.start }; let end = if self.location.end >= other.location.end { self.location.end } else { other.location.end }; Some(Location { file_idx: self.file_idx, location: start..end, }) } } }