use syn::{
Attribute, Error, Expr, Ident, LitStr, Result, Token, Type, Visibility, parse_str,
spanned::Spanned,
};
#[derive(Default)]
pub(crate) struct StructOptions {
pub(crate) view_name: Option<Ident>,
pub(crate) view_visibility: Option<Visibility>,
pub(crate) field_visibility: Option<Visibility>,
pub(crate) method_name: Option<Ident>,
pub(crate) method_visibility: Option<Visibility>,
pub(crate) no_method: bool,
}
#[derive(Default)]
pub(crate) struct FieldOptions {
pub(crate) borrowed_type: Option<Type>,
pub(crate) generation_expression: Option<Expr>,
pub(crate) visibility: Option<Visibility>,
}
pub(crate) fn parse_struct_options(attributes: &[Attribute]) -> Result<StructOptions> {
let mut options = StructOptions::default();
for attribute in borrowize_attributes(attributes) {
attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("no_method") {
if meta.input.peek(Token![=]) {
return Err(meta.error("`no_method` does not take a value"));
}
reject_duplicate_flag(&mut options.no_method, meta.path.span(), "no_method")?;
return Ok(());
}
let value = meta.value()?;
let literal = value.parse::<LitStr>()?;
if meta.path.is_ident("view_name") {
set_once(
&mut options.view_name,
parse_literal(&literal, "view_name")?,
meta.path.span(),
"view_name",
)
} else if meta.path.is_ident("view_visibility") {
set_once(
&mut options.view_visibility,
parse_visibility_literal(&literal, "view_visibility")?,
meta.path.span(),
"view_visibility",
)
} else if meta.path.is_ident("field_visibility") {
set_once(
&mut options.field_visibility,
parse_visibility_literal(&literal, "field_visibility")?,
meta.path.span(),
"field_visibility",
)
} else if meta.path.is_ident("method_name") {
set_once(
&mut options.method_name,
parse_literal(&literal, "method_name")?,
meta.path.span(),
"method_name",
)
} else if meta.path.is_ident("method_visibility") {
set_once(
&mut options.method_visibility,
parse_visibility_literal(&literal, "method_visibility")?,
meta.path.span(),
"method_visibility",
)
} else {
Err(meta.error(format!(
"unsupported borrowize struct option `{}`",
option_name(&meta.path)
)))
}
})?;
}
if options.no_method
&& let Some(method_name) = &options.method_name
{
return Err(Error::new(
method_name.span(),
"`no_method` cannot be combined with `method_name`",
));
}
if options.no_method
&& let Some(method_visibility) = &options.method_visibility
{
return Err(Error::new(
method_visibility.span(),
"`no_method` cannot be combined with `method_visibility`",
));
}
Ok(options)
}
pub(crate) fn parse_field_options(attributes: &[Attribute]) -> Result<FieldOptions> {
let mut options = FieldOptions::default();
for attribute in borrowize_attributes(attributes) {
attribute.parse_nested_meta(|meta| {
let value = meta.value()?;
let literal = value.parse::<LitStr>()?;
if meta.path.is_ident("borrowed_type") {
set_once(
&mut options.borrowed_type,
parse_literal(&literal, "borrowed_type")?,
meta.path.span(),
"borrowed_type",
)
} else if meta.path.is_ident("generation_expression") {
set_once(
&mut options.generation_expression,
parse_literal(&literal, "generation_expression")?,
meta.path.span(),
"generation_expression",
)
} else if meta.path.is_ident("visibility") {
set_once(
&mut options.visibility,
parse_visibility_literal(&literal, "visibility")?,
meta.path.span(),
"visibility",
)
} else {
Err(meta.error(format!(
"unsupported borrowize field option `{}`",
option_name(&meta.path)
)))
}
})?;
}
Ok(options)
}
fn borrowize_attributes(attributes: &[Attribute]) -> impl Iterator<Item = &Attribute> {
attributes
.iter()
.filter(|attribute| attribute.path().is_ident("borrowize"))
}
fn parse_literal<T>(literal: &LitStr, option_name: &str) -> Result<T>
where
T: syn::parse::Parse,
{
literal.parse().map_err(|error| {
Error::new(
literal.span(),
format!("failed to parse `{option_name}` value: {error}"),
)
})
}
fn parse_visibility_literal(literal: &LitStr, option_name: &str) -> Result<Visibility> {
let value = literal.value();
parse_str::<Visibility>(&value).map_err(|error| {
Error::new(
literal.span(),
format!(
"failed to parse `{option_name}` value as Rust visibility; omit the option to inherit visibility: {error}"
),
)
})
}
fn set_once<T>(
target: &mut Option<T>,
value: T,
span: proc_macro2::Span,
name: &str,
) -> Result<()> {
if target.is_some() {
return Err(Error::new(
span,
format!("duplicate borrowize option `{name}`"),
));
}
*target = Some(value);
Ok(())
}
fn reject_duplicate_flag(target: &mut bool, span: proc_macro2::Span, name: &str) -> Result<()> {
if *target {
return Err(Error::new(
span,
format!("duplicate borrowize option `{name}`"),
));
}
*target = true;
Ok(())
}
fn option_name(path: &syn::Path) -> String {
path.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}