diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index d41867a62..d46ac91e0 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -37,85 +37,6 @@ namespace nix { * Miscellaneous *************************************************************/ -StringMap EvalState::realiseContext(const NixStringContext & context) -{ - std::vector drvs; - StringMap res; - - for (auto & c : context) { - auto ensureValid = [&](const StorePath & p) { - if (!store->isValidPath(p)) - error(store->printStorePath(p)).debugThrow(); - }; - std::visit(overloaded { - [&](const NixStringContextElem::Built & b) { - drvs.push_back(DerivedPath::Built { - .drvPath = b.drvPath, - .outputs = OutputsSpec::Names { b.output }, - }); - ensureValid(b.drvPath->getBaseStorePath()); - }, - [&](const NixStringContextElem::Opaque & o) { - auto ctxS = store->printStorePath(o.path); - res.insert_or_assign(ctxS, ctxS); - ensureValid(o.path); - }, - [&](const NixStringContextElem::DrvDeep & d) { - /* Treat same as Opaque */ - auto ctxS = store->printStorePath(d.drvPath); - res.insert_or_assign(ctxS, ctxS); - ensureValid(d.drvPath); - }, - }, c.raw); - } - - if (drvs.empty()) return {}; - - if (!evalSettings.enableImportFromDerivation) - error( - "cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled", - drvs.begin()->to_string(*store) - ).debugThrow(); - - /* Build/substitute the context. */ - std::vector buildReqs; - for (auto & d : drvs) buildReqs.emplace_back(DerivedPath { d }); - buildStore->buildPaths(buildReqs, bmNormal, store); - - StorePathSet outputsToCopyAndAllow; - - for (auto & drv : drvs) { - auto outputs = resolveDerivedPath(*buildStore, drv, &*store); - for (auto & [outputName, outputPath] : outputs) { - outputsToCopyAndAllow.insert(outputPath); - - /* Get all the output paths corresponding to the placeholders we had */ - if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) { - res.insert_or_assign( - DownstreamPlaceholder::fromSingleDerivedPathBuilt( - SingleDerivedPath::Built { - .drvPath = drv.drvPath, - .output = outputName, - }).render(), - buildStore->printStorePath(outputPath) - ); - } - } - } - - if (store != buildStore) copyClosure(*buildStore, *store, outputsToCopyAndAllow); - if (allowedPaths) { - for (auto & outputPath : outputsToCopyAndAllow) { - /* Add the output of this derivations to the allowed - paths. */ - allowPath(outputPath); - } - } - - return res; -} - - /* Execute a program and parse its output */ @@ -136,26 +57,6 @@ template } -static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) -{ - try { - state.forceValue(*args[1], pos); - v = *args[1]; - } catch (Error & e) { - NixStringContext context; - auto message = state.coerceToString(pos, *args[0], context, - "while evaluating the error message passed to builtins.addErrorContext", - false, false).toOwned(); - e.addTrace(nullptr, HintFmt(message)); - throw; - } -} - -static RegisterPrimOp primop_addErrorContext(PrimOp { - .name = "__addErrorContext", - .arity = 2, - .fun = prim_addErrorContext, -}); /* Try evaluating the argument. Success => {success=true; value=something;}, * else => {success=false; value=false;} */ diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc index 36692aafb..3483989f1 100644 --- a/src/libexpr/primops/context.cc +++ b/src/libexpr/primops/context.cc @@ -1,142 +1,168 @@ +#include "downstream-placeholder.hh" +#include "eval-settings.hh" #include "primops.hh" -#include "eval-inline.hh" #include "derivations.hh" #include "store-api.hh" namespace nix { -static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) +StringMap EvalState::realiseContext(const NixStringContext & context) { - NixStringContext context; - auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext"); - v.mkString(*s); -} + std::vector drvs; + StringMap res; -static RegisterPrimOp primop_unsafeDiscardStringContext({ - .name = "__unsafeDiscardStringContext", - .arity = 1, - .fun = prim_unsafeDiscardStringContext -}); + for (auto & c : context) { + auto ensureValid = [&](const StorePath & p) { + if (!store->isValidPath(p)) { + error(store->printStorePath(p)).debugThrow(); + } + }; + std::visit( + overloaded{ + [&](const NixStringContextElem::Built & b) { + drvs.push_back(DerivedPath::Built{ + .drvPath = b.drvPath, + .outputs = OutputsSpec::Names{b.output}, + }); + ensureValid(b.drvPath->getBaseStorePath()); + }, + [&](const NixStringContextElem::Opaque & o) { + auto ctxS = store->printStorePath(o.path); + res.insert_or_assign(ctxS, ctxS); + ensureValid(o.path); + }, + [&](const NixStringContextElem::DrvDeep & d) { + /* Treat same as Opaque */ + auto ctxS = store->printStorePath(d.drvPath); + res.insert_or_assign(ctxS, ctxS); + ensureValid(d.drvPath); + }, + }, + c.raw + ); + } + if (drvs.empty()) { + return {}; + } -static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) -{ - NixStringContext context; - state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext"); - v.mkBool(!context.empty()); -} + if (!evalSettings.enableImportFromDerivation) { + error( + "cannot build '%1%' during evaluation because the option " + "'allow-import-from-derivation' is disabled", + drvs.begin()->to_string(*store) + ) + .debugThrow(); + } -static RegisterPrimOp primop_hasContext({ - .name = "__hasContext", - .args = {"s"}, - .doc = R"( - Return `true` if string *s* has a non-empty context. - The context can be obtained with - [`getContext`](#builtins-getContext). + /* Build/substitute the context. */ + std::vector buildReqs; + for (auto & d : drvs) { + buildReqs.emplace_back(DerivedPath{d}); + } + buildStore->buildPaths(buildReqs, bmNormal, store); - > **Example** - > - > Many operations require a string context to be empty because they are intended only to work with "regular" strings, and also to help users avoid unintentionally losing track of string context elements. - > `builtins.hasContext` can help create better domain-specific errors in those case. - > - > ```nix - > name: meta: - > - > if builtins.hasContext name - > then throw "package name cannot contain string context" - > else { ${name} = meta; } - > ``` - )", - .fun = prim_hasContext -}); + StorePathSet outputsToCopyAndAllow; + for (auto & drv : drvs) { + auto outputs = resolveDerivedPath(*buildStore, drv, &*store); + for (auto & [outputName, outputPath] : outputs) { + outputsToCopyAndAllow.insert(outputPath); -static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v) -{ - NixStringContext context; - auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency"); - - NixStringContext context2; - for (auto && c : context) { - if (auto * ptr = std::get_if(&c.raw)) { - context2.emplace(NixStringContextElem::Opaque { - .path = ptr->drvPath - }); - } else { - /* Can reuse original item */ - context2.emplace(std::move(c).raw); + /* Get all the output paths corresponding to the placeholders we had */ + if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) { + res.insert_or_assign( + DownstreamPlaceholder::fromSingleDerivedPathBuilt(SingleDerivedPath::Built{ + .drvPath = drv.drvPath, + .output = outputName, + }) + .render(), + buildStore->printStorePath(outputPath) + ); + } } } - v.mkString(*s, context2); + if (store != buildStore) { + copyClosure(*buildStore, *store, outputsToCopyAndAllow); + } + if (allowedPaths) { + for (auto & outputPath : outputsToCopyAndAllow) { + /* Add the output of this derivations to the allowed + paths. */ + allowPath(outputPath); + } + } + + return res; } -static RegisterPrimOp primop_unsafeDiscardOutputDependency({ - .name = "__unsafeDiscardOutputDependency", - .args = {"s"}, - .doc = R"( - Create a copy of the given string where every "derivation deep" string context element is turned into a constant string context element. +/** + * builtins.addDrvOutputDependencies + */ - This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies). - - This is unsafe because it allows us to "forget" store objects we would have otherwise refered to with the string context, - whereas Nix normally tracks all dependencies consistently. - Safe operations "grow" but never "shrink" string contexts. - [`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things). - Replacing a constant string context element with a "derivation deep" element is a safe operation that just enlargens the string context without forgetting anything. - - [`builtins.addDrvOutputDependencies`]: #builtins-addDrvOutputDependencies - )", - .fun = prim_unsafeDiscardOutputDependency -}); - - -static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value * * args, Value & v) +static void +prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value ** args, Value & v) { NixStringContext context; - auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies"); + auto s = state.coerceToString( + pos, + *args[0], + context, + "while evaluating the argument passed to builtins.addDrvOutputDependencies" + ); - auto contextSize = context.size(); + auto contextSize = context.size(); if (contextSize != 1) { - state.error( - "context of string '%s' must have exactly one element, but has %d", - *s, - contextSize - ).atPos(pos).debugThrow(); + state + .error( + "context of string '%s' must have exactly one element, but has %d", *s, contextSize + ) + .atPos(pos) + .debugThrow(); } - NixStringContext context2 { - (NixStringContextElem { std::visit(overloaded { - [&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep { - if (!c.path.isDerivation()) { - state.error( - "path '%s' is not a derivation", - state.store->printStorePath(c.path) - ).atPos(pos).debugThrow(); - } - return NixStringContextElem::DrvDeep { - .drvPath = c.path, - }; + NixStringContext context2{ + (NixStringContextElem{std::visit( + overloaded{ + [&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep { + if (!c.path.isDerivation()) { + state + .error( + "path '%s' is not a derivation", state.store->printStorePath(c.path) + ) + .atPos(pos) + .debugThrow(); + } + return NixStringContextElem::DrvDeep{ + .drvPath = c.path, + }; + }, + [&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep { + state + .error( + "`addDrvOutputDependencies` can only act on derivations, not on a " + "derivation output such as '%1%'", + c.output + ) + .atPos(pos) + .debugThrow(); + }, + [&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep { + /* Reuse original item because we want this to be idempotent. */ + return std::move(c); + }, }, - [&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep { - state.error( - "`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", - c.output - ).atPos(pos).debugThrow(); - }, - [&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep { - /* Reuse original item because we want this to be idempotent. */ - return std::move(c); - }, - }, context.begin()->raw) }), + context.begin()->raw + )}), }; v.mkString(*s, context2); } -static RegisterPrimOp primop_addDrvOutputDependencies({ - .name = "__addDrvOutputDependencies", - .args = {"s"}, - .doc = R"( +static RegisterPrimOp primop_addDrvOutputDependencies( + {.name = "__addDrvOutputDependencies", + .args = {"s"}, + .doc = R"( Create a copy of the given string where a single constant string context element is turned into a "derivation deep" string context element. The store path that is the constant string context element should point to a valid derivation, and end in `.drv`. @@ -146,9 +172,153 @@ static RegisterPrimOp primop_addDrvOutputDependencies({ This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency). )", - .fun = prim_addDrvOutputDependencies + .fun = prim_addDrvOutputDependencies} +); + +/** + * builtins.addErrorContext + */ + +static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + try { + state.forceValue(*args[1], pos); + v = *args[1]; + } catch (Error & e) { + NixStringContext context; + auto message = + state + .coerceToString( + pos, + *args[0], + context, + "while evaluating the error message passed to builtins.addErrorContext", + false, + false + ) + .toOwned(); + e.addTrace(nullptr, HintFmt(message)); + throw; + } +} + +static RegisterPrimOp primop_addErrorContext(PrimOp{ + .name = "__addErrorContext", + .arity = 2, + .fun = prim_addErrorContext, }); +/** + * builtins.appendContext + */ + +static void prim_appendContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + NixStringContext context; + auto orig = state.forceString( + *args[0], + context, + noPos, + "while evaluating the first argument passed to builtins.appendContext" + ); + + state.forceAttrs( + *args[1], pos, "while evaluating the second argument passed to builtins.appendContext" + ); + + auto sPath = state.symbols.create("path"); + auto sAllOutputs = state.symbols.create("allOutputs"); + for (auto & i : *args[1]->attrs) { + const auto & name = state.symbols[i.name]; + if (!state.store->isStorePath(name)) { + state.error("context key '%s' is not a store path", name) + .atPos(i.pos) + .debugThrow(); + } + auto namePath = state.store->parseStorePath(name); + if (!settings.readOnlyMode) { + state.store->ensurePath(namePath); + } + state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context"); + auto iter = i.value->attrs->find(sPath); + if (iter != i.value->attrs->end()) { + if (state.forceBool( + *iter->value, + iter->pos, + "while evaluating the `path` attribute of a string context" + )) + { + context.emplace(NixStringContextElem::Opaque{ + .path = namePath, + }); + } + } + + iter = i.value->attrs->find(sAllOutputs); + if (iter != i.value->attrs->end()) { + if (state.forceBool( + *iter->value, + iter->pos, + "while evaluating the `allOutputs` attribute of a string context" + )) + { + if (!isDerivation(name)) { + state + .error( + "tried to add all-outputs context of %s, which is not a derivation, to " + "a string", + name + ) + .atPos(i.pos) + .debugThrow(); + } + context.emplace(NixStringContextElem::DrvDeep{ + .drvPath = namePath, + }); + } + } + + iter = i.value->attrs->find(state.sOutputs); + if (iter != i.value->attrs->end()) { + state.forceList( + *iter->value, + iter->pos, + "while evaluating the `outputs` attribute of a string context" + ); + if (iter->value->listSize() && !isDerivation(name)) { + state + .error( + "tried to add derivation output context of %s, which is not a derivation, " + "to a string", + name + ) + .atPos(i.pos) + .debugThrow(); + } + for (auto elem : iter->value->listItems()) { + auto outputName = state.forceStringNoCtx( + *elem, iter->pos, "while evaluating an output name within a string context" + ); + context.emplace(NixStringContextElem::Built{ + .drvPath = makeConstantStorePathRef(namePath), + .output = std::string{outputName}, + }); + } + } + } + + v.mkString(orig, context); +} + +static RegisterPrimOp primop_appendContext({ + .name = "__appendContext", + .arity = 2, + .fun = prim_appendContext, +}); + +/** + * builtins.getContext + */ /* Extract the context of a string as a structured Nix value. @@ -169,31 +339,37 @@ static RegisterPrimOp primop_addDrvOutputDependencies({ Note that for a given path any combination of the above attributes may be present. */ -static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) +static void prim_getContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) { - struct ContextInfo { + struct ContextInfo + { bool path = false; bool allOutputs = false; Strings outputs; }; NixStringContext context; - state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext"); + state.forceString( + *args[0], context, pos, "while evaluating the argument passed to builtins.getContext" + ); auto contextInfos = std::map(); for (auto && i : context) { - std::visit(overloaded { - [&](NixStringContextElem::DrvDeep && d) { - contextInfos[std::move(d.drvPath)].allOutputs = true; + std::visit( + overloaded{ + [&](NixStringContextElem::DrvDeep && d) { + contextInfos[std::move(d.drvPath)].allOutputs = true; + }, + [&](NixStringContextElem::Built && b) { + // FIXME should eventually show string context as is, no + // resolving here. + auto drvPath = resolveDerivedPath(*state.store, *b.drvPath); + contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output)); + }, + [&](NixStringContextElem::Opaque && o) { + contextInfos[std::move(o.path)].path = true; + }, }, - [&](NixStringContextElem::Built && b) { - // FIXME should eventually show string context as is, no - // resolving here. - auto drvPath = resolveDerivedPath(*state.store, *b.drvPath); - contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output)); - }, - [&](NixStringContextElem::Opaque && o) { - contextInfos[std::move(o.path)].path = true; - }, - }, ((NixStringContextElem &&) i).raw); + ((NixStringContextElem &&) i).raw + ); } auto attrs = state.buildBindings(contextInfos.size()); @@ -202,15 +378,18 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, auto sAllOutputs = state.symbols.create("allOutputs"); for (const auto & info : contextInfos) { auto infoAttrs = state.buildBindings(3); - if (info.second.path) + if (info.second.path) { infoAttrs.alloc(sPath).mkBool(true); - if (info.second.allOutputs) + } + if (info.second.allOutputs) { infoAttrs.alloc(sAllOutputs).mkBool(true); + } if (!info.second.outputs.empty()) { auto & outputsVal = infoAttrs.alloc(state.sOutputs); state.mkList(outputsVal, info.second.outputs.size()); - for (const auto & [i, output] : enumerate(info.second.outputs)) + for (const auto & [i, output] : enumerate(info.second.outputs)) { (outputsVal.listElems()[i] = state.allocValue())->mkString(output); + } } attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs); } @@ -240,84 +419,114 @@ static RegisterPrimOp primop_getContext({ { "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; } ``` )", - .fun = prim_getContext + .fun = prim_getContext, }); +/** + * builtins.hasContext + */ -/* Append the given context to a given string. - - See the commentary above unsafeGetContext for details of the - context representation. -*/ -static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) +static void prim_hasContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) { NixStringContext context; - auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext"); + state.forceString( + *args[0], context, pos, "while evaluating the argument passed to builtins.hasContext" + ); + v.mkBool(!context.empty()); +} - state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext"); +static RegisterPrimOp primop_hasContext( + {.name = "__hasContext", + .args = {"s"}, + .doc = R"( + Return `true` if string *s* has a non-empty context. + The context can be obtained with + [`getContext`](#builtins-getContext). - auto sPath = state.symbols.create("path"); - auto sAllOutputs = state.symbols.create("allOutputs"); - for (auto & i : *args[1]->attrs) { - const auto & name = state.symbols[i.name]; - if (!state.store->isStorePath(name)) - state.error( - "context key '%s' is not a store path", - name - ).atPos(i.pos).debugThrow(); - auto namePath = state.store->parseStorePath(name); - if (!settings.readOnlyMode) - state.store->ensurePath(namePath); - state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context"); - auto iter = i.value->attrs->find(sPath); - if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context")) - context.emplace(NixStringContextElem::Opaque { - .path = namePath, - }); - } + > **Example** + > + > Many operations require a string context to be empty because they are intended only to work with "regular" strings, and also to help users avoid unintentionally losing track of string context elements. + > `builtins.hasContext` can help create better domain-specific errors in those case. + > + > ```nix + > name: meta: + > + > if builtins.hasContext name + > then throw "package name cannot contain string context" + > else { ${name} = meta; } + > ``` + )", + .fun = prim_hasContext} +); - iter = i.value->attrs->find(sAllOutputs); - if (iter != i.value->attrs->end()) { - if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) { - if (!isDerivation(name)) { - state.error( - "tried to add all-outputs context of %s, which is not a derivation, to a string", - name - ).atPos(i.pos).debugThrow(); - } - context.emplace(NixStringContextElem::DrvDeep { - .drvPath = namePath, - }); - } - } +/** + * builtins.unsafeDiscardOutputDependency + */ - iter = i.value->attrs->find(state.sOutputs); - if (iter != i.value->attrs->end()) { - state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context"); - if (iter->value->listSize() && !isDerivation(name)) { - state.error( - "tried to add derivation output context of %s, which is not a derivation, to a string", - name - ).atPos(i.pos).debugThrow(); - } - for (auto elem : iter->value->listItems()) { - auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context"); - context.emplace(NixStringContextElem::Built { - .drvPath = makeConstantStorePathRef(namePath), - .output = std::string { outputName }, - }); - } +static void +prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + NixStringContext context; + auto s = state.coerceToString( + pos, + *args[0], + context, + "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency" + ); + + NixStringContext context2; + for (auto && c : context) { + if (auto * ptr = std::get_if(&c.raw)) { + context2.emplace(NixStringContextElem::Opaque{.path = ptr->drvPath}); + } else { + /* Can reuse original item */ + context2.emplace(std::move(c).raw); } } - v.mkString(orig, context); + v.mkString(*s, context2); } -static RegisterPrimOp primop_appendContext({ - .name = "__appendContext", - .arity = 2, - .fun = prim_appendContext +static RegisterPrimOp primop_unsafeDiscardOutputDependency( + {.name = "__unsafeDiscardOutputDependency", + .args = {"s"}, + .doc = R"( + Create a copy of the given string where every "derivation deep" string context element is turned into a constant string context element. + + This is the opposite of [`builtins.addDrvOutputDependencies`](#builtins-addDrvOutputDependencies). + + This is unsafe because it allows us to "forget" store objects we would have otherwise refered to with the string context, + whereas Nix normally tracks all dependencies consistently. + Safe operations "grow" but never "shrink" string contexts. + [`builtins.addDrvOutputDependencies`] in contrast is safe because "derivation deep" string context element always refers to the underlying derivation (among many more things). + Replacing a constant string context element with a "derivation deep" element is a safe operation that just enlargens the string context without forgetting anything. + + [`builtins.addDrvOutputDependencies`]: #builtins-addDrvOutputDependencies + )", + .fun = prim_unsafeDiscardOutputDependency} +); + +/** + * builtins. unsafeDiscardStringContext + */ + +static void +prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) +{ + NixStringContext context; + auto s = state.coerceToString( + pos, + *args[0], + context, + "while evaluating the argument passed to builtins.unsafeDiscardStringContext" + ); + v.mkString(*s); +} + +static RegisterPrimOp primop_unsafeDiscardStringContext({ + .name = "__unsafeDiscardStringContext", + .arity = 1, + .fun = prim_unsafeDiscardStringContext, }); }