difftreelog
feat show full error range instead of just start
in: master
4 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth1use std::{cmp::Ordering, convert::Infallible, fmt};23use jrsonnet_gcmodule::{Acyclic, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::{BinaryOpType, Source, SourcePath, Span, Spanned, UnaryOpType};6use jrsonnet_types::ValType;7use thiserror::Error;89use crate::{10 ObjValue, ResolvePathOwned,11 function::{CallLocation, FunctionSignature, ParamName},12 stdlib::format::FormatError,13 typed::TypeLocError,14 val::ConvertNumValueError,15};1617#[derive(Debug, Clone)]18pub struct SyntaxError {19 pub message: String,20 pub location: (u32, u32),21}22impl fmt::Display for SyntaxError {23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {24 write!(f, "{}", self.message)25 }26}2728pub(crate) fn format_found(list: &[IStr], what: &str) -> String {29 if list.is_empty() {30 return String::new();31 }32 let mut out = String::new();33 out.push_str("\nThere ");34 if list.len() > 1 {35 out.push_str("are ");36 } else {37 out.push_str("is a ");38 }39 out.push_str(what);40 if list.len() > 1 {41 out.push('s');42 }43 out.push_str(" with similar name");44 if list.len() > 1 {45 out.push('s');46 }47 out.push_str(" present: ");48 for (i, v) in list.iter().enumerate() {49 if i != 0 {50 out.push_str(", ");51 }52 out.push_str(v as &str);53 }54 out55}5657const fn format_empty_str(str: &str) -> &str {58 if str.is_empty() {59 "\"\" (empty string)"60 } else {61 str62 }63}6465pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {66 let mut heap = Vec::new();67 for field in v.fields_ex(68 true,69 #[cfg(feature = "exp-preserve-order")]70 false,71 ) {72 let conf = strsim::jaro_winkler(field.as_str(), key.as_str());73 if conf < 0.8 {74 continue;75 }76 assert!(77 field.as_str() != key.as_str(),78 "looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!"79 );8081 heap.push((conf, field));82 }83 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));84 heap.into_iter().map(|v| v.1).collect()85}8687/// Possible errors88#[allow(missing_docs)]89#[derive(Error, Debug, Clone, Trace)]90#[non_exhaustive]91pub enum ErrorKind {92 #[error("intrinsic not found: {0}")]93 IntrinsicNotFound(IStr),9495 #[error("operator {0} does not operate on type {1}")]96 UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType),97 #[error("binary operation {1} {0} {2} is not implemented")]98 BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType),99100 #[error("self/super/$ are only usable inside objects")]101 CantUseSelfSupOutsideOfObject,102 #[error("no super found")]103 NoSuperFound,104105 #[error("for loop can only iterate over arrays")]106 InComprehensionCanOnlyIterateOverArray,107108 #[error("array out of bounds: {0} is not within [0,{1})")]109 ArrayBoundsError(isize, usize),110 #[error("string out of bounds: {0} is not within [0,{1})")]111 StringBoundsError(usize, usize),112113 #[error("assert failed: {}", format_empty_str(.0))]114 AssertionFailed(IStr),115116 #[error("local is not defined: {0}{found}", found = format_found(.1, "local"))]117 VariableIsNotDefined(IStr, Vec<IStr>),118 #[error("duplicate local var: {0}")]119 DuplicateLocalVar(IStr),120121 #[error("type mismatch: expected {expected}, got {2} {0}", expected = .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]122 TypeMismatch(&'static str, Vec<ValType>, ValType),123 #[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]124 NoSuchField(IStr, Vec<IStr>),125126 #[error("only functions can be called, got {0}")]127 OnlyFunctionsCanBeCalledGot(ValType),128 #[error("parameter {0} is not defined")]129 UnknownFunctionParameter(IStr),130 #[error("argument {0} is already bound")]131 BindingParameterASecondTime(IStr),132 #[error("too many args, function has {0}\nFunction has the following signature: {1}")]133 TooManyArgsFunctionHas(usize, FunctionSignature),134 #[error("function argument is not passed: {0}\nFunction has the following signature: {1}")]135 FunctionParameterNotBoundInCall(ParamName, FunctionSignature),136137 #[error("external variable is not defined: {0}")]138 UndefinedExternalVariable(IStr),139140 #[error("field name should be string, got {0}")]141 FieldMustBeStringGot(ValType),142 #[error("duplicate field name: {}", format_empty_str(.0))]143 DuplicateFieldName(IStr),144145 #[error("attempted to index array with string {}", format_empty_str(.0))]146 AttemptedIndexAnArrayWithString(IStr),147 #[error("{0} index type should be {1}, got {2}")]148 ValueIndexMustBeTypeGot(ValType, ValType, ValType),149 #[error("cant index into {0}")]150 CantIndexInto(ValType),151 #[error("{0} is not indexable")]152 ValueIsNotIndexable(ValType),153154 #[error("super can't be used standalone")]155 StandaloneSuper,156157 #[error("can't resolve {1} from {0}")]158 ImportFileNotFound(SourcePath, ResolvePathOwned),159 #[error("resolved file not found: {:?}", .0)]160 ResolvedFileNotFound(SourcePath),161 #[error("can't import {0}: is a directory")]162 ImportIsADirectory(SourcePath),163 #[error("imported file is not valid utf-8: {0:?}")]164 ImportBadFileUtf8(SourcePath),165 #[error("import io error: {0}")]166 ImportIo(String),167 #[error("tried to import {1} from {0}, but imports are not supported")]168 ImportNotSupported(SourcePath, ResolvePathOwned),169 #[error("can't import from virtual file")]170 CantImportFromVirtualFile,171 #[error("syntax error: {error}")]172 ImportSyntaxError {173 path: Source,174 #[trace(skip)]175 error: Box<SyntaxError>,176 },177178 #[error("runtime error: {}", format_empty_str(.0))]179 RuntimeError(IStr),180 #[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]181 StackOverflow,182 #[error("infinite recursion detected")]183 InfiniteRecursionDetected,184 #[error("tried to index by fractional value")]185 FractionalIndex,186 #[error("attempted to divide by zero")]187 DivisionByZero,188189 #[error("string manifest output is not an string")]190 StringManifestOutputIsNotAString,191 #[error("stream manifest output is not an array")]192 StreamManifestOutputIsNotAArray,193 #[error("multi manifest output is not an object")]194 MultiManifestOutputIsNotAObject,195196 #[error("cant recurse stream manifest")]197 StreamManifestOutputCannotBeRecursed,198 #[error("stream manifest output cannot consist of raw strings")]199 StreamManifestCannotNestString,200201 #[error("{}", format_empty_str(.0))]202 ImportCallbackError(String),203 #[error("invalid unicode codepoint: {0}")]204 InvalidUnicodeCodepointGot(u32),205206 #[error("convert num value: {0}")]207 ConvertNumValue(#[from] ConvertNumValueError),208209 #[error("format error: {0}")]210 Format(#[from] FormatError),211 #[error("type error: {0}")]212 TypeError(TypeLocError),213214 #[cfg(feature = "anyhow-error")]215 #[error(transparent)]216 Other(#[trace(skip)] std::rc::Rc<anyhow::Error>),217}218219#[cfg(feature = "anyhow-error")]220impl From<anyhow::Error> for Error {221 fn from(e: anyhow::Error) -> Self {222 Self::new(ErrorKind::Other(std::rc::Rc::new(e)))223 }224}225226impl From<ErrorKind> for Error {227 fn from(e: ErrorKind) -> Self {228 Self::new(e)229 }230}231232impl From<Infallible> for Error {233 fn from(_value: Infallible) -> Self {234 unreachable!()235 }236}237238/// Single stack trace frame239#[derive(Clone, Debug, Trace)]240pub struct StackTraceElement {241 /// Source of this frame242 /// Some frames only act as description, without attached source243 pub location: Option<Span>,244 /// Frame description245 pub desc: String,246}247#[derive(Debug, Clone, Trace)]248pub struct StackTrace(pub Vec<StackTraceElement>);249250#[derive(Clone, Trace)]251pub struct Error(Box<(ErrorKind, StackTrace)>);252impl Error {253 pub fn new(e: ErrorKind) -> Self {254 Self(Box::new((e, StackTrace(vec![]))))255 }256257 pub const fn error(&self) -> &ErrorKind {258 &(self.0).0259 }260 pub fn error_mut(&mut self) -> &mut ErrorKind {261 &mut (self.0).0262 }263 pub const fn trace(&self) -> &StackTrace {264 &(self.0).1265 }266 pub fn trace_mut(&mut self) -> &mut StackTrace {267 &mut (self.0).1268 }269}270impl fmt::Display for Error {271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {272 writeln!(f, "{}", self.0.0)?;273 for el in &self.0.1.0 {274 write!(f, "\t{}", el.desc)?;275 if let Some(loc) = &el.location {276 write!(f, "at {}", loc.0.0.0)?;277 loc.0.map_source_locations(&[loc.1, loc.2]);278 }279 writeln!(f)?;280 }281 Ok(())282 }283}284impl fmt::Debug for Error {285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {286 f.debug_tuple("LocError").field(&self.0).finish()287 }288}289impl std::error::Error for Error {}290291pub trait ErrorSource {292 fn to_location(self) -> Option<Span>;293}294impl<T: Acyclic> ErrorSource for &Spanned<T> {295 fn to_location(self) -> Option<Span> {296 Some(self.span.clone())297 }298}299impl ErrorSource for &Span {300 fn to_location(self) -> Option<Span> {301 Some(self.clone())302 }303}304impl ErrorSource for CallLocation<'_> {305 fn to_location(self) -> Option<Span> {306 self.0.cloned()307 }308}309310pub type Result<V, E = Error> = std::result::Result<V, E>;311pub trait ResultExt: Sized {312 #[must_use]313 fn with_description<O: Into<String>>(self, msg: impl FnOnce() -> O) -> Self;314 #[must_use]315 fn description(self, msg: &str) -> Self {316 self.with_description(|| msg)317 }318319 #[must_use]320 fn with_description_src<O: Into<String>>(321 self,322 src: impl ErrorSource,323 msg: impl FnOnce() -> O,324 ) -> Self;325 #[must_use]326 fn description_src(self, src: impl ErrorSource, msg: &str) -> Self {327 self.with_description_src(src, || msg)328 }329}330impl<T> ResultExt for Result<T, Error> {331 fn with_description<O: Into<String>>(mut self, msg: impl FnOnce() -> O) -> Self {332 if let Err(e) = &mut self {333 let trace = e.trace_mut();334 trace.0.push(StackTraceElement {335 location: None,336 desc: msg().into(),337 });338 }339 self340 }341342 fn with_description_src<O: Into<String>>(343 mut self,344 src: impl ErrorSource,345 msg: impl FnOnce() -> O,346 ) -> Self {347 if let Err(e) = &mut self {348 let trace = e.trace_mut();349 trace.0.push(StackTraceElement {350 location: src.to_location(),351 desc: msg().into(),352 });353 }354 self355 }356}357358#[macro_export]359macro_rules! bail {360 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {361 return Err($w$(::$i)*$(($($tt)*))?.into())362 };363 ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {364 return Err($w$(::$i)*$({$($tt)*})?.into())365 };366 ($l:literal$(, $($tt:tt)*)?) => {367 return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())368 };369}370371#[macro_export]372macro_rules! runtime_error {373 ($l:literal$(, $($tt:tt)*)?) => {374 $crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))375 };376}crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -50,7 +50,7 @@
#[cfg(not(any(feature = "ir-parser", feature = "peg-parser")))]
compile_error!("at least one of `ir-parser` or `peg-parser` features must be enabled");
-pub use error::{SyntaxError, SyntaxErrorLocation};
+pub use error::SyntaxError;
pub use obj::*;
pub use rustc_hash;
use rustc_hash::FxHashMap;
@@ -87,9 +87,7 @@
jrsonnet_ir_parser::parse(code, &jrsonnet_ir_parser::ParserSettings { source }).map_err(|e| {
SyntaxError {
message: e.message,
- location: SyntaxErrorLocation {
- offset: e.location.offset,
- },
+ location: (e.location.0, e.location.1),
}
})
}
@@ -107,7 +105,7 @@
"expected {}, got {:?}",
e.expected,
code.chars()
- .nth(e.location.offset)
+ .nth(e.location.0)
.map_or_else(|| "EOF".into(), |c: char| c.to_string())
)
},
@@ -115,9 +113,7 @@
);
SyntaxError {
message,
- location: SyntaxErrorLocation {
- offset: e.location.offset,
- },
+ location: e.location,
}
})
}
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -122,7 +122,7 @@
|| path.source_path().to_string(),
|r| self.resolver.resolve(r),
);
- let mut offset = error.location.offset;
+ let mut offset = error.location.0 as usize;
let is_eof = if offset >= path.code().len() {
offset = path.code().len().saturating_sub(1);
true
@@ -263,11 +263,15 @@
write!(out, "{}", error.error())?;
if let ErrorKind::ImportSyntaxError { path, error } = error.error() {
writeln!(out)?;
- let offset = error.location.offset;
+ let mut offset = error.location;
+ // To inclusive range
+ if offset.1 > offset.0 {
+ offset.1 -= 1;
+ }
let mut builder = SnippetBuilder::new(path.code());
builder
.error(Text::fragment("syntax error", Formatting::default()))
- .range(offset..=offset)
+ .range(offset.0 as usize..=offset.1 as usize)
.build();
let source = builder.build();
let ansi = source_to_ansi(&source);
crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -7,21 +7,16 @@
ImportKind, IndexPart, LiteralType, Member, ObjBody, ObjComp, ObjMembers, Slice, SliceDesc,
Source, Span, Spanned, UnaryOpType, Visibility, unescape,
};
-use jrsonnet_lexer::{Lexeme, Lexer, SyntaxKind, T, collect_lexed_str_block};
+use jrsonnet_lexer::{Lexeme, Lexer, Span as LexSpan, SyntaxKind, T, collect_lexed_str_block};
pub struct ParserSettings {
pub source: Source,
-}
-
-#[derive(Debug, Clone)]
-pub struct ParseErrorLocation {
- pub offset: usize,
}
#[derive(Debug, Clone)]
pub struct ParseError {
pub message: String,
- pub location: ParseErrorLocation,
+ pub location: LexSpan,
}
impl std::fmt::Display for ParseError {
@@ -131,9 +126,7 @@
fn error(&self, message: String) -> ParseError {
ParseError {
- location: ParseErrorLocation {
- offset: self.span_start() as usize,
- },
+ location: self.lexemes[self.offset].range,
message,
}
}
@@ -143,9 +136,6 @@
return Err(self.error(format!("expected identifier, got {}", self.current_desc())));
}
let text = self.text();
- if is_reserved(text) {
- return Err(self.error(format!("expected identifier, got reserved word '{text}'")));
- }
let s: IStr = text.into();
self.eat_any();
Ok(s)
@@ -156,23 +146,6 @@
}
}
-fn is_reserved(s: &str) -> bool {
- matches!(
- s,
- "assert"
- | "else" | "error"
- | "false" | "for"
- | "function"
- | "if" | "import"
- | "importstr"
- | "importbin"
- | "in" | "local"
- | "null" | "tailstrict"
- | "then" | "self"
- | "super" | "true"
- )
-}
-
fn spanned<T: Acyclic>(
p: &mut Parser<'_>,
cb: impl FnOnce(&mut Parser<'_>) -> Result<T>,
@@ -1040,9 +1013,7 @@
if let Some(desc) = lexeme.kind.error_description() {
return Err(ParseError {
message: desc.to_owned(),
- location: ParseErrorLocation {
- offset: lexeme.range.0 as usize,
- },
+ location: lexeme.range,
});
}
}