diff --git a/libnixt/lib/Value.cpp b/libnixt/lib/Value.cpp index 2c3de3934..aaedb205f 100644 --- a/libnixt/lib/Value.cpp +++ b/libnixt/lib/Value.cpp @@ -2,6 +2,7 @@ #include #include +#include #include using namespace nixt; @@ -110,9 +111,58 @@ nix::Value &nixt::selectAttrPath(nix::EvalState &State, nix::Value &V, return selectAttrPath(State, Nested, ++Begin, End); } +namespace { + +/// \brief Check if the \p Type is a submodule. +bool isTypeSubmodule(nix::EvalState &State, nix::Value &Type) { + return checkField(State, Type, "name", "submodule"); +} + +nix::Value *trySelectAttr(nix::EvalState &State, nix::Value &V, nix::Symbol S) { + try { + nix::Value &Type = selectAttr(State, V, State.sType); + return &Type; + } catch (nix::TypeError &) { + // The value is not an attrset, thus it definitely cannot be a submodule. + return nullptr; + } catch (nix::AttrPathNotFound &) { + // The value has no "type" field. + return nullptr; + } + return nullptr; +} + +/// \brief Get the type of an option. +nix::Value *tryGetSubmoduleType(nix::EvalState &State, nix::Value &V) { + if (nix::Value *Type = trySelectAttr(State, V, State.sType)) + return isTypeSubmodule(State, *Type) ? Type : nullptr; + return nullptr; +} + +/// \brief Do proper operations to get options declaration on submodule type. +nix::Value getSubOptions(nix::EvalState &State, nix::Value &Type) { + // For example, programs.nixvim has all options nested into this attrpath. + nix::Value &GetSubOptions = + selectAttr(State, Type, State.symbols.create("getSubOptions")); + + nix::Value EmptyList; + EmptyList.mkList(0); + // Invoke "GetSubOptions" + nix::Value VResult; + State.callFunction(GetSubOptions, EmptyList, VResult, nix::noPos); + return VResult; +} + +} // namespace + nix::Value nixt::selectOptions(nix::EvalState &State, nix::Value &V, std::vector::const_iterator Begin, std::vector::const_iterator End) { + // Always try to mangle the value if it is a submodule + if (nix::Value *SubType = tryGetSubmoduleType(State, V)) + // Invoke getSubOptions on that type, and reset the value to it. + V = getSubOptions(State, *SubType); + if (Begin == End) return V; @@ -130,19 +180,9 @@ nix::Value nixt::selectOptions(nix::EvalState &State, nix::Value &V, nix::Value ElemType = selectAttr(State, NestedTypes, State.symbols.create("elemType")); - if (checkField(State, ElemType, "name", "submodule")) { - // Current iterator may be ommited, and V becomes "V.getSubOptions []" - nix::Value &GetSubOptions = - selectAttr(State, ElemType, State.symbols.create("getSubOptions")); - - nix::Value EmptyList; - EmptyList.mkList(0); - - // Invoke "GetSubOptions" - nix::Value Next; - State.callFunction(GetSubOptions, EmptyList, Next, nix::noPos); - - return selectOptions(State, Next, ++Begin, End); + if (isTypeSubmodule(State, ElemType)) { + nix::Value ElemOptions = getSubOptions(State, ElemType); + return selectOptions(State, ElemOptions, ++Begin, End); } } }