git.delta.rocks / jrsonnet / refs/commits / 1979dbda1bc0

difftreelog

feat static analysis

tnwsmlqtYaroslav Bolyukin2026-04-25parent: #259b3ab.patch.diff
in: master

45 files changed

addedcrates/jrsonnet-evaluator/src/analysis_tests/array_comp.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/array_comp.jsonnet
@@ -0,0 +1 @@
+[x * 2 for x in [1, 2, 3] if x > 1]
addedcrates/jrsonnet-evaluator/src/analysis_tests/dollar_deeply_nested.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/dollar_deeply_nested.jsonnet
@@ -0,0 +1,9 @@
+{
+  top: 'outer',
+  a: {
+    b: {
+      c: $.top,
+      d: self,
+    },
+  },
+}
addedcrates/jrsonnet-evaluator/src/analysis_tests/dollar_outside_object.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/dollar_outside_object.jsonnet
@@ -0,0 +1 @@
+$.a
addedcrates/jrsonnet-evaluator/src/analysis_tests/function_def.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/function_def.jsonnet
@@ -0,0 +1 @@
+local f(x, y) = x + y; f(1, 2)
addedcrates/jrsonnet-evaluator/src/analysis_tests/hoistable_local.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/hoistable_local.jsonnet
@@ -0,0 +1 @@
+local outer = 1; local inner = 10 + 20; outer + inner
addedcrates/jrsonnet-evaluator/src/analysis_tests/ifelse.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/ifelse.jsonnet
@@ -0,0 +1 @@
+if true then 1 else 2
addedcrates/jrsonnet-evaluator/src/analysis_tests/literal.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/literal.jsonnet
@@ -0,0 +1 @@
+42
addedcrates/jrsonnet-evaluator/src/analysis_tests/loop_invariant.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/loop_invariant.jsonnet
@@ -0,0 +1 @@
+[ i < j for i in std.range(1, 1000) for j in std.range(1, 1000)]
addedcrates/jrsonnet-evaluator/src/analysis_tests/mutual_recursion.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/mutual_recursion.jsonnet
@@ -0,0 +1 @@
+local a = b, b = 1; a + 2
addedcrates/jrsonnet-evaluator/src/analysis_tests/nested_object_independent.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/nested_object_independent.jsonnet
@@ -0,0 +1,6 @@
+{
+  a: 1,
+  b: {
+    c: 2, d: self.c
+  },
+}
addedcrates/jrsonnet-evaluator/src/analysis_tests/object_comp.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/object_comp.jsonnet
@@ -0,0 +1 @@
+{ [k]: k for k in ['a', 'b'] }
addedcrates/jrsonnet-evaluator/src/analysis_tests/object_dollar.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/object_dollar.jsonnet
@@ -0,0 +1 @@
+{ a: 1, b: { c: $.a } }
addedcrates/jrsonnet-evaluator/src/analysis_tests/object_self.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/object_self.jsonnet
@@ -0,0 +1 @@
+{ a: 1, b: self.a }
addedcrates/jrsonnet-evaluator/src/analysis_tests/object_with_locals.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/object_with_locals.jsonnet
@@ -0,0 +1,5 @@
+{
+  local helper = 10,
+  a: helper,
+  b: helper * 2,
+}
addedcrates/jrsonnet-evaluator/src/analysis_tests/redeclared_local.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/redeclared_local.jsonnet
@@ -0,0 +1 @@
+local x = 1, x = 2; x
addedcrates/jrsonnet-evaluator/src/analysis_tests/shadowing.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/shadowing.jsonnet
@@ -0,0 +1 @@
+local x = 1; local x = 2; x
addedcrates/jrsonnet-evaluator/src/analysis_tests/simple_local.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/simple_local.jsonnet
@@ -0,0 +1 @@
+local x = 1; x + 2
addedcrates/jrsonnet-evaluator/src/analysis_tests/slice.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/slice.jsonnet
@@ -0,0 +1 @@
+[1, 2, 3, 4, 5][1:3]
addedcrates/jrsonnet-evaluator/src/analysis_tests/super_outside_object.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/super_outside_object.jsonnet
@@ -0,0 +1 @@
+super.a
addedcrates/jrsonnet-evaluator/src/analysis_tests/super_usage.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/super_usage.jsonnet
@@ -0,0 +1 @@
+{ a: 1, b: 2 } + { a: super.a + 10, c: self.b }
addedcrates/jrsonnet-evaluator/src/analysis_tests/undefined_var.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/undefined_var.jsonnet
@@ -0,0 +1 @@
+y + 1
addedcrates/jrsonnet-evaluator/src/analysis_tests/unused_local.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/analysis_tests/unused_local.jsonnet
@@ -0,0 +1 @@
+local unused = 1; 2
addedcrates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth

