Remove SpanPos in favor of ErrorPos

This commit is contained in:
Laurenz 2022-10-05 13:15:02 +02:00
parent ec884ec1d8
commit fd8160f374
10 changed files with 79 additions and 80 deletions

View File

@ -2,6 +2,7 @@
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::io; use std::io;
use std::ops::Range;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
@ -9,6 +10,7 @@ use std::string::FromUtf8Error;
use comemo::Tracked; use comemo::Tracked;
use crate::syntax::{Span, Spanned}; use crate::syntax::{Span, Spanned};
use crate::util::EcoString;
use crate::World; use crate::World;
/// Early-return with a [`SourceError`]. /// Early-return with a [`SourceError`].
@ -39,32 +41,64 @@ macro_rules! error {
pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>; pub type SourceResult<T> = Result<T, Box<Vec<SourceError>>>;
/// An error in a source file. /// An error in a source file.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SourceError { pub struct SourceError {
/// The erroneous node in the source code. /// The span of the erroneous node in the source code.
pub span: Span, pub span: Span,
/// The position in the node where the error should be annotated.
pub pos: ErrorPos,
/// A diagnostic message describing the problem. /// A diagnostic message describing the problem.
pub message: String, pub message: EcoString,
/// The trace of function calls leading to the error. /// The trace of function calls leading to the error.
pub trace: Vec<Spanned<Tracepoint>>, pub trace: Vec<Spanned<Tracepoint>>,
} }
impl SourceError { impl SourceError {
/// Create a new, bare error. /// Create a new, bare error.
pub fn new(span: Span, message: impl Into<String>) -> Self { pub fn new(span: Span, message: impl Into<EcoString>) -> Self {
Self { Self {
span, span,
pos: ErrorPos::Full,
trace: vec![], trace: vec![],
message: message.into(), message: message.into(),
} }
} }
/// Adjust the position in the node where the error should be annotated.
pub fn with_pos(mut self, pos: ErrorPos) -> Self {
self.pos = pos;
self
}
/// The range in the source file identified by
/// [`self.span.source()`](Span::source) where the error should be
/// annotated.
pub fn range(&self, world: &dyn World) -> Range<usize> {
let full = world.source(self.span.source()).range(self.span);
match self.pos {
ErrorPos::Full => full,
ErrorPos::Start => full.start .. full.start,
ErrorPos::End => full.end .. full.end,
}
}
}
/// Where in a node an error should be annotated,
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ErrorPos {
/// Over the full width of the node.
Full,
/// At the start of the node.
Start,
/// At the end of the node.
End,
} }
/// A part of an error's [trace](SourceError::trace). /// A part of an error's [trace](SourceError::trace).
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Tracepoint { pub enum Tracepoint {
/// A function call. /// A function call.
Call(Option<String>), Call(Option<EcoString>),
/// A module import. /// A module import.
Import, Import,
} }
@ -135,7 +169,7 @@ pub trait At<T> {
impl<T, S> At<T> for Result<T, S> impl<T, S> At<T> for Result<T, S>
where where
S: Into<String>, S: Into<EcoString>,
{ {
fn at(self, span: Span) -> SourceResult<T> { fn at(self, span: Span) -> SourceResult<T> {
self.map_err(|message| Box::new(vec![error!(span, message)])) self.map_err(|message| Box::new(vec![error!(span, message)]))
@ -204,9 +238,9 @@ impl From<FromUtf8Error> for FileError {
} }
} }
impl From<FileError> for String { impl From<FileError> for EcoString {
fn from(error: FileError) -> Self { fn from(error: FileError) -> Self {
error.to_string() format_eco!("{error}")
} }
} }

View File

@ -695,7 +695,7 @@ impl Eval for FuncCall {
Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(), Value::Array(array) => array.get(args.into_index()?).at(self.span())?.clone(),
Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(), Value::Dict(dict) => dict.get(&args.into_key()?).at(self.span())?.clone(),
Value::Func(func) => { Value::Func(func) => {
let point = || Tracepoint::Call(func.name().map(ToString::to_string)); let point = || Tracepoint::Call(func.name().map(Into::into));
func.call(vm, args).trace(vm.world, point, self.span())? func.call(vm, args).trace(vm.world, point, self.span())?
} }
@ -713,8 +713,8 @@ impl Eval for MethodCall {
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> { fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
let span = self.span(); let span = self.span();
let method = self.method(); let method = self.method().take();
let point = || Tracepoint::Call(Some(method.to_string())); let point = || Tracepoint::Call(Some(method.clone()));
Ok(if methods::is_mutating(&method) { Ok(if methods::is_mutating(&method) {
let args = self.args().eval(vm)?; let args = self.args().eval(vm)?;

View File

@ -323,7 +323,7 @@ fn print_diagnostics(
for error in errors { for error in errors {
// The main diagnostic. // The main diagnostic.
let range = world.source(error.span.source()).range(error.span); let range = error.range(world);
let diag = Diagnostic::error() let diag = Diagnostic::error()
.with_message(error.message) .with_message(error.message)
.with_labels(vec![Label::primary(error.span.source(), range)]); .with_labels(vec![Label::primary(error.span.source(), range)]);

View File

@ -11,8 +11,9 @@ pub use tokens::*;
use std::collections::HashSet; use std::collections::HashSet;
use crate::diag::ErrorPos;
use crate::syntax::ast::{Assoc, BinOp, UnOp}; use crate::syntax::ast::{Assoc, BinOp, UnOp};
use crate::syntax::{NodeKind, SpanPos, SyntaxNode}; use crate::syntax::{NodeKind, SyntaxNode};
use crate::util::EcoString; use crate::util::EcoString;
/// Parse a source file. /// Parse a source file.
@ -811,7 +812,7 @@ fn item(p: &mut Parser, keyed: bool) -> ParseResult<NodeKind> {
msg.push_str(", found "); msg.push_str(", found ");
msg.push_str(kind.name()); msg.push_str(kind.name());
} }
let error = NodeKind::Error(SpanPos::Full, msg); let error = NodeKind::Error(ErrorPos::Full, msg);
marker.end(p, error); marker.end(p, error);
p.eat(); p.eat();
marker.perform(p, NodeKind::Named, expr).ok(); marker.perform(p, NodeKind::Named, expr).ok();

View File

@ -3,7 +3,8 @@ use std::mem;
use std::ops::Range; use std::ops::Range;
use super::{TokenMode, Tokens}; use super::{TokenMode, Tokens};
use crate::syntax::{InnerNode, NodeData, NodeKind, SpanPos, SyntaxNode}; use crate::diag::ErrorPos;
use crate::syntax::{InnerNode, NodeData, NodeKind, SyntaxNode};
use crate::util::EcoString; use crate::util::EcoString;
/// A convenient token-based parser. /// A convenient token-based parser.
@ -398,7 +399,7 @@ impl Parser<'_> {
pub fn unexpected(&mut self) { pub fn unexpected(&mut self) {
if let Some(found) = self.peek() { if let Some(found) = self.peek() {
let msg = format_eco!("unexpected {}", found.name()); let msg = format_eco!("unexpected {}", found.name());
let error = NodeKind::Error(SpanPos::Full, msg); let error = NodeKind::Error(ErrorPos::Full, msg);
self.perform(error, Self::eat); self.perform(error, Self::eat);
} }
} }
@ -412,7 +413,7 @@ impl Parser<'_> {
/// Insert an error message that `what` was expected at the marker position. /// Insert an error message that `what` was expected at the marker position.
pub fn expected_at(&mut self, marker: Marker, what: &str) { pub fn expected_at(&mut self, marker: Marker, what: &str) {
let msg = format_eco!("expected {}", what); let msg = format_eco!("expected {}", what);
let error = NodeKind::Error(SpanPos::Full, msg); let error = NodeKind::Error(ErrorPos::Full, msg);
self.children.insert(marker.0, NodeData::new(error, 0).into()); self.children.insert(marker.0, NodeData::new(error, 0).into());
} }
@ -422,7 +423,7 @@ impl Parser<'_> {
match self.peek() { match self.peek() {
Some(found) => { Some(found) => {
let msg = format_eco!("expected {}, found {}", thing, found.name()); let msg = format_eco!("expected {}, found {}", thing, found.name());
let error = NodeKind::Error(SpanPos::Full, msg); let error = NodeKind::Error(ErrorPos::Full, msg);
self.perform(error, Self::eat); self.perform(error, Self::eat);
} }
None => self.expected(thing), None => self.expected(thing),
@ -494,7 +495,7 @@ impl Marker {
msg.push_str(", found "); msg.push_str(", found ");
msg.push_str(child.kind().name()); msg.push_str(child.kind().name());
} }
let error = NodeKind::Error(SpanPos::Full, msg); let error = NodeKind::Error(ErrorPos::Full, msg);
let inner = mem::take(child); let inner = mem::take(child);
*child = InnerNode::with_child(error, inner).into(); *child = InnerNode::with_child(error, inner).into();
} }

View File

@ -4,9 +4,10 @@ use unicode_xid::UnicodeXID;
use unscanny::Scanner; use unscanny::Scanner;
use super::resolve::{resolve_hex, resolve_raw, resolve_string}; use super::resolve::{resolve_hex, resolve_raw, resolve_string};
use crate::diag::ErrorPos;
use crate::geom::{AngleUnit, LengthUnit}; use crate::geom::{AngleUnit, LengthUnit};
use crate::syntax::ast::{RawNode, Unit}; use crate::syntax::ast::{RawNode, Unit};
use crate::syntax::{NodeKind, SpanPos}; use crate::syntax::NodeKind;
use crate::util::EcoString; use crate::util::EcoString;
/// An iterator over the tokens of a string of source code. /// An iterator over the tokens of a string of source code.
@ -109,7 +110,7 @@ impl<'s> Iterator for Tokens<'s> {
'/' if self.s.eat_if('/') => self.line_comment(), '/' if self.s.eat_if('/') => self.line_comment(),
'/' if self.s.eat_if('*') => self.block_comment(), '/' if self.s.eat_if('*') => self.block_comment(),
'*' if self.s.eat_if('/') => { '*' if self.s.eat_if('/') => {
NodeKind::Error(SpanPos::Full, "unexpected end of block comment".into()) NodeKind::Error(ErrorPos::Full, "unexpected end of block comment".into())
} }
c if c.is_whitespace() => self.whitespace(c), c if c.is_whitespace() => self.whitespace(c),
@ -279,13 +280,13 @@ impl<'s> Tokens<'s> {
NodeKind::Escape(c) NodeKind::Escape(c)
} else { } else {
NodeKind::Error( NodeKind::Error(
SpanPos::Full, ErrorPos::Full,
"invalid unicode escape sequence".into(), "invalid unicode escape sequence".into(),
) )
} }
} else { } else {
self.terminated = false; self.terminated = false;
NodeKind::Error(SpanPos::End, "expected closing brace".into()) NodeKind::Error(ErrorPos::End, "expected closing brace".into())
} }
} }
@ -388,7 +389,7 @@ impl<'s> Tokens<'s> {
let remaining = backticks - found; let remaining = backticks - found;
let noun = if remaining == 1 { "backtick" } else { "backticks" }; let noun = if remaining == 1 { "backtick" } else { "backticks" };
NodeKind::Error( NodeKind::Error(
SpanPos::End, ErrorPos::End,
if found == 0 { if found == 0 {
format_eco!("expected {} {}", remaining, noun) format_eco!("expected {} {}", remaining, noun)
} else { } else {
@ -416,11 +417,11 @@ impl<'s> Tokens<'s> {
if !label.is_empty() { if !label.is_empty() {
NodeKind::Label(label.into()) NodeKind::Label(label.into())
} else { } else {
NodeKind::Error(SpanPos::Full, "label cannot be empty".into()) NodeKind::Error(ErrorPos::Full, "label cannot be empty".into())
} }
} else { } else {
self.terminated = false; self.terminated = false;
NodeKind::Error(SpanPos::End, "expected closing angle bracket".into()) NodeKind::Error(ErrorPos::End, "expected closing angle bracket".into())
} }
} }
@ -519,7 +520,7 @@ impl<'s> Tokens<'s> {
'"' => self.string(), '"' => self.string(),
// Invalid token. // Invalid token.
_ => NodeKind::Error(SpanPos::Full, "not valid here".into()), _ => NodeKind::Error(ErrorPos::Full, "not valid here".into()),
} }
} }
@ -578,10 +579,10 @@ impl<'s> Tokens<'s> {
"em" => NodeKind::Numeric(f, Unit::Em), "em" => NodeKind::Numeric(f, Unit::Em),
"fr" => NodeKind::Numeric(f, Unit::Fr), "fr" => NodeKind::Numeric(f, Unit::Fr),
"%" => NodeKind::Numeric(f, Unit::Percent), "%" => NodeKind::Numeric(f, Unit::Percent),
_ => NodeKind::Error(SpanPos::Full, "invalid number suffix".into()), _ => NodeKind::Error(ErrorPos::Full, "invalid number suffix".into()),
} }
} else { } else {
NodeKind::Error(SpanPos::Full, "invalid number".into()) NodeKind::Error(ErrorPos::Full, "invalid number".into())
} }
} }
@ -601,7 +602,7 @@ impl<'s> Tokens<'s> {
NodeKind::Str(string) NodeKind::Str(string)
} else { } else {
self.terminated = false; self.terminated = false;
NodeKind::Error(SpanPos::End, "expected quote".into()) NodeKind::Error(ErrorPos::End, "expected quote".into())
} }
} }
} }
@ -713,9 +714,9 @@ mod tests {
use super::*; use super::*;
use crate::parse::tests::check; use crate::parse::tests::check;
use ErrorPos::*;
use NodeKind::*; use NodeKind::*;
use Option::None; use Option::None;
use SpanPos::*;
use TokenMode::{Code, Markup}; use TokenMode::{Code, Markup};
fn Space(newlines: usize) -> NodeKind { fn Space(newlines: usize) -> NodeKind {
@ -742,7 +743,7 @@ mod tests {
NodeKind::Ident(ident.into()) NodeKind::Ident(ident.into())
} }
fn Error(pos: SpanPos, message: &str) -> NodeKind { fn Error(pos: ErrorPos, message: &str) -> NodeKind {
NodeKind::Error(pos, message.into()) NodeKind::Error(pos, message.into())
} }

View File

@ -2,7 +2,7 @@ use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use super::ast::{RawNode, Unit}; use super::ast::{RawNode, Unit};
use super::SpanPos; use crate::diag::ErrorPos;
use crate::util::EcoString; use crate::util::EcoString;
/// All syntactical building blocks that can be part of a Typst document. /// All syntactical building blocks that can be part of a Typst document.
@ -271,7 +271,7 @@ pub enum NodeKind {
ReturnExpr, ReturnExpr,
/// An invalid sequence of characters. /// An invalid sequence of characters.
Error(SpanPos, EcoString), Error(ErrorPos, EcoString),
} }
impl NodeKind { impl NodeKind {

View File

@ -73,7 +73,7 @@ impl SyntaxNode {
match self.kind() { match self.kind() {
NodeKind::Error(pos, message) => { NodeKind::Error(pos, message) => {
vec![SourceError::new(self.span().with_pos(*pos), message)] vec![SourceError::new(self.span(), message.clone()).with_pos(*pos)]
} }
_ => self _ => self
.children() .children()
@ -535,14 +535,7 @@ impl NodeData {
/// If the span points into this node, convert it to a byte range. /// If the span points into this node, convert it to a byte range.
pub fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> { pub fn range(&self, span: Span, offset: usize) -> Option<Range<usize>> {
(span.with_pos(SpanPos::Full) == self.span).then(|| { (self.span == span).then(|| offset .. offset + self.len())
let end = offset + self.len();
match span.pos() {
SpanPos::Full => offset .. end,
SpanPos::Start => offset .. offset,
SpanPos::End => end .. end,
}
})
} }
} }

View File

@ -63,10 +63,10 @@ pub struct Span(NonZeroU64);
impl Span { impl Span {
// Data layout: // Data layout:
// | 2 bits span pos | 16 bits source id | 46 bits number | // | 16 bits source id | 48 bits number |
// Number of bits for and minimum and maximum numbers assignable to spans. // Number of bits for and minimum and maximum numbers assignable to spans.
const BITS: usize = 46; const BITS: usize = 48;
const DETACHED: u64 = 1; const DETACHED: u64 = 1;
/// The full range of numbers available to spans. /// The full range of numbers available to spans.
@ -90,12 +90,6 @@ impl Span {
Self(to_non_zero(Self::DETACHED)) Self(to_non_zero(Self::DETACHED))
} }
/// Return this span, but with updated position.
pub const fn with_pos(self, pos: SpanPos) -> Self {
let bits = (self.0.get() & ((1 << 62) - 1)) | ((pos as u64) << 62);
Self(to_non_zero(bits))
}
/// The id of the source file the span points into. /// The id of the source file the span points into.
pub const fn source(self) -> SourceId { pub const fn source(self) -> SourceId {
SourceId::from_u16((self.0.get() >> Self::BITS) as u16) SourceId::from_u16((self.0.get() >> Self::BITS) as u16)
@ -105,16 +99,6 @@ impl Span {
pub const fn number(self) -> u64 { pub const fn number(self) -> u64 {
self.0.get() & ((1 << Self::BITS) - 1) self.0.get() & ((1 << Self::BITS) - 1)
} }
/// Where in the node the span points to.
pub const fn pos(self) -> SpanPos {
match self.0.get() >> 62 {
0 => SpanPos::Full,
1 => SpanPos::Start,
2 => SpanPos::End,
_ => panic!("span pos encoding is invalid"),
}
}
} }
/// Convert to a non zero u64. /// Convert to a non zero u64.
@ -125,17 +109,6 @@ const fn to_non_zero(v: u64) -> NonZeroU64 {
} }
} }
/// Where in a node a span points.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum SpanPos {
/// Over the full width of the node.
Full = 0,
/// At the start of the node.
Start = 1,
/// At the end of the node.
End = 2,
}
/// Result of numbering a node within an interval. /// Result of numbering a node within an interval.
pub type NumberingResult = Result<(), Unnumberable>; pub type NumberingResult = Result<(), Unnumberable>;
@ -153,14 +126,13 @@ impl std::error::Error for Unnumberable {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{SourceId, Span, SpanPos}; use super::{SourceId, Span};
#[test] #[test]
fn test_span_encoding() { fn test_span_encoding() {
let id = SourceId::from_u16(5); let id = SourceId::from_u16(5);
let span = Span::new(id, 10).with_pos(SpanPos::End); let span = Span::new(id, 10);
assert_eq!(span.source(), id); assert_eq!(span.source(), id);
assert_eq!(span.number(), 10); assert_eq!(span.number(), 10);
assert_eq!(span.pos(), SpanPos::End);
} }
} }

View File

@ -433,10 +433,7 @@ fn test_part(
let mut errors: Vec<_> = errors let mut errors: Vec<_> = errors
.into_iter() .into_iter()
.filter(|error| error.span.source() == id) .filter(|error| error.span.source() == id)
.map(|error| { .map(|error| (error.range(world), error.message.to_string()))
let range = world.source(error.span.source()).range(error.span);
(range, error.message.to_string())
})
.collect(); .collect();
errors.sort_by_key(|error| error.0.start); errors.sort_by_key(|error| error.0.start);