use crate::codegen::generators::binding_helpers::{
gen_async_body, gen_call_args, gen_call_args_cfg, gen_call_args_with_let_bindings_mutex_json_str,
gen_named_let_bindings, gen_named_let_bindings_by_ref, gen_serde_let_bindings, gen_unimplemented_body,
has_named_params,
};
use crate::codegen::generators::{AdapterBodies, AsyncPattern, RustBindingConfig};
use crate::codegen::shared::function_sig_defaults;
use crate::codegen::type_mapper::TypeMapper;
use crate::core::ir::{ApiSurface, FunctionDef, TypeRef};
use ahash::{AHashMap, AHashSet};
fn expr_is_already_arc(expr: &str) -> bool {
let trimmed = expr.trim();
trimmed == "self.inner"
|| trimmed == "self.inner.clone()"
|| trimmed.starts_with("self.inner.as_ref()")
|| trimmed.starts_with("self.inner.clone()")
}
fn arc_wrap_expr(val: &str, name: &str, mutex_types: &AHashSet<String>) -> String {
if mutex_types.contains(name) {
format!("Arc::new(std::sync::Mutex::new({val}))")
} else {
format!("Arc::new({val})")
}
}
fn wide_int_cast_target(
prim: &crate::core::ir::PrimitiveType,
cast_large_ints_to_f64: bool,
cast_uints_to_i32: bool,
) -> Option<&'static str> {
use crate::core::ir::PrimitiveType;
match prim {
PrimitiveType::Usize | PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Isize
if cast_large_ints_to_f64 =>
{
Some("f64")
}
PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 if cast_uints_to_i32 => Some("i32"),
_ => None,
}
}
fn cast_return_expr(
ret: &TypeRef,
expr: &str,
cast_large_ints_to_f64: bool,
cast_uints_to_i32: bool,
) -> Option<String> {
if !cast_large_ints_to_f64 && !cast_uints_to_i32 {
return None;
}
match ret {
TypeRef::Primitive(prim) => {
let target = wide_int_cast_target(prim, cast_large_ints_to_f64, cast_uints_to_i32)?;
Some(format!("({expr}) as {target}"))
}
TypeRef::Vec(inner) => {
let TypeRef::Primitive(prim) = inner.as_ref() else {
return None;
};
let target = wide_int_cast_target(prim, cast_large_ints_to_f64, cast_uints_to_i32)?;
Some(format!(
"{expr}.into_iter().map(|v| v as {target}).collect::<Vec<{target}>>()"
))
}
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Primitive(prim) => {
let target = wide_int_cast_target(prim, cast_large_ints_to_f64, cast_uints_to_i32)?;
Some(format!("{expr}.map(|v| v as {target})"))
}
TypeRef::Vec(vinner) => {
let TypeRef::Primitive(prim) = vinner.as_ref() else {
return None;
};
let target = wide_int_cast_target(prim, cast_large_ints_to_f64, cast_uints_to_i32)?;
Some(format!(
"{expr}.map(|xs| xs.into_iter().map(|v| v as {target}).collect::<Vec<{target}>>())"
))
}
_ => None,
},
_ => None,
}
}
pub fn gen_function(
func: &FunctionDef,
mapper: &dyn TypeMapper,
cfg: &RustBindingConfig,
adapter_bodies: &AdapterBodies,
opaque_types: &AHashSet<String>,
) -> String {
gen_function_with_mutex(func, mapper, cfg, adapter_bodies, opaque_types, &AHashSet::new())
}
pub fn gen_function_with_mutex(
func: &FunctionDef,
mapper: &dyn TypeMapper,
cfg: &RustBindingConfig,
adapter_bodies: &AdapterBodies,
opaque_types: &AHashSet<String>,
mutex_types: &AHashSet<String>,
) -> String {
let map_fn = |ty: &crate::core::ir::TypeRef| mapper.map_type(ty);
let param_strings: Vec<String> = if cfg.named_non_opaque_params_by_ref {
let mut seen_optional = false;
func.params
.iter()
.enumerate()
.map(|(idx, p)| {
if p.optional {
seen_optional = true;
}
let promoted =
seen_optional && !p.optional && crate::codegen::shared::is_promoted_optional(&func.params, idx);
let ty = match &p.ty {
TypeRef::Named(n) if !opaque_types.contains(n.as_str()) => {
let _ = promoted;
if p.optional {
format!("Nullable<&{}>", map_fn(&p.ty))
} else {
format!("&{}", map_fn(&p.ty))
}
}
TypeRef::Optional(inner) => {
let inner_str_if_named = if let TypeRef::Named(n) = inner.as_ref() {
if !opaque_types.contains(n.as_str()) {
Some(n.clone())
} else {
None
}
} else {
None
};
if let Some(inner_name) = inner_str_if_named {
format!("extendr_api::Nullable<&{}>", inner_name)
} else if p.optional || seen_optional {
format!("Option<{}>", map_fn(&p.ty))
} else {
map_fn(&p.ty)
}
}
_ => {
if p.optional || seen_optional {
format!("Option<{}>", map_fn(&p.ty))
} else {
map_fn(&p.ty)
}
}
};
format!("{}: {}", p.name, ty)
})
.collect::<Vec<_>>()
} else {
crate::codegen::shared::function_params_vec(&func.params, &map_fn)
};
let params = param_strings.join(", ");
let return_type = mapper.map_type(&func.return_type);
let ret = mapper.wrap_return(&return_type, func.error_type.is_some());
let effective_params: std::borrow::Cow<[crate::core::ir::ParamDef]> = std::borrow::Cow::Borrowed(&func.params);
let use_let_bindings = has_named_params(&effective_params, opaque_types);
let call_args = if use_let_bindings {
gen_call_args_with_let_bindings_mutex_json_str(
&effective_params,
opaque_types,
mutex_types,
cfg.cast_uints_to_i32,
cfg.cast_large_ints_to_f64,
)
} else if cfg.cast_uints_to_i32 || cfg.cast_large_ints_to_f64 {
gen_call_args_cfg(
&effective_params,
opaque_types,
cfg.cast_uints_to_i32,
cfg.cast_large_ints_to_f64,
)
} else {
gen_call_args(&effective_params, opaque_types)
};
let core_import = cfg.core_import;
let let_bindings = if use_let_bindings {
if cfg.named_non_opaque_params_by_ref {
gen_named_let_bindings_by_ref(&func.params, opaque_types, core_import)
} else {
gen_named_let_bindings(&func.params, opaque_types, core_import)
}
} else {
String::new()
};
let core_fn_path = {
let path = func.rust_path.replace('-', "_");
if path.starts_with(core_import) {
path
} else {
format!("{core_import}::{}", func.name)
}
};
let can_delegate = crate::codegen::shared::can_auto_delegate_function(func, opaque_types)
|| can_delegate_with_named_let_bindings(func, opaque_types);
let pyo3_sync = !func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
let detach_core_call = |core_call: &str| -> String {
if pyo3_sync {
if let Some(path) = core_call.strip_suffix("()") {
format!("py.detach({path})")
} else {
format!("py.detach(|| {core_call})")
}
} else {
core_call.to_string()
}
};
let serde_err_conv = match cfg.async_pattern {
AsyncPattern::Pyo3FutureIntoPy => ".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))",
AsyncPattern::NapiNativeAsync => ".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))",
AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
AsyncPattern::TokioBlockOn => {
".map_err(|e| extendr_api::Error::Other(e.to_string().replace(\":\", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").chars().take(255).collect::<String>()))"
}
_ => ".map_err(|e| e.to_string())",
};
let body = if !can_delegate {
if let Some(adapter_body) = adapter_bodies.get(&func.name) {
adapter_body.clone()
} else if cfg.has_serde && use_let_bindings && func.error_type.is_some() {
let is_async_pyo3 = func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
let (serde_indent, serde_err_async) = if is_async_pyo3 {
(
" ",
".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))",
)
} else {
(" ", serde_err_conv)
};
let serde_bindings =
gen_serde_let_bindings(&func.params, opaque_types, core_import, serde_err_async, serde_indent);
let core_call = detach_core_call(&format!("{core_fn_path}({call_args})"));
let returns_ref = func.returns_ref;
let wrap_return = |expr: &str| -> String {
if let Some(cast) = cast_return_expr(
&func.return_type,
expr,
cfg.cast_large_ints_to_f64,
cfg.cast_uints_to_i32,
) {
return cast;
}
match &func.return_type {
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(_) => {
format!("{expr}.into_iter().map(Into::into).collect()")
}
_ => expr.to_string(),
},
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
let mapped_name = mapper.named(name);
if returns_ref {
format!("{mapped_name} {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("{mapped_name} {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{return_type}::from({expr}.clone())")
} else {
format!("{return_type}::from({expr})")
}
}
TypeRef::String | TypeRef::Bytes => expr.to_string(),
TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
TypeRef::Json => format!("{expr}.to_string()"),
_ => expr.to_string(),
}
};
if is_async_pyo3 {
let is_unit = matches!(func.return_type, TypeRef::Unit);
let wrapped = wrap_return("result");
let core_await = format!(
"{core_call}.await\n .map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?"
);
let inner_body = if is_unit {
format!("{serde_bindings}{core_await};\n Ok(())")
} else {
if wrapped.contains(".into()") || wrapped.contains("::from(") || wrapped.contains("Into::into") {
format!(
"{serde_bindings}let result = {core_await};\n let wrapped_result: {return_type} = {wrapped};\n Ok(wrapped_result)"
)
} else {
format!("{serde_bindings}let result = {core_await};\n Ok({wrapped})")
}
};
format!("pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n{inner_body}\n }})")
} else if func.is_async {
let is_unit = matches!(func.return_type, TypeRef::Unit);
let wrapped = wrap_return("result");
let async_body = gen_async_body(
&core_call,
cfg,
func.error_type.is_some(),
&wrapped,
false,
"",
is_unit,
Some(&return_type),
);
format!("{serde_bindings}{async_body}")
} else if matches!(func.return_type, TypeRef::Unit) {
let await_kw = if func.is_async { ".await" } else { "" };
let debug_marker = if func.is_async { "/*ASYNC_UNIT*/ " } else { "" };
format!("{serde_bindings}{debug_marker}{core_call}{await_kw}{serde_err_conv}?;\n Ok(())")
} else {
let wrapped = wrap_return("val");
let await_kw = if func.is_async { ".await" } else { "" };
if wrapped == "val" {
format!("{serde_bindings}{core_call}{await_kw}{serde_err_conv}")
} else if wrapped == "val.into()" {
format!("{serde_bindings}{core_call}{await_kw}.map(Into::into){serde_err_conv}")
} else if let Some(type_path) = wrapped.strip_suffix("::from(val)") {
format!("{serde_bindings}{core_call}{await_kw}.map({type_path}::from){serde_err_conv}")
} else {
format!("{serde_bindings}{core_call}{await_kw}.map(|val| {wrapped}){serde_err_conv}")
}
}
} else if func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy {
let suppress = if func.params.is_empty() {
String::new()
} else {
let names: Vec<&str> = func.params.iter().map(|p| p.name.as_str()).collect();
format!("let _ = ({});\n ", names.join(", "))
};
format!(
"{suppress}Err(pyo3::exceptions::PyNotImplementedError::new_err(\"not implemented: {}\"))",
func.name
)
} else {
gen_unimplemented_body(
&func.return_type,
&func.name,
func.error_type.is_some(),
cfg,
&func.params,
opaque_types,
)
}
} else if func.is_async {
let core_call = format!("{core_fn_path}({call_args})");
let return_wrap = match &func.return_type {
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
let mapped_n = mapper.named(n);
let wrap = arc_wrap_expr("result", n, mutex_types);
format!("{mapped_n} {{ inner: {wrap} }}")
}
TypeRef::Named(_) => {
format!("{return_type}::from(result)")
}
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
let mapped_n = mapper.named(n);
let wrap = arc_wrap_expr("v", n, mutex_types);
format!("result.into_iter().map(|v| {mapped_n} {{ inner: {wrap} }}).collect::<Vec<_>>()")
}
TypeRef::Named(_) => {
let inner_mapped = mapper.map_type(inner);
format!("result.into_iter().map({inner_mapped}::from).collect::<Vec<_>>()")
}
_ => "result".to_string(),
},
TypeRef::Unit => "result".to_string(),
_ => {
let cast = cast_return_expr(
&func.return_type,
"result",
cfg.cast_large_ints_to_f64,
cfg.cast_uints_to_i32,
);
cast.unwrap_or_else(|| {
super::binding_helpers::wrap_return(
"result",
&func.return_type,
"",
opaque_types,
false,
func.returns_ref,
false,
)
})
}
};
if cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy && !let_bindings.is_empty() {
let is_unit = matches!(func.return_type, TypeRef::Unit);
let result_handling = if func.error_type.is_some() {
format!(
"let result = {core_call}.await\n \
.map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
)
} else if is_unit {
format!("{core_call}.await;")
} else {
format!("let result = {core_call}.await;")
};
let (ok_expr, extra_binding) = if is_unit && func.error_type.is_none() {
("()".to_string(), String::new())
} else if return_wrap.contains(".into()") || return_wrap.contains("::from(") {
let wrapped_var = "wrapped_result";
let binding = if let Some(ret_type) = Some(&return_type) {
format!("let {wrapped_var}: {ret_type} = {return_wrap};\n ")
} else {
format!("let {wrapped_var} = {return_wrap};\n ")
};
(wrapped_var.to_string(), binding)
} else {
(return_wrap.to_string(), String::new())
};
let inner_body = format!("{let_bindings}{result_handling}\n {extra_binding}Ok({ok_expr})");
format!("pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n{inner_body}\n }})")
} else {
let async_body = gen_async_body(
&core_call,
cfg,
func.error_type.is_some(),
&return_wrap,
false,
"",
matches!(func.return_type, TypeRef::Unit),
Some(&return_type),
);
format!("{let_bindings}{async_body}")
}
} else {
let core_call = detach_core_call(&format!("{core_fn_path}({call_args})"));
let cast_value = |expr: &str| -> String {
cast_return_expr(
&func.return_type,
expr,
cfg.cast_large_ints_to_f64,
cfg.cast_uints_to_i32,
)
.unwrap_or_else(|| expr.to_string())
};
let returns_ref = func.returns_ref;
let wrap_return = |expr: &str| -> String {
match &func.return_type {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
let mapped_name = mapper.named(name);
if expr_is_already_arc(expr) {
format!("{mapped_name} {{ inner: {expr} }}")
} else if returns_ref {
let wrap = arc_wrap_expr(&format!("{expr}.clone()"), name, mutex_types);
format!("{mapped_name} {{ inner: {wrap} }}")
} else {
let wrap = arc_wrap_expr(expr, name, mutex_types);
format!("{mapped_name} {{ inner: {wrap} }}")
}
}
TypeRef::Named(_name) => {
if returns_ref {
format!("{expr}.clone().into()")
} else {
format!("{expr}.into()")
}
}
TypeRef::String | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.into()")
} else {
expr.to_string()
}
}
TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
TypeRef::Json => format!("{expr}.to_string()"),
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
let mapped_name = mapper.named(name);
if returns_ref {
let wrap = arc_wrap_expr("v.clone()", name, mutex_types);
format!("{expr}.map(|v| {mapped_name} {{ inner: {wrap} }})")
} else {
let wrap = arc_wrap_expr("v", name, mutex_types);
format!("{expr}.map(|v| {mapped_name} {{ inner: {wrap} }})")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.map(|v| v.clone().into())")
} else {
format!("{expr}.map(Into::into)")
}
}
TypeRef::Path => {
format!("{expr}.map(|v| v.to_string_lossy().to_string())")
}
TypeRef::String | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.map(Into::into)")
} else {
expr.to_string()
}
}
TypeRef::Vec(vi) => match vi.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
let mapped_name = mapper.named(name);
let wrap = arc_wrap_expr("x", name, mutex_types);
format!(
"{expr}.map(|v| v.into_iter().map(|x| {mapped_name} {{ inner: {wrap} }}).collect())"
)
}
TypeRef::Named(_) => {
format!("{expr}.map(|v| v.into_iter().map(Into::into).collect())")
}
_ => expr.to_string(),
},
_ => expr.to_string(),
},
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
let mapped_name = mapper.named(name);
if returns_ref {
let wrap = arc_wrap_expr("v.clone()", name, mutex_types);
format!("{expr}.into_iter().map(|v| {mapped_name} {{ inner: {wrap} }}).collect()")
} else {
let wrap = arc_wrap_expr("v", name, mutex_types);
format!("{expr}.into_iter().map(|v| {mapped_name} {{ inner: {wrap} }}).collect()")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.iter().map(|v| v.clone().into()).collect()")
} else {
format!("{expr}.into_iter().map(Into::into).collect()")
}
}
TypeRef::Path => {
format!("{expr}.into_iter().map(|v| v.to_string_lossy().to_string()).collect()")
}
TypeRef::String => {
if returns_ref {
format!("{expr}.iter().map(|s| s.to_string()).collect()")
} else {
expr.to_string()
}
}
TypeRef::Bytes => {
if returns_ref {
format!("{expr}.iter().map(|b| b.to_vec()).collect()")
} else {
expr.to_string()
}
}
_ => expr.to_string(),
},
_ => expr.to_string(),
}
};
if func.error_type.is_some() {
let err_conv = match cfg.async_pattern {
AsyncPattern::Pyo3FutureIntoPy => {
".map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))"
}
AsyncPattern::NapiNativeAsync => {
".map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))"
}
AsyncPattern::WasmNativeAsync => ".map_err(|e| JsValue::from_str(&e.to_string()))",
AsyncPattern::TokioBlockOn => {
".map_err(|e| extendr_api::Error::Other(e.to_string().replace(\":\", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").chars().take(255).collect::<String>()))"
}
_ => ".map_err(|e| e.to_string())",
};
let wrapped = wrap_return("val");
let cast_val = cast_value("val");
if wrapped == "val" {
if cast_val == "val" {
format!("{core_call}{err_conv}")
} else {
format!("{core_call}.map(|val| {cast_val}){err_conv}")
}
} else if wrapped == "val.into()" {
format!("{core_call}.map(Into::into){err_conv}")
} else if let Some(type_path) = wrapped.strip_suffix("::from(val)") {
format!("{core_call}.map({type_path}::from){err_conv}")
} else {
format!("{core_call}.map(|val| {wrapped}){err_conv}")
}
} else {
let cast = cast_value(&core_call);
wrap_return(&cast)
}
};
let body = if !let_bindings.is_empty() && !func.is_async {
if can_delegate {
format!("{let_bindings}{body}")
} else {
let vec_str_bindings: String = func.params.iter().filter(|p| {
p.is_ref && matches!(&p.ty, TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char))
}).map(|p| {
if p.optional {
format!("let {}_refs: Vec<&str> = {}.as_ref().map(|v| v.iter().map(|s| s.as_str()).collect()).unwrap_or_default();\n ", p.name, p.name)
} else {
format!("let {}_refs: Vec<&str> = {}.iter().map(|s| s.as_str()).collect();\n ", p.name, p.name)
}
}).collect();
if !vec_str_bindings.is_empty() {
format!("{vec_str_bindings}{body}")
} else {
body
}
}
} else {
body
};
let async_kw = if func.is_async && cfg.async_pattern != AsyncPattern::TokioBlockOn {
"async "
} else {
""
};
let func_needs_py = func.is_async && cfg.async_pattern == AsyncPattern::Pyo3FutureIntoPy;
let ret = if func_needs_py {
"PyResult<Bound<'py, PyAny>>".to_string()
} else {
ret
};
let func_lifetime = if func_needs_py { "<'py>" } else { "" };
let sync_py_prefix = if pyo3_sync { "py: Python<'_>, " } else { "" };
let (func_sig, _params_formatted) = if params.len() > 100 {
let wrapped_params = param_strings.join(",\n ");
if func_needs_py {
(
format!(
"pub fn {}{func_lifetime}(py: Python<'py>,\n {}\n) -> {ret}",
func.name,
wrapped_params,
ret = ret
),
"",
)
} else {
(
format!(
"pub {async_kw}fn {}(\n {sync_py_prefix}{}\n) -> {ret}",
func.name,
wrapped_params,
ret = ret
),
"",
)
}
} else if func_needs_py {
(
format!(
"pub fn {}{func_lifetime}(py: Python<'py>, {params}) -> {ret}",
func.name
),
"",
)
} else {
(
format!("pub {async_kw}fn {}({sync_py_prefix}{params}) -> {ret}", func.name),
"",
)
};
let total_params = func.params.len() + if func_needs_py || pyo3_sync { 1 } else { 0 };
let sig_defaults = if cfg.needs_signature {
function_sig_defaults(&func.params)
} else {
String::new()
};
let attr_inner = cfg
.function_attr
.trim_start_matches('#')
.trim_start_matches('[')
.trim_end_matches(']');
crate::codegen::template_env::render(
"generators/functions/function_definition.jinja",
minijinja::context! {
has_too_many_arguments => total_params > 7,
has_missing_errors_doc => func.error_type.is_some(),
attr_inner => attr_inner,
needs_signature => cfg.needs_signature,
signature_prefix => cfg.signature_prefix,
sig_defaults => sig_defaults,
signature_suffix => cfg.signature_suffix,
func_sig => func_sig,
body => body,
},
)
}
fn can_delegate_with_named_let_bindings(func: &FunctionDef, opaque_types: &AHashSet<String>) -> bool {
!func.sanitized
&& func
.params
.iter()
.all(|p| !p.sanitized && crate::codegen::shared::is_delegatable_param(&p.ty, opaque_types))
&& crate::codegen::shared::is_delegatable_return(&func.return_type)
}
pub fn collect_trait_imports(api: &ApiSurface) -> Vec<String> {
let mut traits: AHashSet<String> = AHashSet::new();
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
for method in &typ.methods {
if let Some(ref trait_path) = method.trait_source {
traits.insert(trait_path.clone());
}
}
}
let mut by_name: AHashMap<String, String> = AHashMap::new();
for path in traits {
let name = path.split("::").last().unwrap_or(&path).to_string();
let entry = by_name.entry(name).or_insert_with(|| path.clone());
if path.len() < entry.len() {
*entry = path;
}
}
let mut sorted: Vec<String> = by_name.into_values().collect();
sorted.sort();
sorted
}
pub fn has_unresolved_trait_methods(api: &ApiSurface) -> bool {
let mut method_counts: AHashMap<&str, (usize, usize)> = AHashMap::new();
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
if typ.is_trait {
continue;
}
for method in &typ.methods {
let entry = method_counts.entry(&method.name).or_insert((0, 0));
entry.0 += 1;
if method.trait_source.is_some() {
entry.1 += 1;
}
}
}
method_counts
.values()
.any(|&(total, with_source)| total >= 3 && with_source == 0)
}
pub fn collect_explicit_core_imports(api: &ApiSurface) -> Vec<String> {
let mut names = std::collections::BTreeSet::new();
for typ in api.types.iter().filter(|typ| !typ.is_trait) {
names.insert(typ.name.clone());
}
for e in &api.enums {
names.insert(e.name.clone());
}
names.into_iter().collect()
}