Compare commits

...

3 Commits

Author SHA1 Message Date
John Ericson
ddca102f8b Move StringData to its own header
Now that it isn't just used for `Value`, it doesn't really belong in
there.

Rename `static-string-data.hh` to share the same prefix to keep them
close together when sorting lines, also.
2025-11-18 14:01:32 -05:00
John Ericson
351e3870c9 Use StringData for Nix language value documentation too
This probably isn't much of a perf gain, but it does mean we are
completely rid of non-length prefixed strings. That allows us to delete
some code.
2025-11-18 14:01:32 -05:00
John Ericson
1ea6a71b0c Improve formatting of primop_fromTOML
Trailing comma helps a lot.
2025-11-18 14:01:32 -05:00
22 changed files with 356 additions and 376 deletions

View File

@@ -656,7 +656,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
+ "\n\n";
}
markdown += stripIndentation(doc->doc);
markdown += stripIndentation(doc->doc.view());
logger->cout(trim(renderMarkdownToTerminal(markdown)));
} else if (fallbackPos) {

View File

@@ -134,7 +134,7 @@ PrimOp * nix_alloc_primop(
.name = name,
.args = {},
.arity = (size_t) arity,
.doc = doc,
.doc = &nix::StringData::make(doc),
.fun = std::bind(nix_c_primop_wrapper, fun, user_data, _1, _2, _3, _4)};
if (args)
for (size_t i = 0; args[i]; i++)

View File

@@ -1,6 +1,6 @@
#include "nix/expr/tests/libexpr.hh"
#include "nix/expr/value-to-json.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
namespace nix {
// Testing the conversion to JSON

View File

@@ -1,5 +1,5 @@
#include "nix/expr/tests/libexpr.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/expr/value.hh"
#include "nix/expr/print.hh"

View File

@@ -1,5 +1,5 @@
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/store/tests/libstore.hh"
#include <gtest/gtest.h>

View File

@@ -51,36 +51,6 @@ using json = nlohmann::json;
namespace nix {
/**
* Just for doc strings. Not for regular string values.
*/
static char * allocString(size_t size)
{
char * t;
t = (char *) GC_MALLOC_ATOMIC(size);
if (!t)
throw std::bad_alloc();
return t;
}
// When there's no need to write to the string, we can optimize away empty
// string allocations.
// This function handles makeImmutableString(std::string_view()) by returning
// the empty string.
/**
* Just for doc strings. Not for regular string values.
*/
static const char * makeImmutableString(std::string_view s)
{
const size_t size = s.size();
if (size == 0)
return "";
auto t = allocString(size + 1);
memcpy(t, s.data(), size);
t[size] = '\0';
return t;
}
StringData & StringData::alloc(size_t size)
{
void * t = GC_MALLOC_ATOMIC(sizeof(StringData) + size + 1);
@@ -571,7 +541,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
.name = v2->primOp()->name,
.arity = v2->primOp()->arity,
.args = v2->primOp()->args,
.doc = doc,
.doc = *doc,
};
}
if (v.isLambda()) {
@@ -613,9 +583,8 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
.name = name,
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
.args = {},
/* N.B. Can't use StringData here, because that would lead to an interior pointer.
NOTE: memory leak when compiled without GC. */
.doc = makeImmutableString(s.view()),
/* NOTE: memory leak when compiled without GC. */
.doc = StringData::make(s.view()),
};
}
if (isFunctor(v)) {

View File

@@ -108,7 +108,7 @@ struct PrimOp
/**
* Optional free-form documentation about the primop.
*/
const char * doc = nullptr;
const StringData * doc = nullptr;
/**
* Add a trace item, while calling the `<name>` builtin.
@@ -156,7 +156,7 @@ struct Constant
/**
* Optional free-form documentation about the constant.
*/
const char * doc = nullptr;
const StringData * doc = nullptr;
/**
* Whether the constant is impure, and not available in pure mode.
@@ -837,11 +837,7 @@ public:
std::optional<std::string> name;
size_t arity;
std::vector<std::string> args;
/**
* Unlike the other `doc` fields in this file, this one should never be
* `null`.
*/
const char * doc;
const StringData & doc;
};
/**

View File

@@ -31,7 +31,8 @@ headers = [ config_pub_h ] + files(
'print.hh',
'repl-exit-status.hh',
'search-path.hh',
'static-string-data.hh',
'string-data.hh',
'string-data-static.hh',
'symbol-table.hh',
'value-to-json.hh',
'value-to-xml.hh',

View File

@@ -12,7 +12,7 @@
#include "nix/expr/value.hh"
#include "nix/expr/symbol-table.hh"
#include "nix/expr/eval-error.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/util/pos-idx.hh"
#include "nix/expr/counter.hh"
#include "nix/util/pos-table.hh"

View File

@@ -5,7 +5,7 @@
#include "nix/expr/eval.hh"
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
namespace nix {

View File

@@ -1,7 +1,7 @@
#pragma once
///@file
#include "nix/expr/value.hh"
#include "nix/expr/string-data.hh"
namespace nix {

View File

@@ -0,0 +1,96 @@
#pragma once
///@file
#include <cstddef>
#include <cstring>
#include <memory_resource>
#include <string_view>
namespace nix {
class StringData
{
public:
using size_type = std::size_t;
size_type size_;
char data_[];
/*
* This in particular ensures that we cannot have a `StringData`
* that we use by value, which is just what we want!
*
* Dynamically sized types aren't a thing in C++ and even flexible array
* members are a language extension and beyond the realm of standard C++.
* Technically, sizeof data_ member is 0 and the intended way to use flexible
* array members is to allocate sizeof(StrindData) + count * sizeof(char) bytes
* and the compiler will consider alignment restrictions for the FAM.
*
*/
StringData(StringData &&) = delete;
StringData & operator=(StringData &&) = delete;
StringData(const StringData &) = delete;
StringData & operator=(const StringData &) = delete;
~StringData() = default;
private:
StringData() = delete;
explicit StringData(size_type size)
: size_(size)
{
}
public:
/**
* Allocate StringData on the (possibly) GC-managed heap and copy
* the contents of s to it.
*/
static const StringData & make(std::string_view s);
/**
* Allocate StringData on the (possibly) GC-managed heap.
* @param size Length of the string (without the NUL terminator).
*/
static StringData & alloc(size_t size);
size_t size() const
{
return size_;
}
char * data() noexcept
{
return data_;
}
const char * data() const noexcept
{
return data_;
}
const char * c_str() const noexcept
{
return data_;
}
constexpr std::string_view view() const noexcept
{
return std::string_view(data_, size_);
}
template<size_t N>
struct Static;
static StringData & make(std::pmr::memory_resource & resource, std::string_view s)
{
auto & res =
*new (resource.allocate(sizeof(StringData) + s.size() + 1, alignof(StringData))) StringData(s.size());
std::memcpy(res.data_, s.data(), s.size());
res.data_[s.size()] = '\0';
return res;
}
};
} // namespace nix

