git.delta.rocks / jrsonnet / refs/commits / 686e5cc6bf11

difftreelog

feat(evaluator) readable error messages

Lach2020-08-26parent: #eaf3cd1.patch.diff
in: master

6 files changed

modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -37,6 +37,8 @@
 base64 = "0.12.3"
 rustc-hash = "1.1.0"
 
+thiserror = "1.0.20"
+
 # Serialized stdlib
 [dependencies.serde]
 version = "1.0.115"
modifiedcrates/jrsonnet-evaluator/src/builtin/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/format.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/format.rs
@@ -2,16 +2,23 @@
 #![allow(clippy::too_many_arguments)]
 
 use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val, ValType};
+use thiserror::Error;
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Error)]
 pub enum FormatError {
+	#[error("truncated format code")]
 	TruncatedFormatCode,
+	#[error("unrecognized conversion type: {0}")]
 	UnrecognizedConversionType(char),
 
+	#[error("not enough values")]
 	NotEnoughValues,
 
+	#[error("cannot use * width with object")]
 	CannotUseStarWidthWithObject,
+	#[error("mapping keys required")]
 	MappingKeysRequired,
+	#[error("no such format field: {0}")]
 	NoSuchFormatField(Rc<str>),
 }
 
modifiedcrates/jrsonnet-evaluator/src/builtin/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/sort.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs
@@ -4,9 +4,11 @@
 };
 use std::rc::Rc;
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, thiserror::Error)]
 pub enum SortError {
+	#[error("sort key should be string or number")]
 	SortKeyShouldBeStringOrNumber,
+	#[error("sort elements should have equal types")]
 	SortElementsShouldHaveEqualType,
 }
 
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -63,7 +63,7 @@
 			.bindings
 			.get(&name)
 			.cloned()
-			.ok_or_else(|| UnknownVariable(name))?)
+			.ok_or_else(|| VariableIsNotDefined(name))?)
 	}
 	pub fn into_future(self, ctx: FutureContext) -> Self {
 		{
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -4,75 +4,117 @@
 };
 use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType};
 use std::{path::PathBuf, rc::Rc};
+use thiserror::Error;
 
-#[derive(Debug, Clone)]
+#[derive(Error, Debug, Clone)]
 pub enum Error {
+	#[error("intrinsic not found: {0}.{1}")]
 	IntrinsicNotFound(Rc<str>, Rc<str>),
+	#[error("argument reordering in intrisics not supported yet")]
 	IntrinsicArgumentReorderingIsNotSupportedYet,
 
+	#[error("operator {0} does not operate on type {1}")]
 	UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType),
+	#[error("binary operation {1} {0} {2} is not implemented")]
 	BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType),
 
+	#[error("no top level object in this context")]
 	NoTopLevelObjectFound,
+	#[error("self is only usable inside objects")]
 	CantUseSelfOutsideOfObject,
+	#[error("super is only usable inside objects")]
 	CantUseSuperOutsideOfObject,
 
+	#[error("for loop can only iterate over arrays")]
 	InComprehensionCanOnlyIterateOverArray,
 
+	#[error("array out of bounds: {0} is not within [0,{1})")]
 	ArrayBoundsError(usize, usize),
 
+	#[error("assert failed: {0}")]
 	AssertionFailed(Rc<str>),
 
