use std::collections::HashMap;
use std::mem;
use proc_macro2::Span;
use syn::meta::ParseNestedMeta;
use syn::parse::Parse;
use syn::spanned::Spanned;
use syn::Token;
use crate::expander::NameMethod;
use crate::expander::UnsizedMethod;
use crate::internals::name::NameAll;
use crate::internals::ATTR;
use crate::internals::{Ctxt, Mode};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) enum ModeKind {
Binary,
Text,
Custom(Box<str>),
}
impl ModeKind {
pub(crate) fn default_name_all(&self) -> Option<NameAll> {
match self {
ModeKind::Binary => Some(NameAll::Index),
ModeKind::Text => Some(NameAll::Name),
ModeKind::Custom(_) => None,
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct ModeIdent {
pub(crate) ident: syn::Ident,
pub(crate) kind: ModeKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Only {
Encode,
Decode,
}
#[derive(Default)]
struct OneOf<T> {
encode: T,
decode: T,
any: T,
}
#[derive(Clone, Copy)]
pub(crate) enum EnumTagging<'a> {
Default,
Empty,
Internal { tag: &'a syn::Expr },
Adjacent {
tag: &'a syn::Expr,
content: &'a syn::Expr,
},
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Packing {
#[default]
Tagged,
Packed,
Transparent,
}
macro_rules! merge {
($self:expr, $cx:expr, $new:expr, $field:ident, $only:expr) => {{
for $field in $new.$field {
let out = match $only {
None => &mut $self.$field.any,
Some(Only::Decode) => &mut $self.$field.decode,
Some(Only::Encode) => &mut $self.$field.encode,
};
if out.is_some() {
$cx.error_span(
$field.0,
format_args!(
"#[{}] multiple {} attributes specified",
ATTR,
stringify!($field)
),
);
} else {
*out = Some($field);
}
}
}};
}
macro_rules! layer {
($attr:ident, $new:ident, $layer:ident {
$($(#[$($single_meta:meta)*])* $single:ident: $single_ty:ty,)* $(,)?
@multiple
$($(#[$($multiple_meta:meta)*])* $multiple:ident: $multiple_ty:ty,)* $(,)?
}) => {
#[derive(Default)]
pub(crate) struct $attr {
root: $layer,
modes: HashMap<ModeKind, $layer>,
}
impl $attr {
fn by_mode<A, O>(&self, mode: Mode<'_>, access: A) -> Option<&O>
where
A: Copy + Fn(&$layer) -> Option<&O>,
O: ?Sized,
{
if let Some(value) = mode.kind.and_then(|m| self.modes.get(m).and_then(access)) {
Some(value)
} else {
access(&self.root)
}
}
$(
#[allow(unused)]
pub(crate) fn $single(&self, mode: Mode<'_>) -> Option<&(Span, $single_ty)> {
self.by_mode(mode, |m| {
match mode.only {
Only::Encode if m.$single.encode.is_some() => m.$single.encode.as_ref(),
Only::Decode if m.$single.decode.is_some() => m.$single.decode.as_ref(),
_ => m.$single.any.as_ref(),
}
})
}
)*
$(
#[allow(unused)]
pub(crate) fn $multiple(&self, mode: Mode<'_>) -> &[(Span, $multiple_ty)] {
self.by_mode(mode, |m| {
match mode.only {
Only::Encode if !m.$multiple.encode.is_empty() => Some(&m.$multiple.encode[..]),
Only::Decode if !m.$multiple.decode.is_empty() => Some(&m.$multiple.decode[..]),
_ if !m.$multiple.any.is_empty() => Some(&m.$multiple.any[..]),
_ => None,
}
}).unwrap_or_default()
}
)*
}
#[derive(Default)]
struct $new {
$($(#[$($single_meta)*])* $single: Vec<(Span, $single_ty)>,)*
$($(#[$($multiple_meta)*])* $multiple: Vec<(Span, $multiple_ty)>,)*
}
#[derive(Default)]
struct $layer {
$($(#[$($single_meta)*])* $single: OneOf<Option<(Span, $single_ty)>>,)*
$($(#[$($multiple_meta)*])* $multiple: OneOf<Vec<(Span, $multiple_ty)>>,)*
}
impl $layer {
fn merge_with(&mut self, cx: &Ctxt, new: $new, only: Option<Only>) {
$(
merge!(self, cx, new, $single, only);
)*
$(
let list = match only {
None => {
&mut self.$multiple.any
}
Some(Only::Encode) => {
&mut self.$multiple.encode
}
Some(Only::Decode) => {
&mut self.$multiple.decode
}
};
list.extend(new.$multiple);
)*
}
}
}
}
layer! {
TypeAttr, TypeLayerNew, TypeLayer {
krate: syn::Path,
name_type: syn::Type,
name_all: NameAll,
name_method: NameMethod,
name_format_with: syn::Path,
tag: syn::Expr,
content: syn::Expr,
packing: Packing,
@multiple
bounds: syn::WherePredicate,
decode_bounds: syn::WherePredicate,
}
}
impl TypeAttr {
pub(crate) fn is_name_type_ambiguous(&self, mode: Mode<'_>) -> bool {
self.name_type(mode).is_none()
&& self.name_all(mode).is_none()
&& self.name_method(mode).is_none()
}
pub(crate) fn enum_tagging_span(&self, mode: Mode<'_>) -> Option<Span> {
let tag = self.tag(mode);
let content = self.content(mode);
Some(tag.or(content)?.0)
}
pub(crate) fn enum_tagging(&self, mode: Mode<'_>) -> Option<EnumTagging<'_>> {
let (_, tag) = self.tag(mode)?;
Some(match self.content(mode) {
Some((_, content)) => EnumTagging::Adjacent { tag, content },
_ => EnumTagging::Internal { tag },
})
}
pub(crate) fn crate_or_default(&self, default: &str) -> syn::Path {
if let Some((_, krate)) = self.root.krate.any.as_ref() {
return krate.clone();
}
let mut path = syn::Path::from(syn::Ident::new(default, Span::call_site()));
path.leading_colon = Some(<Token![::]>::default());
path
}
}
pub(crate) fn type_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> TypeAttr {
let mut attr = TypeAttr::default();
for a in attrs {
if !a.path().is_ident(ATTR) {
continue;
}
let mut new = TypeLayerNew::default();
let mut mode = None;
let mut only = None;
let result = a.parse_nested_meta(|meta| {
if meta.path.is_ident("mode") {
meta.input.parse::<Token![=]>()?;
mode = Some(parse_mode(&meta)?);
return Ok(());
}
if meta.path.is_ident("encode_only") {
only = Some(Only::Encode);
return Ok(());
}
if meta.path.is_ident("decode_only") {
only = Some(Only::Decode);
return Ok(());
}
if meta.path.is_ident("tag") {
meta.input.parse::<Token![=]>()?;
new.tag.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("content") {
meta.input.parse::<Token![=]>()?;
new.content.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("crate") {
let path = if meta.input.parse::<Option<Token![=]>>()?.is_some() {
meta.input.parse()?
} else {
syn::parse_quote!(crate)
};
new.krate.push((meta.path.span(), path));
return Ok(());
}
if meta.path.is_ident("name_type") {
meta.input.parse::<Token![=]>()?;
new.name_type.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("name_format_with") {
meta.input.parse::<Token![=]>()?;
new.name_format_with
.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("bound") {
meta.input.parse::<Token![=]>()?;
parse_bounds(&meta, &mut new.bounds)?;
return Ok(());
}
if meta.path.is_ident("decode_bound") {
meta.input.parse::<Token![=]>()?;
parse_bounds(&meta, &mut new.decode_bounds)?;
return Ok(());
}
if meta.path.is_ident("packed") {
new.packing.push((meta.path.span(), Packing::Packed));
return Ok(());
}
if meta.path.is_ident("transparent") {
new.packing.push((meta.path.span(), Packing::Transparent));
return Ok(());
}
if meta.path.is_ident("name_all") {
new.name_all
.push((meta.path.span(), parse_name_all(&meta)?));
return Ok(());
}
if meta.path.is_ident("name_method") {
new.name_method
.push((meta.path.span(), parse_name_method(&meta)?));
return Ok(());
}
Err(syn::Error::new_spanned(
meta.path,
format_args!("#[{ATTR}] Unsupported type attribute"),
))
});
if let Err(error) = result {
cx.syn_error(error);
}
let attr = match mode {
Some(mode) => {
let modes = attr.modes.entry(mode.kind.clone()).or_default();
cx.register_mode(mode);
modes
}
None => &mut attr.root,
};
attr.merge_with(cx, new, only);
}
attr
}
fn parse_name_method(meta: &syn::meta::ParseNestedMeta<'_>) -> Result<NameMethod, syn::Error> {
meta.input.parse::<Token![=]>()?;
let string: syn::LitStr = meta.input.parse()?;
let s = string.value();
match s.as_str() {
"value" => Ok(NameMethod::Value),
"unsized" => Ok(NameMethod::Unsized(UnsizedMethod::Default)),
"unsized_bytes" => Ok(NameMethod::Unsized(UnsizedMethod::Bytes)),
_ => Err(syn::Error::new_spanned(
string,
"#[musli(name_method = ..)]: Bad value, expected one of \"value\", \"unsized\", \"unsized_bytes\"",
)),
}
}
fn parse_name_all(meta: &syn::meta::ParseNestedMeta<'_>) -> Result<NameAll, syn::Error> {
meta.input.parse::<Token![=]>()?;
let string: syn::LitStr = meta.input.parse()?;
let s = string.value();
let Some(name_all) = NameAll::parse(s.as_str()) else {
let mut options = Vec::new();
for option in NameAll::ALL {
options.push(format!(r#""{option}""#));
}
let options = options.join(", ");
return Err(syn::Error::new_spanned(
string,
format_args!("#[{ATTR}(name_all = {s:?})]: Bad value, expected one of {options}"),
));
};
Ok(name_all)
}
fn parse_bounds(
meta: &syn::meta::ParseNestedMeta,
out: &mut Vec<(Span, syn::WherePredicate)>,
) -> syn::Result<()> {
let content;
syn::braced!(content in meta.input);
let where_clauses = content.parse_terminated(syn::WherePredicate::parse, Token![,])?;
for where_clause in where_clauses {
out.push((meta.path.span(), where_clause));
}
Ok(())
}
layer! {
VariantAttr, VariantLayerNew, VariantLayer {
name_type: syn::Type,
name_format_with: syn::Path,
name: syn::Expr,
pattern: syn::Pat,
name_all: NameAll,
name_method: NameMethod,
packing: Packing,
default_variant: (),
@multiple
}
}
impl VariantAttr {
pub(crate) fn is_name_type_ambiguous(&self, mode: Mode<'_>) -> bool {
self.name_type(mode).is_none()
&& self.name_all(mode).is_none()
&& self.name_method(mode).is_none()
}
}
pub(crate) fn variant_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> VariantAttr {
let mut attr = VariantAttr::default();
for a in attrs {
if !a.path().is_ident(ATTR) {
continue;
}
let mut new = VariantLayerNew::default();
let mut mode = None;
let mut only = None;
let result = a.parse_nested_meta(|meta| {
if meta.path.is_ident("mode") {
meta.input.parse::<Token![=]>()?;
mode = Some(parse_mode(&meta)?);
return Ok(());
}
if meta.path.is_ident("encode_only") {
only = Some(Only::Encode);
return Ok(());
}
if meta.path.is_ident("decode_only") {
only = Some(Only::Decode);
return Ok(());
}
if meta.path.is_ident("name_type") {
meta.input.parse::<Token![=]>()?;
new.name_type.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("name_format_with") {
meta.input.parse::<Token![=]>()?;
new.name_format_with
.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("rename") {
return Err(syn::Error::new_spanned(
meta.path,
"#[musli(rename = ..)] has been changed to #[musli(name = ..)]",
));
}
if meta.path.is_ident("name") {
meta.input.parse::<Token![=]>()?;
new.name.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("pattern") {
meta.input.parse::<Token![=]>()?;
new.pattern
.push((meta.path.span(), meta.input.call(syn::Pat::parse_single)?));
return Ok(());
}
if meta.path.is_ident("default") {
new.default_variant.push((meta.path.span(), ()));
return Ok(());
}
if meta.path.is_ident("packed") {
new.packing.push((meta.path.span(), Packing::Packed));
return Ok(());
}
if meta.path.is_ident("transparent") {
new.packing.push((meta.path.span(), Packing::Transparent));
return Ok(());
}
if meta.path.is_ident("name_all") {
new.name_all
.push((meta.path.span(), parse_name_all(&meta)?));
return Ok(());
}
if meta.path.is_ident("name_method") {
new.name_method
.push((meta.path.span(), parse_name_method(&meta)?));
return Ok(());
}
Err(syn::Error::new_spanned(
meta.path,
format_args!("#[{ATTR}] Unsupported type attribute"),
))
});
if let Err(error) = result {
cx.syn_error(error);
}
let attr = match mode {
Some(mode) => {
let out = attr.modes.entry(mode.kind.clone()).or_default();
cx.register_mode(mode);
out
}
None => &mut attr.root,
};
attr.merge_with(cx, new, only);
}
attr
}
#[derive(Default, Clone, Copy)]
pub(crate) enum FieldEncoding {
Packed,
Bytes,
Trace,
#[default]
Default,
}
layer! {
Field, FieldNew, FieldLayer {
encode_path: syn::Path,
decode_path: syn::Path,
skip_encoding_if: syn::Path,
name: syn::Expr,
pattern: syn::Pat,
is_default: Option<syn::Path>,
skip: (),
encoding: FieldEncoding,
@multiple
}
}
impl Field {
pub(crate) fn encode_path_expanded(&self, mode: Mode<'_>, span: Span) -> (Span, syn::Path) {
let encode_path = self.encode_path(mode);
if let Some((span, encode_path)) = encode_path {
(*span, encode_path.clone())
} else {
let field_encoding = self.encoding(mode).map(|&(_, e)| e).unwrap_or_default();
let encode_path = mode.encode_t_encode(field_encoding);
(span, encode_path)
}
}
pub(crate) fn decode_path_expanded(&self, mode: Mode<'_>, span: Span) -> (Span, syn::Path) {
let decode_path = self.decode_path(mode);
if let Some((span, decode_path)) = decode_path {
(*span, decode_path.clone())
} else {
let field_encoding = self.encoding(mode).map(|&(_, e)| e).unwrap_or_default();
let decode_path = mode.decode_t_decode(field_encoding);
(span, decode_path)
}
}
}
pub(crate) fn field_attrs(cx: &Ctxt, attrs: &[syn::Attribute]) -> Field {
let mut attr = Field::default();
for a in attrs {
if !a.path().is_ident(ATTR) {
continue;
}
let mut new = FieldNew::default();
let mut mode = None;
let mut only = None;
let result = a.parse_nested_meta(|meta| {
if meta.path.is_ident("mode") {
meta.input.parse::<Token![=]>()?;
mode = Some(parse_mode(&meta)?);
return Ok(());
}
if meta.path.is_ident("encode_only") {
only = Some(Only::Encode);
return Ok(());
}
if meta.path.is_ident("decode_only") {
only = Some(Only::Decode);
return Ok(());
}
if meta.path.is_ident("with") {
meta.input.parse::<Token![=]>()?;
let mut path = meta.input.parse::<syn::Path>()?;
let (span, arguments) = match path.segments.last_mut() {
Some(s) => (
s.span(),
mem::replace(&mut s.arguments, syn::PathArguments::None),
),
None => (path.span(), syn::PathArguments::None),
};
let mut encode_path = path.clone();
encode_path.segments.push({
let mut segment = syn::PathSegment::from(syn::Ident::new("encode", span));
segment.arguments = arguments.clone();
segment
});
let mut decode_path = path.clone();
decode_path.segments.push({
let mut segment = syn::PathSegment::from(syn::Ident::new("decode", span));
segment.arguments = arguments;
segment
});
new.encode_path.push((path.span(), encode_path));
new.decode_path.push((path.span(), decode_path));
return Ok(());
}
if meta.path.is_ident("skip_encoding_if") {
meta.input.parse::<Token![=]>()?;
new.skip_encoding_if
.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("rename") {
return Err(syn::Error::new_spanned(
meta.path,
"#[musli(rename = ..)] has been changed to #[musli(name = ..)]",
));
}
if meta.path.is_ident("name") {
meta.input.parse::<Token![=]>()?;
new.name.push((meta.path.span(), meta.input.parse()?));
return Ok(());
}
if meta.path.is_ident("pattern") {
meta.input.parse::<Token![=]>()?;
new.pattern
.push((meta.path.span(), meta.input.call(syn::Pat::parse_single)?));
return Ok(());
}
if meta.path.is_ident("default") {
if meta.input.parse::<Option<Token![=]>>()?.is_some() {
new.is_default
.push((meta.path.span(), Some(meta.input.parse()?)));
} else {
new.is_default.push((meta.path.span(), None));
}
return Ok(());
}
if meta.path.is_ident("skip") {
new.skip.push((meta.path.span(), ()));
return Ok(());
}
if meta.path.is_ident("trace") {
new.encoding.push((meta.path.span(), FieldEncoding::Trace));
return Ok(());
}
if meta.path.is_ident("bytes") {
new.encoding.push((meta.path.span(), FieldEncoding::Bytes));
return Ok(());
}
if meta.path.is_ident("packed") {
new.encoding.push((meta.path.span(), FieldEncoding::Packed));
return Ok(());
}
Err(syn::Error::new_spanned(
meta.path,
format_args!("#[{ATTR}] Unsupported field attribute"),
))
});
if let Err(error) = result {
cx.syn_error(error);
}
let attr = match mode {
Some(mode) => {
let out = attr.modes.entry(mode.kind.clone()).or_default();
cx.register_mode(mode);
out
}
None => &mut attr.root,
};
attr.merge_with(cx, new, only);
}
attr
}
fn parse_mode(meta: &ParseNestedMeta<'_>) -> syn::Result<ModeIdent> {
let ident: syn::Ident = meta.input.parse()?;
let s = ident.to_string();
let kind = match s.as_str() {
"Binary" => ModeKind::Binary,
"Text" => ModeKind::Text,
other => ModeKind::Custom(other.into()),
};
Ok(ModeIdent { ident, kind })
}