use crate::symbol::*;
use proc_macro2::TokenStream;
use quote::quote;
pub fn expand_derive_opaque(input: syn::DeriveInput) -> syn::Result<TokenStream> {
let ast = Ast::from_ast(&input)?;
let tokens = [
impl_typed(&ast),
impl_into(&ast),
impl_from(&ast),
impl_display(&ast),
]
.into_iter()
.flatten()
.collect();
Ok(tokens)
}
fn add_generic_bounds(
generic_params: &[syn::Ident],
generics: &syn::Generics,
bounds: Vec<syn::TypeParamBound>,
) -> syn::Generics {
let mut generics = generics.clone();
if bounds.is_empty() {
return generics;
}
let bounds = bounds
.into_iter()
.collect::<syn::punctuated::Punctuated<syn::TypeParamBound, syn::Token![+]>>();
for type_param in generic_params {
if generics.where_clause.is_none() {
generics.where_clause = Some(syn::parse_quote!(where));
}
generics
.where_clause
.as_mut()
.unwrap()
.predicates
.push(syn::parse_quote!(#type_param: #bounds));
}
generics
}
fn impl_typed(input: &Ast) -> TokenStream {
let name = &input.ident;
let cel = input.cel_path();
let generics = add_generic_bounds(
&input.generic_params,
&input.generics,
vec![syn::parse_quote!(#cel::values::TypedValue)],
);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let generic_params = &input.generic_params;
let ty = input.opaque_type();
let tokens = quote! {
impl #impl_generics #cel::values::TypedOpaque for #name #ty_generics #where_clause {
fn opaque_type() -> #cel::types::OpaqueType {
#cel::types::OpaqueType::new(
#ty,
vec![
#( <#generic_params as #cel::values::TypedValue>::value_type() ),*
],
)
}
}
impl #impl_generics #cel::values::TypedValue for #name #ty_generics #where_clause {
fn value_type() -> #cel::types::ValueType {
#cel::types::ValueType::Opaque(<Self as #cel::values::TypedOpaque>::opaque_type())
}
}
};
tokens
}
fn impl_into(input: &Ast) -> TokenStream {
let name = &input.ident;
let cel = input.cel_path();
let generics = add_generic_bounds(
&input.generic_params,
&input.generics,
vec![syn::parse_quote!(#cel::values::IntoValue)],
);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let tokens = quote! {
impl #impl_generics #cel::values::IntoValue for #name #ty_generics #where_clause {
fn into_value(self) -> #cel::values::Value {
#cel::values::Value::Opaque(Box::new(self))
}
}
impl #impl_generics ::std::convert::From<#name #ty_generics> for #cel::values::Value {
fn from(value: #name #ty_generics) -> Self {
<#name #ty_generics as #cel::values::IntoValue>::into_value(value)
}
}
};
tokens
}
fn add_generic_lifetime(generics: &syn::Generics, lifetime: syn::Lifetime) -> syn::Generics {
let mut generics = generics.clone();
generics.params.push(syn::parse_quote!(#lifetime));
generics
}
fn impl_from(input: &Ast) -> TokenStream {
let name = &input.ident;
let cel = input.cel_path();
let generics = add_generic_bounds(
&input.generic_params,
&input.generics,
vec![
syn::parse_quote!(#cel::values::FromValue),
syn::parse_quote!(#cel::values::TypedValue),
],
);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let generics2 = add_generic_lifetime(&generics, syn::parse_quote!('opaque));
let (impl_generics2, _, _) = generics2.split_for_impl();
let tokens = quote! {
impl #impl_generics #cel::values::FromValue for #name #ty_generics #where_clause {
type Output<'a> = #name #ty_generics;
fn from_value<'a>(value: &'a #cel::values::Value) -> ::std::result::Result<Self::Output<'a>, #cel::values::FromValueError> {
match value {
#cel::values::Value::Opaque(v) => {
v.downcast_ref::<#name #ty_generics>()
.ok_or(#cel::values::FromValueError::new_typed::<#name #ty_generics>(value.clone()))
.map(|v| v.clone())
}
_ => Err(#cel::values::FromValueError::new_typed::<#name #ty_generics>(value.clone())),
}
}
}
impl #impl_generics ::std::convert::TryFrom<#cel::values::Value> for #name #ty_generics #where_clause {
type Error = #cel::values::FromValueError;
fn try_from(value: #cel::values::Value) -> ::std::result::Result<Self, Self::Error> {
<Self as #cel::values::FromValue>::from_value(&value)
}
}
impl #impl_generics ::std::convert::TryFrom<&#cel::values::Value> for #name #ty_generics #where_clause {
type Error = #cel::values::FromValueError;
fn try_from(value: &#cel::values::Value) -> ::std::result::Result<Self, Self::Error> {
<Self as #cel::values::FromValue>::from_value(value)
}
}
impl #impl_generics #cel::values::FromValue for &#name #ty_generics #where_clause {
type Output<'a> = &'a #name #ty_generics;
fn from_value<'a>(value: &'a #cel::values::Value) -> ::std::result::Result<Self::Output<'a>, #cel::values::FromValueError> {
match value {
#cel::values::Value::Opaque(v) => {
v.downcast_ref::<#name #ty_generics>()
.ok_or(#cel::values::FromValueError::new_typed::<#name #ty_generics>(value.clone()))
}
_ => Err(#cel::values::FromValueError::new_typed::<#name #ty_generics>(value.clone())),
}
}
}
impl #impl_generics2 ::std::convert::TryFrom<&'opaque #cel::values::Value> for &'opaque #name #ty_generics #where_clause {
type Error = #cel::values::FromValueError;
fn try_from(value: &'opaque #cel::values::Value) -> ::std::result::Result<Self, Self::Error> {
<Self as #cel::values::FromValue>::from_value(value)
}
}
};
tokens
}
fn impl_display(input: &Ast) -> TokenStream {
let Some(display) = input.attrs.display() else {
return quote! {};
};
let name = &input.ident;
let generics = add_generic_bounds(
&input.generic_params,
&input.generics,
vec![syn::parse_quote!(::std::fmt::Display)],
);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let tokens = quote! {
impl #impl_generics ::std::fmt::Display for #name #ty_generics #where_clause {
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
#display
}
}
};
tokens
}
struct Ast {
pub ident: syn::Ident,
pub attrs: Attrs,
pub generics: syn::Generics,
pub generic_params: Vec<syn::Ident>,
}
impl Ast {
fn from_ast(input: &syn::DeriveInput) -> syn::Result<Self> {
if let Some(const_param) = input.generics.const_params().next() {
return Err(syn::Error::new_spanned(
const_param,
"opaque types cannot have const generic parameters",
));
}
let attrs = Attrs::from_ast(input)?;
let mut generic_params = Vec::new();
let mut generics = input.generics.clone();
let cel_path = attrs.cel_path();
let bound: syn::TraitBound = syn::parse2(quote! { #cel_path::values::TypedValue })?;
for param in generics.params.iter_mut() {
if let syn::GenericParam::Type(ref mut type_param) = *param {
type_param
.bounds
.push(syn::TypeParamBound::Trait(bound.clone()));
generic_params.push(type_param.ident.clone());
}
}
Ok(Self {
ident: input.ident.clone(),
attrs,
generics,
generic_params,
})
}
fn cel_path(&self) -> syn::Path {
self.attrs.cel_path()
}
fn opaque_type(&self) -> syn::Expr {
self.attrs.opaque_type(&self.ident.to_string())
}
}
struct Attrs {
pub type_name: Option<syn::Expr>,
pub crate_path: Option<syn::Path>,
pub display: Option<syn::Expr>,
}
impl Attrs {
fn from_ast(input: &syn::DeriveInput) -> syn::Result<Self> {
let mut type_name = None;
let mut crate_path = None;
let mut display = None;
for attr in input.attrs.iter() {
if !attr.path().is_ident(CEL_CXX) {
continue;
}
if let syn::Meta::List(meta) = &attr.meta {
if meta.tokens.is_empty() {
continue;
}
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident(TYPE) {
let expr: syn::Expr = meta.value()?.parse()?;
type_name = Some(expr);
} else if meta.path.is_ident(CRATE) {
let path: syn::Path = meta.value()?.parse()?;
crate_path = Some(path);
} else if meta.path.is_ident(DISPLAY) {
let expr: syn::Expr = if meta.input.is_empty() {
syn::parse_quote!(write!(fmt, "{self:?}"))
} else {
meta.value()?.parse()?
};
display = Some(expr);
} else {
return Err(syn::Error::new_spanned(meta.path, "unknown attribute"));
}
Ok(())
})?;
}
Ok(Self {
type_name,
crate_path,
display,
})
}
fn cel_path(&self) -> syn::Path {
self.crate_path
.clone()
.unwrap_or_else(|| syn::parse_str(format!("::{CEL_CXX}").as_str()).unwrap())
}
fn opaque_type(&self, default: &str) -> syn::Expr {
self.type_name
.clone()
.unwrap_or_else(|| syn::parse_quote!(#default))
}
fn display(&self) -> Option<&syn::Expr> {
self.display.as_ref()
}
}