partial-borrow-macros 0.0.1

work in progress
Documentation
// Copyright 2021 Ian Jackson and contributors
// SPDX-License-Identifier: GPL-3.0-or-later
// There is NO WARRANTY.

use super::*;

#[proc_macro_error(allow_not_macro)]
pub fn partial(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
  let output: TokenStream2 = default();
  eprintln!("PARTS {:?} => {}", &input, output);

  let us = format_ident!("partial_borrow");

  #[derive(Debug,Copy,Clone,)]
  enum Permission { Not, Ref, Mut }
  impl quote::IdentFragment for Permission {
    #[throws(fmt::Error)]
    fn fmt(&self, f: &mut fmt::Formatter) { write!(f, "{:?}", self)? }
  }

  struct PartsInput {
    base: syn::TypePath,
    def: Permission,
    fields: IndexMap<syn::Ident, Permission>,
  }
  struct FieldGroup {
    perm: Permission,
    fields: Vec<FieldSpec>,
  }
  struct DefSpec {
    perm: Permission,
    token: Token![*],
  }
  enum FieldSpec {
    Named(syn::Ident),
    Def(Token![*]),
  }
  use FieldSpec as FS;

  impl Parse for PartsInput {
    #[throws(syn::parse::Error)]
    fn parse(input: ParseStream<'_>) -> Self {
      let base = input.parse()?;

      let mut def: Option<DefSpec> = None;
      let mut fields: IndexMap<_,_> = default();
      while ! input.is_empty() {
        let g: FieldGroup = input.parse()?;
        for f in g.fields {
          let already_specified = |here: Span2, there: Span2| {
              emit_error!{
                here, "access permission respecified";
                note = there => "previously specified here";
              }
            };
          match f {
            FS::Named(i) => {
              use indexmap::map::Entry::*;
              match fields.entry(i.clone()) {
                Occupied(o) => already_specified(i.span(), o.key().span()),
                Vacant(v) => { v.insert(g.perm); },
              }
            },
            FS::Def(token) => {
              match &def {
                Some(y) => already_specified(token.span, y.token.span),
                None => def = Some(DefSpec { perm: g.perm, token }),
              }
            }
          }
        }
      }
      PartsInput {
        base,
        fields,
        def: def.as_ref().map(|ds| ds.perm).unwrap_or(Permission::Not),
      }
    }
  }
  impl Parse for FieldGroup {
    #[throws(syn::parse::Error)]
    fn parse(input: ParseStream<'_>) -> Self {
      let perm = input.parse()?;
      let mut fields = vec![];
      while ! input.is_empty() {
        let la = input.lookahead1();
        let field =
          if la.peek(Token![,]) {
            let _: Token![,] = input.parse()?;
            break;
          } else if la.peek(syn::Ident) {
            FS::Named(input.parse()?)
          } else if la.peek(Token![*]) {
            FS::Def(input.parse()?)
          } else {
            throw!(la.error())
          };
        fields.push(field);
      }
      FieldGroup { perm, fields }
    }
  }
  impl Parse for Permission {
    #[throws(syn::parse::Error)]
    fn parse(input: ParseStream<'_>) -> Self {
      let la = input.lookahead1();
      if la.peek(Token![mut]) {
        let _: Token![mut] = input.parse()?;
        Permission::Mut
      } else if la.peek(Token![const]) {
        let _: Token![const] = input.parse()?;
        Permission::Ref
      } else if la.peek(Token![!]) {
        let _: Token![!] = input.parse()?;
        Permission::Not
      } else {
        Permission::Ref
      }
    }
  }

  let input: PartsInput = parse_macro_input!(input);
//  dbg!(&input);
  
  let base = &input.base;
  let mut output = {
    let base_span = (||{
      let last = base.path.segments.last()?;
      Some(last.ident.span())
    })();
    let def_ty = match base_span {
      Some(s) => format_ident!("All_{}", input.def, span=s),
      None    => format_ident!("All_{}", input.def),
    };
    quote!{ <#base as #us::PartialBorrow>::#def_ty }
  };
  for (field,perm) in input.fields {
    let perm = format_ident!{"{}", perm};
    output = quote!{
      <
        #output
        as
        #us::perms::Adjust <
          #us::perms::#perm,
          { <#base as #us::PartialBorrow>::FIELDS.#field },
        >
      > ::Adjusted
    };
  }
  eprintln!("\nPARTIAL {}", &output);
  
  output.into()
}