sway_features/
lib.rs

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                /// Used for testing if a `#[cfg(...)]` feature is enabled.
116                /// Already prepends "experimental_" to the feature name.
117                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    /// Experimental features will be applied in the following order:
139    /// 1 - manifest (no specific order)
140    /// 2 - cli_no_experimental
141    /// 3 - cli_experimental
142    /// 4 - FORC_NO_EXPERIMENTAL (env var)
143    /// 5 - FORC_EXPERIMENTAL (env var)
144    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    /// Comma separated list of all experimental features that will be enabled
189    #[clap(long, value_delimiter = ',')]
190    pub experimental: Vec<Feature>,
191
192    /// Comma separated list of all experimental features that will be disabled
193    #[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    /// Enable and disable features using comma separated feature names from
228    /// environment variables "FORC_EXPERIMENTAL" and "FORC_NO_EXPERIMENTAL".
229    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}