use prebindgen_flat::flat::{Origin, TypeKey};
use quote::ToTokens;
pub fn declared_origin(ty: syn::Type) -> Origin<syn::Type> {
Origin::new(ty, std::rc::Rc::new(prebindgen::SourceLocation::default()))
}
#[derive(Clone)]
pub enum LocalVariant {
Ctor(syn::Ident),
SelfIdentity,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum LocalField {
Named(syn::Ident, Option<String>),
SelfField,
Local {
path: syn::Path,
sig: syn::Signature,
name_override: Option<String>,
},
Fields(FieldsDecl),
}
#[macro_export]
macro_rules! fun {
($name:ident) => {
$crate::FunctionDecl::new($crate::ident!($name))
};
($path:path) => {
$crate::FunctionDecl::new_local($crate::__macro_support::parse_path(stringify!($path)))
};
}
#[macro_export]
macro_rules! sig {
(($($params:tt)*) $(-> $ret:ty)?) => {
$crate::__macro_support::parse_signature(stringify!(($($params)*) $(-> $ret)?))
};
}
#[macro_export]
macro_rules! ty {
($t:ty) => {
$crate::__macro_support::parse_type(stringify!($t))
};
}
#[macro_export]
macro_rules! path {
($p:path) => {
$crate::__macro_support::parse_path(stringify!($p))
};
}
#[macro_export]
macro_rules! expr {
($e:expr) => {
$crate::__macro_support::parse_expr(stringify!($e))
};
}
#[macro_export]
macro_rules! convert {
($t:ty) => {
$crate::ConvertDecl::new($crate::__macro_support::parse_type(stringify!($t)))
};
}
#[macro_export]
macro_rules! expand_param {
($t:ty) => {
$crate::ExpandParamDecl::new($crate::__macro_support::parse_type(stringify!($t)))
};
}
#[macro_export]
macro_rules! from {
($t:ty) => {
$crate::ConvertSourceDecl::from_type($crate::__macro_support::parse_type(stringify!($t)))
};
}
#[macro_export]
macro_rules! try_from {
($t:ty) => {
$crate::ConvertSourceDecl::try_from_type($crate::__macro_support::parse_type(stringify!(
$t
)))
};
}
#[macro_export]
macro_rules! into {
($t:ty) => {
$crate::ConvertSourceDecl::into_type($crate::__macro_support::parse_type(stringify!($t)))
};
}
#[macro_export]
macro_rules! try_into {
($t:ty) => {
$crate::ConvertSourceDecl::try_into_type($crate::__macro_support::parse_type(stringify!(
$t
)))
};
}
#[macro_export]
macro_rules! expand_return {
($t:ty) => {
$crate::ExpandReturnDecl::new($crate::__macro_support::parse_type(stringify!($t)))
};
}
#[macro_export]
macro_rules! fields {
($name:ident) => {
$crate::FieldsDecl::new($crate::ident!($name))
};
}
#[derive(Clone)]
pub struct ExpandParamDecl {
key: TypeKey,
rust_type: Origin<syn::Type>,
variants: Vec<LocalVariant>,
no_split: bool,
}
impl ExpandParamDecl {
pub fn new(rust_type: syn::Type) -> Self {
Self {
key: TypeKey::from_type(&rust_type),
rust_type: declared_origin(rust_type),
variants: Vec::new(),
no_split: false,
}
}
pub fn key(&self) -> &TypeKey {
&self.key
}
pub fn rust_type(&self) -> &Origin<syn::Type> {
&self.rust_type
}
pub fn variants(&self) -> &[LocalVariant] {
&self.variants
}
pub fn is_no_split(&self) -> bool {
self.no_split
}
pub fn variant(mut self, ctor: FunctionDecl) -> Self {
assert!(
ctor.kotlin_name_override.is_none()
&& ctor.param_expands.is_empty()
&& ctor.return_expand.is_none(),
"expand_param!({}).variant(fun!({})): a variant arm only names the \
`#[prebindgen]` constructor — .name()/expand overrides don't apply",
self.key.as_str(),
ctor.rust_ident
);
assert!(
ctor.local.is_none(),
"expand_param!({}).variant(fun!(…::{f})): a variant arm only NAMES a fn — \
declare the binding-local fn via .fun/.method/.constructor/convert! first, \
then reference it here by ident: fun!({f})",
self.key.as_str(),
f = ctor.rust_ident
);
self.variants.push(LocalVariant::Ctor(ctor.rust_ident));
self
}
pub fn variant_self(mut self) -> Self {
self.variants.push(LocalVariant::SelfIdentity);
self
}
pub fn no_split(mut self) -> Self {
self.no_split = true;
self
}
}
#[derive(Clone)]
pub struct ExpandReturnDecl {
key: TypeKey,
rust_type: Origin<syn::Type>,
fields: Vec<LocalField>,
}
impl ExpandReturnDecl {
pub fn new(rust_type: syn::Type) -> Self {
Self {
key: TypeKey::from_type(&rust_type),
rust_type: declared_origin(rust_type),
fields: Vec::new(),
}
}
pub fn key(&self) -> &TypeKey {
&self.key
}
pub fn rust_type(&self) -> &Origin<syn::Type> {
&self.rust_type
}
pub fn field_list(&self) -> &[LocalField] {
&self.fields
}
pub fn field(mut self, accessor: FunctionDecl) -> Self {
self.reject_beside_consuming("field(..)");
assert!(
accessor.param_expands.is_empty() && accessor.return_expand.is_none(),
"expand_return!({}).field(fun!({})): expand overrides don't apply to a \
field accessor — only .name() is honored",
self.key.as_str(),
accessor.rust_ident
);
self.fields.push(match accessor.local {
None => LocalField::Named(accessor.rust_ident, accessor.kotlin_name_override),
Some((path, sig)) => {
let Some(sig) = sig else {
panic!(
"expand_return!({}).field(fun!({p})): a binding-local field states \
its accessor's signature — chain .sig(sig!((v: &{k}) -> Ret))",
self.key.as_str(),
p = quote::quote!(#path),
k = self.key.as_str()
);
};
LocalField::Local {
path,
sig,
name_override: accessor.kotlin_name_override,
}
}
});
self
}
pub fn field_self(mut self) -> Self {
self.reject_beside_consuming("field_self()");
self.fields.push(LocalField::SelfField);
self
}
fn reject_beside_consuming(&self, what: &str) {
if let Some(f) = self.fields.iter().find_map(|f| match f {
LocalField::Fields(d) if d.consuming => Some(&d.func),
_ => None,
}) {
panic!(
"expand_return!({k}).fields_self_into(fields!({f})).{what}: `.fields_self_into(..)` hands \
the value ITSELF over as its fields, so nothing else can read it afterwards — \
it must be the decl's only record. Use `.fields(fields!(..))` with the \
borrowing form of the accessor if you need both.",
k = self.key.as_str(),
f = f,
);
}
}
pub fn fields(mut self, decl: FieldsDecl) -> Self {
self.reject_beside_consuming("fields(..)");
self.reject_second_value_form(&decl);
self.fields.push(LocalField::Fields(decl));
self
}
pub fn fields_self_into(mut self, decl: FieldsDecl) -> Self {
self.reject_second_value_form(&decl);
assert!(
self.fields.is_empty(),
"expand_return!({k}).fields_self_into(fields!({f})): `.fields_self_into(..)` hands the value \
ITSELF over as its fields, so it must be the decl's only record — the records \
already declared would read a value that is gone. Use `.fields(fields!(..))` with \
the borrowing form of the accessor if you need both.",
k = self.key.as_str(),
f = decl.func,
);
self.fields.push(LocalField::Fields(decl.consuming()));
self
}
fn reject_second_value_form(&self, decl: &FieldsDecl) {
assert!(
!self
.fields
.iter()
.any(|f| matches!(f, LocalField::Fields(_))),
"expand_return!({}): the decl already expands a value form (fields!({})) — \
one value form states the whole field set",
self.key.as_str(),
decl.func
);
}
}
#[derive(Clone)]
pub struct FieldsDecl {
func: syn::Ident,
overrides: Vec<(String, ExpandReturnDecl)>,
names: Vec<(String, String)>,
consuming: bool,
}
impl FieldsDecl {
pub fn new(func: syn::Ident) -> Self {
Self {
func,
overrides: Vec::new(),
names: Vec::new(),
consuming: false,
}
}
pub(crate) fn consuming(mut self) -> Self {
self.consuming = true;
self
}
pub fn func(&self) -> &syn::Ident {
&self.func
}
pub fn overrides(&self) -> &[(String, ExpandReturnDecl)] {
&self.overrides
}
pub fn names(&self) -> &[(String, String)] {
&self.names
}
pub fn is_consuming(&self) -> bool {
self.consuming
}
pub fn field(mut self, field: impl AsRef<str>, decl: ExpandReturnDecl) -> Self {
let field = field.as_ref().to_string();
assert!(
!self.overrides.iter().any(|(f, _)| *f == field),
"fields!({}).field(\"{}\", ...): field already has an override — declare its \
complete field set in ONE decl",
self.func,
field
);
self.overrides.push((field, decl));
self
}
pub fn name(mut self, field: impl AsRef<str>, kotlin_name: impl Into<String>) -> Self {
let field = field.as_ref().to_string();
let kotlin_name = kotlin_name.into();
assert!(
!self.names.iter().any(|(f, _)| *f == field),
"fields!({}).name(\"{}\", ...): field is already renamed",
self.func,
field
);
assert!(
!kotlin_name.contains("__"),
"fields!({}).name(\"{}\", \"{}\"): `__` is the reserved chain separator \
and cannot appear in a leaf name",
self.func,
field,
kotlin_name,
);
self.names.push((field, kotlin_name));
self
}
}
pub enum ExpandDecl {
Param(ExpandParamDecl),
Return(ExpandReturnDecl),
}
impl From<ExpandParamDecl> for ExpandDecl {
fn from(d: ExpandParamDecl) -> Self {
Self::Param(d)
}
}
impl From<ExpandReturnDecl> for ExpandDecl {
fn from(d: ExpandReturnDecl) -> Self {
Self::Return(d)
}
}
pub struct FunctionDecl {
rust_ident: syn::Ident,
kotlin_name_override: Option<String>,
param_expands: Vec<(String, ExpandParamDecl)>,
return_expand: Option<ExpandReturnDecl>,
split_on_params: Vec<String>,
local: Option<(syn::Path, Option<syn::Signature>)>,
}
impl FunctionDecl {
pub fn new(rust_ident: syn::Ident) -> Self {
Self {
rust_ident,
kotlin_name_override: None,
param_expands: Vec::new(),
return_expand: None,
split_on_params: Vec::new(),
local: None,
}
}
pub fn rust_ident(&self) -> &syn::Ident {
&self.rust_ident
}
pub fn kotlin_name_override(&self) -> &Option<String> {
&self.kotlin_name_override
}
pub fn param_expands(&self) -> &[(String, ExpandParamDecl)] {
&self.param_expands
}
pub fn return_expand(&self) -> &Option<ExpandReturnDecl> {
&self.return_expand
}
pub fn split_on_params(&self) -> &[String] {
&self.split_on_params
}
pub fn local(&self) -> &Option<(syn::Path, Option<syn::Signature>)> {
&self.local
}
#[allow(clippy::type_complexity)]
pub fn into_parts(
self,
) -> (
syn::Ident,
Option<String>,
Vec<(String, ExpandParamDecl)>,
Option<ExpandReturnDecl>,
Vec<String>,
Option<(syn::Path, Option<syn::Signature>)>,
) {
(
self.rust_ident,
self.kotlin_name_override,
self.param_expands,
self.return_expand,
self.split_on_params,
self.local,
)
}
pub fn new_local(path: syn::Path) -> Self {
assert!(
path.segments.len() >= 2,
"fun!({}): a binding-local fn is called QUALIFIED from the generated file — \
give at least a `crate::`-rooted path (a bare ident declares a `#[prebindgen]` fn)",
quote::quote!(#path)
);
let ident = path.segments.last().expect("non-empty path").ident.clone();
Self {
local: Some((path, None)),
..Self::new(ident)
}
}
pub fn sig(mut self, signature: syn::Signature) -> Self {
let Some((_, slot)) = &mut self.local else {
panic!(
"fun!({}).sig(...): a `#[prebindgen]` fn's signature is read from the \
registry — .sig() applies to path-built binding-local fns (fun!(crate::f))",
self.rust_ident
);
};
assert!(
slot.is_none(),
"fun!({}).sig(...): the signature is already stated",
self.rust_ident
);
*slot = Some(signature);
self
}
pub fn name(mut self, kotlin_name: impl Into<String>) -> Self {
self.kotlin_name_override = Some(kotlin_name.into());
self
}
pub fn expand_param(mut self, param: impl AsRef<str>, decl: ExpandParamDecl) -> Self {
let param = param.as_ref().to_string();
assert!(
!self.param_expands.iter().any(|(p, _)| *p == param),
"fun!({}).expand_param(\"{}\", ...): parameter already has an expand override — \
declare each parameter's complete variant set in ONE decl",
self.rust_ident,
param
);
self.param_expands.push((param, decl));
self
}
pub fn split_on_param(mut self, param: impl AsRef<str>) -> Self {
let param = param.as_ref().to_string();
assert!(
!self.split_on_params.contains(¶m),
"fun!({}).split_on_param(\"{}\"): parameter is already split",
self.rust_ident,
param
);
self.split_on_params.push(param);
self
}
pub fn expand_return(mut self, decl: ExpandReturnDecl) -> Self {
assert!(
self.return_expand.is_none(),
"fun!({}).expand_return(...): the function already has a return expand override — \
declare the complete field set in ONE decl",
self.rust_ident
);
self.return_expand = Some(decl);
self
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub enum ConvertSpec {
PrebindgenFn(syn::Ident),
Trait { repr: syn::Type, fallible: bool },
}
impl ConvertSpec {
pub fn describe(&self) -> String {
match self {
ConvertSpec::PrebindgenFn(f) => format!("`#[prebindgen]` fn `{f}`"),
ConvertSpec::Trait {
repr,
fallible: false,
} => format!("`Into` ⇄ `{}`", repr.to_token_stream()),
ConvertSpec::Trait {
repr,
fallible: true,
} => format!("`TryInto` ⇄ `{}`", repr.to_token_stream()),
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum ConvertDirection {
Input,
Output,
}
impl ConvertDirection {
fn macros(self) -> &'static str {
match self {
ConvertDirection::Input => "from!/try_from!",
ConvertDirection::Output => "into!/try_into!",
}
}
}
#[derive(Clone)]
pub struct ConvertSourceDecl {
kind: ConvertSourceKind,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone)]
pub(crate) enum ConvertSourceKind {
Fun {
ident: syn::Ident,
local: Option<(syn::Path, syn::Signature)>,
},
Repr {
direction: ConvertDirection,
fallible: bool,
ty: syn::Type,
},
}
impl ConvertSourceDecl {
fn repr(direction: ConvertDirection, fallible: bool, ty: syn::Type) -> Self {
Self {
kind: ConvertSourceKind::Repr {
direction,
fallible,
ty,
},
}
}
pub fn from_type(ty: syn::Type) -> Self {
Self::repr(ConvertDirection::Input, false, ty)
}
pub fn try_from_type(ty: syn::Type) -> Self {
Self::repr(ConvertDirection::Input, true, ty)
}
pub fn into_type(ty: syn::Type) -> Self {
Self::repr(ConvertDirection::Output, false, ty)
}
pub fn try_into_type(ty: syn::Type) -> Self {
Self::repr(ConvertDirection::Output, true, ty)
}
}
impl From<FunctionDecl> for ConvertSourceDecl {
fn from(decl: FunctionDecl) -> Self {
assert!(
decl.kotlin_name_override.is_none()
&& decl.param_expands.is_empty()
&& decl.return_expand.is_none(),
"fun!({}) as a conversion source: a conversion fn is never surfaced in \
Kotlin — .name()/expand overrides don't apply",
decl.rust_ident
);
let local = decl.local.map(|(path, sig)| {
let Some(sig) = sig else {
panic!(
"fun!({p}) as a conversion source: a binding-local fn states its \
signature — chain .sig(sig!((params) -> Ret))",
p = quote::quote!(#path)
);
};
(path, sig)
});
Self {
kind: ConvertSourceKind::Fun {
ident: decl.rust_ident,
local,
},
}
}
}
#[derive(Clone)]
pub struct ConvertDecl {
key: TypeKey,
rust_type: Origin<syn::Type>,
input: Option<ConvertSpec>,
output: Option<ConvertSpec>,
domain: Option<crate::RepresentationDomain>,
locals: Vec<(syn::Ident, syn::Path, syn::Signature)>,
}
impl ConvertDecl {
pub fn describe_sources(&self) -> String {
let mut parts = Vec::new();
if let Some(i) = &self.input {
parts.push(format!("input {}", i.describe()));
}
if let Some(o) = &self.output {
parts.push(format!("output {}", o.describe()));
}
if parts.is_empty() {
String::new()
} else {
format!(": {}", parts.join(", "))
}
}
pub fn new(rust_type: syn::Type) -> Self {
reject_builtin_convert_type(&TypeKey::from_type(&rust_type));
Self {
key: TypeKey::from_type(&rust_type),
rust_type: declared_origin(rust_type),
input: None,
output: None,
domain: None,
locals: Vec::new(),
}
}
pub fn key(&self) -> &TypeKey {
&self.key
}
pub fn rust_type(&self) -> &Origin<syn::Type> {
&self.rust_type
}
pub fn input_spec(&self) -> &Option<ConvertSpec> {
&self.input
}
pub fn output_spec(&self) -> &Option<ConvertSpec> {
&self.output
}
pub fn domain(&self) -> &Option<crate::RepresentationDomain> {
&self.domain
}
pub fn locals(&self) -> &[(syn::Ident, syn::Path, syn::Signature)] {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut Vec<(syn::Ident, syn::Path, syn::Signature)> {
&mut self.locals
}
fn set_input(mut self, spec: ConvertSpec) -> Self {
assert!(
self.input.is_none(),
"convert!({}): the input conversion is already declared — \
declare each direction's conversion in ONE .input()/.output() call",
self.key.as_str()
);
self.input = Some(spec);
self
}
fn set_output(mut self, spec: ConvertSpec) -> Self {
assert!(
self.output.is_none(),
"convert!({}): the output conversion is already declared — \
declare each direction's conversion in ONE .input()/.output() call",
self.key.as_str()
);
self.output = Some(spec);
self
}
fn check_repr(&self, method: &str, repr: &syn::Type) {
assert!(
TypeKey::from_type(repr) != self.key,
"convert!({k}).{method}: the representable type must differ from `{k}` itself",
k = self.key.as_str()
);
}
fn spec_of(
&mut self,
direction: ConvertDirection,
method: &str,
src: ConvertSourceDecl,
) -> ConvertSpec {
match src.kind {
ConvertSourceKind::Fun { ident, local } => {
if let Some((path, sig)) = local {
self.locals.push((ident.clone(), path, sig));
}
ConvertSpec::PrebindgenFn(ident)
}
ConvertSourceKind::Repr {
direction: stated,
fallible,
ty,
} => {
assert!(
stated == direction,
"convert!({k}).{method}(...): the source was built with {got} — \
an {method} conversion is built with {want}",
k = self.key.as_str(),
got = stated.macros(),
want = direction.macros(),
);
self.check_repr(method, &ty);
ConvertSpec::Trait { repr: ty, fallible }
}
}
}
pub fn input(mut self, src: impl Into<ConvertSourceDecl>) -> Self {
let spec = self.spec_of(ConvertDirection::Input, "input", src.into());
self.set_input(spec)
}
pub fn output(mut self, src: impl Into<ConvertSourceDecl>) -> Self {
let spec = self.spec_of(ConvertDirection::Output, "output", src.into());
self.set_output(spec)
}
pub fn valid_range<T, R>(mut self, range: R) -> Self
where
T: crate::DomainScalar,
R: ::core::ops::RangeBounds<T>,
{
assert!(
self.domain.is_none(),
"convert!({}): the representation domain is already declared",
self.key.as_str()
);
self.domain = Some(crate::RepresentationDomain::range(range));
self
}
pub fn valid_values<T>(mut self, values: impl IntoIterator<Item = T>) -> Self
where
T: crate::DomainScalar,
{
assert!(
self.domain.is_none(),
"convert!({}): the representation domain is already declared",
self.key.as_str()
);
self.domain = Some(crate::RepresentationDomain::values(values));
self
}
pub fn exclude_values<T>(mut self, values: impl IntoIterator<Item = T>) -> Self
where
T: crate::DomainScalar,
{
self.domain
.as_mut()
.unwrap_or_else(|| {
panic!(
"convert!({}): .exclude_values(...) requires .valid_range(...) \
or .valid_values(...) first",
self.key.as_str()
)
})
.exclude(values);
self
}
}
fn reject_builtin_convert_type(key: &TypeKey) {
const BUILTINS: &[&str] = &[
"usize", "isize", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128",
"f32", "f64", "bool", "char", "str", "String",
];
assert!(
!BUILTINS.contains(&key.as_str()),
"convert!({}): builtins already have converters — wrap the builtin in a newtype instead",
key.as_str()
);
}
pub fn local_path_prefix(path: &syn::Path) -> String {
path.segments
.iter()
.take(path.segments.len() - 1)
.map(|s| s.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}