use crate::core::config::Language;
use crate::core::ir::{ApiSurface, FunctionDef, MethodDef, TypeRef};
use crate::docs::formatting::{IdentifierPosition, report_identifier_violation};
use crate::docs::naming::{field_name, method_name, to_camel_case, type_name};
use crate::docs::rust_types::{rust_param_type, rust_receiver};
use crate::docs::type_mapping::{FFI_HANDLE_TYPE_NAME, doc_type};
use heck::ToSnakeCase;
pub(crate) fn render_function_signature(
func: &FunctionDef,
lang: Language,
ffi_prefix: &str,
crate_name: &str,
api: &ApiSurface,
) -> String {
match lang {
Language::Python => render_python_fn_sig(func, ffi_prefix),
Language::Node | Language::Wasm => render_typescript_fn_sig(func, ffi_prefix),
Language::Go => render_go_fn_sig(func, ffi_prefix),
Language::Java => render_java_fn_sig(func, ffi_prefix, crate_name),
Language::Ruby => render_ruby_fn_sig(func),
Language::Ffi | Language::C | Language::Jni => render_c_fn_sig(func, ffi_prefix),
Language::Php => render_php_fn_sig(func, ffi_prefix),
Language::Elixir => render_elixir_fn_sig(func),
Language::R => render_r_fn_sig(func),
Language::Csharp => render_csharp_fn_sig(func, ffi_prefix),
Language::Rust => render_rust_fn_sig(func, ffi_prefix),
Language::Kotlin | Language::KotlinAndroid => render_kotlin_fn_sig(func, ffi_prefix),
Language::Swift => render_swift_fn_sig(func, ffi_prefix),
Language::Dart => render_dart_fn_sig(func, ffi_prefix),
Language::Zig => render_zig_fn_sig(func, ffi_prefix, api),
Language::Gleam => {
format!("// Phase 1: {lang} backend signature generation")
}
}
}
mod function_sigs;
pub(crate) use function_sigs::*;
pub(crate) fn is_csharp_static_constructor(method: &MethodDef, owner_type: &str) -> bool {
if method.name != "new" {
return false;
}
if !method.is_static || method.params.is_empty() {
return false;
}
match &method.return_type {
TypeRef::Named(n) => n == owner_type,
_ => false,
}
}
pub(crate) fn is_swift_static_constructor(method: &MethodDef, owner_type: &str) -> bool {
if method.name != "new" {
return false;
}
if !method.is_static {
return false;
}
match &method.return_type {
TypeRef::Named(n) => n == owner_type,
_ => false,
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct MethodSignatureOverride {
pub(crate) name: Option<String>,
pub(crate) return_type: Option<String>,
pub(crate) signature: Option<String>,
}
#[cfg(test)]
pub(crate) fn render_method_signature(
method: &MethodDef,
type_name_str: &str,
lang: Language,
ffi_prefix: &str,
) -> String {
render_method_signature_with_override(
method,
type_name_str,
lang,
ffi_prefix,
crate::docs::test_helpers::TEST_CRATE_NAME,
None,
&ApiSurface::default(),
)
}
pub(crate) fn render_method_signature_with_override(
method: &MethodDef,
type_name_str: &str,
lang: Language,
ffi_prefix: &str,
crate_name: &str,
signature_override: Option<&MethodSignatureOverride>,
api: &ApiSurface,
) -> String {
if let Some(signature) = signature_override.and_then(|override_| override_.signature.as_deref()) {
return signature.to_string();
}
let name = signature_override
.and_then(|override_| override_.name.as_deref())
.map(str::to_string)
.unwrap_or_else(|| method_name(type_name_str, &method.name, lang, ffi_prefix));
report_identifier_violation(&name, lang, IdentifierPosition::Member, "a method signature");
let overridden_ret = signature_override.and_then(|override_| override_.return_type.as_deref());
let ret_is_overridden = overridden_ret.is_some();
let ret = overridden_ret.map(str::to_string).unwrap_or_else(|| {
if lang == Language::Zig {
crate::backends::zig::zig_boundary_return_type(&method.return_type, api)
} else {
doc_type(&method.return_type, lang, ffi_prefix)
}
});
match lang {
Language::Python => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = field_name(&p.name, lang);
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pname}: {pty}")
})
.collect();
if method.is_static {
format!("@staticmethod\ndef {}({}) -> {}", name, params.join(", "), ret)
} else {
let mut all_params = vec!["self".to_string()];
all_params.extend(params);
format!("def {}({}) -> {}", name, all_params.join(", "), ret)
}
}
Language::Node | Language::Wasm => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = field_name(&p.name, lang);
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pname}: {pty}")
})
.collect();
let ret = if method.is_async {
format!("Promise<{ret}>")
} else {
ret
};
if method.is_static {
format!("static {}({}): {}", name, params.join(", "), ret)
} else {
format!("{}({}): {}", name, params.join(", "), ret)
}
}
Language::Ruby => {
let params: Vec<String> = method.params.iter().map(|p| p.name.to_snake_case()).collect();
if method.is_static {
format!("def self.{}({})", name, params.join(", "))
} else {
format!("def {}({})", name, params.join(", "))
}
}
Language::Go => {
let go_receiver_type = type_name(type_name_str, Language::Go, ffi_prefix);
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = to_camel_case(&p.name);
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pname} {pty}")
})
.collect();
let has_return_override = signature_override.and_then(|o| o.return_type.as_deref()).is_some();
let ret = if has_return_override {
ret
} else {
go_return_type(&method.return_type, ret)
};
let head = if method.is_static {
format!("func {go_receiver_type}{name}")
} else {
format!("func (o *{go_receiver_type}) {name}")
};
if method.error_type.is_some() {
if ret.is_empty() {
format!("{head}({}) error", params.join(", "))
} else {
format!("{head}({}) ({}, error)", params.join(", "), ret)
}
} else if ret.is_empty() {
format!("{head}({})", params.join(", "))
} else {
format!("{head}({}) {}", params.join(", "), ret)
}
}
Language::Java => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = to_camel_case(&p.name);
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pty} {pname}")
})
.collect();
let throws = if method.name == "clone" {
String::new()
} else {
format!(
" throws {}",
crate::backends::java::naming::exception_class_name(crate_name)
)
};
if method.is_static {
format!("public static {} {}({}){}", ret, name, params.join(", "), throws)
} else {
format!("public {} {}({}){}", ret, name, params.join(", "), throws)
}
}
Language::Csharp => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = to_camel_case(&p.name);
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pty} {pname}")
})
.collect();
if is_csharp_static_constructor(method, type_name_str) {
let class_name = type_name(type_name_str, lang, ffi_prefix);
format!("public {}({})", class_name, params.join(", "))
} else {
let static_kw = if method.is_static { "static " } else { "" };
if method.is_async {
let async_name = crate::docs::naming::csharp_async_member_name(&name, true);
let task_ret = if ret == "void" {
"Task".to_string()
} else {
format!("Task<{ret}>")
};
format!(
"public {static_kw}async {} {}({})",
task_ret,
async_name,
params.join(", ")
)
} else {
format!("public {static_kw}{} {}({})", ret, name, params.join(", "))
}
}
}
Language::Php => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = format!("${}", to_camel_case(&p.name));
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pty} {pname}")
})
.collect();
if method.is_static {
format!("public static function {}({}): {}", name, params.join(", "), ret)
} else {
format!("public function {}({}): {}", name, params.join(", "), ret)
}
}
Language::Elixir => {
let mut params: Vec<String> = if method.is_static {
Vec::new()
} else {
vec!["obj".to_string()]
};
params.extend(method.params.iter().map(|p| p.name.to_snake_case()));
format!("def {}({})", name, params.join(", "))
}
Language::R => {
let params: Vec<String> = method.params.iter().map(|p| p.name.to_snake_case()).collect();
format!("{}({})", name, params.join(", "))
}
Language::Ffi | Language::C | Language::Jni => {
let mut params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = p.name.to_snake_case();
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pty} {pname}")
})
.collect();
if !method.is_static {
let receiver_ty = type_name(FFI_HANDLE_TYPE_NAME, lang, ffi_prefix);
params.insert(0, format!("{receiver_ty} this"));
}
let has_return_override = signature_override.and_then(|o| o.return_type.as_deref()).is_some();
let ret = if matches!(lang, Language::Ffi | Language::C)
&& !has_return_override
&& matches!(method.return_type, TypeRef::Unit)
&& method.error_type.is_some()
{
"int32_t".to_string()
} else {
ret
};
format!("{} {}({});", ret, name, params.join(", "))
}
Language::Rust => {
let mut params: Vec<String> = rust_receiver(method).map(str::to_string).into_iter().collect();
params.extend(
method
.params
.iter()
.map(|p| format!("{}: {}", p.name.to_snake_case(), rust_param_type(p, ffi_prefix))),
);
let ret = if let Some(err) = &method.error_type {
let err_ty = type_name(err, Language::Rust, ffi_prefix);
if ret == "()" {
format!("Result<(), {err_ty}>")
} else {
format!("Result<{ret}, {err_ty}>")
}
} else {
ret
};
let fn_keyword = if method.is_async { "pub async fn" } else { "pub fn" };
if ret == "()" {
format!("{fn_keyword} {}({})", name, params.join(", "))
} else {
format!("{fn_keyword} {}({}) -> {}", name, params.join(", "), ret)
}
}
Language::Kotlin | Language::KotlinAndroid => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = to_camel_case(&p.name);
let pty = doc_type(&p.ty, lang, ffi_prefix);
if p.optional {
format!("{pname}: {pty}? = null")
} else {
format!("{pname}: {pty}")
}
})
.collect();
let throws = method
.error_type
.as_ref()
.map(|e| format!("@Throws({}::class)\n", type_name(e, lang, ffi_prefix)))
.unwrap_or_default();
let ret_part = if ret == "Unit" {
String::new()
} else {
format!(": {ret}")
};
if method.is_static {
format!("{throws}@JvmStatic\nfun {name}({}){ret_part}", params.join(", "))
} else {
format!("{throws}fun {name}({}){ret_part}", params.join(", "))
}
}
Language::Swift => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = to_camel_case(&p.name);
let pty = doc_type(&p.ty, lang, ffi_prefix);
if p.optional {
format!("{pname}: {pty}? = nil")
} else {
format!("{pname}: {pty}")
}
})
.collect();
let throws = if method.error_type.is_some() { " throws" } else { "" };
if is_swift_static_constructor(method, type_name_str) {
format!("public init({}){throws}", params.join(", "))
} else {
let ret_part = if ret == "Void" {
String::new()
} else {
format!(" -> {ret}")
};
if method.is_static {
format!("public static func {name}({}){throws}{ret_part}", params.join(", "))
} else {
format!("public func {name}({}){throws}{ret_part}", params.join(", "))
}
}
}
Language::Dart => {
let required: Vec<String> = method
.params
.iter()
.filter(|p| !p.optional)
.map(|p| {
let pname = to_camel_case(&p.name);
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pty} {pname}")
})
.collect();
let optional: Vec<String> = method
.params
.iter()
.filter(|p| p.optional)
.map(|p| {
let pname = to_camel_case(&p.name);
let pty = doc_type(&p.ty, lang, ffi_prefix);
format!("{pty}? {pname}")
})
.collect();
let params_str = match (required.is_empty(), optional.is_empty()) {
(_, true) => required.join(", "),
(true, false) => format!("{{{}}}", optional.join(", ")),
(false, false) => format!("{}, {{{}}}", required.join(", "), optional.join(", ")),
};
let static_kw = if method.is_static { "static " } else { "" };
let future_ret = if ret_is_overridden {
ret.clone()
} else if ret == "void" {
"Future<void>".to_string()
} else {
format!("Future<{ret}>")
};
format!("{static_kw}{future_ret} {name}({params_str})")
}
Language::Zig => {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let pname = p.name.to_snake_case();
let pty = crate::backends::zig::zig_boundary_param_type(&p.ty, p.optional, api);
format!("{pname}: {pty}")
})
.collect();
let ret_str = if let Some(err) = &method.error_type {
let err_ty = type_name(err, lang, ffi_prefix);
if ret == "void" {
format!("{err_ty}!void")
} else {
format!("{err_ty}!{ret}")
}
} else {
ret
};
let receiver_ty = type_name(type_name_str, lang, ffi_prefix);
if method.is_static {
let type_snake = receiver_ty.to_snake_case();
format!("pub fn {name}_{type_snake}({}) {ret_str}", params.join(", "))
} else {
let mut all_params = vec![format!("self: *const {receiver_ty}")];
all_params.extend(params);
format!("pub fn {name}({}) {ret_str}", all_params.join(", "))
}
}
Language::Gleam => {
format!("// Phase 1: {lang} backend method signature generation")
}
}
}
#[cfg(test)]
#[path = "signatures/tests.rs"]
mod tests;