git.delta.rocks / jrsonnet / refs/commits / bf006f520f66

difftreelog

feat(evaluator) custom source paths

Yaroslav Bolyukin2022-08-27parent: #a7e60a9.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -137,15 +137,21 @@
 	StandaloneSuper,
 
 	#[error("can't resolve {1} from {0}")]
-	ImportFileNotFound(PathBuf, String),
+	ImportFileNotFound(SourcePath, String),
+	#[error("can't resolve absolute {0}")]
+	AbsoluteImportFileNotFound(PathBuf),
 	#[error("resolved file not found: {:?}", .0)]
 	ResolvedFileNotFound(SourcePath),
+	#[error("can't import {0}: is a directory")]
+	ImportIsADirectory(SourcePath),
 	#[error("imported file is not valid utf-8: {0:?}")]
 	ImportBadFileUtf8(SourcePath),
 	#[error("import io error: {0}")]
 	ImportIo(String),
-	#[error("tried to import {1} from {0}, but imports is not supported")]
-	ImportNotSupported(PathBuf, PathBuf),
+	#[error("tried to import {1} from {0}, but imports are not supported")]
+	ImportNotSupported(SourcePath, String),
+	#[error("tried to import {0}, but absolute imports are not supported")]
+	AbsoluteImportNotSupported(PathBuf),
 	#[error("can't import from virtual file")]
 	CantImportFromVirtualFile,
 	#[error(
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -630,15 +630,7 @@
 		}
 		i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {
 			let tmp = loc.clone().0;
-			let import_location = tmp
-				.path()
-				.map(|p| {
-					let mut p = p.to_owned();
-					p.pop();
-					p
-				})
-				.unwrap_or_default();
-			let resolved_path = s.resolve_file(&import_location, path as &str)?;
+			let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;
 			match i {
 				Import(_) => s.push(
 					CallLocation::new(loc),
modifiedcrates/jrsonnet-evaluator/src/import.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/import.rs
+++ b/crates/jrsonnet-evaluator/src/import.rs
@@ -1,51 +1,60 @@
 use std::{
 	any::Any,
+	cell::RefCell,
+	env::current_dir,
 	fs,
-	io::Read,
+	io::{ErrorKind, Read},
 	path::{Path, PathBuf},
 };
 
 use fs::File;
-use jrsonnet_parser::SourcePath;
+use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};
 
 use crate::{
-	error::{Error::*, Result},
+	error::{
+		Error::{self, *},
+		Result,
+	},
 	throw,
 };
 
 /// Implements file resolution logic for `import` and `importStr`
 pub trait ImportResolver {
-	/// Resolves real file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
+	/// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
 	/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`
 	/// where `${vendor}` is a library path.
-	fn resolve_file_relative(&self, from: &Path, path: &str) -> Result<SourcePath>;
+	///
+	/// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value
+	/// may result in panic
+	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+		throw!(ImportNotSupported(from.clone(), path.into()))
+	}
+	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
+		self.resolve_from(&SourcePath::default(), path)
+	}
+	/// Resolves absolute path, doesn't supports jpath and other fancy things
+	fn resolve(&self, path: &Path) -> Result<SourcePath> {
+		throw!(AbsoluteImportNotSupported(path.to_owned()))
+	}
 
 	/// Load resolved file
-	/// This should only be called with value returned from `resolve_file`, this cannot be resolved using associated type,
-	/// as evaluator uses object instead of generic for [`ImportResolver`]
+	/// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],
+	/// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]
 	fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;
 
-	/// # Safety
-	///
-	/// For use only in bindings, should not be used elsewhere.
-	/// Implementations which are not intended to be used in bindings
-	/// should panic on call to this method.
-	unsafe fn as_any(&self) -> &dyn Any;
+	/// For downcasts
+	fn as_any(&self) -> &dyn Any;
 }
 
 /// Dummy resolver, can't resolve/load any file
 pub struct DummyImportResolver;
 impl ImportResolver for DummyImportResolver {
-	fn resolve_file_relative(&self, from: &Path, path: &str) -> Result<SourcePath> {
-		throw!(ImportNotSupported(from.into(), path.into()))
-	}
-
 	fn load_file_contents(&self, _resolved: &SourcePath) -> Result<Vec<u8>> {
 		panic!("dummy resolver can't load any file")
 	}
 
-	unsafe fn as_any(&self) -> &dyn Any {
-		panic!("`as_any(&self)` is not supported by dummy resolver")
+	fn as_any(&self) -> &dyn Any {
+		self
 	}
 }
 #[allow(clippy::use_self)]
@@ -60,36 +69,82 @@
 pub struct FileImportResolver {
 	/// Library directories to search for file.
 	/// Referred to as `jpath` in original jsonnet implementation.
-	pub library_paths: Vec<PathBuf>,
+	library_paths: RefCell<Vec<PathBuf>>,
 }
+impl FileImportResolver {
+	pub fn new(jpath: Vec<PathBuf>) -> Self {
+		Self {
+			library_paths: RefCell::new(jpath),
+		}
+	}
+	/// Dynamically add new jpath, used by bindings
+	pub fn add_jpath(&self, path: PathBuf) {
+		self.library_paths.borrow_mut().push(path);
+	}
+}
 impl ImportResolver for FileImportResolver {
-	fn resolve_file_relative(&self, from: &Path, path: &str) -> Result<SourcePath> {
-		let mut direct = from.to_path_buf();
+	fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+		let mut direct = if let Some(f) = from.downcast_ref::<SourceFile>() {
+			let mut o = f.path().to_owned();
+			o.pop();
+			o
+		} else if let Some(d) = from.downcast_ref::<SourceDirectory>() {
+			d.path().to_owned()
+		} else if from.is_default() {
+			current_dir().map_err(|e| Error::ImportIo(e.to_string()))?
+		} else {
+			unreachable!("resolver can't return this path")
+		};
 		direct.push(path);
-		if direct.exists() {
-			Ok(SourcePath::Path(
+		if direct.is_file() {
+			Ok(SourcePath::new(SourceFile::new(
 				direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
-			))
+			)))
 		} else {
-			for library_path in &self.library_paths {
+			for library_path in self.library_paths.borrow().iter() {
 				let mut cloned = library_path.clone();
 				cloned.push(path);
 				if cloned.exists() {
-					return Ok(SourcePath::Path(
+					return Ok(SourcePath::new(SourceFile::new(
 						cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
-					));
+					)));
 				}
 			}
-			throw!(ImportFileNotFound(from.to_owned(), path.to_owned()))
+			throw!(ImportFileNotFound(from.clone(), path.to_owned()))
+		}
+	}
+	fn resolve(&self, path: &Path) -> Result<SourcePath> {
+		let meta = match fs::metadata(path) {
+			Ok(v) => v,
+			Err(e) if e.kind() == ErrorKind::NotFound => {
+				throw!(AbsoluteImportFileNotFound(path.to_owned()))
+			}
+			Err(e) => throw!(Error::ImportIo(e.to_string())),
+		};
+		if meta.is_file() {
+			Ok(SourcePath::new(SourceFile::new(
+				path.canonicalize()
+					.map_err(|e| ImportIo(e.to_string()))?
+					.to_owned(),
+			)))
+		} else if meta.is_dir() {
+			Ok(SourcePath::new(SourceDirectory::new(
+				path.canonicalize()
+					.map_err(|e| ImportIo(e.to_string()))?
+					.to_owned(),
+			)))
+		} else {
+			unreachable!("this can't be a symlink")
 		}
 	}
 
 	fn load_file_contents(&self, id: &SourcePath) -> Result<Vec<u8>> {
-		let path = match id {
-			SourcePath::Path(path) => path,
-			_ => {
-				panic!("this resolver can only resolve to path")
-			}
+		let path = if let Some(f) = id.downcast_ref::<SourceFile>() {
+			f.path()
+		} else if id.downcast_ref::<SourceDirectory>().is_some() || id.is_default() {
+			throw!(Error::ImportIsADirectory(id.clone()))
+		} else {
+			unreachable!("other types are not supported in resolve");
 		};
 		let mut file = File::open(path).map_err(|_e| ResolvedFileNotFound(id.clone()))?;
 		let mut out = Vec::new();
@@ -97,7 +152,12 @@
 			.map_err(|e| ImportIo(e.to_string()))?;
 		Ok(out)
 	}
-	unsafe fn as_any(&self) -> &dyn Any {
-		panic!("this resolver can't be used as any")
+
+	fn as_any(&self) -> &dyn Any {
+		self
+	}
+
+	fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
+		self.resolve_from(&SourcePath::default(), path)
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/lib.rs
1#![warn(clippy::all, clippy::nursery, clippy::pedantic)]2#![allow(3	macro_expanded_macro_exports_accessed_by_absolute_paths,4	clippy::ptr_arg,5	// Too verbose6	clippy::must_use_candidate,7	// A lot of functions pass around errors thrown by code8	clippy::missing_errors_doc,9	// A lot of pointers have interior Rc10	clippy::needless_pass_by_value,11	// Its fine12	clippy::wildcard_imports,13	clippy::enum_glob_use,14	clippy::module_name_repetitions,15	// TODO: fix individual issues, however this works as intended almost everywhere16	clippy::cast_precision_loss,17	clippy::cast_possible_wrap,18	clippy::cast_possible_truncation,19	clippy::cast_sign_loss,20	// False positives21	// https://github.com/rust-lang/rust-clippy/issues/690222	clippy::use_self,23	// https://github.com/rust-lang/rust-clippy/issues/853924	clippy::iter_with_drain,25)]2627// For jrsonnet-macros28extern crate self as jrsonnet_evaluator;2930mod ctx;31mod dynamic;32pub mod error;33mod evaluate;34pub mod function;35pub mod gc;36mod import;37mod integrations;38mod map;39mod obj;40pub mod stdlib;41pub mod trace;42pub mod typed;43pub mod val;4445use std::{46	any::Any,47	borrow::Cow,48	cell::{Ref, RefCell, RefMut},49	collections::HashMap,50	fmt::{self, Debug},51	path::Path,52	rc::Rc,53};5455pub use ctx::*;56pub use dynamic::*;57use error::{Error::*, LocError, Result, StackTraceElement};58pub use evaluate::*;59use function::{CallLocation, TlaArg};60use gc::{GcHashMap, TraceBox};61use hashbrown::hash_map::RawEntryMut;62pub use import::*;63use jrsonnet_gcmodule::{Cc, Trace};64pub use jrsonnet_interner::{IBytes, IStr};65pub use jrsonnet_parser as parser;66use jrsonnet_parser::*;67pub use obj::*;68use trace::{CompactFormat, TraceFormat};69pub use val::{ManifestFormat, Thunk, Val};7071pub trait Unbound: Trace {72	type Bound;73	fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;74}7576#[derive(Clone, Trace)]77pub enum LazyBinding {78	Bindable(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),79	Bound(Thunk<Val>),80}8182impl Debug for LazyBinding {83	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {84		write!(f, "LazyBinding")85	}86}87impl LazyBinding {88	pub fn evaluate(89		&self,90		s: State,91		sup: Option<ObjValue>,92		this: Option<ObjValue>,93	) -> Result<Thunk<Val>> {94		match self {95			Self::Bindable(v) => v.bind(s, sup, this),96			Self::Bound(v) => Ok(v.clone()),97		}98	}99}100101/// During import, this trait will be called to create initial context for file102/// It may initialize global variables, stdlib for example103pub trait ContextInitializer {104	fn initialize(&self, state: State, for_file: Source) -> Context;105106	/// # Safety107	///108	/// For use only in bindings, should not be used elsewhere.109	/// Implementations which are not intended to be used in bindings110	/// should panic on call to this method.111	unsafe fn as_any(&self) -> &dyn Any;112}113114/// Context initializer, which adds noth115pub struct DummyContextInitializer;116impl ContextInitializer for DummyContextInitializer {117	fn initialize(&self, _state: State, _for_file: Source) -> Context {118		Context::default()119	}120	unsafe fn as_any(&self) -> &dyn Any {121		panic!("`as_any(&self)` is not supported by dummy initializer")122	}123}124125pub struct EvaluationSettings {126	/// Limits recursion by limiting the number of stack frames127	pub max_stack: usize,128	/// Limits amount of stack trace items preserved129	pub max_trace: usize,130	/// TLA vars131	pub tla_vars: HashMap<IStr, TlaArg>,132	/// Context initializer, which will be used for imports and everything133	/// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`134	pub context_initializer: Box<dyn ContextInitializer>,135	/// Used to resolve file locations/contents136	pub import_resolver: Box<dyn ImportResolver>,137	/// Used in manifestification functions138	pub manifest_format: ManifestFormat,139	/// Used for bindings140	pub trace_format: Box<dyn TraceFormat>,141}142impl Default for EvaluationSettings {143	fn default() -> Self {144		Self {145			max_stack: 200,146			max_trace: 20,147			context_initializer: Box::new(DummyContextInitializer),148			tla_vars: HashMap::default(),149			import_resolver: Box::new(DummyImportResolver),150			manifest_format: ManifestFormat::Json {151				padding: 4,152				#[cfg(feature = "exp-preserve-order")]153				preserve_order: false,154			},155			trace_format: Box::new(CompactFormat {156				padding: 4,157				resolver: trace::PathResolver::Absolute,158			}),159		}160	}161}162163#[derive(Default)]164struct EvaluationData {165	/// Used for stack overflow detection, stacktrace is populated on unwind166	stack_depth: usize,167	/// Updated every time stack entry is popt168	stack_generation: usize,169170	breakpoints: Breakpoints,171172	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces173	files: GcHashMap<SourcePath, FileData>,174}175struct FileData {176	string: Option<IStr>,177	bytes: Option<IBytes>,178	parsed: Option<LocExpr>,179	evaluated: Option<Val>,180181	evaluating: bool,182}183impl FileData {184	fn new_string(data: IStr) -> Self {185		Self {186			string: Some(data),187			bytes: None,188			parsed: None,189			evaluated: None,190			evaluating: false,191		}192	}193	fn new_bytes(data: IBytes) -> Self {194		Self {195			string: None,196			bytes: Some(data),197			parsed: None,198			evaluated: None,199			evaluating: false,200		}201	}202}203204#[allow(clippy::type_complexity)]205pub struct Breakpoint {206	loc: ExprLocation,207	collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,208}209#[derive(Default)]210struct Breakpoints(Vec<Rc<Breakpoint>>);211impl Breakpoints {212	fn insert(213		&self,214		stack_depth: usize,215		stack_generation: usize,216		loc: &ExprLocation,217		result: Result<Val>,218	) -> Result<Val> {219		if self.0.is_empty() {220			return result;221		}222		for item in &self.0 {223			if item.loc.belongs_to(loc) {224				let mut collected = item.collected.borrow_mut();225				let (depth, vals) = collected.entry(stack_generation).or_default();226				if stack_depth > *depth {227					vals.clear();228				}229				vals.push(result.clone());230			}231		}232		result233	}234}235236#[derive(Default)]237pub struct EvaluationStateInternals {238	/// Internal state239	data: RefCell<EvaluationData>,240	/// Settings, safe to change at runtime241	settings: RefCell<EvaluationSettings>,242}243244/// Maintains stack trace and import resolution245#[derive(Default, Clone)]246pub struct State(Rc<EvaluationStateInternals>);247248impl State {249	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise250	pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {251		let mut data = self.data_mut();252		let mut file = data.files.raw_entry_mut().from_key(&path);253254		let file = match file {255			RawEntryMut::Occupied(ref mut d) => d.get_mut(),256			RawEntryMut::Vacant(v) => {257				let data = self.settings().import_resolver.load_file_contents(&path)?;258				v.insert(259					path.clone(),260					FileData::new_string(261						std::str::from_utf8(&data)262							.map_err(|_| ImportBadFileUtf8(path.clone()))?263							.into(),264					),265				)266				.1267			}268		};269		if let Some(str) = &file.string {270			return Ok(str.clone());271		}272		if file.string.is_none() {273			file.string = Some(274				file.bytes275					.as_ref()276					.expect("either string or bytes should be set")277					.clone()278					.cast_str()279					.ok_or_else(|| ImportBadFileUtf8(path.clone()))?,280			);281		}282		Ok(file.string.as_ref().expect("just set").clone())283	}284	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise285	pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {286		let mut data = self.data_mut();287		let mut file = data.files.raw_entry_mut().from_key(&path);288289		let file = match file {290			RawEntryMut::Occupied(ref mut d) => d.get_mut(),291			RawEntryMut::Vacant(v) => {292				let data = self.settings().import_resolver.load_file_contents(&path)?;293				v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))294					.1295			}296		};297		if let Some(str) = &file.bytes {298			return Ok(str.clone());299		}300		if file.bytes.is_none() {301			file.bytes = Some(302				file.string303					.as_ref()304					.expect("either string or bytes should be set")305					.clone()306					.cast_bytes(),307			);308		}309		Ok(file.bytes.as_ref().expect("just set").clone())310	}311	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise312	pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {313		let mut data = self.data_mut();314		let mut file = data.files.raw_entry_mut().from_key(&path);315316		let file = match file {317			RawEntryMut::Occupied(ref mut d) => d.get_mut(),318			RawEntryMut::Vacant(v) => {319				let data = self.settings().import_resolver.load_file_contents(&path)?;320				v.insert(321					path.clone(),322					FileData::new_string(323						std::str::from_utf8(&data)324							.map_err(|_| ImportBadFileUtf8(path.clone()))?325							.into(),326					),327				)328				.1329			}330		};331		if let Some(val) = &file.evaluated {332			return Ok(val.clone());333		}334		if file.string.is_none() {335			file.string = Some(336				std::str::from_utf8(337					file.bytes338						.as_ref()339						.expect("either string or bytes should be set"),340				)341				.map_err(|_| ImportBadFileUtf8(path.clone()))?342				.into(),343			);344		}345		let code = file.string.as_ref().expect("just set");346		let file_name =347			Source::new(path.clone(), code.clone()).expect("resolver should return correct name");348		if file.parsed.is_none() {349			file.parsed = Some(350				jrsonnet_parser::parse(351					code,352					&ParserSettings {353						file_name: file_name.clone(),354					},355				)356				.map_err(|e| ImportSyntaxError {357					path: file_name.clone(),358					error: Box::new(e),359				})?,360			);361		}362		let parsed = file.parsed.as_ref().expect("just set").clone();363		if file.evaluating {364			throw!(InfiniteRecursionDetected)365		}366		file.evaluating = true;367		// Dropping file here, as it borrows data, which may be used in evaluation368		drop(data);369		let res = evaluate(370			self.clone(),371			self.create_default_context(file_name),372			&parsed,373		);374375		let mut data = self.data_mut();376		let mut file = data.files.raw_entry_mut().from_key(&path);377378		let file = match file {379			RawEntryMut::Occupied(ref mut d) => d.get_mut(),380			RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),381		};382		file.evaluating = false;383		match res {384			Ok(v) => {385				file.evaluated = Some(v.clone());386				Ok(v)387			}388			Err(e) => Err(e),389		}390	}391	pub fn import(&self, from: &Path, path: &str) -> Result<Val> {392		let resolved = self.resolve_file(from, path)?;393		self.import_resolved(resolved)394	}395396	/// Creates context with all passed global variables397	pub fn create_default_context(&self, source: Source) -> Context {398		let context_initializer = &self.settings().context_initializer;399		context_initializer.initialize(self.clone(), source)400	}401402	/// Executes code creating a new stack frame403	pub fn push<T>(404		&self,405		e: CallLocation,406		frame_desc: impl FnOnce() -> String,407		f: impl FnOnce() -> Result<T>,408	) -> Result<T> {409		{410			let mut data = self.data_mut();411			let stack_depth = &mut data.stack_depth;412			if *stack_depth > self.max_stack() {413				// Error creation uses data, so i drop guard here414				drop(data);415				throw!(StackOverflow);416			}417			*stack_depth += 1;418		}419		let result = f();420		{421			let mut data = self.data_mut();422			data.stack_depth -= 1;423			data.stack_generation += 1;424		}425		if let Err(mut err) = result {426			err.trace_mut().0.push(StackTraceElement {427				location: e.0.cloned(),428				desc: frame_desc(),429			});430			return Err(err);431		}432		result433	}434435	/// Executes code creating a new stack frame436	pub fn push_val(437		&self,438		e: &ExprLocation,439		frame_desc: impl FnOnce() -> String,440		f: impl FnOnce() -> Result<Val>,441	) -> Result<Val> {442		{443			let mut data = self.data_mut();444			let stack_depth = &mut data.stack_depth;445			if *stack_depth > self.max_stack() {446				// Error creation uses data, so i drop guard here447				drop(data);448				throw!(StackOverflow);449			}450			*stack_depth += 1;451		}452		let mut result = f();453		{454			let mut data = self.data_mut();455			data.stack_depth -= 1;456			data.stack_generation += 1;457			result = data458				.breakpoints459				.insert(data.stack_depth, data.stack_generation, e, result);460		}461		if let Err(mut err) = result {462			err.trace_mut().0.push(StackTraceElement {463				location: Some(e.clone()),464				desc: frame_desc(),465			});466			return Err(err);467		}468		result469	}470	/// Executes code creating a new stack frame471	pub fn push_description<T>(472		&self,473		frame_desc: impl FnOnce() -> String,474		f: impl FnOnce() -> Result<T>,475	) -> Result<T> {476		{477			let mut data = self.data_mut();478			let stack_depth = &mut data.stack_depth;479			if *stack_depth > self.max_stack() {480				// Error creation uses data, so i drop guard here481				drop(data);482				throw!(StackOverflow);483			}484			*stack_depth += 1;485		}486		let result = f();487		{488			let mut data = self.data_mut();489			data.stack_depth -= 1;490			data.stack_generation += 1;491		}492		if let Err(mut err) = result {493			err.trace_mut().0.push(StackTraceElement {494				location: None,495				desc: frame_desc(),496			});497			return Err(err);498		}499		result500	}501502	/// # Panics503	/// In case of formatting failure504	pub fn stringify_err(&self, e: &LocError) -> String {505		let mut out = String::new();506		self.settings()507			.trace_format508			.write_trace(&mut out, self, e)509			.unwrap();510		out511	}512513	pub fn manifest(&self, val: Val) -> Result<IStr> {514		self.push_description(515			|| "manifestification".to_string(),516			|| val.manifest(self.clone(), &self.manifest_format()),517		)518	}519	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {520		val.manifest_multi(self.clone(), &self.manifest_format())521	}522	pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {523		val.manifest_stream(self.clone(), &self.manifest_format())524	}525526	/// If passed value is function then call with set TLA527	pub fn with_tla(&self, val: Val) -> Result<Val> {528		Ok(match val {529			Val::Func(func) => self.push_description(530				|| "during TLA call".to_owned(),531				|| {532					func.evaluate(533						self.clone(),534						self.create_default_context(Source::new_virtual(535							Cow::Borrowed("<tla>"),536							IStr::empty(),537						)),538						CallLocation::native(),539						&self.settings().tla_vars,540						true,541					)542				},543			)?,544			v => v,545		})546	}547}548549/// Internals550impl State {551	// fn data(&self) -> Ref<EvaluationData> {552	// 	self.0.data.borrow()553	// }554	fn data_mut(&self) -> RefMut<EvaluationData> {555		self.0.data.borrow_mut()556	}557	pub fn settings(&self) -> Ref<EvaluationSettings> {558		self.0.settings.borrow()559	}560	pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {561		self.0.settings.borrow_mut()562	}563}564565/// Raw methods evaluate passed values but don't perform TLA execution566impl State {567	/// Parses and evaluates the given snippet568	pub fn evaluate_snippet(&self, name: String, code: impl Into<IStr>) -> Result<Val> {569		let code = code.into();570		let source = Source::new_virtual(Cow::Owned(name), code.clone());571		let parsed = jrsonnet_parser::parse(572			&code,573			&ParserSettings {574				file_name: source.clone(),575			},576		)577		.map_err(|e| ImportSyntaxError {578			path: source.clone(),579			error: Box::new(e),580		})?;581		evaluate(self.clone(), self.create_default_context(source), &parsed)582	}583}584585/// Settings utilities586impl State {587	pub fn add_tla(&self, name: IStr, value: Val) {588		self.settings_mut()589			.tla_vars590			.insert(name, TlaArg::Val(value));591	}592	pub fn add_tla_str(&self, name: IStr, value: IStr) {593		self.settings_mut()594			.tla_vars595			.insert(name, TlaArg::String(value));596	}597	pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {598		let source_name = format!("<top-level-arg:{}>", name);599		let source = Source::new_virtual(Cow::Owned(source_name), code.into());600		let parsed = jrsonnet_parser::parse(601			code,602			&ParserSettings {603				file_name: source.clone(),604			},605		)606		.map_err(|e| ImportSyntaxError {607			path: source,608			error: Box::new(e),609		})?;610		self.settings_mut()611			.tla_vars612			.insert(name, TlaArg::Code(parsed));613		Ok(())614	}615616	pub fn resolve_file(&self, from: &Path, path: &str) -> Result<SourcePath> {617		self.settings()618			.import_resolver619			.resolve_file_relative(from, path.as_ref())620	}621622	pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {623		Ref::map(self.settings(), |s| &*s.import_resolver)624	}625	pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {626		self.settings_mut().import_resolver = resolver;627	}628	pub fn context_initializer(&self) -> Ref<dyn ContextInitializer> {629		Ref::map(self.settings(), |s| &*s.context_initializer)630	}631632	pub fn manifest_format(&self) -> ManifestFormat {633		self.settings().manifest_format.clone()634	}635	pub fn set_manifest_format(&self, format: ManifestFormat) {636		self.settings_mut().manifest_format = format;637	}638639	pub fn trace_format(&self) -> Ref<dyn TraceFormat> {640		Ref::map(self.settings(), |s| &*s.trace_format)641	}642	pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {643		self.settings_mut().trace_format = format;644	}645646	pub fn max_trace(&self) -> usize {647		self.settings().max_trace648	}649	pub fn set_max_trace(&self, trace: usize) {650		self.settings_mut().max_trace = trace;651	}652653	pub fn max_stack(&self) -> usize {654		self.settings().max_stack655	}656	pub fn set_max_stack(&self, trace: usize) {657		self.settings_mut().max_stack = trace;658	}659}
after · crates/jrsonnet-evaluator/src/lib.rs
1#![warn(clippy::all, clippy::nursery, clippy::pedantic)]2#![allow(3	macro_expanded_macro_exports_accessed_by_absolute_paths,4	clippy::ptr_arg,5	// Too verbose6	clippy::must_use_candidate,7	// A lot of functions pass around errors thrown by code8	clippy::missing_errors_doc,9	// A lot of pointers have interior Rc10	clippy::needless_pass_by_value,11	// Its fine12	clippy::wildcard_imports,13	clippy::enum_glob_use,14	clippy::module_name_repetitions,15	// TODO: fix individual issues, however this works as intended almost everywhere16	clippy::cast_precision_loss,17	clippy::cast_possible_wrap,18	clippy::cast_possible_truncation,19	clippy::cast_sign_loss,20	// False positives21	// https://github.com/rust-lang/rust-clippy/issues/690222	clippy::use_self,23	// https://github.com/rust-lang/rust-clippy/issues/853924	clippy::iter_with_drain,25)]2627// For jrsonnet-macros28extern crate self as jrsonnet_evaluator;2930mod ctx;31mod dynamic;32pub mod error;33mod evaluate;34pub mod function;35pub mod gc;36mod import;37mod integrations;38mod map;39mod obj;40pub mod stdlib;41pub mod trace;42pub mod typed;43pub mod val;4445use std::{46	any::Any,47	cell::{Ref, RefCell, RefMut},48	collections::HashMap,49	fmt::{self, Debug},50	path::Path,51	rc::Rc,52};5354pub use ctx::*;55pub use dynamic::*;56use error::{Error::*, LocError, Result, StackTraceElement};57pub use evaluate::*;58use function::{CallLocation, TlaArg};59use gc::{GcHashMap, TraceBox};60use hashbrown::hash_map::RawEntryMut;61pub use import::*;62use jrsonnet_gcmodule::{Cc, Trace};63pub use jrsonnet_interner::{IBytes, IStr};64pub use jrsonnet_parser as parser;65use jrsonnet_parser::*;66pub use obj::*;67use trace::{CompactFormat, TraceFormat};68pub use val::{ManifestFormat, Thunk, Val};6970pub trait Unbound: Trace {71	type Bound;72	fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;73}7475#[derive(Clone, Trace)]76pub enum LazyBinding {77	Bindable(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),78	Bound(Thunk<Val>),79}8081impl Debug for LazyBinding {82	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {83		write!(f, "LazyBinding")84	}85}86impl LazyBinding {87	pub fn evaluate(88		&self,89		s: State,90		sup: Option<ObjValue>,91		this: Option<ObjValue>,92	) -> Result<Thunk<Val>> {93		match self {94			Self::Bindable(v) => v.bind(s, sup, this),95			Self::Bound(v) => Ok(v.clone()),96		}97	}98}99100/// During import, this trait will be called to create initial context for file101/// It may initialize global variables, stdlib for example102pub trait ContextInitializer {103	fn initialize(&self, state: State, for_file: Source) -> Context;104105	fn as_any(&self) -> &dyn Any;106}107108/// Context initializer, which adds noth109pub struct DummyContextInitializer;110impl ContextInitializer for DummyContextInitializer {111	fn initialize(&self, _state: State, _for_file: Source) -> Context {112		Context::default()113	}114	fn as_any(&self) -> &dyn Any {115		self116	}117}118119pub struct EvaluationSettings {120	/// Limits recursion by limiting the number of stack frames121	pub max_stack: usize,122	/// Limits amount of stack trace items preserved123	pub max_trace: usize,124	/// TLA vars125	pub tla_vars: HashMap<IStr, TlaArg>,126	/// Context initializer, which will be used for imports and everything127	/// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`128	pub context_initializer: Box<dyn ContextInitializer>,129	/// Used to resolve file locations/contents130	pub import_resolver: Box<dyn ImportResolver>,131	/// Used in manifestification functions132	pub manifest_format: ManifestFormat,133	/// Used for bindings134	pub trace_format: Box<dyn TraceFormat>,135}136impl Default for EvaluationSettings {137	fn default() -> Self {138		Self {139			max_stack: 200,140			max_trace: 20,141			context_initializer: Box::new(DummyContextInitializer),142			tla_vars: HashMap::default(),143			import_resolver: Box::new(DummyImportResolver),144			manifest_format: ManifestFormat::Json {145				padding: 4,146				#[cfg(feature = "exp-preserve-order")]147				preserve_order: false,148			},149			trace_format: Box::new(CompactFormat {150				padding: 4,151				resolver: trace::PathResolver::Absolute,152			}),153		}154	}155}156157#[derive(Default)]158struct EvaluationData {159	/// Used for stack overflow detection, stacktrace is populated on unwind160	stack_depth: usize,161	/// Updated every time stack entry is popt162	stack_generation: usize,163164	breakpoints: Breakpoints,165166	/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces167	files: GcHashMap<SourcePath, FileData>,168}169struct FileData {170	string: Option<IStr>,171	bytes: Option<IBytes>,172	parsed: Option<LocExpr>,173	evaluated: Option<Val>,174175	evaluating: bool,176}177impl FileData {178	fn new_string(data: IStr) -> Self {179		Self {180			string: Some(data),181			bytes: None,182			parsed: None,183			evaluated: None,184			evaluating: false,185		}186	}187	fn new_bytes(data: IBytes) -> Self {188		Self {189			string: None,190			bytes: Some(data),191			parsed: None,192			evaluated: None,193			evaluating: false,194		}195	}196}197198#[allow(clippy::type_complexity)]199pub struct Breakpoint {200	loc: ExprLocation,201	collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,202}203#[derive(Default)]204struct Breakpoints(Vec<Rc<Breakpoint>>);205impl Breakpoints {206	fn insert(207		&self,208		stack_depth: usize,209		stack_generation: usize,210		loc: &ExprLocation,211		result: Result<Val>,212	) -> Result<Val> {213		if self.0.is_empty() {214			return result;215		}216		for item in &self.0 {217			if item.loc.belongs_to(loc) {218				let mut collected = item.collected.borrow_mut();219				let (depth, vals) = collected.entry(stack_generation).or_default();220				if stack_depth > *depth {221					vals.clear();222				}223				vals.push(result.clone());224			}225		}226		result227	}228}229230#[derive(Default)]231pub struct EvaluationStateInternals {232	/// Internal state233	data: RefCell<EvaluationData>,234	/// Settings, safe to change at runtime235	settings: RefCell<EvaluationSettings>,236}237238/// Maintains stack trace and import resolution239#[derive(Default, Clone)]240pub struct State(Rc<EvaluationStateInternals>);241242impl State {243	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise244	pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {245		let mut data = self.data_mut();246		let mut file = data.files.raw_entry_mut().from_key(&path);247248		let file = match file {249			RawEntryMut::Occupied(ref mut d) => d.get_mut(),250			RawEntryMut::Vacant(v) => {251				let data = self.settings().import_resolver.load_file_contents(&path)?;252				v.insert(253					path.clone(),254					FileData::new_string(255						std::str::from_utf8(&data)256							.map_err(|_| ImportBadFileUtf8(path.clone()))?257							.into(),258					),259				)260				.1261			}262		};263		if let Some(str) = &file.string {264			return Ok(str.clone());265		}266		if file.string.is_none() {267			file.string = Some(268				file.bytes269					.as_ref()270					.expect("either string or bytes should be set")271					.clone()272					.cast_str()273					.ok_or_else(|| ImportBadFileUtf8(path.clone()))?,274			);275		}276		Ok(file.string.as_ref().expect("just set").clone())277	}278	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise279	pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {280		let mut data = self.data_mut();281		let mut file = data.files.raw_entry_mut().from_key(&path);282283		let file = match file {284			RawEntryMut::Occupied(ref mut d) => d.get_mut(),285			RawEntryMut::Vacant(v) => {286				let data = self.settings().import_resolver.load_file_contents(&path)?;287				v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))288					.1289			}290		};291		if let Some(str) = &file.bytes {292			return Ok(str.clone());293		}294		if file.bytes.is_none() {295			file.bytes = Some(296				file.string297					.as_ref()298					.expect("either string or bytes should be set")299					.clone()300					.cast_bytes(),301			);302		}303		Ok(file.bytes.as_ref().expect("just set").clone())304	}305	/// Should only be called with path retrieved from [`resolve_path`], may panic otherwise306	pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {307		let mut data = self.data_mut();308		let mut file = data.files.raw_entry_mut().from_key(&path);309310		let file = match file {311			RawEntryMut::Occupied(ref mut d) => d.get_mut(),312			RawEntryMut::Vacant(v) => {313				let data = self.settings().import_resolver.load_file_contents(&path)?;314				v.insert(315					path.clone(),316					FileData::new_string(317						std::str::from_utf8(&data)318							.map_err(|_| ImportBadFileUtf8(path.clone()))?319							.into(),320					),321				)322				.1323			}324		};325		if let Some(val) = &file.evaluated {326			return Ok(val.clone());327		}328		if file.string.is_none() {329			file.string = Some(330				std::str::from_utf8(331					file.bytes332						.as_ref()333						.expect("either string or bytes should be set"),334				)335				.map_err(|_| ImportBadFileUtf8(path.clone()))?336				.into(),337			);338		}339		let code = file.string.as_ref().expect("just set");340		let file_name = Source::new(path.clone(), code.clone());341		if file.parsed.is_none() {342			file.parsed = Some(343				jrsonnet_parser::parse(344					code,345					&ParserSettings {346						file_name: file_name.clone(),347					},348				)349				.map_err(|e| ImportSyntaxError {350					path: file_name.clone(),351					error: Box::new(e),352				})?,353			);354		}355		let parsed = file.parsed.as_ref().expect("just set").clone();356		if file.evaluating {357			throw!(InfiniteRecursionDetected)358		}359		file.evaluating = true;360		// Dropping file here, as it borrows data, which may be used in evaluation361		drop(data);362		let res = evaluate(363			self.clone(),364			self.create_default_context(file_name),365			&parsed,366		);367368		let mut data = self.data_mut();369		let mut file = data.files.raw_entry_mut().from_key(&path);370371		let file = match file {372			RawEntryMut::Occupied(ref mut d) => d.get_mut(),373			RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),374		};375		file.evaluating = false;376		match res {377			Ok(v) => {378				file.evaluated = Some(v.clone());379				Ok(v)380			}381			Err(e) => Err(e),382		}383	}384385	/// Has same semantics as `import 'path'` called from `from` file386	pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {387		let resolved = self.resolve_from(from, path)?;388		self.import_resolved(resolved)389	}390	pub fn import(&self, path: &impl AsRef<Path>) -> Result<Val> {391		let resolved = self.resolve(path)?;392		self.import_resolved(resolved)393	}394395	/// Creates context with all passed global variables396	pub fn create_default_context(&self, source: Source) -> Context {397		let context_initializer = &self.settings().context_initializer;398		context_initializer.initialize(self.clone(), source)399	}400401	/// Executes code creating a new stack frame402	pub fn push<T>(403		&self,404		e: CallLocation,405		frame_desc: impl FnOnce() -> String,406		f: impl FnOnce() -> Result<T>,407	) -> Result<T> {408		{409			let mut data = self.data_mut();410			let stack_depth = &mut data.stack_depth;411			if *stack_depth > self.max_stack() {412				// Error creation uses data, so i drop guard here413				drop(data);414				throw!(StackOverflow);415			}416			*stack_depth += 1;417		}418		let result = f();419		{420			let mut data = self.data_mut();421			data.stack_depth -= 1;422			data.stack_generation += 1;423		}424		if let Err(mut err) = result {425			err.trace_mut().0.push(StackTraceElement {426				location: e.0.cloned(),427				desc: frame_desc(),428			});429			return Err(err);430		}431		result432	}433434	/// Executes code creating a new stack frame435	pub fn push_val(436		&self,437		e: &ExprLocation,438		frame_desc: impl FnOnce() -> String,439		f: impl FnOnce() -> Result<Val>,440	) -> Result<Val> {441		{442			let mut data = self.data_mut();443			let stack_depth = &mut data.stack_depth;444			if *stack_depth > self.max_stack() {445				// Error creation uses data, so i drop guard here446				drop(data);447				throw!(StackOverflow);448			}449			*stack_depth += 1;450		}451		let mut result = f();452		{453			let mut data = self.data_mut();454			data.stack_depth -= 1;455			data.stack_generation += 1;456			result = data457				.breakpoints458				.insert(data.stack_depth, data.stack_generation, e, result);459		}460		if let Err(mut err) = result {461			err.trace_mut().0.push(StackTraceElement {462				location: Some(e.clone()),463				desc: frame_desc(),464			});465			return Err(err);466		}467		result468	}469	/// Executes code creating a new stack frame470	pub fn push_description<T>(471		&self,472		frame_desc: impl FnOnce() -> String,473		f: impl FnOnce() -> Result<T>,474	) -> Result<T> {475		{476			let mut data = self.data_mut();477			let stack_depth = &mut data.stack_depth;478			if *stack_depth > self.max_stack() {479				// Error creation uses data, so i drop guard here480				drop(data);481				throw!(StackOverflow);482			}483			*stack_depth += 1;484		}485		let result = f();486		{487			let mut data = self.data_mut();488			data.stack_depth -= 1;489			data.stack_generation += 1;490		}491		if let Err(mut err) = result {492			err.trace_mut().0.push(StackTraceElement {493				location: None,494				desc: frame_desc(),495			});496			return Err(err);497		}498		result499	}500501	/// # Panics502	/// In case of formatting failure503	pub fn stringify_err(&self, e: &LocError) -> String {504		let mut out = String::new();505		self.settings()506			.trace_format507			.write_trace(&mut out, self, e)508			.unwrap();509		out510	}511512	pub fn manifest(&self, val: Val) -> Result<IStr> {513		self.push_description(514			|| "manifestification".to_string(),515			|| val.manifest(self.clone(), &self.manifest_format()),516		)517	}518	pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {519		val.manifest_multi(self.clone(), &self.manifest_format())520	}521	pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {522		val.manifest_stream(self.clone(), &self.manifest_format())523	}524525	/// If passed value is function then call with set TLA526	pub fn with_tla(&self, val: Val) -> Result<Val> {527		Ok(match val {528			Val::Func(func) => self.push_description(529				|| "during TLA call".to_owned(),530				|| {531					func.evaluate(532						self.clone(),533						self.create_default_context(Source::new_virtual(534							"<tla>".into(),535							IStr::empty(),536						)),537						CallLocation::native(),538						&self.settings().tla_vars,539						true,540					)541				},542			)?,543			v => v,544		})545	}546}547548/// Internals549impl State {550	// fn data(&self) -> Ref<EvaluationData> {551	// 	self.0.data.borrow()552	// }553	fn data_mut(&self) -> RefMut<EvaluationData> {554		self.0.data.borrow_mut()555	}556	pub fn settings(&self) -> Ref<EvaluationSettings> {557		self.0.settings.borrow()558	}559	pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {560		self.0.settings.borrow_mut()561	}562}563564/// Raw methods evaluate passed values but don't perform TLA execution565impl State {566	/// Parses and evaluates the given snippet567	pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {568		let code = code.into();569		let source = Source::new_virtual(name.into(), code.clone());570		let parsed = jrsonnet_parser::parse(571			&code,572			&ParserSettings {573				file_name: source.clone(),574			},575		)576		.map_err(|e| ImportSyntaxError {577			path: source.clone(),578			error: Box::new(e),579		})?;580		evaluate(self.clone(), self.create_default_context(source), &parsed)581	}582}583584/// Settings utilities585impl State {586	pub fn add_tla(&self, name: IStr, value: Val) {587		self.settings_mut()588			.tla_vars589			.insert(name, TlaArg::Val(value));590	}591	pub fn add_tla_str(&self, name: IStr, value: IStr) {592		self.settings_mut()593			.tla_vars594			.insert(name, TlaArg::String(value));595	}596	pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {597		let source_name = format!("<top-level-arg:{}>", name);598		let source = Source::new_virtual(source_name.into(), code.into());599		let parsed = jrsonnet_parser::parse(600			code,601			&ParserSettings {602				file_name: source.clone(),603			},604		)605		.map_err(|e| ImportSyntaxError {606			path: source,607			error: Box::new(e),608		})?;609		self.settings_mut()610			.tla_vars611			.insert(name, TlaArg::Code(parsed));612		Ok(())613	}614615	// Only panics in case of [`ImportResolver`] contract violation616	#[allow(clippy::missing_panics_doc)]617	pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {618		self.import_resolver().resolve_from(from, path.as_ref())619	}620621	// Only panics in case of [`ImportResolver`] contract violation622	#[allow(clippy::missing_panics_doc)]623	pub fn resolve(&self, path: &impl AsRef<Path>) -> Result<SourcePath> {624		self.import_resolver().resolve(path.as_ref())625	}626	pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {627		Ref::map(self.settings(), |s| &*s.import_resolver)628	}629	pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {630		self.settings_mut().import_resolver = resolver;631	}632	pub fn context_initializer(&self) -> Ref<dyn ContextInitializer> {633		Ref::map(self.settings(), |s| &*s.context_initializer)634	}635636	pub fn manifest_format(&self) -> ManifestFormat {637		self.settings().manifest_format.clone()638	}639	pub fn set_manifest_format(&self, format: ManifestFormat) {640		self.settings_mut().manifest_format = format;641	}642643	pub fn trace_format(&self) -> Ref<dyn TraceFormat> {644		Ref::map(self.settings(), |s| &*s.trace_format)645	}646	pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {647		self.settings_mut().trace_format = format;648	}649650	pub fn max_trace(&self) -> usize {651		self.settings().max_trace652	}653	pub fn set_max_trace(&self, trace: usize) {654		self.settings_mut().max_trace = trace;655	}656657	pub fn max_stack(&self) -> usize {658		self.settings().max_stack659	}660	pub fn set_max_stack(&self, trace: usize) {661		self.settings_mut().max_stack = trace;662	}663}
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -5,6 +5,7 @@
 use crate::{error::Error, LocError, State};
 
 /// The way paths should be displayed
+#[derive(Clone)]
 pub enum PathResolver {
 	/// Only filename
 	FileName,
@@ -15,6 +16,13 @@
 }
 
 impl PathResolver {
+	/// Will return Self::Relative(cwd), or Self::Absolute on cwd failure
+	pub fn new_cwd_fallback() -> Self {
+		match std::env::current_dir() {
+			Ok(v) => Self::Relative(v),
+			Err(_) => Self::Absolute,
+		}
+	}
 	pub fn resolve(&self, from: &Path) -> String {
 		match self {
 			Self::FileName => from
@@ -89,9 +97,9 @@
 			use std::fmt::Write;
 
 			writeln!(out)?;
-			let mut n = match path.path() {
+			let mut n = match path.source_path().path() {
 				Some(r) => self.resolver.resolve(r),
-				None => path.short_display().to_string(),
+				None => path.source_path().to_string(),
 			};
 			let mut offset = error.location.offset;
 			let is_eof = if offset >= path.code().len() {
@@ -122,9 +130,9 @@
 				use std::fmt::Write;
 				#[allow(clippy::option_if_let_else)]
 				if let Some(location) = location {
-					let mut resolved_path = match location.0.path() {
+					let mut resolved_path = match location.0.source_path().path() {
 						Some(r) => self.resolver.resolve(r),
-						None => location.0.short_display().to_string(),
+						None => location.0.source_path().to_string(),
 					};
 					// TODO: Process all trace elements first
 					let location = location.0.map_source_locations(&[location.1, location.2]);
@@ -177,9 +185,9 @@
 			let desc = &item.desc;
 			if let Some(source) = &item.location {
 				let start_end = source.0.map_source_locations(&[source.1, source.2]);
-				let resolved_path = match source.0.path() {
+				let resolved_path = match source.0.source_path().path() {
 					Some(r) => r.display().to_string(),
-					None => source.0.short_display().to_string(),
+					None => source.0.source_path().to_string(),
 				};
 
 				write!(
@@ -272,9 +280,9 @@
 			.take(end.line_end_offset - end.line_start_offset)
 			.collect();
 
-		let origin = match origin.path() {
+		let origin = match origin.source_path().path() {
 			Some(r) => self.resolver.resolve(r),
-			None => origin.short_display().to_string(),
+			None => origin.source_path().to_string(),
 		};
 		let snippet = Snippet {
 			opt: FormatOptions {