use std::collections::HashSet;
use crate::{
declared_target::check_declared_target,
registry::{Registry, TypeKey},
};
mod error;
mod plan;
pub use self::{
error::{UnfoldDeclError, UnfoldError},
plan::{
steps_are_movable, DeconId, DeconSpec, Hoist, LeafSource, PathStep, UnfoldLeaf, UnfoldPlan,
UnfoldShape,
},
};
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum DeconRecord {
Acc { func: syn::Ident, name: String },
LocalAcc { path: syn::Path, name: String },
Identity,
Fields {
func: syn::Ident,
consuming: bool,
fields: Vec<FieldRecord>,
},
}
#[derive(Clone)]
pub struct FieldRecord {
pub members: Vec<syn::Ident>,
pub name: String,
pub ty: prebindgen_flat::flat::TypeRef,
pub decon: FieldDecon,
}
#[derive(Clone)]
pub enum FieldDecon {
Default,
Records(Vec<DeconRecord>),
Leaves(Vec<UnfoldLeaf>),
}
impl DeconRecord {
fn local_ident(path: &syn::Path) -> syn::Ident {
path.segments
.last()
.expect("field!(...).with(...): empty accessor path")
.ident
.clone()
}
}
#[derive(Clone)]
pub struct DeconstructorDecl {
pub target: TypeKey,
pub records: Vec<DeconRecord>,
pub default: Option<(DeconTarget, Delivery)>,
}
#[derive(Clone)]
pub enum DeconSel {
TopLevel,
Inline(Vec<DeconRecord>),
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum DeconTarget {
Output,
Error,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Delivery {
Callback,
Return,
}
#[derive(Clone)]
pub struct OutputDecl {
pub func: syn::Ident,
pub sel: DeconSel,
pub target: DeconTarget,
pub delivery: Delivery,
pub declared_source: Option<TypeKey>,
}
#[derive(Clone, Default)]
pub struct Deconstructors {
pub deconstructors: Vec<DeconstructorDecl>,
pub outputs: Vec<OutputDecl>,
pub skip_output: std::collections::HashSet<syn::Ident>,
}
fn validate_declarations(acc: &Deconstructors) -> Result<(), UnfoldError> {
let mut entries: Vec<UnfoldDeclError> = Vec::new();
let mut decon_targets: std::collections::HashSet<String> = std::collections::HashSet::new();
for d in &acc.deconstructors {
let target = d.target.as_str().to_string();
if !decon_targets.insert(target.clone()) {
entries.push(UnfoldDeclError::DuplicateDeconstructor { target });
}
}
let mut output_keys: std::collections::HashSet<(String, DeconTarget)> =
std::collections::HashSet::new();
for od in &acc.outputs {
if !output_keys.insert((od.func.to_string(), od.target)) {
entries.push(UnfoldDeclError::DuplicateOutput {
func: od.func.clone(),
target: od.target,
});
}
}
if entries.is_empty() {
Ok(())
} else {
Err(UnfoldError::InvalidDeclarations { entries })
}
}
pub(crate) fn apply<M>(
registry: &mut Registry<M>,
acc: &Deconstructors,
declared_fns: &std::collections::HashSet<syn::Ident>,
accessor_fns: &std::collections::HashSet<syn::Ident>,
) -> Result<(), UnfoldError> {
validate_declarations(acc)?;
for d in &acc.deconstructors {
check_records(&d.records, accessor_fns)?;
}
let mut done: std::collections::HashSet<(syn::Ident, DeconTarget)> = Default::default();
for ed in &acc.outputs {
if let Some(declared) = &ed.declared_source {
let ret = registry
.flat()
.function(&ed.func)
.map(|f| f.ret.clone())
.ok_or_else(|| UnfoldError::UnknownFunction(ed.func.clone()))?;
if !returns_type(&ret, declared) {
return Err(UnfoldError::ReturnTypeMismatch {
func: ed.func.clone(),
declared: declared.as_str().to_string(),
actual: {
let s = ret.spell();
quote::quote!(#s).to_string()
},
});
}
}
if let DeconSel::Inline(records) = &ed.sel {
if matches!(records.as_slice(), [DeconRecord::Identity]) {
done.insert((ed.func.clone(), ed.target));
continue;
}
}
process_decl(registry, acc, ed)?;
done.insert((ed.func.clone(), ed.target));
}
for d in &acc.deconstructors {
if d.default.is_none() {
continue;
}
let dkey = d.target.clone();
let sel = DeconSel::TopLevel;
for func in declared_fns {
if accessor_fns.contains(func) {
continue;
}
let Some(ret) = registry.flat().function(&func).map(|f| f.ret.clone()) else {
continue;
};
if let Some(err_ty) = ret.fallible_parts().map(|(_, e)| e) {
if err_ty.key() == dkey && done.insert((func.clone(), DeconTarget::Error)) {
process_decl(
registry,
acc,
&OutputDecl {
func: func.clone(),
sel: sel.clone(),
target: DeconTarget::Error,
delivery: Delivery::Callback,
declared_source: None,
},
)?;
}
}
if returns_type(&ret, &dkey)
&& !acc.skip_output.contains(func)
&& done.insert((func.clone(), DeconTarget::Output))
{
process_decl(
registry,
acc,
&OutputDecl {
func: func.clone(),
sel: sel.clone(),
target: DeconTarget::Output,
delivery: Delivery::Callback,
declared_source: None,
},
)?;
}
}
}
for func in declared_fns {
let Some(params) = registry.flat().function(&func).map(|f| f.params.clone()) else {
continue;
};
for param in ¶ms {
let prebindgen_flat::flat::TypeKind::Callback { args } = param.ty.kind() else {
continue;
};
for arg_ty in args {
let (by_ref, core_ty) = peel_borrow(arg_ty);
if !matches!(
core_ty.unwrapped().kind(),
prebindgen_flat::flat::TypeKind::Named { .. }
) {
continue;
}
let key = arg_ty.key();
if registry.callback_arg_plans.contains_key(&key) {
continue;
}
let core_key = core_ty.key();
let Some(d) = acc
.deconstructors
.iter()
.find(|d| d.default.is_some() && d.target == core_key)
else {
continue;
};
let ed = OutputDecl {
func: func.clone(),
sel: DeconSel::TopLevel,
target: DeconTarget::Output,
delivery: Delivery::Callback,
declared_source: None,
};
let decon = decl_id(&core_key, d);
let records = d.records.clone();
register_decon_spec(registry, acc, &decon, &records, core_ty)?;
let plan = build_plan(
acc,
registry,
&ed,
by_ref,
core_ty,
UnfoldShape::Base,
&records,
decon,
)?;
if plan.leaves.is_empty() {
continue;
}
for leaf in &plan.leaves {
registry.require_output(&leaf.out_ty);
}
registry.callback_arg_plans.insert(key, plan);
}
}
}
Ok(())
}
pub struct ValueDecon {
pub key: TypeKey,
pub source: prebindgen_flat::flat::TypeRef,
pub leaves: Vec<UnfoldLeaf>,
}
pub(crate) fn apply_value_structs<M>(
registry: &mut Registry<M>,
decons: Vec<ValueDecon>,
declared_fns: &std::collections::HashSet<syn::Ident>,
) -> Result<(), UnfoldError> {
for vd in &decons {
let decon = wire_fixed_decon(registry, &vd.key, &vd.source, &vd.leaves)?;
wire_fixed_returns(registry, vd, &decon, declared_fns, false);
wire_fixed_callbacks(registry, vd, &decon, declared_fns)?;
}
Ok(())
}
pub struct SumDecon {
pub key: TypeKey,
pub source: prebindgen_flat::flat::TypeRef,
pub leaves: Vec<UnfoldLeaf>,
}
fn register_leaves<M>(registry: &mut crate::registry::Registry<M>, leaves: &[UnfoldLeaf]) {
for leaf in leaves {
if leaf.has_converter() {
registry.require_output(&leaf.out_ty);
} else {
registry.reference_output(&leaf.out_ty);
}
}
}
pub(crate) fn apply_sum_returns<M>(
registry: &mut Registry<M>,
decons: Vec<SumDecon>,
declared_fns: &std::collections::HashSet<syn::Ident>,
) -> Result<(), UnfoldError> {
for sd in &decons {
let decon = wire_fixed_decon(registry, &sd.key, &sd.source, &sd.leaves)?;
let vd = ValueDecon {
key: sd.key.clone(),
source: sd.source.clone(),
leaves: sd.leaves.clone(),
};
wire_fixed_returns(registry, &vd, &decon, declared_fns, true);
wire_fixed_callbacks(registry, &vd, &decon, declared_fns)?;
}
Ok(())
}
fn wire_fixed_decon<M>(
registry: &mut Registry<M>,
key: &TypeKey,
source: &prebindgen_flat::flat::TypeRef,
leaves: &[UnfoldLeaf],
) -> Result<DeconId, UnfoldError> {
let decon = DeconId::Default(key.to_string());
require_unique_leaf_names(source, leaves)?;
registry
.decon_plans
.entry(decon.clone())
.or_insert_with(|| DeconSpec {
source: source.clone(),
leaves: leaves.to_vec(),
});
Ok(decon)
}
fn wire_fixed_returns<M>(
registry: &mut Registry<M>,
vd: &ValueDecon,
decon: &DeconId,
declared_fns: &std::collections::HashSet<syn::Ident>,
no_converter: bool,
) {
for func in declared_fns {
let Some(ret) = registry.flat().function(&func).map(|f| f.ret.clone()) else {
continue;
};
if !returns_type(&ret, &vd.key) || registry.unfold_plans.contains_key(func) {
continue;
}
let layers = peel(&ret);
let by_ref = layers.by_ref;
let shape = layers.shape.clone();
if no_converter {
for layer in &layers.layer_types {
registry.unrequire_output(layer);
}
}
register_leaves(registry, &vd.leaves);
let plan = UnfoldPlan {
source: vd.source.clone(),
decon: Some(decon.clone()),
by_ref,
shape,
leaves: vd.leaves.clone(),
element: None,
delivery: Delivery::Callback,
convert_out_ty: None,
fixed_builder: true,
hoists: Vec::new(),
};
registry.unfold_plans.insert(func.clone(), plan);
}
}
fn wire_fixed_callbacks<M>(
registry: &mut Registry<M>,
vd: &ValueDecon,
decon: &DeconId,
declared_fns: &std::collections::HashSet<syn::Ident>,
) -> Result<(), UnfoldError> {
for func in declared_fns {
let Some(params) = registry.flat().function(&func).map(|f| f.params.clone()) else {
continue;
};
for param in ¶ms {
let prebindgen_flat::flat::TypeKind::Callback { args } = param.ty.kind() else {
continue;
};
for arg_ty in args {
let (by_ref, after_ref) = peel_borrow(arg_ty);
let (shape, matches_key) = match after_ref.sequence_elem() {
Some(elem) => (
UnfoldShape::Iterable(Box::new(UnfoldShape::Base)),
elem.key() == vd.key,
),
None => (UnfoldShape::Base, after_ref.key() == vd.key),
};
if !matches_key {
continue;
}
let key = arg_ty.key();
if registry.callback_arg_plans.contains_key(&key) {
continue;
}
register_leaves(registry, &vd.leaves);
let plan = UnfoldPlan {
source: vd.source.clone(),
decon: Some(decon.clone()),
by_ref,
shape,
leaves: vd.leaves.clone(),
element: None,
delivery: Delivery::Callback,
convert_out_ty: None,
fixed_builder: true,
hoists: Vec::new(),
};
registry.callback_arg_plans.insert(key, plan);
}
}
}
Ok(())
}
pub(crate) fn apply_leaf_vec_folds<M>(
registry: &mut Registry<M>,
elements: Vec<TypeKey>,
declared_fns: &std::collections::HashSet<syn::Ident>,
) -> Result<(), UnfoldError> {
if elements.is_empty() {
return Ok(());
}
let elem_keys = elements;
let is_nominated = |bare: &prebindgen_flat::flat::TypeRef| elem_keys.contains(&bare.key());
for func in declared_fns {
let Some(params) = registry.flat().function(&func).map(|f| f.params.clone()) else {
continue;
};
if !registry.unfold_plans.contains_key(func) {
let Some(ret) = registry.flat().function(&func).map(|f| f.ret.clone()) else {
continue;
};
let (optional, after_opt) = match ret.optional_inner() {
Some(inner) => (true, inner),
None => (false, &ret),
};
if let Some(vec_elem) = after_opt.sequence_elem() {
let bare = peel_borrow(vec_elem).1;
if is_nominated(bare) {
let inner_shape = UnfoldShape::Iterable(Box::new(UnfoldShape::Base));
let shape = if optional {
UnfoldShape::Optional((), Box::new(inner_shape))
} else {
inner_shape
};
registry.require_output(vec_elem);
registry.unrequire_output(&ret);
registry
.unfold_plans
.insert(func.clone(), whole_leaf_fold_plan(vec_elem, shape));
}
}
}
for param in ¶ms {
let prebindgen_flat::flat::TypeKind::Callback { args } = param.ty.kind() else {
continue;
};
for arg_ty in args {
let (_, after_ref) = peel_borrow(arg_ty);
let Some(elem) = after_ref.sequence_elem() else {
continue;
};
if !is_nominated(peel_borrow(elem).1) {
continue;
}
let key = arg_ty.key();
if registry.callback_arg_plans.contains_key(&key) {
continue;
}
registry.require_output(elem);
let plan =
whole_leaf_fold_plan(elem, UnfoldShape::Iterable(Box::new(UnfoldShape::Base)));
registry.callback_arg_plans.insert(key, plan);
}
}
}
Ok(())
}
fn whole_leaf_fold_plan(
vec_elem: &prebindgen_flat::flat::TypeRef,
shape: UnfoldShape,
) -> UnfoldPlan {
UnfoldPlan {
source: vec_elem.clone(),
decon: None,
by_ref: peel_borrow(vec_elem).0,
shape,
leaves: vec![],
element: Some(vec_elem.clone()),
delivery: Delivery::Callback,
convert_out_ty: None,
fixed_builder: true,
hoists: Vec::new(),
}
}
fn check_records(
records: &[DeconRecord],
accessor_fns: &HashSet<syn::Ident>,
) -> Result<(), UnfoldError> {
for rec in records {
let (func, name) = match rec {
DeconRecord::Acc { func, name } => (Some(func), name),
DeconRecord::LocalAcc { name, .. } => (None, name),
DeconRecord::Identity => continue,
DeconRecord::Fields { func, fields, .. } => {
if !accessor_fns.contains(func) {
return Err(UnfoldError::RecordNotAccessor { func: func.clone() });
}
for fr in fields {
if let FieldDecon::Records(recs) = &fr.decon {
check_records(recs, accessor_fns)?;
}
}
continue;
}
};
if name.contains("__") {
return Err(UnfoldError::ReservedSeparator { name: name.clone() });
}
if let Some(func) = func {
if !accessor_fns.contains(func) {
return Err(UnfoldError::RecordNotAccessor { func: func.clone() });
}
}
}
Ok(())
}
struct Layered {
shape: UnfoldShape,
layer_types: Vec<prebindgen_flat::flat::TypeRef>,
core: TypeKey,
by_ref: bool,
}
fn peel(ty: &prebindgen_flat::flat::TypeRef) -> Layered {
let (shape, layered) = ty.layer_stack();
let borrowed = layered.borrow_target();
Layered {
shape,
layer_types: ty.layer_types().into_iter().cloned().collect(),
core: borrowed.unwrap_or(layered).key(),
by_ref: borrowed.is_some(),
}
}
fn peel_borrow(ty: &prebindgen_flat::flat::TypeRef) -> (bool, &prebindgen_flat::flat::TypeRef) {
match ty.borrow_target() {
Some(inner) => (true, inner),
None => (false, ty),
}
}
fn returns_type(ret: &prebindgen_flat::flat::TypeRef, key: &TypeKey) -> bool {
peel(ret).core == *key
}
fn process_decl<M>(
registry: &mut Registry<M>,
acc: &Deconstructors,
ed: &OutputDecl,
) -> Result<(), UnfoldError> {
{
let fn_ret = registry
.flat()
.function(&ed.func)
.map(|f| f.ret.clone())
.ok_or_else(|| UnfoldError::UnknownFunction(ed.func.clone()))?;
let ret_ty = match ed.target {
DeconTarget::Output => fn_ret,
DeconTarget::Error => {
fn_ret
.fallible_parts()
.map(|(_, e)| e.clone())
.ok_or_else(|| UnfoldError::Unsupported {
func: ed.func.clone(),
reason: "convert_error/deconstruct_error on a non-Result return",
})?
}
};
let (optional, after_opt) = match ed.target {
DeconTarget::Output => match ret_ty.optional_inner() {
Some(inner) => (true, inner),
None => (false, &ret_ty),
},
DeconTarget::Error => (false, &ret_ty),
};
let plan = if let Some(inner) = after_opt.sequence_elem() {
if inner.optional_inner().is_some() {
return Err(UnfoldError::Unsupported {
func: ed.func.clone(),
reason: "Vec<Option<…>> returns",
});
}
let iterable = UnfoldShape::Iterable(Box::new(UnfoldShape::Base));
let shape = if optional {
UnfoldShape::Optional((), Box::new(iterable))
} else {
iterable
};
if ed.target == DeconTarget::Output {
registry.unrequire_output(&ret_ty);
if optional {
registry.unrequire_output(after_opt);
}
}
let (by_ref, element) = peel_borrow(inner);
let ekey = element.key();
if let Some(d) = find_deconstructor_by_type(acc, &ekey) {
let records = d.records.clone();
let decon = decl_id(&ekey, d);
register_decon_spec(registry, acc, &decon, &records, element)?;
let plan = build_plan(acc, registry, ed, by_ref, element, shape, &records, decon)?;
for leaf in &plan.leaves {
registry.require_output(&leaf.out_ty);
}
plan
} else {
let by_ref = peel_borrow(inner).0;
registry.require_output(inner);
UnfoldPlan {
source: inner.clone(),
decon: None,
by_ref,
shape,
leaves: vec![],
element: Some(inner.clone()),
delivery: ed.delivery,
convert_out_ty: None,
fixed_builder: false,
hoists: Vec::new(),
}
}
} else {
let (optional, core_ty) = match ed.target {
DeconTarget::Output => (optional, after_opt),
DeconTarget::Error => match after_opt.optional_inner() {
Some(inner) => (true, inner),
None => (false, after_opt),
},
};
let (by_ref, source) = peel_borrow(core_ty);
let source_key = source.key();
let shape = if optional {
UnfoldShape::Optional((), Box::new(UnfoldShape::Base))
} else {
UnfoldShape::Base
};
let (records, decon) = resolve_deconstructor(acc, &source_key, ed)?;
register_decon_spec(registry, acc, &decon, &records, source)?;
let plan = build_plan(acc, registry, ed, by_ref, source, shape, &records, decon)?;
for leaf in &plan.leaves {
registry.require_output(&leaf.out_ty);
}
plan
};
let single_return = ed.target == DeconTarget::Output
&& !plan.shape.has_iterable_layer()
&& plan.leaves.len() == 1
&& !plan.leaves[0].nullable;
let plan = if single_return {
let cv = if matches!(plan.shape, UnfoldShape::Optional((), _)) {
plan.leaves[0].out_ty.optional()
} else {
plan.leaves[0].out_ty.clone()
};
registry.require_output(&cv);
UnfoldPlan {
delivery: Delivery::Return,
convert_out_ty: Some(cv.clone()),
..plan
}
} else {
UnfoldPlan {
delivery: Delivery::Callback,
..plan
}
};
match ed.target {
DeconTarget::Output => registry.unfold_plans.insert(ed.func.clone(), plan),
DeconTarget::Error => registry.error_plans.insert(ed.func.clone(), plan),
};
}
Ok(())
}
fn decl_id(type_key: &TypeKey, _decl: &DeconstructorDecl) -> DeconId {
DeconId::Default(type_key.to_string())
}
fn register_decon_spec<M>(
registry: &mut Registry<M>,
acc: &Deconstructors,
decon: &DeconId,
records: &[DeconRecord],
source: &prebindgen_flat::flat::TypeRef,
) -> Result<(), UnfoldError> {
if registry.decon_plans.contains_key(decon) {
return Ok(());
}
let mut leaves: Vec<UnfoldLeaf> = Vec::new();
let mut visited: HashSet<TypeKey> = HashSet::new();
visited.insert(source.key());
flatten(
acc,
registry,
records,
source,
&[],
&[],
true,
false,
&mut visited,
&mut leaves,
&mut Vec::new(),
)?;
require_unique_leaf_names(source, &leaves)?;
registry.decon_plans.insert(
decon.clone(),
DeconSpec {
source: source.clone(),
leaves,
},
);
Ok(())
}
fn resolve_deconstructor(
acc: &Deconstructors,
source_key: &TypeKey,
ed: &OutputDecl,
) -> Result<(Vec<DeconRecord>, DeconId), UnfoldError> {
match &ed.sel {
DeconSel::Inline(records) => Ok((
records.clone(),
DeconId::PerFn(source_key.to_string(), ed.func.to_string()),
)),
DeconSel::TopLevel => find_deconstructor_by_type(acc, source_key)
.map(|d| (d.records.clone(), DeconId::Default(source_key.to_string())))
.ok_or_else(|| UnfoldError::NoDeconstructor {
func: ed.func.clone(),
target: source_key.to_string(),
}),
}
}
fn find_deconstructor_by_type<'a>(
acc: &'a Deconstructors,
type_key: &TypeKey,
) -> Option<&'a DeconstructorDecl> {
acc.deconstructors.iter().find(|c| c.target == *type_key)
}
#[allow(clippy::too_many_arguments)]
fn build_plan<M>(
acc: &Deconstructors,
registry: &Registry<M>,
ed: &OutputDecl,
by_ref: bool,
source: &prebindgen_flat::flat::TypeRef,
shape: UnfoldShape,
records: &[DeconRecord],
decon: DeconId,
) -> Result<UnfoldPlan, UnfoldError> {
let mut leaves: Vec<UnfoldLeaf> = Vec::new();
let mut visited: HashSet<TypeKey> = HashSet::new();
visited.insert(source.key());
let mut hoists: Vec<Hoist> = Vec::new();
flatten(
acc,
registry,
records,
source,
&[],
&[],
by_ref,
false,
&mut visited,
&mut leaves,
&mut hoists,
)?;
require_unique_leaf_names(source, &leaves)?;
require_root_identity_last(by_ref, source, &leaves)?;
Ok(UnfoldPlan {
source: source.clone(),
decon: Some(decon),
by_ref,
shape,
leaves,
element: None,
delivery: ed.delivery,
convert_out_ty: None,
fixed_builder: false,
hoists,
})
}
fn require_root_identity_last(
by_ref: bool,
source: &prebindgen_flat::flat::TypeRef,
leaves: &[UnfoldLeaf],
) -> Result<(), UnfoldError> {
if by_ref {
return Ok(());
}
let root_at = leaves.iter().position(|l| l.identity && l.path.is_empty());
let last_nested_at = leaves
.iter()
.rposition(|l| l.identity && !l.path.is_empty());
if let (Some(root), Some(nested)) = (root_at, last_nested_at) {
if root < nested {
return Err(UnfoldError::RootIdentityBeforeNested {
target: source.key().to_string(),
});
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn flatten<M>(
acc: &Deconstructors,
registry: &Registry<M>,
records: &[DeconRecord],
source: &prebindgen_flat::flat::TypeRef,
path_prefix: &[PathStep],
name_prefix: &[String],
by_ref: bool,
nullable: bool,
visited: &mut HashSet<TypeKey>,
leaves: &mut Vec<UnfoldLeaf>,
hoists: &mut Vec<Hoist>,
) -> Result<(), UnfoldError> {
let source_key = source.key();
let seg_name = |name: &str| -> Vec<String> {
let mut v = name_prefix.to_vec();
v.push(name.to_string());
v
};
let mut seen_identity = false;
for rec in records {
match rec {
DeconRecord::Identity => {
if seen_identity {
return Err(UnfoldError::MultipleIdentity {
target: source_key.to_string(),
});
}
seen_identity = true;
let out_ty = if place_is_owned(hoists, path_prefix, by_ref) {
source.clone()
} else {
source.borrowed()
};
leaves.push(UnfoldLeaf {
name: if path_prefix.is_empty() {
"handle".to_string()
} else {
name_prefix.join("__")
},
path: path_prefix.to_vec(),
out_ty,
identity: true,
nullable,
source: LeafSource::Accessor,
group: None,
});
}
DeconRecord::Fields {
func,
consuming,
fields,
} => {
let consuming = *consuming;
accessor_signature(registry, func, &source.key())?;
if consuming != accessor_consumes(registry, func) {
return Err(UnfoldError::Unsupported {
func: func.clone(),
reason: if consuming {
"declared as a CONSUMING value form (`.fields_self_into(..)`) but the \
accessor borrows its receiver — declare it with `.fields(..)`, or \
name the by-value accessor"
} else {
"declared as a BORROWING value form (`.fields(..)`) but the accessor \
takes its receiver by value — declare it with `.fields_self_into(..)`, or \
name the `&Self` accessor"
},
});
}
let mut root_path = path_prefix.to_vec();
root_path.push(PathStep::call(func.clone(), false, false));
if root_path.iter().any(PathStep::is_optional)
&& hoists.iter().any(|h| {
h.prefix.len() < root_path.len() && root_path.starts_with(&h.prefix)
})
{
return Err(UnfoldError::Unsupported {
func: func.clone(),
reason: "a value form nested under another one that is reached through \
`Option` — conditional hoists do not nest",
});
}
if consuming && records.len() > 1 {
return Err(UnfoldError::Unsupported {
func: func.clone(),
reason: "a consuming value form must be the only record of its \
declaration — it moves the value, so `.field_self()` or \
a sibling `.field()` would read a moved value",
});
}
hoists.push(Hoist {
prefix: root_path.clone(),
consuming,
});
for fr in fields {
let (opt, core) = match fr.ty.optional_inner() {
Some(inner) => (true, inner),
None => (false, &fr.ty),
};
let child_ty = core.borrow_target().unwrap_or(core);
let child_key = child_ty.key();
let child_records = match &fr.decon {
FieldDecon::Records(recs) => Some(recs.clone()),
FieldDecon::Leaves(_) => None,
FieldDecon::Default => match find_deconstructor_by_type(acc, &child_key) {
Some(child_decl) if !visited.contains(&child_key) => {
Some(child_decl.records.clone())
}
Some(_) => {
return Err(UnfoldError::Cycle {
target: child_key.to_string(),
});
}
None => None,
},
};
let decomposed =
child_records.is_some() || matches!(fr.decon, FieldDecon::Leaves(_));
let mut field_path = root_path.clone();
let (last, lead) = fr
.members
.split_last()
.expect("a field record addresses at least one member");
field_path.extend(lead.iter().map(|m| PathStep::field(m.clone(), false)));
field_path.push(PathStep::field(last.clone(), opt && decomposed));
if let FieldDecon::Leaves(built) = &fr.decon {
for l in built {
let mut path = field_path.clone();
path.extend(l.path.iter().cloned());
let mut name = seg_name(&fr.name);
name.push(l.name.clone());
leaves.push(UnfoldLeaf {
name: name.join("__"),
path,
nullable: l.nullable || nullable || opt,
..l.clone()
});
}
continue;
}
if let Some(child_records) = child_records {
visited.insert(child_key.clone());
flatten(
acc,
registry,
&child_records,
child_ty,
&field_path,
&seg_name(&fr.name),
by_ref,
nullable || opt,
visited,
leaves,
hoists,
)?;
visited.remove(&child_key);
} else {
leaves.push(UnfoldLeaf {
name: seg_name(&fr.name).join("__"),
path: field_path,
out_ty: fr.ty.clone(),
identity: false,
nullable,
source: LeafSource::Field,
group: None,
});
}
}
}
DeconRecord::Acc { name, .. } | DeconRecord::LocalAcc { name, .. } => {
let (func, local) = match rec {
DeconRecord::Acc { func, .. } => (func.clone(), false),
DeconRecord::LocalAcc { path, .. } => (DeconRecord::local_ident(path), true),
DeconRecord::Identity | DeconRecord::Fields { .. } => unreachable!(),
};
let ret = accessor_signature(registry, &func, &source.key())?;
let after_opt = ret.optional_inner();
let opt = after_opt.is_some();
let core = after_opt.unwrap_or(&ret);
let (core_by_ref, child_ty) = peel_borrow(core);
let child_key = child_ty.key();
let splice = match find_deconstructor_by_type(acc, &child_key) {
Some(child_decl) if !visited.contains(&child_key) => Some(child_decl),
Some(_) if local => None,
Some(_) => {
return Err(UnfoldError::Cycle {
target: child_key.to_string(),
});
}
None => None,
};
if let Some(child_decl) = splice {
visited.insert(child_key.clone());
let child_records = child_decl.records.clone();
let mut child_path = path_prefix.to_vec();
child_path.push(PathStep::call(func.clone(), opt, !core_by_ref));
flatten(
acc,
registry,
&child_records,
child_ty,
&child_path,
&seg_name(name),
by_ref,
nullable || opt,
visited,
leaves,
hoists,
)?;
visited.remove(&child_key);
} else {
let cond_handle = local && opt && core_by_ref;
if cond_handle {
if seen_identity {
return Err(UnfoldError::MultipleIdentity {
target: source_key.to_string(),
});
}
seen_identity = true;
}
let (out_ty, nullable, identity) = if cond_handle {
(core.clone(), true, true)
} else {
(ret.clone(), nullable, false)
};
let mut path = path_prefix.to_vec();
path.push(PathStep::call(func.clone(), opt, !core_by_ref));
leaves.push(UnfoldLeaf {
name: seg_name(name).join("__"),
path,
out_ty,
identity,
nullable,
source: LeafSource::Accessor,
group: None,
});
}
}
}
}
Ok(())
}
fn require_unique_leaf_names(
source: &prebindgen_flat::flat::TypeRef,
leaves: &[UnfoldLeaf],
) -> Result<(), UnfoldError> {
let mut seen: HashSet<&str> = HashSet::new();
for l in leaves {
if !seen.insert(l.name.as_str()) {
return Err(UnfoldError::DuplicateLeafName {
target: source.key().to_string(),
name: l.name.clone(),
});
}
}
Ok(())
}
pub fn dedup_names(names: &mut [String]) {
let mut seen: HashSet<String> = HashSet::new();
for n in names.iter_mut() {
if !seen.insert(n.clone()) {
let mut k = 2;
while !seen.insert(format!("{n}{k}")) {
k += 1;
}
*n = format!("{n}{k}");
}
}
}
fn accessor_signature<M>(
registry: &Registry<M>,
func: &syn::Ident,
expected: &TypeKey,
) -> Result<prebindgen_flat::flat::TypeRef, UnfoldError> {
let f = registry
.flat()
.function(&func)
.ok_or_else(|| UnfoldError::UnknownAccessor(func.clone()))?;
let first = f
.params
.first()
.ok_or_else(|| UnfoldError::UnknownAccessor(func.clone()))?;
let takes = match first.ty.borrow_target() {
Some(inner) => inner.key(),
None => first.ty.key(),
};
check_declared_target(func, &takes, expected)?;
Ok(f.ret.clone())
}
fn place_is_owned(hoists: &[Hoist], path_prefix: &[PathStep], by_ref: bool) -> bool {
if path_prefix.is_empty() {
return !by_ref;
}
hoists
.iter()
.filter(|h| h.prefix.len() <= path_prefix.len() && path_prefix.starts_with(&h.prefix))
.max_by_key(|h| h.prefix.len())
.is_some_and(|h| h.consuming && steps_are_movable(&path_prefix[h.prefix.len()..]))
}
fn accessor_consumes<M>(registry: &Registry<M>, func: &syn::Ident) -> bool {
registry
.flat()
.function(&func)
.and_then(|f| f.params.first())
.is_some_and(|p| p.ty.borrow_target().is_none())
}
impl From<crate::declared_target::TargetMismatch> for UnfoldError {
fn from(m: crate::declared_target::TargetMismatch) -> Self {
UnfoldError::AccessorTargetMismatch {
accessor: m.func,
takes: m.actual,
expected: m.expected,
}
}
}
#[cfg(test)]
mod tests;