use proc_macro2::Span;
use syn::{
ext::IdentExt, parse::ParseStream, parse_quote, parse_quote_spanned, Error, Expr, Ident, Path,
Token, Type,
};
use crate::attr::FieldAttr;
#[derive(Default, Clone, Copy)]
pub enum Optional {
#[allow(clippy::enum_variant_names)]
Optional { nullable: bool },
#[allow(clippy::enum_variant_names)]
NotOptional,
#[default]
Inherit,
}
impl Optional {
pub fn or(self, other: Optional) -> Self {
match (self, other) {
(Self::Inherit, other) | (other, Self::Inherit) => other,
(Self::Optional { nullable: a }, Self::Optional { nullable: b }) => {
Self::Optional { nullable: a || b }
}
_ => other,
}
}
}
pub fn parse_optional(input: ParseStream) -> syn::Result<Optional> {
let optional = if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let span = input.span();
match Ident::parse_any(input)?.to_string().as_str() {
"nullable" => Optional::Optional { nullable: true },
"false" => Optional::NotOptional,
_ => Err(Error::new(span, "expected 'nullable'"))?,
}
} else {
Optional::Optional { nullable: false }
};
Ok(optional)
}
pub fn apply(
crate_rename: &Path,
for_struct: Optional,
field_ty: &Type,
attr: &FieldAttr,
span: Span,
) -> (Expr, Type) {
match (for_struct, attr.optional) {
(Optional::NotOptional, Optional::Inherit) | (_, Optional::NotOptional) => {
(parse_quote!(false), field_ty.clone())
}
(_, Optional::Optional { nullable }) => (
parse_quote!(true),
if nullable {
field_ty.clone()
} else {
parse_quote_spanned! {
span => <#field_ty as #crate_rename::IsOption>::Inner
}
},
),
(Optional::Optional { nullable }, Optional::Inherit) if attr.type_override.is_none() => (
parse_quote! {
<#field_ty as #crate_rename::TS>::IS_OPTION
},
if nullable {
field_ty.clone()
} else {
unwrap_option(crate_rename, field_ty)
},
),
_ => {
let is_optional = attr.maybe_omitted && attr.has_default;
(parse_quote!(#is_optional), field_ty.clone())
}
}
}
fn unwrap_option(crate_rename: &Path, ty: &Type) -> Type {
parse_quote! {<#ty as #crate_rename::TS>::OptionInnerType}
}