View File

@@ -3,7 +3,7 @@
#include <memory_resource>
#include "nix/expr/value.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/util/chunked-vector.hh"
#include "nix/util/error.hh"

View File

@@ -3,16 +3,14 @@
#include <bit>
#include <cassert>
#include <cstddef>
#include <cstring>
#include <memory>
#include <memory_resource>
#include <span>
#include <string_view>
#include <type_traits>
#include <concepts>
#include "nix/expr/eval-gc.hh"
#include "nix/expr/string-data.hh"
#include "nix/expr/value/context.hh"
#include "nix/util/source-path.hh"
#include "nix/expr/print-options.hh"
@@ -193,91 +191,6 @@ public:
friend struct Value;
};
class StringData
{
public:
using size_type = std::size_t;
size_type size_;
char data_[];
/*
* This in particular ensures that we cannot have a `StringData`
* that we use by value, which is just what we want!
*
* Dynamically sized types aren't a thing in C++ and even flexible array
* members are a language extension and beyond the realm of standard C++.
* Technically, sizeof data_ member is 0 and the intended way to use flexible
* array members is to allocate sizeof(StrindData) + count * sizeof(char) bytes
* and the compiler will consider alignment restrictions for the FAM.
*
*/
StringData(StringData &&) = delete;
StringData & operator=(StringData &&) = delete;
StringData(const StringData &) = delete;
StringData & operator=(const StringData &) = delete;
~StringData() = default;
private:
StringData() = delete;
explicit StringData(size_type size)
: size_(size)
{
}
public:
/**
* Allocate StringData on the (possibly) GC-managed heap and copy
* the contents of s to it.
*/
static const StringData & make(std::string_view s);
/**
* Allocate StringData on the (possibly) GC-managed heap.
* @param size Length of the string (without the NUL terminator).
*/
static StringData & alloc(size_t size);
size_t size() const
{
return size_;
}
char * data() noexcept
{
return data_;
}
const char * data() const noexcept
{
return data_;
}
const char * c_str() const noexcept
{
return data_;
}
constexpr std::string_view view() const noexcept
{
return std::string_view(data_, size_);
}
template<size_t N>
struct Static;
static StringData & make(std::pmr::memory_resource & resource, std::string_view s)
{
auto & res =
*new (resource.allocate(sizeof(StringData) + s.size() + 1, alignof(StringData))) StringData(s.size());
std::memcpy(res.data_, s.data(), s.size());
res.data_[s.size()] = '\0';
return res;
}
};
namespace detail {
/**

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
#include "nix/expr/primops.hh"
#include "nix/expr/eval-inline.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/store/derivations.hh"
#include "nix/store/store-api.hh"
#include "nix/store/globals.hh"
@@ -17,9 +18,9 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos,
static RegisterPrimOp primop_unsafeDiscardStringContext({
.name = "__unsafeDiscardStringContext",
.args = {"s"},
.doc = R"(
.doc = &R"(
Discard the [string context](@docroot@/language/string-context.md) from a value that can be coerced to a string.
)",
)"_sds,
.fun = prim_unsafeDiscardStringContext,
});
@@ -33,7 +34,7 @@ static void prim_hasContext(EvalState & state, const PosIdx pos, Value ** args,
static RegisterPrimOp primop_hasContext(
{.name = "__hasContext",
.args = {"s"},
.doc = R"(
.doc = &R"(
Return `true` if string *s* has a non-empty context.
The context can be obtained with
[`getContext`](#builtins-getContext).
@@ -50,7 +51,7 @@ static RegisterPrimOp primop_hasContext(
> then throw "package name cannot contain string context"
> else { ${name} = meta; }
> ```
)",
)"_sds,
.fun = prim_hasContext});
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value ** args, Value & v)
@@ -75,7 +76,7 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx p
static RegisterPrimOp primop_unsafeDiscardOutputDependency(
{.name = "__unsafeDiscardOutputDependency",
.args = {"s"},
.doc = R"(
.doc = &R"(
Create a copy of the given string where every
[derivation deep](@docroot@/language/string-context.md#string-context-element-derivation-deep)
string context element is turned into a
@@ -91,7 +92,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency(
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
)",
)"_sds,
.fun = prim_unsafeDiscardOutputDependency});
static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value ** args, Value & v)
@@ -143,7 +144,7 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
static RegisterPrimOp primop_addDrvOutputDependencies(
{.name = "__addDrvOutputDependencies",
.args = {"s"},
.doc = R"(
.doc = &R"(
Create a copy of the given string where a single
[constant](@docroot@/language/string-context.md#string-context-constant)
string context element is turned into a
@@ -156,7 +157,7 @@ static RegisterPrimOp primop_addDrvOutputDependencies(
The latter is supported so this function is idempotent.
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency).
)",
)"_sds,
.fun = prim_addDrvOutputDependencies});
/* Extract the context of a string as a structured Nix value.
@@ -230,7 +231,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value ** args,
static RegisterPrimOp primop_getContext(
{.name = "__getContext",
.args = {"s"},
.doc = R"(
.doc = &R"(
Return the string context of *s*.
The string context tracks references to derivations within a string.
@@ -248,7 +249,7 @@ static RegisterPrimOp primop_getContext(
```
{ "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; }
```
)",
)"_sds,
.fun = prim_getContext});
/* Append the given context to a given string.

View File

@@ -1,4 +1,5 @@
#include "nix/expr/primops.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/store/store-open.hh"
#include "nix/store/realisation.hh"
#include "nix/store/make-content-addressed.hh"
@@ -216,7 +217,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value ** args
static RegisterPrimOp primop_fetchClosure({
.name = "__fetchClosure",
.args = {"args"},
.doc = R"(
.doc = &R"(
Fetch a store path [closure](@docroot@/glossary.md#gloss-closure) from a binary cache, and return the store path as a string with context.
This function can be invoked in three ways that we will discuss in order of preference.
@@ -284,7 +285,7 @@ static RegisterPrimOp primop_fetchClosure({
`fetchClosure` is similar to [`builtins.storePath`](#builtins-storePath) in that it allows you to use a previously built store path in a Nix expression.
However, `fetchClosure` is more reproducible because it specifies a binary cache from which the path can be fetched.
Also, using content-addressed store paths does not require users to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
)",
)"_sds,
.fun = prim_fetchClosure,
.experimentalFeature = Xp::FetchClosure,
});

View File

@@ -2,6 +2,7 @@
#include "nix/expr/primops.hh"
#include "nix/expr/eval-inline.hh"
#include "nix/expr/eval-settings.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/store/store-api.hh"
#include "nix/fetchers/fetchers.hh"
#include "nix/store/filetransfer.hh"
@@ -235,7 +236,7 @@ static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, V
static RegisterPrimOp primop_fetchTree({
.name = "fetchTree",
.args = {"input"},
.doc = R"(
.doc = &R"(
Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with:
- the resulting fixed-output [store path](@docroot@/store/store-path.md)
@@ -457,7 +458,7 @@ static RegisterPrimOp primop_fetchTree({
> ```nix
> builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
> ```
)",
)"_sds,
.fun = prim_fetchTree,
.experimentalFeature = Xp::FetchTree,
});
@@ -617,7 +618,7 @@ static void prim_fetchurl(EvalState & state, const PosIdx pos, Value ** args, Va
static RegisterPrimOp primop_fetchurl({
.name = "__fetchurl",
.args = {"arg"},
.doc = R"(
.doc = &R"(
Download the specified URL and return the path of the downloaded file.
`arg` can be either a string denoting the URL, or an attribute set with the following attributes:
@@ -631,7 +632,7 @@ static RegisterPrimOp primop_fetchurl({
characters that are invalid for the store.
Not available in [restricted evaluation mode](@docroot@/command-ref/conf-file.md#conf-restrict-eval).
)",
)"_sds,
.fun = prim_fetchurl,
});
@@ -643,7 +644,7 @@ static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value ** args
static RegisterPrimOp primop_fetchTarball({
.name = "fetchTarball",
.args = {"args"},
.doc = R"(
.doc = &R"(
Download the specified URL, unpack it and return the path of the
unpacked tree. The file must be a tape archive (`.tar`) compressed
with `gzip`, `bzip2` or `xz`. If the tarball consists of a
@@ -681,7 +682,7 @@ static RegisterPrimOp primop_fetchTarball({
```
Not available in [restricted evaluation mode](@docroot@/command-ref/conf-file.md#conf-restrict-eval).
)",
)"_sds,
.fun = prim_fetchTarball,
});
@@ -694,7 +695,7 @@ static void prim_fetchGit(EvalState & state, const PosIdx pos, Value ** args, Va
static RegisterPrimOp primop_fetchGit({
.name = "fetchGit",
.args = {"args"},
.doc = R"(
.doc = &R"(
Fetch a path from git. *args* can be a URL, in which case the HEAD
of the repo at that URL is fetched. Otherwise, it can be an
attribute with the following attributes (all except `url` optional):
@@ -897,7 +898,7 @@ static RegisterPrimOp primop_fetchGit({
given, `fetchGit` uses the current content of the checked-out
files, even if they are not committed or added to Git's index. It
only considers files added to the Git repository, as listed by `git ls-files`.
)",
)"_sds,
.fun = prim_fetchGit,
});

View File

@@ -1,6 +1,6 @@
#include "nix/expr/primops.hh"
#include "nix/expr/eval-inline.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
#include "expr-config-private.hh"
@@ -170,10 +170,10 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
}
}
static RegisterPrimOp primop_fromTOML(
{.name = "fromTOML",
.args = {"e"},
.doc = R"(
static RegisterPrimOp primop_fromTOML({
.name = "fromTOML",
.args = {"e"},
.doc = &R"(
Convert a TOML string to a Nix value. For example,
```nix
@@ -186,7 +186,8 @@ static RegisterPrimOp primop_fromTOML(
```
returns the value `{ s = "a"; table = { y = 2; }; x = 1; }`.
)",
.fun = prim_fromTOML});
)"_sds,
.fun = prim_fromTOML,
});
} // namespace nix

View File

@@ -19,6 +19,7 @@
#include "nix/expr/eval-settings.hh"
#include "nix/expr/symbol-table.hh"
#include "nix/expr/value.hh"
#include "nix/expr/string-data-static.hh"
#include "nix/fetchers/attrs.hh"
#include "nix/fetchers/fetchers.hh"
#include "nix/util/configuration.hh"
@@ -62,7 +63,7 @@ PrimOp getFlake(const Settings & settings)
return PrimOp{
.name = "__getFlake",
.args = {"args"},
.doc = R"(
.doc = &R"(
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
```nix
@@ -76,7 +77,7 @@ PrimOp getFlake(const Settings & settings)
```nix
(builtins.getFlake "github:edolstra/dwarffs").rev
```
)",
)"_sds,
.fun = prim_getFlake,
.experimentalFeature = Xp::Flakes,
};
@@ -104,7 +105,7 @@ static void prim_parseFlakeRef(EvalState & state, const PosIdx pos, Value ** arg
nix::PrimOp parseFlakeRef({
.name = "__parseFlakeRef",
.args = {"flake-ref"},
.doc = R"(
.doc = &R"(
Parse a flake reference, and return its exploded form.
For example:
@@ -118,7 +119,7 @@ nix::PrimOp parseFlakeRef({
```nix
{ dir = "lib"; owner = "NixOS"; ref = "23.05"; repo = "nixpkgs"; type = "github"; }
```
)",
)"_sds,
.fun = prim_parseFlakeRef,
.experimentalFeature = Xp::Flakes,
});
@@ -162,7 +163,7 @@ static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value **
nix::PrimOp flakeRefToString({
.name = "__flakeRefToString",
.args = {"attrs"},
.doc = R"(
.doc = &R"(
Convert a flake reference from attribute set format to URL format.
For example:
@@ -178,7 +179,7 @@ nix::PrimOp flakeRefToString({
```nix
"github:NixOS/nixpkgs/23.05?dir=lib"
```
)",
)"_sds,
.fun = prim_flakeRefToString,
.experimentalFeature = Xp::Flakes,
});

View File

@@ -440,7 +440,7 @@ void mainWrapped(int argc, char ** argv)
if (!primOp->doc)
continue;
b["args"] = primOp->args;
b["doc"] = trim(stripIndentation(primOp->doc));
b["doc"] = trim(stripIndentation(primOp->doc->view()));
if (primOp->experimentalFeature)
b["experimental-feature"] = primOp->experimentalFeature;
builtinsJson.emplace(state.symbols[builtin.name], std::move(b));
@@ -449,7 +449,7 @@ void mainWrapped(int argc, char ** argv)
auto b = nlohmann::json::object();
if (!info.doc)
continue;
b["doc"] = trim(stripIndentation(info.doc));
b["doc"] = trim(stripIndentation(info.doc->view()));
b["type"] = showType(info.type, false);
if (info.impureOnly)
b["impure-only"] = true;

View File

@@ -9,7 +9,7 @@
#include "nix/expr/eval-inline.hh"
#include "nix/store/profiles.hh"
#include "nix/expr/print-ambiguous.hh"
#include "nix/expr/static-string-data.hh"
#include "nix/expr/string-data-static.hh"
#include <limits>
#include <sstream>