Skip to main content

wit_bindgen_core/
chainable_method.rs

1use anyhow::{Result, bail};
2use std::collections::HashSet;
3use std::fmt;
4use wit_parser::{Function, FunctionKind, Resolve, WorldKey};
5
6/// Structure used to parse the command line argument `--chainable-method` consistently
7/// across guest generators.
8#[cfg_attr(feature = "clap", derive(clap::Parser))]
9#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
10#[derive(Clone, Default, Debug)]
11pub struct ChainableMethodFilterSet {
12    /// Determines which resource methods should have chaining enabled.
13    /// Chaining takes a WIT method import returning nothing, and modifies bindgen
14    /// in a language-dependent way to return `self` in the glue code. This does
15    /// not affect the ABI in any way.
16    ///
17    /// This option can be passed multiple times and additionally accepts
18    /// comma-separated values for each option passed. Each individual argument
19    /// passed here can be one of:
20    ///
21    /// - `all` - all applicable methods will be chainable
22    /// - `foo:bar/baz#my-resource` - enable chaining for all methods in a resource
23    /// - `foo:bar/baz#my-resource.some-method` - enable chaining for particular method
24    ///
25    /// Each filter may also have one of two modifier prefixes:
26    /// - `-` - inverts the selection; e.g. `-all` will disable chaining for all
27    /// - `&` - makes the chainable return `&Self` instead of `Self` (borrowing)
28    ///
29    /// For instance, `&foo:bar/baz#my-resource` will make all methods in said resource
30    /// borrowing chainable, while `-foo:bar/baz#my-resource.some-method` will disable it
31    /// for that particular method.
32    ///
33    /// Options are processed in the order they are passed here, so if a method
34    /// matches two directives passed the least-specific one should be last.
35    #[cfg_attr(
36        feature = "clap",
37        arg(
38            long = "chainable-methods",
39            value_parser = parse_chainable_method,
40            value_delimiter =',',
41            value_name = "FILTER",
42        ),
43    )]
44    chainable_methods: Vec<ChainableMethod>,
45
46    #[cfg_attr(feature = "clap", arg(skip))]
47    #[cfg_attr(feature = "serde", serde(skip))]
48    used_options: HashSet<usize>,
49}
50
51#[cfg(feature = "clap")]
52fn parse_chainable_method(s: &str) -> Result<ChainableMethod, String> {
53    Ok(ChainableMethod::parse(s))
54}
55
56#[derive(Clone, Copy, Debug)]
57pub enum ChainingMode {
58    Owning,
59    Borrowing,
60}
61
62impl ChainableMethodFilterSet {
63    /// Returns a set where all functions should be chainable or not depending on
64    /// `enable` provided.
65    pub fn all(mode: ChainingMode) -> ChainableMethodFilterSet {
66        ChainableMethodFilterSet {
67            chainable_methods: vec![ChainableMethod {
68                mode: Some(mode),
69                filter: ChainableMethodFilter::All,
70            }],
71            used_options: HashSet::new(),
72        }
73    }
74
75    /// Returns whether the `func` provided should be made chainable
76    pub fn should_be_chainable(
77        &mut self,
78        resolve: &Resolve,
79        interface: Option<&WorldKey>,
80        func: &Function,
81        is_import: bool,
82    ) -> Option<ChainingMode> {
83        if !is_import {
84            return None;
85        }
86
87        if func.result.is_some() {
88            return None;
89        }
90
91        match func.kind {
92            FunctionKind::AsyncMethod(resource) | FunctionKind::Method(resource) => {
93                let interface_name = match interface.map(|key| resolve.name_world_key(key)) {
94                    Some(str) => str + "#",
95                    None => "".into(),
96                };
97
98                let resource_name_to_test = format!(
99                    "{}{}",
100                    interface_name,
101                    resolve.types[resource].name.as_ref().unwrap()
102                );
103
104                let method_name_to_test = format!("{}{}", interface_name, func.name);
105
106                for (i, opt) in self.chainable_methods.iter().enumerate() {
107                    match &opt.filter {
108                        ChainableMethodFilter::All => {
109                            self.used_options.insert(i);
110                            return opt.mode;
111                        }
112                        ChainableMethodFilter::Resource(s) => {
113                            if *s == resource_name_to_test {
114                                self.used_options.insert(i);
115                                return opt.mode;
116                            }
117                        }
118                        ChainableMethodFilter::Method(s) => {
119                            if *s == method_name_to_test {
120                                self.used_options.insert(i);
121                                return opt.mode;
122                            }
123                        }
124                    };
125                }
126
127                return None;
128            }
129            _ => {
130                return None;
131            }
132        }
133    }
134
135    /// Intended to be used in the header comment of generated code to help
136    /// indicate what options were specified.
137    pub fn debug_opts(&self) -> impl Iterator<Item = String> + '_ {
138        self.chainable_methods.iter().map(|opt| opt.to_string())
139    }
140
141    /// Tests whether all `--chainable-method` options were used throughout bindings
142    /// generation, returning an error if any were unused.
143    pub fn ensure_all_used(&self) -> Result<()> {
144        for (i, opt) in self.chainable_methods.iter().enumerate() {
145            if self.used_options.contains(&i) {
146                continue;
147            }
148            if !matches!(opt.filter, ChainableMethodFilter::All) {
149                bail!("unused chainable option: {opt}");
150            }
151        }
152        Ok(())
153    }
154
155    /// Pushes a new option into this set.
156    pub fn push(&mut self, directive: &str) {
157        self.chainable_methods
158            .push(ChainableMethod::parse(directive));
159    }
160}
161
162#[derive(Debug, Clone)]
163#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
164struct ChainableMethod {
165    mode: Option<ChainingMode>,
166    filter: ChainableMethodFilter,
167}
168
169impl ChainableMethod {
170    fn parse(s: &str) -> ChainableMethod {
171        let (s, mode) = match s.strip_prefix('-') {
172            Some(s) => (s, None),
173            None => match s.strip_prefix('&') {
174                Some(s) => (s, Some(ChainingMode::Borrowing)),
175                None => (s, Some(ChainingMode::Owning)),
176            },
177        };
178        let filter = match s {
179            "all" => ChainableMethodFilter::All,
180            other => {
181                if other.contains("[method]") {
182                    ChainableMethodFilter::Method(other.to_string())
183                } else {
184                    ChainableMethodFilter::Resource(other.to_string())
185                }
186            }
187        };
188        ChainableMethod { mode, filter }
189    }
190}
191
192impl fmt::Display for ChainableMethod {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        match self.mode {
195            Some(ChainingMode::Owning) => {}
196            Some(ChainingMode::Borrowing) => write!(f, "&")?,
197            None => write!(f, "-")?,
198        };
199        self.filter.fmt(f)
200    }
201}
202
203#[derive(Debug, Clone)]
204#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
205enum ChainableMethodFilter {
206    All,
207    Resource(String),
208    Method(String),
209}
210
211impl fmt::Display for ChainableMethodFilter {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        match self {
214            ChainableMethodFilter::All => write!(f, "all"),
215            ChainableMethodFilter::Resource(s) => write!(f, "{s}"),
216            ChainableMethodFilter::Method(s) => write!(f, "{s}"),
217        }
218    }
219}