Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/component-macro/src/bindgen.rs
3068 views
1
use proc_macro2::{Span, TokenStream};
2
use quote::ToTokens;
3
use std::collections::HashMap;
4
use std::env;
5
use std::path::{Path, PathBuf};
6
use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
7
use syn::parse::{Error, Parse, ParseStream, Result};
8
use syn::punctuated::Punctuated;
9
use syn::{Token, braced, token};
10
use wasmtime_wit_bindgen::{
11
FunctionConfig, FunctionFilter, FunctionFlags, Opts, Ownership, TrappableError,
12
};
13
use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId};
14
15
pub struct Config {
16
opts: Opts,
17
resolve: Resolve,
18
world: WorldId,
19
files: Vec<PathBuf>,
20
include_generated_code_from_file: bool,
21
}
22
23
pub fn expand(input: &Config) -> Result<TokenStream> {
24
let mut src = match input.opts.generate(&input.resolve, input.world) {
25
Ok(s) => s,
26
Err(e) => return Err(Error::new(Span::call_site(), e.to_string())),
27
};
28
29
if input.opts.stringify {
30
return Ok(quote::quote!(#src));
31
}
32
33
// If a magical `WASMTIME_DEBUG_BINDGEN` environment variable is set then
34
// place a formatted version of the expanded code into a file. This file
35
// will then show up in rustc error messages for any codegen issues and can
36
// be inspected manually.
37
if input.include_generated_code_from_file
38
|| input.opts.debug
39
|| std::env::var("WASMTIME_DEBUG_BINDGEN").is_ok()
40
{
41
static INVOCATION: AtomicUsize = AtomicUsize::new(0);
42
let root = Path::new(env!("DEBUG_OUTPUT_DIR"));
43
let world_name = &input.resolve.worlds[input.world].name;
44
let n = INVOCATION.fetch_add(1, Relaxed);
45
let path = root.join(format!("{world_name}{n}.rs"));
46
47
std::fs::write(&path, &src).unwrap();
48
49
// optimistically format the code but don't require success
50
drop(
51
std::process::Command::new("rustfmt")
52
.arg(&path)
53
.arg("--edition=2021")
54
.output(),
55
);
56
57
src = format!("include!({path:?});");
58
}
59
let mut contents = src.parse::<TokenStream>().unwrap();
60
61
// Include a dummy `include_str!` for any files we read so rustc knows that
62
// we depend on the contents of those files.
63
for file in input.files.iter() {
64
contents.extend(
65
format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display())
66
.parse::<TokenStream>()
67
.unwrap(),
68
);
69
}
70
71
Ok(contents)
72
}
73
74
impl Parse for Config {
75
fn parse(input: ParseStream<'_>) -> Result<Self> {
76
let call_site = Span::call_site();
77
let mut opts = Opts::default();
78
let mut world = None;
79
let mut inline = None;
80
let mut paths = Vec::new();
81
let mut imports_configured = false;
82
let mut exports_configured = false;
83
let mut include_generated_code_from_file = false;
84
85
if input.peek(token::Brace) {
86
let content;
87
syn::braced!(content in input);
88
let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
89
for field in fields.into_pairs() {
90
match field.into_value() {
91
Opt::Path(p) => {
92
paths.extend(p.into_iter().map(|p| p.value()));
93
}
94
Opt::World(s) => {
95
if world.is_some() {
96
return Err(Error::new(s.span(), "cannot specify second world"));
97
}
98
world = Some(s.value());
99
}
100
Opt::Inline(s) => {
101
if inline.is_some() {
102
return Err(Error::new(s.span(), "cannot specify second source"));
103
}
104
inline = Some(s.value());
105
}
106
Opt::Debug(val) => opts.debug = val,
107
Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
108
Opt::Ownership(val) => opts.ownership = val,
109
Opt::Interfaces(s) => {
110
if inline.is_some() {
111
return Err(Error::new(s.span(), "cannot specify a second source"));
112
}
113
inline = Some(format!(
114
"
115
package wasmtime:component-macro-synthesized;
116
117
world interfaces {{
118
{}
119
}}
120
",
121
s.value()
122
));
123
124
if world.is_some() {
125
return Err(Error::new(
126
s.span(),
127
"cannot specify a world with `interfaces`",
128
));
129
}
130
world = Some("wasmtime:component-macro-synthesized/interfaces".to_string());
131
132
opts.only_interfaces = true;
133
}
134
Opt::With(val) => opts.with.extend(val),
135
Opt::AdditionalDerives(paths) => {
136
opts.additional_derive_attributes = paths
137
.into_iter()
138
.map(|p| p.into_token_stream().to_string())
139
.collect()
140
}
141
Opt::Stringify(val) => opts.stringify = val,
142
Opt::SkipMutForwardingImpls(val) => opts.skip_mut_forwarding_impls = val,
143
Opt::RequireStoreDataSend(val) => opts.require_store_data_send = val,
144
Opt::WasmtimeCrate(f) => {
145
opts.wasmtime_crate = Some(f.into_token_stream().to_string())
146
}
147
Opt::Anyhow(val) => {
148
opts.anyhow = val;
149
}
150
Opt::IncludeGeneratedCodeFromFile(i) => include_generated_code_from_file = i,
151
Opt::Imports(config, span) => {
152
if imports_configured {
153
return Err(Error::new(span, "cannot specify imports configuration"));
154
}
155
opts.imports = config;
156
imports_configured = true;
157
}
158
Opt::Exports(config, span) => {
159
if exports_configured {
160
return Err(Error::new(span, "cannot specify exports configuration"));
161
}
162
opts.exports = config;
163
exports_configured = true;
164
}
165
}
166
}
167
} else {
168
world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());
169
if input.parse::<Option<syn::token::In>>()?.is_some() {
170
paths.push(input.parse::<syn::LitStr>()?.value());
171
}
172
}
173
let (resolve, pkgs, files) = parse_source(&paths, &inline)
174
.map_err(|err| Error::new(call_site, format!("{err:?}")))?;
175
176
let world = resolve
177
.select_world(&pkgs, world.as_deref())
178
.map_err(|e| Error::new(call_site, format!("{e:?}")))?;
179
Ok(Config {
180
opts,
181
resolve,
182
world,
183
files,
184
include_generated_code_from_file,
185
})
186
}
187
}
188
189
fn parse_source(
190
paths: &Vec<String>,
191
inline: &Option<String>,
192
) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {
193
let mut resolve = Resolve::default();
194
resolve.all_features = true;
195
let mut files = Vec::new();
196
let mut pkgs = Vec::new();
197
let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
198
let default = root.join("wit");
199
200
let parse = |resolve: &mut Resolve,
201
files: &mut Vec<PathBuf>,
202
pkgs: &mut Vec<PackageId>,
203
paths: &[PathBuf]|
204
-> anyhow::Result<_> {
205
for path in paths {
206
let p = root.join(path);
207
// Try to normalize the path to make the error message more understandable when
208
// the path is not correct. Fallback to the original path if normalization fails
209
// (probably return an error somewhere else).
210
let normalized_path = match std::fs::canonicalize(&p) {
211
Ok(p) => p,
212
Err(_) => p.to_path_buf(),
213
};
214
let (pkg, sources) = resolve.push_path(normalized_path)?;
215
pkgs.push(pkg);
216
files.extend(sources.paths().map(|p| p.to_owned()));
217
}
218
Ok(())
219
};
220
221
if paths.is_empty() {
222
if default.exists() {
223
parse(&mut resolve, &mut files, &mut pkgs, &[default])?;
224
}
225
} else {
226
parse(
227
&mut resolve,
228
&mut files,
229
&mut pkgs,
230
&paths.iter().map(|s| s.into()).collect::<Vec<_>>(),
231
)?;
232
}
233
234
if let Some(inline) = inline {
235
pkgs.truncate(0);
236
pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", inline)?)?);
237
}
238
239
Ok((resolve, pkgs, files))
240
}
241
242
mod kw {
243
syn::custom_keyword!(inline);
244
syn::custom_keyword!(path);
245
syn::custom_keyword!(tracing);
246
syn::custom_keyword!(verbose_tracing);
247
syn::custom_keyword!(trappable_error_type);
248
syn::custom_keyword!(world);
249
syn::custom_keyword!(ownership);
250
syn::custom_keyword!(interfaces);
251
syn::custom_keyword!(with);
252
syn::custom_keyword!(except_imports);
253
syn::custom_keyword!(only_imports);
254
syn::custom_keyword!(additional_derives);
255
syn::custom_keyword!(stringify);
256
syn::custom_keyword!(skip_mut_forwarding_impls);
257
syn::custom_keyword!(require_store_data_send);
258
syn::custom_keyword!(wasmtime_crate);
259
syn::custom_keyword!(anyhow);
260
syn::custom_keyword!(include_generated_code_from_file);
261
syn::custom_keyword!(debug);
262
syn::custom_keyword!(imports);
263
syn::custom_keyword!(exports);
264
syn::custom_keyword!(store);
265
syn::custom_keyword!(trappable);
266
syn::custom_keyword!(ignore_wit);
267
syn::custom_keyword!(exact);
268
syn::custom_keyword!(task_exit);
269
}
270
271
enum Opt {
272
World(syn::LitStr),
273
Path(Vec<syn::LitStr>),
274
Inline(syn::LitStr),
275
TrappableErrorType(Vec<TrappableError>),
276
Ownership(Ownership),
277
Interfaces(syn::LitStr),
278
With(HashMap<String, String>),
279
AdditionalDerives(Vec<syn::Path>),
280
Stringify(bool),
281
SkipMutForwardingImpls(bool),
282
RequireStoreDataSend(bool),
283
WasmtimeCrate(syn::Path),
284
Anyhow(bool),
285
IncludeGeneratedCodeFromFile(bool),
286
Debug(bool),
287
Imports(FunctionConfig, Span),
288
Exports(FunctionConfig, Span),
289
}
290
291
impl Parse for Opt {
292
fn parse(input: ParseStream<'_>) -> Result<Self> {
293
let l = input.lookahead1();
294
if l.peek(kw::debug) {
295
input.parse::<kw::debug>()?;
296
input.parse::<Token![:]>()?;
297
Ok(Opt::Debug(input.parse::<syn::LitBool>()?.value))
298
} else if l.peek(kw::path) {
299
input.parse::<kw::path>()?;
300
input.parse::<Token![:]>()?;
301
302
let mut paths: Vec<syn::LitStr> = vec![];
303
304
let l = input.lookahead1();
305
if l.peek(syn::LitStr) {
306
paths.push(input.parse()?);
307
} else if l.peek(syn::token::Bracket) {
308
let contents;
309
syn::bracketed!(contents in input);
310
let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
311
312
paths.extend(list);
313
} else {
314
return Err(l.error());
315
};
316
317
Ok(Opt::Path(paths))
318
} else if l.peek(kw::inline) {
319
input.parse::<kw::inline>()?;
320
input.parse::<Token![:]>()?;
321
Ok(Opt::Inline(input.parse()?))
322
} else if l.peek(kw::world) {
323
input.parse::<kw::world>()?;
324
input.parse::<Token![:]>()?;
325
Ok(Opt::World(input.parse()?))
326
} else if l.peek(kw::ownership) {
327
input.parse::<kw::ownership>()?;
328
input.parse::<Token![:]>()?;
329
let ownership = input.parse::<syn::Ident>()?;
330
Ok(Opt::Ownership(match ownership.to_string().as_str() {
331
"Owning" => Ownership::Owning,
332
"Borrowing" => Ownership::Borrowing {
333
duplicate_if_necessary: {
334
let contents;
335
braced!(contents in input);
336
let field = contents.parse::<syn::Ident>()?;
337
match field.to_string().as_str() {
338
"duplicate_if_necessary" => {
339
contents.parse::<Token![:]>()?;
340
contents.parse::<syn::LitBool>()?.value
341
}
342
name => {
343
return Err(Error::new(
344
field.span(),
345
format!(
346
"unrecognized `Ownership::Borrowing` field: `{name}`; \
347
expected `duplicate_if_necessary`"
348
),
349
));
350
}
351
}
352
},
353
},
354
name => {
355
return Err(Error::new(
356
ownership.span(),
357
format!(
358
"unrecognized ownership: `{name}`; \
359
expected `Owning` or `Borrowing`"
360
),
361
));
362
}
363
}))
364
} else if l.peek(kw::trappable_error_type) {
365
input.parse::<kw::trappable_error_type>()?;
366
input.parse::<Token![:]>()?;
367
let contents;
368
let _lbrace = braced!(contents in input);
369
let fields: Punctuated<_, Token![,]> =
370
contents.parse_terminated(trappable_error_field_parse, Token![,])?;
371
Ok(Opt::TrappableErrorType(Vec::from_iter(fields)))
372
} else if l.peek(kw::interfaces) {
373
input.parse::<kw::interfaces>()?;
374
input.parse::<Token![:]>()?;
375
Ok(Opt::Interfaces(input.parse::<syn::LitStr>()?))
376
} else if l.peek(kw::with) {
377
input.parse::<kw::with>()?;
378
input.parse::<Token![:]>()?;
379
let contents;
380
let _lbrace = braced!(contents in input);
381
let fields: Punctuated<(String, String), Token![,]> =
382
contents.parse_terminated(with_field_parse, Token![,])?;
383
Ok(Opt::With(HashMap::from_iter(fields)))
384
} else if l.peek(kw::additional_derives) {
385
input.parse::<kw::additional_derives>()?;
386
input.parse::<Token![:]>()?;
387
let contents;
388
syn::bracketed!(contents in input);
389
let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
390
Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))
391
} else if l.peek(kw::stringify) {
392
input.parse::<kw::stringify>()?;
393
input.parse::<Token![:]>()?;
394
Ok(Opt::Stringify(input.parse::<syn::LitBool>()?.value))
395
} else if l.peek(kw::skip_mut_forwarding_impls) {
396
input.parse::<kw::skip_mut_forwarding_impls>()?;
397
input.parse::<Token![:]>()?;
398
Ok(Opt::SkipMutForwardingImpls(
399
input.parse::<syn::LitBool>()?.value,
400
))
401
} else if l.peek(kw::require_store_data_send) {
402
input.parse::<kw::require_store_data_send>()?;
403
input.parse::<Token![:]>()?;
404
Ok(Opt::RequireStoreDataSend(
405
input.parse::<syn::LitBool>()?.value,
406
))
407
} else if l.peek(kw::wasmtime_crate) {
408
input.parse::<kw::wasmtime_crate>()?;
409
input.parse::<Token![:]>()?;
410
Ok(Opt::WasmtimeCrate(input.parse()?))
411
} else if l.peek(kw::anyhow) {
412
input.parse::<kw::anyhow>()?;
413
input.parse::<Token![:]>()?;
414
Ok(Opt::Anyhow(input.parse::<syn::LitBool>()?.value))
415
} else if l.peek(kw::include_generated_code_from_file) {
416
input.parse::<kw::include_generated_code_from_file>()?;
417
input.parse::<Token![:]>()?;
418
Ok(Opt::IncludeGeneratedCodeFromFile(
419
input.parse::<syn::LitBool>()?.value,
420
))
421
} else if l.peek(kw::imports) {
422
let span = input.parse::<kw::imports>()?.span;
423
input.parse::<Token![:]>()?;
424
Ok(Opt::Imports(parse_function_config(input)?, span))
425
} else if l.peek(kw::exports) {
426
let span = input.parse::<kw::exports>()?.span;
427
input.parse::<Token![:]>()?;
428
Ok(Opt::Exports(parse_function_config(input)?, span))
429
} else {
430
Err(l.error())
431
}
432
}
433
}
434
435
fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> {
436
let wit_path = input.parse::<syn::LitStr>()?.value();
437
input.parse::<Token![=>]>()?;
438
let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();
439
Ok(TrappableError {
440
wit_path,
441
rust_type_name,
442
})
443
}
444
445
fn with_field_parse(input: ParseStream<'_>) -> Result<(String, String)> {
446
let interface = input.parse::<syn::LitStr>()?.value();
447
input.parse::<Token![:]>()?;
448
let start = input.span();
449
let path = input.parse::<syn::Path>()?;
450
451
// It's not possible for the segments of a path to be empty
452
let span = start
453
.join(path.segments.last().unwrap().ident.span())
454
.unwrap_or(start);
455
456
let mut buf = String::new();
457
let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {
458
if segment.arguments != syn::PathArguments::None {
459
return Err(Error::new(
460
span,
461
"Module path must not contain angles or parens",
462
));
463
}
464
465
buf.push_str(&segment.ident.to_string());
466
467
Ok(())
468
};
469
470
if path.leading_colon.is_some() {
471
buf.push_str("::");
472
}
473
474
let mut segments = path.segments.into_iter();
475
476
if let Some(segment) = segments.next() {
477
append(&mut buf, segment)?;
478
}
479
480
for segment in segments {
481
buf.push_str("::");
482
append(&mut buf, segment)?;
483
}
484
485
Ok((interface, buf))
486
}
487
488
fn parse_function_config(input: ParseStream<'_>) -> Result<FunctionConfig> {
489
let content;
490
syn::braced!(content in input);
491
let mut ret = FunctionConfig::new();
492
493
let list = Punctuated::<FunctionConfigSyntax, Token![,]>::parse_terminated(&content)?;
494
for item in list.into_iter() {
495
ret.push(item.filter, item.flags);
496
}
497
498
return Ok(ret);
499
500
struct FunctionConfigSyntax {
501
filter: FunctionFilter,
502
flags: FunctionFlags,
503
}
504
505
impl Parse for FunctionConfigSyntax {
506
fn parse(input: ParseStream<'_>) -> Result<Self> {
507
let l = input.lookahead1();
508
let filter = if l.peek(syn::LitStr) {
509
FunctionFilter::Name(input.parse::<syn::LitStr>()?.value())
510
} else if l.peek(Token![default]) {
511
input.parse::<Token![default]>()?;
512
FunctionFilter::Default
513
} else {
514
return Err(l.error());
515
};
516
517
input.parse::<Token![:]>()?;
518
519
let mut flags = FunctionFlags::empty();
520
while !input.is_empty() {
521
let l = input.lookahead1();
522
if l.peek(Token![async]) {
523
input.parse::<Token![async]>()?;
524
flags |= FunctionFlags::ASYNC;
525
} else if l.peek(kw::tracing) {
526
input.parse::<kw::tracing>()?;
527
flags |= FunctionFlags::TRACING;
528
} else if l.peek(kw::verbose_tracing) {
529
input.parse::<kw::verbose_tracing>()?;
530
flags |= FunctionFlags::VERBOSE_TRACING;
531
} else if l.peek(kw::store) {
532
input.parse::<kw::store>()?;
533
flags |= FunctionFlags::STORE;
534
} else if l.peek(kw::trappable) {
535
input.parse::<kw::trappable>()?;
536
flags |= FunctionFlags::TRAPPABLE;
537
} else if l.peek(kw::ignore_wit) {
538
input.parse::<kw::ignore_wit>()?;
539
flags |= FunctionFlags::IGNORE_WIT;
540
} else if l.peek(kw::exact) {
541
input.parse::<kw::exact>()?;
542
flags |= FunctionFlags::EXACT;
543
} else if l.peek(kw::task_exit) {
544
input.parse::<kw::task_exit>()?;
545
flags |= FunctionFlags::TASK_EXIT;
546
} else {
547
return Err(l.error());
548
}
549
550
if input.peek(Token![|]) {
551
input.parse::<Token![|]>()?;
552
} else {
553
break;
554
}
555
}
556
557
Ok(FunctionConfigSyntax { filter, flags })
558
}
559
}
560
}
561
562