wit_bindgen_core/
chainable_method.rs1use anyhow::{Result, bail};
2use std::collections::HashSet;
3use std::fmt;
4use wit_parser::{Function, FunctionKind, Resolve, WorldKey};
5
6#[cfg_attr(feature = "clap", derive(clap::Parser))]
9#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
10#[derive(Clone, Default, Debug)]
11pub struct ChainableMethodFilterSet {
12 #[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 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 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 pub fn debug_opts(&self) -> impl Iterator<Item = String> + '_ {
138 self.chainable_methods.iter().map(|opt| opt.to_string())
139 }
140
141 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 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}