no changes

addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@array_comp.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@array_comp.jsonnet.snap
@@ -0,0 +1,64 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/array_comp.jsonnet
+---
+--- source ---
+[x * 2 for x in [1, 2, 3] if x > 1]
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+ArrComp(
+    LArrComp {
+        value: BinaryOp {
+            lhs: Local(
+                LocalId(
+                    0,
+                ),
+            ),
+            op: Mul,
+            rhs: Num(
+                2.0,
+            ),
+        },
+        compspecs: [
+            For {
+                destruct: Full(
+                    LocalId(
+                        0,
+                    ),
+                ),
+                over: Arr(
+                    [
+                        Num(
+                            1.0,
+                        ),
+                        Num(
+                            2.0,
+                        ),
+                        Num(
+                            3.0,
+                        ),
+                    ],
+                ),
+                loop_invariant: true,
+            },
+            If(
+                BinaryOp {
+                    lhs: Local(
+                        LocalId(
+                            0,
+                        ),
+                    ),
+                    op: Gt,
+                    rhs: Num(
+                        1.0,
+                    ),
+                },
+            ),
+        ],
+    },
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@dollar_deeply_nested.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@dollar_deeply_nested.jsonnet.snap
@@ -0,0 +1,126 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/dollar_deeply_nested.jsonnet
+---
+--- source ---
+{
+  top: 'outer',
+  a: {
+    b: {
+      c: $.top,
+      d: self,
+    },
+  },
+}
+--- root analysis ---
+object_dependent_depth: 0
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+Obj(
+    MemberList(
+        LObjMembers {
+            this: Some(
+                LocalId(
+                    0,
+                ),
+            ),
+            set_dollar: true,
+            uses_super: false,
+            locals: [],
+            asserts: [],
+            fields: [
+                LFieldMember {
+                    name: Fixed(
+                        "top",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: Str(
+                        "outer",
+                    ),
+                },
+                LFieldMember {
+                    name: Fixed(
+                        "a",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: Obj(
+                        MemberList(
+                            LObjMembers {
+                                this: None,
+                                set_dollar: false,
+                                uses_super: false,
+                                locals: [],
+                                asserts: [],
+                                fields: [
+                                    LFieldMember {
+                                        name: Fixed(
+                                            "b",
+                                        ),
+                                        plus: false,
+                                        visibility: Normal,
+                                        value: Obj(
+                                            MemberList(
+                                                LObjMembers {
+                                                    this: Some(
+                                                        LocalId(
+                                                            2,
+                                                        ),
+                                                    ),
+                                                    set_dollar: false,
+                                                    uses_super: false,
+                                                    locals: [],
+                                                    asserts: [],
+                                                    fields: [
+                                                        LFieldMember {
+                                                            name: Fixed(
+                                                                "c",
+                                                            ),
+                                                            plus: false,
+                                                            visibility: Normal,
+                                                            value: Index {
+                                                                indexable: Local(
+                                                                    LocalId(
+                                                                        0,
+                                                                    ),
+                                                                ),
+                                                                parts: [
+                                                                    LIndexPart {
+                                                                        span: virtual:<test>:45-48,
+                                                                        value: Str(
+                                                                            "top",
+                                                                        ),
+                                                                    },
+                                                                ],
+                                                            },
+                                                        },
+                                                        LFieldMember {
+                                                            name: Fixed(
+                                                                "d",
+                                                            ),
+                                                            plus: false,
+                                                            visibility: Normal,
+                                                            value: Local(
+                                                                LocalId(
+                                                                    2,
+                                                                ),
+                                                            ),
+                                                        },
+                                                    ],
+                                                },
+                                            ),
+                                        ),
+                                    },
+                                ],
+                            },
+                        ),
+                    ),
+                },
+            ],
+        },
+    ),
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@dollar_outside_object.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@dollar_outside_object.jsonnet.snap
@@ -0,0 +1,27 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/dollar_outside_object.jsonnet
+---
+--- source ---
+$.a
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: none
+errored: true
+--- diagnostics ---
+error: `$` used outside of object
+--- lir ---
+Index {
+    indexable: BadLocal(
+        "$",
+    ),
+    parts: [
+        LIndexPart {
+            span: virtual:<test>:2-3,
+            value: Str(
+                "a",
+            ),
+        },
+    ],
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@function_def.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@function_def.jsonnet.snap
@@ -0,0 +1,104 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/function_def.jsonnet
+---
+--- source ---
+local f(x, y) = x + y; f(1, 2)
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+LocalExpr {
+    binds: [
+        LBind {
+            destruct: Full(
+                LocalId(
+                    0,
+                ),
+            ),
+            value: Function(
+                LFunction {
+                    name: Some(
+                        "f",
+                    ),
+                    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(
+                                    "x",
+                                ),
+                                default: None,
+                            },
+                            ParamParse {
+                                name: Named(
+                                    "y",
+                                ),
+                                default: None,
+                            },
+                        ],
+                    ),
+                    body: BinaryOp {
+                        lhs: Local(
+                            LocalId(
+                                1,
+                            ),
+                        ),
+                        op: Add,
+                        rhs: Local(
+                            LocalId(
+                                2,
+                            ),
+                        ),
+                    },
+                },
+            ),
+        },
+    ],
+    body: Apply {
+        applicable: Local(
+            LocalId(
+                0,
+            ),
+        ),
+        args: LArgsDesc {
+            unnamed: [
+                Num(
+                    1.0,
+                ),
+                Num(
+                    2.0,
+                ),
+            ],
+            names: [],
+            values: [],
+        } from virtual:<test>:24-30,
+        tailstrict: false,
+    },
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@hoistable_local.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@hoistable_local.jsonnet.snap
@@ -0,0 +1,63 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/hoistable_local.jsonnet
+---
+--- source ---
+local outer = 1; local inner = 10 + 20; outer + inner
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+   ·                        ╭────── local could be hoisted to an outer scope: inner
+1  │ local outer = 1; local inner = 10 + 20; outer + inner 
+2  │  
+--- lir ---
+LocalExpr {
+    binds: [
+        LBind {
+            destruct: Full(
+                LocalId(
+                    0,
+                ),
+            ),
+            value: Num(
+                1.0,
+            ),
+        },
+    ],
+    body: LocalExpr {
+        binds: [
+            LBind {
+                destruct: Full(
+                    LocalId(
+                        1,
+                    ),
+                ),
+                value: BinaryOp {
+                    lhs: Num(
+                        10.0,
+                    ),
+                    op: Add,
+                    rhs: Num(
+                        20.0,
+                    ),
+                },
+            },
+        ],
+        body: BinaryOp {
+            lhs: Local(
+                LocalId(
+                    0,
+                ),
+            ),
+            op: Add,
+            rhs: Local(
+                LocalId(
+                    1,
+                ),
+            ),
+        },
+    },
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@ifelse.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@ifelse.jsonnet.snap
@@ -0,0 +1,26 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/ifelse.jsonnet
+---
+--- source ---
+if true then 1 else 2
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: none
+errored: false
+--- diagnostics ---
+--- lir ---
+IfElse {
+    cond: Bool(
+        true,
+    ),
+    cond_then: Num(
+        1.0,
+    ),
+    cond_else: Some(
+        Num(
+            2.0,
+        ),
+    ),
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@literal.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@literal.jsonnet.snap
@@ -0,0 +1,16 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/literal.jsonnet
+---
+--- source ---
+42
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: none
+errored: false
+--- diagnostics ---
+--- lir ---
+Num(
+    42.0,
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@loop_invariant.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@loop_invariant.jsonnet.snap
@@ -0,0 +1,109 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/loop_invariant.jsonnet
+---
+--- source ---
+[ i < j for i in std.range(1, 1000) for j in std.range(1, 1000)]
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: true
+--- diagnostics ---
+   ·                  ╭──────────────────────────────── undefined local: std
+   ·                  │                           ╭──── undefined local: std
+1  │ [ i < j for i in std.range(1, 1000) for j in std.range(1, 1000)] 
+   ·                                         ╰── local could be hoisted to an outer scope: j
+2  │  
+--- lir ---
+ArrComp(
+    LArrComp {
+        value: BinaryOp {
+            lhs: Local(
+                LocalId(
+                    0,
+                ),
+            ),
+            op: Lt,
+            rhs: Local(
+                LocalId(
+                    1,
+                ),
+            ),
+        },
+        compspecs: [
+            For {
+                destruct: Full(
+                    LocalId(
+                        0,
+                    ),
+                ),
+                over: Apply {
+                    applicable: Index {
+                        indexable: BadLocal(
+                            "ref",
+                        ),
+                        parts: [
+                            LIndexPart {
+                                span: virtual:<test>:21-26,
+                                value: Str(
+                                    "range",
+                                ),
+                            },
+                        ],
+                    },
+                    args: LArgsDesc {
+                        unnamed: [
+                            Num(
+                                1.0,
+                            ),
+                            Num(
+                                1000.0,
+                            ),
+                        ],
+                        names: [],
+                        values: [],
+                    } from virtual:<test>:26-35,
+                    tailstrict: false,
+                },
+                loop_invariant: true,
+            },
+            For {
+                destruct: Full(
+                    LocalId(
+                        1,
+                    ),
+                ),
+                over: Apply {
+                    applicable: Index {
+                        indexable: BadLocal(
+                            "ref",
+                        ),
+                        parts: [
+                            LIndexPart {
+                                span: virtual:<test>:49-54,
+                                value: Str(
+                                    "range",
+                                ),
+                            },
+                        ],
+                    },
+                    args: LArgsDesc {
+                        unnamed: [
+                            Num(
+                                1.0,
+                            ),
+                            Num(
+                                1000.0,
+                            ),
+                        ],
+                        names: [],
+                        values: [],
+                    } from virtual:<test>:54-63,
+                    tailstrict: false,
+                },
+                loop_invariant: true,
+            },
+        ],
+    },
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@mutual_recursion.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@mutual_recursion.jsonnet.snap
@@ -0,0 +1,50 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/mutual_recursion.jsonnet
+---
+--- source ---
+local a = b, b = 1; a + 2
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+LocalExpr {
+    binds: [
+        LBind {
+            destruct: Full(
+                LocalId(
+                    0,
+                ),
+            ),
+            value: Local(
+                LocalId(
+                    1,
+                ),
+            ),
+        },
+        LBind {
+            destruct: Full(
+                LocalId(
+                    1,
+                ),
+            ),
+            value: Num(
+                1.0,
+            ),
+        },
+    ],
+    body: BinaryOp {
+        lhs: Local(
+            LocalId(
+                0,
+            ),
+        ),
+        op: Add,
+        rhs: Num(
+            2.0,
+        ),
+    },
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@nested_object_independent.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@nested_object_independent.jsonnet.snap
@@ -0,0 +1,97 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analysis_golden/nested_object_independent.jsonnet
+---
+--- source ---
+{
+  a: 1,
+  b: {
+    c: 2, d: self.c
+  },
+}
+--- root analysis ---
+object_dependent_depth: 1
+local_dependent_depth: 1
+errored: false
+--- diagnostics ---
+--- lir ---
+Obj(
+    MemberList(
+        LObjMembers {
+            this: None,
+            set_dollar: false,
+            uses_super: false,
+            locals: [],
+            asserts: [],
+            fields: [
+                LFieldMember {
+                    name: Fixed(
+                        "a",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: Num(
+                        1.0,
+                    ),
+                },
+                LFieldMember {
+                    name: Fixed(
+                        "b",
+                    ),
+                    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,
+                                        ),
+                                    },
+                                    LFieldMember {
+                                        name: Fixed(
+                                            "d",
+                                        ),
+                                        plus: false,
+                                        visibility: Normal,
+                                        value: Index {
+                                            indexable: Local(
+                                                LocalId(
+                                                    1,
+                                                ),
+                                            ),
+                                            parts: [
+                                                LIndexPart {
+                                                    span: virtual:<test>:35-36,
+                                                    value: Str(
+                                                        "c",
+                                                    ),
+                                                },
+                                            ],
+                                        },
+                                    },
+                                ],
+                            },
+                        ),
+                    ),
+                },
+            ],
+        },
+    ),
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_comp.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_comp.jsonnet.snap
@@ -0,0 +1,59 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_comp.jsonnet
+---
+--- source ---
+{ [k]: k for k in ['a', 'b'] }
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+Obj(
+    ObjComp(
+        LObjComp {
+            this: None,
+            set_dollar: false,
+            uses_super: false,
+            locals: [],
+            field: LFieldMember {
+                name: Dyn(
+                    Local(
+                        LocalId(
+                            0,
+                        ),
+                    ),
+                ),
+                plus: false,
+                visibility: Normal,
+                value: Local(
+                    LocalId(
+                        0,
+                    ),
+                ),
+            },
+            compspecs: [
+                For {
+                    destruct: Full(
+                        LocalId(
+                            0,
+                        ),
+                    ),
+                    over: Arr(
+                        [
+                            Str(
+                                "a",
+                            ),
+                            Str(
+                                "b",
+                            ),
+                        ],
+                    ),
+                    loop_invariant: true,
+                },
+            ],
+        },
+    ),
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_dollar.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_dollar.jsonnet.snap
@@ -0,0 +1,82 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_dollar.jsonnet
+---
+--- source ---
+{ a: 1, b: { c: $.a } }
+--- root analysis ---
+object_dependent_depth: 0
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+Obj(
+    MemberList(
+        LObjMembers {
+            this: Some(
+                LocalId(
+                    0,
+                ),
+            ),
+            set_dollar: true,
+            uses_super: false,
+            locals: [],
+            asserts: [],
+            fields: [
+                LFieldMember {
+                    name: Fixed(
+                        "a",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: Num(
+                        1.0,
+                    ),
+                },
+                LFieldMember {
+                    name: Fixed(
+                        "b",
+                    ),
+                    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(
+                                                    0,
+                                                ),
+                                            ),
+                                            parts: [
+                                                LIndexPart {
+                                                    span: virtual:<test>:18-19,
+                                                    value: Str(
+                                                        "a",
+                                                    ),
+                                                },
+                                            ],
+                                        },
+                                    },
+                                ],
+                            },
+                        ),
+                    ),
+                },
+            ],
+        },
+    ),
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_self.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_self.jsonnet.snap
@@ -0,0 +1,62 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_self.jsonnet
+---
+--- source ---
+{ a: 1, b: self.a }
+--- root analysis ---
+object_dependent_depth: 0
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+Obj(
+    MemberList(
+        LObjMembers {
+            this: Some(
+                LocalId(
+                    0,
+                ),
+            ),
+            set_dollar: false,
+            uses_super: false,
+            locals: [],
+            asserts: [],
+            fields: [
+                LFieldMember {
+                    name: Fixed(
+                        "a",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: Num(
+                        1.0,
+                    ),
+                },
+                LFieldMember {
+                    name: Fixed(
+                        "b",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: Index {
+                        indexable: Local(
+                            LocalId(
+                                0,
+                            ),
+                        ),
+                        parts: [
+                            LIndexPart {
+                                span: virtual:<test>:16-17,
+                                value: Str(
+                                    "a",
+                                ),
+                            },
+                        ],
+                    },
+                },
+            ],
+        },
+    ),
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_with_locals.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@object_with_locals.jsonnet.snap
@@ -0,0 +1,71 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/object_with_locals.jsonnet
+---
+--- source ---
+{
+  local helper = 10,
+  a: helper,
+  b: helper * 2,
+}
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+Obj(
+    MemberList(
+        LObjMembers {
+            this: None,
+            set_dollar: false,
+            uses_super: false,
+            locals: [
+                LBind {
+                    destruct: Full(
+                        LocalId(
+                            1,
+                        ),
+                    ),
+                    value: Num(
+                        10.0,
+                    ),
+                },
+            ],
+            asserts: [],
+            fields: [
+                LFieldMember {
+                    name: Fixed(
+                        "a",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: Local(
+                        LocalId(
+                            1,
+                        ),
+                    ),
+                },
+                LFieldMember {
+                    name: Fixed(
+                        "b",
+                    ),
+                    plus: false,
+                    visibility: Normal,
+                    value: BinaryOp {
+                        lhs: Local(
+                            LocalId(
+                                1,
+                            ),
+                        ),
+                        op: Mul,
+                        rhs: Num(
+                            2.0,
+                        ),
+                    },
+                },
+            ],
+        },
+    ),
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snap
@@ -0,0 +1,35 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/redeclared_local.jsonnet
+---
+--- source ---
+local x = 1, x = 2; x
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: true
+--- diagnostics ---
+   ·              ╭── variable redeclared: x
+1  │ local x = 1, x = 2; x 
+2  │  
+--- lir ---
+LocalExpr {
+    binds: [
+        LBind {
+            destruct: Full(
+                LocalId(
+                    0,
+                ),
+            ),
+            value: Num(
+                1.0,
+            ),
+        },
+    ],
+    body: Local(
+        LocalId(
+            0,
+        ),
+    ),
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@shadowing.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@shadowing.jsonnet.snap
@@ -0,0 +1,50 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/shadowing.jsonnet
+---
+--- source ---
+local x = 1; local x = 2; x
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 1
+errored: false
+--- diagnostics ---
+   ·                    ╭── local could be hoisted to an outer scope: x
+1  │ local x = 1; local x = 2; x 
+   ·       ╰── unused local: x
+2  │  
+--- lir ---
+LocalExpr {
+    binds: [
+        LBind {
+            destruct: Full(
+                LocalId(
+                    0,
+                ),
+            ),
+            value: Num(
+                1.0,
+            ),
+        },
+    ],
+    body: LocalExpr {
+        binds: [
+            LBind {
+                destruct: Full(
+                    LocalId(
+                        1,
+                    ),
+                ),
+                value: Num(
+                    2.0,
+                ),
+            },
+        ],
+        body: Local(
+            LocalId(
+                1,
+            ),
+        ),
+    },
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@simple_local.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@simple_local.jsonnet.snap
@@ -0,0 +1,38 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/simple_local.jsonnet
+---
+--- source ---
+local x = 1; x + 2
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+LocalExpr {
+    binds: [
+        LBind {
+            destruct: Full(
+                LocalId(
+                    0,
+                ),
+            ),
+            value: Num(
+                1.0,
+            ),
+        },
+    ],
+    body: BinaryOp {
+        lhs: Local(
+            LocalId(
+                0,
+            ),
+        ),
+        op: Add,
+        rhs: Num(
+            2.0,
+        ),
+    },
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@slice.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@slice.jsonnet.snap
@@ -0,0 +1,47 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/slice.jsonnet
+---
+--- source ---
+[1, 2, 3, 4, 5][1:3]
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: none
+errored: false
+--- diagnostics ---
+--- lir ---
+Slice(
+    LSliceExpr {
+        value: Arr(
+            [
+                Num(
+                    1.0,
+                ),
+                Num(
+                    2.0,
+                ),
+                Num(
+                    3.0,
+                ),
+                Num(
+                    4.0,
+                ),
+                Num(
+                    5.0,
+                ),
+            ],
+        ),
+        start: Some(
+            Num(
+                1.0,
+            ),
+        ),
+        end: Some(
+            Num(
+                3.0,
+            ),
+        ),
+        step: None,
+    },
+)
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@super_outside_object.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@super_outside_object.jsonnet.snap
@@ -0,0 +1,27 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/super_outside_object.jsonnet
+---
+--- source ---
+super.a
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: none
+errored: true
+--- diagnostics ---
+error: `super` used outside of object
+--- lir ---
+Index {
+    indexable: BadLocal(
+        "super",
+    ),
+    parts: [
+        LIndexPart {
+            span: virtual:<test>:6-7,
+            value: Str(
+                "a",
+            ),
+        },
+    ],
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@super_usage.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@super_usage.jsonnet.snap
@@ -0,0 +1,112 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/super_usage.jsonnet
+---
+--- source ---
+{ a: 1, b: 2 } + { a: super.a + 10, c: self.b }
+--- root analysis ---
+object_dependent_depth: 0
+local_dependent_depth: 0
+errored: false
+--- diagnostics ---
+--- lir ---
+BinaryOp {
+    lhs: Obj(
+        MemberList(
+            LObjMembers {
+                this: None,
+                set_dollar: false,
+                uses_super: false,
+                locals: [],
+                asserts: [],
+                fields: [
+                    LFieldMember {
+                        name: Fixed(
+                            "a",
+                        ),
+                        plus: false,
+                        visibility: Normal,
+                        value: Num(
+                            1.0,
+                        ),
+                    },
+                    LFieldMember {
+                        name: Fixed(
+                            "b",
+                        ),
+                        plus: false,
+                        visibility: Normal,
+                        value: Num(
+                            2.0,
+                        ),
+                    },
+                ],
+            },
+        ),
+    ),
+    op: Add,
+    rhs: Obj(
+        MemberList(
+            LObjMembers {
+                this: Some(
+                    LocalId(
+                        0,
+                    ),
+                ),
+                set_dollar: false,
+                uses_super: true,
+                locals: [],
+                asserts: [],
+                fields: [
+                    LFieldMember {
+                        name: Fixed(
+                            "a",
+                        ),
+                        plus: false,
+                        visibility: Normal,
+                        value: BinaryOp {
+                            lhs: Index {
+                                indexable: Super,
+                                parts: [
+                                    LIndexPart {
+                                        span: virtual:<test>:28-29,
+                                        value: Str(
+                                            "a",
+                                        ),
+                                    },
+                                ],
+                            },
+                            op: Add,
+                            rhs: Num(
+                                10.0,
+                            ),
+                        },
+                    },
+                    LFieldMember {
+                        name: Fixed(
+                            "c",
+                        ),
+                        plus: false,
+                        visibility: Normal,
+                        value: Index {
+                            indexable: Local(
+                                LocalId(
+                                    0,
+                                ),
+                            ),
+                            parts: [
+                                LIndexPart {
+                                    span: virtual:<test>:44-45,
+                                    value: Str(
+                                        "b",
+                                    ),
+                                },
+                            ],
+                        },
+                    },
+                ],
+            },
+        ),
+    ),
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@undefined_var.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@undefined_var.jsonnet.snap
@@ -0,0 +1,25 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/undefined_var.jsonnet
+---
+--- source ---
+y + 1
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: none
+errored: true
+--- diagnostics ---
+   · ╭── undefined local: y
+1  │ y + 1 
+2  │  
+--- lir ---
+BinaryOp {
+    lhs: BadLocal(
+        "ref",
+    ),
+    op: Add,
+    rhs: Num(
+        1.0,
+    ),
+}
addedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@unused_local.jsonnet.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@unused_local.jsonnet.snap
@@ -0,0 +1,33 @@
+---
+source: crates/jrsonnet-evaluator/src/analyze.rs
+expression: rendered
+input_file: crates/jrsonnet-evaluator/src/analyze_tests/unused_local.jsonnet
+---
+--- source ---
+local unused = 1; 2
+--- root analysis ---
+object_dependent_depth: none
+local_dependent_depth: none
+errored: false
+--- diagnostics ---
+   ·       ╭─────── unused local: unused
+1  │ local unused = 1; 2 
+2  │  
+--- lir ---
+LocalExpr {
+    binds: [
+        LBind {
+            destruct: Full(
+                LocalId(
+                    0,
+                ),
+            ),
+            value: Num(
+                1.0,
+            ),
+        },
+    ],
+    body: Num(
+        2.0,
+    ),
+}