use kotlin_codegen::{
KtClass, KtCode, KtCompanion, KtCtorParam, KtEnumEntry, KtFun, KtParam, KtProperty, KtType,
KtVis,
};
use prebindgen_registry::Conversions;
use super::*;
pub(crate) fn build_enum_class(
class_name: &str,
item_enum: &prebindgen_registry::flat::Enum,
) -> KtClass {
let entries: Vec<KtEnumEntry> = item_enum
.discriminant_values()
.unwrap_or_else(|name| {
panic!(
"enum `{}` variant `{name}` has a non-literal discriminant; use a literal \
integer value (e.g. `= 1`) or an implicit discriminant",
item_enum.name
)
})
.into_iter()
.map(|(ident, value)| {
KtEnumEntry::with_args(
mangle_kotlin_ident(&crate::util::camel_to_screaming_snake(&ident.to_string())),
value.to_string(),
)
})
.collect();
let framework_line = format!(
"JVM-side surface for the native Rust `{}` enum.",
item_enum.name
);
let enum_kdoc = item_enum
.docs()
.map(|d| format!("{d}\n\n{framework_line}"))
.unwrap_or(framework_line);
let mut class = KtClass::enum_(class_name)
.vis(KtVis::Public)
.kdoc(enum_kdoc)
.ctor_param(
KtCtorParam::new("value", KtType::int())
.val()
.vis(KtVis::Public),
);
for e in entries {
class = class.entry(e);
}
class.companion(
KtCompanion::new().vis(KtVis::Public).member(
KtFun::new("fromInt")
.vis(KtVis::Public)
.annotation("JvmStatic")
.param(KtParam::new("value", KtType::int()))
.returns(KtType::cls(class_name))
.expr_body(KtCode::new().line("entries.first { it.value == value }")),
),
)
}
pub(crate) fn build_data_class(
ext: &Declarations,
class_name: &str,
item_struct: &prebindgen_registry::flat::Struct,
registry: &Registry<KotlinMeta>,
) -> KtClass {
let fields_named = &item_struct.fields;
let plan = build_struct_plan(ext, registry, item_struct, 0).unwrap_or_else(|| {
panic!(
"data class `{}`: could not classify every field for the fromParts bridge. Each \
field needs a resolved OUTPUT converter (that direction declares the slot the \
encoder fills) AND the Kotlin metadata that converter carries — a `kotlin_name`, \
or a registered class for a projection leaf",
item_struct.name
)
});
let mut ctor_params: Vec<KtCtorParam> = Vec::new();
let mut equality_props: Vec<(String, KtType)> = Vec::new();
let mut destructible_fields: Vec<(String, crate::jni::FoldStrategy)> = Vec::new();
for (field, pf) in fields_named.iter().zip(&plan.fields) {
let field_ident = field.name.as_ref().unwrap_or_else(|| {
panic!(
"render_data_class_source: struct `{}` has an unnamed field in named-fields context",
item_struct.name
)
});
let kotlin_field_name = kotlin_property_name(field_ident);
let owner = format!("{}.{}", item_struct.name, field_ident);
if !matches!(pf.kind, PlanFieldKind::Projection { .. }) {
if let Some(proj) = registry
.input_entry(&field.ty)
.and_then(|e| e.metadata.projection.clone())
{
panic!(
"data class field `{owner}`: the INPUT converter projects this field as `{:?}` \
but the OUTPUT converter does not, so the property type and its own \
`fromParts` parameter would disagree. The output direction declares the \
bridge, so give the field an output converter with the same projection — or \
drop the input-side projection",
proj.kind
);
}
}
let property_type = pf.kind.property_type(&owner);
equality_props.push((kotlin_field_name.clone(), property_type.clone()));
ctor_params.push(KtCtorParam::new(&kotlin_field_name, property_type).val());
if let Some(strategy) = pf.kind.destructible() {
destructible_fields.push((kotlin_field_name, strategy));
}
}
let mut factory_imports: BTreeSet<String> = BTreeSet::new();
let (factory_params, factory_reconstruct) = flatten_struct_factory(
ext,
registry,
item_struct,
"",
class_name,
&mut factory_imports,
0,
)
.unwrap_or_else(|| {
panic!("render_data_class_source: could not build fromParts factory for `{class_name}`")
});
let mut ctor_params = ctor_params.into_iter();
let first = ctor_params.next().unwrap_or_else(|| {
panic!(
"data class `{class_name}`: Rust struct `{}` has no fields — a Kotlin `data class` \
must declare at least one constructor property",
item_struct.name
)
});
let mut class = KtClass::data(class_name, first).vis(KtVis::Public);
if let Some(doc) = item_struct.docs() {
class = class.kdoc(doc);
}
for p in ctor_params {
class = class.ctor_param(p);
}
for m in crate::jni::equality::content_equality_members(class_name, &equality_props)
.into_iter()
.flatten()
{
class = class.member(m);
}
if !destructible_fields.is_empty() {
class = class.implements(KtType::cls("AutoCloseable"));
let mut body = KtCode::new();
for (fname, strategy) in &destructible_fields {
body = body.line(render_handle_close(strategy, fname));
}
class = class.member(KtFun::new("close").modifier("override").body(body));
}
let mut factory_body = KtCode::new().line(factory_reconstruct);
for fqn in factory_imports {
factory_body = factory_body.import(fqn);
}
let mut factory = KtFun::new("fromParts")
.vis(KtVis::Public)
.annotation("JvmStatic")
.returns(KtType::cls(class_name))
.expr_body(factory_body);
for (name, ty) in &factory_params {
factory = factory.param(KtParam::new(name, ty.clone()));
}
class = class.companion(KtCompanion::new().vis(KtVis::Public).member(factory));
class
}
pub(crate) fn build_typed_handle(
ext: &Declarations,
registry: &Registry<KotlinMeta>,
class_name: &str,
rust_doc_name: &str,
key: &TypeKey,
imports: &mut BTreeSet<String>,
) -> KtClass {
let class_fqn = ext
.types
.get(key)
.and_then(|cfg| cfg.name_spec.as_ref())
.map(|spec| ext.fqn_of(spec))
.unwrap_or_else(|| class_name.to_string());
let (class_package, final_class_name) = class_fqn
.rsplit_once('.')
.unwrap_or(("", class_fqn.as_str()));
let free_extern = ext.mangle_method(class_package, final_class_name, "freePtr");
let gc_managed = ext
.types
.get(key)
.and_then(|cfg| cfg.opaque())
.is_some_and(|o| o.gc_managed);
let base_short = if gc_managed {
"GcNativeHandle"
} else {
"NativeHandle"
};
let base_fqn = if ext.package.is_empty() {
base_short.to_string()
} else {
format!("{}.{base_short}", ext.package)
};
let members = ext.class_members.get(key).map(Vec::as_slice).unwrap_or(&[]);
if !members.is_empty() && !ext.package.is_empty() {
imports.insert(format!("{}.{}", ext.package, ext.jni_native_class_name()));
}
let mut companion = KtCompanion::new().vis(KtVis::Public).member(
KtFun::new(free_extern.clone())
.annotation("JvmStatic")
.external()
.param(KtParam::new("ptr", KtType::long())),
);
for m in members.iter().filter(|m| m.kind == MemberKind::Constructor) {
if let Some(item_fn) = registry.flat().function(&m.rust_ident) {
if let Some(f) = render_wrapper_fn(
ext,
item_fn,
registry,
Some(ext.effective_method_name(key, m).as_str()),
None,
) {
for ov in render_param_overloads(ext, item_fn, registry, &f) {
companion = companion.member(ov);
}
companion = companion.member(f);
}
}
}
let framework_line = format!("Typed handle for a native Zenoh `{rust_doc_name}`.");
let class_kdoc = source_item_doc(registry, key)
.map(|d| format!("{d}\n\n{framework_line}"))
.unwrap_or(framework_line);
let mut class = KtClass::class_(class_name)
.vis(KtVis::Public)
.kdoc(class_kdoc)
.ctor_param(KtCtorParam::new("initialPtr", KtType::long()));
class = if gc_managed {
if !ext.package.is_empty() {
imports.insert(format!("{}.releaseCell", ext.package));
imports.insert(format!("{}.registerGcHandle", ext.package));
}
class
.extends(KtType::cls(base_fqn), Some("initialPtr"))
.member(
KtProperty::val("__cleanable")
.vis(KtVis::Private)
.initializer(format!("registerGcHandle(this) {{ {free_extern}(it) }}")),
)
.member(
KtFun::new("close")
.annotation("Synchronized")
.modifier("override")
.body(
KtCode::new()
.line("val p = releaseCell(cell)")
.line(format!("if (p != 0L) {free_extern}(p)"))
.line("__cleanable?.clean()"),
),
)
.member(
KtFun::new("take")
.vis(KtVis::Public)
.annotation("Synchronized")
.returns(KtType::cls(class_name))
.body(
KtCode::new()
.line("val p = releaseCell(cell)")
.line("__cleanable?.clean()")
.line(format!(
"return {class_name}(if (p != 0L) p else cell.get())"
)),
),
)
} else {
class
.extends(KtType::cls(base_fqn), Some("initialPtr"))
.member(
KtFun::new("close")
.annotation("Synchronized")
.modifier("override")
.body(
KtCode::new()
.line("val p = ptr")
.blk("if (p != 0L && (p and 1L) == 0L) {", |c| {
c.line("ptr = p or 1L").line(format!("{free_extern}(p)"))
}),
),
)
.member(
KtFun::new("take")
.vis(KtVis::Public)
.annotation("Synchronized")
.returns(KtType::cls(class_name))
.body(
KtCode::new()
.line("val p = ptr")
.line("ptr = p or 1L")
.line(format!("return {class_name}(p)")),
),
)
};
let mut class = class.companion(companion);
for m in members.iter().filter(|m| m.kind == MemberKind::Method) {
if let Some(item_fn) = registry.flat().function(&m.rust_ident) {
if let Some(f) = render_wrapper_fn(
ext,
item_fn,
registry,
Some(ext.effective_method_name(key, m).as_str()),
Some(key),
) {
for ov in render_param_overloads(ext, item_fn, registry, &f) {
class = class.member(ov);
}
class = class.member(f);
}
}
}
class
}
pub(crate) fn is_iterable_fold(shape: &prebindgen_registry::unfold::UnfoldShape) -> bool {
shape.has_iterable_layer()
}
pub(crate) fn render_extern_decl(
ext: &Declarations,
f: &prebindgen_registry::flat::Function,
registry: &Registry<KotlinMeta>,
) -> Option<KtFun> {
let fplan = ext.fn_plan(registry, f).ok()?;
let jni_call = &fplan.jni_method;
let mut params: Vec<KtParam> = Vec::new();
for leaf in fplan.leaves() {
let name = leaf.kt_name.clone();
match &leaf.kind {
InputKind::FlattenStruct(plan) => {
for l in &plan.leaves {
params.push(KtParam::new(
l.kt_name.clone(),
KtType::cls(l.kt_wire_ty.clone()),
));
}
}
InputKind::OptionScalar(sp) => {
params.push(KtParam::new(sp.present_kt.clone(), KtType::boolean()));
params.push(KtParam::new(
sp.value_kt.clone(),
KtType::cls(sp.value_kt_type.clone()),
));
}
InputKind::VecBuild { .. } => {
params.push(KtParam::new(name, KtType::long()));
}
InputKind::Handle { .. } => {
params.push(KtParam::new(name, KtType::long()));
}
InputKind::Callback { .. } | InputKind::Unsigned64 { .. } | InputKind::Plain => {
let ty = if leaf.as_enum_value {
KtType::int()
} else {
leaf.kt_meta.clone()?
};
let niche_primitive =
matches!(&leaf.kind, InputKind::Unsigned64 { niche: Some(_) });
let ty = if leaf.optional && !niche_primitive {
ty.nullable()
} else {
ty
};
params.push(KtParam::new(name, ty));
}
}
}
if let FnOutputPlan::Unfold(u) = &fplan.output {
if u.iterable_fold {
params.push(KtParam::new("acc", KtType::any().nullable()));
params.push(KtParam::new("fold", KtType::any()));
} else {
params.push(KtParam::new("build", KtType::any()));
}
}
params.push(KtParam::new("errorSink", KtType::any()));
if registry.error_plans().contains_key(&f.name) {
params.push(KtParam::new("domainSink", KtType::any()));
}
let wire_return: Option<KtType> = match &fplan.output {
FnOutputPlan::Unfold(_) => Some(KtType::any().nullable()),
FnOutputPlan::Value(v) => {
let (kt_return, projection) = render_return_surface(&v.surface)?;
match &projection {
Some(p) => Some(projection_wire_return(p)),
None if v.is_enum => Some(KtType::int()),
None if v.is_option_enum => Some(KtType::int().nullable()),
None => kt_return,
}
}
};
let mut fun = KtFun::new(jni_call).external();
for p in params {
fun = fun.param(p);
}
if let Some(rt) = wire_return {
fun = fun.returns(rt);
}
Some(fun)
}
struct Param {
kt_name: String,
kt_type: KtType,
mode: ParamMode,
as_enum_value: bool,
}
enum ParamMode {
Borrow, Consume, BorrowNullable,
ConsumeNullable,
PassThrough,
Unsigned64 {
niche: Option<String>,
},
FlattenStruct {
accesses: Vec<String>,
handles: Vec<Opaque>,
},
VecBuild {
base: String,
elem_accesses: Vec<String>,
},
Callback {
call_arg: String,
},
OptionScalar {
present_expr: String,
value_expr: String,
},
}
#[derive(Clone)]
struct Opaque {
name: String,
target: String,
consume_null: Option<String>,
nullable: bool,
domain: Option<String>,
}
pub(crate) fn peel_receiver_key(ty: &prebindgen_registry::flat::TypeRef) -> TypeKey {
let core = ty.borrow_target().unwrap_or(ty);
match core.optional_inner() {
Some(inner) => inner.borrow_target().unwrap_or(inner).key(),
None => core.key(),
}
}
pub(crate) struct WrapperSurface {
pub fun: KtFun,
params: Vec<Param>,
out: OutputPlan,
sink: ErrorSink,
jni_call: String,
body_imports: BTreeSet<String>,
}
pub(crate) fn build_wrapper_surface(
ext: &Declarations,
f: &prebindgen_registry::flat::Function,
registry: &Registry<KotlinMeta>,
kotlin_name_override: Option<&str>,
receiver_key: Option<&TypeKey>,
) -> Option<WrapperSurface> {
let mut body_imports = BTreeSet::new();
let fplan = ext.fn_plan(registry, f).ok()?;
let kt_name = match kotlin_name_override {
Some(n) => n.to_string(),
None => kt_snake_to_camel(&f.name.to_string()),
};
let jni_call = fplan.jni_method.clone();
let (params, receiver_idx) =
classify_params(ext, &fplan, registry, &mut body_imports, receiver_key)?;
let out = classify_output(ext, f, &fplan, registry, &mut body_imports)?;
let r_ty = out.kt_return.clone().unwrap_or_else(KtType::unit);
let sink = error_sink_parts(f, &fplan, registry, &mut body_imports, &r_ty)?;
let mut fun = KtFun::new(&kt_name).vis(KtVis::Public);
if let Some(g) = &out.generic {
fun = fun.generic(g);
}
for (i, p) in params.iter().enumerate() {
if Some(i) == receiver_idx {
continue;
}
fun = fun.param(KtParam::new(&p.kt_name, p.kt_type.clone()));
}
let mut err_params = vec![KtParam::new(&sink.binding_param, sink.binding_type.clone())];
if let Some(d) = &sink.domain {
err_params.push(KtParam::new("onError", d.onerr_type.clone()));
}
if let Some((bp_name, bp_ty)) = &out.builder_param {
if let Some((lead_name, lead_ty)) = &out.builder_lead {
fun = fun.param(KtParam::new(lead_name, lead_ty.clone()));
}
for ep in err_params {
fun = fun.param(ep);
}
fun = fun.param(KtParam::new(bp_name, bp_ty.clone()));
} else {
for ep in err_params {
fun = fun.param(ep);
}
}
if out.cast_return {
fun = fun.annotation("Suppress(\"UNCHECKED_CAST\")");
}
if let Some(rt) = &out.kt_return {
fun = fun.returns(rt.clone());
}
Some(WrapperSurface {
fun,
params,
out,
sink,
jni_call,
body_imports,
})
}
pub(crate) fn render_wrapper_fn(
ext: &Declarations,
f: &prebindgen_registry::flat::Function,
registry: &Registry<KotlinMeta>,
kotlin_name_override: Option<&str>,
receiver_key: Option<&TypeKey>,
) -> Option<KtFun> {
let surface = build_wrapper_surface(ext, f, registry, kotlin_name_override, receiver_key)?;
let WrapperSurface {
mut fun,
params,
out,
sink,
jni_call,
mut body_imports,
} = surface;
if let Some(doc) = wrapper_kdoc(f, registry) {
fun = fun.kdoc(doc);
}
let opaques = collect_opaques(¶ms);
let is_unit = fun.ret.is_none();
let body_expr = build_native_call(ext, &jni_call, ¶ms, &out, &sink);
let return_mode = if is_unit {
BodyReturn::Unit
} else {
BodyReturn::Value(build_success_return(ext, &out, "__ret"))
};
let mut body = render_body(
ext,
¶ms,
&opaques,
&sink,
&body_expr,
&return_mode,
&mut body_imports,
);
for fqn in body_imports {
body = body.import(fqn);
}
Some(fun.body(body))
}
pub(crate) fn render_const_val(
ext: &Declarations,
package: &str,
c: &prebindgen_registry::flat::Constant,
registry: &Registry<KotlinMeta>,
imports: &mut BTreeSet<String>,
kotlin_name_override: Option<&str>,
) -> Option<(KtFun, KtProperty)> {
let getter = const_getter_fn(c);
let default = kt_snake_to_camel(&getter.name.to_string());
let helper_name = ext.mangle_fun(package, &default);
let helper = render_wrapper_fn(ext, &getter, registry, Some(&helper_name), None)?;
let val_name = kotlin_name_override
.map(str::to_string)
.unwrap_or_else(|| c.name.to_string());
let framework_line = format!(
"Mirrors the Rust `#[prebindgen]` const `{}` (read lazily, once, through \
the generated JNI getter on first use).",
c.name
);
let kdoc = c
.docs()
.map(|d| format!("{d}\n\n{framework_line}"))
.unwrap_or(framework_line);
render_val_over_helper(ext, registry, helper, val_name, kdoc, imports)
}
pub(crate) fn render_constant_fn_val(
ext: &Declarations,
package: &str,
f: &prebindgen_registry::flat::Function,
registry: &Registry<KotlinMeta>,
imports: &mut BTreeSet<String>,
kotlin_name_override: Option<&str>,
) -> Option<(KtFun, KtProperty)> {
let default = kt_snake_to_camel(&f.name.to_string());
let helper_name = ext.mangle_fun(package, &default);
let helper = render_wrapper_fn(ext, f, registry, Some(&helper_name), None)?;
let val_name = kotlin_name_override
.map(str::to_string)
.unwrap_or_else(|| f.name.to_string());
let framework_line = format!(
"Mirrors the Rust `#[prebindgen]` fn `{}()` (evaluated lazily, once, \
through the generated JNI wrapper on first use).",
f.name
);
let kdoc = f
.docs()
.map(|d| format!("{d}\n\n{framework_line}"))
.unwrap_or(framework_line);
render_val_over_helper(ext, registry, helper, val_name, kdoc, imports)
}
pub(crate) fn render_const_expr_val(
ext: &Declarations,
package: &str,
decl: &crate::jni::decl::ConstExprDecl,
registry: &Registry<KotlinMeta>,
imports: &mut BTreeSet<String>,
) -> Option<(KtFun, KtProperty)> {
let getter = const_expr_getter_fn(&decl.kotlin_name, &decl.ty, registry);
let default = kt_snake_to_camel(&getter.name.to_string());
let helper_name = ext.mangle_fun(package, &default);
let helper = render_wrapper_fn(ext, &getter, registry, Some(&helper_name), None)?;
let expr = decl.expr.to_token_stream();
let kdoc = format!(
"Binding-defined constant: `{expr}` (evaluated lazily, once, through \
the generated JNI getter on first use)."
);
render_val_over_helper(
ext,
registry,
helper,
decl.kotlin_name.clone(),
kdoc,
imports,
)
}
fn render_val_over_helper(
ext: &Declarations,
registry: &Registry<KotlinMeta>,
mut helper: KtFun,
val_name: String,
kdoc: String,
imports: &mut BTreeSet<String>,
) -> Option<(KtFun, KtProperty)> {
helper.vis = KtVis::Private;
let helper_name = helper.name.clone();
let val_ty = helper.ret.clone()?;
let spec = ext.iface_spec(registry, &SpecKey::JniErrorHandler)?;
imports.insert(spec.fqn());
let init = format!(
"{helper_name}(JniErrorHandler {{ je -> error(je ?: \"const {val_name}: JNI getter failed\") }})"
);
let prop = KtProperty::val(&val_name)
.ty(val_ty)
.vis(KtVis::Public)
.delegate(format!("lazy {{ {init} }}"))
.kdoc(kdoc);
Some((helper, prop))
}
struct OutputPlan {
kt_return: Option<KtType>,
projection: Option<Projection>,
builder_param: Option<(String, KtType)>,
builder_lead: Option<(String, KtType)>,
generic: Option<String>,
unfold_call_args: Vec<String>,
cast_return: bool,
is_enum_return: bool,
is_option_enum_return: bool,
}
struct ErrorSink {
binding_param: String,
binding_type: KtType,
binding_capture_short: String,
binding_call_arg: String,
domain: Option<DomainSink>,
}
struct DomainSink {
onerr_type: KtType,
capture_short: String,
call_args: String,
}
fn classify_params(
ext: &Declarations,
fplan: &JniFunctionPlan,
registry: &Registry<KotlinMeta>,
imports: &mut BTreeSet<String>,
receiver_key: Option<&TypeKey>,
) -> Option<(Vec<Param>, Option<usize>)> {
let mut receiver_idx: Option<usize> = None;
let mut params: Vec<Param> = Vec::new();
for leaf in fplan.leaves() {
let mut name = leaf.kt_name.clone();
if receiver_idx.is_none() {
if let Some(rk) = receiver_key {
if &peel_receiver_key(&leaf.reading) == rk {
receiver_idx = Some(params.len());
name = "this".to_string();
}
}
}
if let InputKind::Callback { iface, .. } = &leaf.kind {
let spec = iface.as_deref()?;
let kt_type = spec.kt_ref(vec![]);
let call_arg = if spec.needs_raw() {
imports.insert(format!("{}.asRaw", spec.package));
format!("{name}.asRaw()")
} else {
name.clone()
};
params.push(Param {
kt_name: name.clone(),
kt_type,
mode: ParamMode::Callback { call_arg },
as_enum_value: false,
});
continue;
}
let kt_type_raw = leaf.kt_public.clone()?;
let mode = match &leaf.kind {
InputKind::VecBuild { elem, .. } => {
let h = crate::jni::vec_build_helpers(ext, registry, elem)
.expect("vec_build_elem Some ⇒ vec_build_helpers Some");
let elem_accesses = h
.plan
.leaves
.iter()
.filter(|l| !l.is_present_flag)
.map(|l| l.kt_access("__e"))
.collect();
ParamMode::VecBuild {
base: h.base,
elem_accesses,
}
}
InputKind::OptionScalar(sp) => {
let present_expr = format!("{name} != null");
let value_expr = if sp.is_enum {
format!("{name}?.value ?: {}", sp.value_kt_zero)
} else {
format!("{name} ?: {}", sp.value_kt_zero)
};
ParamMode::OptionScalar {
present_expr,
value_expr,
}
}
InputKind::FlattenStruct(plan) => {
let handles = plan
.leaves
.iter()
.filter_map(|leaf| {
let target = leaf.kt_handle_target(&name)?;
let consume_null = if leaf.handle_nullable {
format!("{target}?.markConsumed()")
} else {
format!("{target}.markConsumed()")
};
Some(Opaque {
name: leaf.kt_name.clone(),
target,
consume_null: Some(consume_null),
nullable: leaf.handle_nullable,
domain: None,
})
})
.collect();
ParamMode::FlattenStruct {
accesses: plan.leaves.iter().map(|l| l.kt_call_arg(&name)).collect(),
handles,
}
}
InputKind::Handle { .. } => {
if leaf
.reading
.optional_inner()
.is_some_and(|i| i.borrow_target().is_some())
{
ParamMode::BorrowNullable
} else if leaf.reading.optional_inner().is_some() {
ParamMode::ConsumeNullable
} else if leaf.reading.borrow_target().is_some() {
ParamMode::Borrow
} else {
ParamMode::Consume
}
}
InputKind::Unsigned64 { niche } => ParamMode::Unsigned64 {
niche: niche.clone(),
},
InputKind::Plain => ParamMode::PassThrough,
InputKind::Callback { .. } => unreachable!("callback params handled above"),
};
let kt_type = if leaf.optional {
kt_type_raw.nullable()
} else {
kt_type_raw
};
params.push(Param {
kt_name: name,
kt_type,
mode,
as_enum_value: leaf.as_enum_value,
});
}
Some((params, receiver_idx))
}
fn classify_output(
ext: &Declarations,
f: &prebindgen_registry::flat::Function,
fplan: &JniFunctionPlan,
registry: &Registry<KotlinMeta>,
imports: &mut BTreeSet<String>,
) -> Option<OutputPlan> {
let unfold = registry.unfold_plans().get(&f.name);
let mut builder_param: Option<(String, KtType)> = None;
let mut builder_lead: Option<(String, KtType)> = None;
let mut generic: Option<String> = None;
let mut unfold_call_args: Vec<String> = Vec::new();
let (kt_return, projection) = if let FnOutputPlan::Value(v) = &fplan.output {
render_return_surface(&v.surface)?
} else if let (
FnOutputPlan::Unfold(
u @ UnfoldOutputPlan {
fixed_builder: true,
..
},
),
Some(plan),
) = (&fplan.output, unfold)
{
let class_ty = if u.whole_element {
let spec = u.iface.as_deref()?;
spec.params[1].typed.clone()
} else {
let class_fqn = ext.kotlin_fqn(&plan.source.key()).map(|s| s.to_string())?;
KtType::cls(class_fqn)
};
let class_short = kt_type_short(&class_ty);
if u.iterable_fold {
let spec = u.iface.as_deref()?;
let holder = spec.singleton_holder_name();
let field = crate::jni::SINGLETON_FIELD;
imports.insert(spec.singleton_holder_fqn());
unfold_call_args.push(format!("ArrayList<{class_short}>()"));
unfold_call_args.push(format!("{holder}.{field}"));
let list_ty = KtType::generic("List", [class_ty]);
let kt = if u.optional {
list_ty.nullable()
} else {
list_ty
};
(Some(kt), None)
} else {
let spec = u.iface.as_deref()?;
let singleton = format!("__{}", spec.raw_name());
imports.insert(format!("{}.{singleton}", spec.package));
unfold_call_args.push(singleton);
let kt = if u.optional {
class_ty.nullable()
} else {
class_ty
};
(Some(kt), None)
}
} else if let (FnOutputPlan::Unfold(u), Some(_)) = (&fplan.output, unfold) {
generic = u.generic.map(str::to_string);
if u.generic == Some("A") {
let spec = u.iface.as_deref()?;
builder_lead = Some(("acc".to_string(), KtType::var_("A")));
builder_param = Some(("fold".to_string(), spec.kt_ref(vec![KtType::var_("A")])));
unfold_call_args.push("acc".to_string());
if spec.needs_raw() {
imports.insert(format!("{}.asRaw", spec.package));
unfold_call_args.push("fold.asRaw()".to_string());
} else {
unfold_call_args.push("fold".to_string());
}
let kt = if u.optional {
KtType::var_("A").nullable()
} else {
KtType::var_("A")
};
(Some(kt), None)
} else {
let spec = u.iface.as_deref()?;
builder_param = Some(("build".to_string(), spec.kt_ref(vec![KtType::var_r()])));
if spec.needs_raw() {
imports.insert(format!("{}.asRaw", spec.package));
unfold_call_args.push("build.asRaw()".to_string());
} else {
unfold_call_args.push("build".to_string());
}
let kt = if u.optional {
KtType::var_r().nullable()
} else {
KtType::var_r()
};
(Some(kt), None)
}
} else {
unreachable!("FnOutputPlan is either Value or Unfold-with-plan")
};
let (is_enum_return, is_option_enum_return) = match &fplan.output {
FnOutputPlan::Value(v) if !v.is_convert => (v.is_enum, v.is_option_enum),
_ => (false, false),
};
Some(OutputPlan {
kt_return,
projection,
builder_param,
builder_lead,
generic,
unfold_call_args,
cast_return: matches!(&fplan.output, FnOutputPlan::Unfold(_)),
is_enum_return,
is_option_enum_return,
})
}
fn build_native_call(
ext: &Declarations,
jni_call: &str,
params: &[Param],
out: &OutputPlan,
sink: &ErrorSink,
) -> String {
let mut args: Vec<String> = Vec::with_capacity(params.len());
for p in params.iter() {
if let ParamMode::FlattenStruct { accesses, .. } = &p.mode {
args.extend(accesses.iter().cloned());
continue;
}
if let ParamMode::VecBuild { .. } = &p.mode {
args.push(format!("__vec_{}", p.kt_name));
continue;
}
if let ParamMode::OptionScalar {
present_expr,
value_expr,
} = &p.mode
{
args.push(present_expr.clone());
args.push(value_expr.clone());
continue;
}
let arg = match &p.mode {
ParamMode::Borrow
| ParamMode::Consume
| ParamMode::BorrowNullable
| ParamMode::ConsumeNullable => format!("{}_ptr", p.kt_name),
ParamMode::Unsigned64 { niche } => {
if p.kt_type.is_nullable() {
match niche {
Some(niche) => format!("{}?.toLong() ?: {}", p.kt_name, niche),
None => format!("{}?.toLong()", p.kt_name),
}
} else {
format!("{}.toLong()", p.kt_name)
}
}
ParamMode::PassThrough => {
if p.as_enum_value {
if p.kt_type.is_nullable() {
format!("{}?.value", p.kt_name)
} else {
format!("{}.value", p.kt_name)
}
} else {
p.kt_name.clone()
}
}
ParamMode::Callback { call_arg } => call_arg.clone(),
ParamMode::FlattenStruct { .. } => {
unreachable!("FlattenStruct expanded before the single-arg match")
}
ParamMode::VecBuild { .. } => {
unreachable!("VecBuild expanded before the single-arg match")
}
ParamMode::OptionScalar { .. } => {
unreachable!("OptionScalar expanded before the single-arg match")
}
};
args.push(arg);
}
args.extend(out.unfold_call_args.iter().cloned());
args.push("__bcap".to_string());
if sink.domain.is_some() {
args.push("__dcap".to_string());
}
format!(
"{}.{jni_call}({})",
ext.jni_native_class_name(),
args.join(", ")
)
}
fn build_success_return(ext: &Declarations, out: &OutputPlan, raw: &str) -> String {
if let Some(p) = &out.projection {
let leaf_fqn = ext
.kotlin_fqn(&p.leaf_key)
.unwrap_or_else(|| p.leaf_key.to_string());
let short = leaf_fqn.rsplit('.').next().unwrap_or(&leaf_fqn).to_string();
let sentinel = projection_leaf_sentinel(p);
fold_projection_wrap(&p.strategy, raw, &p.kind, &short, sentinel.as_deref())
} else if out.is_enum_return {
let enum_kt = out
.kt_return
.as_ref()
.expect("enum return has a Kotlin type");
format!("{enum_kt}.fromInt({raw})")
} else if out.is_option_enum_return {
let enum_kt = out
.kt_return
.as_ref()
.expect("Option<enum> return has a Kotlin type")
.to_string();
let enum_kt = enum_kt.trim_end_matches('?');
format!("{raw}?.let {{ {enum_kt}.fromInt(it) }}")
} else if out.cast_return {
let cast_kt = out
.kt_return
.as_ref()
.expect("callback delivery returns R/A");
format!("{raw} as {}", kt_type_short(cast_kt))
} else {
raw.to_string()
}
}
fn collect_opaques(params: &[Param]) -> Vec<Opaque> {
params
.iter()
.flat_map(|p| {
let (target, consume_null, nullable) = match &p.mode {
ParamMode::Borrow => (p.kt_name.clone(), None, false),
ParamMode::Consume => (
p.kt_name.clone(),
Some(format!("{n}.markConsumed()", n = p.kt_name)),
false,
),
ParamMode::BorrowNullable => (p.kt_name.clone(), None, true),
ParamMode::ConsumeNullable => (
p.kt_name.clone(),
Some(format!("{n}?.markConsumed()", n = p.kt_name)),
true,
),
ParamMode::FlattenStruct { handles, .. } => return handles.clone(),
_ => return Vec::new(),
};
vec![Opaque {
name: p.kt_name.clone(),
target,
consume_null,
nullable,
domain: p.kt_type.simple_name().map(str::to_string),
}]
})
.collect()
}
fn error_sink_parts(
f: &prebindgen_registry::flat::Function,
fplan: &JniFunctionPlan,
registry: &Registry<KotlinMeta>,
imports: &mut BTreeSet<String>,
r_ty: &KtType,
) -> Option<ErrorSink> {
let ifaces = fplan.onerror_iface.as_ref()?;
let binding_spec = &ifaces.binding;
let binding_type = binding_spec.kt_ref(vec![r_ty.clone()]);
imports.insert(binding_spec.capture_fqn());
let binding_capture_short = binding_spec.capture_name();
let domain = if let Some(domain_spec) = &ifaces.domain {
let error_plan = registry
.error_plans()
.get(&f.name)
.expect("domain handler ⇒ error plan");
let ze_info: Vec<(KtType, crate::jni::WrapKind)> = domain_spec
.params
.iter()
.map(|p| {
if let Some(fqn) = p.wrap.class_fqn() {
imports.insert(fqn.to_string());
}
(p.raw.clone(), p.wrap.clone())
})
.collect();
debug_assert_eq!(ze_info.len(), error_plan.leaves.len());
imports.insert(domain_spec.capture_fqn());
let call_args = ze_info
.iter()
.enumerate()
.map(|(i, (raw, wrap))| {
if raw.is_nullable() {
wrap.wrap_expr(&format!("__dcap.ze{i}"), true)
} else {
wrap.wrap_expr(&format!("__dcap.ze{i}!!"), false)
}
})
.collect::<Vec<_>>()
.join(", ");
Some(DomainSink {
onerr_type: domain_spec.kt_ref(vec![r_ty.clone()]),
capture_short: domain_spec.capture_name(),
call_args,
})
} else {
None
};
let binding_param = if domain.is_some() {
"onBindingError".to_string()
} else {
"onError".to_string()
};
Some(ErrorSink {
binding_param,
binding_type,
binding_capture_short,
binding_call_arg: "__bcap.ze0".to_string(),
domain,
})
}
fn render_alias_preflight(opaques: &[Opaque], binding_param: &str, is_unit: bool) -> KtCode {
let mut guards = KtCode::new();
if opaques.len() < 2 || !opaques.iter().any(|o| o.consume_null.is_some()) {
return guards;
}
let ptr_of = |o: &Opaque| {
if o.nullable {
format!("({t}?.ptr ?: 0L)", t = o.target)
} else {
format!("{t}.ptr", t = o.target)
}
};
for i in 0..opaques.len() {
for j in (i + 1)..opaques.len() {
let (a, b) = (&opaques[i], &opaques[j]);
if a.consume_null.is_none() && b.consume_null.is_none() {
continue;
}
if let (Some(da), Some(db)) = (&a.domain, &b.domain) {
if da != db {
continue;
}
}
let (pa, pb) = (ptr_of(a), ptr_of(b));
let msg = format!(
"\"Aliasing arguments: '{}' and '{}' are the same native resource; a consumed \
handle may not be passed twice in one call.\"",
a.name, b.name,
);
let cond = format!("{pa} != 0L && {pa} == {pb}");
guards = if is_unit {
guards.wline(format!(
"if ({cond}) {{ {binding_param}.run({msg}); return }}"
))
} else {
guards.wline(format!("if ({cond}) return {binding_param}.run({msg})"))
};
}
}
guards
}
fn render_prelock_guards(opaques: &[Opaque], binding_param: &str, is_unit: bool) -> KtCode {
const CLOSED_MSG: &str = "\"Operation on a closed native handle.\"";
let mut guards = KtCode::new();
for o in opaques {
let cond = if o.nullable {
format!("{t}?.isClosed() == true", t = o.target)
} else {
format!("{t}.isClosed()", t = o.target)
};
guards = if is_unit {
guards.wline(format!(
"if ({cond}) {{ {binding_param}.run({CLOSED_MSG}); return }}"
))
} else {
guards.wline(format!(
"if ({cond}) return {binding_param}.run({CLOSED_MSG})"
))
};
}
guards
}
fn render_value_stmt(bind: &str, body_expr: &str, opaques: &[Opaque]) -> KtCode {
let consume_stmts: Vec<&str> = opaques
.iter()
.filter_map(|o| o.consume_null.as_deref())
.collect();
if consume_stmts.is_empty() {
KtCode::new().wline(format!("{bind}{body_expr}"))
} else {
let mut fin = KtCode::new();
for s in consume_stmts {
fin = fin.line(s);
}
KtCode::new().try_finally(bind, KtCode::new().wline(body_expr), fin)
}
}
fn render_core_stmt(
ext: &Declarations,
opaques: &[Opaque],
body_expr: &str,
imports: &mut BTreeSet<String>,
bind: &str,
) -> KtCode {
let mut ptr_binds = KtCode::new();
for o in opaques {
ptr_binds = if o.nullable {
ptr_binds.line(format!(
"val {n}_ptr = {t}?.ptr ?: 0L",
n = o.name,
t = o.target
))
} else {
ptr_binds.line(format!("val {n}_ptr = {t}.ptr", n = o.name, t = o.target))
};
}
if opaques.is_empty() {
render_value_stmt(bind, body_expr, opaques)
} else if !ext.emit_handle_locks {
KtCode::new().blk(format!("{bind}run {{"), |c| {
c.push(ptr_binds)
.push(render_value_stmt("", body_expr, opaques))
})
} else {
let fixed_arity = !opaques.iter().any(|o| o.nullable) && (1..=3).contains(&opaques.len());
if !ext.package.is_empty() {
imports.insert(format!("{}.withSortedHandleLocks", ext.package));
if !fixed_arity {
imports.insert(format!("{}.NativeHandle", ext.package));
}
}
if fixed_arity {
let targets = opaques
.iter()
.map(|o| o.target.as_str())
.collect::<Vec<_>>()
.join(", ");
KtCode::new().blk(format!("{bind}withSortedHandleLocks({targets}) {{"), |c| {
c.push(ptr_binds)
.push(render_value_stmt("", body_expr, opaques))
})
} else {
let mut adds = KtCode::new();
for o in opaques {
adds = if o.nullable {
adds.line(format!("{t}?.let {{ __locks.add(it) }}", t = o.target))
} else {
adds.line(format!("__locks.add({t})", t = o.target))
};
}
KtCode::new().blk(format!("{bind}run {{"), |c| {
c.line("val __locks = ArrayList<NativeHandle>()")
.push(adds)
.blk("withSortedHandleLocks(__locks) {", |l| {
l.push(ptr_binds)
.push(render_value_stmt("", body_expr, opaques))
})
})
}
}
}
enum BodyReturn {
Unit,
Value(String),
}
fn render_body(
ext: &Declarations,
params: &[Param],
opaques: &[Opaque],
sink: &ErrorSink,
body_expr: &str,
return_mode: &BodyReturn,
imports: &mut BTreeSet<String>,
) -> KtCode {
let is_unit = matches!(return_mode, BodyReturn::Unit);
let vec_build: Vec<(&String, &String, &Vec<String>)> = params
.iter()
.filter_map(|p| match &p.mode {
ParamMode::VecBuild {
base,
elem_accesses,
} => Some((&p.kt_name, base, elem_accesses)),
_ => None,
})
.collect();
let mut b = render_alias_preflight(opaques, &sink.binding_param, is_unit)
.push(render_prelock_guards(opaques, &sink.binding_param, is_unit))
.line(format!(
"val __bcap = {}.acquire()",
sink.binding_capture_short
));
if let Some(d) = &sink.domain {
b = b.line(format!("val __dcap = {}.acquire()", d.capture_short));
}
let mut failed_checks: Vec<String> = vec![format!(
"if (__bcap.failed) return {}.run({})",
sink.binding_param, sink.binding_call_arg
)];
if let Some(d) = &sink.domain {
failed_checks.push(format!(
"if (__dcap.failed) return onError.run({})",
d.call_args
));
}
let bind = if is_unit { "" } else { "val __ret = " };
if vec_build.is_empty() {
b = b.push(render_core_stmt(ext, opaques, body_expr, imports, bind));
for chk in &failed_checks {
b = b.wline(chk);
}
} else {
let native = ext.jni_native_class_name();
for (name, base, _) in &vec_build {
let new_m = crate::jni::vec_helper_method_name(ext, base, "New");
b = b.wline(format!("val __vec_{name} = {native}.{new_m}({name}.size)"));
}
let mut fill = KtCode::new();
for (name, base, accesses) in &vec_build {
let push_m = crate::jni::vec_helper_method_name(ext, base, "Push");
let args = std::iter::once(format!("__vec_{name}"))
.chain(accesses.iter().cloned())
.collect::<Vec<_>>()
.join(", ");
fill = fill.blk(format!("for (__e in {name}) {{"), |c| {
c.wline(format!("{native}.{push_m}({args})"))
});
}
let mut free = KtCode::new();
for (name, base, _) in &vec_build {
let free_m = crate::jni::vec_helper_method_name(ext, base, "Free");
free = free.wline(format!("{native}.{free_m}(__vec_{name})"));
}
let core = render_core_stmt(ext, opaques, body_expr, imports, "");
b = b.try_finally(bind, fill.push(core), free);
for chk in &failed_checks {
b = b.wline(chk);
}
}
b = match return_mode {
BodyReturn::Unit => b,
BodyReturn::Value(expr) => b.line(format!("return {expr}")),
};
b
}
pub(crate) fn unfold_leaf_kt(
ext: &Declarations,
registry: &impl Conversions<KotlinMeta>,
out_ty: &prebindgen_registry::flat::TypeRef,
nullable: bool,
pk: &str,
) -> Option<(KtType, String, String, bool)> {
let proj = registry
.output_entry(out_ty)
.and_then(|e| e.metadata.projection.clone());
let is_value_projection = proj
.as_ref()
.map(|p| matches!(p.kind, crate::jni::ProjectionKind::Unsigned64))
.unwrap_or(false);
let builder_kt = if ext.is_kotlin_enum_reading(out_ty) {
KtType::int()
} else {
classify_return(ext, out_ty, registry)?.0?
};
let (mut wire_kt, wrap) = if is_value_projection {
let p = proj.as_ref().unwrap();
let leaf_fqn = ext
.kotlin_fqn(&p.leaf_key)
.unwrap_or_else(|| p.leaf_key.to_string());
let short = leaf_fqn.rsplit('.').next().unwrap_or(&leaf_fqn).to_string();
let sentinel = wrap_sentinel(p, nullable);
let mut wrap = fold_projection_wrap(&p.strategy, pk, &p.kind, &short, sentinel.as_deref());
if nullable && matches!(p.strategy, crate::jni::FoldStrategy::Base) {
let inner = projection_wrap_expr(&p.kind, &short, "it");
wrap = format!("{pk}?.let {{ {inner} }}");
}
(projection_wire_return(p).to_string(), wrap)
} else {
(builder_kt.to_string(), pk.to_string())
};
let builder_kt = if nullable {
wire_kt.push('?');
builder_kt.nullable()
} else {
builder_kt
};
Some((builder_kt, wire_kt, wrap, is_value_projection))
}
pub(crate) fn plan_leaf_names(leaves: &[prebindgen_registry::unfold::UnfoldLeaf]) -> Vec<String> {
leaves.iter().map(|leaf| leaf.name.clone()).collect()
}
pub(crate) fn whole_value_name(ty: &prebindgen_registry::flat::TypeRef, i: usize) -> String {
use prebindgen_registry::flat::TypeKind;
let t = ty.borrow_target().unwrap_or(ty);
let t = match t.kind() {
TypeKind::Optional(inner) => inner,
_ => t,
};
match crate::util::head_name(t) {
Some(s) => {
let mut cs = s.chars();
let f = cs.next().expect("a name is not empty");
kt_param_name(&format!("{}{}", f.to_lowercase(), cs.as_str()))
}
None => format!("arg{i}"),
}
}
pub(crate) fn kotlin_for_wire(wire: &syn::Type) -> Option<KtType> {
if let Some(p) = JniPrim::from_wire(wire) {
return Some(KtType::cls(p.kotlin_type()));
}
if let syn::Type::Path(tp) = wire {
if let Some(last) = tp.path.segments.last() {
let kt = match last.ident.to_string().as_str() {
"JString" | "jstring" => "String",
"JByteArray" | "jbyteArray" => "ByteArray",
"JObject" | "jobject" | "JClass" => "Any",
_ => return None,
};
return Some(KtType::cls(kt));
}
}
None
}
pub(crate) fn classify_return(
ext: &Declarations,
output: &prebindgen_registry::flat::TypeRef,
registry: &impl Conversions<KotlinMeta>,
) -> Option<(Option<KtType>, Option<crate::jni::Projection>)> {
let (surface, _canonical) = ReturnSurface::classify(ext, registry, output);
render_return_surface(&surface)
}
pub(crate) fn render_return_surface(
surface: &ReturnSurface,
) -> Option<(Option<KtType>, Option<crate::jni::Projection>)> {
match surface {
ReturnSurface::Skip => None,
ReturnSurface::Unit => Some((None, None)),
ReturnSurface::Projected {
projection,
leaf_fqn,
} => {
let fqn = leaf_fqn.clone().unwrap_or_else(|| {
panic!(
"classify_return: projection return type `{}` has no Kotlin FQN \
registered — every opaque class must be declared via `ptr_class!(...)`.",
projection.leaf_key
)
});
Some((
Some(handle_kt_type(&projection.strategy, &KtType::cls(fqn))),
Some(projection.clone()),
))
}
ReturnSurface::Plain { kt } => Some((Some(kt.clone()), None)),
}
}
pub(crate) fn kt_type_short(ty: &KtType) -> String {
ty.render(&mut kt::ImportSet::new(""))
}
pub(crate) fn kotlin_property_name(field: &syn::Ident) -> String {
mangle_kotlin_ident(&kt_snake_to_camel(&field.to_string()))
}
pub(crate) fn kt_snake_to_camel(s: &str) -> String {
let mut out = String::new();
let mut upper = false;
for c in s.chars() {
if c == '_' {
upper = true;
} else if upper {
out.push(c.to_ascii_uppercase());
upper = false;
} else {
out.push(c);
}
}
out
}
pub(crate) fn kt_param_name(rust_ident: &str) -> String {
mangle_kotlin_ident(&kt_snake_to_camel(rust_ident))
}
fn wrapper_kdoc(
f: &prebindgen_registry::flat::Function,
registry: &Registry<KotlinMeta>,
) -> Option<String> {
let prose = f.docs();
let notes = shape_notes(f, registry);
match (prose, notes) {
(Some(p), Some(n)) => Some(format!("{p}\n\n{n}")),
(Some(p), None) => Some(p),
(None, Some(n)) => Some(n),
(None, None) => None,
}
}
fn shape_notes(
f: &prebindgen_registry::flat::Function,
registry: &Registry<KotlinMeta>,
) -> Option<String> {
let fn_ident = &f.name;
let mut notes: Vec<String> = Vec::new();
let mut plans: Vec<(&syn::Ident, &prebindgen_registry::expand::FoldPlan)> = registry
.expansion_plans()
.iter()
.filter(|((func, _), _)| func == fn_ident)
.map(|((_, param), plan)| (param, plan))
.collect();
plans.sort_by_key(|(p, _)| p.to_string());
for (param, plan) in plans {
let target = plan.target.to_string();
let arms: Vec<String> = plan
.variants
.iter()
.map(|v| match &v.ctor {
Some(c) => format!("its `{c}` inputs"),
None => format!("an existing `{target}`"),
})
.collect();
let leaf_names: Vec<String> = plan
.leaves
.iter()
.map(|l| snake_to_camel(&l.name.to_string()))
.collect();
let how = if plan.selector.is_some() {
if plan.produces_option() {
format!(
"pass EITHER {} — the selector chooses the arm, `-1` = absent",
arms.join(" OR ")
)
} else {
format!(
"pass EITHER {} — the selector chooses the arm",
arms.join(" OR ")
)
}
} else {
arms.join(" / ").to_string()
};
notes.push(format!(
"Parameter `{param}` is the Rust `{target}` argument, expanded: {how} \
(crosses as `{}`).",
leaf_names.join("`, `")
));
}
if let Some(plan) = registry.unfold_plans().get(fn_ident) {
let source = plan.source.to_string();
let leaves: Vec<&str> = plan.leaves.iter().map(|l| l.name.as_str()).collect();
match plan.delivery {
prebindgen_registry::unfold::Delivery::Callback if !leaves.is_empty() => {
notes.push(format!(
"The Rust `{source}` result is delivered decomposed: the builder \
callback receives (`{}`).",
leaves.join("`, `")
));
}
prebindgen_registry::unfold::Delivery::Return => {
notes.push(format!(
"The Rust `{source}` result is converted and returned as a single value."
));
}
_ => {}
}
}
if let Some(plan) = registry.error_plans().get(fn_ident) {
let source = plan.source.to_string();
let leaves: Vec<&str> = plan.leaves.iter().map(|l| l.name.as_str()).collect();
notes.push(format!(
"On a domain error `onError` receives the decomposed Rust `{source}` error \
(`{}`); a binding/system failure goes to `onBindingError` instead.",
leaves.join("`, `")
));
}
if notes.is_empty() {
None
} else {
Some(notes.join("\n"))
}
}
pub(crate) fn source_item_doc<M>(registry: &Registry<M>, key: &TypeKey) -> Option<String> {
match registry.flat().declared_type(&key.ident()?)? {
prebindgen_registry::flat::Type::Struct(s) => s.docs(),
prebindgen_registry::flat::Type::Enum(e) => e.docs(),
prebindgen_registry::flat::Type::Variant(v) => v.docs(),
prebindgen_registry::flat::Type::Extern(_) => None,
}
}