df_ls_derive 0.3.0-rc.1

A language server for Dwarf Fortress RAW files
Documentation
mod token_ref_info;

use crate::common;
use quote::quote;
use syn::{Data, Field, Type};
pub use token_ref_info::*;

pub fn impl_referenceable_macro(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
    let name = &ast.ident;
    let fields = common::get_struct_enum_fields(ast);
    match ast.data {
        Data::Struct(_) => {
            let self_reference = get_self_reference_token(&fields)
                .expect("There is no `#[referenceable(self_reference)]` set.");
            let self_ref_ident = self_reference.ident.clone().unwrap();
            let self_ref_type = unwrap_option_type(self_reference);

            match self_ref_type {
                Type::Tuple(_) => {
                    let self_ref_index = find_self_ref_in_tuple(&self_ref_type);
                    quote! {
                        impl Referenceable for #name {
                            fn get_reference(&self) -> Option<df_ls_core::ReferenceTo<Self>> {
                                match &self.#self_ref_ident {
                                    Some(self_ref) => Some(self_ref.#self_ref_index.clone()),
                                    None => None,
                                }
                            }
                            fn get_ref_type() -> &'static str {
                                stringify!(#name)
                            }
                        }
                    }
                }
                Type::Path(_) => {
                    quote! {
                        impl Referenceable for #name {
                            fn get_reference(&self) -> Option<df_ls_core::ReferenceTo<Self>> {
                                self.#self_ref_ident.clone()
                            }
                            fn get_ref_type() -> &'static str {
                                stringify!(#name)
                            }
                        }
                    }
                }
                _ => panic!("This type is not supported for Referenceable."),
            }
        }
        Data::Enum(_) => {
            // Each nested type should implement Referenceable
            let match_items = {
                let mut parse_gen = quote! {};
                for (_i, field) in fields.iter().enumerate() {
                    let ident = field.ident.as_ref().unwrap();
                    parse_gen = quote! {
                        #parse_gen
                        Self::#ident(x) => {
                            // Switch from Reference to internal object to Self
                            match x.get_reference() {
                                Some(reference) => Some(ReferenceTo::new(reference.0)),
                                None => None,
                            }
                        },
                    };
                }
                parse_gen
            };
            quote! {
                impl Referenceable for #name {
                    fn get_reference(&self) -> Option<df_ls_core::ReferenceTo<Self>> {
                        match self{
                            #match_items
                        }
                    }
                    fn get_ref_type() -> &'static str {
                        stringify!(#name)
                    }
                }
            }
        }
        Data::Union(_) => {
            unimplemented!("Union is not implemented for Referenceable.");
        }
    }
}

fn get_self_reference_token(struct_fields: &[Field]) -> Option<Field> {
    let mut result = None;
    for field in struct_fields.iter() {
        let token_info = get_token_ref_info(field);
        if token_info.self_reference {
            if result.is_none() {
                result = Some(field.clone());
            } else {
                panic!("ERROR: Only one primary_token can be set.");
            }
        }
    }
    result
}

/// Take inside of `Option<T>` and return `T`
fn unwrap_option_type(field: Field) -> Type {
    if let Type::Path(path) = field.ty {
        for path_segment in path.path.segments {
            if path_segment.ident == "Option" {
                if let syn::PathArguments::AngleBracketed(args) = path_segment.arguments {
                    for generic_arg in args.args {
                        if let syn::GenericArgument::Type(ty) = generic_arg {
                            return ty;
                        }
                    }
                }
            }
        }
    }
    panic!("This type is not supported for Referenceable.");
}

/// Find the first index in tuple that is of type `ReferenceTo<Self>`
fn find_self_ref_in_tuple(ty: &Type) -> syn::Index {
    if let Type::Tuple(tuple) = ty {
        for (index, item) in tuple.elems.iter().enumerate() {
            if is_self_ref(item) {
                return syn::Index::from(index);
            }
        }
    }
    panic!("Internal error: Expected Tuple, found something else.");
}

/// Check if type is `ReferenceTo<Self>`
fn is_self_ref(ty: &Type) -> bool {
    if let Type::Path(path) = ty {
        for segment in &path.path.segments {
            if segment.ident == "ReferenceTo" {
                if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
                    for generic_arg in &args.args {
                        if let syn::GenericArgument::Type(ty) = generic_arg {
                            return is_self(ty);
                        }
                    }
                }
            }
        }
    }
    false
}

/// Check if type is `Self`
fn is_self(ty: &Type) -> bool {
    if let Type::Path(path) = ty {
        for segment in &path.path.segments {
            if segment.ident == "Self" {
                return true;
            }
        }
    }
    false
}