From 6551086333176a9792cbe7ee9ffd5ac29d8a2b25 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sat, 07 Feb 2026 17:37:36 +0000 Subject: [PATCH] fix: align std.format output with standard jsonnet changes --- --- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs @@ -225,15 +225,20 @@ if v2.get() < 0.0 { bail!("shift by negative exponent") } - let exp = ((v2.get() as i64) & 63) as u32; - Val::try_num((v1.get() as i64).wrapping_shl(exp) as f64)? + let base = v1.truncate_for_bitwise()?; + let exp = v2.truncate_for_bitwise()? % 64; + + if exp >= 1 && base >= (1i64 << (63 - exp as u32)) { + bail!("left shift would overflow") + } + Val::try_num(base.wrapping_shl(exp as u32) as f64)? } (Num(v1), Rhs, Num(v2)) => { if v2.get() < 0.0 { bail!("shift by negative exponent") } let exp = ((v2.get() as i64) & 63) as u32; - Val::try_num((v1.get() as i64).wrapping_shr(exp) as f64)? + Val::try_num(v1.truncate_for_bitwise()?.wrapping_shr(exp) as f64)? } // Bigint X Bigint --- 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, + width: u16, + precision: Option, ) -> 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, --- 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)*) => {$( --- 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 { + 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 { - use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER}; let value = value as f64; if value < MIN_SAFE_INTEGER { return Err(ConvertNumValueError::Underflow) -- gitstuff