afa6c51f27
Nix can perform static scope checking, but whenever code is inside a `with` expression, the analysis breaks down, because it can't know statically what's in the attribute set whose attributes were brought into scope. In those cases, Nix has to assume that everything works out. Except it doesnt. Removing `with` from lib/ revealed an undefined variable in an error message. If that doesn't convince you that we're better off without `with`, I can tell you that this PR results in a 3% evaluation performance improvement because Nix can look up local variables by index. This adds up with applications like the module system. Furthermore, removing `with` makes the binding site of each variable obvious, which helps with comprehension.
270 lines
8.3 KiB
Nix
270 lines
8.3 KiB
Nix
/* Collection of functions useful for debugging
|
|
broken nix expressions.
|
|
|
|
* `trace`-like functions take two values, print
|
|
the first to stderr and return the second.
|
|
* `traceVal`-like functions take one argument
|
|
which both printed and returned.
|
|
* `traceSeq`-like functions fully evaluate their
|
|
traced value before printing (not just to “weak
|
|
head normal form” like trace does by default).
|
|
* Functions that end in `-Fn` take an additional
|
|
function as their first argument, which is applied
|
|
to the traced value before it is printed.
|
|
*/
|
|
{ lib }:
|
|
let
|
|
inherit (lib)
|
|
isInt
|
|
attrNames
|
|
isList
|
|
isAttrs
|
|
substring
|
|
addErrorContext
|
|
attrValues
|
|
concatLists
|
|
concatStringsSep
|
|
const
|
|
elem
|
|
generators
|
|
head
|
|
id
|
|
isDerivation
|
|
isFunction
|
|
mapAttrs
|
|
trace;
|
|
in
|
|
|
|
rec {
|
|
|
|
# -- TRACING --
|
|
|
|
/* Conditionally trace the supplied message, based on a predicate.
|
|
|
|
Type: traceIf :: bool -> string -> a -> a
|
|
|
|
Example:
|
|
traceIf true "hello" 3
|
|
trace: hello
|
|
=> 3
|
|
*/
|
|
traceIf =
|
|
# Predicate to check
|
|
pred:
|
|
# Message that should be traced
|
|
msg:
|
|
# Value to return
|
|
x: if pred then trace msg x else x;
|
|
|
|
/* Trace the supplied value after applying a function to it, and
|
|
return the original value.
|
|
|
|
Type: traceValFn :: (a -> b) -> a -> a
|
|
|
|
Example:
|
|
traceValFn (v: "mystring ${v}") "foo"
|
|
trace: mystring foo
|
|
=> "foo"
|
|
*/
|
|
traceValFn =
|
|
# Function to apply
|
|
f:
|
|
# Value to trace and return
|
|
x: trace (f x) x;
|
|
|
|
/* Trace the supplied value and return it.
|
|
|
|
Type: traceVal :: a -> a
|
|
|
|
Example:
|
|
traceVal 42
|
|
# trace: 42
|
|
=> 42
|
|
*/
|
|
traceVal = traceValFn id;
|
|
|
|
/* `builtins.trace`, but the value is `builtins.deepSeq`ed first.
|
|
|
|
Type: traceSeq :: a -> b -> b
|
|
|
|
Example:
|
|
trace { a.b.c = 3; } null
|
|
trace: { a = <CODE>; }
|
|
=> null
|
|
traceSeq { a.b.c = 3; } null
|
|
trace: { a = { b = { c = 3; }; }; }
|
|
=> null
|
|
*/
|
|
traceSeq =
|
|
# The value to trace
|
|
x:
|
|
# The value to return
|
|
y: trace (builtins.deepSeq x x) y;
|
|
|
|
/* Like `traceSeq`, but only evaluate down to depth n.
|
|
This is very useful because lots of `traceSeq` usages
|
|
lead to an infinite recursion.
|
|
|
|
Example:
|
|
traceSeqN 2 { a.b.c = 3; } null
|
|
trace: { a = { b = {…}; }; }
|
|
=> null
|
|
*/
|
|
traceSeqN = depth: x: y:
|
|
let snip = v: if isList v then noQuotes "[…]" v
|
|
else if isAttrs v then noQuotes "{…}" v
|
|
else v;
|
|
noQuotes = str: v: { __pretty = const str; val = v; };
|
|
modify = n: fn: v: if (n == 0) then fn v
|
|
else if isList v then map (modify (n - 1) fn) v
|
|
else if isAttrs v then mapAttrs
|
|
(const (modify (n - 1) fn)) v
|
|
else v;
|
|
in trace (generators.toPretty { allowPrettyValues = true; }
|
|
(modify depth snip x)) y;
|
|
|
|
/* A combination of `traceVal` and `traceSeq` that applies a
|
|
provided function to the value to be traced after `deepSeq`ing
|
|
it.
|
|
*/
|
|
traceValSeqFn =
|
|
# Function to apply
|
|
f:
|
|
# Value to trace
|
|
v: traceValFn f (builtins.deepSeq v v);
|
|
|
|
/* A combination of `traceVal` and `traceSeq`. */
|
|
traceValSeq = traceValSeqFn id;
|
|
|
|
/* A combination of `traceVal` and `traceSeqN` that applies a
|
|
provided function to the value to be traced. */
|
|
traceValSeqNFn =
|
|
# Function to apply
|
|
f:
|
|
depth:
|
|
# Value to trace
|
|
v: traceSeqN depth (f v) v;
|
|
|
|
/* A combination of `traceVal` and `traceSeqN`. */
|
|
traceValSeqN = traceValSeqNFn id;
|
|
|
|
|
|
# -- TESTING --
|
|
|
|
/* Evaluate a set of tests. A test is an attribute set `{expr,
|
|
expected}`, denoting an expression and its expected result. The
|
|
result is a list of failed tests, each represented as `{name,
|
|
expected, actual}`, denoting the attribute name of the failing
|
|
test and its expected and actual results.
|
|
|
|
Used for regression testing of the functions in lib; see
|
|
tests.nix for an example. Only tests having names starting with
|
|
"test" are run.
|
|
|
|
Add attr { tests = ["testName"]; } to run these tests only.
|
|
*/
|
|
runTests =
|
|
# Tests to run
|
|
tests: concatLists (attrValues (mapAttrs (name: test:
|
|
let testsToRun = if tests ? tests then tests.tests else [];
|
|
in if (substring 0 4 name == "test" || elem name testsToRun)
|
|
&& ((testsToRun == []) || elem name tests.tests)
|
|
&& (test.expr != test.expected)
|
|
|
|
then [ { inherit name; expected = test.expected; result = test.expr; } ]
|
|
else [] ) tests));
|
|
|
|
/* Create a test assuming that list elements are `true`.
|
|
|
|
Example:
|
|
{ testX = allTrue [ true ]; }
|
|
*/
|
|
testAllTrue = expr: { inherit expr; expected = map (x: true) expr; };
|
|
|
|
|
|
# -- DEPRECATED --
|
|
|
|
traceShowVal = x: trace (showVal x) x;
|
|
traceShowValMarked = str: x: trace (str + showVal x) x;
|
|
|
|
attrNamesToStr = a:
|
|
trace ( "Warning: `attrNamesToStr` is deprecated "
|
|
+ "and will be removed in the next release. "
|
|
+ "Please use more specific concatenation "
|
|
+ "for your uses (`lib.concat(Map)StringsSep`)." )
|
|
(concatStringsSep "; " (map (x: "${x}=") (attrNames a)));
|
|
|
|
showVal =
|
|
trace ( "Warning: `showVal` is deprecated "
|
|
+ "and will be removed in the next release, "
|
|
+ "please use `traceSeqN`" )
|
|
(let
|
|
modify = v:
|
|
let pr = f: { __pretty = f; val = v; };
|
|
in if isDerivation v then pr
|
|
(drv: "<δ:${drv.name}:${concatStringsSep ","
|
|
(attrNames drv)}>")
|
|
else if [] == v then pr (const "[]")
|
|
else if isList v then pr (l: "[ ${go (head l)}, … ]")
|
|
else if isAttrs v then pr
|
|
(a: "{ ${ concatStringsSep ", " (attrNames a)} }")
|
|
else v;
|
|
go = x: generators.toPretty
|
|
{ allowPrettyValues = true; }
|
|
(modify x);
|
|
in go);
|
|
|
|
traceXMLVal = x:
|
|
trace ( "Warning: `traceXMLVal` is deprecated "
|
|
+ "and will be removed in the next release. "
|
|
+ "Please use `traceValFn builtins.toXML`." )
|
|
(trace (builtins.toXML x) x);
|
|
traceXMLValMarked = str: x:
|
|
trace ( "Warning: `traceXMLValMarked` is deprecated "
|
|
+ "and will be removed in the next release. "
|
|
+ "Please use `traceValFn (x: str + builtins.toXML x)`." )
|
|
(trace (str + builtins.toXML x) x);
|
|
|
|
# trace the arguments passed to function and its result
|
|
# maybe rewrite these functions in a traceCallXml like style. Then one function is enough
|
|
traceCall = n: f: a: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a));
|
|
traceCall2 = n: f: a: b: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b));
|
|
traceCall3 = n: f: a: b: c: let t = n2: x: traceShowValMarked "${n} ${n2}:" x; in t "result" (f (t "arg 1" a) (t "arg 2" b) (t "arg 3" c));
|
|
|
|
traceValIfNot = c: x:
|
|
trace ( "Warning: `traceValIfNot` is deprecated "
|
|
+ "and will be removed in the next release. "
|
|
+ "Please use `if/then/else` and `traceValSeq 1`.")
|
|
(if c x then true else traceSeq (showVal x) false);
|
|
|
|
|
|
addErrorContextToAttrs = attrs:
|
|
trace ( "Warning: `addErrorContextToAttrs` is deprecated "
|
|
+ "and will be removed in the next release. "
|
|
+ "Please use `builtins.addErrorContext` directly." )
|
|
(mapAttrs (a: v: addErrorContext "while evaluating ${a}" v) attrs);
|
|
|
|
# example: (traceCallXml "myfun" id 3) will output something like
|
|
# calling myfun arg 1: 3 result: 3
|
|
# this forces deep evaluation of all arguments and the result!
|
|
# note: if result doesn't evaluate you'll get no trace at all (FIXME)
|
|
# args should be printed in any case
|
|
traceCallXml = a:
|
|
trace ( "Warning: `traceCallXml` is deprecated "
|
|
+ "and will be removed in the next release. "
|
|
+ "Please complain if you use the function regularly." )
|
|
(if !isInt a then
|
|
traceCallXml 1 "calling ${a}\n"
|
|
else
|
|
let nr = a;
|
|
in (str: expr:
|
|
if isFunction expr then
|
|
(arg:
|
|
traceCallXml (builtins.add 1 nr) "${str}\n arg ${builtins.toString nr} is \n ${builtins.toXML (builtins.seq arg arg)}" (expr arg)
|
|
)
|
|
else
|
|
let r = builtins.seq expr expr;
|
|
in trace "${str}\n result:\n${builtins.toXML r}" r
|
|
));
|
|
}
|