1use std::collections::HashMap;
2
3use clap::{Parser, ValueEnum};
4
5macro_rules! features {
6 ($($name:ident = $enabled:literal, $url:literal),* $(,)?) => {
7 paste::paste! {
8 #[derive(Copy, Clone, Debug, ValueEnum, PartialEq, Eq, Hash)]
9 #[value(rename_all = "snake")]
10 pub enum Feature {
11 $(
12 [<$name:camel>],
13 )*
14 }
15
16 impl Feature {
17 pub const CFG: &[&str] = &[
18 $(
19 stringify!([<experimental_ $name:snake>]),
20 )*
21 ];
22
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 references = true,
171 "https://github.com/FuelLabs/sway/issues/5063",
172 const_generics = false,
173 "https://github.com/FuelLabs/sway/issues/6860",
174 new_hashing = false,
175 "https://github.com/FuelLabs/sway/issues/7256",
176}
177
178#[derive(Clone, Debug, Default, Parser)]
179pub struct CliFields {
180 #[clap(long, value_delimiter = ',')]
182 pub experimental: Vec<Feature>,
183
184 #[clap(long, value_delimiter = ',')]
186 pub no_experimental: Vec<Feature>,
187}
188
189#[derive(Debug)]
190pub enum Error {
191 ParseError(String),
192 UnknownFeature(String),
193}
194
195impl std::fmt::Display for Error {
196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197 match self {
198 Error::ParseError(feature) => f.write_fmt(format_args!(
199 "Experimental feature \"{feature}\" cannot be parsed."
200 )),
201 Error::UnknownFeature(feature) => {
202 f.write_fmt(format_args!("Unknown experimental feature: \"{feature}\"."))
203 }
204 }
205 }
206}
207
208impl ExperimentalFeatures {
209 pub fn parse_from_package_manifest(
210 &mut self,
211 experimental: &std::collections::HashMap<String, bool>,
212 ) -> Result<(), Error> {
213 for (feature, enabled) in experimental {
214 self.set_enabled_by_name(feature, *enabled)?;
215 }
216 Ok(())
217 }
218
219 pub fn parse_from_environment_variables(&mut self) -> Result<(), Error> {
222 if let Ok(features) = std::env::var("FORC_NO_EXPERIMENTAL") {
223 self.parse_comma_separated_list(&features, false)?;
224 }
225
226 if let Ok(features) = std::env::var("FORC_EXPERIMENTAL") {
227 self.parse_comma_separated_list(&features, true)?;
228 }
229
230 Ok(())
231 }
232
233 pub fn parse_comma_separated_list(
234 &mut self,
235 features: impl AsRef<str>,
236 enabled: bool,
237 ) -> Result<(), Error> {
238 for feature in features.as_ref().split(',') {
239 self.set_enabled_by_name(feature, enabled)?;
240 }
241 Ok(())
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 struct RollbackEnvVar(String, Option<String>);
250
251 impl RollbackEnvVar {
252 pub fn new(name: &str) -> Self {
253 let old = std::env::var(name).ok();
254 RollbackEnvVar(name.to_string(), old)
255 }
256 }
257
258 impl Drop for RollbackEnvVar {
259 fn drop(&mut self) {
260 if let Some(old) = self.1.take() {
261 std::env::set_var(&self.0, old);
262 }
263 }
264 }
265
266 #[test]
267 fn ok_parse_experimental_features() {
268 let _old = RollbackEnvVar::new("FORC_EXPERIMENTAL");
269 let _old = RollbackEnvVar::new("FORC_NO_EXPERIMENTAL");
270
271 let mut features = ExperimentalFeatures {
272 new_encoding: false,
273 ..Default::default()
274 };
275
276 std::env::set_var("FORC_EXPERIMENTAL", "new_encoding");
277 std::env::set_var("FORC_NO_EXPERIMENTAL", "");
278 assert!(!features.new_encoding);
279 let _ = features.parse_from_environment_variables();
280 assert!(features.new_encoding);
281
282 std::env::set_var("FORC_EXPERIMENTAL", "");
283 std::env::set_var("FORC_NO_EXPERIMENTAL", "new_encoding");
284 assert!(features.new_encoding);
285 let _ = features.parse_from_environment_variables();
286 assert!(!features.new_encoding);
287 }
288}