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
/*
 * Copyright 2020 Fluence Labs Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use super::ParseMacroInput;
use crate::{fce_ast_types, AstRecordField};
use crate::fce_ast_types::FCEAst;

use syn::Error;
use syn::Result;
use syn::spanned::Spanned;
use crate::parsed_type::ParsedType;

impl ParseMacroInput for syn::ItemStruct {
    fn parse_macro_input(self) -> Result<FCEAst> {
        check_record(&self)?;

        let fields = match &self.fields {
            syn::Fields::Named(named_fields) => &named_fields.named,
            syn::Fields::Unnamed(unnamed_fields) => &unnamed_fields.unnamed,
            _ => return Err(Error::new(self.span(), "only named field allowed")),
        };

        let fields = fields
            .iter()
            .map(|field| {
                check_field(field)?;
                let field_name = field.ident.as_ref().map(|ident| {
                    ident
                        .to_string()
                        .split(' ')
                        .last()
                        .unwrap_or_default()
                        .to_string()
                });
                let field_type = ParsedType::from_type(&field.ty)?;
                Ok(AstRecordField {
                    name: field_name,
                    ty: field_type,
                })
            })
            .collect::<Result<Vec<_>>>()?;

        let name = self.ident.to_string();
        let ast_record_item = fce_ast_types::AstRecordItem {
            name,
            fields,
            original: Some(self),
        };

        Ok(FCEAst::Record(ast_record_item))
    }
}

fn check_record(record: &syn::ItemStruct) -> Result<()> {
    if record.generics.lt_token.is_some()
        || record.generics.gt_token.is_some()
        || record.generics.where_clause.is_some()
    {
        return Err(Error::new(
            record.span(),
            "#[fce] couldn't be applied to a struct with generics",
        ));
    }

    Ok(())
}

/// Check that record fields satisfy the following requirements:
///  - all fields must be public
///  - field must have only doc attributes
fn check_field(field: &syn::Field) -> Result<()> {
    match field.vis {
        syn::Visibility::Public(_) => {}
        _ => {
            return Err(Error::new(
                field.span(),
                "#[fce] could be applied only to struct with all public fields",
            ))
        }
    };

    const DOC_ATTR_NAME: &str = "doc";

    // Check that all attributes are doc attributes
    let is_all_attrs_public = field.attrs.iter().all(|attr| {
        let meta = match attr.parse_meta() {
            Ok(meta) => meta,
            Err(_) => return false,
        };
        meta.path().is_ident(DOC_ATTR_NAME)
    });

    if !is_all_attrs_public {
        return Err(Error::new(field.span(), "field attributes isn't allowed"));
    }

    Ok(())
}