git.delta.rocks / jrsonnet / refs/commits / 3738ed2a5354

difftreelog

feat(ir) source url

qkmunwwmYaroslav Bolyukin2026-05-05parent: #ede8518.patch.diff
in: master

7 files changed

modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -35,14 +35,14 @@
 
 pub use ctx::*;
 pub use dynamic::*;
-pub use error::{Error, ErrorKind::*, Result, ResultExt};
+pub use error::{Error, ErrorKind::*, Result, ResultExt, StackTraceElement};
 pub use evaluate::ensure_sufficient_stack;
 use function::CallLocation;
 pub use import::*;
 use jrsonnet_gcmodule::{Cc, Trace, cc_dyn};
 pub use jrsonnet_interner::{IBytes, IStr};
 use jrsonnet_ir::Expr;
-pub use jrsonnet_ir::{NumValue, Source, SourcePath, Span};
+pub use jrsonnet_ir::{NumValue, Source, SourcePath, SourceUrl, SourceVirtual, Span};
 #[doc(hidden)]
 pub use jrsonnet_macros;
 
@@ -396,9 +396,17 @@
 			file.parsed = Some(
 				parse_jsonnet(&code, file_name.clone())
 					.map(Rc::new)
-					.map_err(|e| ImportSyntaxError {
-						path: file_name.clone(),
-						error: Box::new(e),
+					.map_err(|e| {
+						let span = e.location.clone();
+						let mut err = Error::from(ImportSyntaxError {
+							path: file_name.clone(),
+							error: Box::new(e),
+						});
+						err.trace_mut().0.push(StackTraceElement {
+							location: Some(span),
+							desc: "parse imported".to_string(),
+						});
+						err
 					})?,
 			);
 		}
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -82,18 +82,24 @@
 ) -> Result<(), std::fmt::Error> {
 	if start.line == end.line {
 		if start.column == end.column {
-			write!(out, "{}:{}", start.line, end.column.saturating_sub(1))?;
+			write!(out, "{}:{}", start.line, start.column)?;
 		} else {
-			write!(out, "{}:{}-{}", start.line, start.column - 1, end.column)?;
+			write!(
+				out,
+				"{}:{}-{}",
+				start.line,
+				start.column,
+				end.column.saturating_sub(1)
+			)?;
 		}
 	} else {
 		write!(
 			out,
 			"{}:{}-{}:{}",
-			start.line,
-			end.column.saturating_sub(1),
 			start.line,
-			end.column
+			start.column,
+			end.line,
+			end.column.saturating_sub(1)
 		)?;
 	}
 	Ok(())
@@ -131,22 +137,13 @@
 				|| path.source_path().to_string(),
 				|r| self.resolver.resolve(r),
 			);
-			let mut offset = error.location.1 as usize;
-			let is_eof = if offset >= path.code().len() {
-				offset = path.code().len().saturating_sub(1);
-				true
-			} else {
-				false
-			};
+			let offset = (error.location.1 as usize).min(path.code().len());
 			#[expect(clippy::cast_possible_truncation, reason = "code is limited by 4gb")]
-			let mut location = path
+			let location = path
 				.map_source_locations(&[offset as u32])
 				.into_iter()
 				.next()
 				.unwrap();
-			if is_eof {
-				location.column += 1;
-			}
 
 			write!(n, ":").unwrap();
 			print_code_location(&mut n, &location, &location).unwrap();
modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/lib.rs
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -895,10 +895,16 @@
 	}
 }
 
+#[derive(Default)]
 pub struct FormatOptions {
 	// 0 for hard tabs, otherwise number of spaces
 	pub indent: u8,
 }
+impl FormatOptions {
+	pub fn new() -> Self {
+		Self::default()
+	}
+}
 
 #[allow(
 	clippy::result_large_err,
