named 0.1.0

Procedural macro which allows you to produce functions which can be called with named arguments, optionally with default values.
Documentation
use crate::attr_parser::Attributes;
use indexmap::IndexMap;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::ToTokens;
use std::collections::BTreeSet;
use syn::{FnArg, ItemFn, Pat};

pub struct ArgDetails {
    pub args: Vec<Ident>,
    pub defaults: IndexMap<String, Option<proc_macro2::TokenStream>>,
}

pub fn reconcile(f: &ItemFn, attr: TokenStream) -> syn::Result<ArgDetails> {
    let args: Result<Vec<_>, _> = f
        .sig
        .inputs
        .iter()
        .map(|arg| match arg {
            FnArg::Receiver(_) => {
                Err(syn::Error::new_spanned(arg, "`named` does not currently support functions which take `self`."))
            },
            FnArg::Typed(pat_type) => {
                if let Pat::Ident(ident) = pat_type.pat.as_ref() {
                    Ok(ident.ident.clone())
                } else {
                    panic!("Didn't recognise function signature - expected all args to be idents, but found: {:?}", pat_type);
                }
            }
        })
        .collect();
    let args = args?;

    let attr: Attributes = syn::parse_macro_input::parse(attr)?;
    let defaults = attr.defaults();

    let fn_arg_names = args
        .iter()
        .map(|ident| ident.to_string())
        .collect::<BTreeSet<_>>();
    let attr_arg_names = defaults
        .keys()
        .map(|ident| ident.to_string())
        .collect::<BTreeSet<_>>();
    let extras = attr_arg_names
        .difference(&fn_arg_names)
        .cloned()
        .collect::<Vec<_>>();
    if !extras.is_empty() {
        let extras_plural_suffix;
        let span;
        let extras_str;
        if extras.len() == 1 {
            extras_plural_suffix = "";
            span = defaults[&extras[0]].0;
            extras_str = format!("`{}`", &extras[0]);
        } else {
            extras_plural_suffix = "s";
            span = Span::call_site();
            extras_str = format!("[{}]", extras.join(", "));
        }
        return Err(syn::Error::new(
            span,
            format!(
                "Unrecognized argument{} - attribute had argument{} {} but function takes argument{}: [{}]",
                extras_plural_suffix,
                extras_plural_suffix,
                extras_str,
                if fn_arg_names.len() == 1 { "" } else { "s" },
                fn_arg_names.into_iter().collect::<Vec<_>>().join(", "),
            )
        ));
    }

    let defaults = fn_arg_names
        .into_iter()
        .map(|arg| {
            let value = defaults
                .get(&arg)
                .map(|(_span, value)| value.to_token_stream());
            (arg, value)
        })
        .collect();

    Ok(ArgDetails { args, defaults })
}