libexpr: throw a more helpful eval-error if a builtin is not available due to a missing feature-flag

I found it somewhat confusing to have an error like

    error: attribute 'getFlake' missing

if the required experimental-feature (`flakes`) is not enabled. Instead,
I'd expect Nix to throw an error just like it's the case when using e.g. `nix
flake` without `flakes` being enabled.

With this change, the error looks like this:

    $ nix-instantiate -E 'builtins.getFlake "nixpkgs"'
    error: Cannot call 'builtins.getFlake' because experimental Nix feature 'flakes' is disabled. You can enable it via '--extra-experimental-features flakes'.

           at «string»:1:1:

                1| builtins.getFlake "nixpkgs"
                 | ^

I didn't use `settings.requireExperimentalFeature` here on purpose
because this doesn't contain a position. Also, it doesn't seem as if we
need to catch the error and check for the missing feature here since
this already happens at evaluation time.
This commit is contained in:
Maximilian Bosch 2021-09-28 22:12:41 +02:00
parent a0bb5c4130
commit 2b02ce0e48
No known key found for this signature in database
GPG key ID: 091DBF4D1FC46B8E
5 changed files with 35 additions and 15 deletions

View file

@ -465,6 +465,23 @@ EvalState::~EvalState()
} }
void EvalState::requireExperimentalFeatureOnEvaluation(
const std::string & feature,
const std::string_view fName,
const Pos & pos)
{
if (!settings.isExperimentalFeatureEnabled(feature)) {
throw EvalError({
.msg = hintfmt(
"Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.",
feature,
fName
),
.errPos = pos
});
}
}
Path EvalState::checkSourcePath(const Path & path_) Path EvalState::checkSourcePath(const Path & path_)
{ {
if (!allowedPaths) return path_; if (!allowedPaths) return path_;

View file

@ -140,6 +140,12 @@ public:
std::shared_ptr<Store> buildStore = nullptr); std::shared_ptr<Store> buildStore = nullptr);
~EvalState(); ~EvalState();
void requireExperimentalFeatureOnEvaluation(
const std::string & feature,
const std::string_view fName,
const Pos & pos
);
void addToSearchPath(const string & s); void addToSearchPath(const string & s);
SearchPath getSearchPath() { return searchPath; } SearchPath getSearchPath() { return searchPath; }

View file

@ -688,6 +688,8 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
state.requireExperimentalFeatureOnEvaluation("flakes", "builtins.getFlake", pos);
auto flakeRefS = state.forceStringNoCtx(*args[0], pos); auto flakeRefS = state.forceStringNoCtx(*args[0], pos);
auto flakeRef = parseFlakeRef(flakeRefS, {}, true); auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isImmutable()) if (evalSettings.pureEval && !flakeRef.input.isImmutable())
@ -703,7 +705,7 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
v); v);
} }
static RegisterPrimOp r2("__getFlake", 1, prim_getFlake, "flakes"); static RegisterPrimOp r2("__getFlake", 1, prim_getFlake);
} }

View file

@ -3606,15 +3606,13 @@ static RegisterPrimOp primop_splitVersion({
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps; RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun, RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
std::optional<std::string> requiredFeature)
{ {
if (!primOps) primOps = new PrimOps; if (!primOps) primOps = new PrimOps;
primOps->push_back({ primOps->push_back({
.name = name, .name = name,
.args = {}, .args = {},
.arity = arity, .arity = arity,
.requiredFeature = std::move(requiredFeature),
.fun = fun .fun = fun
}); });
} }
@ -3688,14 +3686,13 @@ void EvalState::createBaseEnv()
if (RegisterPrimOp::primOps) if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps) for (auto & primOp : *RegisterPrimOp::primOps)
if (!primOp.requiredFeature || settings.isExperimentalFeatureEnabled(*primOp.requiredFeature)) addPrimOp({
addPrimOp({ .fun = primOp.fun,
.fun = primOp.fun, .arity = std::max(primOp.args.size(), primOp.arity),
.arity = std::max(primOp.args.size(), primOp.arity), .name = symbols.create(primOp.name),
.name = symbols.create(primOp.name), .args = std::move(primOp.args),
.args = std::move(primOp.args), .doc = primOp.doc,
.doc = primOp.doc, });
});
/* Add a wrapper around the derivation primop that computes the /* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */ `drvPath' and `outPath' attributes lazily. */

View file

@ -15,7 +15,6 @@ struct RegisterPrimOp
std::vector<std::string> args; std::vector<std::string> args;
size_t arity = 0; size_t arity = 0;
const char * doc; const char * doc;
std::optional<std::string> requiredFeature;
PrimOpFun fun; PrimOpFun fun;
}; };
@ -28,8 +27,7 @@ struct RegisterPrimOp
RegisterPrimOp( RegisterPrimOp(
std::string name, std::string name,
size_t arity, size_t arity,
PrimOpFun fun, PrimOpFun fun);
std::optional<std::string> requiredFeature = {});
RegisterPrimOp(Info && info); RegisterPrimOp(Info && info);
}; };