modifiedcrates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -220,10 +220,7 @@
 
 fn ident(p: &mut Parser<'_>) -> Result<IStr> {
 	if !p.at(SyntaxKind::IDENT) {
-		return Err(p.error(format!(
-			"expected identifier, got {}",
-			p.current_desc()
-		)));
+		return Err(p.error(format!("expected identifier, got {}", p.current_desc())));
 	}
 	let text = p.text();
 	p.eat_any();
modifiedcrates/jrsonnet-ir/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-ir/src/lib.rs
1#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)]23mod expr;4use std::{cmp::Ordering, fmt, ops::Deref};56pub use expr::*;7use jrsonnet_gcmodule::Acyclic;8pub use jrsonnet_interner::IStr;9pub mod function;10mod location;11mod source;12pub mod unescape;13pub mod visit;1415pub use location::CodeLocation;16pub use source::{17	Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath,18	SourcePathT, SourceVirtual,19};2021// It seels to be a wrong place for this kind of stuff, but as it would also be used for static analysis and22// is already wanted for NumValue, I don't know a better place.23#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]24pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;25#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]26pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;2728/// Represents jsonnet number29/// Jsonnet numbers are finite f64, with NaNs disallowed30#[derive(Acyclic, Clone, Copy)]31pub struct NumValue(f64);32impl NumValue {33	/// Creates a [`NumValue`], if value is finite and not NaN34	pub fn new(v: f64) -> Option<Self> {35		if !v.is_finite() {36			return None;37		}38		Some(Self(v))39	}40	#[inline]41	pub const fn get(&self) -> f64 {42		self.043	}44	pub fn truncate_for_bitwise(self) -> Result<i64, ConvertNumValueError> {45		if self.0 < MIN_SAFE_INTEGER || self.0 > MAX_SAFE_INTEGER {46			return Err(ConvertNumValueError::BitwiseSafeRange);47		}48		#[expect(clippy::cast_possible_truncation, reason = "intended")]49		Ok(self.0 as i64)50	}51}52impl PartialEq for NumValue {53	fn eq(&self, other: &Self) -> bool {54		self.0 == other.055	}56}57impl Eq for NumValue {}58impl Ord for NumValue {59	#[inline]60	fn cmp(&self, other: &Self) -> Ordering {61		// Can't use `total_cmp`: its behavior for `-0` and `0`62		// is not following wanted.63		unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }64	}65}66impl PartialOrd for NumValue {67	#[inline]68	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {69		Some(self.cmp(other))70	}71}72impl fmt::Debug for NumValue {73	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {74		fmt::Debug::fmt(&self.0, f)75	}76}77impl fmt::Display for NumValue {78	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {79		fmt::Display::fmt(&self.0, f)80	}81}82impl Deref for NumValue {83	type Target = f64;8485	#[inline]86	fn deref(&self) -> &Self::Target {87		&self.088	}89}90macro_rules! impl_num {91	($($ty:ty),+) => {$(92		impl From<$ty> for NumValue {93			#[inline]94			fn from(value: $ty) -> Self {95				Self(value.into())96			}97		}98	)+};99}100impl_num!(i8, u8, i16, u16, i32, u32);101102#[derive(Clone, Copy, Debug, thiserror::Error, Acyclic)]103pub enum ConvertNumValueError {104	#[error("overflow")]105	Overflow,106	#[error("underflow")]107	Underflow,108	#[error("non-finite")]109	NonFinite,110	#[error("float out of safe int range")]111	BitwiseSafeRange,112}113114macro_rules! impl_try_num {115	($($ty:ty),+) => {$(116		impl TryFrom<$ty> for NumValue {117			type Error = ConvertNumValueError;118			#[inline]119			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {120				#[expect(clippy::cast_precision_loss, reason = "precision loss is explicitly handled")]121				let value = value as f64;122				if value < MIN_SAFE_INTEGER {123					return Err(ConvertNumValueError::Underflow)124				} else if value > MAX_SAFE_INTEGER {125					return Err(ConvertNumValueError::Overflow)126				}127				// Number is finite.128				Ok(Self(value))129			}130		}131	)+};132}133impl_try_num!(usize, isize, i64, u64);134135impl TryFrom<f64> for NumValue {136	type Error = ConvertNumValueError;137138	#[inline]139	fn try_from(value: f64) -> Result<Self, Self::Error> {140		Self::new(value).ok_or(ConvertNumValueError::NonFinite)141	}142}143impl TryFrom<f32> for NumValue {144	type Error = ConvertNumValueError;145146	#[inline]147	fn try_from(value: f32) -> Result<Self, Self::Error> {148		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)149	}150}
after · crates/jrsonnet-ir/src/lib.rs
1#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)]23mod expr;4use std::{cmp::Ordering, fmt, ops::Deref};56pub use expr::*;7use jrsonnet_gcmodule::Acyclic;8pub use jrsonnet_interner::IStr;9pub mod function;10mod location;11mod source;12pub mod unescape;13pub mod visit;1415pub use location::CodeLocation;16pub use source::{17	Source, SourceDefaultIgnoreJpath, SourceDirectory, SourceFifo, SourceFile, SourcePath,18	SourcePathT, SourceUrl, SourceVirtual,19};2021// It seels to be a wrong place for this kind of stuff, but as it would also be used for static analysis and22// is already wanted for NumValue, I don't know a better place.23#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]24pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;25#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]26pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;2728/// Represents jsonnet number29/// Jsonnet numbers are finite f64, with NaNs disallowed30#[derive(Acyclic, Clone, Copy)]31pub struct NumValue(f64);32impl NumValue {33	/// Creates a [`NumValue`], if value is finite and not NaN34	pub fn new(v: f64) -> Option<Self> {35		if !v.is_finite() {36			return None;37		}38		Some(Self(v))39	}40	#[inline]41	pub const fn get(&self) -> f64 {42		self.043	}44	pub fn truncate_for_bitwise(self) -> Result<i64, ConvertNumValueError> {45		if self.0 < MIN_SAFE_INTEGER || self.0 > MAX_SAFE_INTEGER {46			return Err(ConvertNumValueError::BitwiseSafeRange);47		}48		#[expect(clippy::cast_possible_truncation, reason = "intended")]49		Ok(self.0 as i64)50	}51}52impl PartialEq for NumValue {53	fn eq(&self, other: &Self) -> bool {54		self.0 == other.055	}56}57impl Eq for NumValue {}58impl Ord for NumValue {59	#[inline]60	fn cmp(&self, other: &Self) -> Ordering {61		// Can't use `total_cmp`: its behavior for `-0` and `0`62		// is not following wanted.63		unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }64	}65}66impl PartialOrd for NumValue {67	#[inline]68	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {69		Some(self.cmp(other))70	}71}72impl fmt::Debug for NumValue {73	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {74		fmt::Debug::fmt(&self.0, f)75	}76}77impl fmt::Display for NumValue {78	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {79		fmt::Display::fmt(&self.0, f)80	}81}82impl Deref for NumValue {83	type Target = f64;8485	#[inline]86	fn deref(&self) -> &Self::Target {87		&self.088	}89}90macro_rules! impl_num {91	($($ty:ty),+) => {$(92		impl From<$ty> for NumValue {93			#[inline]94			fn from(value: $ty) -> Self {95				Self(value.into())96			}97		}98	)+};99}100impl_num!(i8, u8, i16, u16, i32, u32);101102#[derive(Clone, Copy, Debug, thiserror::Error, Acyclic)]103pub enum ConvertNumValueError {104	#[error("overflow")]105	Overflow,106	#[error("underflow")]107	Underflow,108	#[error("non-finite")]109	NonFinite,110	#[error("float out of safe int range")]111	BitwiseSafeRange,112}113114macro_rules! impl_try_num {115	($($ty:ty),+) => {$(116		impl TryFrom<$ty> for NumValue {117			type Error = ConvertNumValueError;118			#[inline]119			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {120				#[expect(clippy::cast_precision_loss, reason = "precision loss is explicitly handled")]121				let value = value as f64;122				if value < MIN_SAFE_INTEGER {123					return Err(ConvertNumValueError::Underflow)124				} else if value > MAX_SAFE_INTEGER {125					return Err(ConvertNumValueError::Overflow)126				}127				// Number is finite.128				Ok(Self(value))129			}130		}131	)+};132}133impl_try_num!(usize, isize, i64, u64);134135impl TryFrom<f64> for NumValue {136	type Error = ConvertNumValueError;137138	#[inline]139	fn try_from(value: f64) -> Result<Self, Self::Error> {140		Self::new(value).ok_or(ConvertNumValueError::NonFinite)141	}142}143impl TryFrom<f32> for NumValue {144	type Error = ConvertNumValueError;145146	#[inline]147	fn try_from(value: f32) -> Result<Self, Self::Error> {148		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)149	}150}
modifiedcrates/jrsonnet-ir/src/location.rsdiffbeforeafterboth
--- a/crates/jrsonnet-ir/src/location.rs
+++ b/crates/jrsonnet-ir/src/location.rs
@@ -29,7 +29,7 @@
 		return [CodeLocation::default(); S];
 	}
 	let mut line = 1;
