1use std::collections::HashMap;
2
3use clap::{Parser, ValueEnum};
4
5macro_rules! features {
6 ($($name:ident = $enabled:literal, $url:literal),* $(,)?) => {
7 paste::paste! {
8 pub const CFG: &[&str] = &[
9 $(
10 stringify!([<experimental_ $name:snake>]),
11 )*
12 ];
13
14 #[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, Hash)]
15 #[value(rename_all = "snake")]
16 pub enum Feature {
17 $(
18 [<$name:camel>],
19 )*
20 }
21
22 impl Feature {
23 pub fn name(&self) -> &'static str {
24 match self {
25 $(
26 Feature::[<$name:camel>] => {
27 stringify!([<$name:snake>])
28 },
29 )*
30 }
31 }
32
33 pub fn url(&self) -> &'static str {
34 match self {
35 $(
36 Feature::[<$name:camel>] => {
37 $url
38 },
39 )*
40 }
41 }
42
43 pub fn error_because_is_disabled(&self, span: &sway_types::Span) -> sway_error::error::CompileError {
44 match self {
45 $(
46 Self::[<$name:camel>] => {
47 sway_error::error::CompileError::FeatureIsDisabled {
48 feature: stringify!([<$name:snake>]).into(),
49 url: $url.into(),
50 span: span.clone()
51 }
52 },
53 )*
54 }
55 }
56 }
57
58 impl std::str::FromStr for Feature {
59 type Err = Error;
60
61 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 match s {
63 $(
64 stringify!([<$name:snake>]) => {
65 Ok(Self::[<$name:camel>])
66 },
67 )*
68 _ => Err(Error::UnknownFeature(s.to_string())),
69 }
70 }
71 }
72
73 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
74 pub struct ExperimentalFeatures {
75 $(
76 pub [<$name:snake>]: bool,
77 )*
78 }
79
80 impl std::default::Default for ExperimentalFeatures {
81 fn default() -> Self {
82 Self {
83 $(
84 [<$name:snake>]: $enabled,
85 )*
86 }
87 }
88 }
89
90 impl ExperimentalFeatures {
91 pub fn set_enabled_by_name(&mut self, feature: &str, enabled: bool) -> Result<(), Error> {
92 let feature = feature.trim();
93 match feature {
94 $(
95 stringify!([<$name:snake>]) => {
96 self.[<$name:snake>] = enabled;
97 Ok(())
98 },
99 )*
100 "" => Ok(()),
101 _ => Err(Error::UnknownFeature(feature.to_string())),
102 }
103 }
104
105 pub fn set_enabled(&mut self, feature: Feature, enabled: bool) {
106 match feature {
107 $(
108 Feature::[<$name:camel>] => {
109 self.[<$name:snake>] = enabled
110 },
111 )*
112 }
113 }
114
115 pub fn is_enabled_for_cfg(&self, cfg: &str) -> Result<bool, Error> {
118 match cfg {
119 $(
120 stringify!([<experimental_ $name:snake>]) => Ok(self.[<$name:snake>]),
121 )*
122 _ => Err(Error::UnknownFeature(cfg.to_string()))
123 }
124 }
125
126 $(
127 pub fn [<with_ $name:snake>](mut self, enabled: bool) -> Self {
128 self.[<$name:snake>] = enabled;
129 self
130 }
131 )*
132 }
133 }
134 };
135}
136
137impl ExperimentalFeatures {
138 pub fn new(
145 manifest: &HashMap<String, bool>,
146 cli_experimental: &[Feature],
147 cli_no_experimental: &[Feature],
148 ) -> Result<ExperimentalFeatures, Error> {
149 let mut experimental = ExperimentalFeatures::default();
150
151 experimental.parse_from_package_manifest(manifest)?;
152
153 for f in cli_no_experimental {
154 experimental.set_enabled(*f, false);
155 }
156
157 for f in cli_experimental {
158 experimental.set_enabled(*f, true);
159 }
160
161 experimental.parse_from_environment_variables()?;
162
163 Ok(experimental)
164 }
165}
166
167features! {
168 new_encoding = true,
169 "https://github.com/FuelLabs/sway/issues/5727",
170 storage_domains = false,
171 "https://github.com/FuelLabs/sway/issues/6701",
172 references = true,
173 "https://github.com/FuelLabs/sway/issues/5063",
174 error_type = false,
175 "https://github.com/FuelLabs/sway/issues/6765",
176 partial_eq = false,
177 "https://github.com/FuelLabs/sway/issues/6883",
178 const_generics = false,
179 "https://github.com/FuelLabs/sway/issues/6860",
180 try_from_bytes_for_b256 = false,
181 "https://github.com/FuelLabs/sway/issues/6994",
182 merge_core_std = false,
183 "https://github.com/FuelLabs/sway/issues/7006",
184}
185
186#[derive(Clone, Debug, Default, Parser)]
187pub struct CliFields {
188 #[clap(long, value_delimiter = ',')]
190 pub experimental: Vec<Feature>,
191
192 #[clap(long, value_delimiter = ',')]
194 pub no_experimental: Vec<Feature>,
195}
196
197#[derive(Debug)]
198pub enum Error {
199 ParseError(String),
200 UnknownFeature(String),
201}
202
203impl std::fmt::Display for Error {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 match self {
206 Error::ParseError(feature) => f.write_fmt(format_args!(
207 "Experimental feature \"{feature}\" cannot be parsed."
208 )),
209 Error::UnknownFeature(feature) => {
210 f.write_fmt(format_args!("Unknown experimental feature: \"{feature}\"."))
211 }
212 }
213 }
214}
215
216impl ExperimentalFeatures {
217 pub fn parse_from_package_manifest(
218 &mut self,
219 experimental: &std::collections::HashMap<String, bool>,
220 ) -> Result<(), Error> {
221 for (feature, enabled) in experimental {
222 self.set_enabled_by_name(feature, *enabled)?;
223 }
224 Ok(())
225 }
226
227 pub fn parse_from_environment_variables(&mut self) -> Result<(), Error> {
230 if let Ok(features) = std::env::var("FORC_NO_EXPERIMENTAL") {
231 self.parse_comma_separated_list(&features, false)?;
232 }
233
234 if let Ok(features) = std::env::var("FORC_EXPERIMENTAL") {
235 self.parse_comma_separated_list(&features, true)?;
236 }
237
238 Ok(())
239 }
240
241 pub fn parse_comma_separated_list(
242 &mut self,
243 features: impl AsRef<str>,
244 enabled: bool,
245 ) -> Result<(), Error> {
246 for feature in features.as_ref().split(',') {
247 self.set_enabled_by_name(feature, enabled)?;
248 }
249 Ok(())
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 struct RollbackEnvVar(String, Option<String>);
258
259 impl RollbackEnvVar {
260 pub fn new(name: &str) -> Self {
261 let old = std::env::var(name).ok();
262 RollbackEnvVar(name.to_string(), old)
263 }
264 }
265
266 impl Drop for RollbackEnvVar {
267 fn drop(&mut self) {
268 if let Some(old) = self.1.take() {
269 std::env::set_var(&self.0, old);
270 }
271 }
272 }
273
274 #[test]
275 fn ok_parse_experimental_features() {
276 let _old = RollbackEnvVar::new("FORC_EXPERIMENTAL");
277 let _old = RollbackEnvVar::new("FORC_NO_EXPERIMENTAL");
278
279 let mut features = ExperimentalFeatures {
280 new_encoding: false,
281 ..Default::default()
282 };
283
284 std::env::set_var("FORC_EXPERIMENTAL", "new_encoding");
285 std::env::set_var("FORC_NO_EXPERIMENTAL", "");
286 assert!(!features.new_encoding);
287 let _ = features.parse_from_environment_variables();
288 assert!(features.new_encoding);
289
290 std::env::set_var("FORC_EXPERIMENTAL", "");
291 std::env::set_var("FORC_NO_EXPERIMENTAL", "new_encoding");
292 assert!(features.new_encoding);
293 let _ = features.parse_from_environment_variables();
294 assert!(!features.new_encoding);
295 }
296}