--- a/crates/jrsonnet-evaluator/src/builtin/format.rs +++ b/crates/jrsonnet-evaluator/src/builtin/format.rs @@ -86,6 +86,7 @@ } } +#[allow(clippy::struct_excessive_bools)] #[derive(Default, Debug)] pub struct CFlags { pub alt: bool, @@ -270,12 +271,12 @@ return Ok(out); } str = &str[offset + 1..]; - let (code, nstr) = parse_code(str)?; - str = nstr; + let code; + (code, str) = parse_code(str)?; bytes = str.as_bytes(); offset = 0; - out.push(Element::Code(code)) + out.push(Element::Code(code)); } } @@ -313,7 +314,7 @@ .saturating_sub(prefix.len() + digits.len()); if neg { - out.push('-') + out.push('-'); } else if sign { out.push('+'); } else if blank { @@ -340,7 +341,7 @@ blank: bool, sign: bool, ) { - render_integer(out, iv, padding, precision, blank, sign, 10, "", false) + render_integer(out, iv, padding, precision, blank, sign, 10, "", false); } pub fn render_octal( out: &mut String, @@ -361,8 +362,10 @@ 8, if alt && iv != 0 { "0" } else { "" }, false, - ) + ); } + +#[allow(clippy::fn_params_excessive_bools)] pub fn render_hexadecimal( out: &mut String, iv: i64, @@ -387,9 +390,10 @@ (false, _) => "", }, caps, - ) + ); } +#[allow(clippy::fn_params_excessive_bools)] pub fn render_float( out: &mut String, n: f64, @@ -431,6 +435,7 @@ } } +#[allow(clippy::fn_params_excessive_bools)] pub fn render_float_sci( out: &mut String, n: f64, @@ -461,6 +466,7 @@ out.push_str(&exponent_str); } +#[allow(clippy::too_many_lines)] pub fn format_code( s: State, out: &mut String, --- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs +++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs @@ -158,7 +158,7 @@ '\r' => buf.push_str("\\r"), '\t' => buf.push_str("\\t"), c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => { - write!(buf, "\\u{:04x}", c as u32).unwrap() + write!(buf, "\\u{:04x}", c as u32).unwrap(); } c => buf.push(c), } @@ -194,7 +194,7 @@ pub preserve_order: bool, } -/// From https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289 +/// From /// With added date check fn yaml_needs_quotes(string: &str) -> bool { fn need_quotes_spaces(string: &str) -> bool { @@ -227,6 +227,8 @@ manifest_yaml_ex_buf(s, val, &mut out, &mut String::new(), options)?; Ok(out) } + +#[allow(clippy::too_many_lines)] fn manifest_yaml_ex_buf( s: State, val: &Val, @@ -238,9 +240,9 @@ match val { Val::Bool(v) => { if *v { - buf.push_str("true") + buf.push_str("true"); } else { - buf.push_str("false") + buf.push_str("false"); } } Val::Null => buf.push_str("null"), --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -1,3 +1,6 @@ +// All builtins should return results +#![allow(clippy::unnecessary_wraps)] + use std::collections::HashMap; use format::{format_arr, format_obj}; @@ -173,7 +176,7 @@ fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result { let mut out = Vec::with_capacity(sz); for i in 0..sz { - out.push(func.evaluate_simple(s.clone(), &[i as f64].as_slice())?) + out.push(func.evaluate_simple(s.clone(), &[i as f64].as_slice())?); } Ok(VecVal(Cc::new(out))) } @@ -420,7 +423,7 @@ match func.evaluate_simple(s.clone(), &[Any(el)].as_slice())? { Val::Arr(o) => { for oe in o.iter(s.clone()) { - out.push(oe?) + out.push(oe?); } } _ => throw!(RuntimeError( @@ -707,7 +710,7 @@ #[jrsonnet_macros::builtin] fn builtin_count(s: State, arr: Vec, v: Any) -> Result { let mut count = 0; - for item in arr.iter() { + for item in &arr { if equals(s.clone(), &item.0, &v.0)? { count += 1; } --- a/crates/jrsonnet-evaluator/src/builtin/sort.rs +++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs @@ -53,10 +53,10 @@ match (i, sort_type) { (Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String, (Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number, - (Val::Str(_), SortKeyType::String) => {} - (Val::Str(_), _) => throw!(SortError::SortElementsShouldHaveEqualType), - (Val::Num(_), SortKeyType::Number) => {} - (Val::Num(_), _) => throw!(SortError::SortElementsShouldHaveEqualType), + (Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {} + (Val::Str(_) | Val::Num(_), _) => { + throw!(SortError::SortElementsShouldHaveEqualType) + } _ => throw!(SortError::SortKeyShouldBeStringOrNumber), } } @@ -91,19 +91,19 @@ Ok(Cc::new(vk.into_iter().map(|v| v.0).collect())) } else { // Fast path, identity key getter - let mut mvalues = (*values).clone(); - let sort_type = get_sort_type(&mut mvalues, |k| k)?; + let mut values = (*values).clone(); + let sort_type = get_sort_type(&mut values, |k| k)?; match sort_type { - SortKeyType::Number => mvalues.sort_unstable_by_key(|v| match v { + SortKeyType::Number => values.sort_unstable_by_key(|v| match v { Val::Num(n) => NonNaNf64(*n), _ => unreachable!(), }), - SortKeyType::String => mvalues.sort_unstable_by_key(|v| match v { + SortKeyType::String => values.sort_unstable_by_key(|v| match v { Val::Str(s) => s.clone(), _ => unreachable!(), }), SortKeyType::Unknown => unreachable!(), }; - Ok(Cc::new(mvalues)) + Ok(Cc::new(values)) } } --- a/crates/jrsonnet-evaluator/src/builtin/stdlib.rs +++ b/crates/jrsonnet-evaluator/src/builtin/stdlib.rs @@ -24,5 +24,5 @@ } pub fn get_parsed_stdlib() -> LocExpr { - PARSED_STDLIB.with(|t| t.clone()) + PARSED_STDLIB.with(Clone::clone) } --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -79,6 +79,7 @@ pub fn contains_binding(&self, name: IStr) -> bool { self.0.bindings.contains_key(&name) } + #[must_use] pub fn into_future(self, ctx: FutureWrapper) -> Self { { ctx.0.borrow_mut().replace(self); @@ -86,16 +87,19 @@ ctx.unwrap() } + #[must_use] pub fn with_var(self, name: IStr, value: Val) -> Self { let mut new_bindings = GcHashMap::with_capacity(1); new_bindings.insert(name, LazyVal::new_resolved(value)); self.extend(new_bindings, None, None, None) } + #[must_use] pub fn with_this_super(self, new_this: ObjValue, new_super_obj: Option) -> Self { self.extend(GcHashMap::new(), None, Some(new_this), new_super_obj) } + #[must_use] pub fn extend( self, new_bindings: GcHashMap, @@ -119,6 +123,7 @@ bindings, })) } + #[must_use] pub fn extend_bound(self, new_bindings: GcHashMap) -> Self { let new_this = self.0.this.clone(); let new_super_obj = self.0.super_obj.clone(); @@ -135,7 +140,7 @@ let this = new_this.or_else(|| self.0.this.clone()); let super_obj = new_super_obj.or_else(|| self.0.super_obj.clone()); let mut new = GcHashMap::with_capacity(new_bindings.len()); - for (k, v) in new_bindings.0.into_iter() { + for (k, v) in new_bindings.0 { new.insert(k, v.evaluate(s.clone(), this.clone(), super_obj.clone())?); } Ok(self.extend(new, new_dollar, this, super_obj)) --- a/crates/jrsonnet-evaluator/src/dynamic.rs +++ b/crates/jrsonnet-evaluator/src/dynamic.rs @@ -8,12 +8,16 @@ pub fn new() -> Self { Self(Cc::new(RefCell::new(None))) } + /// # Panics + /// If wrapper is filled already pub fn fill(self, value: T) { assert!(self.0.borrow().is_none(), "wrapper is filled already"); self.0.borrow_mut().replace(value); } } impl FutureWrapper { + /// # Panics + /// If wrapper is not yet filled pub fn unwrap(&self) -> T { self.0.borrow().as_ref().cloned().unwrap() } --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -96,7 +96,8 @@ #[error( "syntax error: expected {}, got {:?}", .error.expected, - .source_code.chars().nth(error.location.offset).map(|c| c.to_string()).unwrap_or_else(|| "EOF".into()) + .source_code.chars().nth(error.location.offset) + .map_or_else(|| "EOF".into(), |c| c.to_string()) )] ImportSyntaxError { #[skip_trace] @@ -190,7 +191,7 @@ impl Debug for LocError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "{}", self.0 .0)?; - for el in self.0 .1 .0.iter() { + for el in &self.0 .1 .0 { writeln!(f, "\t{:?}", el)?; } Ok(()) --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -23,8 +23,6 @@ pub fn evaluate_binding_in_future(b: &BindSpec, fctx: FutureWrapper) -> LazyVal { let b = b.clone(); if let Some(params) = &b.params { - let params = params.clone(); - #[derive(Trace)] struct LazyMethodBinding { fctx: FutureWrapper, @@ -43,6 +41,8 @@ } } + let params = params.clone(); + LazyVal::new(TraceBox(Box::new(LazyMethodBinding { fctx, name: b.name.clone(), @@ -69,11 +69,10 @@ } } +#[allow(clippy::too_many_lines)] pub fn evaluate_binding(b: &BindSpec, cctx: ContextCreator) -> (IStr, LazyBinding) { let b = b.clone(); if let Some(params) = &b.params { - let params = params.clone(); - #[derive(Trace)] struct BindableMethodLazyVal { this: Option, @@ -121,6 +120,8 @@ } } + let params = params.clone(); + ( b.name.clone(), LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod { @@ -227,7 +228,7 @@ None => callback(ctx)?, Some(CompSpec::IfSpec(IfSpecData(cond))) => { if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? { - evaluate_comp(s, ctx, &specs[1..], callback)? + evaluate_comp(s, ctx, &specs[1..], callback)?; } } Some(CompSpec::ForSpec(ForSpecData(var, expr))) => { @@ -239,7 +240,7 @@ ctx.clone().with_var(var.clone(), item?.clone()), &specs[1..], callback, - )? + )?; } } _ => throw!(InComprehensionCanOnlyIterateOverArray), @@ -249,6 +250,7 @@ Ok(()) } +#[allow(clippy::too_many_lines)] pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result { let new_bindings = FutureWrapper::new(); let future_this = FutureWrapper::new(); @@ -278,12 +280,6 @@ visibility, value, }) => { - let name = evaluate_field_name(s.clone(), ctx.clone(), name)?; - if name.is_none() { - continue; - } - let name = name.unwrap(); - #[derive(Trace)] struct ObjMemberBinding { cctx: ContextCreator, @@ -305,6 +301,14 @@ )?)) } } + + let name = evaluate_field_name(s.clone(), ctx.clone(), name)?; + let name = if let Some(name) = name { + name + } else { + continue; + }; + builder .member(name.clone()) .with_add(*plus) @@ -325,11 +329,6 @@ value, .. }) => { - let name = evaluate_field_name(s.clone(), ctx.clone(), name)?; - if name.is_none() { - continue; - } - let name = name.unwrap(); #[derive(Trace)] struct ObjMemberBinding { cctx: ContextCreator, @@ -352,6 +351,13 @@ ))) } } + + let name = if let Some(name) = evaluate_field_name(s.clone(), ctx.clone(), name)? { + name + } else { + continue; + }; + builder .member(name.clone()) .hide() @@ -505,24 +511,24 @@ throw!(AssertionFailed( evaluate(s.clone(), ctx, msg)?.to_string(s.clone())? )); - } else { - throw!(AssertionFailed(Val::Null.to_string(s.clone())?)); } + throw!(AssertionFailed(Val::Null.to_string(s.clone())?)); }, - )? + )?; } Ok(()) } -pub fn evaluate_named(s: State, ctx: Context, lexpr: &LocExpr, name: IStr) -> Result { +pub fn evaluate_named(s: State, ctx: Context, expr: &LocExpr, name: IStr) -> Result { use Expr::*; - let LocExpr(expr, _loc) = lexpr; - Ok(match &**expr { + let LocExpr(raw_expr, _loc) = expr; + Ok(match &**raw_expr { Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()), - _ => evaluate(s, ctx, lexpr)?, + _ => evaluate(s, ctx, expr)?, }) } +#[allow(clippy::too_many_lines)] pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result { use Expr::*; let LocExpr(expr, loc) = expr; @@ -532,10 +538,11 @@ Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?) } Literal(LiteralType::Super) => Val::Obj( - ctx.super_obj() - .clone() - .ok_or(NoSuperFound)? - .with_this(ctx.this().clone().unwrap()), + ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this( + ctx.this() + .clone() + .expect("if super exists - then this should to"), + ), ), Literal(LiteralType::Dollar) => { Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?) @@ -699,9 +706,6 @@ } } Slice(value, desc) => { - let indexable = evaluate(s.clone(), ctx.clone(), value)?; - let loc = CallLocation::new(loc); - fn parse_idx( loc: CallLocation, s: State, @@ -720,6 +724,9 @@ } } + let indexable = evaluate(s.clone(), ctx.clone(), value)?; + let loc = CallLocation::new(loc); + let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?; let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?; let step = parse_idx(loc, s, &ctx, &desc.step, "step")?; --- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use jrsonnet_parser::{BinaryOpType, LocExpr, UnaryOpType}; use crate::{ @@ -11,7 +13,7 @@ Ok(match (op, b) { (Not, Bool(v)) => Bool(!v), (Minus, Num(n)) => Num(-*n), - (BitNot, Num(n)) => Num(!(*n as i32) as f64), + (BitNot, Num(n)) => Num(f64::from(!(*n as i32))), (op, o) => throw!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())), }) } @@ -22,8 +24,8 @@ (Str(v1), Str(v2)) => Str(((**v1).to_owned() + v2).into()), // Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890) - (Num(n), Str(o)) => Str(format!("{}{}", n, o).into()), - (Str(o), Num(n)) => Str(format!("{}{}", o, n).into()), + (Num(a), Str(b)) => Str(format!("{a}{b}").into()), + (Str(a), Num(b)) => Str(format!("{a}{b}").into()), (Str(a), o) => Str(format!("{}{}", a, o.clone().to_string(s)?).into()), (o, Str(a)) => Str(format!("{}{}", o.clone().to_string(s)?, a).into()), @@ -47,7 +49,12 @@ pub fn evaluate_mod_op(s: State, a: &Val, b: &Val) -> Result { use Val::*; match (a, b) { - (Num(a), Num(b)) => Ok(Num(a % b)), + (Num(a), Num(b)) => { + if *b == 0.0 { + throw!(DivisionByZero) + } + Ok(Num(a % b)) + } (Str(str), vals) => { String::into_untyped(std_format(s.clone(), str.clone(), vals.clone())?, s) } @@ -75,6 +82,32 @@ }) } +pub fn evaluate_compare_op(s: State, a: &Val, op: BinaryOpType, b: &Val) -> Result { + use Val::*; + Ok(match (a, b) { + (Str(a), Str(b)) => a.cmp(b), + (Num(a), Num(b)) => a.partial_cmp(b).expect("jsonnet numbers are non NaN"), + (Arr(a), Arr(b)) => { + let ai = a.iter(s.clone()); + let bi = b.iter(s.clone()); + + for (a, b) in ai.zip(bi) { + let ord = evaluate_compare_op(s.clone(), &a?, op, &b?)?; + if !ord.is_eq() { + return Ok(ord); + } + } + + a.len().cmp(&b.len()) + } + (_, _) => throw!(BinaryOperatorDoesNotOperateOnValues( + op, + a.value_type(), + b.value_type() + )), + }) +} + pub fn evaluate_binary_op_normal(s: State, a: &Val, op: BinaryOpType, b: &Val) -> Result { use BinaryOpType::*; use Val::*; @@ -84,6 +117,11 @@ (a, Eq, b) => Bool(equals(s, a, b)?), (a, Neq, b) => Bool(!equals(s, a, b)?), + (a, Lt, b) => Bool(evaluate_compare_op(s, a, Lt, b)?.is_lt()), + (a, Gt, b) => Bool(evaluate_compare_op(s, a, Gt, b)?.is_gt()), + (a, Lte, b) => Bool(evaluate_compare_op(s, a, Lte, b)?.is_le()), + (a, Gte, b) => Bool(evaluate_compare_op(s, a, Gte, b)?.is_ge()), + (Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone(), true)), (a, Mod, b) => evaluate_mod_op(s, a, b)?, @@ -92,17 +130,11 @@ // Bool X Bool (Bool(a), And, Bool(b)) => Bool(*a && *b), (Bool(a), Or, Bool(b)) => Bool(*a || *b), - - // Str X Str - (Str(v1), Lt, Str(v2)) => Bool(v1 < v2), - (Str(v1), Gt, Str(v2)) => Bool(v1 > v2), - (Str(v1), Lte, Str(v2)) => Bool(v1 <= v2), - (Str(v1), Gte, Str(v2)) => Bool(v1 >= v2), // Num X Num (Num(v1), Mul, Num(v2)) => Val::new_checked_num(v1 * v2)?, (Num(v1), Div, Num(v2)) => { - if *v2 <= f64::EPSILON { + if *v2 == 0.0 { throw!(DivisionByZero) } Val::new_checked_num(v1 / v2)? @@ -110,25 +142,20 @@ (Num(v1), Sub, Num(v2)) => Val::new_checked_num(v1 - v2)?, - (Num(v1), Lt, Num(v2)) => Bool(v1 < v2), - (Num(v1), Gt, Num(v2)) => Bool(v1 > v2), - (Num(v1), Lte, Num(v2)) => Bool(v1 <= v2), - (Num(v1), Gte, Num(v2)) => Bool(v1 >= v2), - - (Num(v1), BitAnd, Num(v2)) => Num(((*v1 as i32) & (*v2 as i32)) as f64), - (Num(v1), BitOr, Num(v2)) => Num(((*v1 as i32) | (*v2 as i32)) as f64), - (Num(v1), BitXor, Num(v2)) => Num(((*v1 as i32) ^ (*v2 as i32)) as f64), + (Num(v1), BitAnd, Num(v2)) => Num(f64::from((*v1 as i32) & (*v2 as i32))), + (Num(v1), BitOr, Num(v2)) => Num(f64::from((*v1 as i32) | (*v2 as i32))), + (Num(v1), BitXor, Num(v2)) => Num(f64::from((*v1 as i32) ^ (*v2 as i32))), (Num(v1), Lhs, Num(v2)) => { if *v2 < 0.0 { throw!(RuntimeError("shift by negative exponent".into())) } - Num(((*v1 as i32) << (*v2 as i32)) as f64) + Num(f64::from((*v1 as i32) << (*v2 as i32))) } (Num(v1), Rhs, Num(v2)) => { if *v2 < 0.0 { throw!(RuntimeError("shift by negative exponent".into())) } - Num(((*v1 as i32) >> (*v2 as i32)) as f64) + Num(f64::from((*v1 as i32) >> (*v2 as i32))) } _ => throw!(BinaryOperatorDoesNotOperateOnValues( --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -145,7 +145,7 @@ tailstrict: bool, handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, ) -> Result<()> { - for (name, arg) in self.named.iter() { + for (name, arg) in &self.named { handler( name, if tailstrict { @@ -162,8 +162,8 @@ } fn named_names(&self, handler: &mut dyn FnMut(&IStr)) { - for (name, _) in self.named.iter() { - handler(name) + for (name, _) in &self.named { + handler(name); } } } @@ -231,7 +231,7 @@ } } -impl ArgsLike for HashMap { +impl ArgsLike for HashMap { fn unnamed_len(&self) -> usize { 0 } @@ -325,7 +325,7 @@ } fn named_names(&self, handler: &mut dyn FnMut(&IStr)) { - (*self).named_names(handler) + (*self).named_names(handler); } } @@ -381,18 +381,13 @@ if passed_args.contains_key(¶m.0.clone()) { continue; } - LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal { - ctx: fctx.clone(), - name: param.0.clone(), - value: param.1.clone().unwrap(), - }))); defaults.insert( param.0.clone(), LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal { ctx: fctx.clone(), name: param.0.clone(), - value: param.1.clone().unwrap(), + value: param.1.clone().expect("default exists"), }))), ); filled_args += 1; @@ -447,7 +442,7 @@ // const INST: &'static Self; } -/// You shouldn't probally use this function, use jrsonnet_macros::builtin instead +/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead /// /// ## Parameters /// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set) @@ -518,10 +513,6 @@ /// Creates Context, which has all argument default values applied /// and with unbound values causing error to be returned pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context { - let fctx = Context::new_future(); - - let mut bindings = GcHashMap::new(); - #[derive(Trace)] struct DependsOnUnbound(IStr); impl LazyValValue for DependsOnUnbound { @@ -530,6 +521,10 @@ } } + let fctx = Context::new_future(); + + let mut bindings = GcHashMap::new(); + for param in params.iter() { if let Some(v) = ¶m.1 { bindings.insert( --- a/crates/jrsonnet-evaluator/src/gc.rs +++ b/crates/jrsonnet-evaluator/src/gc.rs @@ -1,6 +1,7 @@ /// Macros to help deal with Gc use std::{ borrow::{Borrow, BorrowMut}, + collections::{HashMap, HashSet}, hash::BuildHasherDefault, ops::{Deref, DerefMut}, }; @@ -14,7 +15,7 @@ impl Trace for TraceBox { fn trace(&self, tracer: &mut Tracer) { - self.0.trace(tracer) + self.0.trace(tracer); } fn is_type_tracked() -> bool { @@ -70,7 +71,7 @@ pub struct GcHashSet(pub FxHashSet); impl GcHashSet { pub fn new() -> Self { - Self(Default::default()) + Self(HashSet::default()) } pub fn with_capacity(capacity: usize) -> Self { Self(FxHashSet::with_capacity_and_hasher( @@ -111,7 +112,7 @@ pub struct GcHashMap(pub FxHashMap); impl GcHashMap { pub fn new() -> Self { - Self(Default::default()) + Self(HashMap::default()) } pub fn with_capacity(capacity: usize) -> Self { Self(FxHashMap::with_capacity_and_hasher( --- a/crates/jrsonnet-evaluator/src/import.rs +++ b/crates/jrsonnet-evaluator/src/import.rs @@ -79,7 +79,7 @@ if direct.exists() { Ok(direct.into()) } else { - for library_path in self.library_paths.iter() { + for library_path in &self.library_paths { let mut cloned = library_path.clone(); cloned.push(path); if cloned.exists() { --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -1,7 +1,22 @@ -#![warn(clippy::all, clippy::nursery)] +#![warn(clippy::all, clippy::nursery, clippy::pedantic)] #![allow( macro_expanded_macro_exports_accessed_by_absolute_paths, - clippy::ptr_arg + clippy::ptr_arg, + // Too verbose + clippy::must_use_candidate, + // A lot of functions pass around errors thrown by code + clippy::missing_errors_doc, + // A lot of pointers have interior Rc + clippy::needless_pass_by_value, + // Its fine + clippy::wildcard_imports, + clippy::enum_glob_use, + clippy::module_name_repetitions, + // TODO: fix individual issues, however this works as intended almost everywhere + clippy::cast_precision_loss, + clippy::cast_possible_wrap, + clippy::cast_possible_truncation, + clippy::cast_sign_loss, )] // For jrsonnet-macros @@ -105,10 +120,10 @@ Self { max_stack: 200, max_trace: 20, - globals: Default::default(), - ext_vars: Default::default(), - ext_natives: Default::default(), - tla_vars: Default::default(), + globals: HashMap::default(), + ext_vars: HashMap::default(), + ext_natives: HashMap::default(), + tla_vars: HashMap::default(), import_resolver: Box::new(DummyImportResolver), manifest_format: ManifestFormat::Json { padding: 4, @@ -161,7 +176,7 @@ if self.0.is_empty() { return result; } - for item in self.0.iter() { + for item in &self.0 { if item.loc.belongs_to(loc) { let mut collected = item.collected.borrow_mut(); let (depth, vals) = collected.entry(stack_generation).or_default(); @@ -198,7 +213,7 @@ ) .map_err(|error| ImportSyntaxError { error: Box::new(error), - path: path.to_owned(), + path: path.clone(), source_code: source_code.clone(), })?; self.add_parsed_file(path, source_code, parsed.clone())?; @@ -210,7 +225,7 @@ self.data_mut() .files .get_mut(name) - .unwrap() + .expect("file not found") .evaluated .take(); } @@ -246,7 +261,11 @@ line: usize, column: usize, ) -> Option { - location_to_offset(&self.get_source(file).unwrap(), line, column) + location_to_offset( + &self.get_source(file).expect("file not found"), + line, + column, + ) } pub fn import_file(&self, from: &Path, path: &Path) -> Result { let file_path = self.resolve_file(from, path)?; @@ -312,8 +331,10 @@ STDLIB_STR.to_owned().into(), builtin::get_parsed_stdlib(), ) - .unwrap(); - let val = self.evaluate_loaded_file_raw(&std_path).unwrap(); + .expect("stdlib is correct"); + let val = self + .evaluate_loaded_file_raw(&std_path) + .expect("stdlib is correct"); self.settings_mut().globals.insert("std".into(), val); self } @@ -342,9 +363,8 @@ // Error creation uses data, so i drop guard here drop(data); throw!(StackOverflow); - } else { - *stack_depth += 1; } + *stack_depth += 1; } let result = f(); { @@ -376,9 +396,8 @@ // Error creation uses data, so i drop guard here drop(data); throw!(StackOverflow); - } else { - *stack_depth += 1; } + *stack_depth += 1; } let mut result = f(); { @@ -411,9 +430,8 @@ // Error creation uses data, so i drop guard here drop(data); throw!(StackOverflow); - } else { - *stack_depth += 1; } + *stack_depth += 1; } let result = f(); { @@ -431,6 +449,8 @@ result } + /// # Panics + /// In case of formatting failure pub fn stringify_err(&self, e: &LocError) -> String { let mut out = String::new(); self.settings() @@ -1232,49 +1252,5 @@ assert_eval!(r#"std.assertEqual(std.count([], ""), 0)"#); assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "d"), 0)"#); assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "a"), 2)"#); - } - - mod derive_typed { - use std::path::PathBuf; - - use crate::{typed::Typed, State}; - - #[derive(PartialEq, Debug, Typed)] - struct MyTyped { - a: u32, - #[typed(rename = "b")] - c: String, - } - - #[test] - fn test() { - let es = State::default(); - let val = eval!("{a: 14, b: 'Hello, world!'}"); - let typed = MyTyped::try_from(val).unwrap(); - - assert_eq!( - typed, - MyTyped { - a: 14, - c: "Hello, world!".to_string() - } - ); - es.settings_mut() - .globals - .insert("mytyped".into(), typed.try_into().unwrap()); - - let v = es - .evaluate_snippet_raw( - PathBuf::from("raw.jsonnet").into(), - " - mytyped == {a: 14, b: 'Hello, world!'} - " - .into(), - ) - .unwrap() - .as_bool() - .unwrap(); - assert!(v) - } } } --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -34,8 +34,7 @@ .0 .parent .as_ref() - .map(|p| p.contains_key(key)) - .unwrap_or(false) + .map_or(false, |p| p.contains_key(key)) } } --- a/crates/jrsonnet-evaluator/src/native.rs +++ b/crates/jrsonnet-evaluator/src/native.rs @@ -37,7 +37,7 @@ fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result { let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?; let mut out_args = Vec::with_capacity(self.params.len()); - for p in self.params.iter() { + for p in &self.params { out_args.push(args[&p.name].evaluate(s.clone())?); } self.handler.call(s, loc.0.map(|l| l.0.clone()), &out_args) --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -2,6 +2,7 @@ cell::RefCell, fmt::Debug, hash::{Hash, Hasher}, + ptr::addr_of, }; use gcmodule::{Cc, Trace, Weak}; @@ -20,6 +21,11 @@ #[cfg(not(feature = "exp-preserve-order"))] mod ordering { + #![allow( + // This module works as stub for preserve-order feature + clippy::unused_self, + )] + use gcmodule::Trace; #[derive(Clone, Copy, Default, Debug, Trace)] @@ -89,6 +95,7 @@ use ordering::*; +#[allow(clippy::module_name_repetitions)] #[derive(Debug, Trace)] pub struct ObjMember { pub add: bool, @@ -113,6 +120,7 @@ Errored(LocError), } +#[allow(clippy::module_name_repetitions)] #[derive(Trace)] #[force_tracking] pub struct ObjValueInternals { @@ -136,10 +144,11 @@ impl Eq for WeakObjValue {} impl Hash for WeakObjValue { fn hash(&self, hasher: &mut H) { - hasher.write_usize(weak_raw(self.0.clone()) as usize) + hasher.write_usize(weak_raw(self.0.clone()) as usize); } } +#[allow(clippy::module_name_repetitions)] #[derive(Clone, Trace)] pub struct ObjValue(pub(crate) Cc); impl Debug for ObjValue { @@ -178,6 +187,7 @@ pub fn new_empty() -> Self { Self::new(None, Cc::new(GcHashMap::new()), Cc::new(Vec::new())) } + #[must_use] pub fn extend_from(&self, super_obj: Self) -> Self { match &self.0.super_obj { None => Self::new( @@ -200,6 +210,8 @@ pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder { ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default()) } + + #[must_use] pub fn with_this(&self, this_obj: Self) -> Self { Self(Cc::new(ObjValueInternals { super_obj: self.0.super_obj.clone(), @@ -222,11 +234,7 @@ if !self.0.this_entries.is_empty() { return false; } - self.0 - .super_obj - .as_ref() - .map(|s| s.is_empty()) - .unwrap_or(true) + self.0.super_obj.as_ref().map_or(true, Self::is_empty) } /// Run callback for every field found in object @@ -254,15 +262,15 @@ let new_sort_key = FieldSortKey::new(depth, member.original_index); match member.visibility { Visibility::Normal => { - let entry = out.entry(name.to_owned()); + let entry = out.entry(name.clone()); let v = entry.or_insert((true, new_sort_key)); v.1 = new_sort_key; } Visibility::Hidden => { - out.insert(name.to_owned(), (false, new_sort_key)); + out.insert(name.clone(), (false, new_sort_key)); } Visibility::Unhide => { - out.insert(name.to_owned(), (true, new_sort_key)); + out.insert(name.clone(), (true, new_sort_key)); } }; false @@ -356,8 +364,7 @@ } pub fn has_field(&self, name: IStr) -> bool { self.field_visibility(name) - .map(|v| v.is_visible()) - .unwrap_or(false) + .map_or(false, |v| v.is_visible()) } pub fn get(&self, s: State, key: IStr) -> Result> { @@ -459,10 +466,11 @@ impl Eq for ObjValue {} impl Hash for ObjValue { fn hash(&self, hasher: &mut H) { - hasher.write_usize(&*self.0 as *const _ as usize) + hasher.write_usize(addr_of!(*self.0) as usize); } } +#[allow(clippy::module_name_repetitions)] pub struct ObjValueBuilder { super_obj: Option, map: GcHashMap, @@ -510,6 +518,7 @@ } } +#[allow(clippy::module_name_repetitions)] #[must_use = "value not added unless binding() was called"] pub struct ObjMemberBuilder { kind: Kind, @@ -583,7 +592,7 @@ CallLocation(location.as_ref()), || format!("field <{}> initializtion", name.clone()), || throw!(DuplicateFieldName(name.clone())), - )? + )?; } Ok(()) } @@ -592,14 +601,14 @@ pub struct ExtendBuilder<'v>(&'v mut ObjValue); impl<'v> ObjMemberBuilder> { pub fn value(self, value: Val) { - self.binding(LazyBinding::Bound(LazyVal::new_resolved(value))) + self.binding(LazyBinding::Bound(LazyVal::new_resolved(value))); } pub fn bindable(self, bindable: TraceBox) { - self.binding(LazyBinding::Bindable(Cc::new(bindable))) + self.binding(LazyBinding::Bindable(Cc::new(bindable))); } pub fn binding(self, binding: LazyBinding) { let (receiver, name, member) = self.build_member(binding); let new = receiver.0.clone(); - *receiver.0 = new.extend_with_raw_member(name, member) + *receiver.0 = new.extend_with_raw_member(name, member); } } --- a/crates/jrsonnet-evaluator/src/trace/location.rs +++ b/crates/jrsonnet-evaluator/src/trace/location.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_name_repetitions)] #[derive(Clone, PartialEq, Debug)] pub struct CodeLocation { pub offset: usize, @@ -9,6 +10,7 @@ pub line_end_offset: usize, } +#[allow(clippy::module_name_repetitions)] pub fn location_to_offset(mut file: &str, mut line: usize, column: usize) -> Option { let mut offset = 0; while line > 1 { @@ -21,13 +23,14 @@ Some(offset) } +#[allow(clippy::module_name_repetitions)] pub fn offset_to_location(file: &str, offsets: &[usize]) -> Vec { if offsets.is_empty() { return vec![]; } let mut line = 1; let mut column = 1; - let max_offset = *offsets.iter().max().unwrap(); + let max_offset = *offsets.iter().max().expect("offsets is not empty"); let mut offset_map = offsets .iter() --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -19,14 +19,18 @@ impl PathResolver { pub fn resolve(&self, from: &Path) -> String { match self { - Self::FileName => from.file_name().unwrap().to_string_lossy().into_owned(), + Self::FileName => from + .file_name() + .expect("file name exists") + .to_string_lossy() + .into_owned(), Self::Absolute => from.to_string_lossy().into_owned(), Self::Relative(base) => { if from.is_relative() { return from.to_string_lossy().into_owned(); } pathdiff::diff_paths(from, base) - .unwrap() + .expect("base is absolute") .to_string_lossy() .into_owned() } @@ -35,6 +39,7 @@ } /// Implements pretty-printing of traces +#[allow(clippy::module_name_repetitions)] pub trait TraceFormat { fn write_trace( &self, @@ -88,8 +93,9 @@ error, } = error.error() { + use std::fmt::Write; + writeln!(out)?; - use std::fmt::Write; let mut n = self.resolver.resolve(path); let mut offset = error.location.offset; let is_eof = if offset >= source_code.len() { @@ -134,7 +140,7 @@ let align = file_names .iter() .flatten() - .map(|e| e.len()) + .map(String::len) .max() .unwrap_or(0); for (el, file) in error.trace().0.iter().zip(file_names) { @@ -166,7 +172,7 @@ error: &LocError, ) -> Result<(), std::fmt::Error> { write!(out, "{}", error.error())?; - for item in error.trace().0.iter() { + for item in &error.trace().0 { writeln!(out)?; let desc = &item.desc; if let Some(source) = &item.location { @@ -227,7 +233,7 @@ )?; } let trace = &error.trace(); - for item in trace.0.iter() { + for item in &trace.0 { writeln!(out)?; let desc = &item.desc; if let Some(source) = &item.location { @@ -273,7 +279,7 @@ let snippet = Snippet { opt: FormatOptions { color: true, - ..Default::default() + ..FormatOptions::default() }, title: None, footer: vec![], --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -38,6 +38,7 @@ ::TYPE.check(s, &value)?; match value { Val::Num(n) => { + #[allow(clippy::float_cmp)] if n.trunc() != n { throw!(RuntimeError( format!( @@ -95,6 +96,7 @@ ::TYPE.check(s, &value)?; match value { Val::Num(n) => { + #[allow(clippy::float_cmp)] if n.trunc() != n { throw!(RuntimeError( format!( @@ -160,7 +162,7 @@ impl Typed for usize { // It is possible to store 54 bits of precision in f64, but leaving u32::MAX here for compatibility const TYPE: &'static ComplexValType = - &ComplexValType::BoundedNumber(Some(0.0), Some(4294967295.0)); + &ComplexValType::BoundedNumber(Some(0.0), Some(u32::MAX as f64)); fn into_untyped(value: Self, _: State) -> Result { if value > u32::MAX as Self { @@ -173,6 +175,7 @@ ::TYPE.check(s, &value)?; match value { Val::Num(n) => { + #[allow(clippy::float_cmp)] if n.trunc() != n { throw!(RuntimeError( "cannot convert number with fractional part to usize".into() @@ -263,7 +266,7 @@ } /// To be used in Vec -/// Regular Val can't be used here, because it has wrong TryFrom::Error type +/// Regular Val can't be used here, because it has wrong `TryFrom::Error` type #[derive(Clone)] pub struct Any(pub Val); @@ -279,7 +282,7 @@ } } -/// Specialization, provides faster TryFrom for Val +/// Specialization, provides faster `TryFrom` for Val pub struct VecVal(pub Cc>); impl Typed for VecVal { @@ -310,22 +313,20 @@ } fn from_untyped(value: Val, s: State) -> Result { + if let Val::Arr(ArrValue::Bytes(bytes)) = value { + return Ok(Self(bytes)); + } + ::TYPE.check(s.clone(), &value)?; match value { - Val::Arr(ArrValue::Bytes(bytes)) => Ok(Self(bytes)), - _ => { - ::TYPE.check(s.clone(), &value)?; - match value { - Val::Arr(a) => { - let mut out = Vec::with_capacity(a.len()); - for e in a.iter(s.clone()) { - let r = e?; - out.push(u8::from_untyped(r, s.clone())?); - } - Ok(Self(out.into())) - } - _ => unreachable!(), + Val::Arr(a) => { + let mut out = Vec::with_capacity(a.len()); + for e in a.iter(s.clone()) { + let r = e?; + out.push(u8::from_untyped(r, s.clone())?); } + Ok(Self(out.into())) } + _ => unreachable!(), } } } --- a/crates/jrsonnet-evaluator/src/typed/mod.rs +++ b/crates/jrsonnet-evaluator/src/typed/mod.rs @@ -71,11 +71,11 @@ if line.trim().is_empty() { continue; } - if i != 0 { + if i == 0 { + write!(f, " - ")?; + } else { writeln!(f)?; write!(f, " ")?; - } else { - write!(f, " - ")?; } write!(f, "{}", line)?; } @@ -94,7 +94,7 @@ Ok(_) => Ok(()), Err(mut e) => { if let Error::TypeError(e) = &mut e.error_mut() { - (e.1).0.push(path()) + (e.1).0.push(path()); } Err(e) } @@ -145,6 +145,7 @@ } impl CheckType for ComplexValType { + #[allow(clippy::too_many_lines)] fn check(&self, s: State, value: &Val) -> Result<()> { match self { Self::Any => Ok(()), @@ -202,7 +203,7 @@ || format!("property {}", k), || ValuePathItem::Field((*k).into()), || v.check(s.clone(), &got_v), - )? + )?; } else { return Err( TypeError::MissingProperty((*k).into(), self.clone()).into() @@ -245,13 +246,13 @@ } Self::Sum(types) => { for ty in types.iter() { - ty.check(s.clone(), value)? + ty.check(s.clone(), value)?; } Ok(()) } Self::SumRef(types) => { for ty in types.iter() { - ty.check(s.clone(), value)? + ty.check(s.clone(), value)?; } Ok(()) } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -32,6 +32,7 @@ Pending, } +#[allow(clippy::module_name_repetitions)] #[derive(Clone, Trace)] pub struct LazyVal(Cc>); impl LazyVal { @@ -50,7 +51,7 @@ LazyValInternals::Computed(v) => return Ok(v.clone()), LazyValInternals::Errored(e) => return Err(e.clone()), LazyValInternals::Pending => return Err(InfiniteRecursionDetected.into()), - _ => (), + LazyValInternals::Waiting(..) => (), }; let value = if let LazyValInternals::Waiting(value) = std::mem::replace(&mut *self.0.borrow_mut(), LazyValInternals::Pending) @@ -114,6 +115,7 @@ } } +#[allow(clippy::module_name_repetitions)] #[derive(Trace, Clone)] pub enum FuncVal { /// Plain function implemented in jsonnet @@ -225,10 +227,10 @@ let rem = diff % self.step(); let div = diff / self.step(); - if rem != 0 { + if rem == 0 { + div + } else { div + 1 - } else { - div } } } @@ -248,11 +250,17 @@ pub fn new_eager() -> Self { Self::Eager(Cc::new(Vec::new())) } + + /// # Panics + /// If a > b pub fn new_range(a: i32, b: i32) -> Self { assert!(a <= b); Self::Range(a, b) } + /// # Panics + /// If passed numbers are incorrect + #[must_use] pub fn slice(self, from: Option, to: Option, step: Option) -> Self { let len = self.len(); let from = from.unwrap_or(0); @@ -289,7 +297,7 @@ match self { Self::Bytes(i) => i .get(index) - .map_or(Ok(None), |v| Ok(Some(Val::Num(*v as f64)))), + .map_or(Ok(None), |v| Ok(Some(Val::Num(f64::from(*v))))), Self::Lazy(vec) => { if let Some(v) = vec.get(index) { Ok(Some(v.evaluate(s)?)) @@ -333,7 +341,7 @@ match self { Self::Bytes(i) => i .get(index) - .map(|b| LazyVal::new_resolved(Val::Num(*b as f64))), + .map(|b| LazyVal::new_resolved(Val::Num(f64::from(*b)))), Self::Lazy(vec) => vec.get(index).cloned(), Self::Eager(vec) => vec.get(index).cloned().map(LazyVal::new_resolved), Self::Extended(v) => { @@ -374,7 +382,7 @@ Self::Bytes(i) => { let mut out = Vec::with_capacity(i.len()); for v in i.iter() { - out.push(Val::Num(*v as f64)); + out.push(Val::Num(f64::from(*v))); } Cc::new(out) } @@ -396,7 +404,7 @@ Self::Range(a, b) => { let mut out = Vec::with_capacity(self.len()); for i in *a..*b { - out.push(Val::Num(i as f64)); + out.push(Val::Num(f64::from(i))); } Cc::new(out) } @@ -414,7 +422,7 @@ .take(v.to() - v.from()) .step_by(v.step()) { - out.push(v.evaluate(s.clone())?) + out.push(v.evaluate(s.clone())?); } Cc::new(out) } @@ -423,28 +431,27 @@ pub fn iter(&self, s: State) -> impl DoubleEndedIterator> + '_ { (0..self.len()).map(move |idx| match self { - Self::Bytes(b) => Ok(Val::Num(b[idx] as f64)), + Self::Bytes(b) => Ok(Val::Num(f64::from(b[idx]))), Self::Lazy(l) => l[idx].evaluate(s.clone()), Self::Eager(e) => Ok(e[idx].clone()), - Self::Extended(_) => self.get(s.clone(), idx).map(|e| e.unwrap()), - Self::Range(..) => self.get(s.clone(), idx).map(|e| e.unwrap()), - Self::Reversed(..) => self.get(s.clone(), idx).map(|e| e.unwrap()), - Self::Slice(..) => self.get(s.clone(), idx).map(|e| e.unwrap()), + Self::Extended(..) | Self::Range(..) | Self::Reversed(..) | Self::Slice(..) => { + self.get(s.clone(), idx).map(|e| e.expect("idx < len")) + } }) } pub fn iter_lazy(&self) -> impl DoubleEndedIterator + '_ { (0..self.len()).map(move |idx| match self { - Self::Bytes(b) => LazyVal::new_resolved(Val::Num(b[idx] as f64)), + Self::Bytes(b) => LazyVal::new_resolved(Val::Num(f64::from(b[idx]))), Self::Lazy(l) => l[idx].clone(), Self::Eager(e) => LazyVal::new_resolved(e[idx].clone()), - Self::Extended(_) => self.get_lazy(idx).unwrap(), - Self::Range(..) => self.get_lazy(idx).unwrap(), - Self::Reversed(..) => self.get_lazy(idx).unwrap(), - Self::Slice(..) => self.get_lazy(idx).unwrap(), + Self::Slice(..) | Self::Extended(..) | Self::Range(..) | Self::Reversed(..) => { + self.get_lazy(idx).expect("idx < len") + } }) } + #[must_use] pub fn reversed(self) -> Self { Self::Reversed(Box::new(self)) } @@ -493,6 +500,7 @@ } } +#[allow(clippy::module_name_repetitions)] pub enum IndexableVal { Str(IStr), Arr(ArrValue), @@ -708,7 +716,7 @@ preserve_order, }, ) - .map(|s| s.into()) + .map(Into::into) } /// Calls `std.manifestJson` @@ -730,7 +738,7 @@ preserve_order, }, ) - .map(|s| s.into()) + .map(Into::into) } pub fn to_yaml( @@ -751,7 +759,7 @@ preserve_order, }, ) - .map(|s| s.into()) + .map(Into::into) } pub fn into_indexable(self) -> Result { Ok(match self { @@ -824,8 +832,8 @@ for field in fields { if !equals( s.clone(), - &a.get(s.clone(), field.clone())?.unwrap(), - &b.get(s.clone(), field)?.unwrap(), + &a.get(s.clone(), field.clone())?.expect("field exists"), + &b.get(s.clone(), field)?.expect("field exists"), )? { return Ok(false); } --- a/crates/jrsonnet-evaluator/tests/common.rs +++ b/crates/jrsonnet-evaluator/tests/common.rs @@ -12,6 +12,15 @@ } #[macro_export] +macro_rules! ensure { + ($v:expr $(,)?) => { + if !$v { + ::jrsonnet_evaluator::throw_runtime!("assertion failed: {}", stringify!($v)) + } + }; +} + +#[macro_export] macro_rules! ensure_val_eq { ($s:expr, $a:expr, $b:expr) => {{ if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? { --- /dev/null +++ b/crates/jrsonnet-evaluator/tests/sanity.rs @@ -0,0 +1,46 @@ +use std::path::PathBuf; + +use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val}; + +mod common; + +#[test] +fn assert_positive() -> Result<()> { + let s = State::default(); + s.with_stdlib(); + + let v = s.evaluate_snippet_raw(PathBuf::new().into(), "assert 1 == 1: 'fail'; null".into())?; + ensure_val_eq!(s.clone(), v, Val::Null); + let v = s.evaluate_snippet_raw(PathBuf::new().into(), "std.assertEqual(1, 1)".into())?; + ensure_val_eq!(s.clone(), v, Val::Bool(true)); + + Ok(()) +} + +#[test] +fn assert_negative() -> Result<()> { + let s = State::default(); + s.with_stdlib(); + + { + let e = match s + .evaluate_snippet_raw(PathBuf::new().into(), "assert 1 == 2: 'fail'; null".into()) + { + Ok(_) => throw_runtime!("assertion should fail"), + Err(e) => e, + }; + let e = s.stringify_err(&e); + ensure!(e.starts_with("assert failed: fail\n")); + } + { + let e = match s.evaluate_snippet_raw(PathBuf::new().into(), "std.assertEqual(1, 2)".into()) + { + Ok(_) => throw_runtime!("assertion should fail"), + Err(e) => e, + }; + let e = s.stringify_err(&e); + ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2")) + } + + Ok(()) +} --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -157,8 +157,8 @@ }); } - match &ty as &Type { - Type::Reference(r) if type_is_path(&r.elem, &name).is_some() => return Ok(Self::This), + match ty as &Type { + Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This), _ => {} } @@ -465,14 +465,13 @@ "strategy should be set when flattening Option", )); } - } else { - if attr.flatten_ok { - return Err(Error::new( - field.span(), - "flatten(ok) is only useable on optional fields", - )); - } + } else if attr.flatten_ok { + return Err(Error::new( + field.span(), + "flatten(ok) is only useable on optional fields", + )); } + Ok(Self { attr, ident,