-	let mut column = 1;
+	let mut column = 0;
 	let max_offset = *offsets.iter().max().expect("offsets is not empty");
 
 	let mut offset_map = offsets
@@ -63,7 +63,7 @@
 		}
 		if ch == '\n' {
 			line += 1;
-			column = 1;
+			column = 0;
 
 			for idx in with_no_known_line_ending.drain(..) {
 				out[idx].line_end_offset = pos;
@@ -98,14 +98,14 @@
 				CodeLocation {
 					offset: 0,
 					line: 1,
-					column: 2,
+					column: 1,
 					line_start_offset: 0,
 					line_end_offset: 11,
 				},
 				CodeLocation {
 					offset: 14,
 					line: 2,
-					column: 4,
+					column: 3,
 					line_start_offset: 12,
 					line_end_offset: 67
 				}
modifiedcrates/jrsonnet-ir/src/source.rsdiffbeforeafterboth
--- a/crates/jrsonnet-ir/src/source.rs
+++ b/crates/jrsonnet-ir/src/source.rs
@@ -8,6 +8,7 @@
 
 use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_interner::{IBytes, IStr};
+use url::Url;
 
 use crate::location::{CodeLocation, location_to_offset, offset_to_location};
 
@@ -186,6 +187,28 @@
 	any_ext_impl!(SourcePathT);
 }
 
+#[derive(Acyclic, Hash, PartialEq, Eq, Debug)]
+pub struct SourceUrl(Url);
+impl SourceUrl {
+	pub fn new(url: Url) -> Self {
+		Self(url)
+	}
+}
+impl Display for SourceUrl {
+	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+		write!(f, "{}", self.0)
+	}
+}
+impl SourcePathT for SourceUrl {
+	fn is_default(&self) -> bool {
+		false
+	}
+	fn path(&self) -> Option<&Path> {
+		None
+	}
+	any_ext_impl!(SourcePathT);
+}
+
 /// Represents path to the directory on the disk
 ///
 /// See also [`SourceFile`]