-	VariableIsNotDefined(String),
+	#[error("variable is not defined: {0}")]
+	VariableIsNotDefined(Rc<str>),
+	#[error("type mismatch: expected {2}, got {1:?} {0}")]
 	TypeMismatch(&'static str, Vec<ValType>, ValType),
+	#[error("no such field: {0}")]
 	NoSuchField(Rc<str>),
 
-	UnknownVariable(Rc<str>),
-
+	#[error("only functions can be called, got {0}")]
 	OnlyFunctionsCanBeCalledGot(ValType),
+	#[error("parameter {0} is not defined")]
 	UnknownFunctionParameter(String),
+	#[error("argument {0} is already bound")]
 	BindingParameterASecondTime(Rc<str>),
+	#[error("too many args, function has {0}")]
 	TooManyArgsFunctionHas(usize),
+	#[error("founction argument is not passed: {0}")]
 	FunctionParameterNotBoundInCall(Rc<str>),
 
+	#[error("external variable is not defined: {0}")]
 	UndefinedExternalVariable(Rc<str>),
+	#[error("native is not defined: {0}")]
 	UndefinedExternalFunction(Rc<str>),
 
+	#[error("field name should be string, got {0}")]
 	FieldMustBeStringGot(ValType),
 
+	#[error("attempted to index array with string {0}")]
 	AttemptedIndexAnArrayWithString(Rc<str>),
+	#[error("{0} index type should be {1}, got {2}")]
 	ValueIndexMustBeTypeGot(ValType, ValType, ValType),
+	#[error("cant index into {0}")]
 	CantIndexInto(ValType),
 
+	#[error("super can't be used standalone")]
 	StandaloneSuper,
 
+	#[error("can't resolve {1} from {0}")]
 	ImportFileNotFound(PathBuf, PathBuf),
+	#[error("resolved file not found: {0}")]
 	ResolvedFileNotFound(PathBuf),
+	#[error("imported file is not valid utf-8: {0:?}")]
 	ImportBadFileUtf8(PathBuf),
+	#[error("tried to import {1} from {0}, but imports is not supported")]
 	ImportNotSupported(PathBuf, PathBuf),
+	#[error("syntax error")]
 	ImportSyntaxError {
 		path: Rc<PathBuf>,
 		source_code: Rc<str>,
 		error: Box<jrsonnet_parser::ParseError>,
 	},
 
+	#[error("runtime error: {0}")]
 	RuntimeError(Rc<str>),
+	#[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]
 	StackOverflow,
+	#[error("tried to index by fractional value")]
 	FractionalIndex,
+	#[error("attempted to divide by zero")]
 	DivisionByZero,
 
+	#[error("string manifest output is not an string")]
 	StringManifestOutputIsNotAString,
+	#[error("stream manifest output is not an array")]
 	StreamManifestOutputIsNotAArray,
+	#[error("multi manifest output is not an object")]
 	MultiManifestOutputIsNotAObject,
 
+	#[error("cant recurse stream manifest")]
 	StreamManifestOutputCannotBeRecursed,
+	#[error("stream manifest output cannot consist of raw strings")]
 	StreamManifestCannotNestString,
 
+	#[error("{0}")]
 	ImportCallbackError(String),
+	#[error("invalid unicode codepoint: {0}")]
 	InvalidUnicodeCodepointGot(u32),
 
-	Format(FormatError),
-	Sort(SortError),
+	#[error("format error: {0}")]
+	Format(#[from] FormatError),
+	#[error("sort error: {0}")]
+	Sort(#[from] SortError),
 }
 impl From<Error> for LocError {
 	fn from(e: Error) -> Self {
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/trace/mod.rs
1mod location;23use crate::{EvaluationState, LocError};4pub use location::*;5use std::path::PathBuf;67/// The way paths should be displayed8pub enum PathResolver {9	/// Only filename10	FileName,11	/// Absolute path12	Absolute,13	/// Path relative to base directory14	Relative(PathBuf),15}1617impl PathResolver {18	pub fn resolve(&self, from: &PathBuf) -> String {19		match self {20			Self::FileName => from.file_name().unwrap().to_string_lossy().into_owned(),21			Self::Absolute => from.to_string_lossy().into_owned(),22			Self::Relative(base) => {23				if from.is_relative() {24					return from.to_string_lossy().into_owned();25				}26				pathdiff::diff_paths(from, base)27					.unwrap()28					.to_string_lossy()29					.into_owned()30			}31		}32	}33}3435/// Implements pretty-printing of traces36pub trait TraceFormat {37	fn write_trace(38		&self,39		out: &mut dyn std::fmt::Write,40		evaluation_state: &EvaluationState,41		error: &LocError,42	) -> Result<(), std::fmt::Error>;43	// fn print_trace(44	// 	&self,45	// 	evaluation_state: &EvaluationState,46	// 	error: &LocError,47	// ) -> Result<(), std::fmt::Error> {48	// 	self.write_trace(&mut std::fmt::stdout(), evaluation_state, error)49	// }50}5152fn print_code_location(53	out: &mut impl std::fmt::Write,54	start: &CodeLocation,55	end: &CodeLocation,56) -> Result<(), std::fmt::Error> {57	if start.line == end.line {58		if start.column == end.column {59			write!(out, "{}:{}", start.line, end.column - 1)?;60		} else {61			write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;62		}63	} else {64		write!(65			out,66			"{}:{}-{}:{}",67			start.line,68			end.column - 1,69			start.line,70			end.column71		)?;72	}73	Ok(())74}7576/// vanilla-like jsonnet formatting77pub struct CompactFormat {78	pub resolver: PathResolver,79	pub padding: usize,80}8182impl TraceFormat for CompactFormat {83	fn write_trace(84		&self,85		out: &mut dyn std::fmt::Write,86		evaluation_state: &EvaluationState,87		error: &LocError,88	) -> Result<(), std::fmt::Error> {89		writeln!(out, "{:?}", error.error())?;90		let file_names = error91			.trace()92			.093			.iter()94			.map(|el| {95				let resolved_path = self.resolver.resolve(&el.location.0);96				// TODO: Process all trace elements first97				let location = evaluation_state98					.map_source_locations(&el.location.0, &[el.location.1, el.location.2]);99				(resolved_path, location)100			})101			.map(|(mut n, location)| {102				use std::fmt::Write;103				write!(n, ":").unwrap();104				print_code_location(&mut n, &location[0], &location[1]).unwrap();105				n106			})107			.collect::<Vec<_>>();108		let align = file_names.iter().map(|e| e.len()).max().unwrap_or(0);109		for (i, (el, file)) in error.trace().0.iter().zip(file_names).enumerate() {110			if i != 0 {111				writeln!(out)?;112			}113			write!(114				out,115				"{:<p$}{:<w$}: {}",116				"",117				file,118				el.desc,119				p = self.padding,120				w = align121			)?;122		}123		Ok(())124	}125}126127pub struct JSFormat;128impl TraceFormat for JSFormat {129	fn write_trace(130		&self,131		out: &mut dyn std::fmt::Write,132		evaluation_state: &EvaluationState,133		error: &LocError,134	) -> Result<(), std::fmt::Error> {135		writeln!(out, "{:?}", error.error())?;136		for (i, item) in error.trace().0.iter().enumerate() {137			if i != 0 {138				writeln!(out)?;139			}140			let desc = &item.desc;141			let source = item.location.clone();142			let start_end = evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);143144			write!(145				out,146				"    at {} ({}:{}:{})",147				desc,148				source.0.to_str().unwrap(),149				start_end[0].line,150				start_end[0].column,151			)?;152		}153		Ok(())154	}155}156157/// rustc-like trace displaying158#[cfg(feature = "explaining-traces")]159pub struct ExplainingFormat {160	pub resolver: PathResolver,161}162#[cfg(feature = "explaining-traces")]163impl TraceFormat for ExplainingFormat {164	fn write_trace(165		&self,166		out: &mut dyn std::fmt::Write,167		evaluation_state: &EvaluationState,168		error: &LocError,169	) -> Result<(), std::fmt::Error> {170		use annotate_snippets::{171			display_list::{DisplayList, FormatOptions},172			snippet::{AnnotationType, Slice, Snippet, SourceAnnotation},173		};174		writeln!(out, "{:?}", error.error())?;175		let trace = &error.trace();176		for item in trace.0.iter() {177			let desc = &item.desc;178			let source = item.location.clone();179			let start_end = evaluation_state.map_source_locations(&source.0, &[source.1, source.2]);180181			let source_fragment: String = evaluation_state182				.get_source(&source.0)183				.unwrap()184				.chars()185				.skip(start_end[0].line_start_offset)186				.take(start_end[1].line_end_offset - start_end[0].line_start_offset)187				.collect();188189			let origin = self.resolver.resolve(&source.0);190			let snippet = Snippet {191				opt: FormatOptions {192					color: true,193					..Default::default()194				},195				title: None,196				footer: vec![],197				slices: vec![Slice {198					source: &source_fragment,199					line_start: start_end[0].line,200					origin: Some(&origin),201					fold: false,202					annotations: vec![SourceAnnotation {203						label: desc,204						annotation_type: AnnotationType::Error,205						range: (206							source.1 - start_end[0].line_start_offset,207							source.2 - start_end[0].line_start_offset,208						),209					}],210				}],211			};212213			let dl = DisplayList::from(snippet);214			writeln!(out, "{}", dl)?;215		}216		Ok(())217	}218}