forked from lix-project/lix
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:
parent
1c080a8239
commit
49d61b2e4b
|
@ -1,9 +1,14 @@
|
||||||
|
# Usually "experimental" or "deprecated"
|
||||||
|
kind:
|
||||||
|
# "xp" or "dp"
|
||||||
|
kindShort:
|
||||||
|
|
||||||
with builtins;
|
with builtins;
|
||||||
with import ./utils.nix;
|
with import ./utils.nix;
|
||||||
|
|
||||||
let
|
let
|
||||||
showExperimentalFeature = name: doc: ''
|
showExperimentalFeature = name: doc: ''
|
||||||
- [`${name}`](@docroot@/contributing/experimental-features.md#xp-feature-${name})
|
- [`${name}`](@docroot@/contributing/${kind}-features.md#${kindShort}-feature-${name})
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
xps: indent " " (concatStrings (attrValues (mapAttrs showExperimentalFeature xps)))
|
xps: indent " " (concatStrings (attrValues (mapAttrs showExperimentalFeature xps)))
|
18
doc/manual/generate-features.nix
Normal file
18
doc/manual/generate-features.nix
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Usually "experimental" or "deprecated"
|
||||||
|
_kind:
|
||||||
|
# "xp" or "dp"
|
||||||
|
kindShort:
|
||||||
|
|
||||||
|
with builtins;
|
||||||
|
with import ./utils.nix;
|
||||||
|
|
||||||
|
let
|
||||||
|
showFeature =
|
||||||
|
name: doc:
|
||||||
|
squash ''
|
||||||
|
## [`${name}`]{#${kindShort}-feature-${name}}
|
||||||
|
|
||||||
|
${doc}
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
xps: (concatStringsSep "\n" (attrValues (mapAttrs showFeature xps)))
|
|
@ -1,13 +0,0 @@
|
||||||
with builtins;
|
|
||||||
with import ./utils.nix;
|
|
||||||
|
|
||||||
let
|
|
||||||
showExperimentalFeature =
|
|
||||||
name: doc:
|
|
||||||
squash ''
|
|
||||||
## [`${name}`]{#xp-feature-${name}}
|
|
||||||
|
|
||||||
${doc}
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
xps: (concatStringsSep "\n" (attrValues (mapAttrs showExperimentalFeature xps)))
|
|
|
@ -72,9 +72,9 @@ generate_manual_deps = files(
|
||||||
|
|
||||||
# Generates builtins.md and builtin-constants.md.
|
# Generates builtins.md and builtin-constants.md.
|
||||||
subdir('src/language')
|
subdir('src/language')
|
||||||
# Generates new-cli pages, experimental-features-shortlist.md, and conf-file.md.
|
# Generates new-cli pages, {experimental,deprecated}-features-shortlist.md, and conf-file.md.
|
||||||
subdir('src/command-ref')
|
subdir('src/command-ref')
|
||||||
# Generates experimental-feature-descriptions.md.
|
# Generates {experimental,deprecated}-feature-descriptions.md.
|
||||||
subdir('src/contributing')
|
subdir('src/contributing')
|
||||||
# Generates rl-next-generated.md.
|
# Generates rl-next-generated.md.
|
||||||
subdir('src/release-notes')
|
subdir('src/release-notes')
|
||||||
|
@ -106,6 +106,8 @@ manual = custom_target(
|
||||||
nix3_cli_files,
|
nix3_cli_files,
|
||||||
experimental_features_shortlist_md,
|
experimental_features_shortlist_md,
|
||||||
experimental_feature_descriptions_md,
|
experimental_feature_descriptions_md,
|
||||||
|
deprecated_features_shortlist_md,
|
||||||
|
deprecated_feature_descriptions_md,
|
||||||
conf_file_md,
|
conf_file_md,
|
||||||
builtins_md,
|
builtins_md,
|
||||||
builtin_constants_md,
|
builtin_constants_md,
|
||||||
|
|
|
@ -192,6 +192,7 @@
|
||||||
- [Hacking](contributing/hacking.md)
|
- [Hacking](contributing/hacking.md)
|
||||||
- [Testing](contributing/testing.md)
|
- [Testing](contributing/testing.md)
|
||||||
- [Experimental Features](contributing/experimental-features.md)
|
- [Experimental Features](contributing/experimental-features.md)
|
||||||
|
- [Deprecated Features](contributing/deprecated-features.md)
|
||||||
- [CLI guideline](contributing/cli-guideline.md)
|
- [CLI guideline](contributing/cli-guideline.md)
|
||||||
- [C++ style guide](contributing/cxx.md)
|
- [C++ style guide](contributing/cxx.md)
|
||||||
- [Release Notes](release-notes/release-notes.md)
|
- [Release Notes](release-notes/release-notes.md)
|
||||||
|
|
|
@ -7,10 +7,10 @@ xp_features_json = custom_target(
|
||||||
experimental_features_shortlist_md = custom_target(
|
experimental_features_shortlist_md = custom_target(
|
||||||
command : nix_eval_for_docs + [
|
command : nix_eval_for_docs + [
|
||||||
'--expr',
|
'--expr',
|
||||||
'import @INPUT0@ (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
'import @INPUT0@ "experimental" "xp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||||
],
|
],
|
||||||
input : [
|
input : [
|
||||||
'../../generate-xp-features-shortlist.nix',
|
'../../generate-features-shortlist.nix',
|
||||||
xp_features_json,
|
xp_features_json,
|
||||||
],
|
],
|
||||||
capture : true,
|
capture : true,
|
||||||
|
@ -18,6 +18,26 @@ experimental_features_shortlist_md = custom_target(
|
||||||
env : nix_env_for_docs,
|
env : nix_env_for_docs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dp_features_json = custom_target(
|
||||||
|
command : [nix, '__dump-dp-features'],
|
||||||
|
capture : true,
|
||||||
|
output : 'dp-features.json',
|
||||||
|
)
|
||||||
|
|
||||||
|
deprecated_features_shortlist_md = custom_target(
|
||||||
|
command : nix_eval_for_docs + [
|
||||||
|
'--expr',
|
||||||
|
'import @INPUT0@ "deprecated" "dp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||||
|
],
|
||||||
|
input : [
|
||||||
|
'../../generate-features-shortlist.nix',
|
||||||
|
dp_features_json,
|
||||||
|
],
|
||||||
|
capture : true,
|
||||||
|
output : 'deprecated-features-shortlist.md',
|
||||||
|
env : nix_env_for_docs,
|
||||||
|
)
|
||||||
|
|
||||||
# Intermediate step for manpage generation.
|
# Intermediate step for manpage generation.
|
||||||
# This splorks the output of generate-manpage.nix as JSON,
|
# This splorks the output of generate-manpage.nix as JSON,
|
||||||
# which gets written as a directory tree below.
|
# which gets written as a directory tree below.
|
||||||
|
@ -60,6 +80,7 @@ conf_file_md = custom_target(
|
||||||
'../../utils.nix',
|
'../../utils.nix',
|
||||||
conf_file_json,
|
conf_file_json,
|
||||||
experimental_features_shortlist_md,
|
experimental_features_shortlist_md,
|
||||||
|
deprecated_features_shortlist_md,
|
||||||
],
|
],
|
||||||
output : 'conf-file.md',
|
output : 'conf-file.md',
|
||||||
env : nix_env_for_docs,
|
env : nix_env_for_docs,
|
||||||
|
|
37
doc/manual/src/contributing/deprecated-features.md
Normal file
37
doc/manual/src/contributing/deprecated-features.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
This section describes the notion of *deprecated features*, and how it fits into the big picture of the development of Lix.
|
||||||
|
|
||||||
|
# What are deprecated features?
|
||||||
|
|
||||||
|
Deprecated features are disabled by default, with the intent to eventually remove them.
|
||||||
|
Users must explicitly enable them to keep using them, by toggling the associated [deprecated feature flags](@docroot@/command-ref/conf-file.md#conf-deprecated-features).
|
||||||
|
This allows backwards compatibility and a graceful transition away from undesired features.
|
||||||
|
|
||||||
|
# Which features can be deprecated?
|
||||||
|
|
||||||
|
Undesired features should be soft-deprecated by yielding a warning when used for a significant amount of time before the can be deprecated.
|
||||||
|
Legacy obsolete feature with little to no usage may go through this process faster.
|
||||||
|
Deprecated features should have a migration path to a preferred alternative.
|
||||||
|
|
||||||
|
# Lifecycle of a deprecated feature
|
||||||
|
|
||||||
|
This description is not normative, but a feature removal may roughly happen like this:
|
||||||
|
|
||||||
|
1. Add a warning when the feature is being used.
|
||||||
|
2. Disable the feature by default, putting it behind a deprecated feature flag.
|
||||||
|
- If disabling the feature started out as an opt-in experimental feature, turn that experimental flag into a no-op or remove it entirely.
|
||||||
|
For example, `--extra-experimental-features=no-url-literals` becomes `--extra-deprecated-features=url-literals`.
|
||||||
|
3. Decide on a time frame for how long that feature will still be supported for backwards compatibility, and clearly communicate that in the error messages.
|
||||||
|
- Sometimes, automatic migration to alternatives is possible, and such should be provided if possible
|
||||||
|
- At least one NixOS release cycle should be the minimum
|
||||||
|
4. Finally remove the feature entirely, only keeping the error message for those still using it.
|
||||||
|
|
||||||
|
# Relation to language versioning
|
||||||
|
|
||||||
|
Obviously, removing anything breaks backwards compatibility.
|
||||||
|
In an ideal world, we'd have SemVer controls over the language and its features, cleanly allowing us to make breaking changes.
|
||||||
|
See https://wiki.lix.systems/books/lix-contributors/page/language-versioning and [RFC 137](https://github.com/nixos/rfcs/pull/137) for efforts on that.
|
||||||
|
However, we do not live in such an ideal world, and currently this goal is so far away, that "just disable it with some back-compat for a couple of years" is the most realistic solution, especially for comparatively minor changes.
|
||||||
|
|
||||||
|
# Currently available deprecated features
|
||||||
|
|
||||||
|
{{#include @generated@/contributing/deprecated-feature-descriptions.md}}
|
|
@ -4,12 +4,25 @@
|
||||||
experimental_feature_descriptions_md = custom_target(
|
experimental_feature_descriptions_md = custom_target(
|
||||||
command : nix_eval_for_docs + [
|
command : nix_eval_for_docs + [
|
||||||
'--expr',
|
'--expr',
|
||||||
'import @INPUT0@ (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
'import @INPUT0@ "experimental" "xp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||||
],
|
],
|
||||||
input : [
|
input : [
|
||||||
'../../generate-xp-features.nix',
|
'../../generate-features.nix',
|
||||||
xp_features_json,
|
xp_features_json,
|
||||||
],
|
],
|
||||||
capture : true,
|
capture : true,
|
||||||
output : 'experimental-feature-descriptions.md',
|
output : 'experimental-feature-descriptions.md',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
deprecated_feature_descriptions_md = custom_target(
|
||||||
|
command : nix_eval_for_docs + [
|
||||||
|
'--expr',
|
||||||
|
'import @INPUT0@ "deprecated" "dp" (builtins.fromJSON (builtins.readFile @INPUT1@))',
|
||||||
|
],
|
||||||
|
input : [
|
||||||
|
'../../generate-features.nix',
|
||||||
|
dp_features_json,
|
||||||
|
],
|
||||||
|
capture : true,
|
||||||
|
output : 'deprecated-feature-descriptions.md',
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
// 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
|
// into their respective Pos::Origin until the parser stops overwriting its input
|
||||||
// data.
|
// data.
|
||||||
auto s = make_ref<std::string>(s_);
|
auto s = make_ref<std::string>(s_);
|
||||||
s_.append("\0\0", 2);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -344,8 +344,17 @@ public:
|
||||||
/**
|
/**
|
||||||
* Parse a Nix expression from the specified string.
|
* 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(
|
||||||
Expr & parseExprFromString(std::string s, const SourcePath & basePath, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
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();
|
Expr & parseStdin();
|
||||||
|
|
||||||
|
@ -569,7 +578,7 @@ private:
|
||||||
Pos::Origin origin,
|
Pos::Origin origin,
|
||||||
const SourcePath & basePath,
|
const SourcePath & basePath,
|
||||||
std::shared_ptr<StaticEnv> & staticEnv,
|
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.
|
* Current Nix call stack depth, used with `max-call-depth` setting to throw stack overflow hopefully before we run out of system stack.
|
||||||
|
|
|
@ -115,7 +115,7 @@ struct ExprState
|
||||||
|
|
||||||
std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false)
|
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({
|
throw ParseError({
|
||||||
.msg = HintFmt("Pipe operator is disabled"),
|
.msg = HintFmt("Pipe operator is disabled"),
|
||||||
.pos = state.positions[pos]
|
.pos = state.positions[pos]
|
||||||
|
@ -656,7 +656,7 @@ template<> struct BuildAST<grammar::expr::path> : p::maybe_nothing {};
|
||||||
|
|
||||||
template<> struct BuildAST<grammar::expr::uri> {
|
template<> struct BuildAST<grammar::expr::uri> {
|
||||||
static void apply(const auto & in, ExprState & s, State & ps) {
|
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)
|
if (noURLLiterals)
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = HintFmt("URL literals are disabled"),
|
.msg = HintFmt("URL literals are disabled"),
|
||||||
|
@ -858,7 +858,7 @@ Expr * EvalState::parse(
|
||||||
Pos::Origin origin,
|
Pos::Origin origin,
|
||||||
const SourcePath & basePath,
|
const SourcePath & basePath,
|
||||||
std::shared_ptr<StaticEnv> & staticEnv,
|
std::shared_ptr<StaticEnv> & staticEnv,
|
||||||
const ExperimentalFeatureSettings & xpSettings)
|
const FeatureSettings & featureSettings)
|
||||||
{
|
{
|
||||||
parser::State s = {
|
parser::State s = {
|
||||||
symbols,
|
symbols,
|
||||||
|
@ -866,7 +866,7 @@ Expr * EvalState::parse(
|
||||||
basePath,
|
basePath,
|
||||||
positions.addOrigin(origin, length),
|
positions.addOrigin(origin, length),
|
||||||
exprSymbols,
|
exprSymbols,
|
||||||
xpSettings
|
featureSettings,
|
||||||
};
|
};
|
||||||
parser::ExprState x;
|
parser::ExprState x;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ struct State
|
||||||
SourcePath basePath;
|
SourcePath basePath;
|
||||||
PosTable::Origin origin;
|
PosTable::Origin origin;
|
||||||
const Expr::AstSymbols & s;
|
const Expr::AstSymbols & s;
|
||||||
const ExperimentalFeatureSettings & xpSettings;
|
const FeatureSettings & featureSettings;
|
||||||
|
|
||||||
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
|
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
|
||||||
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);
|
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);
|
||||||
|
|
|
@ -34,6 +34,10 @@ template<> struct BaseSetting<std::set<ExperimentalFeature>>::trait
|
||||||
{
|
{
|
||||||
static constexpr bool appendable = true;
|
static constexpr bool appendable = true;
|
||||||
};
|
};
|
||||||
|
template<> struct BaseSetting<std::set<DeprecatedFeature>>::trait
|
||||||
|
{
|
||||||
|
static constexpr bool appendable = true;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct BaseSetting<T>::trait
|
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<StringSet>::appendOrSet(StringSet newValue, bool append);
|
||||||
template<> void BaseSetting<StringMap>::appendOrSet(StringMap 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<ExperimentalFeature>>::appendOrSet(std::set<ExperimentalFeature> newValue, bool append);
|
||||||
|
template<> void BaseSetting<std::set<DeprecatedFeature>>::appendOrSet(std::set<DeprecatedFeature> newValue, bool append);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void BaseSetting<T>::appendOrSet(T newValue, bool append)
|
void BaseSetting<T>::appendOrSet(T newValue, bool append)
|
||||||
|
@ -116,6 +121,7 @@ DECLARE_CONFIG_SERIALISER(Strings)
|
||||||
DECLARE_CONFIG_SERIALISER(StringSet)
|
DECLARE_CONFIG_SERIALISER(StringSet)
|
||||||
DECLARE_CONFIG_SERIALISER(StringMap)
|
DECLARE_CONFIG_SERIALISER(StringMap)
|
||||||
DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>)
|
DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>)
|
||||||
|
DECLARE_CONFIG_SERIALISER(std::set<DeprecatedFeature>)
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T BaseSetting<T>::parse(const std::string & str) const
|
T BaseSetting<T>::parse(const std::string & str) const
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
#include "abstract-setting-to-json.hh"
|
#include "abstract-setting-to-json.hh"
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
|
#include "deprecated-features.hh"
|
||||||
#include "file-system.hh"
|
#include "file-system.hh"
|
||||||
#include "logging.hh"
|
#include "logging.hh"
|
||||||
#include "strings.hh"
|
#include "strings.hh"
|
||||||
|
@ -355,6 +356,32 @@ template<> std::string BaseSetting<std::set<ExperimentalFeature>>::to_string() c
|
||||||
return concatStringsSep(" ", stringifiedXpFeatures);
|
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
|
template<> StringMap BaseSetting<StringMap>::parse(const std::string & str) const
|
||||||
{
|
{
|
||||||
StringMap res;
|
StringMap res;
|
||||||
|
@ -391,6 +418,7 @@ template class BaseSetting<Strings>;
|
||||||
template class BaseSetting<StringSet>;
|
template class BaseSetting<StringSet>;
|
||||||
template class BaseSetting<StringMap>;
|
template class BaseSetting<StringMap>;
|
||||||
template class BaseSetting<std::set<ExperimentalFeature>>;
|
template class BaseSetting<std::set<ExperimentalFeature>>;
|
||||||
|
template class BaseSetting<std::set<DeprecatedFeature>>;
|
||||||
|
|
||||||
static Path parsePath(const AbstractSetting & s, const std::string & str)
|
static Path parsePath(const AbstractSetting & s, const std::string & str)
|
||||||
{
|
{
|
||||||
|
@ -525,28 +553,52 @@ GlobalConfig::Register::Register(Config * config)
|
||||||
configRegistrations->emplace_back(config);
|
configRegistrations->emplace_back(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
ExperimentalFeatureSettings experimentalFeatureSettings;
|
FeatureSettings experimentalFeatureSettings;
|
||||||
|
|
||||||
|
FeatureSettings& featureSettings = experimentalFeatureSettings;
|
||||||
|
|
||||||
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
|
static GlobalConfig::Register rSettings(&experimentalFeatureSettings);
|
||||||
|
|
||||||
bool ExperimentalFeatureSettings::isEnabled(const ExperimentalFeature & feature) const
|
bool FeatureSettings::isEnabled(const ExperimentalFeature & feature) const
|
||||||
{
|
{
|
||||||
auto & f = experimentalFeatures.get();
|
auto & f = experimentalFeatures.get();
|
||||||
return std::find(f.begin(), f.end(), feature) != f.end();
|
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))
|
if (!isEnabled(feature))
|
||||||
throw MissingExperimentalFeature(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);
|
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);
|
if (feature) require(*feature);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
|
#include "deprecated-features.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -441,7 +442,7 @@ struct GlobalConfig : public AbstractConfig
|
||||||
extern GlobalConfig globalConfig;
|
extern GlobalConfig globalConfig;
|
||||||
|
|
||||||
|
|
||||||
struct ExperimentalFeatureSettings : Config {
|
struct FeatureSettings : Config {
|
||||||
|
|
||||||
Setting<std::set<ExperimentalFeature>> experimentalFeatures{
|
Setting<std::set<ExperimentalFeature>> experimentalFeatures{
|
||||||
this, {}, "experimental-features",
|
this, {}, "experimental-features",
|
||||||
|
@ -483,9 +484,47 @@ struct ExperimentalFeatureSettings : Config {
|
||||||
* disabled, and so the function does nothing in that case.
|
* disabled, and so the function does nothing in that case.
|
||||||
*/
|
*/
|
||||||
void require(const std::optional<ExperimentalFeature> &) const;
|
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.
|
// 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
src/libutil/deprecated-features-json.hh
Normal file
29
src/libutil/deprecated-features-json.hh
Normal 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
src/libutil/deprecated-features.cc
Normal file
108
src/libutil/deprecated-features.cc
Normal 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
src/libutil/deprecated-features.hh
Normal file
69
src/libutil/deprecated-features.hh
Normal 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, don’t 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ libutil_sources = files(
|
||||||
'compute-levels.cc',
|
'compute-levels.cc',
|
||||||
'config.cc',
|
'config.cc',
|
||||||
'current-process.cc',
|
'current-process.cc',
|
||||||
|
'deprecated-features.cc',
|
||||||
'english.cc',
|
'english.cc',
|
||||||
'environment-variables.cc',
|
'environment-variables.cc',
|
||||||
'error.cc',
|
'error.cc',
|
||||||
|
@ -64,6 +65,8 @@ libutil_headers = files(
|
||||||
'config-impl.hh',
|
'config-impl.hh',
|
||||||
'config.hh',
|
'config.hh',
|
||||||
'current-process.hh',
|
'current-process.hh',
|
||||||
|
'deprecated-features.hh',
|
||||||
|
'deprecated-features-json.hh',
|
||||||
'english.hh',
|
'english.hh',
|
||||||
'environment-variables.hh',
|
'environment-variables.hh',
|
||||||
'error.hh',
|
'error.hh',
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "loggers.hh"
|
#include "loggers.hh"
|
||||||
#include "markdown.hh"
|
#include "markdown.hh"
|
||||||
#include "experimental-features-json.hh"
|
#include "experimental-features-json.hh"
|
||||||
|
#include "deprecated-features-json.hh"
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
@ -422,6 +423,11 @@ void mainWrapped(int argc, char * * argv)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (argc == 2 && std::string(argv[1]) == "__dump-dp-features") {
|
||||||
|
logger->cout(documentDeprecatedFeatures().dump());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Finally printCompletions([&]()
|
Finally printCompletions([&]()
|
||||||
{
|
{
|
||||||
if (args.completions) {
|
if (args.completions) {
|
||||||
|
|
|
@ -26,9 +26,9 @@ namespace nix {
|
||||||
, state({}, store)
|
, state({}, store)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
Value eval(std::string input, bool forceValue = true, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings) {
|
Value eval(std::string input, bool forceValue = true, const FeatureSettings & fSettings = featureSettings) {
|
||||||
Value v;
|
Value v;
|
||||||
Expr & e = state.parseExprFromString(input, state.rootPath(CanonPath::root), xpSettings);
|
Expr & e = state.parseExprFromString(input, state.rootPath(CanonPath::root), fSettings);
|
||||||
state.eval(e, v);
|
state.eval(e, v);
|
||||||
if (forceValue)
|
if (forceValue)
|
||||||
state.forceValue(v, noPos);
|
state.forceValue(v, noPos);
|
||||||
|
|
|
@ -214,36 +214,36 @@ namespace nix {
|
||||||
// pipes are gated behind an experimental feature flag
|
// pipes are gated behind an experimental feature flag
|
||||||
TEST_F(TrivialExpressionTest, pipeDisabled) {
|
TEST_F(TrivialExpressionTest, pipeDisabled) {
|
||||||
ASSERT_THROW(eval("let add = l: r: l + r; in ''a'' |> add ''b''"), Error);
|
ASSERT_THROW(eval("let add = l: r: l + r; in ''a'' |> add ''b''"), Error);
|
||||||
ASSERT_THROW(eval("let add = l: r: l + r; in ''a'' <| add ''b''"), Error);
|
ASSERT_THROW(eval("let add = l: r: l + r; in add ''a'' <| ''b''"), Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TrivialExpressionTest, pipeRight) {
|
TEST_F(TrivialExpressionTest, pipeRight) {
|
||||||
ExperimentalFeatureSettings mockXpSettings;
|
FeatureSettings mockFeatureSettings;
|
||||||
mockXpSettings.set("experimental-features", "pipe-operator");
|
mockFeatureSettings.set("experimental-features", "pipe-operator");
|
||||||
|
|
||||||
auto v = eval("let add = l: r: l + r; in ''a'' |> add ''b''", true, mockXpSettings);
|
auto v = eval("let add = l: r: l + r; in ''a'' |> add ''b''", true, mockFeatureSettings);
|
||||||
ASSERT_THAT(v, IsStringEq("ba"));
|
ASSERT_THAT(v, IsStringEq("ba"));
|
||||||
v = eval("let add = l: r: l + r; in ''a'' |> add ''b'' |> add ''c''", true, mockXpSettings);
|
v = eval("let add = l: r: l + r; in ''a'' |> add ''b'' |> add ''c''", true, mockFeatureSettings);
|
||||||
ASSERT_THAT(v, IsStringEq("cba"));
|
ASSERT_THAT(v, IsStringEq("cba"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TrivialExpressionTest, pipeLeft) {
|
TEST_F(TrivialExpressionTest, pipeLeft) {
|
||||||
ExperimentalFeatureSettings mockXpSettings;
|
FeatureSettings mockFeatureSettings;
|
||||||
mockXpSettings.set("experimental-features", "pipe-operator");
|
mockFeatureSettings.set("experimental-features", "pipe-operator");
|
||||||
|
|
||||||
auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b''", true, mockXpSettings);
|
auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b''", true, mockFeatureSettings);
|
||||||
ASSERT_THAT(v, IsStringEq("ab"));
|
ASSERT_THAT(v, IsStringEq("ab"));
|
||||||
v = eval("let add = l: r: l + r; in add ''a'' <| add ''b'' <| ''c''", true, mockXpSettings);
|
v = eval("let add = l: r: l + r; in add ''a'' <| add ''b'' <| ''c''", true, mockFeatureSettings);
|
||||||
ASSERT_THAT(v, IsStringEq("abc"));
|
ASSERT_THAT(v, IsStringEq("abc"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TrivialExpressionTest, pipeMixed) {
|
TEST_F(TrivialExpressionTest, pipeMixed) {
|
||||||
ExperimentalFeatureSettings mockXpSettings;
|
FeatureSettings mockFeatureSettings;
|
||||||
mockXpSettings.set("experimental-features", "pipe-operator");
|
mockFeatureSettings.set("experimental-features", "pipe-operator");
|
||||||
|
|
||||||
auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b'' |> add ''c''", true, mockXpSettings);
|
auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b'' |> add ''c''", true, mockFeatureSettings);
|
||||||
ASSERT_THAT(v, IsStringEq("acb"));
|
ASSERT_THAT(v, IsStringEq("acb"));
|
||||||
v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockXpSettings);
|
v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockFeatureSettings);
|
||||||
ASSERT_THAT(v, IsStringEq("ac"));
|
ASSERT_THAT(v, IsStringEq("ac"));
|
||||||
}
|
}
|
||||||
} /* namespace nix */
|
} /* namespace nix */
|
||||||
|
|
Loading…
Reference in a new issue