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

difftreelog

fix align std.format output with standard jsonnet changes

mykzwksoYaroslav Bolyukin2026-02-07parent: #c368769.patch.diff
in: master

4 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/evaluate/operator.rs
1use std::cmp::Ordering;23use jrsonnet_parser::{BinaryOpType, LocExpr, UnaryOpType};45use crate::{6	arr::ArrValue,7	bail,8	error::ErrorKind::*,9	evaluate,10	stdlib::std_format,11	typed::Typed,12	val::{equals, StrValue},13	Context, Result, Val,14};1516pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {17	use UnaryOpType::*;18	use Val::*;19	Ok(match (op, b) {20		(Plus, Num(n)) => Val::Num(*n),21		(Minus, Num(n)) => Val::try_num(-n.get())?,22		(Not, Bool(v)) => Bool(!v),23		(BitNot, Num(n)) => Val::try_num(!(n.get() as i64) as f64)?,24		(op, o) => bail!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())),25	})26}2728pub fn evaluate_add_op(a: &Val, b: &Val) -> Result<Val> {29	use Val::*;30	Ok(match (a, b) {31		(Str(v1), Str(v2)) => Str(StrValue::concat(v1.clone(), v2.clone())),3233		(Num(a), Str(b)) => Val::string(format!("{a}{b}")),34		(Str(a), Num(b)) => Val::string(format!("{a}{b}")),3536		(Str(a), o) | (o, Str(a)) if a.is_empty() => Val::string(o.clone().to_string()?),37		(Str(a), o) => Val::string(format!("{a}{}", o.clone().to_string()?)),38		(o, Str(a)) => Val::string(format!("{}{a}", o.clone().to_string()?)),3940		(Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),41		(Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),4243		(Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,4445		#[cfg(feature = "exp-bigint")]46		(BigInt(a), BigInt(b)) => BigInt(Box::new(&**a + &**b)),4748		_ => bail!(BinaryOperatorDoesNotOperateOnValues(49			BinaryOpType::Add,50			a.value_type(),51			b.value_type(),52		)),53	})54}5556pub fn evaluate_sub_op(a: &Val, b: &Val) -> Result<Val> {57	use Val::*;58	Ok(match (a, b) {59		(Num(v1), Num(v2)) => Val::try_num(v1.get() - v2.get())?,6061		#[cfg(feature = "exp-bigint")]62		(BigInt(a), BigInt(b)) => BigInt(Box::new(&**a - &**b)),6364		// TODO: Support objects and arrays65		_ => bail!(BinaryOperatorDoesNotOperateOnValues(66			BinaryOpType::Sub,67			a.value_type(),68			b.value_type(),69		)),70	})71}7273pub fn evaluate_mul_op(a: &Val, b: &Val) -> Result<Val> {74	use Val::*;75	Ok(match (a, b) {76		(Str(s), Num(c)) => Val::string(s.to_string().repeat(c.get() as usize)),77		(Num(c), Str(s)) => Val::string(s.to_string().repeat(c.get() as usize)),7879		(Num(v1), Num(v2)) => Val::try_num(v1.get() * v2.get())?,8081		#[cfg(feature = "exp-bigint")]82		(BigInt(a), BigInt(b)) => BigInt(Box::new(&**a * &**b)),8384		_ => bail!(BinaryOperatorDoesNotOperateOnValues(85			BinaryOpType::Mul,86			a.value_type(),87			b.value_type(),88		)),89	})90}9192fn is_attempt_to_divide_by_zero(a: &Val, b: &Val) -> bool {93	use Val::*;94	match (a, b) {95		// string format96		(Str(_), _) => false,9798		(_, Num(b)) => return **b == 0.,99		#[cfg(feature = "exp-bigint")]100		(_, BigInt(b)) => return **b == num_bigint::BigInt::ZERO,101102		// something else103		_ => false,104	}105}106107pub fn evaluate_div_op(a: &Val, b: &Val) -> Result<Val> {108	use Val::*;109110	if is_attempt_to_divide_by_zero(a, b) {111		bail!(DivisionByZero);112	}113114	Ok(match (a, b) {115		(Num(a), Num(b)) => Val::try_num(a.get() / b.get())?,116		#[cfg(feature = "exp-bigint")]117		(BigInt(a), BigInt(b)) => BigInt(Box::new(&**a / &**b)),118		(a, b) => bail!(BinaryOperatorDoesNotOperateOnValues(119			BinaryOpType::Div,120			a.value_type(),121			b.value_type()122		)),123	})124}125126pub fn evaluate_mod_op(a: &Val, b: &Val) -> Result<Val> {127	use Val::*;128129	if is_attempt_to_divide_by_zero(a, b) {130		bail!(DivisionByZero);131	}132133	Ok(match (a, b) {134		(Num(a), Num(b)) => Val::try_num(a.get() % b.get())?,135		#[cfg(feature = "exp-bigint")]136		(BigInt(a), BigInt(b)) => BigInt(Box::new(&**a % &**b)),137		(Str(str), vals) => {138			String::into_untyped(std_format(&str.clone().into_flat(), vals.clone())?)?139		}140		(a, b) => bail!(BinaryOperatorDoesNotOperateOnValues(141			BinaryOpType::Mod,142			a.value_type(),143			b.value_type()144		)),145	})146}147148pub fn evaluate_binary_op_special(149	ctx: Context,150	a: &LocExpr,151	op: BinaryOpType,152	b: &LocExpr,153) -> Result<Val> {154	use BinaryOpType::*;155	use Val::*;156	Ok(match (evaluate(ctx.clone(), a)?, op, b) {157		(Bool(true), Or, _o) => Val::Bool(true),158		(Bool(false), And, _o) => Val::Bool(false),159		#[cfg(feature = "exp-null-coaelse")]160		(Null, NullCoaelse, eb) => evaluate(ctx, eb)?,161		#[cfg(feature = "exp-null-coaelse")]162		(a, NullCoaelse, _o) => a,163		(a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(ctx, eb)?)?,164	})165}166167pub fn evaluate_compare_op(a: &Val, b: &Val, op: BinaryOpType) -> Result<Ordering> {168	use Val::*;169	Ok(match (a, b) {170		(Str(a), Str(b)) => a.cmp(b),171172		(Num(a), Num(b)) => a.cmp(b),173174		#[cfg(feature = "exp-bigint")]175		(BigInt(a), BigInt(b)) => a.cmp(b),176177		(Arr(a), Arr(b)) => {178			let ai = a.iter();179			let bi = b.iter();180181			for (a, b) in ai.zip(bi) {182				let ord = evaluate_compare_op(&a?, &b?, op)?;183				if !ord.is_eq() {184					return Ok(ord);185				}186			}187			a.len().cmp(&b.len())188		}189		(_, _) => bail!(BinaryOperatorDoesNotOperateOnValues(190			op,191			a.value_type(),192			b.value_type()193		)),194	})195}196197pub fn evaluate_binary_op_normal(a: &Val, op: BinaryOpType, b: &Val) -> Result<Val> {198	use BinaryOpType::*;199	use Val::*;200	Ok(match (a, op, b) {201		(a, Eq, b) => Bool(equals(a, b)?),202		(a, Neq, b) => Bool(!equals(a, b)?),203204		(a, Lt, b) => Bool(evaluate_compare_op(a, b, Lt)?.is_lt()),205		(a, Gt, b) => Bool(evaluate_compare_op(a, b, Gt)?.is_gt()),206		(a, Lte, b) => Bool(evaluate_compare_op(a, b, Lte)?.is_le()),207		(a, Gte, b) => Bool(evaluate_compare_op(a, b, Gte)?.is_ge()),208209		(Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone().into_flat(), true)),210211		// Bool X Bool212		(Bool(a), And, Bool(b)) => Bool(*a && *b),213		(Bool(a), Or, Bool(b)) => Bool(*a || *b),214215		(a, Add, b) => evaluate_add_op(a, b)?,216		(a, Sub, b) => evaluate_sub_op(a, b)?,217		(a, Mul, b) => evaluate_mul_op(a, b)?,218		(a, Div, b) => evaluate_div_op(a, b)?,219		(a, Mod, b) => evaluate_mod_op(a, b)?,220221		(Num(v1), BitAnd, Num(v2)) => Val::try_num((v1.get() as i64 & v2.get() as i64) as f64)?,222		(Num(v1), BitOr, Num(v2)) => Val::try_num((v1.get() as i64 | v2.get() as i64) as f64)?,223		(Num(v1), BitXor, Num(v2)) => Val::try_num((v1.get() as i64 ^ v2.get() as i64) as f64)?,224		(Num(v1), Lhs, Num(v2)) => {225			if v2.get() < 0.0 {226				bail!("shift by negative exponent")227			}228			let exp = ((v2.get() as i64) & 63) as u32;229			Val::try_num((v1.get() as i64).wrapping_shl(exp) as f64)?230		}231		(Num(v1), Rhs, Num(v2)) => {232			if v2.get() < 0.0 {233				bail!("shift by negative exponent")234			}235			let exp = ((v2.get() as i64) & 63) as u32;236			Val::try_num((v1.get() as i64).wrapping_shr(exp) as f64)?237		}238239		// Bigint X Bigint240		_ => bail!(BinaryOperatorDoesNotOperateOnValues(241			op,242			a.value_type(),243			b.value_type(),244		)),245	})246}
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -137,7 +137,7 @@
 #[derive(Debug, PartialEq, Eq)]
 pub enum Width {
 	Star,
-	Fixed(usize),
+	Fixed(u16),
 }
 pub fn try_parse_field_width(str: &str) -> ParseResult<'_, Width> {
 	if str.is_empty() {
@@ -147,11 +147,11 @@
 	if bytes[0] == b'*' {
 		return Ok((Width::Star, &str[1..]));
 	}
-	let mut out: usize = 0;
+	let mut out: u16 = 0;
 	let mut digits = 0;
 	while let Some(digit) = (bytes[digits] as char).to_digit(10) {
 		out *= 10;
-		out += digit as usize;
+		out += digit as u16;
 		digits += 1;
 		if digits == bytes.len() {
 			return Err(TruncatedFormatCode);
@@ -299,15 +299,18 @@
 #[inline]
 pub fn render_integer(
 	out: &mut String,
+	neg: bool,
 	iv: f64,
-	padding: usize,
-	precision: usize,
+	padding: u16,
+	precision: u16,
 	blank: bool,
 	sign: bool,
 	radix: i64,
-	prefix: &str,
+	zero_prefix: &str,
+	prefix_in_padding: bool,
 	caps: bool,
 ) {
+	debug_assert!(iv >= 0.0, "render_integer receives sign using arg");
 	let iv = iv.floor() as i64;
 	// Digit char indexes in reverse order, i.e
 	// for radix = 16 and n = 12f: [15, 2, 1]
@@ -322,12 +325,14 @@
 		}
 		nums
 	};
-	let neg = iv < 0;
 	#[allow(clippy::bool_to_int_with_if)]
 	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
+
+	let pref_len = zero_prefix.len() as u16;
 	let zp2 = zp
+		.saturating_sub(if !prefix_in_padding { pref_len } else { 0 })
 		.max(precision)
-		.saturating_sub(prefix.len() + digits.len());
+		.saturating_sub(if prefix_in_padding { pref_len } else { 0 } + digits.len() as u16);
 
 	if neg {
 		out.push('-');
@@ -337,11 +342,13 @@
 		out.push(' ');
 	}
 
-	out.reserve(zp2);
+	out.reserve(zp2 as usize);
+	if iv != 0 {
+		out.push_str(zero_prefix);
+	}
 	for _ in 0..zp2 {
 		out.push('0');
 	}
-	out.push_str(prefix);
 
 	for digit in digits.into_iter().rev() {
 		let ch = NUMBERS[digit as usize] as char;
@@ -351,25 +358,30 @@
 
 pub fn render_decimal(
 	out: &mut String,
+	neg: bool,
 	iv: f64,
-	padding: usize,
-	precision: usize,
+	padding: u16,
+	precision: u16,
 	blank: bool,
 	sign: bool,
 ) {
-	render_integer(out, iv, padding, precision, blank, sign, 10, "", false);
+	render_integer(
+		out, neg, iv, padding, precision, blank, sign, 10, "", false, false,
+	);
 }
 pub fn render_octal(
 	out: &mut String,
+	neg: bool,
 	iv: f64,
-	padding: usize,
-	precision: usize,
+	padding: u16,
+	precision: u16,
 	alt: bool,
 	blank: bool,
 	sign: bool,
 ) {
 	render_integer(
 		out,
+		neg,
 		iv,
 		padding,
 		precision,
@@ -377,6 +389,7 @@
 		sign,
 		8,
 		if alt && iv != 0.0 { "0" } else { "" },
+		true,
 		false,
 	);
 }
@@ -385,8 +398,8 @@
 pub fn render_hexadecimal(
 	out: &mut String,
 	iv: f64,
-	padding: usize,
-	precision: usize,
+	padding: u16,
+	precision: u16,
 	alt: bool,
 	blank: bool,
 	sign: bool,
@@ -394,7 +407,8 @@
 ) {
 	render_integer(
 		out,
-		iv,
+		iv < 0.0,
+		iv.abs(),
 		padding,
 		precision,
 		blank,
@@ -405,6 +419,7 @@
 			(true, false) => "0x",
 			(false, _) => "",
 		},
+		false,
 		caps,
 	);
 }
@@ -413,31 +428,36 @@
 pub fn render_float(
 	out: &mut String,
 	n: f64,
-	mut padding: usize,
-	precision: usize,
+	mut padding: u16,
+	precision: u16,
 	blank: bool,
 	sign: bool,
 	ensure_pt: bool,
 	trailing: bool,
 ) {
+	// Represent the rounded number as an integer * 1/10**prec.
+	// Note that it can also be equal to 10**prec and we'll need to carry
+	// over to the wholes.  We operate on the absolute numbers, so that we
+	// don't have trouble with the rounding direction.
+	let denominator = 10.0f64.powi(precision as i32);
+	let numerator = n.abs() * denominator + 0.5;
+	let whole = (numerator / denominator).floor();
+	let frac = numerator.floor() % denominator;
+
 	#[allow(clippy::bool_to_int_with_if)]
 	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
 	padding = padding.saturating_sub(dot_size + precision);
-	render_decimal(out, n.floor(), padding, 0, blank, sign);
+	render_decimal(out, n < 0.0, whole, padding, 0, blank, sign);
 	if precision == 0 {
 		if ensure_pt {
 			out.push('.');
 		}
 		return;
 	}
-	let frac = n
-		.fract()
-		.mul_add(10.0_f64.powf(precision as f64), 0.5)
-		.floor();
 	if trailing || frac > 0.0 {
 		out.push('.');
 		let mut frac_str = String::new();
-		render_decimal(&mut frac_str, frac, precision, 0, false, false);
+		render_decimal(&mut frac_str, false, frac, precision, 0, false, false);
 		let mut trim = frac_str.len();
 		if !trailing {
 			for b in frac_str.as_bytes().iter().rev() {
@@ -458,25 +478,38 @@
 pub fn render_float_sci(
 	out: &mut String,
 	n: f64,
-	mut padding: usize,
-	precision: usize,
+	mut padding: u16,
+	precision: u16,
 	blank: bool,
 	sign: bool,
 	ensure_pt: bool,
 	trailing: bool,
 	caps: bool,
 ) {
-	let exponent = n.log10().floor();
+	let exponent = if n == 0.0 {
+		0.0
+	} else {
+		n.abs().log10().floor()
+	};
+
 	let mantissa = if exponent as i16 == -324 {
 		n * 10.0 / 10.0_f64.powf(exponent + 1.0)
 	} else {
 		n / 10.0_f64.powf(exponent)
 	};
 	let mut exponent_str = String::new();
-	render_decimal(&mut exponent_str, exponent, 3, 0, false, true);
+	render_decimal(
+		&mut exponent_str,
+		exponent < 0.0,
+		exponent.abs(),
+		3,
+		0,
+		false,
+		true,
+	);
 
 	// +1 for e
-	padding = padding.saturating_sub(exponent_str.len() + 1);
+	padding = padding.saturating_sub(exponent_str.len() as u16 + 1);
 
 	render_float(
 		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,
@@ -490,8 +523,8 @@
 	out: &mut String,
 	value: &Val,
 	code: &Code<'_>,
-	width: usize,
-	precision: Option<usize>,
+	width: u16,
+	precision: Option<u16>,
 ) -> Result<()> {
 	let clfags = &code.cflags;
 	let (fpprec, iprec) = precision.map_or((6, 0), |v| (v, v));
@@ -510,7 +543,8 @@
 			let value = f64::from_untyped(value.clone())?;
 			render_decimal(
 				&mut tmp_out,
-				value,
+				value <= -1.0,
+				value.abs(),
 				padding,
 				iprec,
 				clfags.blank,
@@ -521,7 +555,8 @@
 			let value = f64::from_untyped(value.clone())?;
 			render_octal(
 				&mut tmp_out,
-				value,
+				value <= -1.0,
+				value.abs(),
 				padding,
 				iprec,
 				clfags.alt,
@@ -589,7 +624,7 @@
 					code.caps,
 				);
 			} else {
-				let digits_before_pt = 1.max(exponent as usize + 1);
+				let digits_before_pt = 1.max(exponent as u16 + 1);
 				render_float(
 					&mut tmp_out,
 					value,
@@ -628,7 +663,7 @@
 		ConvTypeV::Percent => tmp_out.push('%'),
 	};
 
-	let padding = width.saturating_sub(tmp_out.len());
+	let padding = width.saturating_sub(tmp_out.len() as u16);
 
 	if !clfags.left {
 		for _ in 0..padding {
@@ -663,7 +698,7 @@
 						}
 						let value = &values[0];
 						values = &values[1..];
-						usize::from_untyped(value.clone())?
+						u16::from_untyped(value.clone())?
 					}
 					Width::Fixed(n) => n,
 				};
@@ -674,7 +709,7 @@
 						}
 						let value = &values[0];
 						values = &values[1..];
-						Some(usize::from_untyped(value.clone())?)
+						Some(u16::from_untyped(value.clone())?)
 					}
 					Some(Width::Fixed(n)) => Some(n),
 					None => None,
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -120,8 +120,8 @@
 	}
 }
 
-pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;
-pub const MIN_SAFE_INTEGER: f64 = -MAX_SAFE_INTEGER;
+pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;
+pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;
 
 macro_rules! impl_int {
 	($($ty:ty)*) => {$(
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -22,7 +22,7 @@
 	function::FuncVal,
 	gc::WithCapacityExt as _,
 	manifest::{ManifestFormat, ToStringFormat},
-	typed::BoundedUsize,
+	typed::{BoundedUsize, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER},
 	ObjValue, Result, Unbound, WeakObjValue,
 };
 
@@ -418,6 +418,12 @@
 	pub const fn get(&self) -> f64 {
 		self.0
 	}
+	pub(crate) fn truncate_for_bitwise(&self) -> Result<i64> {
+		if self.0 < MIN_SAFE_INTEGER || self.0 > dbg!(MAX_SAFE_INTEGER) {
+			bail!("numberic value outside of safe integer range for bitwise operation");
+		}
+		Ok(self.0 as i64)
+	}
 }
 impl PartialEq for NumValue {
 	fn eq(&self, other: &Self) -> bool {
@@ -490,7 +496,6 @@
 			type Error = ConvertNumValueError;
 			#[inline]
 			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {
-				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};
 				let value = value as f64;
 				if value < MIN_SAFE_INTEGER {
 					return Err(ConvertNumValueError::Underflow)