wit_bindgen_rust_macro/
lib.rs

1use proc_macro2::{Span, TokenStream};
2use quote::ToTokens;
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
6use syn::parse::{Error, Parse, ParseStream, Result};
7use syn::punctuated::Punctuated;
8use syn::spanned::Spanned;
9use syn::{braced, token, LitStr, Token};
10use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId};
11use wit_bindgen_rust::{AsyncConfig, Opts, Ownership, WithOption};
12
13#[proc_macro]
14pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15    syn::parse_macro_input!(input as Config)
16        .expand()
17        .unwrap_or_else(Error::into_compile_error)
18        .into()
19}
20
21fn anyhow_to_syn(span: Span, err: anyhow::Error) -> Error {
22    let err = attach_with_context(err);
23    let mut msg = err.to_string();
24    for cause in err.chain().skip(1) {
25        msg.push_str(&format!("\n\nCaused by:\n  {cause}"));
26    }
27    Error::new(span, msg)
28}
29
30fn attach_with_context(err: anyhow::Error) -> anyhow::Error {
31    if let Some(e) = err.downcast_ref::<wit_bindgen_rust::MissingWith>() {
32        let option = e.0.clone();
33        return err.context(format!(
34            "missing one of:\n\
35            * `generate_all` option\n\
36            * `with: {{ \"{option}\": path::to::bindings, }}`\n\
37            * `with: {{ \"{option}\": generate, }}`\
38            "
39        ));
40    }
41    err
42}
43
44struct Config {
45    opts: Opts,
46    resolve: Resolve,
47    world: WorldId,
48    files: Vec<PathBuf>,
49    debug: bool,
50}
51
52/// The source of the wit package definition
53enum Source {
54    /// A path to a wit directory
55    Paths(Vec<PathBuf>),
56    /// Inline sources have an optional path to a directory of their dependencies
57    Inline(String, Option<Vec<PathBuf>>),
58}
59
60impl Parse for Config {
61    fn parse(input: ParseStream<'_>) -> Result<Self> {
62        let call_site = Span::call_site();
63        let mut opts = Opts::default();
64        let mut world = None;
65        let mut source = None;
66        let mut features = Vec::new();
67        let mut async_configured = false;
68        let mut debug = false;
69
70        if input.peek(token::Brace) {
71            let content;
72            syn::braced!(content in input);
73            let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
74            for field in fields.into_pairs() {
75                match field.into_value() {
76                    Opt::Path(span, p) => {
77                        let paths = p.into_iter().map(|f| PathBuf::from(f.value())).collect();
78
79                        source = Some(match source {
80                            Some(Source::Paths(_)) | Some(Source::Inline(_, Some(_))) => {
81                                return Err(Error::new(span, "cannot specify second source"));
82                            }
83                            Some(Source::Inline(i, None)) => Source::Inline(i, Some(paths)),
84                            None => Source::Paths(paths),
85                        })
86                    }
87                    Opt::World(s) => {
88                        if world.is_some() {
89                            return Err(Error::new(s.span(), "cannot specify second world"));
90                        }
91                        world = Some(s.value());
92                    }
93                    Opt::Inline(s) => {
94                        source = Some(match source {
95                            Some(Source::Inline(_, _)) => {
96                                return Err(Error::new(s.span(), "cannot specify second source"));
97                            }
98                            Some(Source::Paths(p)) => Source::Inline(s.value(), Some(p)),
99                            None => Source::Inline(s.value(), None),
100                        })
101                    }
102                    Opt::UseStdFeature => opts.std_feature = true,
103                    Opt::RawStrings => opts.raw_strings = true,
104                    Opt::Ownership(ownership) => opts.ownership = ownership,
105                    Opt::Skip(list) => opts.skip.extend(list.iter().map(|i| i.value())),
106                    Opt::RuntimePath(path) => opts.runtime_path = Some(path.value()),
107                    Opt::BitflagsPath(path) => opts.bitflags_path = Some(path.value()),
108                    Opt::Stubs => {
109                        opts.stubs = true;
110                    }
111                    Opt::ExportPrefix(prefix) => opts.export_prefix = Some(prefix.value()),
112                    Opt::AdditionalDerives(paths) => {
113                        opts.additional_derive_attributes = paths
114                            .into_iter()
115                            .map(|p| p.into_token_stream().to_string())
116                            .collect()
117                    }
118                    Opt::AdditionalDerivesIgnore(list) => {
119                        opts.additional_derive_ignore =
120                            list.into_iter().map(|i| i.value()).collect()
121                    }
122                    Opt::With(with) => opts.with.extend(with),
123                    Opt::GenerateAll => {
124                        opts.generate_all = true;
125                    }
126                    Opt::TypeSectionSuffix(suffix) => {
127                        opts.type_section_suffix = Some(suffix.value());
128                    }
129                    Opt::DisableRunCtorsOnceWorkaround(enable) => {
130                        opts.disable_run_ctors_once_workaround = enable.value();
131                    }
132                    Opt::DefaultBindingsModule(enable) => {
133                        opts.default_bindings_module = Some(enable.value());
134                    }
135                    Opt::ExportMacroName(name) => {
136                        opts.export_macro_name = Some(name.value());
137                    }
138                    Opt::PubExportMacro(enable) => {
139                        opts.pub_export_macro = enable.value();
140                    }
141                    Opt::GenerateUnusedTypes(enable) => {
142                        opts.generate_unused_types = enable.value();
143                    }
144                    Opt::Features(f) => {
145                        features.extend(f.into_iter().map(|f| f.value()));
146                    }
147                    Opt::DisableCustomSectionLinkHelpers(disable) => {
148                        opts.disable_custom_section_link_helpers = disable.value();
149                    }
150                    Opt::Debug(enable) => {
151                        debug = enable.value();
152                    }
153                    Opt::Async(val, span) => {
154                        if async_configured {
155                            return Err(Error::new(span, "cannot specify second async config"));
156                        }
157                        async_configured = true;
158                        if !matches!(val, AsyncConfig::None) && !cfg!(feature = "async") {
159                            return Err(Error::new(
160                                span,
161                                "must enable `async` feature to enable async imports and/or exports",
162                            ));
163                        }
164                        opts.async_ = val;
165                    }
166                }
167            }
168        } else {
169            world = input.parse::<Option<syn::LitStr>>()?.map(|s| s.value());
170            if input.parse::<Option<syn::token::In>>()?.is_some() {
171                source = Some(Source::Paths(vec![PathBuf::from(
172                    input.parse::<syn::LitStr>()?.value(),
173                )]));
174            }
175        }
176        let (resolve, pkgs, files) =
177            parse_source(&source, &features).map_err(|err| anyhow_to_syn(call_site, err))?;
178        let world = select_world(&resolve, &pkgs, world.as_deref())
179            .map_err(|e| anyhow_to_syn(call_site, e))?;
180        Ok(Config {
181            opts,
182            resolve,
183            world,
184            files,
185            debug,
186        })
187    }
188}
189
190fn select_world(
191    resolve: &Resolve,
192    pkgs: &[PackageId],
193    world: Option<&str>,
194) -> anyhow::Result<WorldId> {
195    if pkgs.len() == 1 {
196        resolve.select_world(pkgs[0], world)
197    } else {
198        assert!(!pkgs.is_empty());
199        match world {
200            Some(name) => {
201                if !name.contains(":") {
202                    anyhow::bail!(
203                        "with multiple packages a fully qualified \
204                         world name must be specified"
205                    )
206                }
207
208                // This will ignore the package argument due to the fully
209                // qualified name being used.
210                resolve.select_world(pkgs[0], world)
211            }
212            None => {
213                let worlds = pkgs
214                    .iter()
215                    .filter_map(|p| resolve.select_world(*p, None).ok())
216                    .collect::<Vec<_>>();
217                match &worlds[..] {
218                    [] => anyhow::bail!("no packages have a world"),
219                    [world] => Ok(*world),
220                    _ => anyhow::bail!("multiple packages have a world, must specify which to use"),
221                }
222            }
223        }
224    }
225}
226
227/// Parse the source
228fn parse_source(
229    source: &Option<Source>,
230    features: &[String],
231) -> anyhow::Result<(Resolve, Vec<PackageId>, Vec<PathBuf>)> {
232    let mut resolve = Resolve::default();
233    resolve.features.extend(features.iter().cloned());
234    let mut files = Vec::new();
235    let mut pkgs = Vec::new();
236    let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
237    let mut parse = |paths: &[PathBuf]| -> anyhow::Result<()> {
238        for path in paths {
239            let p = root.join(path);
240            // Try to normalize the path to make the error message more understandable when
241            // the path is not correct. Fallback to the original path if normalization fails
242            // (probably return an error somewhere else).
243            let normalized_path = match std::fs::canonicalize(&p) {
244                Ok(p) => p,
245                Err(_) => p.to_path_buf(),
246            };
247            let (pkg, sources) = resolve.push_path(normalized_path)?;
248            pkgs.push(pkg);
249            files.extend(sources.paths().map(|p| p.to_owned()));
250        }
251        Ok(())
252    };
253    match source {
254        Some(Source::Inline(s, path)) => {
255            if let Some(p) = path {
256                parse(p)?;
257            }
258            pkgs.push(resolve.push_group(UnresolvedPackageGroup::parse("macro-input", s)?)?);
259        }
260        Some(Source::Paths(p)) => parse(p)?,
261        None => parse(&vec![root.join("wit")])?,
262    };
263
264    Ok((resolve, pkgs, files))
265}
266
267impl Config {
268    fn expand(self) -> Result<TokenStream> {
269        let mut files = Default::default();
270        let mut generator = self.opts.build();
271        generator
272            .generate(&self.resolve, self.world, &mut files)
273            .map_err(|e| anyhow_to_syn(Span::call_site(), e))?;
274        let (_, src) = files.iter().next().unwrap();
275        let mut src = std::str::from_utf8(src).unwrap().to_string();
276
277        // If a magical `WIT_BINDGEN_DEBUG` environment variable is set then
278        // place a formatted version of the expanded code into a file. This file
279        // will then show up in rustc error messages for any codegen issues and can
280        // be inspected manually.
281        if std::env::var("WIT_BINDGEN_DEBUG").is_ok() || self.debug {
282            static INVOCATION: AtomicUsize = AtomicUsize::new(0);
283            let root = Path::new(env!("DEBUG_OUTPUT_DIR"));
284            let world_name = &self.resolve.worlds[self.world].name;
285            let n = INVOCATION.fetch_add(1, Relaxed);
286            let path = root.join(format!("{world_name}{n}.rs"));
287
288            // optimistically format the code but don't require success
289            let contents = match fmt(&src) {
290                Ok(formatted) => formatted,
291                Err(_) => src.clone(),
292            };
293            std::fs::write(&path, contents.as_bytes()).unwrap();
294
295            src = format!("include!({path:?});");
296        }
297        let mut contents = src.parse::<TokenStream>().unwrap();
298
299        // Include a dummy `include_bytes!` for any files we read so rustc knows that
300        // we depend on the contents of those files.
301        for file in self.files.iter() {
302            contents.extend(
303                format!(
304                    "const _: &[u8] = include_bytes!(r#\"{}\"#);\n",
305                    file.display()
306                )
307                .parse::<TokenStream>()
308                .unwrap(),
309            );
310        }
311
312        Ok(contents)
313    }
314}
315
316mod kw {
317    syn::custom_keyword!(std_feature);
318    syn::custom_keyword!(raw_strings);
319    syn::custom_keyword!(skip);
320    syn::custom_keyword!(world);
321    syn::custom_keyword!(path);
322    syn::custom_keyword!(inline);
323    syn::custom_keyword!(ownership);
324    syn::custom_keyword!(runtime_path);
325    syn::custom_keyword!(bitflags_path);
326    syn::custom_keyword!(exports);
327    syn::custom_keyword!(stubs);
328    syn::custom_keyword!(export_prefix);
329    syn::custom_keyword!(additional_derives);
330    syn::custom_keyword!(additional_derives_ignore);
331    syn::custom_keyword!(with);
332    syn::custom_keyword!(generate_all);
333    syn::custom_keyword!(type_section_suffix);
334    syn::custom_keyword!(disable_run_ctors_once_workaround);
335    syn::custom_keyword!(default_bindings_module);
336    syn::custom_keyword!(export_macro_name);
337    syn::custom_keyword!(pub_export_macro);
338    syn::custom_keyword!(generate_unused_types);
339    syn::custom_keyword!(features);
340    syn::custom_keyword!(disable_custom_section_link_helpers);
341    syn::custom_keyword!(imports);
342    syn::custom_keyword!(debug);
343}
344
345#[derive(Clone)]
346enum ExportKey {
347    World,
348    Name(syn::LitStr),
349}
350
351impl Parse for ExportKey {
352    fn parse(input: ParseStream<'_>) -> Result<Self> {
353        let l = input.lookahead1();
354        Ok(if l.peek(kw::world) {
355            input.parse::<kw::world>()?;
356            Self::World
357        } else {
358            Self::Name(input.parse()?)
359        })
360    }
361}
362
363impl From<ExportKey> for wit_bindgen_rust::ExportKey {
364    fn from(key: ExportKey) -> Self {
365        match key {
366            ExportKey::World => Self::World,
367            ExportKey::Name(s) => Self::Name(s.value()),
368        }
369    }
370}
371
372enum AsyncConfigSomeKind {
373    Imports,
374    Exports,
375}
376
377enum Opt {
378    World(syn::LitStr),
379    Path(Span, Vec<syn::LitStr>),
380    Inline(syn::LitStr),
381    UseStdFeature,
382    RawStrings,
383    Skip(Vec<syn::LitStr>),
384    Ownership(Ownership),
385    RuntimePath(syn::LitStr),
386    BitflagsPath(syn::LitStr),
387    Stubs,
388    ExportPrefix(syn::LitStr),
389    // Parse as paths so we can take the concrete types/macro names rather than raw strings
390    AdditionalDerives(Vec<syn::Path>),
391    AdditionalDerivesIgnore(Vec<syn::LitStr>),
392    With(HashMap<String, WithOption>),
393    GenerateAll,
394    TypeSectionSuffix(syn::LitStr),
395    DisableRunCtorsOnceWorkaround(syn::LitBool),
396    DefaultBindingsModule(syn::LitStr),
397    ExportMacroName(syn::LitStr),
398    PubExportMacro(syn::LitBool),
399    GenerateUnusedTypes(syn::LitBool),
400    Features(Vec<syn::LitStr>),
401    DisableCustomSectionLinkHelpers(syn::LitBool),
402    Async(AsyncConfig, Span),
403    Debug(syn::LitBool),
404}
405
406impl Parse for Opt {
407    fn parse(input: ParseStream<'_>) -> Result<Self> {
408        let l = input.lookahead1();
409        if l.peek(kw::path) {
410            input.parse::<kw::path>()?;
411            input.parse::<Token![:]>()?;
412            // the `path` supports two forms:
413            // * path: "xxx"
414            // * path: ["aaa", "bbb"]
415            if input.peek(token::Bracket) {
416                let contents;
417                syn::bracketed!(contents in input);
418                let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
419                Ok(Opt::Path(list.span(), list.into_iter().collect()))
420            } else {
421                let path: LitStr = input.parse()?;
422                Ok(Opt::Path(path.span(), vec![path]))
423            }
424        } else if l.peek(kw::inline) {
425            input.parse::<kw::inline>()?;
426            input.parse::<Token![:]>()?;
427            Ok(Opt::Inline(input.parse()?))
428        } else if l.peek(kw::world) {
429            input.parse::<kw::world>()?;
430            input.parse::<Token![:]>()?;
431            Ok(Opt::World(input.parse()?))
432        } else if l.peek(kw::std_feature) {
433            input.parse::<kw::std_feature>()?;
434            Ok(Opt::UseStdFeature)
435        } else if l.peek(kw::raw_strings) {
436            input.parse::<kw::raw_strings>()?;
437            Ok(Opt::RawStrings)
438        } else if l.peek(kw::ownership) {
439            input.parse::<kw::ownership>()?;
440            input.parse::<Token![:]>()?;
441            let ownership = input.parse::<syn::Ident>()?;
442            Ok(Opt::Ownership(match ownership.to_string().as_str() {
443                "Owning" => Ownership::Owning,
444                "Borrowing" => Ownership::Borrowing {
445                    duplicate_if_necessary: {
446                        let contents;
447                        braced!(contents in input);
448                        let field = contents.parse::<syn::Ident>()?;
449                        match field.to_string().as_str() {
450                            "duplicate_if_necessary" => {
451                                contents.parse::<Token![:]>()?;
452                                contents.parse::<syn::LitBool>()?.value
453                            }
454                            name => {
455                                return Err(Error::new(
456                                    field.span(),
457                                    format!(
458                                        "unrecognized `Ownership::Borrowing` field: `{name}`; \
459                                         expected `duplicate_if_necessary`"
460                                    ),
461                                ));
462                            }
463                        }
464                    },
465                },
466                name => {
467                    return Err(Error::new(
468                        ownership.span(),
469                        format!(
470                            "unrecognized ownership: `{name}`; \
471                             expected `Owning` or `Borrowing`"
472                        ),
473                    ));
474                }
475            }))
476        } else if l.peek(kw::skip) {
477            input.parse::<kw::skip>()?;
478            input.parse::<Token![:]>()?;
479            let contents;
480            syn::bracketed!(contents in input);
481            let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
482            Ok(Opt::Skip(list.iter().cloned().collect()))
483        } else if l.peek(kw::runtime_path) {
484            input.parse::<kw::runtime_path>()?;
485            input.parse::<Token![:]>()?;
486            Ok(Opt::RuntimePath(input.parse()?))
487        } else if l.peek(kw::bitflags_path) {
488            input.parse::<kw::bitflags_path>()?;
489            input.parse::<Token![:]>()?;
490            Ok(Opt::BitflagsPath(input.parse()?))
491        } else if l.peek(kw::stubs) {
492            input.parse::<kw::stubs>()?;
493            Ok(Opt::Stubs)
494        } else if l.peek(kw::export_prefix) {
495            input.parse::<kw::export_prefix>()?;
496            input.parse::<Token![:]>()?;
497            Ok(Opt::ExportPrefix(input.parse()?))
498        } else if l.peek(kw::additional_derives) {
499            input.parse::<kw::additional_derives>()?;
500            input.parse::<Token![:]>()?;
501            let contents;
502            syn::bracketed!(contents in input);
503            let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
504            Ok(Opt::AdditionalDerives(list.iter().cloned().collect()))
505        } else if l.peek(kw::additional_derives_ignore) {
506            input.parse::<kw::additional_derives_ignore>()?;
507            input.parse::<Token![:]>()?;
508            let contents;
509            syn::bracketed!(contents in input);
510            let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
511            Ok(Opt::AdditionalDerivesIgnore(list.iter().cloned().collect()))
512        } else if l.peek(kw::with) {
513            input.parse::<kw::with>()?;
514            input.parse::<Token![:]>()?;
515            let contents;
516            let _lbrace = braced!(contents in input);
517            let fields: Punctuated<_, Token![,]> =
518                contents.parse_terminated(with_field_parse, Token![,])?;
519            Ok(Opt::With(HashMap::from_iter(fields.into_iter())))
520        } else if l.peek(kw::generate_all) {
521            input.parse::<kw::generate_all>()?;
522            Ok(Opt::GenerateAll)
523        } else if l.peek(kw::type_section_suffix) {
524            input.parse::<kw::type_section_suffix>()?;
525            input.parse::<Token![:]>()?;
526            Ok(Opt::TypeSectionSuffix(input.parse()?))
527        } else if l.peek(kw::disable_run_ctors_once_workaround) {
528            input.parse::<kw::disable_run_ctors_once_workaround>()?;
529            input.parse::<Token![:]>()?;
530            Ok(Opt::DisableRunCtorsOnceWorkaround(input.parse()?))
531        } else if l.peek(kw::default_bindings_module) {
532            input.parse::<kw::default_bindings_module>()?;
533            input.parse::<Token![:]>()?;
534            Ok(Opt::DefaultBindingsModule(input.parse()?))
535        } else if l.peek(kw::export_macro_name) {
536            input.parse::<kw::export_macro_name>()?;
537            input.parse::<Token![:]>()?;
538            Ok(Opt::ExportMacroName(input.parse()?))
539        } else if l.peek(kw::pub_export_macro) {
540            input.parse::<kw::pub_export_macro>()?;
541            input.parse::<Token![:]>()?;
542            Ok(Opt::PubExportMacro(input.parse()?))
543        } else if l.peek(kw::generate_unused_types) {
544            input.parse::<kw::generate_unused_types>()?;
545            input.parse::<Token![:]>()?;
546            Ok(Opt::GenerateUnusedTypes(input.parse()?))
547        } else if l.peek(kw::features) {
548            input.parse::<kw::features>()?;
549            input.parse::<Token![:]>()?;
550            let contents;
551            syn::bracketed!(contents in input);
552            let list = Punctuated::<_, Token![,]>::parse_terminated(&contents)?;
553            Ok(Opt::Features(list.into_iter().collect()))
554        } else if l.peek(kw::disable_custom_section_link_helpers) {
555            input.parse::<kw::disable_custom_section_link_helpers>()?;
556            input.parse::<Token![:]>()?;
557            Ok(Opt::DisableCustomSectionLinkHelpers(input.parse()?))
558        } else if l.peek(kw::debug) {
559            input.parse::<kw::debug>()?;
560            input.parse::<Token![:]>()?;
561            Ok(Opt::Debug(input.parse()?))
562        } else if l.peek(Token![async]) {
563            let span = input.parse::<Token![async]>()?.span;
564            input.parse::<Token![:]>()?;
565            if input.peek(syn::LitBool) {
566                if input.parse::<syn::LitBool>()?.value {
567                    Ok(Opt::Async(AsyncConfig::All, span))
568                } else {
569                    Ok(Opt::Async(AsyncConfig::None, span))
570                }
571            } else {
572                let mut imports = Vec::new();
573                let mut exports = Vec::new();
574                let contents;
575                syn::braced!(contents in input);
576                for (kind, values) in
577                    contents.parse_terminated(parse_async_some_field, Token![,])?
578                {
579                    match kind {
580                        AsyncConfigSomeKind::Imports => imports = values,
581                        AsyncConfigSomeKind::Exports => exports = values,
582                    }
583                }
584                Ok(Opt::Async(AsyncConfig::Some { imports, exports }, span))
585            }
586        } else {
587            Err(l.error())
588        }
589    }
590}
591
592fn with_field_parse(input: ParseStream<'_>) -> Result<(String, WithOption)> {
593    let interface = input.parse::<syn::LitStr>()?.value();
594    input.parse::<Token![:]>()?;
595    let start = input.span();
596    let path = input.parse::<syn::Path>()?;
597
598    // It's not possible for the segments of a path to be empty
599    let span = start
600        .join(path.segments.last().unwrap().ident.span())
601        .unwrap_or(start);
602
603    if path.is_ident("generate") {
604        return Ok((interface, WithOption::Generate));
605    }
606
607    let mut buf = String::new();
608    let append = |buf: &mut String, segment: syn::PathSegment| -> Result<()> {
609        if !segment.arguments.is_none() {
610            return Err(Error::new(
611                span,
612                "Module path must not contain angles or parens",
613            ));
614        }
615
616        buf.push_str(&segment.ident.to_string());
617
618        Ok(())
619    };
620
621    if path.leading_colon.is_some() {
622        buf.push_str("::");
623    }
624
625    let mut segments = path.segments.into_iter();
626
627    if let Some(segment) = segments.next() {
628        append(&mut buf, segment)?;
629    }
630
631    for segment in segments {
632        buf.push_str("::");
633        append(&mut buf, segment)?;
634    }
635
636    Ok((interface, WithOption::Path(buf)))
637}
638
639/// Format a valid Rust string
640fn fmt(input: &str) -> Result<String> {
641    let syntax_tree = syn::parse_file(&input)?;
642    Ok(prettyplease::unparse(&syntax_tree))
643}
644
645fn parse_async_some_field(input: ParseStream<'_>) -> Result<(AsyncConfigSomeKind, Vec<String>)> {
646    let lookahead = input.lookahead1();
647    let kind = if lookahead.peek(kw::imports) {
648        input.parse::<kw::imports>()?;
649        input.parse::<Token![:]>()?;
650        AsyncConfigSomeKind::Imports
651    } else if lookahead.peek(kw::exports) {
652        input.parse::<kw::exports>()?;
653        input.parse::<Token![:]>()?;
654        AsyncConfigSomeKind::Exports
655    } else {
656        return Err(lookahead.error());
657    };
658
659    let list;
660    syn::bracketed!(list in input);
661    let fields = list.parse_terminated(Parse::parse, Token![,])?;
662
663    Ok((
664        kind,
665        fields.iter().map(|s: &syn::LitStr| s.value()).collect(),
666    ))
667}