Trace the eval.cc functions with Tracy

We're leveraging the ZoneTransientN macro to send dynamic strings
containing the expression type, file and position in that file of the
expression Nix is currently evaluating.

We had to add a new showExprType method to the Expr class to get a
const string containing the name of an expression.
This commit is contained in:
Félix Baylac Jacqué 2024-02-06 18:04:23 +01:00
parent 0905dea80f
commit f2e832d0f8
3 changed files with 53 additions and 2 deletions

View File

@ -55,6 +55,11 @@
#endif #endif
#define TRACY_TRACE(evalstate, expr) \
std::ostringstream tracyss; \
tracyss << evalstate.positions[(expr)->getPos()] << " " << (expr)->showExprType(); \
ZoneTransientN(nix, tracyss.str().c_str(), true);
using json = nlohmann::json; using json = nlohmann::json;
namespace nix { namespace nix {
@ -1204,29 +1209,34 @@ void Expr::eval(EvalState & state, Env & env, Value & v)
void ExprInt::eval(EvalState & state, Env & env, Value & v) void ExprInt::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v = this->v; v = this->v;
} }
void ExprFloat::eval(EvalState & state, Env & env, Value & v) void ExprFloat::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v = this->v; v = this->v;
} }
void ExprString::eval(EvalState & state, Env & env, Value & v) void ExprString::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v = this->v; v = this->v;
} }
void ExprPath::eval(EvalState & state, Env & env, Value & v) void ExprPath::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v = this->v; v = this->v;
} }
void ExprAttrs::eval(EvalState & state, Env & env, Value & v) void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v.mkAttrs(state.buildBindings(attrs.size() + dynamicAttrs.size()).finish()); v.mkAttrs(state.buildBindings(attrs.size() + dynamicAttrs.size()).finish());
auto dynamicEnv = &env; auto dynamicEnv = &env;
@ -1311,6 +1321,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
void ExprLet::eval(EvalState & state, Env & env, Value & v) void ExprLet::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
/* Create a new environment that contains the attributes in this /* Create a new environment that contains the attributes in this
`let'. */ `let'. */
Env & env2(state.allocEnv(attrs->attrs.size())); Env & env2(state.allocEnv(attrs->attrs.size()));
@ -1329,6 +1340,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
void ExprList::eval(EvalState & state, Env & env, Value & v) void ExprList::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
state.mkList(v, elems.size()); state.mkList(v, elems.size());
for (auto [n, v2] : enumerate(v.listItems())) for (auto [n, v2] : enumerate(v.listItems()))
const_cast<Value * &>(v2) = elems[n]->maybeThunk(state, env); const_cast<Value * &>(v2) = elems[n]->maybeThunk(state, env);
@ -1337,6 +1349,7 @@ void ExprList::eval(EvalState & state, Env & env, Value & v)
Value * ExprList::maybeThunk(EvalState & state, Env & env) Value * ExprList::maybeThunk(EvalState & state, Env & env)
{ {
TRACY_TRACE(state, this);
if (elems.empty()) { if (elems.empty()) {
return &state.vEmptyList; return &state.vEmptyList;
} }
@ -1346,6 +1359,7 @@ Value * ExprList::maybeThunk(EvalState & state, Env & env)
void ExprVar::eval(EvalState & state, Env & env, Value & v) void ExprVar::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
Value * v2 = state.lookupVar(&env, *this, false); Value * v2 = state.lookupVar(&env, *this, false);
state.forceValue(*v2, pos); state.forceValue(*v2, pos);
v = *v2; v = *v2;
@ -1373,7 +1387,7 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
void ExprSelect::eval(EvalState & state, Env & env, Value & v) void ExprSelect::eval(EvalState & state, Env & env, Value & v)
{ {
ZoneScopedN("select"); TRACY_TRACE(state, this)
Value vTmp; Value vTmp;
PosIdx pos2; PosIdx pos2;
Value * vAttrs = &vTmp; Value * vAttrs = &vTmp;
@ -1438,6 +1452,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this)
Value vTmp; Value vTmp;
Value * vAttrs = &vTmp; Value * vAttrs = &vTmp;
@ -1463,6 +1478,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
void ExprLambda::eval(EvalState & state, Env & env, Value & v) void ExprLambda::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this)
v.mkLambda(&env, this); v.mkLambda(&env, this);
} }
@ -1720,6 +1736,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
void ExprCall::eval(EvalState & state, Env & env, Value & v) void ExprCall::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
Value vFun; Value vFun;
fun->eval(state, env, vFun); fun->eval(state, env, vFun);
@ -1797,6 +1814,7 @@ https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbo
void ExprWith::eval(EvalState & state, Env & env, Value & v) void ExprWith::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
Env & env2(state.allocEnv(1)); Env & env2(state.allocEnv(1));
env2.up = &env; env2.up = &env;
env2.values[0] = attrs->maybeThunk(state, env); env2.values[0] = attrs->maybeThunk(state, env);
@ -1807,6 +1825,7 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
void ExprIf::eval(EvalState & state, Env & env, Value & v) void ExprIf::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
// We cheat in the parser, and pass the position of the condition as the position of the if itself. // We cheat in the parser, and pass the position of the condition as the position of the if itself.
(state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v); (state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
} }
@ -1814,6 +1833,7 @@ void ExprIf::eval(EvalState & state, Env & env, Value & v)
void ExprAssert::eval(EvalState & state, Env & env, Value & v) void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) { if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
std::ostringstream out; std::ostringstream out;
cond->show(state.symbols, out); cond->show(state.symbols, out);
@ -1825,12 +1845,14 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
void ExprOpNot::eval(EvalState & state, Env & env, Value & v) void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v.mkBool(!state.evalBool(env, e, getPos(), "in the argument of the not operator")); // XXX: FIXME: ! v.mkBool(!state.evalBool(env, e, getPos(), "in the argument of the not operator")); // XXX: FIXME: !
} }
void ExprOpEq::eval(EvalState & state, Env & env, Value & v) void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality")); v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality"));
@ -1839,6 +1861,7 @@ void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
void ExprOpNEq::eval(EvalState & state, Env & env, Value & v) void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality")); v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality"));
@ -1847,6 +1870,7 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v) void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator")); v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator"));
} }
@ -1859,12 +1883,14 @@ void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v) void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator")); v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
} }
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
Value v1, v2; Value v1, v2;
state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator"); state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator"); state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
@ -1903,6 +1929,7 @@ void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v) void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
Value * lists[2] = { &v1, &v2 }; Value * lists[2] = { &v1, &v2 };
@ -1941,6 +1968,7 @@ void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Po
void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
NixStringContext context; NixStringContext context;
std::vector<BackedStringView> s; std::vector<BackedStringView> s;
size_t sSize = 0; size_t sSize = 0;
@ -2033,12 +2061,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
void ExprPos::eval(EvalState & state, Env & env, Value & v) void ExprPos::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
state.mkPos(v, pos); state.mkPos(v, pos);
} }
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v) void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
{ {
TRACY_TRACE(state, this);
state.error("infinite recursion encountered") state.error("infinite recursion encountered")
.debugThrow<InfiniteRecursionError>(); .debugThrow<InfiniteRecursionError>();
} }

View File

@ -13,7 +13,7 @@ libexpr_SOURCES := \
$(d)/parser-tab.cc \ $(d)/parser-tab.cc \
$(d)/tracy/public/TracyClient.cpp $(d)/tracy/public/TracyClient.cpp
libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libmain -I src/libexpr -I src/libexpr/tracy/public libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libmain -I src/libexpr -I src/libexpr/tracy/public -DTRACY_ENABLE=1
libexpr_LIBS = libutil libstore libfetchers libexpr_LIBS = libutil libstore libfetchers
@ -49,3 +49,4 @@ $(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh
$(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/flake/call-flake.nix.gen.hh $(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/flake/call-flake.nix.gen.hh
$(buildprefix)src/libexpr/primops/fromTOML.o: ERROR_SWITCH_ENUM = $(buildprefix)src/libexpr/primops/fromTOML.o: ERROR_SWITCH_ENUM =
$(buildprefix)src/libexpr/tracy/public/TracyClient.o: ERROR_SWITCH_ENUM =

View File

@ -156,6 +156,7 @@ struct Expr
virtual Value * maybeThunk(EvalState & state, Env & env); virtual Value * maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol name); virtual void setName(Symbol name);
virtual PosIdx getPos() const { return noPos; } virtual PosIdx getPos() const { return noPos; }
virtual const char* showExprType() const { return "undefined"; }
}; };
#define COMMON_METHODS \ #define COMMON_METHODS \
@ -168,6 +169,7 @@ struct ExprInt : Expr
Value v; Value v;
ExprInt(NixInt n) { v.mkInt(n); }; ExprInt(NixInt n) { v.mkInt(n); };
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
const char* showExprType() const { return "int"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -176,6 +178,7 @@ struct ExprFloat : Expr
Value v; Value v;
ExprFloat(NixFloat nf) { v.mkFloat(nf); }; ExprFloat(NixFloat nf) { v.mkFloat(nf); };
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
const char* showExprType() const { return "float"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -185,6 +188,7 @@ struct ExprString : Expr
Value v; Value v;
ExprString(std::string &&s) : s(std::move(s)) { v.mkString(this->s.data()); }; ExprString(std::string &&s) : s(std::move(s)) { v.mkString(this->s.data()); };
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
const char* showExprType() const { return "string"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -198,6 +202,7 @@ struct ExprPath : Expr
v.mkPath(&*accessor, this->s.c_str()); v.mkPath(&*accessor, this->s.c_str());
} }
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
const char* showExprType() const { return "path"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -229,6 +234,7 @@ struct ExprVar : Expr
ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { }; ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { };
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "var"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -240,6 +246,7 @@ struct ExprSelect : Expr
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { }; ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "select"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -249,6 +256,7 @@ struct ExprOpHasAttr : Expr
AttrPath attrPath; AttrPath attrPath;
ExprOpHasAttr(Expr * e, AttrPath attrPath) : e(e), attrPath(std::move(attrPath)) { }; ExprOpHasAttr(Expr * e, AttrPath attrPath) : e(e), attrPath(std::move(attrPath)) { };
PosIdx getPos() const override { return e->getPos(); } PosIdx getPos() const override { return e->getPos(); }
const char* showExprType() const { return "op_has_attr"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -278,6 +286,7 @@ struct ExprAttrs : Expr
ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { }; ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { };
ExprAttrs() : recursive(false) { }; ExprAttrs() : recursive(false) { };
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "attrs"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -285,6 +294,7 @@ struct ExprList : Expr
{ {
std::vector<Expr *> elems; std::vector<Expr *> elems;
ExprList() { }; ExprList() { };
const char* showExprType() const { return "list"; }
COMMON_METHODS COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
@ -345,6 +355,7 @@ struct ExprLambda : Expr
std::string showNamePos(const EvalState & state) const; std::string showNamePos(const EvalState & state) const;
inline bool hasFormals() const { return formals != nullptr; } inline bool hasFormals() const { return formals != nullptr; }
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "lambda"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -357,6 +368,7 @@ struct ExprCall : Expr
: fun(fun), args(args), pos(pos) : fun(fun), args(args), pos(pos)
{ } { }
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "call"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -365,6 +377,7 @@ struct ExprLet : Expr
ExprAttrs * attrs; ExprAttrs * attrs;
Expr * body; Expr * body;
ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { }; ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { };
const char* showExprType() const { return "let"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -376,6 +389,7 @@ struct ExprWith : Expr
ExprWith * parentWith; ExprWith * parentWith;
ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "with"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -385,6 +399,7 @@ struct ExprIf : Expr
Expr * cond, * then, * else_; Expr * cond, * then, * else_;
ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { }; ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "if"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -394,6 +409,7 @@ struct ExprAssert : Expr
Expr * cond, * body; Expr * cond, * body;
ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "assert"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -422,6 +438,7 @@ struct ExprOpNot : Expr
} \ } \
void eval(EvalState & state, Env & env, Value & v) override; \ void eval(EvalState & state, Env & env, Value & v) override; \
PosIdx getPos() const override { return pos; } \ PosIdx getPos() const override { return pos; } \
const char* showExprType() const { return #name; } \
}; };
MakeBinOp(ExprOpEq, "==") MakeBinOp(ExprOpEq, "==")
@ -440,6 +457,7 @@ struct ExprConcatStrings : Expr
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *>> * es) ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *>> * es)
: pos(pos), forceString(forceString), es(es) { }; : pos(pos), forceString(forceString), es(es) { };
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "concat_strings"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -448,6 +466,7 @@ struct ExprPos : Expr
PosIdx pos; PosIdx pos;
ExprPos(const PosIdx & pos) : pos(pos) { }; ExprPos(const PosIdx & pos) : pos(pos) { };
PosIdx getPos() const override { return pos; } PosIdx getPos() const override { return pos; }
const char* showExprType() const { return "pos"; }
COMMON_METHODS COMMON_METHODS
}; };
@ -457,6 +476,7 @@ struct ExprBlackHole : Expr
void show(const SymbolTable & symbols, std::ostream & str) const override {} void show(const SymbolTable & symbols, std::ostream & str) const override {}
void eval(EvalState & state, Env & env, Value & v) override; void eval(EvalState & state, Env & env, Value & v) override;
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override {} void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override {}
const char* showExprType() const { return "blackhole"; }
}; };
extern ExprBlackHole eBlackHole; extern ExprBlackHole eBlackHole;