libexpr: implement actual constructors for nix::Value

Change-Id: Iebc2bb4e4ea5e93045afe47677df756de4ec4d05
This commit is contained in:
Qyriad 2024-07-16 11:25:05 -06:00 committed by eldritch horrors
parent c347d3df8f
commit ddfca6e81b
4 changed files with 421 additions and 9 deletions

View file

@ -494,6 +494,14 @@ std::ostream & operator<<(std::ostream & output, PrimOp & primOp)
} }
Value::Value(primop_t, PrimOp & primop)
: internalType(tPrimOp)
, primOp(&primop)
, _primop_pad(0)
{
primop.check();
}
PrimOp * Value::primOpAppPrimOp() const PrimOp * Value::primOpAppPrimOp() const
{ {
Value * left = primOpApp.left; Value * left = primOpApp.left;
@ -506,7 +514,6 @@ PrimOp * Value::primOpAppPrimOp() const
return left->primOp; return left->primOp;
} }
void Value::mkPrimOp(PrimOp * p) void Value::mkPrimOp(PrimOp * p)
{ {
p->check(); p->check();

View file

@ -3,6 +3,9 @@
#include <cassert> #include <cassert>
#include <climits> #include <climits>
#include <functional>
#include <ranges>
#include <span>
#include "gc-alloc.hh" #include "gc-alloc.hh"
#include "symbol-table.hh" #include "symbol-table.hh"
@ -11,6 +14,7 @@
#include "source-path.hh" #include "source-path.hh"
#include "print-options.hh" #include "print-options.hh"
#include "checked-arithmetic.hh" #include "checked-arithmetic.hh"
#include "concepts.hh"
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
@ -132,6 +136,55 @@ class ExternalValueBase
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
extern ExprBlackHole eBlackHole;
struct NewValueAs
{
struct integer_t { };
constexpr static integer_t integer{};
struct floating_t { };
constexpr static floating_t floating{};
struct boolean_t { };
constexpr static boolean_t boolean{};
struct string_t { };
constexpr static string_t string{};
struct path_t { };
constexpr static path_t path{};
struct list_t { };
constexpr static list_t list{};
struct attrs_t { };
constexpr static attrs_t attrs{};
struct thunk_t { };
constexpr static thunk_t thunk{};
struct null_t { };
constexpr static null_t null{};
struct app_t { };
constexpr static app_t app{};
struct primop_t { };
constexpr static primop_t primop{};
struct primOpApp_t { };
constexpr static primOpApp_t primOpApp{};
struct lambda_t { };
constexpr static lambda_t lambda{};
struct external_t { };
constexpr static external_t external{};
struct blackhole_t { };
constexpr static blackhole_t blackhole{};
};
struct Value struct Value
{ {
@ -142,6 +195,315 @@ private:
public: public:
// Discount `using NewValueAs::*;`
#define USING_VALUETYPE(name) using name = NewValueAs::name
USING_VALUETYPE(integer_t);
USING_VALUETYPE(floating_t);
USING_VALUETYPE(boolean_t);
USING_VALUETYPE(string_t);
USING_VALUETYPE(path_t);
USING_VALUETYPE(list_t);
USING_VALUETYPE(attrs_t);
USING_VALUETYPE(thunk_t);
USING_VALUETYPE(primop_t);
USING_VALUETYPE(app_t);
USING_VALUETYPE(null_t);
USING_VALUETYPE(primOpApp_t);
USING_VALUETYPE(lambda_t);
USING_VALUETYPE(external_t);
USING_VALUETYPE(blackhole_t);
#undef USING_VALUETYPE
/// Default constructor which is still used in the codebase but should not
/// be used in new code. Zero initializes its members.
[[deprecated]] Value()
: internalType(static_cast<InternalType>(0))
, _empty{ 0, 0 }
{ }
/// Constructs a nix language value of type "int", with the integral value
/// of @ref i.
Value(integer_t, NixInt i)
: internalType(tInt)
, _empty{ 0, 0 }
{
// the NixInt ctor here is is special because NixInt has a ctor too, so
// we're not allowed to have it as an anonymous aggreagte member. we do
// however still have the option to clear the data members using _empty
// and leaving the second word of data cleared by setting only integer.
integer = i;
}
/// Constructs a nix language value of type "float", with the floating
/// point value of @ref f.
Value(floating_t, NixFloat f)
: internalType(tFloat)
, fpoint(f)
, _float_pad(0)
{ }
/// Constructs a nix language value of type "bool", with the boolean
/// value of @ref b.
Value(boolean_t, bool b)
: internalType(tBool)
, boolean(b)
, _bool_pad(0)
{ }
/// Constructs a nix language value of type "string", with the value of the
/// C-string pointed to by @ref strPtr, and optionally with an array of
/// string context pointed to by @ref contextPtr.
///
/// Neither the C-string nor the context array are copied; this constructor
/// assumes suitable memory has already been allocated (with the GC if
/// enabled), and string and context data copied into that memory.
Value(string_t, char const * strPtr, char const ** contextPtr = nullptr)
: internalType(tString)
, string({ .s = strPtr, .context = contextPtr })
{ }
/// Constructx a nix language value of type "string", with a copy of the
/// string data viewed by @ref copyFrom.
///
/// The string data *is* copied from @ref copyFrom, and this constructor
/// performs a dynamic (GC) allocation to do so.
Value(string_t, std::string_view copyFrom, NixStringContext const & context = {})
: internalType(tString)
, string({ .s = gcCopyStringIfNeeded(copyFrom), .context = nullptr })
{
if (context.empty()) {
// It stays nullptr.
return;
}
// Copy the context.
this->string.context = gcAllocType<char const *>(context.size() + 1);
size_t n = 0;
for (NixStringContextElem const & contextElem : context) {
this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string());
n += 1;
}
// Terminator sentinel.
this->string.context[n] = nullptr;
}
/// Constructx a nix language value of type "string", with the value of the
/// C-string pointed to by @ref strPtr, and optionally with a set of string
/// context @ref context.
///
/// The C-string is not copied; this constructor assumes suitable memory
/// has already been allocated (with the GC if enabled), and string data
/// has been copied into that memory. The context data *is* copied from
/// @ref context, and this constructor performs a dynamic (GC) allocation
/// to do so.
Value(string_t, char const * strPtr, NixStringContext const & context)
: internalType(tString)
, string({ .s = strPtr, .context = nullptr })
{
if (context.empty()) {
// It stays nullptr
return;
}
// Copy the context.
this->string.context = gcAllocType<char const *>(context.size() + 1);
size_t n = 0;
for (NixStringContextElem const & contextElem : context) {
this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string());
n += 1;
}
// Terminator sentinel.
this->string.context[n] = nullptr;
}
/// Constructs a nix language value of type "path", with the value of the
/// C-string pointed to by @ref strPtr.
///
/// The C-string is not copied; this constructor assumes suitable memory
/// has already been allocated (with the GC if enabled), and string data
/// has been copied into that memory.
Value(path_t, char const * strPtr)
: internalType(tPath)
, _path(strPtr)
, _path_pad(0)
{ }
/// Constructs a nix language value of type "path", with the path
/// @ref path.
///
/// The data from @ref path *is* copied, and this constructor performs a
/// dynamic (GC) allocation to do so.
Value(path_t, SourcePath const & path)
: internalType(tPath)
, _path(gcCopyStringIfNeeded(path.path.abs()))
, _path_pad(0)
{ }
/// Constructs a nix language value of type "list", with element array
/// @ref items.
///
/// Generally, the data in @ref items is neither deep copied nor shallow
/// copied. This construct assumes the std::span @ref items is a region of
/// memory that has already been allocated (with the GC if enabled), and
/// an array of valid Value pointers has been copied into that memory.
///
/// Howver, as an implementation detail, if @ref items is only 2 items or
/// smaller, the list is stored inline, and the Value pointers in
/// @ref items are shallow copied into this structure, without dynamically
/// allocating memory.
Value(list_t, std::span<Value *> items)
{
if (items.size() == 1) {
this->internalType = tList1;
this->smallList[0] = items[0];
this->smallList[1] = nullptr;
} else if (items.size() == 2) {
this->internalType = tList2;
this->smallList[0] = items[0];
this->smallList[1] = items[1];
} else {
this->internalType = tListN;
this->bigList.size = items.size();
this->bigList.elems = items.data();
}
}
/// Constructs a nix language value of type "list", with an element array
/// initialized by applying @ref transformer to each element in @ref items.
///
/// This allows "in-place" construction of a nix list when some logic is
/// needed to get each Value pointer. This constructor dynamically (GC)
/// allocates memory for the size of @ref items, and the Value pointers
/// returned by @ref transformer are shallow copied into it.
template<
std::ranges::sized_range SizedIterableT,
InvocableR<Value *, typename SizedIterableT::value_type const &> TransformerT
>
Value(list_t, SizedIterableT & items, TransformerT const & transformer)
{
if (items.size() == 1) {
this->internalType = tList1;
this->smallList[0] = transformer(*items.begin());
this->smallList[1] = nullptr;
} else if (items.size() == 2) {
this->internalType = tList2;
auto it = items.begin();
this->smallList[0] = transformer(*it);
it++;
this->smallList[1] = transformer(*it);
} else {
this->internalType = tListN;
this->bigList.size = items.size();
this->bigList.elems = gcAllocType<Value *>(items.size());
auto it = items.begin();
for (size_t i = 0; i < items.size(); i++, it++) {
this->bigList.elems[i] = transformer(*it);
}
}
}
/// Constructs a nix language value of the singleton type "null".
Value(null_t)
: internalType(tNull)
, _empty{0, 0}
{ }
/// Constructs a nix language value of type "set", with the attribute
/// bindings pointed to by @ref bindings.
///
/// The bindings are not not copied; this constructor assumes @ref bindings
/// has already been suitably allocated by something like nix::buildBindings.
Value(attrs_t, Bindings * bindings)
: internalType(tAttrs)
, attrs(bindings)
, _attrs_pad(0)
{ }
/// Constructs a nix language lazy delayed computation, or "thunk".
///
/// The thunk stores the environment it will be computed in @ref env, and
/// the expression that will need to be evaluated @ref expr.
Value(thunk_t, Env & env, Expr & expr)
: internalType(tThunk)
, thunk({ .env = &env, .expr = &expr })
{ }
/// Constructs a nix language value of type "lambda", which represents
/// a builtin, primitive operation ("primop"), from the primop
/// implemented by @ref primop.
Value(primop_t, PrimOp & primop);
/// Constructs a nix language value of type "lambda", which represents a
/// partially applied primop.
Value(primOpApp_t, Value & lhs, Value & rhs)
: internalType(tPrimOpApp)
, primOpApp({ .left = &lhs, .right = &rhs })
{ }
/// Constructs a nix language value of type "lambda", which represents a
/// lazy partial application of another lambda.
Value(app_t, Value & lhs, Value & rhs)
: internalType(tApp)
, app({ .left = &lhs, .right = &rhs })
{ }
/// Constructs a nix language value of type "external", which is only used
/// by plugins. Do any existing plugins even use this mechanism?
Value(external_t, ExternalValueBase & external)
: internalType(tExternal)
, external(&external)
, _external_pad(0)
{ }
/// Constructs a nix language value of type "lambda", which represents a
/// run of the mill lambda defined in nix code.
///
/// This takes the environment the lambda is closed over @ref env, and
/// the lambda expression itself @ref lambda, which will not be evaluated
/// until it is applied.
Value(lambda_t, Env & env, ExprLambda & lambda)
: internalType(tLambda)
, lambda({ .env = &env, .fun = &lambda })
{ }
/// Constructs an evil thunk, whose evaluation represents infinite recursion.
explicit Value(blackhole_t)
: internalType(tThunk)
, thunk({ .env = nullptr, .expr = reinterpret_cast<Expr *>(&eBlackHole) })
{ }
Value(Value const & rhs) = default;
/// Move constructor. Does the same thing as the copy constructor, but
/// also zeroes out the other Value.
Value(Value && rhs)
: internalType(rhs.internalType)
, _empty{ 0, 0 }
{
*this = std::move(rhs);
}
Value & operator=(Value const & rhs) = default;
/// Move assignment operator.
/// Does the same thing as the copy assignment operator, but also zeroes out
/// the rhs.
inline Value & operator=(Value && rhs)
{
*this = static_cast<const Value &>(rhs);
if (this != &rhs) {
// Kill `rhs`, because non-destructive move lol.
rhs.internalType = static_cast<InternalType>(0);
rhs._empty[0] = 0;
rhs._empty[1] = 0;
}
return *this;
}
void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {}); void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {});
// Functions needed to distinguish the type // Functions needed to distinguish the type
@ -160,8 +522,15 @@ public:
union union
{ {
/// Dummy field, which takes up as much space as the largest union variants
/// to set the union's memory to zeroed memory.
uintptr_t _empty[2];
NixInt integer; NixInt integer;
struct {
bool boolean; bool boolean;
uintptr_t _bool_pad;
};
/** /**
* Strings in the evaluator carry a so-called `context` which * Strings in the evaluator carry a so-called `context` which
@ -190,8 +559,14 @@ public:
const char * * context; // must be in sorted order const char * * context; // must be in sorted order
} string; } string;
struct {
const char * _path; const char * _path;
uintptr_t _path_pad;
};
struct {
Bindings * attrs; Bindings * attrs;
uintptr_t _attrs_pad;
};
struct { struct {
size_t size; size_t size;
Value * * elems; Value * * elems;
@ -208,12 +583,21 @@ public:
Env * env; Env * env;
ExprLambda * fun; ExprLambda * fun;
} lambda; } lambda;
struct {
PrimOp * primOp; PrimOp * primOp;
uintptr_t _primop_pad;
};
struct { struct {
Value * left, * right; Value * left, * right;
} primOpApp; } primOpApp;
struct {
ExternalValueBase * external; ExternalValueBase * external;
uintptr_t _external_pad;
};
struct {
NixFloat fpoint; NixFloat fpoint;
uintptr_t _float_pad;
};
}; };
/** /**
@ -449,8 +833,6 @@ public:
}; };
extern ExprBlackHole eBlackHole;
bool Value::isBlackhole() const bool Value::isBlackhole() const
{ {
return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole; return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole;

22
src/libutil/concepts.hh Normal file
View file

@ -0,0 +1,22 @@
#pragma once
/// @file Defines C++ 20 concepts that std doesn't have.
#include <type_traits>
namespace nix
{
/// Like std::invocable<>, but also constrains the return type as well.
///
/// Somehow, there is no std concept to do this, even though there is a type trait
/// for it.
///
/// @tparam CallableT The type you want to constrain to be callable, and to return
/// @p ReturnT when called with @p Args as arguments.
///
/// @tparam ReturnT The type the callable should return when called.
/// @tparam Args The arguments the callable should accept to return @p ReturnT.
template<typename CallableT, typename ReturnT, typename ...Args>
concept InvocableR = std::is_invocable_r_v<ReturnT, CallableT, Args...>;
}

View file

@ -58,6 +58,7 @@ libutil_headers = files(
'comparator.hh', 'comparator.hh',
'compression.hh', 'compression.hh',
'compute-levels.hh', 'compute-levels.hh',
'concepts.hh',
'config-impl.hh', 'config-impl.hh',
'config.hh', 'config.hh',
'current-process.hh', 'current-process.hh',