marine_macro_impl/parse_macro_input/
item_record.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::AstRecordField;
20use crate::ast_types::AstRecordFields;
21use crate::ast_types::MarineAst;
22use crate::syn_error;
23use crate::parsed_type::ParsedType;
24
25use syn::Result;
26use syn::spanned::Spanned;
27
28impl ParseMacroInput for syn::ItemStruct {
29    fn parse_macro_input(self) -> Result<MarineAst> {
30        check_record(&self)?;
31
32        let fields = match &self.fields {
33            syn::Fields::Named(named_fields) => &named_fields.named,
34            _ => return syn_error!(self.span(), "only named fields are allowed in structs"),
35        };
36
37        let fields = fields_into_ast(fields, &self.ident)?;
38        let fields = AstRecordFields::Named(fields);
39
40        let name = self.ident.to_string();
41        let ast_record_item = ast_types::AstRecord {
42            name,
43            fields,
44            original: self,
45        };
46        let ast_record_item = MarineAst::Record(Box::new(ast_record_item));
47
48        Ok(ast_record_item)
49    }
50}
51
52fn check_record(record: &syn::ItemStruct) -> Result<()> {
53    if record.generics.lt_token.is_some()
54        || record.generics.gt_token.is_some()
55        || record.generics.where_clause.is_some()
56    {
57        return syn_error!(
58            record.span(),
59            "#[marine] couldn't be applied to a struct with generics or lifetimes"
60        );
61    }
62
63    Ok(())
64}
65
66fn fields_into_ast(
67    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
68    record_ident: &syn::Ident,
69) -> Result<Vec<AstRecordField>> {
70    fields
71        .iter()
72        .map(|field| {
73            maybe_warn_about_non_doc_attributes(field, record_ident);
74
75            let name = field.ident.as_ref().map(|ident| {
76                ident
77                    .to_string()
78                    .split(' ')
79                    .last()
80                    .unwrap_or_default()
81                    .to_string()
82            });
83            let ty = ParsedType::from_type(&field.ty)?;
84
85            let record_field = AstRecordField { name, ty };
86            Ok(record_field)
87        })
88        .collect::<Result<Vec<_>>>()
89}
90
91/// Prints an error if a field has an any attribute except doc.
92fn maybe_warn_about_non_doc_attributes(field: &syn::Field, record_ident: &syn::Ident) {
93    for attr in field.attrs.iter() {
94        match attr.parse_meta() {
95            Ok(meta) if is_doc_attribute(&meta) => continue,
96            _ => {}
97        }
98
99        // TODO: print message with a span when diagnostic API stabilized
100        // https://github.com/rust-lang/rust/issues/54140
101        match &field.ident {
102            Some(ident) => eprintln!(
103                r#"warning: field "{}" of struct "{}" has an attribute which could cause compatibility issues"#,
104                ident, record_ident
105            ),
106            None => eprintln!(
107                r#"warning: field of struct "{}" has an attribute which could cause compatibility issues"#,
108                record_ident
109            ),
110        };
111    }
112}
113
114fn is_doc_attribute(meta: &syn::Meta) -> bool {
115    const DOC_ATTR_NAME: &str = "doc";
116
117    meta.path().is_ident(DOC_ATTR_NAME)
118}