difftreelog
feat(analyze) explicit captures/locals
in: master
Inspired by GHC closures
30 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -852,18 +852,18 @@
[[package]]
name = "jrsonnet-gcmodule"
-version = "0.4.4"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21dd97b40cbfb2043094219f95d96519858ba1aee4e8260eb048a1774832a517"
+checksum = "95f9ce64915cdb0cab5367940a7cc024394fcf4f2608531e49f6dad39e2082d7"
dependencies = [
"jrsonnet-gcmodule-derive",
]
[[package]]
name = "jrsonnet-gcmodule-derive"
-version = "0.4.4"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ede3d0445c2a7d7adab0a3cc33bdb33df78ffebebc21a2848c221526cb1795d4"
+checksum = "64364cfb68be0968a940d69ccb651ec445cde47830da5b294d55d2e47eee8708"
dependencies = [
"proc-macro2",
"quote",
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@
[workspace.package]
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
-edition = "2021"
+edition = "2024"
license = "MIT"
repository = "https://github.com/CertainLach/jrsonnet"
version = "0.5.0-pre98"
@@ -22,7 +22,7 @@
jrsonnet-cli = { path = "./crates/jrsonnet-cli", version = "0.5.0-pre98" }
jrsonnet-types = { path = "./crates/jrsonnet-types", version = "0.5.0-pre98" }
jrsonnet-formatter = { path = "./crates/jrsonnet-formatter", version = "0.5.0-pre98" }
-jrsonnet-gcmodule = { version = "0.4.4" }
+jrsonnet-gcmodule = { version = "0.4.5" }
# Diagnostics.
# hi-doc is my library, which handles text formatting very well, but isn't polished enough yet
# Previous implementation was based on annotate-snippets, which I don't like for many reasons.
crates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -14,10 +14,9 @@
//! }
//! ```
-use std::{fmt::Write, rc::Rc};
+use std::rc::Rc;
use drop_bomb::DropBomb;
-use hi_doc::{Formatting, SnippetBuilder, Text};
use jrsonnet_gcmodule::Acyclic;
use jrsonnet_interner::IStr;
use jrsonnet_ir::{
@@ -78,6 +77,20 @@
}
}
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Acyclic)]
+pub enum LSlot {
+ /// Enclosing frame locals (sibling letrec, params, etc.).
+ Local(LocalSlot),
+ /// Enclosing closure's capture pack.
+ Capture(CaptureSlot),
+}
+
+#[derive(Debug, Acyclic)]
+pub struct ClosureShape {
+ pub captures: Box<[LSlot]>,
+ pub n_locals: u16,
+}
+
struct LocalDefinition {
name: IStr,
span: Option<Span>,
@@ -121,12 +134,15 @@
#[derive(Debug, Acyclic)]
pub enum LExpr {
- Local(LocalId),
+ Slot(LSlot),
Null,
Bool(bool),
Str(IStr),
Num(NumValue),
- Arr(Rc<Vec<LExpr>>),
+ Arr {
+ shape: ClosureShape,
+ items: Rc<Vec<LExpr>>,
+ },
ArrComp(Box<LArrComp>),
Obj(LObjBody),
ObjExtend(Box<LExpr>, LObjBody),
@@ -141,10 +157,7 @@
rest: Box<LExpr>,
},
Error(Span, Box<LExpr>),
- LocalExpr {
- binds: Vec<LBind>,
- body: Box<LExpr>,
- },
+ LocalExpr(Box<LLocalExpr>),
Import {
kind: Spanned<ImportKind>,
kind_span: Span,
@@ -160,6 +173,7 @@
parts: Vec<LIndexPart>,
},
Function(Rc<LFunction>),
+ IdentityFunction,
IfElse {
cond: Box<LExpr>,
cond_then: Box<LExpr>,
@@ -174,10 +188,19 @@
}
#[derive(Debug, Acyclic)]
+pub struct LLocalExpr {
+ pub frame_shape: ClosureShape,
+ pub binds: Vec<LBind>,
+ pub body: LExpr,
+}
+
+#[derive(Debug, Acyclic)]
pub struct LFunction {
pub name: Option<IStr>,
pub params: Vec<LParam>,
pub signature: FunctionSignature,
+
+ pub body_shape: ClosureShape,
pub body: Rc<LExpr>,
}
@@ -185,18 +208,25 @@
pub struct LParam {
pub name: Option<IStr>,
pub destruct: LDestruct,
- pub default: Option<Rc<LExpr>>,
+
+ pub default: Option<(ClosureShape, Rc<LExpr>)>,
}
#[derive(Debug, Acyclic)]
pub struct LBind {
pub destruct: LDestruct,
+ pub value_shape: ClosureShape,
pub value: Rc<LExpr>,
}
-#[derive(Debug, Clone, Acyclic)]
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Acyclic)]
+pub struct CaptureSlot(pub(crate) u16);
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Acyclic)]
+pub struct LocalSlot(pub(crate) u16);
+
+#[derive(Debug, Acyclic)]
pub enum LDestruct {
- Full(LocalId),
+ Full(LocalSlot),
#[cfg(feature = "exp-destruct")]
Skip,
#[cfg(feature = "exp-destruct")]
@@ -214,54 +244,54 @@
#[derive(Debug, Clone, Copy, Acyclic)]
pub enum LDestructRest {
- Keep(LocalId),
+ Keep(LocalSlot),
Drop,
}
-#[derive(Debug, Clone, Acyclic)]
+#[derive(Debug, Acyclic)]
pub struct LDestructField {
pub name: IStr,
pub into: Option<LDestruct>,
- pub default: Option<Rc<LExpr>>,
+ pub default: Option<(ClosureShape, Rc<LExpr>)>,
}
impl LDestruct {
- pub fn each_id<F: FnMut(LocalId)>(&self, f: &mut F) {
+ pub fn each_slot<F: FnMut(LocalSlot)>(&self, f: &mut F) {
match self {
- Self::Full(id) => f(*id),
+ Self::Full(s) => f(*s),
#[cfg(feature = "exp-destruct")]
Self::Skip => {}
#[cfg(feature = "exp-destruct")]
Self::Array { start, rest, end } => {
for d in start {
- d.each_id(f);
+ d.each_slot(f);
}
- if let Some(LDestructRest::Keep(id)) = rest {
- f(*id);
+ if let Some(LDestructRest::Keep(s)) = rest {
+ f(*s);
}
for d in end {
- d.each_id(f);
+ d.each_slot(f);
}
}
#[cfg(feature = "exp-destruct")]
Self::Object { fields, rest } => {
for field in fields {
if let Some(into) = &field.into {
- into.each_id(f);
+ into.each_slot(f);
} else {
unreachable!("shorthand object destruct must store `into`");
}
}
- if let Some(LDestructRest::Keep(id)) = rest {
- f(*id);
+ if let Some(LDestructRest::Keep(s)) = rest {
+ f(*s);
}
}
}
}
- pub fn ids(&self) -> SmallVec<[LocalId; 1]> {
+ pub fn slots(&self) -> SmallVec<[LocalSlot; 1]> {
let mut out = SmallVec::new();
- self.each_id(&mut |id| out.push(id));
+ self.each_slot(&mut |s| out.push(s));
out
}
}
@@ -303,21 +333,24 @@
#[derive(Debug, Acyclic)]
pub struct LObjMembers {
- /// If current object identity (`super`/`this`/`$`) is used, `this` should be saved to the specified local
- pub this: Option<LocalId>,
+ pub frame_shape: ClosureShape,
+ /// If current object identity (`super`/`this`/`$`) is used, `this` should
+ /// be saved to the specified local slot.
+ pub this: Option<LocalSlot>,
/// Set if dollar should also be assigned to object identity, `this` should also be set (TODO: proper type-level validation)
pub set_dollar: bool,
/// True iff `super` is referenced by this object's members.
pub uses_super: bool,
pub locals: Rc<Vec<LBind>>,
- pub asserts: Rc<Vec<LAssertStmt>>,
+ pub asserts: Option<Rc<LObjAsserts>>,
pub fields: Vec<LFieldMember>,
}
#[derive(Debug, Acyclic)]
pub struct LObjComp {
- pub this: Option<LocalId>,
+ pub frame_shape: Rc<ClosureShape>,
+ pub this: Option<LocalSlot>,
pub set_dollar: bool,
pub uses_super: bool,
@@ -331,7 +364,19 @@
pub name: LFieldName,
pub plus: bool,
pub visibility: Visibility,
- pub value: Rc<LExpr>,
+ pub value: Rc<(ClosureShape, LExpr)>,
+}
+
+#[derive(Debug, Acyclic)]
+pub struct LClosure<T: Acyclic> {
+ pub shape: ClosureShape,
+ pub value: T,
+}
+
+#[derive(Debug, Acyclic)]
+pub struct LObjAsserts {
+ pub shape: ClosureShape,
+ pub asserts: Vec<LAssertStmt>,
}
#[derive(Debug, Acyclic)]
@@ -350,6 +395,7 @@
#[derive(Debug, Acyclic)]
pub struct LArrComp {
+ pub value_shape: ClosureShape,
pub value: Rc<LExpr>,
pub compspecs: Vec<LCompSpec>,
}
@@ -358,6 +404,7 @@
pub enum LCompSpec {
If(LExpr),
For {
+ frame_shape: ClosureShape,
destruct: LDestruct,
over: LExpr,
/// Is `over` does not depend on any variable introduced by an earlier for-spec in this comprehension chain
@@ -365,23 +412,323 @@
},
}
-// TODO: Binding frame state machine:
-// Pending => AllocIds => Initialize => Body => Exit
+struct FrameAlloc<'s> {
+ first_in_frame: LocalId,
+ stack: &'s mut AnalysisStack,
+ bomb: DropBomb,
+}
+impl<'s> FrameAlloc<'s> {
+ fn new(stack: &'s mut AnalysisStack) -> Self {
+ FrameAlloc {
+ first_in_frame: stack.next_local_id(),
+ stack,
+ bomb: DropBomb::new("binding frame state"),
+ }
+ }
+
+ fn push_locals_closure(&mut self) -> ClosureOnStack {
+ self.stack.push_closure_a(self.first_in_frame)
+ }
+
+ fn define_local(&mut self, name: IStr, span: Option<Span>) -> Option<(LocalId, LocalSlot)> {
+ let id = self.stack.next_local_id();
+ let stack = self.stack.local_by_name.entry(name.clone()).or_default();
+ if let Some(&existing) = stack.last()
+ && !existing.defined_before(self.first_in_frame)
+ {
+ self.stack.report_error(
+ format!("local is already defined in the current frame: {name}"),
+ span,
+ );
+ return None;
+ }
+ stack.push(id);
+ self.stack.local_defs.push(LocalDefinition {
+ name,
+ span,
+ defined_at_depth: self.stack.depth,
+ used_at_depth: u32::MAX,
+ used_by_sibling: false,
+ analysis: AnalysisResult::default(),
+ analyzed: false,
+ scratch_referenced: false,
+ });
+ let def = self.stack.defining_closure_mut();
+ Some((id, def.define_local(id)))
+ }
+ fn alloc_bind(&mut self, bind: &BindSpec) -> Option<LDestruct> {
+ match bind {
+ BindSpec::Field { into, .. } => self.alloc_destruct(into),
+ BindSpec::Function { name, .. } => {
+ let (_, id) = self.define_local(name.clone(), None)?;
+ Some(LDestruct::Full(id))
+ }
+ }
+ }
+ fn alloc_destruct(&mut self, destruct: &Destruct) -> Option<LDestruct> {
+ Some(match destruct {
+ Destruct::Full(name) => {
+ let (_, id) = self.define_local(name.value.clone(), Some(name.span.clone()))?;
+ LDestruct::Full(id)
+ }
+ #[cfg(feature = "exp-destruct")]
+ Destruct::Skip => LDestruct::Skip,
+ #[cfg(feature = "exp-destruct")]
+ Destruct::Array { start, rest, end } => {
+ let start = start
+ .iter()
+ .map(|d| self.alloc_destruct(d))
+ .collect::<Option<Vec<_>>>()?;
+ let rest = match rest {
+ Some(jrsonnet_ir::DestructRest::Keep(name)) => {
+ let (_, id) = self.define_local(name.clone(), None)?;
+ Some(LDestructRest::Keep(id))
+ }
+ Some(jrsonnet_ir::DestructRest::Drop) => Some(LDestructRest::Drop),
+ None => None,
+ };
+ let end = end
+ .iter()
+ .map(|d| self.alloc_destruct(d))
+ .collect::<Option<Vec<_>>>()?;
+ LDestruct::Array { start, rest, end }
+ }
+ #[cfg(feature = "exp-destruct")]
+ Destruct::Object { fields, rest } => {
+ let mut l_fields: Vec<(IStr, LDestruct)> = Vec::with_capacity(fields.len());
+ // Allocate destruct LocalIds, then analyse defaults
+ for (name, into, _default) in fields {
+ let into = if let Some(inner) = into {
+ self.alloc_destruct(inner)?
+ } else {
+ let (_, id) = self.define_local(name.clone(), None)?;
+ LDestruct::Full(id)
+ };
+ l_fields.push((name.clone(), into));
+ }
+ // All locals exist, so defaults can reference any sibling.
+ let l_fields: Vec<LDestructField> = l_fields
+ .into_iter()
+ .zip(fields.iter())
+ .map(|((name, into), (_n, _i, default))| {
+ let default = match default {
+ Some(e) => {
+ let mut default_taint = AnalysisResult::default();
+ Some(self.stack.in_using_closure(|stack| {
+ Rc::new(analyze(&e.value, stack, &mut default_taint))
+ }))
+ }
+ None => None,
+ };
+ LDestructField {
+ name,
+ into: Some(into),
+ default,
+ }
+ })
+ .collect();
+ let rest = match rest {
+ Some(jrsonnet_ir::DestructRest::Keep(name)) => {
+ let (_, id) = self.define_local(name.clone(), None)?;
+ Some(LDestructRest::Keep(id))
+ }
+ Some(jrsonnet_ir::DestructRest::Drop) => Some(LDestructRest::Drop),
+ None => None,
+ };
+ LDestruct::Object {
+ fields: l_fields,
+ rest,
+ }
+ }
+ })
+ }
+
+ fn finish(self) -> PendingInit<'s> {
+ let Self {
+ first_in_frame,
+ stack,
+ bomb,
+ } = self;
+ let first_after_frame = stack.next_local_id();
+ PendingInit {
+ first_after_frame,
+ stack,
+ closures: Closures {
+ referenced: vec![],
+ spec_shapes: vec![],
+ first_in_frame,
+ },
+ bomb,
+ }
+ }
+}
/// Frame state: `LocalIds` allocated, values not yet analysed.
-struct PendingInit {
- first_in_frame: LocalId,
+struct PendingInit<'s> {
first_after_frame: LocalId,
+ stack: &'s mut AnalysisStack,
+ closures: Closures,
bomb: DropBomb,
}
+impl<'s> PendingInit<'s> {
+ /// Record the analysis of a spec's value: stamp every id bound by the
+ /// spec with `analysis`, collect the spec's same-frame references, and
+ /// append them to `closures`.
+ fn record_spec_init(&mut self, destruct: &LDestruct, analysis: AnalysisResult) {
+ let mut refs: SmallVec<[LocalId; 4]> = SmallVec::new();
+ for i in self.closures.first_in_frame.0..self.first_after_frame.0 {
+ let def = &mut self.stack.local_defs[i as usize];
+ if def.scratch_referenced {
+ refs.push(LocalId(i));
+ def.scratch_referenced = false;
+ }
+ }
+
+ let mut ids_count = 0;
+ let first_local = self.stack.top_defining_local();
+ destruct.each_slot(&mut |slot| {
+ ids_count += 1;
+ let id = LocalId(first_local.0 + u32::from(slot.0));
+ let def = &mut self.stack.local_defs[id.idx()];
+ debug_assert!(!def.analyzed, "sanity: local {:?} analysed twice", def.name);
+ def.analysis = analysis;
+ def.analyzed = true;
+ });
+ self.closures.push_spec(ids_count, &refs);
+ }
+ /// After all specs are analysed, propagate dependency information between
+ /// siblings to a fix-point, then switch to "body" mode.
+ fn finish(self) -> PendingBody<'s> {
+ let Self {
+ first_after_frame,
+ closures,
+ stack,
+ bomb,
+ } = self;
+
+ debug_assert_eq!(
+ first_after_frame,
+ stack.next_local_id(),
+ "frame initialisation left unfinished locals"
+ );
+
+ debug_assert_eq!(
+ closures.spec_shapes.iter().map(|(_, d)| *d).sum::<usize>(),
+ (first_after_frame.0 - closures.first_in_frame.0) as usize,
+ "closures destruct-id counts must match frame local count"
+ );
+
+ let mut changed = true;
+ while changed {
+ changed = false;
+ for spec in closures.iter_specs() {
+ for id_raw in spec.ids.clone() {
+ let user = LocalId(id_raw);
+ for &used in spec.references {
+ changed |= stack.propagate_analysis(user, used);
+ }
+ }
+ }
+ }
+
+ stack.depth += 1;
+ PendingBody {
+ first_after_frame,
+ closures,
+ stack,
+ bomb,
+ }
+ }
+}
+
/// Frame state: values analysed, body not yet walked.
-struct PendingBody {
- first_in_frame: LocalId,
+struct PendingBody<'s> {
first_after_frame: LocalId,
closures: Closures,
+ stack: &'s mut AnalysisStack,
bomb: DropBomb,
}
+impl<'s> PendingBody<'s> {
+ /// After the body is processed, drop the frame's locals and emit any
+ /// "unused local" warnings.
+ fn finish(self) {
+ let PendingBody {
+ first_after_frame,
+ closures,
+ stack,
+ mut bomb,
+ } = self;
+ bomb.defuse();
+ stack.depth -= 1;
+
+ debug_assert_eq!(
+ first_after_frame,
+ stack.next_local_id(),
+ "nested scopes must be popped before outer frames"
+ );
+
+ let mut changed = true;
+ while changed {
+ changed = false;
+ for spec in closures.iter_specs() {
+ // Effective used_at_depth for the spec = min over its ids.
+ let mut min_used_at = u32::MAX;
+ for id_raw in spec.ids.clone() {
+ min_used_at = min_used_at.min(stack.local_defs[id_raw as usize].used_at_depth);
+ }
+ if min_used_at == u32::MAX {
+ continue;
+ }
+ for &used in spec.references {
+ let used_def = &mut stack.local_defs[used.idx()];
+ if min_used_at < used_def.used_at_depth {
+ used_def.used_at_depth = min_used_at;
+ changed = true;
+ }
+ }
+ }
+ }
+
+ let drained: Vec<LocalDefinition> = stack
+ .local_defs
+ .drain(closures.first_in_frame.idx()..)
+ .collect();
+ for (i, def) in drained.iter().enumerate().rev() {
+ let id = LocalId(closures.first_in_frame.0 + i as u32);
+ let stack_locals = stack
+ .local_by_name
+ .get_mut(&def.name)
+ .expect("local must be in name map");
+ let popped = stack_locals.pop().expect("name stack should not be empty");
+ debug_assert_eq!(popped, id, "name stack integrity");
+ if stack_locals.is_empty() {
+ stack.local_by_name.remove(&def.name);
+ }
+
+ if def.used_at_depth == u32::MAX {
+ if def.used_by_sibling {
+ stack.report_warning(
+ format!("local is only referenced by unused siblings: {}", def.name),
+ def.span.clone(),
+ );
+ } else {
+ stack.report_warning(format!("unused local: {}", def.name), def.span.clone());
+ }
+ } else if def.analysis.local_dependent_depth > def.defined_at_depth
+ && def.analysis.object_dependent_depth > def.defined_at_depth
+ && def.defined_at_depth != 0
+ {
+ // The value doesn't depend on anything defined at or inside
+ // this local's scope - can be hoisted, unfortunately not automatically.
+ stack.report_warning(
+ format!("local could be hoisted to an outer scope: {}", def.name),
+ def.span.clone(),
+ );
+ }
+ }
+ }
+}
struct Closures {
/// All the referenced locals, maybe repeated multiple times
@@ -451,14 +798,6 @@
}
impl Closures {
- fn new(first_in_frame: LocalId) -> Self {
- Self {
- referenced: Vec::new(),
- spec_shapes: Vec::new(),
- first_in_frame,
- }
- }
-
fn push_spec(&mut self, destruct_ids_count: usize, refs: &[LocalId]) {
self.referenced.extend_from_slice(refs);
self.spec_shapes.push((refs.len(), destruct_ids_count));
@@ -493,6 +832,41 @@
pub span: Option<Span>,
}
+struct DefiningClosure {
+ first_local: LocalId,
+ n_locals: u16,
+}
+
+impl DefiningClosure {
+ fn resolve(&self, target: LocalId) -> Option<LocalSlot> {
+ let end = self.first_local.0 + u32::from(self.n_locals);
+ if target.0 >= self.first_local.0 && target.0 < end {
+ Some(LocalSlot(
+ u16::try_from(target.0 - self.first_local.0).expect("local slots overflow"),
+ ))
+ } else {
+ None
+ }
+ }
+ fn define_local(&mut self, local: LocalId) -> LocalSlot {
+ let slot = self.n_locals;
+ let id = self.first_local.0 + u32::from(slot);
+ debug_assert_eq!(local.0, id);
+ self.n_locals = self.n_locals.checked_add(1).expect("local slots overflow");
+ LocalSlot(slot)
+ }
+}
+
+/// Per-closure capture computation state.
+struct ClosureFrame {
+ /// Closure may allocate locals
+ defining: Option<DefiningClosure>,
+ /// `LocalId` => capture index
+ captures: FxHashMap<LocalId, CaptureSlot>,
+ /// Capture sources in insertion order; consumed by `pop_closure_frame`.
+ capture_sources: Vec<LSlot>,
+}
+
#[allow(clippy::struct_excessive_bools)]
pub struct AnalysisStack {
local_defs: Vec<LocalDefinition>,
@@ -519,11 +893,19 @@
/// True iff `$` has been referenced anywhere since the outermost object's scope was entered.
dollar_used: bool,
+ /// Stack of closure frames (innermost on top).
+ closure_stack: Vec<ClosureFrame>,
+
diagnostics: Vec<Diagnostic>,
/// Whenever analysis would be broken due to static analysis error.
errored: bool,
}
+#[must_use]
+struct ClosureOnStack {
+ bomb: DropBomb,
+}
+
impl AnalysisStack {
pub fn new() -> Self {
Self {
@@ -537,11 +919,120 @@
cur_self_used: false,
cur_super_used: false,
dollar_used: false,
+ closure_stack: Vec::new(),
diagnostics: Vec::new(),
errored: false,
}
}
+ fn push_root_closure(&mut self, externals: u16) -> ClosureOnStack {
+ assert!(
+ self.closure_stack.is_empty(),
+ "root is only possible with empty stack"
+ );
+
+ self.closure_stack.push(ClosureFrame {
+ defining: Some(DefiningClosure {
+ first_local: LocalId(0),
+ n_locals: externals,
+ }),
+ captures: FxHashMap::default(),
+ capture_sources: Vec::new(),
+ });
+
+ ClosureOnStack {
+ bomb: DropBomb::new("root closure"),
+ }
+ }
+
+ fn push_closure_a(&mut self, first_local: LocalId) -> ClosureOnStack {
+ self.closure_stack.push(ClosureFrame {
+ defining: Some(DefiningClosure {
+ first_local,
+ n_locals: 0,
+ }),
+ captures: FxHashMap::default(),
+ capture_sources: Vec::new(),
+ });
+ ClosureOnStack {
+ bomb: DropBomb::new("closure with locals"),
+ }
+ }
+
+ #[inline]
+ fn in_using_closure<T>(
+ &mut self,
+ inner: impl FnOnce(&mut AnalysisStack) -> T,
+ ) -> (ClosureShape, T) {
+ fn push_closure_b(stack: &mut AnalysisStack) -> ClosureOnStack {
+ stack.closure_stack.push(ClosureFrame {
+ defining: None,
+ captures: FxHashMap::default(),
+ capture_sources: Vec::new(),
+ });
+ ClosureOnStack {
+ bomb: DropBomb::new("closure with locals"),
+ }
+ }
+ let closure = push_closure_b(self);
+ let v = inner(self);
+ let shape = self.pop_closure(closure);
+ (shape, v)
+ }
+
+ fn pop_closure(&mut self, mut closure: ClosureOnStack) -> ClosureShape {
+ closure.bomb.defuse();
+ let frame = self.closure_stack.pop().expect("closure frame");
+ ClosureShape {
+ captures: frame.capture_sources.into_boxed_slice(),
+ n_locals: frame.defining.map(|d| d.n_locals).unwrap_or_default(),
+ }
+ }
+
+ /// Resolve a `LocalId` reference to an `LSlot` against the innermost
+ /// closure frame. May insert capture entries up the closure stack as
+ /// needed.
+ fn resolve_to_slot(&mut self, target: LocalId) -> LSlot {
+ let top = self.closure_stack.len();
+ debug_assert!(top > 0, "resolve_to_slot called with no closure frame");
+ Self::resolve_at(&mut self.closure_stack, top - 1, target)
+ }
+
+ fn resolve_at(stack: &mut [ClosureFrame], idx: usize, target: LocalId) -> LSlot {
+ if let Some(def) = &stack[idx].defining {
+ if let Some(resolved) = def.resolve(target) {
+ return LSlot::Local(resolved);
+ }
+ } else {
+ // A sibling letrec slot must never be packed as a capture, or
+ // it would read an empty `OnceCell`.
+ for j in (0..idx).rev() {
+ if let Some(def) = &stack[j].defining {
+ if let Some(resolved) = def.resolve(target) {
+ return LSlot::Local(resolved);
+ }
+ break;
+ }
+ }
+ }
+ if let Some(&cap_idx) = stack[idx].captures.get(&target) {
+ return LSlot::Capture(cap_idx);
+ }
+ debug_assert!(idx > 0, "no enclosing closure frame for target {target:?}");
+ let parent_slot = Self::resolve_at(stack, idx - 1, target);
+ let frame = &mut stack[idx];
+ let cap_idx = CaptureSlot(
+ frame
+ .capture_sources
+ .len()
+ .try_into()
+ .expect("frame has more than u16::MAX captures"),
+ );
+ frame.capture_sources.push(parent_slot);
+ frame.captures.insert(target, cap_idx);
+ LSlot::Capture(cap_idx)
+ }
+
fn next_local_id(&self) -> LocalId {
LocalId(self.local_defs.len() as u32)
}
@@ -562,12 +1053,7 @@
});
}
- fn use_local(
- &mut self,
- name: &IStr,
- span: Span,
- taint: &mut AnalysisResult,
- ) -> Option<LocalId> {
+ fn use_local(&mut self, name: &IStr, span: Span, taint: &mut AnalysisResult) -> Option<LSlot> {
let Some(ids) = self.local_by_name.get(name) else {
let names = suggest_names(name, self.local_by_name.keys());
self.report_error(
@@ -586,7 +1072,7 @@
} else {
def.scratch_referenced = true;
}
- Some(id)
+ Some(self.resolve_to_slot(id))
}
/// Assign name to the value provided externally, e.g `std`.
@@ -613,37 +1099,20 @@
self.local_by_name.entry(name).or_default().push(id);
}
- /// Define a new local inside a frame currently being built.
- fn define_local(
- &mut self,
- name: IStr,
- span: Option<Span>,
- frame_start: LocalId,
- ) -> Option<LocalId> {
- let id = self.next_local_id();
- let stack = self.local_by_name.entry(name.clone()).or_default();
- if let Some(&existing) = stack.last() {
- if !existing.defined_before(frame_start) {
- self.report_error(
- format!("local is already defined in the current frame: {name}"),
- span,
- );
- return None;
- }
- }
- stack.push(id);
- self.local_defs.push(LocalDefinition {
- name,
- span,
- defined_at_depth: self.depth,
- used_at_depth: u32::MAX,
- used_by_sibling: false,
- analysis: AnalysisResult::default(),
- analyzed: false,
- scratch_referenced: false,
- });
- Some(id)
+ fn defining_closure_mut(&mut self) -> &mut DefiningClosure {
+ self.closure_stack
+ .iter_mut()
+ .rev()
+ .find_map(|c| c.defining.as_mut())
+ .expect("no enclosing defining closure frame")
}
+ fn defining_closure(&self) -> &DefiningClosure {
+ self.closure_stack
+ .iter()
+ .rev()
+ .find_map(|c| c.defining.as_ref())
+ .expect("no enclosing defining closure frame")
+ }
}
impl Default for AnalysisStack {
@@ -653,169 +1122,8 @@
}
impl AnalysisStack {
- fn alloc_destruct(&mut self, destruct: &Destruct, frame_start: LocalId) -> Option<LDestruct> {
- match destruct {
- Destruct::Full(name) => {
- let id =
- self.define_local(name.value.clone(), Some(name.span.clone()), frame_start)?;
- Some(LDestruct::Full(id))
- }
- #[cfg(feature = "exp-destruct")]
- Destruct::Skip => Some(LDestruct::Skip),
- #[cfg(feature = "exp-destruct")]
- Destruct::Array { start, rest, end } => {
- let start = start
- .iter()
- .map(|d| self.alloc_destruct(d, frame_start))
- .collect::<Option<Vec<_>>>()?;
- let rest = match rest {
- Some(jrsonnet_ir::DestructRest::Keep(name)) => {
- let id = self.define_local(name.clone(), None, frame_start)?;
- Some(LDestructRest::Keep(id))
- }
- Some(jrsonnet_ir::DestructRest::Drop) => Some(LDestructRest::Drop),
- None => None,
- };
- let end = end
- .iter()
- .map(|d| self.alloc_destruct(d, frame_start))
- .collect::<Option<Vec<_>>>()?;
- Some(LDestruct::Array { start, rest, end })
- }
- #[cfg(feature = "exp-destruct")]
- Destruct::Object { fields, rest } => {
- let mut l_fields: Vec<(IStr, LDestruct)> = Vec::with_capacity(fields.len());
- // Two passes: first allocate ALL destruct LocalIds, then
- // analyse defaults (which may reference later fields).
- let mut l_fields: Vec<(IStr, LDestruct)> = Vec::with_capacity(fields.len());
- for (name, into, _default) in fields {
- let into = if let Some(inner) = into {
- self.alloc_destruct(inner, frame_start)?
- } else {
- let id = self.define_local(name.clone(), None, frame_start)?;
- LDestruct::Full(id)
- };
- l_fields.push((name.clone(), into));
- }
- // Second pass: all locals exist, so defaults can reference
- // any sibling.
- let l_fields: Vec<LDestructField> = l_fields
- .into_iter()
- .zip(fields.iter())
- .map(|((name, into), (_n, _i, default))| {
- let default = default.as_ref().map(|e| {
- let mut default_taint = AnalysisResult::default();
- Rc::new(analyze(&e.value, self, &mut default_taint))
- });
- LDestructField {
- name,
- into: Some(into),
- default,
- }
- })
- .collect();
- let rest = match rest {
- Some(jrsonnet_ir::DestructRest::Keep(name)) => {
- let id = self.define_local(name.clone(), None, frame_start)?;
- Some(LDestructRest::Keep(id))
- }
- Some(jrsonnet_ir::DestructRest::Drop) => Some(LDestructRest::Drop),
- None => None,
- };
- Some(LDestruct::Object {
- fields: l_fields,
- rest,
- })
- }
- }
- }
-
- // TODO: Proper state machine
- fn begin_frame_alloc(&mut self) -> LocalId {
- self.next_local_id()
- }
-
- fn finish_frame_alloc(&mut self, first_in_frame: LocalId) -> PendingInit {
- let first_after_frame = self.next_local_id();
- PendingInit {
- first_in_frame,
- first_after_frame,
- bomb: DropBomb::new("PendingInit must be passed to finish_frame_init"),
- }
- }
-
- /// Record the analysis of a spec's value: stamp every id bound by the
- /// spec with `analysis`, collect the spec's same-frame references, and
- /// append them to `closures`.
- fn record_spec_init(
- &mut self,
- pending: &PendingInit,
- destruct: &LDestruct,
- analysis: AnalysisResult,
- closures: &mut Closures,
- ) {
- let mut refs: SmallVec<[LocalId; 4]> = SmallVec::new();
- for i in pending.first_in_frame.0..pending.first_after_frame.0 {
- let def = &mut self.local_defs[i as usize];
- if def.scratch_referenced {
- refs.push(LocalId(i));
- def.scratch_referenced = false;
- }
- }
-
- let mut ids_count = 0;
- destruct.each_id(&mut |id| {
- ids_count += 1;
- let def = &mut self.local_defs[id.idx()];
- debug_assert!(!def.analyzed, "sanity: local {:?} analysed twice", def.name);
- def.analysis = analysis;
- def.analyzed = true;
- });
- closures.push_spec(ids_count, &refs);
- }
-
- /// After all specs are analysed, propagate dependency information between
- /// siblings to a fix-point, then switch to "body" mode.
- fn finish_frame_init(&mut self, pending: PendingInit, closures: Closures) -> PendingBody {
- let PendingInit {
- first_in_frame,
- first_after_frame,
- mut bomb,
- } = pending;
- bomb.defuse();
-
- debug_assert_eq!(
- first_after_frame,
- self.next_local_id(),
- "frame initialisation left unfinished locals"
- );
-
- debug_assert_eq!(
- closures.spec_shapes.iter().map(|(_, d)| *d).sum::<usize>(),
- (first_after_frame.0 - first_in_frame.0) as usize,
- "closures destruct-id counts must match frame local count"
- );
-
- let mut changed = true;
- while changed {
- changed = false;
- for spec in closures.iter_specs() {
- for id_raw in spec.ids.clone() {
- let user = LocalId(id_raw);
- for &used in spec.references {
- changed |= self.propagate_analysis(user, used);
- }
- }
- }
- }
-
- self.depth += 1;
- PendingBody {
- first_in_frame,
- first_after_frame,
- closures,
- bomb: DropBomb::new("PendingBody must be passed to finish_frame_body"),
- }
+ fn top_defining_local(&self) -> LocalId {
+ self.defining_closure().first_local
}
/// Merge `used`'s analysis into `user`'s analysis and record that `user`
@@ -834,82 +1142,6 @@
before_obj != user_def.analysis.object_dependent_depth
|| before_loc != user_def.analysis.local_dependent_depth
}
-
- /// After the body is processed, drop the frame's locals and emit any
- /// "unused local" warnings.
- fn finish_frame_body(&mut self, pending: PendingBody) {
- let PendingBody {
- first_in_frame,
- first_after_frame,
- closures,
- mut bomb,
- } = pending;
- bomb.defuse();
- self.depth -= 1;
-
- debug_assert_eq!(
- first_after_frame,
- self.next_local_id(),
- "nested scopes must be popped before outer frames"
- );
-
- let mut changed = true;
- while changed {
- changed = false;
- for spec in closures.iter_specs() {
- // Effective used_at_depth for the spec = min over its ids.
- let mut min_used_at = u32::MAX;
- for id_raw in spec.ids.clone() {
- min_used_at = min_used_at.min(self.local_defs[id_raw as usize].used_at_depth);
- }
- if min_used_at == u32::MAX {
- continue;
- }
- for &used in spec.references {
- let used_def = &mut self.local_defs[used.idx()];
- if min_used_at < used_def.used_at_depth {
- used_def.used_at_depth = min_used_at;
- changed = true;
- }
- }
- }
- }
-
- let drained: Vec<LocalDefinition> = self.local_defs.drain(first_in_frame.idx()..).collect();
- for (i, def) in drained.iter().enumerate().rev() {
- let id = LocalId(first_in_frame.0 + i as u32);
- let stack = self
- .local_by_name
- .get_mut(&def.name)
- .expect("local must be in name map");
- let popped = stack.pop().expect("name stack should not be empty");
- debug_assert_eq!(popped, id, "name stack integrity");
- if stack.is_empty() {
- self.local_by_name.remove(&def.name);
- }
-
- if def.used_at_depth == u32::MAX {
- if def.used_by_sibling {
- self.report_warning(
- format!("local is only referenced by unused siblings: {}", def.name),
- def.span.clone(),
- );
- } else {
- self.report_warning(format!("unused local: {}", def.name), def.span.clone());
- }
- } else if def.analysis.local_dependent_depth > def.defined_at_depth
- && def.analysis.object_dependent_depth > def.defined_at_depth
- && def.defined_at_depth != 0
- {
- // The value doesn't depend on anything defined at or inside
- // this local's scope - can be hoisted, unfortunately not automatically.
- self.report_warning(
- format!("local could be hoisted to an outer scope: {}", def.name),
- def.span.clone(),
- );
- }
- }
- }
}
mod names {
@@ -922,56 +1154,85 @@
// Object scope helpers
impl AnalysisStack {
- // TODO: proper state machine
- fn enter_object_scope(&mut self) -> ObjectScope {
- let is_outermost = self.first_object_depth == u32::MAX;
- let scope = ObjectScope {
- this_id: self.push_pseudo_local(names::this()),
- is_outermost,
- prev_this_local: self.this_local,
- prev_dollar_alias: self.dollar_alias,
- prev_cur_self_used: self.cur_self_used,
- prev_cur_super_used: self.cur_super_used,
- prev_dollar_used: is_outermost.then_some(self.dollar_used),
- prev_last_object: self.last_object_depth,
- prev_first_object: self.first_object_depth,
- };
+ #[inline]
+ fn in_object_scope<T>(
+ &mut self,
+ inner: impl FnOnce(&mut AnalysisStack) -> T,
+ ) -> (ObjectUsage, ClosureShape, T) {
+ fn enter_object_scope(stack: &mut AnalysisStack) -> ObjectScope {
+ let is_outermost = stack.first_object_depth == u32::MAX;
+ let this_id = stack.next_local_id();
+ let closure = stack.push_closure_a(this_id);
+ let pushed = stack.push_pseudo_local(names::this());
+ debug_assert_eq!(pushed, this_id, "this pseudo-local id");
+ let scope = ObjectScope {
+ this_id,
+ is_outermost,
+ prev_this_local: stack.this_local,
+ prev_dollar_alias: stack.dollar_alias,
+ prev_cur_self_used: stack.cur_self_used,
+ prev_cur_super_used: stack.cur_super_used,
+ prev_dollar_used: is_outermost.then_some(stack.dollar_used),
+ prev_last_object: stack.last_object_depth,
+ prev_first_object: stack.first_object_depth,
+ closure,
+ };
- self.this_local = Some(scope.this_id);
- if is_outermost {
- self.dollar_alias = Some(scope.this_id);
- self.first_object_depth = self.depth;
- self.dollar_used = false;
+ stack.this_local = Some(scope.this_id);
+ if is_outermost {
+ stack.dollar_alias = Some(scope.this_id);
+ stack.first_object_depth = stack.depth;
+ stack.dollar_used = false;
+ }
+ stack.last_object_depth = stack.depth;
+ stack.cur_self_used = false;
+ stack.cur_super_used = false;
+ scope
}
- self.last_object_depth = self.depth;
- self.cur_self_used = false;
- self.cur_super_used = false;
- scope
- }
- fn leave_object_scope(&mut self, scope: ObjectScope) -> ObjectUsage {
- let _ = self.local_defs.pop().expect("this pseudo-local exists");
- debug_assert_eq!(self.local_defs.len(), scope.this_id.0 as usize);
+ fn leave_object_scope(
+ stack: &mut AnalysisStack,
+ scope: ObjectScope,
+ ) -> (ObjectUsage, ClosureShape) {
+ let ObjectScope {
+ this_id,
+ is_outermost,
+ prev_this_local,
+ prev_dollar_alias,
+ prev_cur_self_used,
+ prev_cur_super_used,
+ prev_dollar_used,
+ prev_last_object,
+ prev_first_object,
+ closure,
+ } = scope;
+ let _ = stack.local_defs.pop().expect("this pseudo-local exists");
+ debug_assert_eq!(stack.local_defs.len(), this_id.0 as usize);
- let set_dollar = scope.is_outermost && self.dollar_used;
- let usage = ObjectUsage {
- this_id: scope.this_id,
- this_used: self.cur_self_used || self.cur_super_used || set_dollar,
- uses_super: self.cur_super_used,
- set_dollar,
- };
+ let set_dollar = is_outermost && stack.dollar_used;
+ let usage = ObjectUsage {
+ this_used: stack.cur_self_used || stack.cur_super_used || set_dollar,
+ uses_super: stack.cur_super_used,
+ set_dollar,
+ };
+
+ stack.this_local = prev_this_local;
+ stack.dollar_alias = prev_dollar_alias;
+ stack.cur_self_used = prev_cur_self_used;
+ stack.cur_super_used = prev_cur_super_used;
+ if let Some(prev) = prev_dollar_used {
+ stack.dollar_used = prev;
+ }
+ stack.last_object_depth = prev_last_object;
+ stack.first_object_depth = prev_first_object;
- self.this_local = scope.prev_this_local;
- self.dollar_alias = scope.prev_dollar_alias;
- self.cur_self_used = scope.prev_cur_self_used;
- self.cur_super_used = scope.prev_cur_super_used;
- if let Some(prev) = scope.prev_dollar_used {
- self.dollar_used = prev;
+ let frame_shape = stack.pop_closure(closure);
+ (usage, frame_shape)
}
- self.last_object_depth = scope.prev_last_object;
- self.first_object_depth = scope.prev_first_object;
-
- usage
+ let scope = enter_object_scope(self);
+ let v = inner(self);
+ let (usage, shape) = leave_object_scope(self, scope);
+ (usage, shape, v)
}
fn push_pseudo_local(&mut self, name: IStr) -> LocalId {
@@ -986,14 +1247,18 @@
analyzed: true,
scratch_referenced: false,
});
+ {
+ let def = self.defining_closure_mut();
+ let _ = def.define_local(id);
+ }
id
}
- fn use_this(&mut self, taint: &mut AnalysisResult) -> Option<LocalId> {
+ fn use_this(&mut self, taint: &mut AnalysisResult) -> Option<LSlot> {
let id = self.this_local?;
self.cur_self_used = true;
self.use_pseudo_local(id, taint);
- Some(id)
+ Some(self.resolve_to_slot(id))
}
fn use_super(&mut self, taint: &mut AnalysisResult) -> Option<()> {
@@ -1003,11 +1268,11 @@
Some(())
}
- fn use_dollar(&mut self, taint: &mut AnalysisResult) -> Option<LocalId> {
+ fn use_dollar(&mut self, taint: &mut AnalysisResult) -> Option<LSlot> {
let id = self.dollar_alias?;
self.dollar_used = true;
self.use_pseudo_local(id, taint);
- Some(id)
+ Some(self.resolve_to_slot(id))
}
// TODO: Dedicated type for object references instead of "pseudo local" BS, idk
@@ -1020,6 +1285,7 @@
}
}
+#[must_use]
struct ObjectScope {
this_id: LocalId,
is_outermost: bool,
@@ -1030,10 +1296,10 @@
prev_dollar_used: Option<bool>,
prev_last_object: u32,
prev_first_object: u32,
+ closure: ClosureOnStack,
}
struct ObjectUsage {
- this_id: LocalId,
this_used: bool,
uses_super: bool,
set_dollar: bool,
@@ -1073,7 +1339,7 @@
stack.report_error("`self` used outside of object", None);
LExpr::BadLocal("self")
},
- LExpr::Local,
+ LExpr::Slot,
),
LiteralType::Super => {
if stack.use_super(taint).is_some() {
@@ -1088,7 +1354,7 @@
stack.report_error("`$` used outside of object", None);
LExpr::BadLocal("$")
},
- LExpr::Local,
+ LExpr::Slot,
),
LiteralType::Null => LExpr::Null,
LiteralType::True => LExpr::Bool(true),
@@ -1098,10 +1364,15 @@
Expr::Num(n) => LExpr::Num(*n),
Expr::Var(v) => stack
.use_local(&v.value, v.span.clone(), taint)
- .map_or_else(|| LExpr::BadLocal("ref"), LExpr::Local),
- Expr::Arr(a) => LExpr::Arr(Rc::new(
- a.iter().map(|v| analyze(v, stack, taint)).collect(),
- )),
+ .map_or_else(|| LExpr::BadLocal("ref"), LExpr::Slot),
+ Expr::Arr(a) => {
+ let (shape, items) = stack
+ .in_using_closure(|stack| a.iter().map(|v| analyze(v, stack, taint)).collect());
+ LExpr::Arr {
+ shape,
+ items: Rc::new(items),
+ }
+ }
Expr::ArrComp(inner, comp) => analyze_arr_comp(inner, comp, stack, taint),
Expr::Obj(obj) => LExpr::Obj(analyze_obj_body(obj, stack, taint)),
Expr::ObjExtend(base, obj) => LExpr::ObjExtend(
@@ -1238,14 +1509,17 @@
if binds.is_empty() {
return analyze(body, stack, taint);
}
- let (_frame_start, l_binds, body_expr) =
- process_local_frame(binds, stack, taint, |stack, taint| {
- analyze(body, stack, taint)
- });
- LExpr::LocalExpr {
+ let frame_start = stack.next_local_id();
+ let closure = stack.push_closure_a(frame_start);
+ let (l_binds, body_expr) = process_local_frame(binds, stack, taint, |stack, taint| {
+ analyze(body, stack, taint)
+ });
+ let frame_shape = stack.pop_closure(closure);
+ LExpr::LocalExpr(Box::new(LLocalExpr {
+ frame_shape,
binds: l_binds,
- body: Box::new(body_expr),
- }
+ body: body_expr,
+ }))
}
fn analyze_bind_value(
@@ -1267,55 +1541,44 @@
}
}
-fn alloc_bind_destruct(
- bind: &BindSpec,
- stack: &mut AnalysisStack,
- frame_start: LocalId,
-) -> Option<LDestruct> {
- match bind {
- BindSpec::Field { into, .. } => stack.alloc_destruct(into, frame_start),
- BindSpec::Function { name, .. } => stack
- .define_local(name.clone(), None, frame_start)
- .map(LDestruct::Full),
- }
-}
-
fn process_local_frame<R>(
binds: &[BindSpec],
stack: &mut AnalysisStack,
taint: &mut AnalysisResult,
body_fn: impl FnOnce(&mut AnalysisStack, &mut AnalysisResult) -> R,
-) -> (LocalId, Vec<LBind>, R) {
- let frame_start = stack.begin_frame_alloc();
+) -> (Vec<LBind>, R) {
+ let mut alloc = FrameAlloc::new(stack);
let mut destructs: Vec<Option<LDestruct>> = Vec::with_capacity(binds.len());
for bind in binds {
- destructs.push(alloc_bind_destruct(bind, stack, frame_start));
+ destructs.push(alloc.alloc_bind(bind));
}
- let pending = stack.finish_frame_alloc(frame_start);
+ let mut pending = alloc.finish();
- let mut closures = Closures::new(frame_start);
let mut l_binds: Vec<LBind> = Vec::with_capacity(binds.len());
for (bind, destruct) in binds.iter().zip(destructs.into_iter()) {
let mut value_taint = AnalysisResult::default();
- let value = analyze_bind_value(bind, stack, &mut value_taint);
+ let (value_shape, value) = pending
+ .stack
+ .in_using_closure(|stack| analyze_bind_value(bind, stack, &mut value_taint));
taint.taint_by(value_taint);
if let Some(destruct) = destruct {
- stack.record_spec_init(&pending, &destruct, value_taint, &mut closures);
+ pending.record_spec_init(&destruct, value_taint);
l_binds.push(LBind {
destruct,
+ value_shape,
value: Rc::new(value),
});
} else {
- closures.push_spec(0, &[]);
+ pending.closures.push_spec(0, &[]);
}
}
- let body_frame = stack.finish_frame_init(pending, closures);
- let result = body_fn(stack, taint);
- stack.finish_frame_body(body_frame);
+ let body_frame = pending.finish();
+ let result = body_fn(body_frame.stack, taint);
+ body_frame.finish();
- (frame_start, l_binds, result)
+ (l_binds, result)
}
fn analyze_function(
@@ -1325,23 +1588,29 @@
stack: &mut AnalysisStack,
taint: &mut AnalysisResult,
) -> LExpr {
- let frame_start = stack.begin_frame_alloc();
+ let mut alloc = FrameAlloc::new(stack);
+ let closure = alloc.push_locals_closure();
let mut param_destructs: Vec<Option<LDestruct>> = Vec::with_capacity(params.exprs.len());
for p in ¶ms.exprs {
- param_destructs.push(stack.alloc_destruct(&p.destruct, frame_start));
+ param_destructs.push(alloc.alloc_destruct(&p.destruct));
}
- let pending = stack.finish_frame_alloc(frame_start);
+ let mut pending = alloc.finish();
- let mut closures = Closures::new(frame_start);
let mut l_params: Vec<LParam> = Vec::with_capacity(params.exprs.len());
for (p, destruct) in params.exprs.iter().zip(param_destructs.into_iter()) {
let mut value_taint = AnalysisResult::default();
- let default = p
- .default
- .as_ref()
- .map(|d| Rc::new(analyze(d, stack, &mut value_taint)));
+ let default = p.default.as_ref().map_or_else(
+ || None,
+ |d| {
+ Some(
+ pending
+ .stack
+ .in_using_closure(|stack| Rc::new(analyze(d, stack, &mut value_taint))),
+ )
+ },
+ );
taint.taint_by(value_taint);
if let Some(destruct) = destruct {
let name = match &p.destruct {
@@ -1349,25 +1618,42 @@
#[cfg(feature = "exp-destruct")]
_ => None,
};
- stack.record_spec_init(&pending, &destruct, value_taint, &mut closures);
+ pending.record_spec_init(&destruct, value_taint);
l_params.push(LParam {
name,
destruct,
default,
});
} else {
- closures.push_spec(0, &[]);
+ pending.closures.push_spec(0, &[]);
}
}
- let body_frame = stack.finish_frame_init(pending, closures);
- let body_expr = analyze(body, stack, taint);
- stack.finish_frame_body(body_frame);
+ let body_frame = pending.finish();
+ let body_expr = analyze(body, body_frame.stack, taint);
+ body_frame.finish();
+ let body_shape = stack.pop_closure(closure);
+ // function(x) x is an identity function
+ if l_params.len() == 1 && l_params[0].default.is_none() {
+ stack.report_warning(
+ "do not define identity functions manually, use std.id instead",
+ None,
+ );
+ #[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]
+ if let LDestruct::Full(param_slot) = &l_params[0].destruct
+ && let LExpr::Slot(LSlot::Local(s)) = &body_expr
+ && s == param_slot
+ {
+ return LExpr::IdentityFunction {};
+ }
+ }
+
LExpr::Function(Rc::new(LFunction {
name,
params: l_params,
signature: params.signature.clone(),
+ body_shape,
body: Rc::new(body_expr),
}))
}
@@ -1405,38 +1691,55 @@
})
.collect();
- let scope = stack.enter_object_scope();
- let (_frame_start, l_binds, (l_asserts, l_fields)) =
- process_local_frame(locals, stack, taint, |stack, taint| {
- let mut l_asserts = Vec::with_capacity(asserts.len());
- for a in asserts {
- let mut assert_taint = AnalysisResult::default();
- l_asserts.push(analyze_assert(a, stack, &mut assert_taint));
- taint.taint_by(assert_taint);
- }
- let mut l_fields = Vec::with_capacity(fields.len());
- for (f, name) in fields.iter().zip(field_names) {
- let value = if let Some(params) = &f.params {
- analyze_function(name.function_name(), params, &f.value, stack, taint)
+ let (usage, frame_shape, (l_binds, (l_asserts_opt, l_fields))) =
+ stack.in_object_scope(|stack| {
+ process_local_frame(locals, stack, taint, |stack, taint| {
+ let l_asserts_opt = if asserts.is_empty() {
+ None
} else {
- analyze(&f.value, stack, taint)
+ let (shape, l_asserts) = stack.in_using_closure(|stack| {
+ let mut l_asserts = Vec::with_capacity(asserts.len());
+ for a in asserts {
+ let mut assert_taint = AnalysisResult::default();
+ l_asserts.push(analyze_assert(a, stack, &mut assert_taint));
+ taint.taint_by(assert_taint);
+ }
+ l_asserts
+ });
+ Some(Rc::new(LObjAsserts {
+ shape,
+ asserts: l_asserts,
+ }))
};
- l_fields.push(LFieldMember {
- name,
- plus: f.plus,
- visibility: f.visibility,
- value: Rc::new(value),
- });
- }
- (l_asserts, l_fields)
+ let mut l_fields = Vec::with_capacity(fields.len());
+ for (f, name) in fields.iter().zip(field_names) {
+ let value = stack.in_using_closure(|stack| {
+ if let Some(params) = &f.params {
+ analyze_function(name.function_name(), params, &f.value, stack, taint)
+ } else {
+ analyze(&f.value, stack, taint)
+ }
+ });
+ l_fields.push(LFieldMember {
+ name,
+ plus: f.plus,
+ visibility: f.visibility,
+ value: Rc::new(value),
+ });
+ }
+ (l_asserts_opt, l_fields)
+ })
});
- let usage = stack.leave_object_scope(scope);
+ // `this` was allocated as the first local of the object's frame,
+ // so its slot is 0 within that frame.
+ let this_slot = usage.this_used.then_some(LocalSlot(0));
LObjMembers {
- this: usage.this_used.then_some(usage.this_id),
+ frame_shape,
+ this: this_slot,
set_dollar: usage.set_dollar,
uses_super: usage.uses_super,
locals: Rc::new(l_binds),
- asserts: Rc::new(l_asserts),
+ asserts: l_asserts_opt,
fields: l_fields,
}
}
@@ -1452,26 +1755,30 @@
FieldName::Dyn(e) => LFieldName::Dyn(analyze(e, stack, taint)),
};
- let scope = stack.enter_object_scope();
- let body = process_local_frame(&comp.locals, stack, taint, |stack, taint| {
- let value = if let Some(params) = &comp.field.params {
- analyze_function(None, params, &comp.field.value, stack, taint)
- } else {
- analyze(&comp.field.value, stack, taint)
- };
- LFieldMember {
- name: field_name,
- plus: comp.field.plus,
- visibility: comp.field.visibility,
- value: Rc::new(value),
- }
+ let (usage, frame_shape, body) = stack.in_object_scope(|stack| {
+ process_local_frame(&comp.locals, stack, taint, |stack, taint| {
+ let value = stack.in_using_closure(|stack| {
+ if let Some(params) = &comp.field.params {
+ analyze_function(None, params, &comp.field.value, stack, taint)
+ } else {
+ analyze(&comp.field.value, stack, taint)
+ }
+ });
+ LFieldMember {
+ name: field_name,
+ plus: comp.field.plus,
+ visibility: comp.field.visibility,
+ value: Rc::new(value),
+ }
+ })
});
- let usage = stack.leave_object_scope(scope);
- (usage, body)
+ (usage, frame_shape, body)
});
- let (usage, (_frame_start, locals, field)) = res.inner;
+ let (usage, frame_shape, (locals, field)) = res.inner;
+ let this_slot = usage.this_used.then_some(LocalSlot(0));
LObjComp {
- this: usage.this_used.then_some(usage.this_id),
+ frame_shape: Rc::new(frame_shape),
+ this: this_slot,
set_dollar: usage.set_dollar,
uses_super: usage.uses_super,
locals: Rc::new(locals),
@@ -1487,10 +1794,12 @@
taint: &mut AnalysisResult,
) -> LExpr {
let res = analyze_comp_specs(specs, stack, taint, |stack, taint| {
- analyze(inner, stack, taint)
+ stack.in_using_closure(|stack| analyze(inner, stack, taint))
});
+ let (value_shape, value) = res.inner;
LExpr::ArrComp(Box::new(LArrComp {
- value: Rc::new(res.inner),
+ value_shape,
+ value: Rc::new(value),
compspecs: res.compspecs,
}))
}
@@ -1525,23 +1834,27 @@
let loop_invariant = over_taint.local_dependent_depth > outer_depth;
taint.taint_by(over_taint);
- let frame_start = stack.begin_frame_alloc();
- let Some(l_destruct) = stack.alloc_destruct(destruct, frame_start) else {
+ let mut alloc = FrameAlloc::new(stack);
+ let closure = alloc.push_locals_closure();
+ let Some(l_destruct) = alloc.alloc_destruct(destruct) else {
+ stack.pop_closure(closure);
return go(idx + 1, specs, outer_depth, stack, taint, inside);
};
- let pending = stack.finish_frame_alloc(frame_start);
+ let mut pending = alloc.finish();
let var_analysis = AnalysisResult::default();
- let mut closures = Closures::new(frame_start);
- stack.record_spec_init(&pending, &l_destruct, var_analysis, &mut closures);
+ pending.record_spec_init(&l_destruct, var_analysis);
- let body_frame = stack.finish_frame_init(pending, closures);
- let (r, mut rest) = go(idx + 1, specs, outer_depth, stack, taint, inside);
- stack.finish_frame_body(body_frame);
+ let body_frame = pending.finish();
+ let (r, mut rest) =
+ go(idx + 1, specs, outer_depth, body_frame.stack, taint, inside);
+ body_frame.finish();
+ let frame_shape = stack.pop_closure(closure);
rest.insert(
0,
LCompSpec::For {
+ frame_shape,
destruct: l_destruct,
over: over_l,
loop_invariant,
@@ -1570,18 +1883,37 @@
stack.define_external_local(name, id);
}
+ let externals_count: u16 = stack
+ .local_defs
+ .len()
+ .try_into()
+ .expect("more than u16::MAX externals");
+ let closure = stack.push_root_closure(externals_count);
+
let mut taint = AnalysisResult::default();
let lir = analyze(expr, &mut stack, &mut taint);
+ let root_shape = stack.pop_closure(closure);
+ debug_assert!(
+ stack.closure_stack.is_empty(),
+ "closure stack imbalance after analyze"
+ );
+
AnalysisReport {
lir,
+ root_shape,
root_analysis: taint,
diagnostics_list: stack.diagnostics,
errored: stack.errored,
}
}
+#[cfg(test)]
fn render_diagnostics(src: &str, diags: &[Diagnostic]) -> String {
+ use std::fmt::Write;
+
+ use hi_doc::{Formatting, SnippetBuilder, Text};
+
let mut out = String::new();
let mut unspanned = Vec::new();
let mut spanned: Vec<&Diagnostic> = Vec::new();
@@ -1620,6 +1952,7 @@
pub struct AnalysisReport {
pub lir: LExpr,
+ pub root_shape: ClosureShape,
pub root_analysis: AnalysisResult,
pub diagnostics_list: Vec<Diagnostic>,
pub errored: bool,
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@array_comp.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@array_comp.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@array_comp.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/array_comp.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/array_comp.jsonnet
---
--- source ---
[x * 2 for x in [1, 2, 3] if x > 1]
@@ -13,10 +13,16 @@
--- lir ---
ArrComp(
LArrComp {
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
value: BinaryOp {
- lhs: Local(
- LocalId(
- 0,
+ lhs: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
),
op: Mul,
@@ -26,13 +32,21 @@
},
compspecs: [
For {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
destruct: Full(
- LocalId(
+ LocalSlot(
0,
),
),
- over: Arr(
- [
+ over: Arr {
+ shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ items: [
Num(
1.0,
),
@@ -43,14 +57,16 @@
3.0,
),
],
- ),
+ },
loop_invariant: true,
},
If(
BinaryOp {
- lhs: Local(
- LocalId(
- 0,
+ lhs: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
),
op: Gt,
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@dollar_deeply_nested.jsonnet.snapdiffbeforeafterboth1---2source: crates/jrsonnet-evaluator/src/analyze.rs3expression: rendered4input_file: crates/jrsonnet-evaluator/src/analyze_tests/dollar_deeply_nested.jsonnet5---6--- source ---7{8 top: 'outer',9 a: {10 b: {11 c: $.top,12 d: self,13 },14 },15}16--- root analysis ---17object_dependent_depth: 018local_dependent_depth: 019errored: false20--- diagnostics ---21--- lir ---22Obj(23 MemberList(24 LObjMembers {25 this: Some(26 LocalId(27 0,28 ),29 ),30 set_dollar: true,31 uses_super: false,32 locals: [],33 asserts: [],34 fields: [35 LFieldMember {36 name: Fixed(37 "top",38 ),39 plus: false,40 visibility: Normal,41 value: Str(42 "outer",43 ),44 },45 LFieldMember {46 name: Fixed(47 "a",48 ),49 plus: false,50 visibility: Normal,51 value: Obj(52 MemberList(53 LObjMembers {54 this: None,55 set_dollar: false,56 uses_super: false,57 locals: [],58 asserts: [],59 fields: [60 LFieldMember {61 name: Fixed(62 "b",63 ),64 plus: false,65 visibility: Normal,66 value: Obj(67 MemberList(68 LObjMembers {69 this: Some(70 LocalId(71 2,72 ),73 ),74 set_dollar: false,75 uses_super: false,76 locals: [],77 asserts: [],78 fields: [79 LFieldMember {80 name: Fixed(81 "c",82 ),83 plus: false,84 visibility: Normal,85 value: Index {86 indexable: Local(87 LocalId(88 0,89 ),90 ),91 parts: [92 LIndexPart {93 span: virtual:<test>:45-48,94 value: Str(95 "top",96 ),97 },98 ],99 },100 },101 LFieldMember {102 name: Fixed(103 "d",104 ),105 plus: false,106 visibility: Normal,107 value: Local(108 LocalId(109 2,110 ),111 ),112 },113 ],114 },115 ),116 ),117 },118 ],119 },120 ),121 ),122 },123 ],124 },125 ),126)1---2source: crates/jrsonnet-evaluator/src/analyze.rs3expression: rendered4input_file: crates/jrsonnet-evaluator/src/analysis_tests/dollar_deeply_nested.jsonnet5---6--- source ---7{8 top: 'outer',9 a: {10 b: {11 c: $.top,12 d: self,13 },14 },15}16--- root analysis ---17object_dependent_depth: 018local_dependent_depth: 019errored: false20--- diagnostics ---21--- lir ---22Obj(23 MemberList(24 LObjMembers {25 frame_shape: ClosureShape {26 captures: [],27 n_locals: 1,28 },29 this: Some(30 LocalSlot(31 0,32 ),33 ),34 set_dollar: true,35 uses_super: false,36 locals: [],37 asserts: None,38 fields: [39 LFieldMember {40 name: Fixed(41 "top",42 ),43 plus: false,44 visibility: Normal,45 value: (46 ClosureShape {47 captures: [],48 n_locals: 0,49 },50 Str(51 "outer",52 ),53 ),54 },55 LFieldMember {56 name: Fixed(57 "a",58 ),59 plus: false,60 visibility: Normal,61 value: (62 ClosureShape {63 captures: [],64 n_locals: 0,65 },66 Obj(67 MemberList(68 LObjMembers {69 frame_shape: ClosureShape {70 captures: [71 Local(72 LocalSlot(73 0,74 ),75 ),76 ],77 n_locals: 1,78 },79 this: None,80 set_dollar: false,81 uses_super: false,82 locals: [],83 asserts: None,84 fields: [85 LFieldMember {86 name: Fixed(87 "b",88 ),89 plus: false,90 visibility: Normal,91 value: (92 ClosureShape {93 captures: [94 Capture(95 CaptureSlot(96 0,97 ),98 ),99 ],100 n_locals: 0,101 },102 Obj(103 MemberList(104 LObjMembers {105 frame_shape: ClosureShape {106 captures: [107 Capture(108 CaptureSlot(109 0,110 ),111 ),112 ],113 n_locals: 1,114 },115 this: Some(116 LocalSlot(117 0,118 ),119 ),120 set_dollar: false,121 uses_super: false,122 locals: [],123 asserts: None,124 fields: [125 LFieldMember {126 name: Fixed(127 "c",128 ),129 plus: false,130 visibility: Normal,131 value: (132 ClosureShape {133 captures: [134 Capture(135 CaptureSlot(136 0,137 ),138 ),139 ],140 n_locals: 0,141 },142 Index {143 indexable: Slot(144 Capture(145 CaptureSlot(146 0,147 ),148 ),149 ),150 parts: [151 LIndexPart {152 span: virtual:<test>:45-48,153 value: Str(154 "top",155 ),156 },157 ],158 },159 ),160 },161 LFieldMember {162 name: Fixed(163 "d",164 ),165 plus: false,166 visibility: Normal,167 value: (168 ClosureShape {169 captures: [],170 n_locals: 0,171 },172 Slot(173 Local(174 LocalSlot(175 0,176 ),177 ),178 ),179 ),180 },181 ],182 },183 ),184 ),185 ),186 },187 ],188 },189 ),190 ),191 ),192 },193 ],194 },195 ),196)crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@function_def.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@function_def.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@function_def.jsonnet.snap
@@ -11,94 +11,114 @@
errored: false
--- diagnostics ---
--- lir ---
-LocalExpr {
- binds: [
- LBind {
- destruct: Full(
- LocalId(
- 0,
- ),
- ),
- value: Function(
- LFunction {
- name: Some(
- "f",
+LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
+ binds: [
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 0,
),
- params: [
- LParam {
- name: Some(
- "x",
- ),
- destruct: Full(
- LocalId(
- 1,
- ),
- ),
- default: None,
- },
- LParam {
- name: Some(
- "y",
- ),
- destruct: Full(
- LocalId(
- 2,
- ),
- ),
- default: None,
- },
- ],
- signature: FunctionSignature(
- [
- ParamParse {
- name: Named(
+ ),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Function(
+ LFunction {
+ name: Some(
+ "f",
+ ),
+ params: [
+ LParam {
+ name: Some(
"x",
),
+ destruct: Full(
+ LocalSlot(
+ 0,
+ ),
+ ),
default: None,
},
- ParamParse {
- name: Named(
+ LParam {
+ name: Some(
"y",
),
+ destruct: Full(
+ LocalSlot(
+ 1,
+ ),
+ ),
default: None,
},
],
- ),
- body: BinaryOp {
- lhs: Local(
- LocalId(
- 1,
- ),
+ signature: FunctionSignature(
+ [
+ ParamParse {
+ name: Named(
+ "x",
+ ),
+ default: None,
+ },
+ ParamParse {
+ name: Named(
+ "y",
+ ),
+ default: None,
+ },
+ ],
),
- op: Add,
- rhs: Local(
- LocalId(
- 2,
+ body_shape: ClosureShape {
+ captures: [],
+ n_locals: 2,
+ },
+ body: BinaryOp {
+ lhs: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
),
- ),
+ op: Add,
+ rhs: Slot(
+ Local(
+ LocalSlot(
+ 1,
+ ),
+ ),
+ ),
+ },
},
- },
- ),
- },
- ],
- body: Apply {
- applicable: Local(
- LocalId(
- 0,
- ),
- ),
- args: LArgsDesc {
- unnamed: [
- Num(
- 1.0,
),
- Num(
- 2.0,
+ },
+ ],
+ body: Apply {
+ applicable: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
- ],
- names: [],
- values: [],
- } from virtual:<test>:24-30,
- tailstrict: false,
+ ),
+ args: LArgsDesc {
+ unnamed: [
+ Num(
+ 1.0,
+ ),
+ Num(
+ 2.0,
+ ),
+ ],
+ names: [],
+ values: [],
+ } from virtual:<test>:24-30,
+ tailstrict: false,
+ },
},
-}
+)
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@hoistable_local.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@hoistable_local.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@hoistable_local.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/hoistable_local.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/hoistable_local.jsonnet
---
--- source ---
local outer = 1; local inner = 10 + 20; outer + inner
@@ -14,50 +14,80 @@
1 │ local outer = 1; local inner = 10 + 20; outer + inner
2 │
--- lir ---
-LocalExpr {
- binds: [
- LBind {
- destruct: Full(
- LocalId(
- 0,
- ),
- ),
- value: Num(
- 1.0,
- ),
+LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
},
- ],
- body: LocalExpr {
binds: [
LBind {
destruct: Full(
- LocalId(
- 1,
+ LocalSlot(
+ 0,
),
),
- value: BinaryOp {
- lhs: Num(
- 10.0,
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Num(
+ 1.0,
+ ),
+ },
+ ],
+ body: LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ ],
+ n_locals: 1,
+ },
+ binds: [
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: BinaryOp {
+ lhs: Num(
+ 10.0,
+ ),
+ op: Add,
+ rhs: Num(
+ 20.0,
+ ),
+ },
+ },
+ ],
+ body: BinaryOp {
+ lhs: Slot(
+ Capture(
+ CaptureSlot(
+ 0,
+ ),
+ ),
),
op: Add,
- rhs: Num(
- 20.0,
+ rhs: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
),
},
},
- ],
- body: BinaryOp {
- lhs: Local(
- LocalId(
- 0,
- ),
- ),
- op: Add,
- rhs: Local(
- LocalId(
- 1,
- ),
- ),
- },
+ ),
},
-}
+)
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@loop_invariant.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@loop_invariant.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@loop_invariant.jsonnet.snap
@@ -18,23 +18,41 @@
--- lir ---
ArrComp(
LArrComp {
+ value_shape: ClosureShape {
+ captures: [
+ Capture(
+ CaptureSlot(
+ 0,
+ ),
+ ),
+ ],
+ n_locals: 0,
+ },
value: BinaryOp {
- lhs: Local(
- LocalId(
- 0,
+ lhs: Slot(
+ Capture(
+ CaptureSlot(
+ 0,
+ ),
),
),
op: Lt,
- rhs: Local(
- LocalId(
- 1,
+ rhs: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
),
},
compspecs: [
For {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
destruct: Full(
- LocalId(
+ LocalSlot(
0,
),
),
@@ -69,9 +87,19 @@
loop_invariant: true,
},
For {
+ frame_shape: ClosureShape {
+ captures: [
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ ],
+ n_locals: 1,
+ },
destruct: Full(
- LocalId(
- 1,
+ LocalSlot(
+ 0,
),
),
over: Apply {
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@mutual_recursion.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@mutual_recursion.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@mutual_recursion.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/mutual_recursion.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/mutual_recursion.jsonnet
---
--- source ---
local a = b, b = 1; a + 2
@@ -11,40 +11,58 @@
errored: false
--- diagnostics ---
--- lir ---
-LocalExpr {
- binds: [
- LBind {
- destruct: Full(
- LocalId(
- 0,
+LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 2,
+ },
+ binds: [
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Slot(
+ Local(
+ LocalSlot(
+ 1,
+ ),
+ ),
+ ),
+ },
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 1,
+ ),
),
- ),
- value: Local(
- LocalId(
- 1,
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Num(
+ 1.0,
),
- ),
- },
- LBind {
- destruct: Full(
- LocalId(
- 1,
+ },
+ ],
+ body: BinaryOp {
+ lhs: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
),
- value: Num(
- 1.0,
+ op: Add,
+ rhs: Num(
+ 2.0,
),
},
- ],
- body: BinaryOp {
- lhs: Local(
- LocalId(
- 0,
- ),
- ),
- op: Add,
- rhs: Num(
- 2.0,
- ),
},
-}
+)
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@nested_object_independent.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@nested_object_independent.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@nested_object_independent.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analysis_golden/nested_object_independent.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/nested_object_independent.jsonnet
---
--- source ---
{
@@ -19,11 +19,15 @@
Obj(
MemberList(
LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
this: None,
set_dollar: false,
uses_super: false,
locals: [],
- asserts: [],
+ asserts: None,
fields: [
LFieldMember {
name: Fixed(
@@ -31,8 +35,14 @@
),
plus: false,
visibility: Normal,
- value: Num(
- 1.0,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Num(
+ 1.0,
+ ),
),
},
LFieldMember {
@@ -41,53 +51,77 @@
),
plus: false,
visibility: Normal,
- value: Obj(
- MemberList(
- LObjMembers {
- this: Some(
- LocalId(
- 1,
- ),
- ),
- set_dollar: false,
- uses_super: false,
- locals: [],
- asserts: [],
- fields: [
- LFieldMember {
- name: Fixed(
- "c",
- ),
- plus: false,
- visibility: Normal,
- value: Num(
- 2.0,
- ),
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Obj(
+ MemberList(
+ LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
},
- LFieldMember {
- name: Fixed(
- "d",
+ this: Some(
+ LocalSlot(
+ 0,
),
- plus: false,
- visibility: Normal,
- value: Index {
- indexable: Local(
- LocalId(
- 1,
+ ),
+ set_dollar: false,
+ uses_super: false,
+ locals: [],
+ asserts: None,
+ fields: [
+ LFieldMember {
+ name: Fixed(
+ "c",
+ ),
+ plus: false,
+ visibility: Normal,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Num(
+ 2.0,
),
),
- parts: [
- LIndexPart {
- span: virtual:<test>:35-36,
- value: Str(
- "c",
+ },
+ LFieldMember {
+ name: Fixed(
+ "d",
+ ),
+ plus: false,
+ visibility: Normal,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Index {
+ indexable: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
),
+ parts: [
+ LIndexPart {
+ span: virtual:<test>:35-36,
+ value: Str(
+ "c",
+ ),
+ },
+ ],
},
- ],
+ ),
},
- },
- ],
- },
+ ],
+ },
+ ),
),
),
},
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_comp.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_comp.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_comp.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_comp.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/object_comp.jsonnet
---
--- source ---
{ [k]: k for k in ['a', 'b'] }
@@ -14,35 +14,69 @@
Obj(
ObjComp(
LObjComp {
+ frame_shape: ClosureShape {
+ captures: [
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ ],
+ n_locals: 1,
+ },
this: None,
set_dollar: false,
uses_super: false,
locals: [],
field: LFieldMember {
name: Dyn(
- Local(
- LocalId(
- 0,
+ Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
),
),
plus: false,
visibility: Normal,
- value: Local(
- LocalId(
- 0,
+ value: (
+ ClosureShape {
+ captures: [
+ Capture(
+ CaptureSlot(
+ 0,
+ ),
+ ),
+ ],
+ n_locals: 0,
+ },
+ Slot(
+ Capture(
+ CaptureSlot(
+ 0,
+ ),
+ ),
),
),
},
compspecs: [
For {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
destruct: Full(
- LocalId(
+ LocalSlot(
0,
),
),
- over: Arr(
- [
+ over: Arr {
+ shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ items: [
Str(
"a",
),
@@ -50,7 +84,7 @@
"b",
),
],
- ),
+ },
loop_invariant: true,
},
],
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_dollar.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_dollar.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_dollar.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_dollar.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/object_dollar.jsonnet
---
--- source ---
{ a: 1, b: { c: $.a } }
@@ -14,15 +14,19 @@
Obj(
MemberList(
LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
this: Some(
- LocalId(
+ LocalSlot(
0,
),
),
set_dollar: true,
uses_super: false,
locals: [],
- asserts: [],
+ asserts: None,
fields: [
LFieldMember {
name: Fixed(
@@ -30,8 +34,14 @@
),
plus: false,
visibility: Normal,
- value: Num(
- 1.0,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Num(
+ 1.0,
+ ),
),
},
LFieldMember {
@@ -40,39 +50,69 @@
),
plus: false,
visibility: Normal,
- value: Obj(
- MemberList(
- LObjMembers {
- this: None,
- set_dollar: false,
- uses_super: false,
- locals: [],
- asserts: [],
- fields: [
- LFieldMember {
- name: Fixed(
- "c",
- ),
- plus: false,
- visibility: Normal,
- value: Index {
- indexable: Local(
- LocalId(
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Obj(
+ MemberList(
+ LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [
+ Local(
+ LocalSlot(
0,
),
),
- parts: [
- LIndexPart {
- span: virtual:<test>:18-19,
- value: Str(
- "a",
+ ],
+ n_locals: 1,
+ },
+ this: None,
+ set_dollar: false,
+ uses_super: false,
+ locals: [],
+ asserts: None,
+ fields: [
+ LFieldMember {
+ name: Fixed(
+ "c",
+ ),
+ plus: false,
+ visibility: Normal,
+ value: (
+ ClosureShape {
+ captures: [
+ Capture(
+ CaptureSlot(
+ 0,
+ ),
+ ),
+ ],
+ n_locals: 0,
+ },
+ Index {
+ indexable: Slot(
+ Capture(
+ CaptureSlot(
+ 0,
+ ),
+ ),
),
+ parts: [
+ LIndexPart {
+ span: virtual:<test>:18-19,
+ value: Str(
+ "a",
+ ),
+ },
+ ],
},
- ],
+ ),
},
- },
- ],
- },
+ ],
+ },
+ ),
),
),
},
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_self.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_self.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_self.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_self.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/object_self.jsonnet
---
--- source ---
{ a: 1, b: self.a }
@@ -14,15 +14,19 @@
Obj(
MemberList(
LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
this: Some(
- LocalId(
+ LocalSlot(
0,
),
),
set_dollar: false,
uses_super: false,
locals: [],
- asserts: [],
+ asserts: None,
fields: [
LFieldMember {
name: Fixed(
@@ -30,8 +34,14 @@
),
plus: false,
visibility: Normal,
- value: Num(
- 1.0,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Num(
+ 1.0,
+ ),
),
},
LFieldMember {
@@ -40,21 +50,29 @@
),
plus: false,
visibility: Normal,
- value: Index {
- indexable: Local(
- LocalId(
- 0,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Index {
+ indexable: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
),
- ),
- parts: [
- LIndexPart {
- span: virtual:<test>:16-17,
- value: Str(
- "a",
- ),
- },
- ],
- },
+ parts: [
+ LIndexPart {
+ span: virtual:<test>:16-17,
+ value: Str(
+ "a",
+ ),
+ },
+ ],
+ },
+ ),
},
],
},
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_with_locals.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_with_locals.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_with_locals.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_with_locals.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/object_with_locals.jsonnet
---
--- source ---
{
@@ -18,22 +18,30 @@
Obj(
MemberList(
LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 2,
+ },
this: None,
set_dollar: false,
uses_super: false,
locals: [
LBind {
destruct: Full(
- LocalId(
+ LocalSlot(
1,
),
),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
value: Num(
10.0,
),
},
],
- asserts: [],
+ asserts: None,
fields: [
LFieldMember {
name: Fixed(
@@ -41,9 +49,17 @@
),
plus: false,
visibility: Normal,
- value: Local(
- LocalId(
- 1,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Slot(
+ Local(
+ LocalSlot(
+ 1,
+ ),
+ ),
),
),
},
@@ -53,17 +69,25 @@
),
plus: false,
visibility: Normal,
- value: BinaryOp {
- lhs: Local(
- LocalId(
- 1,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ BinaryOp {
+ lhs: Slot(
+ Local(
+ LocalSlot(
+ 1,
+ ),
+ ),
+ ),
+ op: Mul,
+ rhs: Num(
+ 2.0,
),
- ),
- op: Mul,
- rhs: Num(
- 2.0,
- ),
- },
+ },
+ ),
},
],
},
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
@@ -14,22 +14,34 @@
1 │ local x = 1, x = 2; x
2 │
--- lir ---
-LocalExpr {
- binds: [
- LBind {
- destruct: Full(
- LocalId(
+LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
+ binds: [
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Num(
+ 1.0,
+ ),
+ },
+ ],
+ body: Slot(
+ Local(
+ LocalSlot(
0,
),
),
- value: Num(
- 1.0,
- ),
- },
- ],
- body: Local(
- LocalId(
- 0,
),
- ),
-}
+ },
+)
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@shadowing.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@shadowing.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@shadowing.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/shadowing.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/shadowing.jsonnet
---
--- source ---
local x = 1; local x = 2; x
@@ -15,36 +15,58 @@
· ╰── unused local: x
2 │
--- lir ---
-LocalExpr {
- binds: [
- LBind {
- destruct: Full(
- LocalId(
- 0,
- ),
- ),
- value: Num(
- 1.0,
- ),
+LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
},
- ],
- body: LocalExpr {
binds: [
LBind {
destruct: Full(
- LocalId(
- 1,
+ LocalSlot(
+ 0,
),
),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
value: Num(
- 2.0,
+ 1.0,
),
},
],
- body: Local(
- LocalId(
- 1,
- ),
+ body: LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
+ binds: [
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Num(
+ 2.0,
+ ),
+ },
+ ],
+ body: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ ),
+ },
),
},
-}
+)
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@simple_local.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@simple_local.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@simple_local.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/simple_local.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/simple_local.jsonnet
---
--- source ---
local x = 1; x + 2
@@ -11,28 +11,40 @@
errored: false
--- diagnostics ---
--- lir ---
-LocalExpr {
- binds: [
- LBind {
- destruct: Full(
- LocalId(
- 0,
+LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
+ binds: [
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Num(
+ 1.0,
+ ),
+ },
+ ],
+ body: BinaryOp {
+ lhs: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
),
- value: Num(
- 1.0,
+ op: Add,
+ rhs: Num(
+ 2.0,
),
},
- ],
- body: BinaryOp {
- lhs: Local(
- LocalId(
- 0,
- ),
- ),
- op: Add,
- rhs: Num(
- 2.0,
- ),
},
-}
+)
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@slice.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@slice.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@slice.jsonnet.snap
@@ -1,7 +1,8 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
+assertion_line: 2017
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/slice.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/slice.jsonnet
---
--- source ---
[1, 2, 3, 4, 5][1:3]
@@ -13,8 +14,12 @@
--- lir ---
Slice(
LSliceExpr {
- value: Arr(
- [
+ value: Arr {
+ shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ items: [
Num(
1.0,
),
@@ -31,7 +36,7 @@
5.0,
),
],
- ),
+ },
start: Some(
Num(
1.0,
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@super_usage.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@super_usage.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@super_usage.jsonnet.snap
@@ -15,11 +15,15 @@
lhs: Obj(
MemberList(
LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
this: None,
set_dollar: false,
uses_super: false,
locals: [],
- asserts: [],
+ asserts: None,
fields: [
LFieldMember {
name: Fixed(
@@ -27,8 +31,14 @@
),
plus: false,
visibility: Normal,
- value: Num(
- 1.0,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Num(
+ 1.0,
+ ),
),
},
LFieldMember {
@@ -37,8 +47,14 @@
),
plus: false,
visibility: Normal,
- value: Num(
- 2.0,
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Num(
+ 2.0,
+ ),
),
},
],
@@ -49,15 +65,19 @@
rhs: Obj(
MemberList(
LObjMembers {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
this: Some(
- LocalId(
+ LocalSlot(
0,
),
),
set_dollar: false,
uses_super: true,
locals: [],
- asserts: [],
+ asserts: None,
fields: [
LFieldMember {
name: Fixed(
@@ -65,23 +85,29 @@
),
plus: false,
visibility: Normal,
- value: BinaryOp {
- lhs: Index {
- indexable: Super,
- parts: [
- LIndexPart {
- span: virtual:<test>:28-29,
- value: Str(
- "a",
- ),
- },
- ],
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ BinaryOp {
+ lhs: Index {
+ indexable: Super,
+ parts: [
+ LIndexPart {
+ span: virtual:<test>:28-29,
+ value: Str(
+ "a",
+ ),
+ },
+ ],
+ },
+ op: Add,
+ rhs: Num(
+ 10.0,
+ ),
},
- op: Add,
- rhs: Num(
- 10.0,
- ),
- },
+ ),
},
LFieldMember {
name: Fixed(
@@ -89,21 +115,29 @@
),
plus: false,
visibility: Normal,
- value: Index {
- indexable: Local(
- LocalId(
- 0,
- ),
- ),
- parts: [
- LIndexPart {
- span: virtual:<test>:44-45,
- value: Str(
- "b",
+ value: (
+ ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ Index {
+ indexable: Slot(
+ Local(
+ LocalSlot(
+ 0,
+ ),
),
- },
- ],
- },
+ ),
+ parts: [
+ LIndexPart {
+ span: virtual:<test>:44-45,
+ value: Str(
+ "b",
+ ),
+ },
+ ],
+ },
+ ),
},
],
},
crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@unused_local.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@unused_local.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@unused_local.jsonnet.snap
@@ -1,7 +1,7 @@
---
source: crates/jrsonnet-evaluator/src/analyze.rs
expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/unused_local.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/unused_local.jsonnet
---
--- source ---
local unused = 1; 2
@@ -14,20 +14,30 @@
1 │ local unused = 1; 2
2 │
--- lir ---
-LocalExpr {
- binds: [
- LBind {
- destruct: Full(
- LocalId(
- 0,
+LocalExpr(
+ LLocalExpr {
+ frame_shape: ClosureShape {
+ captures: [],
+ n_locals: 1,
+ },
+ binds: [
+ LBind {
+ destruct: Full(
+ LocalSlot(
+ 0,
+ ),
+ ),
+ value_shape: ClosureShape {
+ captures: [],
+ n_locals: 0,
+ },
+ value: Num(
+ 1.0,
),
- ),
- value: Num(
- 1.0,
- ),
- },
- ],
- body: Num(
- 2.0,
- ),
-}
+ },
+ ],
+ body: Num(
+ 2.0,
+ ),
+ },
+)
tests/go_testdata_golden_override/bad_function_call.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/bad_function_call.jsonnet.golden
+++ b/tests/go_testdata_golden_override/bad_function_call.jsonnet.golden
@@ -1,3 +1,3 @@
function argument is not passed: x
Function has the following signature: (x)
- bad_function_call.jsonnet:1:16-19: function <anonymous> preparation
\ No newline at end of file
+ bad_function_call.jsonnet:1:16-19: function <builtin_id> preparation
\ No newline at end of file
tests/go_testdata_golden_override/bad_function_call2.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/bad_function_call2.jsonnet.golden
+++ b/tests/go_testdata_golden_override/bad_function_call2.jsonnet.golden
@@ -1,3 +1,3 @@
too many args, function has 1
Function has the following signature: (x)
- bad_function_call2.jsonnet:1:16-23: function <anonymous> preparation
\ No newline at end of file
+ bad_function_call2.jsonnet:1:16-23: function <builtin_id> preparation
\ No newline at end of file
tests/go_testdata_golden_override/bad_function_call_and_error.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/bad_function_call_and_error.jsonnet.golden
+++ b/tests/go_testdata_golden_override/bad_function_call_and_error.jsonnet.golden
@@ -1,3 +1,3 @@
too many args, function has 1
Function has the following signature: (x)
- bad_function_call_and_error.jsonnet:1:16-39: function <anonymous> preparation
\ No newline at end of file
+ bad_function_call_and_error.jsonnet:1:16-39: function <builtin_id> preparation
\ No newline at end of file
tests/go_testdata_golden_override/optional_args9.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/optional_args9.jsonnet.golden
+++ b/tests/go_testdata_golden_override/optional_args9.jsonnet.golden
@@ -1,2 +1,2 @@
argument x is already bound
- optional_args9.jsonnet:1:16-27: function <anonymous> preparation
\ No newline at end of file
+ optional_args9.jsonnet:1:16-27: function <builtin_id> preparation
\ No newline at end of file
tests/golden/comp_if_with_multiple_captures.jsonnetdiffbeforeafterboth--- a/tests/golden/comp_if_with_multiple_captures.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-local features = { gc: 'serialgc', libc: 'musl' };
-local order = ['gc', 'libc', 'missing'];
-[features[f] for f in order if std.objectHas(features, f)]
tests/golden/object_assert_after_member_local.jsonnetdiffbeforeafterboth--- a/tests/golden/object_assert_after_member_local.jsonnet
+++ /dev/null
@@ -1,7 +0,0 @@
-local outer1 = 'one';
-local outer2 = 'two';
-{
- local member_local = outer1,
- assert outer2 == 'two' : 'wrong outer2: ' + outer2,
- result: member_local,
-}.result
tests/suite/comp_eager_array_body_capture.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/comp_eager_array_body_capture.jsonnet
@@ -0,0 +1,2 @@
+std.assertEqual([[v] for v in ['a', 'b']], [['a'], ['b']])
+&& std.assertEqual(std.flattenArrays([[{ x: v }] for v in ['a', 'b']]), [{ x: 'a' }, { x: 'b' }])
tests/suite/comp_if_with_multiple_captures.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/comp_if_with_multiple_captures.jsonnet
@@ -0,0 +1,6 @@
+local features = { gc: 'serialgc', libc: 'musl' };
+local order = ['gc', 'libc', 'missing'];
+std.assertEqual(
+ [features[f] for f in order if std.objectHas(features, f)],
+ ['serialgc', 'musl']
+)
tests/suite/object_assert_after_member_local.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_assert_after_member_local.jsonnet
@@ -0,0 +1,7 @@
+local outer1 = 'one';
+local outer2 = 'two';
+std.assertEqual({
+ local member_local = outer1,
+ assert outer2 == 'two' : 'wrong outer2: ' + outer2,
+ result: member_local,
+}.result, 'one')
tests/tests/common.rsdiffbeforeafterboth--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -1,6 +1,6 @@
use jrsonnet_evaluator::{
- ContextBuilder, ContextInitializer as ContextInitializerT, InitialContextBuilder,
- ObjValueBuilder, Result, Source, Thunk, Val, bail,
+ ContextInitializer as ContextInitializerT, InitialContextBuilder, ObjValueBuilder, Result,
+ Source, Thunk, Val, bail,
function::{FuncVal, builtin},
};
use jrsonnet_gcmodule::Trace;
@@ -29,7 +29,7 @@
macro_rules! ensure_val_eq {
($a:expr, $b:expr) => {{
if !::jrsonnet_evaluator::val::equals(&$a.clone(), &$b.clone())? {
- use ::jrsonnet_evaluator::manifest::JsonFormat;
+ use jrsonnet_evaluator::manifest::JsonFormat;
::jrsonnet_evaluator::bail!(
"assertion failed: a != b\na={:#?}\nb={:#?}",
$a.manifest(JsonFormat::default())?,