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:
parent
81f586e8b8
commit
55ea29fd8c
|
@ -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
|
||||
|
|
|
@ -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 ] ];
|
||||
|
|
Loading…
Reference in a new issue