1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
use anyhow::{Result, bail};
use std::collections::HashSet;
use std::fmt;
use wit_parser::{Function, FunctionKind, Resolve, WorldKey};
/// Structure used to parse the command line argument `--async` consistently
/// across guest generators.
#[cfg_attr(feature = "clap", derive(clap::Parser))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[derive(Clone, Default, Debug)]
pub struct AsyncFilterSet {
/// Determines which functions to lift or lower `async`, if any.
///
/// This option can be passed multiple times and additionally accepts
/// comma-separated values for each option passed. Each individual argument
/// passed here can be one of:
///
/// - `all` - all imports and exports will be async
/// - `-all` - force all imports and exports to be sync
/// - `foo:bar/baz#method` - force this method to be async
/// - `import:foo:bar/baz#method` - force this method to be async, but only
/// as an import
/// - `-export:foo:bar/baz#method` - force this export to be sync
///
/// If a method is not listed in this option then the WIT's default bindings
/// mode will be used. If the WIT function is defined as `async` then async
/// bindings will be generated, otherwise sync bindings will be generated.
///
/// Options are processed in the order they are passed here, so if a method
/// matches two directives passed the least-specific one should be last.
#[cfg_attr(
feature = "clap",
arg(
long = "async",
value_parser = parse_async,
value_delimiter =',',
value_name = "FILTER",
),
)]
#[cfg_attr(feature = "serde", serde(rename = "async"))]
async_: Vec<Async>,
#[cfg_attr(feature = "clap", arg(skip))]
#[cfg_attr(feature = "serde", serde(skip))]
used_options: HashSet<usize>,
}
#[cfg(feature = "clap")]
fn parse_async(s: &str) -> Result<Async, String> {
Ok(Async::parse(s))
}
impl AsyncFilterSet {
/// Returns a set where all functions should be async or not depending on
/// `async_` provided.
pub fn all(async_: bool) -> AsyncFilterSet {
AsyncFilterSet {
async_: vec![Async {
enabled: async_,
filter: AsyncFilter::All,
}],
used_options: HashSet::new(),
}
}
/// Returns whether the `func` provided is to be bound `async` or not.
pub fn is_async(
&mut self,
resolve: &Resolve,
interface: Option<&WorldKey>,
func: &Function,
is_import: bool,
) -> bool {
let name_to_test = match interface {
Some(key) => format!("{}#{}", resolve.name_world_key(key), func.name),
None => func.name.clone(),
};
for (i, opt) in self.async_.iter().enumerate() {
let name = match &opt.filter {
AsyncFilter::All => {
self.used_options.insert(i);
return opt.enabled;
}
AsyncFilter::Function(s) => s,
AsyncFilter::Import(s) => {
if !is_import {
continue;
}
s
}
AsyncFilter::Export(s) => {
if is_import {
continue;
}
s
}
};
if *name == name_to_test {
self.used_options.insert(i);
return opt.enabled;
}
}
match &func.kind {
FunctionKind::Freestanding
| FunctionKind::Method(_)
| FunctionKind::Static(_)
| FunctionKind::Constructor(_) => false,
FunctionKind::AsyncFreestanding
| FunctionKind::AsyncMethod(_)
| FunctionKind::AsyncStatic(_) => true,
}
}
/// Intended to be used in the header comment of generated code to help
/// indicate what options were specified.
pub fn debug_opts(&self) -> impl Iterator<Item = String> + '_ {
self.async_.iter().map(|opt| opt.to_string())
}
/// Tests whether all `--async` options were used throughout bindings
/// generation, returning an error if any were unused.
pub fn ensure_all_used(&self) -> Result<()> {
for (i, opt) in self.async_.iter().enumerate() {
if self.used_options.contains(&i) {
continue;
}
if !matches!(opt.filter, AsyncFilter::All) {
bail!("unused async option: {opt}");
}
}
Ok(())
}
/// Returns whether any option explicitly requests that async is enabled.
pub fn any_enabled(&self) -> bool {
self.async_.iter().any(|o| o.enabled)
}
/// Pushes a new option into this set.
pub fn push(&mut self, directive: &str) {
self.async_.push(Async::parse(directive));
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
struct Async {
enabled: bool,
filter: AsyncFilter,
}
impl Async {
fn parse(s: &str) -> Async {
let (s, enabled) = match s.strip_prefix('-') {
Some(s) => (s, false),
None => (s, true),
};
let filter = match s {
"all" => AsyncFilter::All,
other => match other.strip_prefix("import:") {
Some(s) => AsyncFilter::Import(s.to_string()),
None => match other.strip_prefix("export:") {
Some(s) => AsyncFilter::Export(s.to_string()),
None => AsyncFilter::Function(s.to_string()),
},
},
};
Async { enabled, filter }
}
}
impl fmt::Display for Async {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.enabled {
write!(f, "-")?;
}
self.filter.fmt(f)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
enum AsyncFilter {
All,
Function(String),
Import(String),
Export(String),
}
impl fmt::Display for AsyncFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AsyncFilter::All => write!(f, "all"),
AsyncFilter::Function(s) => write!(f, "{s}"),
AsyncFilter::Import(s) => write!(f, "import:{s}"),
AsyncFilter::Export(s) => write!(f, "export:{s}"),
}
}
}