use quote::{format_ident, quote, ToTokens};
use rustler::{NifResult, Term};
use syn::{Arm, Expr, Field, Item, Pat, Stmt, Type};
use crate::generated_ast::{
atom, atom_key, decode_arm, decode_ast_expr, decode_ast_item, decode_ast_pat, decode_ast_stmt,
decode_ast_type, decode_enum_variant, decode_function_arg, decode_struct_field, is_nil,
optional_map_get, struct_name,
};
use crate::{
ident_from_part, parse_expr, parse_syn, parse_type, parse_type_generic, parse_type_ref,
path_from_parts,
};
// Primitive-boundary inventory:
// - Forever primitive: Rustler Term APIs, atom/string conversion, map/list traversal.
// - Generic glue: list/optional decoding and typed named-field collection.
// - Dogfood candidates: keyword_args, path_parts, decode_lifetime_list once iterator lowering exists.
// - Parse assembly belongs in parse.rs, parse_item.rs, or parse_type.rs, not here.
// Temporary dogfood bridge helpers called by generated defrust decoders.
pub(crate) fn decode_item_list(term: Term) -> NifResult<Vec<Item>> {
term.decode::<Vec<Term>>()?
.into_iter()
.map(decode_ast_item)
.collect::<NifResult<Vec<Item>>>()
}
pub(crate) fn decode_function_arg_list(term: Term) -> NifResult<Vec<syn::FnArg>> {
term.decode::<Vec<Term>>()?
.into_iter()
.map(decode_function_arg_value)
.collect()
}
fn decode_function_arg_value(term: Term) -> NifResult<syn::FnArg> {
match decode_function_arg(term) {
Ok(arg) => Ok(arg),
Err(_) => {
let (name, ty) = term.decode::<(Term, Term)>()?;
let name = format_ident!("{}", atom_or_string(name)?);
let ty = decode_type(ty)?;
crate::parse_function_arg(name, ty)
}
}
}
pub(crate) fn decode_struct_field_list(term: Term) -> NifResult<Vec<Field>> {
decode_list(term, decode_struct_field)
}
pub(crate) fn decode_enum_variant_list(term: Term) -> NifResult<Vec<syn::Variant>> {
decode_list(term, decode_enum_variant)
}
pub(crate) fn decode_type_list(term: Term) -> NifResult<Vec<Type>> {
decode_list(term, decode_type)
}
// Rust syntax attribute/visibility decoders shared by handwritten and generated code.
pub(crate) fn decode_vis(term: Term) -> NifResult<syn::Visibility> {
if is_nil(term)? {
syn::parse2(quote!()).map_err(|_| rustler::Error::BadArg)
} else {
match term.atom_to_string()?.as_str() {
"pub" => syn::parse2(quote!(pub)).map_err(|_| rustler::Error::BadArg),
"crate" => syn::parse2(quote!(pub(crate))).map_err(|_| rustler::Error::BadArg),
_ => Err(rustler::Error::BadArg),
}
}
}
pub(crate) fn decode_derive(term: Term) -> NifResult<Vec<syn::Attribute>> {
let paths = crate::generated_ast::decode_derive_path_list(term)?;
if paths.is_empty() {
Ok(Vec::new())
} else {
Ok(vec![syn::parse_quote!(#[derive(#(#paths),*)])])
}
}
enum AttributeArg {
Ident(proc_macro2::Ident),
NameValueString(proc_macro2::Ident, String),
}
impl ToTokens for AttributeArg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
AttributeArg::Ident(ident) => tokens.extend(quote!(#ident)),
AttributeArg::NameValueString(ident, value) => tokens.extend(quote!(#ident = #value)),
}
}
}
fn decode_attribute_value(term: Term) -> NifResult<String> {
if let Ok(value) = term.decode::<String>() {
Ok(value)
} else {
atom_or_string(term)
}
}
pub(crate) fn decode_attribute_list(term: Term) -> NifResult<Vec<syn::Attribute>> {
term.decode::<Vec<Term>>()?
.into_iter()
.map(decode_attribute)
.collect()
}
fn decode_attribute(term: Term) -> NifResult<syn::Attribute> {
let path = path_from_parts(decode_string_list(
term.map_get(atom(term.get_env(), "path")?)?,
)?)?;
let arg_term = term.map_get(atom(term.get_env(), "args")?)?;
if let Ok((tag, value)) = arg_term.decode::<(Term, Term)>() {
if atom_or_string(tag)? == "value" {
let value = decode_attribute_value(value)?;
return parse_syn(quote!(#[#path = #value]));
}
}
let args = decode_attribute_args(arg_term)?;
if args.is_empty() {
parse_syn(quote!(#[#path]))
} else {
parse_syn(quote!(#[#path(#(#args),*)]))
}
}
fn decode_attribute_args(term: Term) -> NifResult<Vec<AttributeArg>> {
if let Ok(args) = term.decode::<Vec<(Term, Term)>>() {
return args
.into_iter()
.map(|(key, value)| {
Ok(AttributeArg::NameValueString(
format_ident!("{}", atom_or_string(key)?),
decode_attribute_value(value)?,
))
})
.collect();
}
term.decode::<Vec<Term>>()?
.into_iter()
.map(|value| {
Ok(AttributeArg::Ident(format_ident!(
"{}",
atom_or_string(value)?
)))
})
.collect()
}
pub(crate) fn decode_derive_path_terms(term: Term) -> NifResult<Vec<Term>> {
term.decode::<Vec<Term>>()?
.into_iter()
.map(|term| {
if struct_name(term).ok().as_deref() == Some("Elixir.RustQ.Rust.AST.Derive") {
term.map_get(atom(term.get_env(), "paths")?)?
.decode::<Vec<Term>>()
} else {
Ok(vec![term])
}
})
.collect::<NifResult<Vec<Vec<Term>>>>()
.map(|terms| terms.into_iter().flatten().collect())
}
pub(crate) fn decode_path_value(term: Term) -> NifResult<syn::Path> {
let parts = if let Ok(parts) = term.decode::<Vec<Term>>() {
parts
.into_iter()
.map(atom_or_string)
.collect::<NifResult<Vec<String>>>()?
} else {
vec![atom_or_string(term)?]
};
path_from_parts(parts)
}
// Collection decoders for generated AST nodes.
pub(crate) fn decode_list<T>(term: Term, decoder: fn(Term) -> NifResult<T>) -> NifResult<Vec<T>> {
term.decode::<Vec<Term>>()?
.into_iter()
.map(decoder)
.collect()
}
pub(crate) fn decode_stmt_list(term: Term) -> NifResult<Vec<Stmt>> {
decode_list(term, decode_stmt)
}
pub(crate) fn decode_block(term: Term) -> NifResult<syn::Block> {
let stmts = decode_stmt_list(term)?;
syn::parse2::<syn::Block>(quote!({ #(#stmts)* })).map_err(|_| rustler::Error::BadArg)
}
pub(crate) fn parse_block_arm(pat: Pat, block: syn::Block) -> NifResult<Arm> {
Ok(Arm {
attrs: Vec::new(),
pat,
guard: None,
fat_arrow_token: Default::default(),
body: Box::new(Expr::Block(syn::ExprBlock {
attrs: Vec::new(),
label: None,
block,
})),
comma: Some(Default::default()),
})
}
pub(crate) fn parse_guarded_block_arm(
pat: Pat,
guard: Option<Expr>,
block: syn::Block,
) -> NifResult<Arm> {
if let Some(guard) = guard {
parse_syn::<Arm>(quote!(#pat if #guard => #block,))
} else {
parse_block_arm(pat, block)
}
}
pub(crate) fn decode_optional_block_field(
term: Term,
field: &str,
) -> NifResult<Option<syn::Block>> {
let value = term.map_get(atom(term.get_env(), field)?)?;
let values = value.decode::<Vec<Term>>()?;
if values.is_empty() {
Ok(None)
} else {
Ok(Some(decode_block(value)?))
}
}
pub(crate) fn decode_stmt(term: Term) -> NifResult<Stmt> {
decode_ast_stmt(term)
}
pub(crate) fn decode_let_pattern(pat: Pat, mutable: bool) -> NifResult<proc_macro2::TokenStream> {
if mutable {
let Pat::Ident(mut pat_ident) = pat else {
return Err(rustler::Error::BadArg);
};
pat_ident.mutability = Some(Default::default());
Ok(quote!(#pat_ident))
} else {
Ok(quote!(#pat))
}
}
pub(crate) fn parse_let_stmt(
pat_tokens: proc_macro2::TokenStream,
ty: Option<Type>,
expr: Expr,
) -> NifResult<Stmt> {
if let Some(ty) = ty {
parse_syn::<Stmt>(quote!(let #pat_tokens: #ty = #expr;))
} else {
parse_syn::<Stmt>(quote!(let #pat_tokens = #expr;))
}
}
pub(crate) fn parse_let_else_stmt(pat: Pat, expr: Expr, else_block: syn::Block) -> NifResult<Stmt> {
parse_syn::<Stmt>(quote!(let #pat = #expr else #else_block;))
}
pub(crate) fn parse_assign_stmt(target: Expr, expr: Expr) -> NifResult<Stmt> {
parse_syn::<Stmt>(quote!(#target = #expr;))
}
pub(crate) fn parse_return_stmt(expr: Expr) -> NifResult<Stmt> {
parse_syn::<Stmt>(quote!(return #expr;))
}
pub(crate) fn parse_block_expr(block: syn::Block) -> NifResult<Expr> {
Ok(Expr::Block(syn::ExprBlock {
attrs: Vec::new(),
label: None,
block,
}))
}
pub(crate) fn parse_if_expr(
condition: Expr,
then_block: syn::Block,
else_block: Option<syn::Block>,
) -> NifResult<Expr> {
if let Some(else_block) = else_block {
parse_syn::<Expr>(quote!(if #condition #then_block else #else_block))
} else {
parse_syn::<Expr>(quote!(if #condition #then_block))
}
}
pub(crate) fn parse_if_let_stmt(
pattern: Pat,
expr: Expr,
then_block: syn::Block,
else_block: Option<syn::Block>,
) -> NifResult<Stmt> {
if let Some(else_block) = else_block {
parse_syn::<Stmt>(quote!(if let #pattern = #expr #then_block else #else_block))
} else {
parse_syn::<Stmt>(quote!(if let #pattern = #expr #then_block))
}
}
pub(crate) fn parse_for_stmt(pattern: Pat, expr: Expr, body: syn::Block) -> NifResult<Stmt> {
parse_syn::<Stmt>(quote!(for #pattern in #expr #body))
}
pub(crate) fn parse_loop_stmt(body: syn::Block) -> NifResult<Stmt> {
parse_syn::<Stmt>(quote!(loop #body))
}
pub(crate) fn parse_break_stmt(expr: Option<Expr>) -> NifResult<Stmt> {
if let Some(expr) = expr {
parse_syn::<Stmt>(quote!(break #expr;))
} else {
parse_syn::<Stmt>(quote!(break;))
}
}
pub(crate) fn parse_continue_stmt() -> NifResult<Stmt> {
parse_syn::<Stmt>(quote!(continue;))
}
pub(crate) fn decode_expr(term: Term) -> NifResult<Expr> {
decode_ast_expr(term)
}
// syn parser helpers used as explicit Rusty-Elixir primitive boundaries.
pub(crate) fn parse_ident_expr(ident: proc_macro2::Ident) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(#ident))
}
pub(crate) fn parse_ref_expr(expr: Expr, mutable: bool) -> NifResult<Expr> {
if mutable {
parse_syn::<Expr>(quote!(&mut #expr))
} else {
parse_syn::<Expr>(quote!(&#expr))
}
}
pub(crate) fn parse_struct_literal_expr(
path: Expr,
fields: Vec<NamedField<Expr>>,
) -> NifResult<Expr> {
let Expr::Path(path) = path else {
return Err(rustler::Error::BadArg);
};
parse_syn::<Expr>(quote!(#path { #(#fields),* }))
}
pub(crate) fn parse_raise_atom_expr(name: String) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(rustler::Error::RaiseAtom(#name)))
}
pub(crate) fn parse_binary_expr(left: Expr, op: String, right: Expr) -> NifResult<Expr> {
match op.as_str() {
"eq" => parse_syn::<Expr>(quote!(#left == #right)),
"ne" => parse_syn::<Expr>(quote!(#left != #right)),
"lt" => parse_syn::<Expr>(quote!(#left < #right)),
"lte" => parse_syn::<Expr>(quote!(#left <= #right)),
"gt" => parse_syn::<Expr>(quote!(#left > #right)),
"gte" => parse_syn::<Expr>(quote!(#left >= #right)),
"add" => parse_syn::<Expr>(quote!(#left + #right)),
"sub" => parse_syn::<Expr>(quote!(#left - #right)),
"mul" => parse_syn::<Expr>(quote!(#left * #right)),
"div" => parse_syn::<Expr>(quote!(#left / #right)),
"and" => parse_syn::<Expr>(quote!(#left && #right)),
"or" => parse_syn::<Expr>(quote!(#left || #right)),
"shr" => parse_syn::<Expr>(quote!(#left >> #right)),
"bitand" => parse_syn::<Expr>(quote!(#left & #right)),
_ => Err(rustler::Error::BadArg),
}
}
pub(crate) fn parse_match_expr(expr: Expr, arms: Vec<Arm>) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(match #expr { #(#arms)* }))
}
pub(crate) fn parse_tuple_expr(values: Vec<Expr>) -> NifResult<Expr> {
parse_syn::<Expr>(quote!((#(#values),*)))
}
pub(crate) fn parse_vec_expr(values: Vec<Expr>) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(vec![#(#values),*]))
}
pub(crate) fn parse_closure_expr(args: Vec<proc_macro2::Ident>, body: Expr) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(|#(#args),*| #body))
}
pub(crate) fn parse_macro_call_expr(path: syn::Path, args: Vec<Expr>) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(#path!(#(#args),*)))
}
pub(crate) fn parse_ok_expr(expr: Option<Expr>) -> NifResult<Expr> {
if let Some(expr) = expr {
parse_syn::<Expr>(quote!(Ok(#expr)))
} else {
parse_syn::<Expr>(quote!(Ok(())))
}
}
pub(crate) fn parse_none_expr(_term: Term) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(None))
}
pub(crate) fn parse_some_expr(expr: Expr) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(Some(#expr)))
}
pub(crate) fn parse_err_expr(expr: Expr) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(Err(#expr)))
}
pub(crate) fn parse_try_expr(expr: Expr) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(#expr?))
}
pub(crate) fn parse_expr_stmt(expr: Expr) -> NifResult<Stmt> {
parse_syn::<Stmt>(quote!(#expr;))
}
pub(crate) fn parse_path_call_expr(
path: syn::Path,
args: Vec<Expr>,
generics: Vec<Type>,
) -> NifResult<Expr> {
if generics.is_empty() {
parse_syn::<Expr>(quote!(#path(#(#args),*)))
} else {
parse_syn::<Expr>(quote!(#path::<#(#generics),*>(#(#args),*)))
}
}
pub(crate) fn parse_method_call_expr(
receiver: Expr,
method: proc_macro2::Ident,
args: Vec<Expr>,
generics: Vec<Type>,
) -> NifResult<Expr> {
if method_receiver_needs_grouping(&receiver) {
if generics.is_empty() {
parse_syn::<Expr>(quote!((#receiver).#method(#(#args),*)))
} else {
parse_syn::<Expr>(quote!((#receiver).#method::<#(#generics),*>(#(#args),*)))
}
} else if generics.is_empty() {
parse_syn::<Expr>(quote!(#receiver.#method(#(#args),*)))
} else {
parse_syn::<Expr>(quote!(#receiver.#method::<#(#generics),*>(#(#args),*)))
}
}
fn method_receiver_needs_grouping(receiver: &Expr) -> bool {
matches!(receiver, Expr::Binary(_) | Expr::Cast(_))
}
pub(crate) fn parse_field_expr(receiver: Expr, field: Term) -> NifResult<Expr> {
if let Ok(index) = field.decode::<u32>() {
let index = syn::Index::from(index as usize);
return parse_syn::<Expr>(quote!(#receiver.#index));
}
let field = format_ident!("{}", atom_or_string(field)?);
parse_syn::<Expr>(quote!(#receiver.#field))
}
pub(crate) fn parse_index_expr(receiver: Expr, index: Expr) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(#receiver[#index]))
}
pub(crate) fn parse_range_expr(start: Option<Expr>, stop: Option<Expr>) -> NifResult<Expr> {
match (start, stop) {
(Some(start), Some(stop)) => parse_syn::<Expr>(quote!(#start..#stop)),
(Some(start), None) => parse_syn::<Expr>(quote!(#start..)),
(None, Some(stop)) => parse_syn::<Expr>(quote!(..#stop)),
(None, None) => parse_syn::<Expr>(quote!(..)),
}
}
pub(crate) fn parse_cast_expr(expr: Expr, ty: Type) -> NifResult<Expr> {
if matches!(expr, Expr::Binary(_)) {
parse_syn::<Expr>(quote!((#expr) as #ty))
} else {
parse_syn::<Expr>(quote!(#expr as #ty))
}
}
pub(crate) fn parse_unary_expr(op: String, expr: Expr) -> NifResult<Expr> {
match op.as_str() {
"not" => parse_syn::<Expr>(quote!( !#expr )),
"neg" => parse_syn::<Expr>(quote!( -#expr )),
"deref" => parse_syn::<Expr>(quote!( *#expr )),
_ => Err(rustler::Error::BadArg),
}
}
pub(crate) fn parse_byte_string_expr(value: String) -> NifResult<Expr> {
let bytes = proc_macro2::Literal::byte_string(value.as_bytes());
parse_syn::<Expr>(quote!(#bytes))
}
pub(crate) fn parse_array_expr(values: Vec<Expr>) -> NifResult<Expr> {
parse_syn::<Expr>(quote!([#(#values),*]))
}
pub(crate) fn parse_local_call(name: String, args: Vec<Expr>) -> NifResult<Expr> {
if name.ends_with('!') {
return Err(rustler::Error::BadArg);
}
let name = format_ident!("{}", name);
parse_syn::<Expr>(quote!(#name(#(#args),*)))
}
pub(crate) struct NamedField<T> {
name: proc_macro2::Ident,
value: T,
}
impl<T: ToTokens> ToTokens for NamedField<T> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let name = &self.name;
let value = &self.value;
tokens.extend(quote!(#name: #value));
}
}
pub(crate) fn decode_struct_literal_fields(term: Term) -> NifResult<Vec<NamedField<Expr>>> {
decode_named_field_list(term, decode_expr)
}
pub(crate) fn decode_arm_list(term: Term) -> NifResult<Vec<Arm>> {
decode_list(term, decode_arm)
}
pub(crate) fn decode_atom_guard_arm(pat_term: Term, block: syn::Block) -> NifResult<Arm> {
let name = format_ident_value(atom_key(pat_term, "name")?);
parse_syn::<Arm>(quote!(value if value == atoms::#name() => #block,))
}
pub(crate) fn format_ident_value(name: String) -> proc_macro2::Ident {
ident_from_part(&name)
}
pub(crate) fn parse_ast_path(term: Term) -> NifResult<syn::Path> {
path_from_parts(decode_string_list(
term.map_get(atom(term.get_env(), "parts")?)?,
)?)
}
pub(crate) fn parse_path_expr(path: syn::Path) -> NifResult<Expr> {
parse_syn::<Expr>(quote!(#path))
}
pub(crate) fn parse_atom_value_expr(
module: Vec<String>,
name: proc_macro2::Ident,
) -> NifResult<Expr> {
let module = path_from_parts(module)?;
parse_syn::<Expr>(quote!(#module::#name()))
}
pub(crate) fn parse_item_use_group_term(term: Term) -> NifResult<syn::ItemUse> {
let (base, names) = term.decode::<(Term, Term)>()?;
crate::parse_item_use_group(decode_string_list(base)?, decode_string_list(names)?)
}
pub(crate) fn string_field(term: Term, key: &str) -> NifResult<String> {
term.map_get(atom(term.get_env(), key)?)?.decode()
}
pub(crate) fn decode_optional_field<T>(
term: Term,
key: &str,
decoder: fn(Term) -> NifResult<T>,
) -> NifResult<Option<T>> {
match optional_map_get(term, key)? {
Some(value) if !is_nil(value)? => Ok(Some(decoder(value)?)),
_ => Ok(None),
}
}
pub(crate) fn decode_optional_type_field(term: Term, key: &str) -> NifResult<Option<Type>> {
decode_optional_field(term, key, decode_type)
}
pub(crate) fn decode_optional_expr_field(term: Term, key: &str) -> NifResult<Option<Expr>> {
decode_optional_field(term, key, decode_expr)
}
enum LiteralTerm {
Bool(bool),
I64(i64),
F64(f64),
String(String),
Atom(String),
}
fn decode_literal_term(term: Term) -> NifResult<LiteralTerm> {
if let Ok(value) = term.decode::<bool>() {
return Ok(LiteralTerm::Bool(value));
}
if let Ok(value) = term.decode::<i64>() {
return Ok(LiteralTerm::I64(value));
}
if let Ok(value) = term.decode::<f64>() {
return Ok(LiteralTerm::F64(value));
}
if let Ok(value) = term.decode::<String>() {
return Ok(LiteralTerm::String(value));
}
if term.is_atom() {
return Ok(LiteralTerm::Atom(term.atom_to_string()?));
}
Err(rustler::Error::BadArg)
}
pub(crate) fn parse_var_pat(ident: proc_macro2::Ident, mutable: bool) -> NifResult<Pat> {
if mutable {
parse_syn::<Pat>(quote!(mut #ident))
} else {
parse_syn::<Pat>(quote!(#ident))
}
}
pub(crate) fn parse_wildcard_pat(_term: Term) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(_))
}
pub(crate) fn parse_none_pat(_term: Term) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(None))
}
pub(crate) fn parse_path_pat(path: syn::Path) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(#path))
}
pub(crate) fn parse_some_pat(pat: Pat) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(Some(#pat)))
}
pub(crate) fn parse_ok_pat(pat: Pat) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(Ok(#pat)))
}
pub(crate) fn parse_err_pat(pat: Pat) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(Err(#pat)))
}
pub(crate) fn parse_tuple_pat(patterns: Vec<Pat>) -> NifResult<Pat> {
parse_syn::<Pat>(quote!((#(#patterns),*)))
}
pub(crate) fn parse_path_tuple_pat(path: syn::Path, patterns: Vec<Pat>) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(#path(#(#patterns),*)))
}
pub(crate) fn parse_struct_pat(path: syn::Path, fields: Vec<NamedField<Pat>>) -> NifResult<Pat> {
parse_syn::<Pat>(quote!(#path { #(#fields),* }))
}
pub(crate) fn decode_pat_literal_value(term: Term) -> NifResult<Pat> {
match decode_literal_term(term)? {
LiteralTerm::Bool(true) => parse_syn::<Pat>(quote!(true)),
LiteralTerm::Bool(false) => parse_syn::<Pat>(quote!(false)),
LiteralTerm::I64(value) => parse_syn::<Pat>(quote!(#value)),
LiteralTerm::F64(value) => parse_syn::<Pat>(quote!(#value)),
LiteralTerm::String(value) | LiteralTerm::Atom(value) => parse_syn::<Pat>(quote!(#value)),
}
}
pub(crate) fn decode_pat(term: Term) -> NifResult<Pat> {
decode_ast_pat(term)
}
pub(crate) fn decode_pat_atom_guard(_term: Term) -> NifResult<Pat> {
Err(rustler::Error::BadArg)
}
pub(crate) fn decode_pat_list(term: Term) -> NifResult<Vec<Pat>> {
decode_list(term, decode_pat)
}
pub(crate) fn decode_named_field_list<T>(
term: Term,
decoder: fn(Term) -> NifResult<T>,
) -> NifResult<Vec<NamedField<T>>> {
term.decode::<Vec<(Term, Term)>>()?
.into_iter()
.map(|(name, value)| {
Ok(NamedField {
name: format_ident!("{}", atom_or_string(name)?),
value: decoder(value)?,
})
})
.collect()
}
pub(crate) fn decode_pat_struct_fields(term: Term) -> NifResult<Vec<NamedField<Pat>>> {
decode_named_field_list(term, decode_pat)
}
pub(crate) fn decode_expr_list(term: Term) -> NifResult<Vec<Expr>> {
decode_list(term, decode_expr)
}
pub(crate) fn decode_ident_list(term: Term) -> NifResult<Vec<proc_macro2::Ident>> {
decode_string_list(term).map(|names| {
names
.into_iter()
.map(|name| format_ident!("{}", name))
.collect()
})
}
pub(crate) fn decode_literal_expr(term: Term) -> NifResult<Expr> {
match decode_literal_term(term)? {
LiteralTerm::Bool(true) => parse_syn::<Expr>(quote!(true)),
LiteralTerm::Bool(false) => parse_syn::<Expr>(quote!(false)),
LiteralTerm::I64(value) => parse_expr(value.to_string()),
LiteralTerm::F64(value) => parse_expr(format_float_literal(value)),
LiteralTerm::String(value) => parse_syn::<Expr>(quote!(#value)),
LiteralTerm::Atom(_) => Err(rustler::Error::BadArg),
}
}
fn format_float_literal(value: f64) -> String {
let formatted = value.to_string();
if formatted.contains('.') || formatted.contains('e') || formatted.contains('E') {
formatted
} else {
format!("{formatted}.0")
}
}
pub(crate) fn path_parts(term: Term) -> NifResult<String> {
crate::generated_ast::path_parts(term)
}
pub(crate) fn atom_or_string(term: Term) -> NifResult<String> {
if term.is_atom() {
term.atom_to_string()
} else {
term.decode::<String>()
}
}
// Type decoding primitives retained until all type shapes are dogfooded.
pub(crate) fn decode_type(term: Term) -> NifResult<Type> {
if let Ok(source) = term.decode::<String>() {
return parse_type(&source);
}
if term.is_atom() {
return parse_type(&atom_or_string(term)?);
}
if let Ok((tag, value)) = term.decode::<(Term, Term)>() {
return decode_type_tuple2(atom_or_string(tag)?.as_str(), value);
}
if let Ok((tag, ok, error)) = term.decode::<(Term, Term, Term)>() {
if atom_or_string(tag)? == "result" {
return parse_type_generic("Result", vec![decode_type(ok)?, decode_type(error)?]);
}
}
decode_ast_type(term)
}
fn decode_type_tuple2(tag: &str, value: Term) -> NifResult<Type> {
match tag {
"option" => parse_type_generic("Option", vec![decode_type(value)?]),
"vec" => parse_type_generic("Vec", vec![decode_type(value)?]),
"ref" => parse_type_ref(decode_type(value)?, false, None),
"mut_ref" => parse_type_ref(decode_type(value)?, true, None),
"raw" => parse_type(&atom_or_string(value)?),
_ => Err(rustler::Error::BadArg),
}
}
pub(crate) fn decode_string_list(term: Term) -> NifResult<Vec<String>> {
term.decode::<Vec<Term>>()?
.into_iter()
.map(atom_or_string)
.collect::<NifResult<Vec<String>>>()
}