use super::*;
pub(crate) fn rust_short_name(key: &TypeKey) -> String {
rust_short_name_opt(key).unwrap_or_else(|| {
panic!(
"rust_short_name: cannot derive Kotlin name from type-key `{}` — \
only path-shaped types are supported here",
key.as_str()
)
})
}
pub(crate) fn rust_short_name_opt(key: &TypeKey) -> Option<String> {
key.short_name()
}
pub(crate) struct QualifyEmittedTypes<'a> {
pub(crate) source_names: &'a std::collections::HashMap<String, syn::Path>,
pub(crate) length_names: &'a std::collections::HashMap<String, syn::Path>,
}
impl syn::visit_mut::VisitMut for QualifyEmittedTypes<'_> {
fn visit_type_path_mut(&mut self, tp: &mut syn::TypePath) {
if tp.qself.is_none() && tp.path.leading_colon.is_none() && tp.path.segments.len() == 1 {
let ident = tp.path.segments[0].ident.to_string();
if let Some(module) = self.source_names.get(&ident) {
let mut qualified = module.clone();
qualified.segments.push(tp.path.segments[0].clone());
tp.path = qualified;
}
}
syn::visit_mut::visit_type_path_mut(self, tp);
}
fn visit_type_array_mut(&mut self, arr: &mut syn::TypeArray) {
syn::visit_mut::visit_type_mut(self, &mut arr.elem);
reject_unsupported_array_length(arr);
let mut lengths = QualifyLengthPaths {
length_names: self.length_names,
};
syn::visit_mut::visit_expr_mut(&mut lengths, &mut arr.len);
}
}
fn reject_unsupported_array_length(arr: &mut syn::TypeArray) {
let rendered = quote::ToTokens::to_token_stream(&*arr).to_string();
struct Check(Option<&'static str>);
impl syn::visit_mut::VisitMut for Check {
fn visit_expr_mut(&mut self, e: &mut syn::Expr) {
let ok = matches!(
e,
syn::Expr::Lit(_)
| syn::Expr::Path(_)
);
if !ok && self.0.is_none() {
self.0 = Some("an unsupported expression form");
}
syn::visit_mut::visit_expr_mut(self, e);
}
}
let mut check = Check(None);
syn::visit_mut::VisitMut::visit_expr_mut(&mut check, &mut arr.len);
if let Some(what) = check.0 {
panic!(
"fixed-size array `{rendered}`: the length uses {what}. Only a literal and the name \
of a `#[prebindgen]` const are supported — anything that can bind a name \
(`const {{ … }}`, `match`, `if let`, a closure, a loop) would let a LOCAL be \
mistaken for a source item, because this generator qualifies the length's paths \
against their source module. Hoist the value into a named `const` and use that as \
the length."
);
}
}
struct QualifyLengthPaths<'a> {
length_names: &'a std::collections::HashMap<String, syn::Path>,
}
impl syn::visit_mut::VisitMut for QualifyLengthPaths<'_> {
fn visit_expr_path_mut(&mut self, ep: &mut syn::ExprPath) {
if ep.qself.is_none() && ep.path.leading_colon.is_none() {
let ident = ep.path.segments[0].ident.to_string();
if let Some(module) = self.length_names.get(&ident) {
let mut qualified = module.clone();
qualified.segments.extend(ep.path.segments.iter().cloned());
ep.path = qualified;
}
}
syn::visit_mut::visit_expr_path_mut(self, ep);
}
}
pub(crate) fn annotate_jobject_with_lifetime(ty: &syn::Type, life: &str) -> syn::Type {
if let syn::Type::Path(tp) = ty {
if let Some(last) = tp.path.segments.last() {
if crate::jni::wire_access::is_jni_reference_wire(ty)
&& matches!(last.arguments, syn::PathArguments::None)
{
let mut new = tp.clone();
if let Some(last) = new.path.segments.last_mut() {
let lt =
syn::Lifetime::new(&format!("'{}", life), proc_macro2::Span::call_site());
last.arguments =
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
colon2_token: None,
lt_token: syn::token::Lt::default(),
args: syn::punctuated::Punctuated::from_iter(std::iter::once(
syn::GenericArgument::Lifetime(lt),
)),
gt_token: syn::token::Gt::default(),
});
}
return syn::Type::Path(new);
}
}
}
ty.clone()
}
pub(crate) fn input_name(rust: &TokenStream, wire: &syn::Type) -> syn::Ident {
let rust_id = sanitize_for_ident(&rust.to_string());
let wire_id = wire_short(wire);
let h = hash_pair(rust, wire);
let s = format!("{}_to_{}_{:08x}", wire_id, rust_id, h & 0xffff_ffff);
syn::Ident::new(&s, Span::call_site())
}
pub(crate) fn output_name(rust: &TokenStream, wire: &syn::Type) -> syn::Ident {
let rust_id = sanitize_for_ident(&rust.to_string());
let wire_id = wire_short(wire);
let h = hash_pair(rust, wire);
let s = format!("{}_to_{}_{:08x}", rust_id, wire_id, h & 0xffff_ffff);
syn::Ident::new(&s, Span::call_site())
}
pub(crate) fn sanitize_for_ident(s: &str) -> String {
if s.trim() == "()" {
return "unit".to_string();
}
let mut out = String::with_capacity(s.len());
let mut prev_underscore = false;
for c in s.chars() {
if c.is_ascii_alphanumeric() {
out.push(c);
prev_underscore = false;
} else if !prev_underscore {
out.push('_');
prev_underscore = true;
}
}
while out.starts_with('_') {
out.remove(0);
}
while out.ends_with('_') {
out.pop();
}
if out.is_empty() {
out.push_str("ty");
}
if out.chars().next().is_some_and(|c| c.is_ascii_digit()) {
out.insert(0, '_');
}
out
}
pub(crate) fn wire_short(wire: &syn::Type) -> String {
if let syn::Type::Path(tp) = wire {
if let Some(last) = tp.path.segments.last() {
return sanitize_for_ident(&last.ident.to_string());
}
}
sanitize_for_ident(&wire.to_token_stream().to_string())
}
pub(crate) fn hash_pair(rust: &TokenStream, wire: &syn::Type) -> u64 {
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
let mut h = DefaultHasher::new();
rust.to_string().hash(&mut h);
"::".hash(&mut h);
wire.to_token_stream().to_string().hash(&mut h);
h.finish()
}