3
0
Fork 0
forked from mirrors/nixpkgs

lib/generators/toPretty: add evaluation-limit

When having e.g. recursive attr-set, it cannot be printed which is
solved by Nix itself like this:

    $ nix-instantiate --eval -E 'let a.b = 1; a.c = a; in builtins.trace a 1'
    trace: { b = 1; c = <CYCLE>; }
    1

However, `generators.toPretty` tries to evaluate something until it's
done which can result in a spurious `stack-overflow`-error:

    $ nix-instantiate --eval -E 'with import <nixpkgs/lib>; generators.toPretty {  } (mkOption { type = types.str; })'
    error: stack overflow (possible infinite recursion)

Those attr-sets are in fact rather common, one example is shown above, a
`types.<type>`-declaration is such an example. By adding an optional
`depthLimit`-argument, `toPretty` will stop evaluating as soon as the
limit is reached:

    $ nix-instantiate --eval -E 'with import ./Projects/nixpkgs-update-int/lib; generators.toPretty { depthLimit = 2; } (mkOption { type = types.str; })' |xargs -0 echo -e
    "{
      _type = \"option\";
      type = {
        _type = \"option-type\";
        check = <function>;
        deprecationMessage = null;
        description = \"string\";
        emptyValue = { };
        functor = {
          binOp = <unevaluated>;
          name = <unevaluated>;
          payload = <unevaluated>;
          type = <unevaluated>;
          wrapped = <unevaluated>;
        };
        getSubModules = null;
        getSubOptions = <function>;
        merge = <function>;
        name = \"str\";
        nestedTypes = { };
        substSubModules = <function>;
        typeMerge = <function>;
      };
    }"

Optionally, it's also possible to let `toPretty` throw an error if the
limit is exceeded.
This commit is contained in:
Maximilian Bosch 2021-07-23 10:43:44 +02:00
parent 81f586e8b8
commit 55ea29fd8c
No known key found for this signature in database
GPG key ID: 091DBF4D1FC46B8E
2 changed files with 35 additions and 7 deletions

View file

@ -205,13 +205,23 @@ rec {
(This means fn is type Val -> String.) */
allowPrettyValues ? false,
/* If this option is true, the output is indented with newlines for attribute sets and lists */
multiline ? true
}@args: let
go = indent: v: with builtins;
multiline ? true,
/* If this option is not null, `toPretty` will stop evaluating at a certain depth */
depthLimit ? null,
/* If this option is true, an error will be thrown, if a certain given depth is exceeded */
throwOnDepthLimit ? false
}@args:
assert depthLimit != null -> builtins.isInt depthLimit;
assert throwOnDepthLimit -> depthLimit != null;
let
go = depth: indent: v: with builtins;
let isPath = v: typeOf v == "path";
introSpace = if multiline then "\n${indent} " else " ";
outroSpace = if multiline then "\n${indent}" else " ";
in if isInt v then toString v
in if depthLimit != null && depth > depthLimit then
if throwOnDepthLimit then throw "Exceeded maximum eval-depth limit of ${toString depthLimit} while trying to pretty-print with `generators.toPretty'!"
else "<unevaluated>"
else if isInt v then toString v
else if isFloat v then "~${toString v}"
else if isString v then
let
@ -233,7 +243,7 @@ rec {
else if isList v then
if v == [] then "[ ]"
else "[" + introSpace
+ libStr.concatMapStringsSep introSpace (go (indent + " ")) v
+ libStr.concatMapStringsSep introSpace (go (depth + 1) (indent + " ")) v
+ outroSpace + "]"
else if isFunction v then
let fna = lib.functionArgs v;
@ -252,10 +262,10 @@ rec {
else "{" + introSpace
+ libStr.concatStringsSep introSpace (libAttr.mapAttrsToList
(name: value:
"${libStr.escapeNixIdentifier name} = ${go (indent + " ") value};") v)
"${libStr.escapeNixIdentifier name} = ${go (depth + 1) (indent + " ") value};") v)
+ outroSpace + "}"
else abort "generators.toPretty: should never happen (v = ${v})";
in go "";
in go 0 "";
# PLIST handling
toPlist = {}: v: let

View file

@ -529,6 +529,24 @@ runTests {
};
};
testToPrettyLimit =
let
a.b = 1;
a.c = a;
in {
expr = generators.toPretty { depthLimit = 2; } a;
expected = "{\n b = 1;\n c = {\n b = 1;\n c = {\n b = <unevaluated>;\n c = <unevaluated>;\n };\n };\n}";
};
testToPrettyLimitThrow =
let
a.b = 1;
a.c = a;
in {
expr = (builtins.tryEval (generators.toPretty { depthLimit = 2; throwOnDepthLimit = true; } a)).success;
expected = false;
};
testToPrettyMultiline = {
expr = mapAttrs (const (generators.toPretty { })) rec {
list = [ 3 4 [ false ] ];