fluence_sdk_wit/parse_macro_input/
item_foreign_mod.rs

1/*
2 * Copyright 2020 Fluence Labs Limited
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use super::ParseMacroInput;
18use crate::ast_types;
19use crate::ast_types::FCEAst;
20use crate::syn_error;
21
22use syn::Result;
23use syn::spanned::Spanned;
24
25const LINK_DIRECTIVE_NAME: &str = "link";
26const LINK_NAME_DIRECTIVE_NAME: &str = "link_name";
27const WASM_IMPORT_MODULE_DIRECTIVE_NAME: &str = "wasm_import_module";
28
29impl ParseMacroInput for syn::ItemForeignMod {
30    fn parse_macro_input(self) -> Result<FCEAst> {
31        check_foreign_section(&self)?;
32
33        let wasm_import_module: Option<String> = parse_wasm_import_module(&self);
34        let namespace = try_extract_namespace(wasm_import_module, &self)?;
35
36        let imports = extract_import_functions(&self)?;
37        check_imports(imports.iter().zip(self.items.iter().map(|i| i.span())))?;
38
39        let extern_mod_item = ast_types::AstExternMod {
40            namespace,
41            imports,
42            original: self,
43        };
44        Ok(FCEAst::ExternMod(extern_mod_item))
45    }
46}
47
48fn check_foreign_section(foreign_mod: &syn::ItemForeignMod) -> Result<()> {
49    match &foreign_mod.abi.name {
50        Some(name) if name.value() != "C" => {
51            syn_error!(foreign_mod.span(), "only 'C' abi is allowed")
52        }
53        _ => Ok(()),
54    }
55}
56
57/// Tries to find and parse wasm module name from
58///   #[link(wasm_import_module = "host")]
59fn parse_wasm_import_module(foreign_mod: &syn::ItemForeignMod) -> Option<String> {
60    foreign_mod
61        .attrs
62        .iter()
63        .filter_map(|attr| attr.parse_meta().ok())
64        .filter(|meta| meta.path().is_ident(LINK_DIRECTIVE_NAME))
65        .filter_map(|meta| {
66            let pair = match meta {
67                syn::Meta::List(mut meta_list) if meta_list.nested.len() == 1 => {
68                    meta_list.nested.pop().unwrap()
69                }
70                _ => return None,
71            };
72            Some(pair.into_tuple().0)
73        })
74        .filter_map(|nested| match nested {
75            syn::NestedMeta::Meta(meta) => Some(meta),
76            _ => None,
77        })
78        .filter(|meta| meta.path().is_ident(WASM_IMPORT_MODULE_DIRECTIVE_NAME))
79        .map(extract_value)
80        .collect()
81}
82
83fn try_extract_namespace(
84    attr: Option<String>,
85    foreign_mod: &syn::ItemForeignMod,
86) -> Result<String> {
87    match attr {
88        Some(namespace) if namespace.is_empty() => syn_error!(
89            foreign_mod.span(),
90            "import module name should be defined by 'wasm_import_module' directive"
91        ),
92        Some(namespace) => Ok(namespace),
93        None => syn_error!(
94            foreign_mod.span(),
95            "import module name should be defined by 'wasm_import_module' directive"
96        ),
97    }
98}
99
100fn extract_import_functions(
101    foreign_mod: &syn::ItemForeignMod,
102) -> Result<Vec<ast_types::AstExternFn>> {
103    foreign_mod
104        .items
105        .iter()
106        .cloned()
107        .map(parse_raw_foreign_item)
108        .collect::<Result<_>>()
109}
110
111/// This function checks whether these imports contains inner references. In this case glue
112/// code couldn't be generated.
113fn check_imports<'i>(
114    extern_fns: impl ExactSizeIterator<Item = (&'i ast_types::AstExternFn, proc_macro2::Span)>,
115) -> Result<()> {
116    use super::utils::contain_inner_ref;
117
118    for (extern_fn, span) in extern_fns {
119        if let Some(output_type) = &extern_fn.signature.output_type {
120            if contain_inner_ref(output_type) {
121                return crate::syn_error!(
122                    span,
123                    "import function can't return a value with references"
124                );
125            }
126        }
127    }
128
129    Ok(())
130}
131
132fn parse_raw_foreign_item(raw_item: syn::ForeignItem) -> Result<ast_types::AstExternFn> {
133    let function_item = match raw_item {
134        syn::ForeignItem::Fn(function_item) => function_item,
135        _ => {
136            return syn_error!(
137                raw_item.span(),
138                "#[fce] could be applied only to a function, struct ot extern block"
139            )
140        }
141    };
142
143    // parse the link_name attribute
144    //  #[link_name = "put"]
145    //  fn ipfs_put(ptr: i32, size: i32);
146    let link_name: Option<String> = function_item
147        .attrs
148        .iter()
149        .filter_map(|attr| attr.parse_meta().ok())
150        .filter(|meta| meta.path().is_ident(LINK_NAME_DIRECTIVE_NAME))
151        .map(extract_value)
152        .collect();
153
154    let link_name = match link_name {
155        Some(name) if name.is_empty() => None,
156        v @ Some(_) => v,
157        None => None,
158    };
159
160    let signature = super::item_fn::try_to_ast_signature(function_item.sig, function_item.vis)?;
161    let ast_extern_fn_item = ast_types::AstExternFn {
162        link_name,
163        signature,
164    };
165
166    Ok(ast_extern_fn_item)
167}
168
169fn extract_value(nested_meta: syn::Meta) -> Option<String> {
170    match nested_meta {
171        syn::Meta::NameValue(name_value) => match name_value.lit {
172            syn::Lit::Str(str) => Some(str.value()),
173            _ => None,
174        },
175        _ => None,
176    }
177}