--- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -53,8 +53,6 @@ serde.workspace = true anyhow = { workspace = true, optional = true } -# Serialized stdlib -bincode = { workspace = true, optional = true } # Explaining traces annotate-snippets = { workspace = true, optional = true } # Better explaining traces --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -7,7 +7,7 @@ use jrsonnet_gcmodule::Trace; use jrsonnet_interner::IStr; -use jrsonnet_parser::{BinaryOpType, ExprLocation, LocExpr, Source, SourcePath, UnaryOpType}; +use jrsonnet_parser::{BinaryOpType, LocExpr, Source, SourcePath, Span, UnaryOpType}; use jrsonnet_types::ValType; use thiserror::Error; @@ -275,7 +275,7 @@ pub struct StackTraceElement { /// Source of this frame /// Some frames only act as description, without attached source - pub location: Option, + pub location: Option, /// Frame description pub desc: String, } @@ -324,20 +324,20 @@ impl std::error::Error for Error {} pub trait ErrorSource { - fn to_location(self) -> Option; + fn to_location(self) -> Option; } impl ErrorSource for &LocExpr { - fn to_location(self) -> Option { - Some(self.1.clone()) + fn to_location(self) -> Option { + Some(self.span()) } } -impl ErrorSource for &ExprLocation { - fn to_location(self) -> Option { +impl ErrorSource for &Span { + fn to_location(self) -> Option { Some(self.clone()) } } impl ErrorSource for CallLocation<'_> { - fn to_location(self) -> Option { + fn to_location(self) -> Option { self.0.cloned() } } --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -26,7 +26,7 @@ pub fn evaluate_trivial(expr: &LocExpr) -> Option { fn is_trivial(expr: &LocExpr) -> bool { - match &*expr.0 { + match expr.expr() { Expr::Str(_) | Expr::Num(_) | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true, @@ -35,7 +35,7 @@ _ => false, } } - Some(match &*expr.0 { + Some(match expr.expr() { Expr::Str(s) => Val::string(s.clone()), Expr::Num(n) => { Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values")) @@ -72,7 +72,7 @@ Ok(match field_name { FieldName::Fixed(n) => Some(n.clone()), FieldName::Dyn(expr) => State::push( - CallLocation::new(&expr.1), + CallLocation::new(&expr.span()), || "evaluating field name".to_string(), || { let value = evaluate(ctx, expr)?; @@ -231,7 +231,7 @@ .field(name.clone()) .with_add(*plus) .with_visibility(*visibility) - .with_location(value.1.clone()) + .with_location(value.span()) .bindable(UnboundValue { uctx, value: value.clone(), @@ -266,7 +266,7 @@ builder .field(name.clone()) .with_visibility(*visibility) - .with_location(value.1.clone()) + .with_location(value.span()) .bindable(UnboundMethod { uctx, value: value.clone(), @@ -385,13 +385,13 @@ let value = &assertion.0; let msg = &assertion.1; let assertion_result = State::push( - CallLocation::new(&value.1), + CallLocation::new(&value.span()), || "assertion condition".to_owned(), || bool::from_untyped(evaluate(ctx.clone(), value)?), )?; if !assertion_result { State::push( - CallLocation::new(&value.1), + CallLocation::new(&value.span()), || "assertion failure".to_owned(), || { if let Some(msg) = msg { @@ -406,8 +406,7 @@ pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result { use Expr::*; - let LocExpr(raw_expr, _loc) = expr; - Ok(match &**raw_expr { + Ok(match expr.expr() { Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()), _ => evaluate(ctx, expr)?, }) @@ -420,8 +419,8 @@ if let Some(trivial) = evaluate_trivial(expr) { return Ok(trivial); } - let LocExpr(expr, loc) = expr; - Ok(match &**expr { + let loc = expr.span(); + Ok(match expr.expr() { Literal(LiteralType::This) => { Val::Obj(ctx.this().ok_or(CantUseSelfOutsideOfObject)?.clone()) } @@ -448,7 +447,7 @@ // because the standalone super literal is not supported, that is because in other // implementations `in super` treated differently from in `smth_else`. BinaryOp(field, BinaryOpType::In, e) - if matches!(&*e.0, Expr::Literal(LiteralType::Super)) => + if matches!(e.expr(), Expr::Literal(LiteralType::Super)) => { let Some(super_obj) = ctx.super_obj() else { return Ok(Val::Bool(false)); @@ -459,52 +458,50 @@ BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?, UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?, Var(name) => State::push( - CallLocation::new(loc), + CallLocation::new(&loc), || format!("variable <{name}> access"), || ctx.binding(name.clone())?.evaluate(), )?, Index { indexable, parts } => { let mut parts = parts.iter(); - let mut indexable = match &indexable { - // Cheaper to execute than creating object with overriden `this` - LocExpr(v, _) if matches!(&**v, Expr::Literal(LiteralType::Super)) => { - let part = parts.next().expect("at least part should exist"); - let Some(super_obj) = ctx.super_obj() else { - #[cfg(feature = "exp-null-coaelse")] - if part.null_coaelse { - return Ok(Val::Null); - } - bail!(NoSuperFound) - }; - let name = evaluate(ctx.clone(), &part.value)?; + let mut indexable = if matches!(indexable.expr(), Expr::Literal(LiteralType::Super)) { + let part = parts.next().expect("at least part should exist"); + let Some(super_obj) = ctx.super_obj() else { + #[cfg(feature = "exp-null-coaelse")] + if part.null_coaelse { + return Ok(Val::Null); + } + bail!(NoSuperFound) + }; + let name = evaluate(ctx.clone(), &part.value)?; - let Val::Str(name) = name else { - bail!(ValueIndexMustBeTypeGot( - ValType::Obj, - ValType::Str, - name.value_type(), - )) - }; + let Val::Str(name) = name else { + bail!(ValueIndexMustBeTypeGot( + ValType::Obj, + ValType::Str, + name.value_type(), + )) + }; - let this = ctx - .this() - .expect("no this found, while super present, should not happen"); - let name = name.into_flat(); - match super_obj - .get_for(name.clone(), this.clone()) - .with_description_src(&part.value, || format!("field <{name}> access"))? - { - Some(v) => v, - #[cfg(feature = "exp-null-coaelse")] - None if part.null_coaelse => return Ok(Val::Null), - None => { - let suggestions = suggest_object_fields(super_obj, name.clone()); + let this = ctx + .this() + .expect("no this found, while super present, should not happen"); + let name = name.into_flat(); + match super_obj + .get_for(name.clone(), this.clone()) + .with_description_src(&part.value, || format!("field <{name}> access"))? + { + Some(v) => v, + #[cfg(feature = "exp-null-coaelse")] + None if part.null_coaelse => return Ok(Val::Null), + None => { + let suggestions = suggest_object_fields(super_obj, name.clone()); - bail!(NoSuchField(name, suggestions)) - } + bail!(NoSuchField(name, suggestions)) } } - e => evaluate(ctx.clone(), e)?, + } else { + evaluate(ctx.clone(), indexable)? }; for part in parts { @@ -639,7 +636,7 @@ &Val::Obj(evaluate_object(ctx, b)?), )?, Apply(value, args, tailstrict) => { - evaluate_apply(ctx, value, args, CallLocation::new(loc), *tailstrict)? + evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)? } Function(params, body) => { evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone()) @@ -649,7 +646,7 @@ evaluate(ctx, returned)? } ErrorStmt(e) => State::push( - CallLocation::new(loc), + CallLocation::new(&loc), || "error statement".to_owned(), || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)), )?, @@ -659,7 +656,7 @@ cond_else, } => { if State::push( - CallLocation::new(loc), + CallLocation::new(&loc), || "if condition".to_owned(), || bool::from_untyped(evaluate(ctx.clone(), &cond.0)?), )? { @@ -690,7 +687,7 @@ } let indexable = evaluate(ctx.clone(), value)?; - let loc = CallLocation::new(loc); + let loc = CallLocation::new(&loc); let start = parse_idx(loc, &ctx, desc.start.as_ref(), "start")?; let end = parse_idx(loc, &ctx, desc.end.as_ref(), "end")?; @@ -699,7 +696,7 @@ IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)? } i @ (Import(path) | ImportStr(path) | ImportBin(path)) => { - let Expr::Str(path) = &*path.0 else { + let Expr::Str(path) = &path.expr() else { bail!("computed imports are not supported") }; let tmp = loc.clone().0; @@ -707,7 +704,7 @@ let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?; match i { Import(_) => State::push( - CallLocation::new(loc), + CallLocation::new(&loc), || format!("import {:?}", path.clone()), || s.import_resolved(resolved_path), )?, --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -4,7 +4,7 @@ use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; pub use jrsonnet_macros::builtin; -use jrsonnet_parser::{Destruct, Expr, ExprLocation, LocExpr, ParamsDesc}; +use jrsonnet_parser::{Destruct, Expr, LocExpr, ParamsDesc, Span}; use self::{ arglike::OptionalContext, @@ -22,10 +22,10 @@ /// Function callsite location. /// Either from other jsonnet code, specified by expression location, or from native (without location). #[derive(Clone, Copy)] -pub struct CallLocation<'l>(pub Option<&'l ExprLocation>); +pub struct CallLocation<'l>(pub Option<&'l Span>); impl<'l> CallLocation<'l> { /// Construct new location for calls coming from specified jsonnet expression location. - pub const fn new(loc: &'l ExprLocation) -> Self { + pub const fn new(loc: &'l Span) -> Self { Self(Some(loc)) } } @@ -225,7 +225,7 @@ #[cfg(feature = "exp-destruct")] _ => return false, }; - &desc.body.0 as &Expr == &Expr::Var(id.clone()) + desc.body.expr() == &Expr::Var(id.clone()) } _ => false, } --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -45,7 +45,7 @@ #[doc(hidden)] pub use jrsonnet_macros; pub use jrsonnet_parser as parser; -use jrsonnet_parser::{ExprLocation, LocExpr, ParserSettings, Source, SourcePath}; +use jrsonnet_parser::{LocExpr, ParserSettings, Source, SourcePath, Span}; pub use obj::*; use stack::check_depth; pub use tla::apply_tla; @@ -369,7 +369,7 @@ /// Executes code creating a new stack frame pub fn push_val( &self, - e: &ExprLocation, + e: &Span, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -8,7 +8,7 @@ use jrsonnet_gcmodule::{Cc, Trace, Weak}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ExprLocation, Visibility}; +use jrsonnet_parser::{Span, Visibility}; use rustc_hash::FxHashMap; use crate::{ @@ -135,7 +135,7 @@ flags: ObjFieldFlags, original_index: FieldIndex, pub invoke: MaybeUnbound, - pub location: Option, + pub location: Option, } pub trait ObjectAssertion: Trace { @@ -896,7 +896,7 @@ add: bool, visibility: Visibility, original_index: FieldIndex, - location: Option, + location: Option, } #[allow(clippy::missing_const_for_fn)] @@ -926,7 +926,7 @@ pub fn hide(self) -> Self { self.with_visibility(Visibility::Hidden) } - pub fn with_location(mut self, location: ExprLocation) -> Self { + pub fn with_location(mut self, location: Span) -> Self { self.location = Some(location); self } --- a/crates/jrsonnet-evaluator/src/trace/mod.rs +++ b/crates/jrsonnet-evaluator/src/trace/mod.rs @@ -5,7 +5,7 @@ }; use jrsonnet_gcmodule::Trace; -use jrsonnet_parser::{CodeLocation, ExprLocation, Source}; +use jrsonnet_parser::{CodeLocation, Source, Span}; use crate::{error::ErrorKind, Error}; @@ -380,7 +380,7 @@ error: &Error, ) -> Result<(), std::fmt::Error> { struct ResetData { - loc: ExprLocation, + loc: Span, } use hi_doc::{source_to_ansi, Formatting, SnippetBuilder, Text}; @@ -399,7 +399,7 @@ } let trace = &error.trace(); let snippet_builder: RefCell> = RefCell::new(None); - let mut last_location: Option = None; + let mut last_location: Option = None; let mut flush_builder = |data: Option| { use std::fmt::Write; let mut out = String::new(); --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -376,7 +376,7 @@ State, Val, function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call}, Result, Context, typed::Typed, - parser::ExprLocation, + parser::Span, }; const PARAMS: &'static [BuiltinParam] = &[ #(#params_desc)* --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -385,17 +385,16 @@ #[derive(Clone, PartialEq, Eq, Trace)] #[trace(skip)] #[repr(C)] -pub struct ExprLocation(pub Source, pub u32, pub u32); -impl ExprLocation { - pub fn belongs_to(&self, other: &ExprLocation) -> bool { +pub struct Span(pub Source, pub u32, pub u32); +impl Span { + pub fn belongs_to(&self, other: &Span) -> bool { other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2 } } -#[cfg(target_pointer_width = "64")] -static_assertions::assert_eq_size!(ExprLocation, [u8; 16]); +static_assertions::assert_eq_size!(Span, (usize, usize)); -impl Debug for ExprLocation { +impl Debug for Span { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}:{:?}-{:?}", self.0, self.1, self.2) } @@ -403,19 +402,32 @@ /// Holds AST expression and its location in source file #[derive(Clone, PartialEq, Trace)] -pub struct LocExpr(pub Rc, pub ExprLocation); +pub struct LocExpr(Rc<(Expr, Span)>); +impl LocExpr { + pub fn new(expr: Expr, span: Span) -> Self { + Self(Rc::new((expr, span))) + } + #[inline] + pub fn span(&self) -> Span { + self.0 .1.clone() + } + #[inline] + pub fn expr(&self) -> &Expr { + &self.0 .0 + } +} -#[cfg(target_pointer_width = "64")] -static_assertions::assert_eq_size!(LocExpr, [u8; 24]); +static_assertions::assert_eq_size!(LocExpr, usize); impl Debug for LocExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let expr = self.expr(); if f.alternate() { - write!(f, "{:#?}", self.0)?; + write!(f, "{:#?}", expr)?; } else { - write!(f, "{:?}", self.0)?; + write!(f, "{:?}", expr)?; } - write!(f, " from {:?}", self.1)?; + write!(f, " from {:?}", self.span())?; Ok(()) } } --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -232,7 +232,7 @@ pub rule var_expr(s: &ParserSettings) -> Expr = n:id() { expr::Expr::Var(n) } pub rule id_loc(s: &ParserSettings) -> LocExpr - = a:position!() n:id() b:position!() { LocExpr(Rc::new(expr::Expr::Str(n)), ExprLocation(s.source.clone(), a as u32,b as u32)) } + = a:position!() n:id() b:position!() { LocExpr::new(expr::Expr::Str(n), Span(s.source.clone(), a as u32,b as u32)) } pub rule if_then_else_expr(s: &ParserSettings) -> Expr = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{ cond, @@ -299,7 +299,7 @@ use UnaryOpType::*; rule expr(s: &ParserSettings) -> LocExpr = precedence! { - start:position!() v:@ end:position!() { LocExpr(Rc::new(v), ExprLocation(s.source.clone(), start as u32, end as u32)) } + start:position!() v:@ end:position!() { LocExpr::new(v, Span(s.source.clone(), start as u32, end as u32)) } -- a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)} a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ { @@ -370,10 +370,7 @@ /// Used for importstr values pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> LocExpr { let len = str.len(); - LocExpr( - Rc::new(Expr::Str(str)), - ExprLocation(settings.source.clone(), 0, len as u32), - ) + LocExpr::new(Expr::Str(str), Span(settings.source.clone(), 0, len as u32)) } #[cfg(test)] @@ -398,9 +395,9 @@ macro_rules! el { ($expr:expr, $from:expr, $to:expr$(,)?) => { - LocExpr( - std::rc::Rc::new($expr), - ExprLocation( + LocExpr::new( + $expr, + Span( Source::new_virtual("".into(), IStr::empty()), $from, $to,