difftreelog
fix exp-destruct
in: master
7 files changed
crates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -425,7 +425,7 @@
/// h = 1 => referenced += [], closures += 0, destructs += 1
/// And the result is
///
- /// ```
+ /// ```rust,ignore
/// Closures {
/// referenced: vec![d, e, f, a, b, c, h]
/// spec_shapes: vec![(3, 3), (4, 3), (0, 1)],
crates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
@@ -195,7 +195,7 @@
) -> Result<()> {
if idx >= specs.len() {
collector.reserve(guaranteed_reserve);
- return collector.collect(ctx.clone());
+ return collector.collect(ctx);
}
match &specs[idx] {
LCompSpec::If(cond) => {
@@ -239,18 +239,20 @@
)?;
}
}
+ // TODO: Should not be eager? CoW won't work here
#[cfg(feature = "exp-destruct")]
_ => {
for (i, item) in arr.iter().enumerate() {
let item_val = item?;
let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);
+ let fctx = Pending::new();
destructure::destruct(
destruct,
Thunk::evaluated(item_val),
- None,
+ fctx.clone(),
&mut inner_builder,
);
- let inner_ctx = inner_builder.build();
+ let inner_ctx = inner_builder.build().into_future(fctx);
evaluate_compspecs_eager(
inner_ctx,
specs,
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6 Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,7 analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},8 bail,9 evaluate::evaluate,10};1112#[allow(dead_code, reason = "not dead in exp-destruct")]13fn destruct_array(14 start: &[LDestruct],15 rest: Option<LDestructRest>,16 end: &[LDestruct],1718 value: Thunk<Val>,19 fctx: Pending<Context>,20 builder: &mut ContextBuilder,21) {22 let min_len = start.len() + end.len();23 let has_rest = rest.is_some();24 let full = Thunk!(move || {25 let v = value.evaluate()?;26 let Val::Arr(arr) = v else {27 bail!("expected array");28 };29 if !has_rest {30 if arr.len() as usize != min_len {31 bail!("expected {} elements, got {}", min_len, arr.len())32 }33 } else if (arr.len() as usize) < min_len {34 bail!(35 "expected at least {} elements, but array was only {}",36 min_len,37 arr.len()38 )39 }40 Ok(arr)41 });4243 for (i, d) in start.iter().enumerate() {44 let full = full.clone();45 destruct(46 d,47 Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),48 fctx.clone(),49 builder,50 );51 }5253 let start_len = start.len() as u32;54 let end_len = end.len() as u32;5556 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {57 let full = full.clone();58 builder.bind(59 id,60 Thunk!(move || {61 let full = full.evaluate()?;62 let to = full.len() - end_len;63 Ok(Val::Arr(full.slice(64 Some(start_len as i32),65 Some(to as i32),66 None,67 )))68 }),69 );70 }7172 for (i, d) in end.iter().enumerate() {73 let full = full.clone();74 destruct(75 d,76 Thunk!(move || {77 let full = full.evaluate()?;78 Ok(full79 .get(full.len() - end_len + i as u32)?80 .expect("length is checked"))81 }),82 fctx.clone(),83 builder,84 );85 }86}8788#[allow(dead_code, reason = "not dead in exp-destruct")]89fn destruct_object(90 fields: &[LDestructField],91 rest: Option<LDestructRest>,9293 value: Thunk<Val>,94 fctx: Pending<Context>,95 builder: &mut ContextBuilder,96) {97 use jrsonnet_interner::IStr;98 use rustc_hash::FxHashSet;99100 use crate::{ObjValueBuilder, bail};101102 let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();103 let field_names: Vec<(IStr, bool)> = fields104 .iter()105 .map(|f| (f.name.clone(), f.default.is_some()))106 .collect();107 let has_rest = rest.is_some();108 let full = Thunk!(move || {109 let v = value.evaluate()?;110 let Val::Obj(obj) = v else {111 bail!("expected object");112 };113 for (field, has_default) in &field_names {114 if !has_default && !obj.has_field_ex(field.clone(), true) {115 bail!("missing field: {field}");116 }117 }118 if !has_rest {119 let len = obj.len();120 if len as usize > field_names.len() {121 bail!("too many fields, and rest not found");122 }123 }124 Ok(obj)125 });126127 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {128 let full = full.clone();129 builder.bind(130 id,131 Thunk!(move || {132 let full = full.evaluate()?;133 let mut out = ObjValueBuilder::new();134 out.extend_with_core(full.as_standalone());135 out.with_fields_omitted(captured_fields);136 Ok(Val::Obj(out.build()))137 }),138 );139 }140141 for field in fields {142 let field_name = field.name.clone();143 let default: Option<(Pending<Context>, Rc<LExpr>)> =144 field.default.as_ref().map(|e| (fctx.clone(), e.clone()));145 let field_full = full.clone();146 let value_thunk = Thunk!(move || {147 let obj = field_full.evaluate()?;148 obj.get(field_name)?.map_or_else(149 || {150 let (fctx, expr) = default.as_ref().expect("shape is checked");151 evaluate(fctx.unwrap(), expr)152 },153 Ok,154 )155 });156157 if let Some(into) = &field.into {158 destruct(into, value_thunk, fctx.clone(), builder);159 } else {160 unreachable!("analyzer lowers object-destruct shorthands into `into`");161 }162 }163}164165/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one166/// binding per [`LocalId`] the pattern introduces.167///168/// `fctx` is needed for object-destruct defaults (feature `exp-destruct`).169#[allow(unused_variables)]170pub fn destruct(171 d: &LDestruct,172 value: Thunk<Val>,173 fctx: Pending<Context>,174 builder: &mut ContextBuilder,175) {176 match d {177 LDestruct::Full(id) => builder.bind(*id, value),178 #[cfg(feature = "exp-destruct")]179 LDestruct::Skip => {}180 #[cfg(feature = "exp-destruct")]181 LDestruct::Array { start, rest, end } => destruct_array(start, rest, end, value, fctx, builder),182 #[cfg(feature = "exp-destruct")]183 LDestruct::Object { fields, rest } => destruct_object(fields, rest, value, fctx, builder),184 }185}186187/// Bind one [`LBind`] as a lazy thunk that evaluates in the given188/// future context. Mirrors the old `evaluate_dest` — one entry per189/// binding in a `local … ;` frame.190pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {191 let value = bind.value.clone();192 let fctx_clone = fctx.clone();193 let thunk = Thunk!(move || {194 let ctx = fctx_clone.unwrap();195 evaluate(ctx, &value)196 });197 destruct(&bind.destruct, thunk, fctx, builder);198}199200/// Bind each LBind's value as a lazy thunk. Mutually recursive locals201/// resolve lazily through the shared Pending<Context>.202pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {203 if binds.is_empty() {204 return parent;205 }206 let fctx = Context::new_future();207 let mut builder =208 ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());209 for bind in binds {210 evaluate_dest(bind, fctx.clone(), &mut builder);211 }212 builder.build().into_future(fctx)213}214215pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}216impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}217218pub fn evaluate_locals_unbound(219 fctx: Context,220 locals: Rc<Vec<LBind>>,221 this_id: Option<LocalId>,222) -> impl CloneableUnbound<Context> {223 #[derive(Trace, Clone)]224 struct UnboundLocals {225 fctx: Context,226 locals: Rc<Vec<LBind>>,227 this_id: Option<LocalId>,228 }229 impl Unbound for UnboundLocals {230 type Bound = Context;231232 fn bind(&self, sup_this: SupThis) -> Result<Context> {233 let parent = self.fctx.clone();234235 let fctx = Context::new_future();236 let mut builder = ContextBuilder::extend(237 parent,238 self.locals.iter().map(|b| b.destruct.ids().len()).sum(),239 );240 for b in self.locals.iter() {241 evaluate_dest(b, fctx.clone(), &mut builder);242 }243 if let Some(this_id) = self.this_id {244 builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));245 }246 let ctx = builder.build_sup_this(sup_this).into_future(fctx);247 Ok(ctx)248 }249 }250251 UnboundLocals {252 fctx,253 locals,254 this_id,255 }256}1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6 Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,7 analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},8 bail,9 evaluate::evaluate,10};1112#[allow(dead_code, reason = "not dead in exp-destruct")]13fn destruct_array(14 start: &[LDestruct],15 rest: Option<&LDestructRest>,16 end: &[LDestruct],1718 value: Thunk<Val>,19 fctx: Pending<Context>,20 builder: &mut ContextBuilder,21) {22 let min_len = start.len() + end.len();23 let has_rest = rest.is_some();24 let full = Thunk!(move || {25 let v = value.evaluate()?;26 let Val::Arr(arr) = v else {27 bail!("expected array");28 };29 if !has_rest {30 if arr.len() as usize != min_len {31 bail!("expected {} elements, got {}", min_len, arr.len())32 }33 } else if (arr.len() as usize) < min_len {34 bail!(35 "expected at least {} elements, but array was only {}",36 min_len,37 arr.len()38 )39 }40 Ok(arr)41 });4243 for (i, d) in start.iter().enumerate() {44 let full = full.clone();45 destruct(46 d,47 Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),48 fctx.clone(),49 builder,50 );51 }5253 let start_len = start.len() as u32;54 let end_len = end.len() as u32;5556 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {57 let full = full.clone();58 builder.bind(59 *id,60 Thunk!(move || {61 let full = full.evaluate()?;62 let to = full.len() - end_len;63 Ok(Val::Arr(full.slice(64 Some(start_len as i32),65 Some(to as i32),66 None,67 )))68 }),69 );70 }7172 for (i, d) in end.iter().enumerate() {73 let full = full.clone();74 destruct(75 d,76 Thunk!(move || {77 let full = full.evaluate()?;78 Ok(full79 .get(full.len() - end_len + i as u32)?80 .expect("length is checked"))81 }),82 fctx.clone(),83 builder,84 );85 }86}8788#[allow(dead_code, reason = "not dead in exp-destruct")]89fn destruct_object(90 fields: &[LDestructField],91 rest: Option<&LDestructRest>,9293 value: Thunk<Val>,94 fctx: Pending<Context>,95 builder: &mut ContextBuilder,96) {97 use jrsonnet_interner::IStr;98 use rustc_hash::FxHashSet;99100 use crate::{ObjValueBuilder, bail};101102 let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();103 let field_names: Vec<(IStr, bool)> = fields104 .iter()105 .map(|f| (f.name.clone(), f.default.is_some()))106 .collect();107 let has_rest = rest.is_some();108 let full = Thunk!(move || {109 let v = value.evaluate()?;110 let Val::Obj(obj) = v else {111 bail!("expected object");112 };113 for (field, has_default) in &field_names {114 if !has_default && !obj.has_field_ex(field.clone(), true) {115 bail!("missing field: {field}");116 }117 }118 if !has_rest {119 let len = obj.len();120 if len as usize > field_names.len() {121 bail!("too many fields, and rest not found");122 }123 }124 Ok(obj)125 });126127 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {128 let full = full.clone();129 builder.bind(130 *id,131 Thunk!(move || {132 let full = full.evaluate()?;133 let mut out = ObjValueBuilder::new();134 out.extend_with_core(full.as_standalone());135 out.with_fields_omitted(captured_fields);136 Ok(Val::Obj(out.build()))137 }),138 );139 }140141 for field in fields {142 let field_name = field.name.clone();143 let default: Option<(Pending<Context>, Rc<LExpr>)> =144 field.default.as_ref().map(|e| (fctx.clone(), e.clone()));145 let field_full = full.clone();146 let value_thunk = Thunk!(move || {147 let obj = field_full.evaluate()?;148 obj.get(field_name)?.map_or_else(149 || {150 let (fctx, expr) = default.as_ref().expect("shape is checked");151 evaluate(fctx.unwrap(), expr)152 },153 Ok,154 )155 });156157 if let Some(into) = &field.into {158 destruct(into, value_thunk, fctx.clone(), builder);159 } else {160 unreachable!("analyzer lowers object-destruct shorthands into `into`");161 }162 }163}164165/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one166/// binding per [`LocalId`] the pattern introduces.167///168/// `fctx` is needed for object-destruct defaults (feature `exp-destruct`).169#[allow(unused_variables)]170pub fn destruct(171 d: &LDestruct,172 value: Thunk<Val>,173 fctx: Pending<Context>,174 builder: &mut ContextBuilder,175) {176 match d {177 LDestruct::Full(id) => builder.bind(*id, value),178 #[cfg(feature = "exp-destruct")]179 LDestruct::Skip => {}180 #[cfg(feature = "exp-destruct")]181 LDestruct::Array { start, rest, end } => {182 destruct_array(start, rest.as_ref(), end, value, fctx, builder)183 }184 #[cfg(feature = "exp-destruct")]185 LDestruct::Object { fields, rest } => {186 destruct_object(fields, rest.as_ref(), value, fctx, builder)187 }188 }189}190191/// Bind one [`LBind`] as a lazy thunk that evaluates in the given192/// future context. Mirrors the old `evaluate_dest` — one entry per193/// binding in a `local … ;` frame.194pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {195 let value = bind.value.clone();196 let fctx_clone = fctx.clone();197 let thunk = Thunk!(move || {198 let ctx = fctx_clone.unwrap();199 evaluate(ctx, &value)200 });201 destruct(&bind.destruct, thunk, fctx, builder);202}203204/// Bind each LBind's value as a lazy thunk. Mutually recursive locals205/// resolve lazily through the shared Pending<Context>.206pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {207 if binds.is_empty() {208 return parent;209 }210 let fctx = Context::new_future();211 let mut builder =212 ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());213 for bind in binds {214 evaluate_dest(bind, fctx.clone(), &mut builder);215 }216 builder.build().into_future(fctx)217}218219pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}220impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}221222pub fn evaluate_locals_unbound(223 fctx: Context,224 locals: Rc<Vec<LBind>>,225 this_id: Option<LocalId>,226) -> impl CloneableUnbound<Context> {227 #[derive(Trace, Clone)]228 struct UnboundLocals {229 fctx: Context,230 locals: Rc<Vec<LBind>>,231 this_id: Option<LocalId>,232 }233 impl Unbound for UnboundLocals {234 type Bound = Context;235236 fn bind(&self, sup_this: SupThis) -> Result<Context> {237 let parent = self.fctx.clone();238239 let fctx = Context::new_future();240 let mut builder = ContextBuilder::extend(241 parent,242 self.locals.iter().map(|b| b.destruct.ids().len()).sum(),243 );244 for b in self.locals.iter() {245 evaluate_dest(b, fctx.clone(), &mut builder);246 }247 if let Some(this_id) = self.this_id {248 builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));249 }250 let ctx = builder.build_sup_this(sup_this).into_future(fctx);251 Ok(ctx)252 }253 }254255 UnboundLocals {256 fctx,257 locals,258 this_id,259 }260}crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/redeclared_local.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/redeclared_local.jsonnet
---
--- source ---
local x = 1, x = 2; x
@@ -10,7 +10,7 @@
local_dependent_depth: 0
errored: true
--- diagnostics ---
- · ╭── variable redeclared: x
+ · ╭── local is already defined in the current frame: x
1 │ local x = 1, x = 2; x
2 │
--- lir ---
crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -381,7 +381,7 @@
None
};
let default = if p.try_eat(T![=]) {
- Some(Rc::new(spanned(p, expr)?))
+ Some(spanned(p, expr)?)
} else {
None
};
@@ -466,8 +466,10 @@
if !p.at(SyntaxKind::IDENT) {
let d = destruct(p)?;
p.eat(T![=])?;
- let value = Rc::new(expr(p)?);
- return Ok(BindSpec::Field { into: d, value });
+ return Ok(BindSpec::Field {
+ into: d,
+ value: expr(p)?,
+ });
}
}
let name_spanned = spanned(p, ident)?;
crates/jrsonnet-ir/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir/src/expr.rs
+++ b/crates/jrsonnet-ir/src/expr.rs
@@ -211,7 +211,7 @@
}
}
-#[derive(Debug, Clone, PartialEq, Eq, Acyclic)]
+#[derive(Debug, PartialEq, Eq, Acyclic)]
pub enum DestructRest {
/// ...rest
Keep(IStr),
@@ -219,7 +219,7 @@
Drop,
}
-#[derive(Debug, Clone, PartialEq, Acyclic)]
+#[derive(Debug, PartialEq, Acyclic)]
pub enum Destruct {
Full(Spanned<IStr>),
#[cfg(feature = "exp-destruct")]
@@ -233,7 +233,7 @@
#[cfg(feature = "exp-destruct")]
Object {
#[allow(clippy::type_complexity)]
- fields: Vec<(IStr, Option<Destruct>, Option<Rc<Spanned<Expr>>>)>,
+ fields: Vec<(IStr, Option<Destruct>, Option<Spanned<Expr>>)>,
rest: Option<DestructRest>,
},
}
crates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-peg-parser/src/lib.rs
+++ b/crates/jrsonnet-peg-parser/src/lib.rs
@@ -1,5 +1,3 @@
-use std::rc::Rc;
-
use jrsonnet_gcmodule::Acyclic;
use jrsonnet_ir::{
ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BindSpec, CompSpec, Destruct, DestructRest, Expr,
@@ -110,7 +108,7 @@
}
pub rule destruct_object(s: &ParserSettings) -> Destruct
= "{" _
- fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:spanned(<expr(s)>, s) {v})? {(name, into, default.map(Rc::new))})**comma()
+ fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:spanned(<expr(s)>, s) {v})? {(name, into, default)})**comma()
rest:(
comma() rest:destruct_rest()? {rest}
/ comma()? {None}