use prebindgen_registry::Conversions;
use super::*;
impl CbindgenBuilder {
pub(crate) fn prereq_domain_constants(&self, registry: &Registry<()>) -> Vec<syn::Item> {
let mut items = Vec::new();
for decl in &self.convert_decls {
let Some(domain) = decl.domain() else {
continue;
};
let demand = [Direction::Input, Direction::Output]
.into_iter()
.flat_map(|direction| registry.readings(direction))
.map(|subject| option_depth(subject, decl.key()))
.max()
.unwrap_or(0);
let ty = domain.ty();
let base = self
.convert_bases
.get(decl.key())
.cloned()
.unwrap_or_else(|| {
let short = type_short(&decl.rust_type().key().clone());
self.mangle_rust_type
.as_ref()
.map(|m| m(&short))
.unwrap_or_else(|| snake_case(&short))
})
.to_ascii_uppercase();
for (index, value) in domain
.niche_values(demand.saturating_add(8))
.into_iter()
.filter_map(prebindgen_registry::ScalarValue::portable_expr)
.take(demand)
.enumerate()
{
let name = format_ident!("{}_NICHE_{}", base, index);
items.push(syn::parse_quote!(
#[doc = "Reserved representation value used by generated sum-type ABIs."]
pub const #name: #ty = #value;
));
if index == 0 {
let none = format_ident!("{}_NONE", base);
items.push(syn::parse_quote!(
#[doc = "Representation of None for the first optional layer."]
pub const #none: #ty = #value;
));
}
}
}
items
}
pub(crate) fn in_custom(
&self,
ty: &TypeRef,
registry: &impl Conversions<()>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<()>> {
let key = ty.key();
let decl = self.convert_decls.iter().find(|d| *d.key() == key)?;
let spec = decl.input_spec().as_ref()?;
let (repr, conversion, fallible) = self.input_conversion(decl, spec, registry, emit);
assert!(
is_scalar(&repr),
"Cbindgen custom representations must be C scalar types"
);
if let Some(domain) = decl.domain() {
assert_eq!(
TypeKey::from_type(domain.ty()),
TypeKey::from_type(&repr),
"Cbindgen conversion domain type does not match its input representation"
);
}
let src = self.src_ty_of(&key);
let wire = repr.clone();
let name = Self::in_name_of(&key);
let valid = decl
.domain()
.as_ref()
.map(|d| d.contains_expr(quote!(v)))
.unwrap_or_else(|| quote!(true));
let msg = format!("{} representation is outside its declared domain", key);
let function: syn::ItemFn = if decl.domain().is_some() || fallible {
let converted = if fallible {
quote!((#conversion).map_err(|e| e.to_string()))
} else {
quote!(::core::result::Result::Ok(#conversion))
};
syn::parse_quote!(
#[allow(non_snake_case, unused_variables, dead_code)]
pub(crate) fn #name(v: #wire)
-> ::core::result::Result<#src, ::std::string::String>
{
if !(#valid) {
return ::core::result::Result::Err(
::std::string::String::from(#msg)
);
}
#converted
}
)
} else {
syn::parse_quote!(
#[allow(non_snake_case, unused_variables, dead_code)]
pub(crate) fn #name(v: #wire) -> #src {
#conversion
}
)
};
let niches = self.c_domain_niches(decl, registry, Direction::Input);
Some(ConverterImpl {
subs: vec![TypeKey::from_type(&repr)],
destination: wire,
function,
pre_stages: vec![],
niches,
metadata: (),
})
}
pub(crate) fn out_custom(
&self,
ty: &TypeRef,
registry: &impl Conversions<()>,
emit: &prebindgen_registry::Emit,
) -> Option<ConverterImpl<()>> {
let key = ty.key();
let decl = self.convert_decls.iter().find(|d| *d.key() == key)?;
let spec = decl.output_spec().as_ref()?;
let (repr, conversion, fallible) = self.output_conversion(decl, spec, registry, emit);
assert!(
is_scalar(&repr),
"Cbindgen custom representations must be C scalar types"
);
if let Some(domain) = decl.domain() {
assert_eq!(
TypeKey::from_type(domain.ty()),
TypeKey::from_type(&repr),
"Cbindgen conversion domain type does not match its output representation"
);
}
let src = self.src_ty_of(&key);
let wire = repr.clone();
let name = Self::out_name_of(&key);
let valid = decl
.domain()
.as_ref()
.map(|d| d.contains_expr(quote!(__repr)))
.unwrap_or_else(|| quote!(true));
let msg = format!("{} representation is outside its declared domain", key);
let function: syn::ItemFn = if decl.domain().is_some() || fallible {
let repr_expr = if fallible {
quote!((#conversion).map_err(|error| error.to_string())?)
} else {
quote!(#conversion)
};
syn::parse_quote!(
#[allow(non_snake_case, unused_variables, dead_code)]
pub(crate) fn #name(v: #src)
-> ::core::result::Result<#wire, ::std::string::String>
{
let __repr: #repr = #repr_expr;
if !(#valid) {
return ::core::result::Result::Err(
::std::string::String::from(#msg)
);
}
::core::result::Result::Ok(__repr)
}
)
} else {
syn::parse_quote!(
#[allow(non_snake_case, unused_variables, dead_code)]
pub(crate) fn #name(v: #src) -> #wire {
#conversion
}
)
};
let niches = self.c_domain_niches(decl, registry, Direction::Output);
Some(ConverterImpl {
subs: vec![TypeKey::from_type(&repr)],
destination: wire,
function,
pre_stages: vec![],
niches,
metadata: (),
})
}
fn input_conversion(
&self,
decl: &ConvertDecl,
spec: &ConvertSpec,
registry: &impl Conversions<()>,
emit: &prebindgen_registry::Emit,
) -> (syn::Type, syn::Expr, bool) {
let target = self.src_ty_of(&decl.rust_type().key());
match spec {
ConvertSpec::PrebindgenFn(f) => {
let item = registry
.flat()
.function(&f)
.unwrap_or_else(|| panic!("Cbindgen conversion function {} was not found", f));
let (repr_reading, by_ref) = one_param(item);
let repr = emit.spell_ty(repr_reading);
let (ok, fallible) = match item.ret.fallible_parts() {
Some((ok, _)) => (ok, true),
None => (&item.ret, false),
};
assert_eq!(ok.key(), *decl.key());
let path = self.conversion_fn_path(registry, f);
let expr = if by_ref {
syn::parse_quote!(#path(&v))
} else {
syn::parse_quote!(#path(v))
};
(repr, expr, fallible)
}
ConvertSpec::Trait { repr, fallible } => {
let expr = if *fallible {
syn::parse_quote!(
<#repr as ::core::convert::TryInto<#target>>::try_into(v)
)
} else {
syn::parse_quote!(
<#repr as ::core::convert::Into<#target>>::into(v)
)
};
(repr.clone(), expr, *fallible)
}
}
}
fn output_conversion(
&self,
decl: &ConvertDecl,
spec: &ConvertSpec,
registry: &impl Conversions<()>,
emit: &prebindgen_registry::Emit,
) -> (syn::Type, syn::Expr, bool) {
let target = self.src_ty_of(&decl.rust_type().key());
match spec {
ConvertSpec::PrebindgenFn(f) => {
let item = registry
.flat()
.function(&f)
.unwrap_or_else(|| panic!("Cbindgen conversion function {} was not found", f));
let (param, by_ref) = one_param(item);
assert_eq!(param.key(), *decl.key());
let (repr, fallible) = match item.ret.fallible_parts() {
Some((ok, _)) => (emit.spell_ty(ok), true),
None => (emit.spell_ty(&item.ret), false),
};
let path = self.conversion_fn_path(registry, f);
let expr = if by_ref {
syn::parse_quote!(#path(&v))
} else {
syn::parse_quote!(#path(v))
};
(repr, expr, fallible)
}
ConvertSpec::Trait { repr, fallible } => {
let expr = if *fallible {
syn::parse_quote!(
<#target as ::core::convert::TryInto<#repr>>::try_into(v)
)
} else {
syn::parse_quote!(
<#target as ::core::convert::Into<#repr>>::into(v)
)
};
(repr.clone(), expr, *fallible)
}
}
}
fn c_domain_niches(
&self,
decl: &ConvertDecl,
registry: &impl Conversions<()>,
direction: Direction,
) -> Niches {
let Some(domain) = decl.domain() else {
return Niches::empty();
};
let demand = registry
.crossing_keys(direction)
.iter()
.map(|candidate| {
registry
.reading(candidate)
.map_or(0, |reading| option_depth(&reading, decl.key()))
})
.max()
.unwrap_or(0);
Niches::from_slots(
domain
.niche_values(demand.saturating_add(8))
.into_iter()
.filter_map(|value| value.portable_expr().map(|literal| (value, literal)))
.take(demand)
.map(|(value, literal)| {
let matches = match value {
prebindgen_registry::ScalarValue::F32(bits) => {
syn::parse_quote!(v.to_bits() == #bits)
}
prebindgen_registry::ScalarValue::F64(bits) => {
syn::parse_quote!(v.to_bits() == #bits)
}
_ => syn::parse_quote!(v == #literal),
};
NicheSlot {
value: literal,
matches,
}
}),
)
}
fn conversion_fn_path(&self, registry: &impl Conversions<()>, ident: &syn::Ident) -> syn::Path {
let Some(mut module) = registry.origin_module(ident) else {
return self.src_fn(ident);
};
module.segments.push(syn::PathSegment::from(ident.clone()));
module
}
}
fn one_param(f: &prebindgen_registry::flat::Function) -> (&TypeRef, bool) {
assert_eq!(
f.params.len(),
1,
"conversion functions take exactly one parameter"
);
let ty = &f.params[0].ty;
match ty.kind() {
TypeKind::Ref { inner, .. } => (inner, true),
_ => (ty, false),
}
}
fn option_depth(candidate: &prebindgen_registry::flat::TypeRef, target: &TypeKey) -> usize {
let mut reading = candidate;
let mut depth = 0;
while let Some(inner) = reading.optional_inner() {
reading = inner;
depth += 1;
}
if reading.key() == *target {
depth
} else {
0
}
}