diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 3db4bb66f..3d8ee9934 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -129,6 +129,18 @@ string showType(const Value & v) } +Symbol getName(const AttrName & name, EvalState & state, Env & env) { + if (name.symbol.set()) { + return name.symbol; + } else { + Value nameValue; + name.expr->eval(state, env, nameValue); + state.forceStringNoCtx(nameValue); + return state.symbols.create(nameValue.string.s); + } +} + + EvalState::EvalState() : sWith(symbols.create("")) , sOutPath(symbols.create("outPath")) @@ -247,6 +259,11 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, con throw EvalError(format(s) % s2 % s3); } +LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, const Pos & p1, const Pos & p2)) +{ + throw EvalError(format(s) % sym % p1 % p2); +} + LocalNoInlineNoReturn(void throwTypeError(const char * s)) { throw TypeError(s); @@ -557,12 +574,14 @@ void ExprPath::eval(EvalState & state, Env & env, Value & v) void ExprAttrs::eval(EvalState & state, Env & env, Value & v) { state.mkAttrs(v, attrs.size()); + Env *dynamicEnv = &env; if (recursive) { /* Create a new environment that contains the attributes in this `rec'. */ Env & env2(state.allocEnv(attrs.size())); env2.up = &env; + dynamicEnv = &env2; AttrDefs::iterator overrides = attrs.find(state.sOverrides); bool hasOverrides = overrides != attrs.end(); @@ -605,9 +624,24 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v) } } - else { + else foreach (AttrDefs::iterator, i, attrs) v.attrs->push_back(Attr(i->first, i->second.e->maybeThunk(state, env), &i->second.pos)); + + /* dynamic attrs apply *after* rec and __overrides */ + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) { + Value nameVal; + i->nameExpr->eval(state, *dynamicEnv, nameVal); + state.forceStringNoCtx(nameVal); + Symbol nameSym = state.symbols.create(nameVal.string.s); + Bindings::iterator j = v.attrs->find(nameSym); + if (j != v.attrs->end()) + throwEvalError("dynamic attribute `%1%' at %2% already defined at %3%", nameSym, i->pos, *j->pos); + + i->valueExpr->setName(nameSym); + /* Keep sorted order so find can catch duplicates */ + v.attrs->insert(lower_bound(v.attrs->begin(), v.attrs->end(), Attr(nameSym, 0)), + Attr(nameSym, i->valueExpr->maybeThunk(state, *dynamicEnv), &i->pos)); } } @@ -661,17 +695,18 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) foreach (AttrPath::const_iterator, i, attrPath) { nrLookups++; Bindings::iterator j; + Symbol name = getName(*i, state, env); if (def) { state.forceValue(*vAttrs); if (vAttrs->type != tAttrs || - (j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end()) + (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { def->eval(state, env, v); return; } } else { state.forceAttrs(*vAttrs); - if ((j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end()) + if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) throwEvalError("attribute `%1%' missing", showAttrPath(attrPath)); } vAttrs = j->value; @@ -702,8 +737,9 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v) foreach (AttrPath::const_iterator, i, attrPath) { state.forceValue(*vAttrs); Bindings::iterator j; + Symbol name = getName(*i, state, env); if (vAttrs->type != tAttrs || - (j = vAttrs->attrs->find(*i)) == vAttrs->attrs->end()) + (j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { mkBool(v, false); return; @@ -908,6 +944,17 @@ void ExprOpNot::eval(EvalState & state, Env & env, Value & v) } +void ExprBuiltin::eval(EvalState & state, Env & env, Value & v) +{ + // Not a hot path at all, but would be nice to access state.baseEnv directly + Env *baseEnv = &env; + while (baseEnv->up) baseEnv = baseEnv->up; + Bindings::iterator binding = baseEnv->values[0]->attrs->find(name); + assert(binding != baseEnv->values[0]->attrs->end()); + v = *binding->value; +} + + void ExprOpEq::eval(EvalState & state, Env & env, Value & v) { Value v1; e1->eval(state, env, v1); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index f4b4295e2..9f0bc2630 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -61,6 +61,8 @@ void ExprAttrs::show(std::ostream & str) str << "inherit " << i->first << " " << "; "; else str << i->first << " = " << *i->second.e << "; "; + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) + str << "\"${" << *i->nameExpr << "}\" = " << *i->valueExpr << "; "; str << "}"; } @@ -121,6 +123,11 @@ void ExprOpNot::show(std::ostream & str) str << "! " << *e; } +void ExprBuiltin::show(std::ostream & str) +{ + str << "builtins." << name; +} + void ExprConcatStrings::show(std::ostream & str) { bool first = true; @@ -148,12 +155,19 @@ std::ostream & operator << (std::ostream & str, const Pos & pos) string showAttrPath(const AttrPath & attrPath) { - string s; + std::ostringstream out; + bool first = true; foreach (AttrPath::const_iterator, i, attrPath) { - if (!s.empty()) s += '.'; - s += *i; + if (!first) + out << '.'; + else + first = false; + if (i->symbol.set()) + out << i->symbol; + else + out << "\"${" << *i->expr << "}\""; } - return s; + return out.str(); } @@ -213,17 +227,25 @@ void ExprSelect::bindVars(const StaticEnv & env) { e->bindVars(env); if (def) def->bindVars(env); + foreach (AttrPath::iterator, i, attrPath) + if (!i->symbol.set()) + i->expr->bindVars(env); } void ExprOpHasAttr::bindVars(const StaticEnv & env) { e->bindVars(env); + foreach (AttrPath::iterator, i, attrPath) + if (!i->symbol.set()) + i->expr->bindVars(env); } void ExprAttrs::bindVars(const StaticEnv & env) { + const StaticEnv *dynamicEnv = &env; if (recursive) { StaticEnv newEnv(false, &env); + dynamicEnv = &newEnv; unsigned int displ = 0; foreach (AttrDefs::iterator, i, attrs) @@ -236,6 +258,11 @@ void ExprAttrs::bindVars(const StaticEnv & env) else foreach (AttrDefs::iterator, i, attrs) i->second.e->bindVars(env); + + foreach (DynamicAttrDefs::iterator, i, dynamicAttrs) { + i->nameExpr->bindVars(*dynamicEnv); + i->valueExpr->bindVars(*dynamicEnv); + } } void ExprList::bindVars(const StaticEnv & env) @@ -314,6 +341,10 @@ void ExprOpNot::bindVars(const StaticEnv & env) e->bindVars(env); } +void ExprBuiltin::bindVars(const StaticEnv & env) +{ +} + void ExprConcatStrings::bindVars(const StaticEnv & env) { foreach (vector::iterator, i, *es) diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 61eb81fab..bc6993477 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -50,10 +50,19 @@ struct Env; struct Value; struct EvalState; struct StaticEnv; +struct Expr; /* An attribute path is a sequence of attribute names. */ -typedef vector AttrPath; +struct AttrName +{ + Symbol symbol; + Expr *expr; + AttrName(const Symbol & s) : symbol(s) {}; + AttrName(Expr *e) : expr(e) {}; +}; + +typedef std::vector AttrPath; string showAttrPath(const AttrPath & attrPath); @@ -138,7 +147,7 @@ struct ExprSelect : Expr Expr * e, * def; AttrPath attrPath; ExprSelect(Expr * e, const AttrPath & attrPath, Expr * def) : e(e), def(def), attrPath(attrPath) { }; - ExprSelect(Expr * e, const Symbol & name) : e(e), def(0) { attrPath.push_back(name); }; + ExprSelect(Expr * e, const Symbol & name) : e(e), def(0) { attrPath.push_back(AttrName(name)); }; COMMON_METHODS }; @@ -163,6 +172,14 @@ struct ExprAttrs : Expr }; typedef std::map AttrDefs; AttrDefs attrs; + struct DynamicAttrDef { + Expr * nameExpr; + Expr * valueExpr; + Pos pos; + DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos) : nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; + }; + typedef std::vector DynamicAttrDefs; + DynamicAttrDefs dynamicAttrs; ExprAttrs() : recursive(false) { }; COMMON_METHODS }; @@ -248,6 +265,13 @@ struct ExprOpNot : Expr COMMON_METHODS }; +struct ExprBuiltin : Expr +{ + Symbol name; + ExprBuiltin(const Symbol & name) : name(name) { }; + COMMON_METHODS +}; + #define MakeBinOp(name, s) \ struct Expr##name : Expr \ { \ diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 7699cf502..28972cf72 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -74,37 +74,49 @@ static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prev static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos) { - AttrPath attrPath; attrPath.push_back(attr); throw ParseError(format("attribute `%1%' at %2% already defined at %3%") - % showAttrPath(attrPath) % pos % prevPos); + % attr % pos % prevPos); } static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, Expr * e, const Pos & pos) { - unsigned int n = 0; - foreach (AttrPath::const_iterator, i, attrPath) { - n++; - ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(*i); - if (j != attrs->attrs.end()) { - if (!j->second.inherited) { - ExprAttrs * attrs2 = dynamic_cast(j->second.e); - if (!attrs2 || n == attrPath.size()) dupAttr(attrPath, pos, j->second.pos); - attrs = attrs2; - } else - dupAttr(attrPath, pos, j->second.pos); - } else { - if (n == attrPath.size()) - attrs->attrs[*i] = ExprAttrs::AttrDef(e, pos); - else { + AttrPath::iterator i; + // All attrpaths have at least one attr + assert(!attrPath.empty()); + for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) { + if (i->symbol.set()) { + ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); + if (j != attrs->attrs.end()) { + if (!j->second.inherited) { + ExprAttrs * attrs2 = dynamic_cast(j->second.e); + if (!attrs2) dupAttr(attrPath, pos, j->second.pos); + attrs = attrs2; + } else + dupAttr(attrPath, pos, j->second.pos); + } else { ExprAttrs * nested = new ExprAttrs; - attrs->attrs[*i] = ExprAttrs::AttrDef(nested, pos); + attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos); attrs = nested; } + } else { + ExprAttrs *nested = new ExprAttrs; + attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos)); + attrs = nested; } } - e->setName(attrPath.back()); + if (i->symbol.set()) { + ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol); + if (j != attrs->attrs.end()) { + dupAttr(attrPath, pos, j->second.pos); + } else { + attrs->attrs[i->symbol] = ExprAttrs::AttrDef(e, pos); + e->setName(i->symbol); + } + } else { + attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos)); + } } @@ -243,7 +255,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err char * id; // !!! -> Symbol char * path; char * uri; - std::vector * attrNames; + std::vector * attrNames; std::vector * string_parts; } @@ -254,7 +266,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err %type formals %type formal %type attrs attrpath -%type string_parts ind_string_parts +%type string_parts_interpolated ind_string_parts +%type string_parts string_attr %type attr %token ID ATTRPATH %token STR IND_STR @@ -300,7 +313,11 @@ expr_function | WITH expr ';' expr_function { $$ = new ExprWith(CUR_POS, $2, $4); } | LET binds IN expr_function - { $$ = new ExprLet($2, $4); } + { if (!$2->dynamicAttrs.empty()) + throw ParseError(format("dynamic attributes not allowed in let at %1%") + % CUR_POS); + $$ = new ExprLet($2, $4); + } | expr_if ; @@ -311,13 +328,13 @@ expr_if expr_op : '!' expr_op %prec NOT { $$ = new ExprOpNot($2); } -| '-' expr_op %prec NEGATE { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__sub")), new ExprInt(0)), $2); } +| '-' expr_op %prec NEGATE { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("sub")), new ExprInt(0)), $2); } | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); } - | expr_op '<' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $1), $3); } - | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $3), $1)); } - | expr_op '>' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $3), $1); } - | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__lessThan")), $1), $3)); } + | expr_op '<' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $1), $3); } + | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $3), $1)); } + | expr_op '>' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $3), $1); } + | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("lessThan")), $1), $3)); } | expr_op AND expr_op { $$ = new ExprOpAnd($1, $3); } | expr_op OR expr_op { $$ = new ExprOpOr($1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl($1, $3); } @@ -329,9 +346,9 @@ expr_op l->push_back($3); $$ = new ExprConcatStrings(false, l); } - | expr_op '-' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__sub")), $1), $3); } - | expr_op '*' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__mul")), $1), $3); } - | expr_op '/' expr_op { $$ = new ExprApp(new ExprApp(new ExprVar(noPos, data->symbols.create("__div")), $1), $3); } + | expr_op '-' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("sub")), $1), $3); } + | expr_op '*' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("mul")), $1), $3); } + | expr_op '/' expr_op { $$ = new ExprApp(new ExprApp(new ExprBuiltin(data->symbols.create("div")), $1), $3); } | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists($1, $3); } | expr_app ; @@ -362,12 +379,7 @@ expr_simple $$ = new ExprVar(CUR_POS, data->symbols.create($1)); } | INT { $$ = new ExprInt($1); } - | '"' string_parts '"' { - /* For efficiency, and to simplify parse trees a bit. */ - if ($2->empty()) $$ = new ExprString(data->symbols.create("")); - else if ($2->size() == 1 && dynamic_cast($2->front())) $$ = $2->front(); - else $$ = new ExprConcatStrings(true, $2); - } + | '"' string_parts '"' { $$ = $2; } | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE { $$ = stripIndentation(data->symbols, *$2); } @@ -381,7 +393,7 @@ expr_simple ‘throw’. */ $$ = path2 == "" ? (Expr * ) new ExprApp( - new ExprVar(noPos, data->symbols.create("throw")), + new ExprBuiltin(data->symbols.create("throw")), new ExprString(data->symbols.create( (format("file `%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)") % path).str()))) : (Expr * ) new ExprPath(path2); @@ -400,9 +412,27 @@ expr_simple ; string_parts - : string_parts STR { $$ = $1; $1->push_back($2); } - | string_parts DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); } - | { $$ = new vector; } + : STR + | string_parts_interpolated { $$ = new ExprConcatStrings(true, $1); } + | { $$ = new ExprString(data->symbols.create("")) } + ; + +string_parts_interpolated + : string_parts_interpolated STR { $$ = $1; $1->push_back($2); } + | string_parts_interpolated DOLLAR_CURLY expr '}' { backToString(scanner); $$ = $1; $1->push_back($3); } + | STR DOLLAR_CURLY expr '}' + { + backToString(scanner); + $$ = new vector; + $$->push_back($1); + $$->push_back($3); + } + | DOLLAR_CURLY expr '}' + { + backToString(scanner); + $$ = new vector; + $$->push_back($2); + } ; ind_string_parts @@ -416,39 +446,69 @@ binds | binds INHERIT attrs ';' { $$ = $1; foreach (AttrPath::iterator, i, *$3) { - if ($$->attrs.find(*i) != $$->attrs.end()) - dupAttr(*i, makeCurPos(@3, data), $$->attrs[*i].pos); + if ($$->attrs.find(i->symbol) != $$->attrs.end()) + dupAttr(i->symbol, makeCurPos(@3, data), $$->attrs[i->symbol].pos); Pos pos = makeCurPos(@3, data); - $$->attrs[*i] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, *i), pos, true); + $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprVar(CUR_POS, i->symbol), pos, true); } } | binds INHERIT '(' expr ')' attrs ';' { $$ = $1; /* !!! Should ensure sharing of the expression in $4. */ - foreach (vector::iterator, i, *$6) { - if ($$->attrs.find(*i) != $$->attrs.end()) - dupAttr(*i, makeCurPos(@6, data), $$->attrs[*i].pos); - $$->attrs[*i] = ExprAttrs::AttrDef(new ExprSelect($4, *i), makeCurPos(@6, data)); + foreach (AttrPath::iterator, i, *$6) { + if ($$->attrs.find(i->symbol) != $$->attrs.end()) + dupAttr(i->symbol, makeCurPos(@6, data), $$->attrs[i->symbol].pos); + $$->attrs[i->symbol] = ExprAttrs::AttrDef(new ExprSelect($4, i->symbol), makeCurPos(@6, data)); } } | { $$ = new ExprAttrs; } ; attrs - : attrs attr { $$ = $1; $1->push_back(data->symbols.create($2)); /* !!! dangerous */ } - | { $$ = new vector; } + : attrs attr { $$ = $1; $1->push_back(AttrName(data->symbols.create($2))); } + | attrs string_attr + { $$ = $1; + ExprString *str = dynamic_cast($2); + if (str) { + $$->push_back(AttrName(str->s)); + delete str; + } else + throw ParseError(format("dynamic attributes not allowed in inherit at %1%") + % makeCurPos(@2, data)); + } + | { $$ = new AttrPath; } ; attrpath - : attrpath '.' attr { $$ = $1; $1->push_back(data->symbols.create($3)); } - | attr { $$ = new vector; $$->push_back(data->symbols.create($1)); } + : attrpath '.' attr { $$ = $1; $1->push_back(AttrName(data->symbols.create($3))); } + | attrpath '.' string_attr + { $$ = $1; + ExprString *str = dynamic_cast($3); + if (str) { + $$->push_back(AttrName(str->s)); + delete str; + } else + $$->push_back(AttrName($3)); + } + | attr { $$ = new vector; $$->push_back(AttrName(data->symbols.create($1))); } + | string_attr + { $$ = new vector; + ExprString *str = dynamic_cast($1); + if (str) { + $$->push_back(AttrName(str->s)); + delete str; + } else + $$->push_back(AttrName($1)); + } ; attr : ID { $$ = $1; } | OR_KW { $$ = "or"; } - | '"' STR '"' - { $$ = strdup(((string) ((ExprString *) $2)->s).c_str()); delete $2; } + ; + +string_attr + : '"' string_parts '"' { $$ = $2; } ; expr_list diff --git a/tests/lang/eval-okay-dynamic-attrs-2.exp b/tests/lang/eval-okay-dynamic-attrs-2.exp new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs-2.exp @@ -0,0 +1 @@ +true diff --git a/tests/lang/eval-okay-dynamic-attrs-2.nix b/tests/lang/eval-okay-dynamic-attrs-2.nix new file mode 100644 index 000000000..6d57bf854 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs-2.nix @@ -0,0 +1 @@ +{ a."${"b"}" = true; a."${"c"}" = false; }.a.b diff --git a/tests/lang/eval-okay-dynamic-attrs.exp b/tests/lang/eval-okay-dynamic-attrs.exp new file mode 100644 index 000000000..df8750afc --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs.exp @@ -0,0 +1 @@ +{ binds = true; hasAttrs = true; multiAttrs = true; recBinds = true; selectAttrs = true; selectOrAttrs = true; } diff --git a/tests/lang/eval-okay-dynamic-attrs.nix b/tests/lang/eval-okay-dynamic-attrs.nix new file mode 100644 index 000000000..ee02ac7e6 --- /dev/null +++ b/tests/lang/eval-okay-dynamic-attrs.nix @@ -0,0 +1,17 @@ +let + aString = "a"; + + bString = "b"; +in { + hasAttrs = { a.b = null; } ? "${aString}".b; + + selectAttrs = { a.b = true; }.a."${bString}"; + + selectOrAttrs = { }."${aString}" or true; + + binds = { "${aString}"."${bString}c" = true; }.a.bc; + + recBinds = rec { "${bString}" = a; a = true; }.b; + + multiAttrs = { "${aString}" = true; "${bString}" = false; }.a; +} diff --git a/tests/lang/eval-okay-redefine-builtin.exp b/tests/lang/eval-okay-redefine-builtin.exp new file mode 100644 index 000000000..c508d5366 --- /dev/null +++ b/tests/lang/eval-okay-redefine-builtin.exp @@ -0,0 +1 @@ +false diff --git a/tests/lang/eval-okay-redefine-builtin.nix b/tests/lang/eval-okay-redefine-builtin.nix new file mode 100644 index 000000000..df9fc3f37 --- /dev/null +++ b/tests/lang/eval-okay-redefine-builtin.nix @@ -0,0 +1,3 @@ +let + throw = abort "Error!"; +in (builtins.tryEval ).success