Files
ngr/src/syntax/location.rs
2023-10-14 16:39:16 -07:00

120 lines
4.9 KiB
Rust

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<usize>,
}
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<usize>) -> 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<usize> {
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<usize> {
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<usize> {
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<T: AsRef<str>>(&self, msg: T) -> Diagnostic<usize> {
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<Self> {
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,
})
}
}
}