diff --git a/doc/manual/rl-next/pretty-printing.md b/doc/manual/rl-next/pretty-printing.md new file mode 100644 index 000000000..f7953f9ff --- /dev/null +++ b/doc/manual/rl-next/pretty-printing.md @@ -0,0 +1,58 @@ +--- +synopsis: "Eliminate some pretty-printing surprises" +cls: [1616, 1617, 1618] +prs: [11100] +credits: [alois31, roberth] +category: Improvements +--- + +Some inconsistent and surprising behaviours have been eliminated from the pretty-printing used by the REPL and `nix eval`: +* Lists and attribute sets that contain only a single item without nested structures are no longer sometimes inappropriately indented in the REPL, depending on internal state of the evaluator. +* Empty attribute sets and derivations are no longer shown as `«repeated»`, since they are always cheap to print. + This matches the existing behaviour of `nix-instantiate` on empty attribute sets. + Empty lists were never printed as `«repeated»` already. +* The REPL by default does not print nested attribute sets and lists, and indicates elided items with an ellipsis. + Previously, the ellipsis was printed even when the structure was empty, so that such items do not in fact exist. + Since this behaviour was confusing, it does not happen any more. + +Before: +``` +nix-repl> :p let x = 1 + 2; in [ [ x ] [ x ] ] +[ + [ + 3 + ] + [ 3 ] +] + +nix-repl> let inherit (import { }) hello; in [ hello hello ] +[ + «derivation /nix/store/fqs92lzychkm6p37j7fnj4d65nq9fzla-hello-2.12.1.drv» + «repeated» +] + +nix-repl> let x = {}; in [ x ] +[ + { ... } +] +``` + +After: +``` +nix-repl> :p let x = 1 + 2; in [ [ x ] [ x ] ] +[ + [ 3 ] + [ 3 ] +] + +nix-repl> let inherit (import { }) hello; in [ hello hello ] +[ + «derivation /nix/store/fqs92lzychkm6p37j7fnj4d65nq9fzla-hello-2.12.1.drv» + «derivation /nix/store/fqs92lzychkm6p37j7fnj4d65nq9fzla-hello-2.12.1.drv» +] + +nix-repl> let x = {}; in [ x ] +[ + { } +] +``` diff --git a/src/libexpr/print.cc b/src/libexpr/print.cc index 87db004b2..b7ff50f48 100644 --- a/src/libexpr/print.cc +++ b/src/libexpr/print.cc @@ -264,22 +264,24 @@ private: return true; } + if (options.force) { + // The item is going to be forced during printing anyway, but we need its type now. + state.forceValue(*item, item->determinePos(noPos)); + } + // Pretty-print single-item attrsets only if they contain nested // structures. auto itemType = item->type(); - return itemType == nList || itemType == nAttrs || itemType == nThunk; + return itemType == nList || itemType == nAttrs; } void printAttrs(Value & v, size_t depth) { - if (seen && !seen->insert(v.attrs).second) { - printRepeated(); - return; - } - if (options.force && options.derivationPaths && state.isDerivation(v)) { printDerivation(v); - } else if (depth < options.maxDepth) { + } else if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second) { + printRepeated(); + } else if (depth < options.maxDepth || v.attrs->empty()) { increaseIndent(); output << "{"; @@ -335,10 +337,15 @@ private: return true; } + if (options.force) { + // The item is going to be forced during printing anyway, but we need its type now. + state.forceValue(*item, item->determinePos(noPos)); + } + // Pretty-print single-item lists only if they contain nested // structures. auto itemType = item->type(); - return itemType == nList || itemType == nAttrs || itemType == nThunk; + return itemType == nList || itemType == nAttrs; } void printList(Value & v, size_t depth) @@ -348,7 +355,7 @@ private: return; } - if (depth < options.maxDepth) { + if (depth < options.maxDepth || v.listSize() == 0) { increaseIndent(); output << "["; auto listItems = v.listItems(); diff --git a/tests/functional/repl_characterization/data/idempotent.test b/tests/functional/repl_characterization/data/idempotent.test new file mode 100644 index 000000000..4ab087d45 --- /dev/null +++ b/tests/functional/repl_characterization/data/idempotent.test @@ -0,0 +1,13 @@ +A previously unforced thunk in an attribute set does not lead to indentation when it won't evaluate to a nested structure: + nix-repl> :p let x = 1 + 2; in [ { inherit x; } { inherit x; } ] + [ + { x = 3; } + { x = 3; } + ] + +Same for a list: + nix-repl> :p let x = 1 + 2; in [ [ x ] [ x ] ] + [ + [ 3 ] + [ 3 ] + ] diff --git a/tests/functional/repl_characterization/repl_characterization.cc b/tests/functional/repl_characterization/repl_characterization.cc index c91d6c1e3..f2cf0ca3d 100644 --- a/tests/functional/repl_characterization/repl_characterization.cc +++ b/tests/functional/repl_characterization/repl_characterization.cc @@ -185,5 +185,6 @@ REPL_TEST(repl_overlays_error); REPL_TEST(repl_printing); REPL_TEST(stack_vars); REPL_TEST(errors); +REPL_TEST(idempotent); }; // namespace nix diff --git a/tests/unit/libexpr/value/print.cc b/tests/unit/libexpr/value/print.cc index cdbc8f8f9..ddf8b8b99 100644 --- a/tests/unit/libexpr/value/print.cc +++ b/tests/unit/libexpr/value/print.cc @@ -193,6 +193,9 @@ TEST_F(ValuePrintingTests, vBlackhole) TEST_F(ValuePrintingTests, depthAttrs) { + Value vZero; + vZero.mkInt(0); + Value vOne; vOne.mkInt(1); @@ -203,10 +206,16 @@ TEST_F(ValuePrintingTests, depthAttrs) Value vAttrsEmpty; vAttrsEmpty.mkAttrs(builderEmpty.finish()); + BindingsBuilder builderNested(state, state.allocBindings(1)); + builderNested.insert(state.symbols.create("zero"), &vZero); + Value vAttrsNested; + vAttrsNested.mkAttrs(builderNested.finish()); + BindingsBuilder builder(state, state.allocBindings(10)); builder.insert(state.symbols.create("one"), &vOne); builder.insert(state.symbols.create("two"), &vTwo); - builder.insert(state.symbols.create("nested"), &vAttrsEmpty); + builder.insert(state.symbols.create("empty"), &vAttrsEmpty); + builder.insert(state.symbols.create("nested"), &vAttrsNested); Value vAttrs; vAttrs.mkAttrs(builder.finish()); @@ -220,9 +229,9 @@ TEST_F(ValuePrintingTests, depthAttrs) vNested.mkAttrs(builder2.finish()); test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions { .maxDepth = 1 }); - test(vNested, "{ nested = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 }); - test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 }); - test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 }); + test(vNested, "{ nested = { empty = { }; nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 }); + test(vNested, "{ nested = { empty = { }; nested = { zero = 0; }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 }); + test(vNested, "{ nested = { empty = { }; nested = { zero = 0; }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 }); } TEST_F(ValuePrintingTests, depthList) @@ -641,20 +650,24 @@ TEST_F(ValuePrintingTests, ansiColorsBlackhole) TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated) { - BindingsBuilder emptyBuilder(state, state.allocBindings(1)); + Value vZero; + vZero.mkInt(0); - Value vEmpty; - vEmpty.mkAttrs(emptyBuilder.finish()); + BindingsBuilder innerBuilder(state, state.allocBindings(1)); + innerBuilder.insert(state.symbols.create("x"), &vZero); + + Value vInner; + vInner.mkAttrs(innerBuilder.finish()); BindingsBuilder builder(state, state.allocBindings(10)); - builder.insert(state.symbols.create("a"), &vEmpty); - builder.insert(state.symbols.create("b"), &vEmpty); + builder.insert(state.symbols.create("a"), &vInner); + builder.insert(state.symbols.create("b"), &vInner); Value vAttrs; vAttrs.mkAttrs(builder.finish()); test(vAttrs, - "{ a = { }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }", + "{ a = { x = " ANSI_CYAN "0" ANSI_NORMAL "; }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }", PrintOptions { .ansiColors = true }); @@ -662,19 +675,23 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated) TEST_F(ValuePrintingTests, ansiColorsListRepeated) { - BindingsBuilder emptyBuilder(state, state.allocBindings(1)); + Value vZero; + vZero.mkInt(0); - Value vEmpty; - vEmpty.mkAttrs(emptyBuilder.finish()); + BindingsBuilder innerBuilder(state, state.allocBindings(1)); + innerBuilder.insert(state.symbols.create("x"), &vZero); + + Value vInner; + vInner.mkAttrs(innerBuilder.finish()); Value vList; state.mkList(vList, 3); - vList.bigList.elems[0] = &vEmpty; - vList.bigList.elems[1] = &vEmpty; + vList.bigList.elems[0] = &vInner; + vList.bigList.elems[1] = &vInner; vList.bigList.size = 2; test(vList, - "[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]", + "[ { x = " ANSI_CYAN "0" ANSI_NORMAL "; } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]", PrintOptions { .ansiColors = true }); @@ -682,20 +699,24 @@ TEST_F(ValuePrintingTests, ansiColorsListRepeated) TEST_F(ValuePrintingTests, listRepeated) { - BindingsBuilder emptyBuilder(state, state.allocBindings(1)); + Value vZero; + vZero.mkInt(0); - Value vEmpty; - vEmpty.mkAttrs(emptyBuilder.finish()); + BindingsBuilder innerBuilder(state, state.allocBindings(1)); + innerBuilder.insert(state.symbols.create("x"), &vZero); + + Value vInner; + vInner.mkAttrs(innerBuilder.finish()); Value vList; state.mkList(vList, 3); - vList.bigList.elems[0] = &vEmpty; - vList.bigList.elems[1] = &vEmpty; + vList.bigList.elems[0] = &vInner; + vList.bigList.elems[1] = &vInner; vList.bigList.size = 2; - test(vList, "[ { } «repeated» ]", PrintOptions { }); + test(vList, "[ { x = 0; } «repeated» ]", PrintOptions { }); test(vList, - "[ { } { } ]", + "[ { x = 0; } { x = 0; } ]", PrintOptions { .trackRepeated = false });