Skip to content

fix: tryEagerEval does not force unevaluated lazy thunks#949

Merged
stephenamar-db merged 2 commits into
databricks:masterfrom
He-Pin:fix/tryEagerEval-lazy-thunks
Jun 18, 2026
Merged

fix: tryEagerEval does not force unevaluated lazy thunks#949
stephenamar-db merged 2 commits into
databricks:masterfrom
He-Pin:fix/tryEagerEval-lazy-thunks

Conversation

@He-Pin

@He-Pin He-Pin commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Motivation

The tryEagerEval optimization forced evaluation of lazy thunks by calling binding.value on scope bindings, violating Jsonnet's lazy evaluation semantics. Unused bindings with side effects (e.g., error, std.trace) were evaluated even when their results were never needed.

Modification

  • Changed resolveAsDouble to pattern-match on the binding directly instead of forcing .value
  • Only already-evaluated Val.Num bindings are used for eager evaluation
  • Unevaluated thunks return Double.NaN (skip optimization), preserving lazy semantics
  • Added test verifying unused error bindings are not forced through eager eval paths

Result

local a = error "should not be evaluated"; local b = a + 1; if false then b else 0 now correctly returns 0 without evaluating a, matching go-jsonnet and jrsonnet lazy evaluation semantics.

References

Expression go-jsonnet v0.22.0 jrsonnet v0.5.0-pre98 sjsonnet (before) sjsonnet (after)
local f(x) = x; local y = f(error "boom"); if true then 42 else y 42 42 ERROR (forced thunk) ❌ 42
local a = error "x"; if false then a + 1 else 0 0 0 0 0

@stephenamar-db

Copy link
Copy Markdown
Collaborator

rebase

@He-Pin He-Pin force-pushed the fix/tryEagerEval-lazy-thunks branch 2 times, most recently from 6c87e96 to ba16bd2 Compare June 17, 2026 19:33
Motivation:
The resolveAsDouble optimization called binding.value on scope
bindings, which forced lazy thunk evaluation even when the result
would be discarded (e.g., dead branches). This violated Jsonnet's
lazy evaluation semantics — side effects like std.trace or error
in unused bindings would execute when they should not.

Modification:
In resolveAsDouble, pattern match on the binding directly instead
of calling binding.value. Only Val.Num (already evaluated numbers)
are used; unevaluated lazy thunks return NaN (skip), preserving
lazy semantics.

Result:
`local a = error "x"; local b = a + 1; if false then b else 0`
now returns 0 without evaluating 'a', matching go-jsonnet and
jrsonnet behavior.
@He-Pin He-Pin force-pushed the fix/tryEagerEval-lazy-thunks branch from ba16bd2 to 039bb95 Compare June 18, 2026 00:12
@stephenamar-db stephenamar-db merged commit 4dba501 into databricks:master Jun 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants