difftreelog
refactor reenable clippy integer cast checks
in: master
17 files changed
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -122,11 +122,6 @@
wildcard_imports = "allow"
enum_glob_use = "allow"
module_name_repetitions = "allow"
-# TODO: fix individual issues, however this works as intended almost everywhere
-cast_precision_loss = "allow"
-cast_possible_wrap = "allow"
-cast_possible_truncation = "allow"
-cast_sign_loss = "allow"
# False positives
# https://github.com/rust-lang/rust-clippy/issues/6902
use_self = "allow"
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -128,7 +128,12 @@
#[must_use]
pub fn slice(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {
let get_idx = |pos: Option<i32>, len: usize, default| match pos {
+ #[expect(
+ clippy::cast_sign_loss,
+ reason = "abs value is used, len is limited to u31"
+ )]
Some(v) if v < 0 => len.saturating_sub((-v) as usize),
+ #[expect(clippy::cast_sign_loss, reason = "abs value is used")]
Some(v) => (v as usize).min(len),
None => default,
};
@@ -142,7 +147,9 @@
Self::new(SliceArray {
inner: self,
+ #[expect(clippy::cast_possible_truncation, reason = "len is limited to u31")]
from: index as u32,
+ #[expect(clippy::cast_possible_truncation, reason = "len is limited to u31")]
to: end as u32,
step: step.get(),
})
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -350,22 +350,26 @@
pub fn new_inclusive(start: i32, end: i32) -> Self {
Self { start, end }
}
+ #[expect(
+ clippy::cast_sign_loss,
+ reason = "the math is valid with wrapping, sign loss works as intended"
+ )]
+ fn size(&self) -> usize {
+ (self.end as usize)
+ .wrapping_sub(self.start as usize)
+ .wrapping_add(1)
+ }
fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {
- WithExactSize(
- self.start..=self.end,
- (self.end as usize)
- .wrapping_sub(self.start as usize)
- .wrapping_add(1),
- )
+ WithExactSize(self.start..=self.end, self.size())
}
}
impl ArrayLike for RangeArray {
fn len(&self) -> usize {
- self.range().len()
+ self.size()
}
fn is_empty(&self) -> bool {
- self.range().len() == 0
+ self.size() == 0
}
fn get(&self, index: usize) -> Result<Option<Val>> {
@@ -431,6 +435,10 @@
fn evaluate(&self, index: usize, value: Val) -> Result<Val> {
match &self.mapper {
ArrayMapper::Plain(f) => f.call(value),
+ #[expect(
+ clippy::cast_possible_truncation,
+ reason = "array len is limited to u31"
+ )]
ArrayMapper::WithIndex(f) => f.call(index as u32, value),
}
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -548,8 +548,18 @@
bail!(FractionalIndex)
}
if n < 0.0 {
- bail!(ArrayBoundsError(n as isize, v.len()));
+ #[expect(
+ clippy::cast_possible_truncation,
+ reason = "it would be truncated anyway"
+ )]
+ let n = n as isize;
+ bail!(ArrayBoundsError(n, v.len()));
}
+ #[expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "n is checked postive"
+ )]
v.get(n as usize)?
.ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?
}
@@ -568,18 +578,29 @@
bail!(FractionalIndex)
}
if n < 0.0 {
- bail!(ArrayBoundsError(n as isize, s.into_flat().chars().count()));
+ #[expect(
+ clippy::cast_possible_truncation,
+ reason = "it would be truncated anyway"
+ )]
+ let n = n as isize;
+ bail!(ArrayBoundsError(n, s.into_flat().chars().count()));
}
+ #[expect(
+ clippy::cast_sign_loss,
+ clippy::cast_possible_truncation,
+ reason = "n is positive, overflow will truncate as expected"
+ )]
+ let n = n as usize;
let v: IStr = s
.clone()
.into_flat()
.chars()
- .skip(n as usize)
+ .skip(n)
.take(1)
.collect::<String>()
.into();
if v.is_empty() {
- bail!(StringBoundsError(n as usize, s.into_flat().chars().count()))
+ bail!(StringBoundsError(n, s.into_flat().chars().count()))
}
StrValue::Flat(v)
}),
crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -20,7 +20,8 @@
(Plus, Num(n)) => Val::Num(*n),
(Minus, Num(n)) => Val::try_num(-n.get())?,
(Not, Bool(v)) => Bool(!v),
- (BitNot, Num(n)) => Val::try_num(!(n.get() as i64) as f64)?,
+ #[expect(clippy::cast_precision_loss, reason = "as spec")]
+ (BitNot, Num(n)) => Val::try_num(!n.truncate_for_bitwise()? as f64)?,
(op, o) => bail!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())),
})
}
@@ -73,7 +74,17 @@
pub fn evaluate_mul_op(a: &Val, b: &Val) -> Result<Val> {
use Val::*;
Ok(match (a, b) {
+ #[expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "should not be used with values too large, negative == 0"
+ )]
(Str(s), Num(c)) => Val::string(s.to_string().repeat(c.get() as usize)),
+ #[expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "should not be used with values too large"
+ )]
(Num(c), Str(s)) => Val::string(s.to_string().repeat(c.get() as usize)),
(Num(v1), Num(v2)) => Val::try_num(v1.get() * v2.get())?,
@@ -218,13 +229,28 @@
(a, Div, b) => evaluate_div_op(a, b)?,
(a, Mod, b) => evaluate_mod_op(a, b)?,
- (Num(v1), BitAnd, Num(v2)) => {
+ (Num(v1), BitAnd, Num(v2)) =>
+ {
+ #[expect(
+ clippy::cast_precision_loss,
+ reason = "values are within safe integer ranges"
+ )]
Val::try_num((v1.truncate_for_bitwise()? & v2.truncate_for_bitwise()?) as f64)?
}
- (Num(v1), BitOr, Num(v2)) => {
+ (Num(v1), BitOr, Num(v2)) =>
+ {
+ #[expect(
+ clippy::cast_precision_loss,
+ reason = "values are within safe integer ranges"
+ )]
Val::try_num((v1.truncate_for_bitwise()? | v2.truncate_for_bitwise()?) as f64)?
}
- (Num(v1), BitXor, Num(v2)) => {
+ (Num(v1), BitXor, Num(v2)) =>
+ {
+ #[expect(
+ clippy::cast_precision_loss,
+ reason = "values are within safe integer ranges"
+ )]
Val::try_num((v1.truncate_for_bitwise()? ^ v2.truncate_for_bitwise()?) as f64)?
}
(Num(v1), Lhs, Num(v2)) => {
@@ -234,16 +260,28 @@
let base = v1.truncate_for_bitwise()?;
let exp = v2.truncate_for_bitwise()? % 64;
+ #[expect(clippy::cast_sign_loss, reason = "exp is positive")]
if exp >= 1 && base >= (1i64 << (63 - exp as u32)) {
bail!("left shift would overflow")
}
+ #[expect(
+ clippy::cast_precision_loss,
+ clippy::cast_sign_loss,
+ reason = "checked as original impl"
+ )]
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")
}
+ #[expect(
+ clippy::cast_sign_loss,
+ clippy::cast_possible_truncation,
+ reason = "checked as original impl"
+ )]
let exp = ((v2.get() as i64) & 63) as u32;
+ #[expect(clippy::cast_precision_loss, reason = "checked as upstream impl")]
Val::try_num(v1.truncate_for_bitwise()?.wrapping_shr(exp) as f64)?
}
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -69,12 +69,20 @@
where
E: de::Error,
{
+ #[expect(
+ clippy::cast_precision_loss,
+ reason = "this is how it works with stdlib functions"
+ )]
Ok(Val::Num(NumValue::new(v as f64).expect("no overflow")))
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
+ #[expect(
+ clippy::cast_precision_loss,
+ reason = "this is how it works with stdlib functions"
+ )]
Ok(Val::Num(NumValue::new(v as f64).expect("no overflow")))
}
@@ -161,6 +169,10 @@
Self::Num(n) => {
let n = n.get();
if n.fract() == 0.0 {
+ #[expect(
+ clippy::cast_possible_truncation,
+ reason = "no correct implementation is possible here; expected"
+ )]
let n = n as i64;
serializer.serialize_i64(n)
} else {
crates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -792,6 +792,8 @@
key,
})
}
+
+ #[allow(dead_code, reason = "used in object ...rest destructuring")]
pub(crate) fn as_standalone(&self) -> StandaloneSuperCore {
StandaloneSuperCore {
sup: CoreIdx {
crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -1,5 +1,10 @@
//! faster std.format impl
#![allow(clippy::too_many_arguments)]
+#![expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "many safe integer casts, behavior on overflow is not specified"
+)]
use jrsonnet_gcmodule::Trace;
use jrsonnet_interner::IStr;
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -129,6 +129,7 @@
} else {
false
};
+ #[expect(clippy::cast_possible_truncation, reason = "code is limited by 4gb")]
let mut location = path
.map_source_locations(&[offset as u32])
.into_iter()
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -157,7 +157,9 @@
}
}
+#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]
pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;
+#[expect(clippy::cast_precision_loss, reason = "checked to not overflow")]
pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;
macro_rules! impl_int {
@@ -179,6 +181,7 @@
stringify!($ty)
)
}
+ #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation, reason = "checked by TYPE")]
Ok(n as Self)
}
_ => unreachable!(),
@@ -198,6 +201,7 @@
macro_rules! impl_bounded_int {
($($name:ident = $ty:ty)*) => {$(
#[derive(Clone, Copy)]
+ #[allow(clippy::cast_possible_truncation, reason = "overflow is api misuse")]
pub struct $name<const MIN: $ty, const MAX: $ty>($ty);
impl<const MIN: $ty, const MAX: $ty> $name<MIN, MAX> {
pub const fn new(value: $ty) -> Option<$name<MIN, MAX>> {
@@ -219,6 +223,7 @@
}
impl<const MIN: $ty, const MAX: $ty> Typed for $name<MIN, MAX> {
+ #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, reason = "overflow is api misuse")]
const TYPE: &'static ComplexValType =
&ComplexValType::BoundedNumber(
Some(MIN as f64),
@@ -239,6 +244,7 @@
stringify!($ty)
)
}
+ #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, reason = "overflow is api misuse, the range is checked by TYPE")]
Ok(Self(n as $ty))
}
_ => unreachable!(),
@@ -318,6 +324,11 @@
if n.trunc() != n {
bail!("cannot convert number with fractional part to usize")
}
+ #[allow(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "the range is checked by TYPE"
+ )]
Ok(n as Self)
}
_ => unreachable!(),
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -295,8 +295,10 @@
};
let mut get_idx = |pos: Option<i32>, default| {
match pos {
- Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),
+ #[expect(clippy::cast_sign_loss, reason = "abs value is used")]
+ Some(v) if v < 0 => get_len().saturating_sub((-v as isize) as usize),
// No need to clamp, as iterator interface is used
+ #[expect(clippy::cast_sign_loss, reason = "abs value is used")]
Some(v) => v as usize,
None => default,
}
@@ -322,6 +324,10 @@
Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(
index,
end,
+ #[expect(
+ clippy::cast_possible_truncation,
+ reason = "overflow will result with skip too large which would be equivalent"
+ )]
step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),
))),
}
@@ -446,6 +452,7 @@
if self.0 < MIN_SAFE_INTEGER || self.0 > MAX_SAFE_INTEGER {
bail!("numberic value outside of safe integer range for bitwise operation");
}
+ #[expect(clippy::cast_possible_truncation, reason = "intended")]
Ok(self.0 as i64)
}
}
@@ -520,6 +527,7 @@
type Error = ConvertNumValueError;
#[inline]
fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {
+ #[expect(clippy::cast_precision_loss, reason = "precision loss is explicitly handled")]
let value = value as f64;
if value < MIN_SAFE_INTEGER {
return Err(ConvertNumValueError::Underflow)
crates/jrsonnet-interner/src/inner.rsdiffbeforeafterboth--- a/crates/jrsonnet-interner/src/inner.rs
+++ b/crates/jrsonnet-interner/src/inner.rs
@@ -67,7 +67,7 @@
.cast();
assert!(!data.is_null());
*data = InnerHeader::new(bytes.len().try_into().expect("bytes > 4GB"), is_utf8);
- ptr::copy_nonoverlapping(bytes.as_ptr(), data.offset(1).cast::<u8>(), bytes.len());
+ ptr::copy_nonoverlapping(bytes.as_ptr(), data.add(1).cast::<u8>(), bytes.len());
Self(UnsafeCell::new(NonNull::new_unchecked(data)))
}
}
@@ -89,10 +89,7 @@
let size = unsafe { (*header).size };
// SAFETY: bytes after data is allocated to be exactly data.size in length
unsafe {
- slice::from_raw_parts(
- (*self.0.get()).as_ptr().offset(1).cast::<u8>(),
- size as usize,
- )
+ slice::from_raw_parts((*self.0.get()).as_ptr().add(1).cast::<u8>(), size as usize)
}
}
@@ -156,7 +153,7 @@
}
pub fn as_ptr(this: &Self) -> *const u8 {
// SAFETY: data is initialized
- unsafe { (*this.0.get()).as_ptr().offset(1).cast() }
+ unsafe { (*this.0.get()).as_ptr().add(1).cast() }
}
pub fn strong_count(this: &Self) -> u32 {
crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -638,6 +638,7 @@
}
}
+#[allow(clippy::too_many_lines)]
fn expr_basic(p: &mut Parser<'_>) -> Result<Expr> {
if let Some(lit) = literal(p) {
return Ok(Expr::Literal(lit));
@@ -764,7 +765,6 @@
}
SyntaxKind::IDENT => {
- let text = p.text();
let n = spanned(p, |p| {
let s: IStr = p.text().into();
p.eat_any();
@@ -1005,8 +1005,9 @@
}
pub fn string_to_expr(s: IStr, settings: &ParserSettings) -> Spanned<Expr> {
- let len = s.len();
- Spanned::new(Expr::Str(s), Span(settings.source.clone(), 0, len as u32))
+ let len = u32::try_from(s.len()).expect("code size is limited by 4gb");
+
+ Spanned::new(Expr::Str(s), Span(settings.source.clone(), 0, len))
}
#[cfg(test)]
crates/jrsonnet-lexer/src/lex.rsdiffbeforeafterboth--- a/crates/jrsonnet-lexer/src/lex.rs
+++ b/crates/jrsonnet-lexer/src/lex.rs
@@ -60,7 +60,10 @@
range: {
let Range { start, end } = self.inner.span();
- Span(start as u32, end as u32)
+ Span(
+ u32::try_from(start).expect("code size is limited by 4gb"),
+ u32::try_from(end).expect("code size is limited by 4gb"),
+ )
},
})
}
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth1#![allow(non_snake_case)]23use jrsonnet_evaluator::{4 Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, bail,5 function::{FuncVal, NativeFn, builtin},6 runtime_error,7 typed::{BoundedI32, BoundedUsize, Either2, FromUntyped},8 val::{ArrValue, IndexableVal, equals},9};1011pub fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {12 if let Some(on_empty) = on_empty {13 on_empty.evaluate()14 } else {15 bail!("expected non-empty array")16 }17}1819#[builtin]20pub fn builtin_make_array(sz: BoundedI32<0, { i32::MAX }>, func: FuncVal) -> Result<ArrValue> {21 if *sz == 0 {22 return Ok(ArrValue::empty());23 }24 func.evaluate_trivial().map_or_else(25 // TODO: Different mapped array impl avoiding allocating unnecessary vals26 || Ok(ArrValue::range_exclusive(0, *sz).map(FromUntyped::from_untyped(Val::Func(func))?)),27 |trivial| {28 let mut out = Vec::with_capacity(*sz as usize);29 for _ in 0..*sz {30 out.push(trivial.clone());31 }32 Ok(ArrValue::eager(out))33 },34 )35}3637#[builtin]38pub fn builtin_repeat(what: Either![IStr, ArrValue], count: usize) -> Result<Val> {39 Ok(match what {40 Either2::A(s) => Val::string(s.repeat(count)),41 Either2::B(arr) => Val::Arr(42 ArrValue::repeated(arr, count)43 .ok_or_else(|| runtime_error!("repeated length overflow"))?,44 ),45 })46}4748#[builtin]49pub fn builtin_slice(50 indexable: IndexableVal,51 index: Option<Option<i32>>,52 end: Option<Option<i32>>,53 step: Option<Option<BoundedUsize<1, { i32::MAX as usize }>>>,54) -> Result<Val> {55 indexable56 .slice(index.flatten(), end.flatten(), step.flatten())57 .map(Val::from)58}5960#[builtin]61pub fn builtin_map(func: NativeFn!((Val) -> Val), arr: IndexableVal) -> ArrValue {62 let arr = arr.to_array();63 arr.map(func)64}6566#[builtin]67pub fn builtin_map_with_index(func: NativeFn!((u32, Val) -> Val), arr: IndexableVal) -> ArrValue {68 let arr = arr.to_array();69 arr.map_with_index(func)70}7172#[builtin]73pub fn builtin_map_with_key(74 func: NativeFn!((IStr, Val) -> Val),75 obj: ObjValue,76) -> Result<ObjValue> {77 let mut out = ObjValueBuilder::new();78 for (k, v) in obj.iter(79 // Makes sense mapped object should be ordered the same way, should not break anything when the output is not ordered (the default).80 // The thrown error might be different, but jsonnet81 // does not specify the evaluation order.82 #[cfg(feature = "exp-preserve-order")]83 true,84 ) {85 let v = v?;86 out.field(k.clone()).value(func.call(k, v)?);87 }88 Ok(out.build())89}9091#[builtin]92pub fn builtin_flatmap(93 func: NativeFn!((Either![String, Val]) -> Val),94 arr: IndexableVal,95) -> Result<IndexableVal> {96 use std::fmt::Write;97 match arr {98 IndexableVal::Str(str) => {99 let mut out = String::new();100 for c in str.chars() {101 match func.call(Either2::A(c.to_string()))? {102 Val::Str(o) => write!(out, "{o}").unwrap(),103 Val::Null => {}104 _ => bail!("in std.join all items should be strings"),105 }106 }107 Ok(IndexableVal::Str(out.into()))108 }109 IndexableVal::Arr(a) => {110 let mut out = Vec::new();111 for el in a.iter() {112 let el = el?;113 match func.call(Either2::B(el))? {114 Val::Arr(o) => {115 for oe in o.iter() {116 out.push(oe?);117 }118 }119 Val::Null => {}120 _ => bail!("in std.join all items should be arrays"),121 }122 }123 Ok(IndexableVal::Arr(out.into()))124 }125 }126}127128type FilterFunc = NativeFn!((Thunk<Val>) -> bool);129130#[builtin]131pub fn builtin_filter(func: FilterFunc, arr: ArrValue) -> Result<ArrValue> {132 arr.filter(func)133}134135#[builtin]136pub fn builtin_filter_map(137 filter_func: FilterFunc,138 map_func: NativeFn!((Val) -> Val),139 arr: ArrValue,140) -> Result<ArrValue> {141 Ok(arr.filter(filter_func)?.map(map_func))142}143144#[builtin]145pub fn builtin_foldl(146 func: NativeFn!((Val, Either![Val, char]) -> Val),147 arr: Either![ArrValue, IStr],148 init: Val,149) -> Result<Val> {150 let mut acc = init;151 match arr {152 Either2::A(arr) => {153 for i in arr.iter() {154 acc = func.call(acc, Either2::A(i?))?;155 }156 }157 Either2::B(arr) => {158 for c in arr.chars() {159 acc = func.call(acc, Either2::B(c))?;160 }161 }162 }163 Ok(acc)164}165166#[builtin]167pub fn builtin_foldr(168 func: NativeFn!((Either![Val, char], Val) -> Val),169 arr: Either![ArrValue, IStr],170 init: Val,171) -> Result<Val> {172 let mut acc = init;173 match arr {174 Either2::A(arr) => {175 for i in arr.iter().rev() {176 acc = func.call(Either2::A(i?), acc)?;177 }178 }179 Either2::B(arr) => {180 for c in arr.chars().rev() {181 acc = func.call(Either2::B(c), acc)?;182 }183 }184 }185 Ok(acc)186}187188#[builtin]189pub fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {190 if to < from {191 return Ok(ArrValue::empty());192 }193 Ok(ArrValue::range_inclusive(from, to))194}195196#[builtin]197pub fn builtin_join(sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {198 use std::fmt::Write;199 Ok(match sep {200 IndexableVal::Arr(joiner_items) => {201 let mut out = Vec::new();202203 let mut first = true;204 for item in arr.iter() {205 let item = item?.clone();206 if let Val::Arr(items) = item {207 if !first {208 out.reserve(joiner_items.len());209 // TODO: extend210 for item in joiner_items.iter() {211 out.push(item?);212 }213 }214 first = false;215 out.reserve(items.len());216 for item in items.iter() {217 out.push(item?);218 }219 } else if matches!(item, Val::Null) {220 } else {221 bail!("in std.join all items should be arrays");222 }223 }224225 IndexableVal::Arr(out.into())226 }227 IndexableVal::Str(sep) => {228 let mut out = String::new();229230 let mut first = true;231 for item in arr.iter() {232 let item = item?.clone();233 if let Val::Str(item) = item {234 if !first {235 out += &sep;236 }237 first = false;238 write!(out, "{item}").unwrap();239 } else if matches!(item, Val::Null) {240 } else {241 bail!("in std.join all items should be strings");242 }243 }244245 IndexableVal::Str(out.into())246 }247 })248}249250#[builtin]251pub fn builtin_lines(arr: ArrValue) -> Result<IndexableVal> {252 builtin_join(253 IndexableVal::Str("\n".into()),254 ArrValue::extended(arr, ArrValue::eager(vec![Val::string("")])),255 )256}257258#[builtin]259pub fn builtin_resolve_path(f: String, r: String) -> String {260 let Some(pos) = f.rfind('/') else {261 return r;262 };263 format!("{}{}", &f[..=pos], r)264}265266pub fn deep_join_inner(out: &mut String, arr: IndexableVal) -> Result<()> {267 use std::fmt::Write;268 match arr {269 IndexableVal::Str(s) => write!(out, "{s}").expect("no error"),270 IndexableVal::Arr(arr) => {271 for ele in arr.iter() {272 let indexable = IndexableVal::from_untyped(ele?)?;273 deep_join_inner(out, indexable)?;274 }275 }276 }277 Ok(())278}279280#[builtin]281pub fn builtin_deep_join(arr: IndexableVal) -> Result<String> {282 let mut out = String::new();283 deep_join_inner(&mut out, arr)?;284 Ok(out)285}286287#[builtin]288pub fn builtin_reverse(arr: ArrValue) -> ArrValue {289 arr.reversed()290}291292#[builtin]293pub fn builtin_any(arr: ArrValue) -> Result<bool> {294 for v in arr.iter() {295 let v = bool::from_untyped(v?)?;296 if v {297 return Ok(true);298 }299 }300 Ok(false)301}302303#[builtin]304pub fn builtin_all(arr: ArrValue) -> Result<bool> {305 for v in arr.iter() {306 let v = bool::from_untyped(v?)?;307 if !v {308 return Ok(false);309 }310 }311 Ok(true)312}313314#[builtin]315pub fn builtin_member(arr: IndexableVal, x: Val) -> Result<bool> {316 match arr {317 IndexableVal::Str(str) => {318 let x: IStr = IStr::from_untyped(x)?;319 Ok(!x.is_empty() && str.contains(&*x))320 }321 IndexableVal::Arr(a) => {322 for item in a.iter() {323 let item = item?;324 if equals(&item, &x)? {325 return Ok(true);326 }327 }328 Ok(false)329 }330 }331}332333#[builtin]334pub fn builtin_find(value: Val, arr: ArrValue) -> Result<Vec<usize>> {335 let mut out = Vec::new();336 for (i, ele) in arr.iter().enumerate() {337 let ele = ele?;338 if equals(&ele, &value)? {339 out.push(i);340 }341 }342 Ok(out)343}344345#[builtin]346pub fn builtin_contains(arr: IndexableVal, elem: Val) -> Result<bool> {347 builtin_member(arr, elem)348}349350#[builtin]351pub fn builtin_count(arr: ArrValue, x: Val) -> Result<usize> {352 let mut count = 0;353 for item in arr.iter() {354 if equals(&item?, &x)? {355 count += 1;356 }357 }358 Ok(count)359}360361#[builtin]362pub fn builtin_avg(arr: Vec<f64>, onEmpty: Option<Thunk<Val>>) -> Result<Val> {363 if arr.is_empty() {364 return eval_on_empty(onEmpty);365 }366 Ok(Val::try_num(arr.iter().sum::<f64>() / (arr.len() as f64))?)367}368369#[builtin]370pub fn builtin_remove_at(arr: ArrValue, at: i32) -> Result<ArrValue> {371 let newArrLeft = arr.clone().slice(None, Some(at), None);372 let newArrRight = arr.slice(Some(at + 1), None, None);373374 Ok(ArrValue::extended(newArrLeft, newArrRight))375}376377#[builtin]378pub fn builtin_remove(arr: ArrValue, elem: Val) -> Result<ArrValue> {379 for (index, item) in arr.iter().enumerate() {380 if equals(&item?, &elem)? {381 return builtin_remove_at(arr.clone(), index as i32);382 }383 }384 Ok(arr)385}386387#[builtin]388pub fn builtin_flatten_arrays(arrs: Vec<ArrValue>) -> ArrValue {389 pub fn flatten_inner(values: &[ArrValue]) -> ArrValue {390 if values.len() == 1 {391 return values[0].clone();392 } else if values.len() == 2 {393 return ArrValue::extended(values[0].clone(), values[1].clone());394 }395 let (a, b) = values.split_at(values.len() / 2);396 ArrValue::extended(flatten_inner(a), flatten_inner(b))397 }398 if arrs.is_empty() {399 return ArrValue::empty();400 } else if arrs.len() == 1 {401 return arrs.into_iter().next().expect("single");402 }403 flatten_inner(&arrs)404}405406#[builtin]407pub fn builtin_flatten_deep_array(value: Val) -> Result<Vec<Val>> {408 fn process(value: Val, out: &mut Vec<Val>) -> Result<()> {409 match value {410 Val::Arr(arr) => {411 for ele in arr.iter() {412 process(ele?, out)?;413 }414 }415 _ => out.push(value),416 }417 Ok(())418 }419 let mut out = Vec::new();420 process(value, &mut out)?;421 Ok(out)422}423424#[builtin]425pub fn builtin_prune(426 a: Val,427428 #[default(false)]429 #[cfg(feature = "exp-preserve-order")]430 preserve_order: bool,431) -> Result<Val> {432 fn is_content(val: &Val) -> bool {433 match val {434 Val::Null => false,435 Val::Arr(a) => !a.is_empty(),436 Val::Obj(o) => !o.is_empty(),437 _ => true,438 }439 }440 Ok(match a {441 Val::Arr(a) => {442 let mut out = Vec::new();443 for (i, ele) in a.iter().enumerate() {444 let ele = ele445 .and_then(|v| {446 builtin_prune(447 v,448 #[cfg(feature = "exp-preserve-order")]449 preserve_order,450 )451 })452 .with_description(|| format!("elem <{i}> pruning"))?;453 if is_content(&ele) {454 out.push(ele);455 }456 }457 Val::Arr(ArrValue::eager(out))458 }459 Val::Obj(o) => {460 let mut out = ObjValueBuilder::new();461 for (name, value) in o.iter(462 #[cfg(feature = "exp-preserve-order")]463 preserve_order,464 ) {465 let value = value466 .and_then(|v| {467 builtin_prune(468 v,469 #[cfg(feature = "exp-preserve-order")]470 preserve_order,471 )472 })473 .with_description(|| format!("field <{name}> pruning"))?;474 if !is_content(&value) {475 continue;476 }477 out.field(name).value(value);478 }479 Val::Obj(out.build())480 }481 _ => a,482 })483}crates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/math.rs
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -120,6 +120,7 @@
let lg = s.abs().log2();
let x = (lg - lg.floor() - 1.0).exp2();
let exp = lg.floor() + 1.0;
+ #[expect(clippy::cast_possible_truncation, reason = "exponent can fit in i16")]
(s.signum() * x, exp as i16)
}
}
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ b/flake.nix
@@ -66,6 +66,7 @@
"clippy"
"rustc"
"rust-src"
+ "rust-analyzer"
])
rustfmt
];