libexpr: Introduce Deprecated features

They are like experimental features, but opt-in instead of opt-out. They
will allow us to gracefully remove language features. See #437

Change-Id: I9ca04cc48e6926750c4d622c2b229b25cc142c42
This commit is contained in:
piegames
2024-08-17 19:47:51 +02:00
parent 1c080a8239
commit 49d61b2e4b
22 changed files with 468 additions and 54 deletions
+13 -4
View File
@@ -2710,20 +2710,29 @@ Expr & EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<Sta
}
Expr & EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, const ExperimentalFeatureSettings & xpSettings)
Expr & EvalState::parseExprFromString(
std::string s_,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv,
const FeatureSettings & featureSettings
)
{
// NOTE this method (and parseStdin) must take care to *fully copy* their input
// into their respective Pos::Origin until the parser stops overwriting its input
// data.
auto s = make_ref<std::string>(s_);
s_.append("\0\0", 2);
return *parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv, xpSettings);
return *parse(s_.data(), s_.size(), Pos::String{.source = s}, basePath, staticEnv, featureSettings);
}
Expr & EvalState::parseExprFromString(std::string s, const SourcePath & basePath, const ExperimentalFeatureSettings & xpSettings)
Expr & EvalState::parseExprFromString(
std::string s,
const SourcePath & basePath,
const FeatureSettings & featureSettings
)
{
return parseExprFromString(std::move(s), basePath, staticBaseEnv, xpSettings);
return parseExprFromString(std::move(s), basePath, staticBaseEnv, featureSettings);
}
+12 -3
View File
@@ -344,8 +344,17 @@ public:
/**
* Parse a Nix expression from the specified string.
*/
Expr & parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
Expr & parseExprFromString(std::string s, const SourcePath & basePath, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
Expr & parseExprFromString(
std::string s,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv,
const FeatureSettings & xpSettings = featureSettings
);
Expr & parseExprFromString(
std::string s,
const SourcePath & basePath,
const FeatureSettings & xpSettings = featureSettings
);
Expr & parseStdin();
@@ -569,7 +578,7 @@ private:
Pos::Origin origin,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
const FeatureSettings & xpSettings = featureSettings);
/**
* Current Nix call stack depth, used with `max-call-depth` setting to throw stack overflow hopefully before we run out of system stack.
+4 -4
View File
@@ -115,7 +115,7 @@ struct ExprState
std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false)
{
if (!state.xpSettings.isEnabled(Xp::PipeOperator))
if (!state.featureSettings.isEnabled(Xp::PipeOperator))
throw ParseError({
.msg = HintFmt("Pipe operator is disabled"),
.pos = state.positions[pos]
@@ -656,7 +656,7 @@ template<> struct BuildAST<grammar::expr::path> : p::maybe_nothing {};
template<> struct BuildAST<grammar::expr::uri> {
static void apply(const auto & in, ExprState & s, State & ps) {
bool noURLLiterals = ps.xpSettings.isEnabled(Xp::NoUrlLiterals);
bool noURLLiterals = ps.featureSettings.isEnabled(Xp::NoUrlLiterals);
if (noURLLiterals)
throw ParseError({
.msg = HintFmt("URL literals are disabled"),
@@ -858,7 +858,7 @@ Expr * EvalState::parse(
Pos::Origin origin,
const SourcePath & basePath,
std::shared_ptr<StaticEnv> & staticEnv,
const ExperimentalFeatureSettings & xpSettings)
const FeatureSettings & featureSettings)
{
parser::State s = {
symbols,
@@ -866,7 +866,7 @@ Expr * EvalState::parse(
basePath,
positions.addOrigin(origin, length),
exprSymbols,
xpSettings
featureSettings,
};
parser::ExprState x;
+1 -1
View File
@@ -19,7 +19,7 @@ struct State
SourcePath basePath;
PosTable::Origin origin;
const Expr::AstSymbols & s;
const ExperimentalFeatureSettings & xpSettings;
const FeatureSettings & featureSettings;
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);
+6
View File
@@ -34,6 +34,10 @@ template<> struct BaseSetting<std::set<ExperimentalFeature>>::trait
{
static constexpr bool appendable = true;
};
template<> struct BaseSetting<std::set<DeprecatedFeature>>::trait
{
static constexpr bool appendable = true;
};
template<typename T>
struct BaseSetting<T>::trait
@@ -51,6 +55,7 @@ template<> void BaseSetting<Strings>::appendOrSet(Strings newValue, bool append)
template<> void BaseSetting<StringSet>::appendOrSet(StringSet newValue, bool append);
template<> void BaseSetting<StringMap>::appendOrSet(StringMap newValue, bool append);
template<> void BaseSetting<std::set<ExperimentalFeature>>::appendOrSet(std::set<ExperimentalFeature> newValue, bool append);
template<> void BaseSetting<std::set<DeprecatedFeature>>::appendOrSet(std::set<DeprecatedFeature> newValue, bool append);
template<typename T>
void BaseSetting<T>::appendOrSet(T newValue, bool append)
@@ -116,6 +121,7 @@ DECLARE_CONFIG_SERIALISER(Strings)
DECLARE_CONFIG_SERIALISER(StringSet)
DECLARE_CONFIG_SERIALISER(StringMap)
DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>)
DECLARE_CONFIG_SERIALISER(std::set<DeprecatedFeature>)
template<typename T>
T BaseSetting<T>::parse(const std::string & str) const
+57 -5
View File
@@ -2,6 +2,7 @@
#include "args.hh"
#include "abstract-setting-to-json.hh"
#include "experimental-features.hh"
#include "deprecated-features.hh"
#include "file-system.hh"
#include "logging.hh"
#include "strings.hh"
@@ -355,6 +356,32 @@ template<> std::string BaseSetting<std::set<ExperimentalFeature>>::to_string() c
return concatStringsSep(" ", stringifiedXpFeatures);
}
template<> std::set<DeprecatedFeature> BaseSetting<std::set<DeprecatedFeature>>::parse(const std::string & str) const
{
std::set<DeprecatedFeature> res;
for (auto & s : tokenizeString<StringSet>(str)) {
if (auto thisDpFeature = parseDeprecatedFeature(s); thisDpFeature)
res.insert(thisDpFeature.value());
else
warn("unknown deprecated feature '%s'", s);
}
return res;
}
template<> void BaseSetting<std::set<DeprecatedFeature>>::appendOrSet(std::set<DeprecatedFeature> newValue, bool append)
{
if (!append) value.clear();
value.insert(std::make_move_iterator(newValue.begin()), std::make_move_iterator(newValue.end()));
}
template<> std::string BaseSetting<std::set<DeprecatedFeature>>::to_string() const
{
StringSet stringifiedDpFeatures;
for (const auto & feature : value)
stringifiedDpFeatures.insert(std::string(showDeprecatedFeature(feature)));
return concatStringsSep(" ", stringifiedDpFeatures);
}
template<> StringMap BaseSetting<StringMap>::parse(const std::string & str) const
{
StringMap res;
@@ -391,6 +418,7 @@ template class BaseSetting<Strings>;
template class BaseSetting<StringSet>;
template class BaseSetting<StringMap>;
template class BaseSetting<std::set<ExperimentalFeature>>;
template class BaseSetting<std::set<DeprecatedFeature>>;
static Path parsePath(const AbstractSetting & s, const std::string & str)
{
@@ -525,28 +553,52 @@ GlobalConfig::Register::Register(Config * config)
configRegistrations->emplace_back(config);
}
ExperimentalFeatureSettings experimentalFeatureSettings;
FeatureSettings experimentalFeatureSettings;
FeatureSettings& featureSettings = experimentalFeatureSettings;
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature) const
bool FeatureSettings::isEnabled(const ExperimentalFeature & feature) const
{
auto & f = experimentalFeatures.get();
return std::find(f.begin(), f.end(), feature) != f.end();
}
void ExperimentalFeatureSettings::require(const ExperimentalFeature & feature) const
void FeatureSettings::require(const ExperimentalFeature & feature) const
{
if (!isEnabled(feature))
throw MissingExperimentalFeature(feature);
}
bool ExperimentalFeatureSettings::isEnabled(const std::optional<ExperimentalFeature> & feature) const
bool FeatureSettings::isEnabled(const std::optional<ExperimentalFeature> & feature) const
{
return !feature || isEnabled(*feature);
}
void ExperimentalFeatureSettings::require(const std::optional<ExperimentalFeature> & feature) const
void FeatureSettings::require(const std::optional<ExperimentalFeature> & feature) const
{
if (feature) require(*feature);
}
bool FeatureSettings::isEnabled(const DeprecatedFeature & feature) const
{
auto & f = deprecatedFeatures.get();
return std::find(f.begin(), f.end(), feature) != f.end();
}
void FeatureSettings::require(const DeprecatedFeature & feature) const
{
if (!isEnabled(feature))
throw MissingDeprecatedFeature(feature);
}
bool FeatureSettings::isEnabled(const std::optional<DeprecatedFeature> & feature) const
{
return !feature || isEnabled(*feature);
}
void FeatureSettings::require(const std::optional<DeprecatedFeature> & feature) const
{
if (feature) require(*feature);
}
+41 -2
View File
@@ -9,6 +9,7 @@
#include "types.hh"
#include "experimental-features.hh"
#include "deprecated-features.hh"
namespace nix {
@@ -441,7 +442,7 @@ struct GlobalConfig : public AbstractConfig
extern GlobalConfig globalConfig;
struct ExperimentalFeatureSettings : Config {
struct FeatureSettings : Config {
Setting<std::set<ExperimentalFeature>> experimentalFeatures{
this, {}, "experimental-features",
@@ -483,9 +484,47 @@ struct ExperimentalFeatureSettings : Config {
* disabled, and so the function does nothing in that case.
*/
void require(const std::optional<ExperimentalFeature> &) const;
Setting<std::set<DeprecatedFeature>> deprecatedFeatures{
this, {}, "deprecated-features",
R"(
Deprecated features that are allowed. (Currently there are none.)
The following deprecated feature features can be re-activated:
{{#include @generated@/command-ref/deprecated-features-shortlist.md}}
Deprecated features are [further documented in the manual](@docroot@/contributing/deprecated-features.md).
)"};
/**
* Check whether the given deprecated feature is enabled.
*/
bool isEnabled(const DeprecatedFeature &) const;
/**
* Require an deprecated feature be enabled, throwing an error if it is
* not.
*/
void require(const DeprecatedFeature &) const;
/**
* `std::nullopt` pointer means no feature, which means there is nothing that could be
* disabled, and so the function returns true in that case.
*/
bool isEnabled(const std::optional<DeprecatedFeature> &) const;
/**
* `std::nullopt` pointer means no feature, which means there is nothing that could be
* disabled, and so the function does nothing in that case.
*/
void require(const std::optional<DeprecatedFeature> &) const;
};
// FIXME: don't use a global variable.
extern ExperimentalFeatureSettings experimentalFeatureSettings;
extern FeatureSettings& featureSettings;
// Aliases to `featureSettings` for not having to change the name in the code everywhere
using ExperimentalFeatureSettings = FeatureSettings;
extern FeatureSettings experimentalFeatureSettings;
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
///@file
#include "deprecated-features.hh"
#include "json-utils.hh"
namespace nix {
/**
* Compute the documentation of all deprecated features.
*
* See `doc/manual` for how this information is used.
*/
nlohmann::json documentDeprecatedFeatures();
/**
* Semi-magic conversion to and from json.
* See the nlohmann/json readme for more details.
*/
void to_json(nlohmann::json &, const DeprecatedFeature &);
void from_json(const nlohmann::json &, DeprecatedFeature &);
/**
* It is always rendered as a string
*/
template<>
struct json_avoids_null<DeprecatedFeature> : std::true_type {};
};
+108
View File
@@ -0,0 +1,108 @@
#include "deprecated-features.hh"
// Required for instances of to_json and from_json for DeprecatedFeature
#include "deprecated-features-json.hh"
#include "strings.hh"
#include "nlohmann/json.hpp"
namespace nix {
struct DeprecatedFeatureDetails
{
DeprecatedFeature tag;
std::string_view name;
std::string_view description;
};
/**
* If two different PRs both add a deprecated feature, and we just
* used a number for this, we *woudln't* get merge conflict and the
* counter will be incremented once instead of twice, causing a build
* failure.
*
* By instead defining this instead as 1 + the bottom deprecated
* feature, we either have no issue at all if few features are not added
* at the end of the list, or a proper merge conflict if they are.
*/
constexpr size_t numDepFeatures = 0;
constexpr std::array<DeprecatedFeatureDetails, numDepFeatures> depFeatureDetails = {{
}};
static_assert(
[]() constexpr {
for (auto [index, feature] : enumerate(depFeatureDetails))
if (index != (size_t)feature.tag)
return false;
return true;
}(),
"array order does not match enum tag order");
const std::optional<DeprecatedFeature> parseDeprecatedFeature(const std::string_view & name)
{
using ReverseDepMap = std::map<std::string_view, DeprecatedFeature>;
static std::unique_ptr<ReverseDepMap> reverseDepMap = []() {
auto reverseDepMap = std::make_unique<ReverseDepMap>();
for (auto & depFeature : depFeatureDetails)
(*reverseDepMap)[depFeature.name] = depFeature.tag;
return reverseDepMap;
}();
if (auto feature = get(*reverseDepMap, name))
return *feature;
else
return std::nullopt;
}
std::string_view showDeprecatedFeature(const DeprecatedFeature tag)
{
assert((size_t)tag < depFeatureDetails.size());
return depFeatureDetails[(size_t)tag].name;
}
nlohmann::json documentDeprecatedFeatures()
{
StringMap res;
for (auto & depFeature : depFeatureDetails)
res[std::string { depFeature.name }] =
trim(stripIndentation(depFeature.description));
return (nlohmann::json) res;
}
std::set<DeprecatedFeature> parseDeprecatedFeatures(const std::set<std::string> & rawFeatures)
{
std::set<DeprecatedFeature> res;
for (auto & rawFeature : rawFeatures)
if (auto feature = parseDeprecatedFeature(rawFeature))
res.insert(*feature);
return res;
}
MissingDeprecatedFeature::MissingDeprecatedFeature(DeprecatedFeature feature)
: Error("Lix feature '%1%' is deprecated and should not be used anymore; use '--extra-deprecated-features %1%' to disable this error", showDeprecatedFeature(feature))
, missingFeature(feature)
{}
std::ostream & operator <<(std::ostream & str, const DeprecatedFeature & feature)
{
return str << showDeprecatedFeature(feature);
}
void to_json(nlohmann::json & j, const DeprecatedFeature & feature)
{
j = showDeprecatedFeature(feature);
}
void from_json(const nlohmann::json & j, DeprecatedFeature & feature)
{
const std::string input = j;
const auto parsed = parseDeprecatedFeature(input);
if (parsed.has_value())
feature = *parsed;
else
throw Error("Unknown deprecated feature '%s' in JSON input", input);
}
}
+69
View File
@@ -0,0 +1,69 @@
#pragma once
///@file
#include "error.hh"
#include "types.hh"
namespace nix {
/**
* The list of available deprecated features.
*
* If you update this, dont forget to also change the map defining
* their string representation and documentation in the corresponding
* `.cc` file as well.
*
* Reminder: New deprecated features should start out with a warning without throwing an error.
* See the developer documentation for details.
*/
enum struct DeprecatedFeature
{
};
/**
* Just because writing `DeprecatedFeature::UrlLiterals` is way too long
*/
using Dep = DeprecatedFeature;
/**
* Parse a deprecated feature (enum value) from its name. Deprecated
* feature flag names are hyphenated and do not contain spaces.
*/
const std::optional<DeprecatedFeature> parseDeprecatedFeature(
const std::string_view & name);
/**
* Show the name of a deprecated feature. This is the opposite of
* parseDeprecatedFeature().
*/
std::string_view showDeprecatedFeature(const DeprecatedFeature);
/**
* Shorthand for `str << showDeprecatedFeature(feature)`.
*/
std::ostream & operator<<(
std::ostream & str,
const DeprecatedFeature & feature);
/**
* Parse a set of strings to the corresponding set of deprecated
* features, ignoring (but warning for) any unknown feature.
*/
std::set<DeprecatedFeature> parseDeprecatedFeatures(const std::set<std::string> &);
/**
* A deprecated feature used for some
* operation, but was not enabled.
*/
class MissingDeprecatedFeature : public Error
{
public:
/**
* The deprecated feature that was required but not enabled.
*/
DeprecatedFeature missingFeature;
MissingDeprecatedFeature(DeprecatedFeature missingFeature);
};
}
+3
View File
@@ -7,6 +7,7 @@ libutil_sources = files(
'compute-levels.cc',
'config.cc',
'current-process.cc',
'deprecated-features.cc',
'english.cc',
'environment-variables.cc',
'error.cc',
@@ -64,6 +65,8 @@ libutil_headers = files(
'config-impl.hh',
'config.hh',
'current-process.hh',
'deprecated-features.hh',
'deprecated-features-json.hh',
'english.hh',
'environment-variables.hh',
'error.hh',
+6
View File
@@ -14,6 +14,7 @@
#include "loggers.hh"
#include "markdown.hh"
#include "experimental-features-json.hh"
#include "deprecated-features-json.hh"
#include <sys/types.h>
#include <sys/socket.h>
@@ -422,6 +423,11 @@ void mainWrapped(int argc, char * * argv)
return;
}
if (argc == 2 && std::string(argv[1]) == "__dump-dp-features") {
logger->cout(documentDeprecatedFeatures().dump());
return;
}
Finally printCompletions([&]()
{
if (args.completions) {