Wrapping a value with builtins.break breaks most builtins #1165

Closed
opened 2026-03-20 23:56:40 +00:00 by blokyk · 4 comments

Describe the bug

Wrapping a value with builtins.break, which should be a transparent operation (like builtins.trace is), results in it being treated as a thunk by most builtins, instead of being seen as the underlying value.

Steps To Reproduce

Multiple examples:

nix-repl> builtins.isAttrs (builtins.break {})
false

nix-repl> builtins.mapAttrs (n: v: v) (break {})
error:
        while calling the 'mapAttrs' builtin
         at «string»:1:1:
            1| builtins.mapAttrs (n: v: v) (break {})
             | ^

        while evaluating the second argument passed to builtins.mapAttrs

       error: expected a set but found a thunk: «thunk»

nix-repl> map (x: x) (break [ 1 ])
error:
        while calling the 'map' builtin
         at «string»:1:1:
            1| builtins.map (x: x) (break [ 1 ])
             | ^

        while evaluating the second argument passed to builtins.map

       error: expected a list but found a thunk: «thunk»

Expected behavior

A call to builtins.break is completely transparent to its consumer, such that ∀f,e, f (break e) == f e. This is the way that, for example, builtins.trace and builtins.warn work (e.g. isAttrs (trace "foo" {}) == true).

nix --version output

nix (Lix, like Nix) 2.94.0
System type: x86_64-linux
Additional system types: i686-linux, x86_64-v1-linux, x86_64-v2-linux, x86_64-v3-linux, x86_64-v4-linux
Features: gc, signed-caches
System configuration file: /etc/nix/nix.conf
User configuration files: /home/blokyk/.config/nix/nix.conf:/etc/xdg/xdg-ubuntu-xorg/nix/nix.conf:/etc/xdg/nix/nix.conf
Store directory: /nix/store
State directory: /nix/var/nix
Data directory: /nix/store/vr4a39d4bw01793jl4qap839rlvmc143-lix-2.94.0/share

Additional context

From what i can tell, this is caused by prim_break not calling forceValue on its argument. Adding that call currently changes the output of tests repl_characterization/stack_vars and repl_characterization/regression_9917; this seems to be because it gives a different call stack:

Screenshot From 2026-03-21 00-30-15

(on the left, the final call stack with current nix; on the right, the final call stack with forceValue(args[0]) added in prim_break)

## Describe the bug Wrapping a value with `builtins.break`, which should be a transparent operation (like `builtins.trace` is), results in it being treated as a thunk by most builtins, instead of being seen as the underlying value. ## Steps To Reproduce Multiple examples: ```nix nix-repl> builtins.isAttrs (builtins.break {}) false nix-repl> builtins.mapAttrs (n: v: v) (break {}) error: … while calling the 'mapAttrs' builtin at «string»:1:1: 1| builtins.mapAttrs (n: v: v) (break {}) | ^ … while evaluating the second argument passed to builtins.mapAttrs error: expected a set but found a thunk: «thunk» nix-repl> map (x: x) (break [ 1 ]) error: … while calling the 'map' builtin at «string»:1:1: 1| builtins.map (x: x) (break [ 1 ]) | ^ … while evaluating the second argument passed to builtins.map error: expected a list but found a thunk: «thunk» ``` ## Expected behavior A call to `builtins.break` is completely transparent to its consumer, such that ∀f,e, `f (break e) == f e`. This is the way that, for example, `builtins.trace` and `builtins.warn` work (e.g. `isAttrs (trace "foo" {}) == true`). ## `nix --version` output ``` nix (Lix, like Nix) 2.94.0 System type: x86_64-linux Additional system types: i686-linux, x86_64-v1-linux, x86_64-v2-linux, x86_64-v3-linux, x86_64-v4-linux Features: gc, signed-caches System configuration file: /etc/nix/nix.conf User configuration files: /home/blokyk/.config/nix/nix.conf:/etc/xdg/xdg-ubuntu-xorg/nix/nix.conf:/etc/xdg/nix/nix.conf Store directory: /nix/store State directory: /nix/var/nix Data directory: /nix/store/vr4a39d4bw01793jl4qap839rlvmc143-lix-2.94.0/share ``` ## Additional context From what i can tell, this is caused by [`prim_break`](https://git.lix.systems/lix-project/lix/src/branch/83bca23d4a/lix/libexpr/primops.cc#L647) not calling `forceValue` on its argument. Adding that call currently changes the output of tests [`repl_characterization/stack_vars`](https://git.lix.systems/lix-project/lix/src/commit/7d361f1a82/tests/functional/repl_characterization/data/stack_vars.test) and [`repl_characterization/regression_9917`](https://git.lix.systems/lix-project/lix/src/commit/8a8715af89/tests/functional/repl_characterization/data/regression_9917.test); this seems to be because it gives a different call stack: ![Screenshot From 2026-03-21 00-30-15](/attachments/bb0e4afa-3f1f-4ba9-ba49-a75de9fca74e) (on the left, the final call stack with current nix; on the right, the final call stack with `forceValue(args[0])` added in `prim_break`)
Owner

yup, that's a bug. break returns its input unchanged instead of forcing it first, as would be required by our invariants. it's been like this forever :dragnupsidedown:

yup, that's a bug. `break` returns its input unchanged instead of forcing it first, as would be required by our invariants. it's been like this forever :dragnupsidedown:
Author

Yeah, i thought this was known for a while but when i mentioned it to piegames on matrix they asked me to fill it here

Yeah, i thought this was known for a while but when i mentioned it to piegames on matrix they asked me to fill it here
Author

fyi so there's no duplicate work: i'm making a cl for this (again it's basically just one line), including a test to make sure we don't regress on this (though i'll probably only finish it tomorrow, it's kinda late here)

fyi so there's no duplicate work: i'm making a cl for this (again it's basically just one line), including a test to make sure we don't regress on this (though i'll probably only finish it tomorrow, it's kinda late here)
Member

This issue was mentioned on Gerrit on the following CLs:

  • commit message in cl/5422 ("libexpr/primops: make break force its argument")
<!-- GERRIT_LINKBOT: {"cls": [{"backlink": "https://gerrit.lix.systems/c/lix/+/5422", "number": 5422, "kind": "commit message"}], "cl_meta": {"5422": {"change_title": "libexpr/primops: make break force its argument"}}} --> This issue was mentioned on Gerrit on the following CLs: * commit message in [cl/5422](https://gerrit.lix.systems/c/lix/+/5422) ("libexpr/primops: make break force its argument")
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lix-project/lix#1165
No description provided.