Automatically document builtin constants

This is done in roughly the same way builtin functions are documented.

Also auto-link experimental features for primops, subsuming PR #8371.

Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
This commit is contained in:
John Ericson 2023-05-13 13:52:45 -04:00
parent d40f0e534d
commit 22b278e011
15 changed files with 447 additions and 147 deletions

3
.gitignore vendored
View file

@ -18,7 +18,7 @@ perl/Makefile.config
/doc/manual/generated/* /doc/manual/generated/*
/doc/manual/nix.json /doc/manual/nix.json
/doc/manual/conf-file.json /doc/manual/conf-file.json
/doc/manual/builtins.json /doc/manual/language.json
/doc/manual/xp-features.json /doc/manual/xp-features.json
/doc/manual/src/SUMMARY.md /doc/manual/src/SUMMARY.md
/doc/manual/src/command-ref/new-cli /doc/manual/src/command-ref/new-cli
@ -26,6 +26,7 @@ perl/Makefile.config
/doc/manual/src/command-ref/experimental-features-shortlist.md /doc/manual/src/command-ref/experimental-features-shortlist.md
/doc/manual/src/contributing/experimental-feature-descriptions.md /doc/manual/src/contributing/experimental-feature-descriptions.md
/doc/manual/src/language/builtins.md /doc/manual/src/language/builtins.md
/doc/manual/src/language/builtin-constants.md
# /scripts/ # /scripts/
/scripts/nix-profile.sh /scripts/nix-profile.sh

View file

@ -0,0 +1,29 @@
let
inherit (builtins) concatStringsSep attrValues mapAttrs;
inherit (import ./utils.nix) optionalString squash;
in
builtinsInfo:
let
showBuiltin = name: { doc, type, impure-only }:
let
type' = optionalString (type != null) " (${type})";
impureNotice = optionalString impure-only ''
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
'';
in
squash ''
<dt id="builtin-constants-${name}">
<a href="#builtin-constants-${name}"><code>${name}</code>${type'}</a>
</dt>
<dd>
${doc}
${impureNotice}
</dd>
'';
in
concatStringsSep "\n" (attrValues (mapAttrs showBuiltin builtinsInfo))

View file

@ -1,24 +1,28 @@
let let
inherit (builtins) concatStringsSep attrNames; inherit (builtins) concatStringsSep attrValues mapAttrs;
inherit (import ./utils.nix) optionalString squash;
in in
builtinsInfo: builtinsInfo:
let let
showBuiltin = name: showBuiltin = name: { doc, args, arity, experimental-feature }:
let let
inherit (builtinsInfo.${name}) doc args; experimentalNotice = optionalString (experimental-feature != null) ''
This function is only available if the [${experimental-feature}](@docroot@/contributing/experimental-features.md#xp-feature-${experimental-feature}) experimental feature is enabled.
'';
in in
'' squash ''
<dt id="builtins-${name}"> <dt id="builtins-${name}">
<a href="#builtins-${name}"><code>${name} ${listArgs args}</code></a> <a href="#builtins-${name}"><code>${name} ${listArgs args}</code></a>
</dt> </dt>
<dd> <dd>
${doc} ${doc}
${experimentalNotice}
</dd> </dd>
''; '';
listArgs = args: concatStringsSep " " (map (s: "<var>${s}</var>") args); listArgs = args: concatStringsSep " " (map (s: "<var>${s}</var>") args);
in in
concatStringsSep "\n" (map showBuiltin (attrNames builtinsInfo)) concatStringsSep "\n" (attrValues (mapAttrs showBuiltin builtinsInfo))

View file

@ -128,14 +128,20 @@ $(d)/xp-features.json: $(bindir)/nix
$(trace-gen) $(dummy-env) NIX_PATH=nix/corepkgs=corepkgs $(bindir)/nix __dump-xp-features > $@.tmp $(trace-gen) $(dummy-env) NIX_PATH=nix/corepkgs=corepkgs $(bindir)/nix __dump-xp-features > $@.tmp
@mv $@.tmp $@ @mv $@.tmp $@
$(d)/src/language/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/language/builtins-prefix.md $(bindir)/nix $(d)/src/language/builtins.md: $(d)/language.json $(d)/generate-builtins.nix $(d)/src/language/builtins-prefix.md $(bindir)/nix
@cat doc/manual/src/language/builtins-prefix.md > $@.tmp @cat doc/manual/src/language/builtins-prefix.md > $@.tmp
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp; $(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<)).builtins' >> $@.tmp;
@cat doc/manual/src/language/builtins-suffix.md >> $@.tmp @cat doc/manual/src/language/builtins-suffix.md >> $@.tmp
@mv $@.tmp $@ @mv $@.tmp $@
$(d)/builtins.json: $(bindir)/nix $(d)/src/language/builtin-constants.md: $(d)/language.json $(d)/generate-builtin-constants.nix $(d)/src/language/builtin-constants-prefix.md $(bindir)/nix
$(trace-gen) $(dummy-env) NIX_PATH=nix/corepkgs=corepkgs $(bindir)/nix __dump-builtins > $@.tmp @cat doc/manual/src/language/builtin-constants-prefix.md > $@.tmp
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtin-constants.nix (builtins.fromJSON (builtins.readFile $<)).constants' >> $@.tmp;
@cat doc/manual/src/language/builtin-constants-suffix.md >> $@.tmp
@mv $@.tmp $@
$(d)/language.json: $(bindir)/nix
$(trace-gen) $(dummy-env) NIX_PATH=nix/corepkgs=corepkgs $(bindir)/nix __dump-language > $@.tmp
@mv $@.tmp $@ @mv $@.tmp $@
# Generate the HTML manual. # Generate the HTML manual.
@ -167,7 +173,7 @@ doc/manual/generated/man1/nix3-manpages: $(d)/src/command-ref/new-cli
done done
@touch $@ @touch $@
$(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/contributing/experimental-feature-descriptions.md $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md $(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/contributing/experimental-feature-descriptions.md $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md $(d)/src/language/builtin-constants.md
$(trace-gen) \ $(trace-gen) \
tmp="$$(mktemp -d)"; \ tmp="$$(mktemp -d)"; \
cp -r doc/manual "$$tmp"; \ cp -r doc/manual "$$tmp"; \

View file

@ -0,0 +1,5 @@
# Built-in Constants
These constants are built into the Nix language evaluator:
<dl>

View file

@ -0,0 +1 @@
</dl>

View file

@ -1,44 +0,0 @@
# Built-in Constants
These constants are built into the Nix language evaluator:
- [`builtins`]{#builtins-builtins} (attribute set)
Contains all the [built-in functions](./builtins.md) and values.
Since built-in functions were added over time, [testing for attributes](./operators.md#has-attribute) in `builtins` can be used for graceful fallback on older Nix installations:
```nix
# if hasContext is not available, we assume `s` has a context
if builtins ? hasContext then builtins.hasContext s else true
```
- [`builtins.currentSystem`]{#builtins-currentSystem} (string)
The built-in value `currentSystem` evaluates to the Nix platform
identifier for the Nix installation on which the expression is being
evaluated, such as `"i686-linux"` or `"x86_64-darwin"`.
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
- [`builtins.currentTime`]{#builtins-currentTime} (integer)
Return the [Unix time](https://en.wikipedia.org/wiki/Unix_time) at first evaluation.
Repeated references to that name will re-use the initially obtained value.
Example:
```console
$ nix repl
Welcome to Nix 2.15.1 Type :? for help.
nix-repl> builtins.currentTime
1683705525
nix-repl> builtins.currentTime
1683705525
```
The [store path](@docroot@/glossary.md#gloss-store-path) of a derivation depending on `currentTime` will differ for each evaluation.
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).

View file

@ -703,28 +703,34 @@ Path EvalState::toRealPath(const Path & path, const NixStringContext & context)
} }
Value * EvalState::addConstant(const std::string & name, Value & v) Value * EvalState::addConstant(const std::string & name, Value & v, Constant info)
{ {
Value * v2 = allocValue(); Value * v2 = allocValue();
*v2 = v; *v2 = v;
addConstant(name, v2); addConstant(name, v2, info);
return v2; return v2;
} }
void EvalState::addConstant(const std::string & name, Value * v) void EvalState::addConstant(const std::string & name, Value * v, Constant info)
{ {
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
}
constantInfos.push_back({name2, info});
Value * EvalState::addPrimOp(const std::string & name, if (!(evalSettings.pureEval && info.impureOnly)) {
size_t arity, PrimOpFun primOp) /* Check the type, if possible.
{
return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name }); We might know the type of a thunk in advance, so be allowed
to just write it down in that case. */
if (auto gotType = v->type(true); gotType != nThunk)
assert(info.type == gotType);
/* Install value the base environment. */
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
}
} }
@ -738,7 +744,10 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
vPrimOp->mkPrimOp(new PrimOp(primOp)); vPrimOp->mkPrimOp(new PrimOp(primOp));
Value v; Value v;
v.mkApp(vPrimOp, vPrimOp); v.mkApp(vPrimOp, vPrimOp);
return addConstant(primOp.name, v); return addConstant(primOp.name, v, {
.type = nThunk, // FIXME
.doc = primOp.doc,
});
} }
auto envName = symbols.create(primOp.name); auto envName = symbols.create(primOp.name);
@ -764,13 +773,13 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
{ {
if (v.isPrimOp()) { if (v.isPrimOp()) {
auto v2 = &v; auto v2 = &v;
if (v2->primOp->doc) if (auto * doc = v2->primOp->doc)
return Doc { return Doc {
.pos = {}, .pos = {},
.name = v2->primOp->name, .name = v2->primOp->name,
.arity = v2->primOp->arity, .arity = v2->primOp->arity,
.args = v2->primOp->args, .args = v2->primOp->args,
.doc = v2->primOp->doc, .doc = doc,
}; };
} }
return {}; return {};

View file

@ -25,15 +25,72 @@ struct DerivedPath;
enum RepairFlag : bool; enum RepairFlag : bool;
/**
* Function that implements a primop.
*/
typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v); typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v);
/**
* Info about a primitive operation, and its implementation
*/
struct PrimOp struct PrimOp
{ {
PrimOpFun fun; /**
size_t arity; * Name of the primop. `__` prefix is treated specially.
*/
std::string name; std::string name;
/**
* Names of the parameters of a primop, for primops that take a
* fixed number of arguments to be substituted for these parameters.
*/
std::vector<std::string> args; std::vector<std::string> args;
/**
* Aritiy of the primop.
*
* If `args` is not empty, this field will be computed from that
* field instead, so it doesn't need to be manually set.
*/
size_t arity = 0;
/**
* Optional free-form documentation about the primop.
*/
const char * doc = nullptr; const char * doc = nullptr;
/**
* Implementation of the primop.
*/
PrimOpFun fun;
/**
* Optional experimental for this to be gated on.
*/
std::optional<ExperimentalFeature> experimentalFeature;
};
/**
* Info about a constant
*/
struct Constant
{
/**
* Optional type of the constant (known since it is a fixed value).
*
* @todo we should use an enum for this.
*/
ValueType type = nThunk;
/**
* Optional free-form documentation about the constant.
*/
const char * doc = nullptr;
/**
* Whether the constant is impure, and not available in pure mode.
*/
bool impureOnly = false;
}; };
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
@ -513,18 +570,23 @@ public:
*/ */
std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
/**
* Name and documentation about every constant.
*
* Constants from primops are hard to crawl, and their docs will go
* here too.
*/
std::vector<std::pair<std::string, Constant>> constantInfos;
private: private:
unsigned int baseEnvDispl = 0; unsigned int baseEnvDispl = 0;
void createBaseEnv(); void createBaseEnv();
Value * addConstant(const std::string & name, Value & v); Value * addConstant(const std::string & name, Value & v, Constant info);
void addConstant(const std::string & name, Value * v); void addConstant(const std::string & name, Value * v, Constant info);
Value * addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp);
Value * addPrimOp(PrimOp && primOp); Value * addPrimOp(PrimOp && primOp);
@ -538,6 +600,10 @@ public:
std::optional<std::string> name; std::optional<std::string> name;
size_t arity; size_t arity;
std::vector<std::string> args; std::vector<std::string> args;
/**
* Unlike the other `doc` fields in this file, this one should never be
* `null`.
*/
const char * doc; const char * doc;
}; };
@ -740,7 +806,12 @@ struct EvalSettings : Config
Setting<Strings> nixPath{ Setting<Strings> nixPath{
this, getDefaultNixPath(), "nix-path", this, getDefaultNixPath(), "nix-path",
"List of directories to be searched for `<...>` file references."}; R"(
List of directories to be searched for `<...>` file references
In particular, outside of [pure evaluation mode](#conf-pure-evaluation), this determines the value of
[`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtin-constants-nixPath).
)"};
Setting<bool> restrictEval{ Setting<bool> restrictEval{
this, false, "restrict-eval", this, false, "restrict-eval",

View file

@ -788,9 +788,6 @@ static RegisterPrimOp r2({
```nix ```nix
(builtins.getFlake "github:edolstra/dwarffs").rev (builtins.getFlake "github:edolstra/dwarffs").rev
``` ```
This function is only available if you enable the experimental feature
`flakes`.
)", )",
.fun = prim_getFlake, .fun = prim_getFlake,
.experimentalFeature = Xp::Flakes, .experimentalFeature = Xp::Flakes,

View file

@ -238,7 +238,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
} }
} }
static RegisterPrimOp primop_scopedImport(RegisterPrimOp::Info { static RegisterPrimOp primop_scopedImport(PrimOp {
.name = "scopedImport", .name = "scopedImport",
.arity = 2, .arity = 2,
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v) .fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
@ -692,7 +692,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
v.listElems()[n++] = i; v.listElems()[n++] = i;
} }
static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info { static RegisterPrimOp primop_genericClosure(PrimOp {
.name = "__genericClosure", .name = "__genericClosure",
.args = {"attrset"}, .args = {"attrset"},
.arity = 1, .arity = 1,
@ -809,7 +809,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
} }
} }
static RegisterPrimOp primop_addErrorContext(RegisterPrimOp::Info { static RegisterPrimOp primop_addErrorContext(PrimOp {
.name = "__addErrorContext", .name = "__addErrorContext",
.arity = 2, .arity = 2,
.fun = prim_addErrorContext, .fun = prim_addErrorContext,
@ -1400,7 +1400,7 @@ drvName, Bindings * attrs, Value & v)
v.mkAttrs(result); v.mkAttrs(result);
} }
static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info { static RegisterPrimOp primop_derivationStrict(PrimOp {
.name = "derivationStrict", .name = "derivationStrict",
.arity = 1, .arity = 1,
.fun = prim_derivationStrict, .fun = prim_derivationStrict,
@ -1667,9 +1667,52 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos))); v.mkPath(state.checkSourcePath(state.findFile(searchPath, path, pos)));
} }
static RegisterPrimOp primop_findFile(RegisterPrimOp::Info { static RegisterPrimOp primop_findFile(PrimOp {
.name = "__findFile", .name = "__findFile",
.arity = 2, .args = {"search path", "lookup path"},
.doc = R"(
Look up the given path with the given search path.
A search path is represented list of [attribute sets](./values.md#attribute-set) with two attributes, `prefix`, and `path`.
`prefix` is a relative path.
`path` denotes a file system location; the exact syntax depends on the command line interface.
Examples of search path attribute sets:
- ```
{
prefix = "nixos-config";
path = "/etc/nixos/configuration.nix";
}
```
- ```
{
prefix = "";
path = "/nix/var/nix/profiles/per-user/root/channels";
}
```
The lookup algorithm checks each entry until a match is found, returning a [path value](@docroot@/language/values.html#type-path) of the match.
This is the process for each entry:
If the lookup path matches `prefix`, then the remainder of the lookup path (the "suffix") is searched for within the directory denoted by `patch`.
Note that the `path` may need to be downloaded at this point to look inside.
If the suffix is found inside that directory, then the entry is a match;
the combined absolute path of the directory (now downloaded if need be) and the suffix is returned.
The syntax
```nix
<nixpkgs>
```
is equivalent to:
```nix
builtins.findFile builtins.nixPath "nixpkgs"
```
)",
.fun = prim_findFile, .fun = prim_findFile,
}); });
@ -2388,7 +2431,7 @@ static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value * *
state.mkPos(v, i->pos); state.mkPos(v, i->pos);
} }
static RegisterPrimOp primop_unsafeGetAttrPos(RegisterPrimOp::Info { static RegisterPrimOp primop_unsafeGetAttrPos(PrimOp {
.name = "__unsafeGetAttrPos", .name = "__unsafeGetAttrPos",
.arity = 2, .arity = 2,
.fun = prim_unsafeGetAttrPos, .fun = prim_unsafeGetAttrPos,
@ -4061,10 +4104,10 @@ static RegisterPrimOp primop_splitVersion({
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps; RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
RegisterPrimOp::RegisterPrimOp(Info && info) RegisterPrimOp::RegisterPrimOp(PrimOp && primOp)
{ {
if (!primOps) primOps = new PrimOps; if (!primOps) primOps = new PrimOps;
primOps->push_back(std::move(info)); primOps->push_back(std::move(primOp));
} }
@ -4077,54 +4120,202 @@ void EvalState::createBaseEnv()
/* `builtins' must be first! */ /* `builtins' must be first! */
v.mkAttrs(buildBindings(128).finish()); v.mkAttrs(buildBindings(128).finish());
addConstant("builtins", v); addConstant("builtins", v, {
.type = nAttrs,
.doc = R"(
Contains all the [built-in functions](@docroot@/language/builtins.md) and values.
Since built-in functions were added over time, [testing for attributes](./operators.md#has-attribute) in `builtins` can be used for graceful fallback on older Nix installations:
```nix
# if hasContext is not available, we assume `s` has a context
if builtins ? hasContext then builtins.hasContext s else true
```
)",
});
v.mkBool(true); v.mkBool(true);
addConstant("true", v); addConstant("true", v, {
.type = nBool,
.doc = R"(
Primitive value.
It can be returned by
[comparison operators](@docroot@/language/operators.md#Comparison)
and used in
[conditional expressions](@docroot@/language/constructs.md#Conditionals).
The name `true` is not special, and can be shadowed:
```nix-repl
nix-repl> let true = 1; in true
1
```
)",
});
v.mkBool(false); v.mkBool(false);
addConstant("false", v); addConstant("false", v, {
.type = nBool,
.doc = R"(
Primitive value.
It can be returned by
[comparison operators](@docroot@/language/operators.md#Comparison)
and used in
[conditional expressions](@docroot@/language/constructs.md#Conditionals).
The name `false` is not special, and can be shadowed:
```nix-repl
nix-repl> let false = 1; in false
1
```
)",
});
v.mkNull(); v.mkNull();
addConstant("null", v); addConstant("null", v, {
.type = nNull,
.doc = R"(
Primitive value.
The name `null` is not special, and can be shadowed:
```nix-repl
nix-repl> let null = 1; in null
1
```
)",
});
if (!evalSettings.pureEval) { if (!evalSettings.pureEval) {
v.mkInt(time(0)); v.mkInt(time(0));
addConstant("__currentTime", v);
v.mkString(settings.thisSystem.get());
addConstant("__currentSystem", v);
} }
addConstant("__currentTime", v, {
.type = nInt,
.doc = R"(
Return the [Unix time](https://en.wikipedia.org/wiki/Unix_time) at first evaluation.
Repeated references to that name will re-use the initially obtained value.
Example:
```console
$ nix repl
Welcome to Nix 2.15.1 Type :? for help.
nix-repl> builtins.currentTime
1683705525
nix-repl> builtins.currentTime
1683705525
```
The [store path](@docroot@/glossary.md#gloss-store-path) of a derivation depending on `currentTime` will differ for each evaluation, unless both evaluate `builtins.currentTime` in the same second.
)",
.impureOnly = true,
});
if (!evalSettings.pureEval) {
v.mkString(settings.thisSystem.get());
}
addConstant("__currentSystem", v, {
.type = nString,
.doc = R"(
The value of the [`system` configuration option](@docroot@/command-ref/conf-file.md#conf-pure-eval).
It can be used to set the `system` attribute for [`builtins.derivation`](@docroot@/language/derivations.md) such that the resulting derivation can be built on the same system that evaluates the Nix expression:
```nix
builtins.derivation {
# ...
system = builtins.currentSystem;
}
```
It can be overridden in order to create derivations for different system than the current one:
```console
$ nix-instantiate --system "mips64-linux" --eval --expr 'builtins.currentSystem'
"mips64-linux"
```
)",
.impureOnly = true,
});
v.mkString(nixVersion); v.mkString(nixVersion);
addConstant("__nixVersion", v); addConstant("__nixVersion", v, {
.type = nString,
.doc = R"(
The version of Nix.
For example, where the command line returns the current Nix version,
```shell-session
$ nix --version
nix (Nix) 2.16.0
```
the Nix language evaluator returns the same value:
```nix-repl
nix-repl> builtins.nixVersion
"2.16.0"
```
)",
});
v.mkString(store->storeDir); v.mkString(store->storeDir);
addConstant("__storeDir", v); addConstant("__storeDir", v, {
.type = nString,
.doc = R"(
Logical file system location of the [Nix store](@docroot@/glossary.md#gloss-store) currently in use.
This value is determined by the `store` parameter in [Store URLs](@docroot@/command-ref/new-cli/nix3-help-stores.md):
```shell-session
$ nix-instantiate --store 'dummy://?store=/blah' --eval --expr builtins.storeDir
"/blah"
```
)",
});
/* Language version. This should be increased every time a new /* Language version. This should be increased every time a new
language feature gets added. It's not necessary to increase it language feature gets added. It's not necessary to increase it
when primops get added, because you can just use `builtins ? when primops get added, because you can just use `builtins ?
primOp' to check. */ primOp' to check. */
v.mkInt(6); v.mkInt(6);
addConstant("__langVersion", v); addConstant("__langVersion", v, {
.type = nInt,
.doc = R"(
The current version of the Nix language.
)",
});
// Miscellaneous // Miscellaneous
if (evalSettings.enableNativeCode) { if (evalSettings.enableNativeCode) {
addPrimOp("__importNative", 2, prim_importNative); addPrimOp({
addPrimOp("__exec", 1, prim_exec); .name = "__importNative",
.arity = 2,
.fun = prim_importNative,
});
addPrimOp({
.name = "__exec",
.arity = 1,
.fun = prim_exec,
});
} }
addPrimOp({ addPrimOp({
.fun = evalSettings.traceVerbose ? prim_trace : prim_second,
.arity = 2,
.name = "__traceVerbose", .name = "__traceVerbose",
.args = { "e1", "e2" }, .args = { "e1", "e2" },
.arity = 2,
.doc = R"( .doc = R"(
Evaluate *e1* and print its abstract syntax representation on standard Evaluate *e1* and print its abstract syntax representation on standard
error if `--trace-verbose` is enabled. Then return *e2*. This function error if `--trace-verbose` is enabled. Then return *e2*. This function
is useful for debugging. is useful for debugging.
)", )",
.fun = evalSettings.traceVerbose ? prim_trace : prim_second,
}); });
/* Add a value containing the current Nix expression search path. */ /* Add a value containing the current Nix expression search path. */
@ -4136,26 +4327,46 @@ void EvalState::createBaseEnv()
attrs.alloc("prefix").mkString(i.prefix); attrs.alloc("prefix").mkString(i.prefix);
(v.listElems()[n++] = allocValue())->mkAttrs(attrs); (v.listElems()[n++] = allocValue())->mkAttrs(attrs);
} }
addConstant("__nixPath", v); addConstant("__nixPath", v, {
.type = nList,
.doc = R"(
The search path used to resolve angle bracket path lookups.
Angle bracket expressions can be
[desugared](https://en.wikipedia.org/wiki/Syntactic_sugar)
using this and
[`builtins.findFile`](./builtins.html#builtins-findFile):
```nix
<nixpkgs>
```
is equivalent to:
```nix
builtins.findFile builtins.nixPath "nixpkgs"
```
)",
});
if (RegisterPrimOp::primOps) if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps) for (auto & primOp : *RegisterPrimOp::primOps)
if (!primOp.experimentalFeature if (experimentalFeatureSettings.isEnabled(primOp.experimentalFeature))
|| experimentalFeatureSettings.isEnabled(*primOp.experimentalFeature))
{ {
addPrimOp({ auto primOpAdjusted = primOp;
.fun = primOp.fun, primOpAdjusted.arity = std::max(primOp.args.size(), primOp.arity);
.arity = std::max(primOp.args.size(), primOp.arity), addPrimOp(std::move(primOpAdjusted));
.name = primOp.name,
.args = primOp.args,
.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.
Null docs because it is documented separately.
*/
auto vDerivation = allocValue(); auto vDerivation = allocValue();
addConstant("derivation", vDerivation); addConstant("derivation", vDerivation, {
.type = nFunction,
});
/* Now that we've added all primops, sort the `builtins' set, /* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */ because attribute lookups expect it to be sorted. */

View file

@ -10,17 +10,7 @@ namespace nix {
struct RegisterPrimOp struct RegisterPrimOp
{ {
struct Info typedef std::vector<PrimOp> PrimOps;
{
std::string name;
std::vector<std::string> args;
size_t arity = 0;
const char * doc;
PrimOpFun fun;
std::optional<ExperimentalFeature> experimentalFeature;
};
typedef std::vector<Info> PrimOps;
static PrimOps * primOps; static PrimOps * primOps;
/** /**
@ -28,7 +18,7 @@ struct RegisterPrimOp
* will get called during EvalState initialization, so there * will get called during EvalState initialization, so there
* may be primops not yet added and builtins is not yet sorted. * may be primops not yet added and builtins is not yet sorted.
*/ */
RegisterPrimOp(Info && info); RegisterPrimOp(PrimOp && primOp);
}; };
/* These primops are disabled without enableNativeCode, but plugins /* These primops are disabled without enableNativeCode, but plugins

View file

@ -154,9 +154,6 @@ static RegisterPrimOp primop_fetchClosure({
specifying a binary cache from which the path can be fetched. specifying a binary cache from which the path can be fetched.
Also, requiring a content-addressed final store path avoids the Also, requiring a content-addressed final store path avoids the
need for users to configure binary cache public keys. need for users to configure binary cache public keys.
This function is only available if you enable the experimental
feature `fetch-closure`.
)", )",
.fun = prim_fetchClosure, .fun = prim_fetchClosure,
.experimentalFeature = Xp::FetchClosure, .experimentalFeature = Xp::FetchClosure,

View file

@ -218,8 +218,11 @@ public:
/** /**
* Returns the normal type of a Value. This only returns nThunk if * Returns the normal type of a Value. This only returns nThunk if
* the Value hasn't been forceValue'd * the Value hasn't been forceValue'd
*
* @param invalidIsThunk Instead of aborting an an invalid (probably
* 0, so uninitialized) internal type, return `nThunk`.
*/ */
inline ValueType type() const inline ValueType type(bool invalidIsThunk = false) const
{ {
switch (internalType) { switch (internalType) {
case tInt: return nInt; case tInt: return nInt;
@ -234,7 +237,10 @@ public:
case tFloat: return nFloat; case tFloat: return nFloat;
case tThunk: case tApp: case tBlackhole: return nThunk; case tThunk: case tApp: case tBlackhole: return nThunk;
} }
abort(); if (invalidIsThunk)
return nThunk;
else
abort();
} }
/** /**

View file

@ -352,7 +352,7 @@ void mainWrapped(int argc, char * * argv)
return; return;
} }
if (argc == 2 && std::string(argv[1]) == "__dump-builtins") { if (argc == 2 && std::string(argv[1]) == "__dump-language") {
experimentalFeatureSettings.experimentalFeatures = { experimentalFeatureSettings.experimentalFeatures = {
Xp::Flakes, Xp::Flakes,
Xp::FetchClosure, Xp::FetchClosure,
@ -360,17 +360,34 @@ void mainWrapped(int argc, char * * argv)
evalSettings.pureEval = false; evalSettings.pureEval = false;
EvalState state({}, openStore("dummy://")); EvalState state({}, openStore("dummy://"));
auto res = nlohmann::json::object(); auto res = nlohmann::json::object();
auto builtins = state.baseEnv.values[0]->attrs; res["builtins"] = ({
for (auto & builtin : *builtins) { auto builtinsJson = nlohmann::json::object();
auto b = nlohmann::json::object(); auto builtins = state.baseEnv.values[0]->attrs;
if (!builtin.value->isPrimOp()) continue; for (auto & builtin : *builtins) {
auto primOp = builtin.value->primOp; auto b = nlohmann::json::object();
if (!primOp->doc) continue; if (!builtin.value->isPrimOp()) continue;
b["arity"] = primOp->arity; auto primOp = builtin.value->primOp;
b["args"] = primOp->args; if (!primOp->doc) continue;
b["doc"] = trim(stripIndentation(primOp->doc)); b["arity"] = primOp->arity;
res[state.symbols[builtin.name]] = std::move(b); b["args"] = primOp->args;
} b["doc"] = trim(stripIndentation(primOp->doc));
b["experimental-feature"] = primOp->experimentalFeature;
builtinsJson[state.symbols[builtin.name]] = std::move(b);
}
std::move(builtinsJson);
});
res["constants"] = ({
auto constantsJson = nlohmann::json::object();
for (auto & [name, info] : state.constantInfos) {
auto c = nlohmann::json::object();
if (!info.doc) continue;
c["doc"] = trim(stripIndentation(info.doc));
c["type"] = showType(info.type, false);
c["impure-only"] = info.impureOnly;
constantsJson[name] = std::move(c);
}
std::move(constantsJson);
});
logger->cout("%s", res); logger->cout("%s", res);
return; return;
} }