Skip to main content

prebindgen_jni/jni/
decl.rs

1//! Declaration objects: one standalone, independently-constructible value
2//! type per kind of thing `Declarations` can be told about (a `ptr_class`, an
3//! `enum_class`, a function, a scalar wire mapping, …), plus the `PackageDecl`
4//! that aggregates the package-scoped ones. Each type is both its own
5//! "builder" and the final value `Declarations`/`PackageDecl` accepts — no separate
6//! `Builder`/`Decl` split, no terminal `.build()` call.
7//!
8//! `Declarations` itself only ever *accepts* fully-built values of these types
9//! (`JniGenBuilder::package`, `JniGenBuilder::expand`, `JniGenBuilder::convert`, in
10//! `builder.rs`); none of them reach back
11//! into any `Declarations` state while being built.
12
13// Language-neutral declaration vocabulary moved to `api::core::decl` (it
14// references only `TypeKey`/`Origin<syn::Type>`/plain `syn` types, nothing
15// Kotlin/JNI-specific) — re-exported here so `jni`'s `pub use decl::*;` (and
16// therefore `prebindgen::lang::*` / `prebindgen_registry::*`) is unaffected.
17pub(crate) use prebindgen_registry::decl::{
18    declared_origin, local_path_prefix, ConvertSpec, LocalField, LocalVariant,
19};
20pub use prebindgen_registry::decl::{
21    ConvertDecl, ConvertSourceDecl, ExpandDecl, ExpandParamDecl, ExpandReturnDecl, FieldsDecl,
22    FunctionDecl,
23};
24
25use super::*;
26
27// Class members are stored as the full `(FunctionDecl, MemberKind)` pair —
28// not a reduced ident+name record — so the `FunctionDecl`'s per-fn
29// `.expand_param`/`.expand_return` overrides survive to `builder.rs`'s
30// `accept_members`, which applies them exactly like `accept_function` does
31// for free package functions.
32
33// ──────────────────────────────────────────────────────────────────────
34// Decl constructor macros — one per decl type built from bare Rust syntax
35// or with no arguments at all. Each is restricted at the `macro_rules!`
36// fragment level (`:ty` / `:ident`) and expands to a call with a hard-coded
37// concrete return type, so `syn::parse_quote!`/`syn::parse_str` never has to
38// infer its output type against a generic bound — there is no `E0283` risk
39// to route around here, unlike a bare `syn::parse_quote!(...)` would have if
40// fed into a generic `impl Into<T>` parameter.
41// ──────────────────────────────────────────────────────────────────────
42
43/// Build a [`PtrClassDecl`] directly from a bare Rust type: `ptr_class!(Foo)`
44/// is `PtrClassDecl::new(<Foo as a parsed syn::Type>)`.
45#[macro_export]
46macro_rules! ptr_class {
47    ($t:ty) => {
48        $crate::PtrClassDecl::new(prebindgen_registry::__macro_support::parse_type(
49            stringify!($t),
50        ))
51    };
52}
53
54/// Build an [`EnumClassDecl`] directly from a bare Rust type. See [`ptr_class!`].
55#[macro_export]
56macro_rules! enum_class {
57    ($t:ty) => {
58        $crate::EnumClassDecl::new(prebindgen_registry::__macro_support::parse_type(
59            stringify!($t),
60        ))
61    };
62}
63
64/// Build a [`SealedClassDecl`] directly from a bare Rust type. See
65/// [`ptr_class!`].
66#[macro_export]
67macro_rules! sealed_class {
68    ($t:ty) => {
69        $crate::SealedClassDecl::new(prebindgen_registry::__macro_support::parse_type(
70            stringify!($t),
71        ))
72    };
73}
74
75/// Build a [`VariantDecl`] from a bare variant ident, for
76/// [`SealedClassDecl::variant`]: `variant!(PeriodicQueries).name("Periodic")`.
77#[macro_export]
78macro_rules! variant {
79    ($name:ident) => {
80        $crate::VariantDecl::new(stringify!($name))
81    };
82}
83
84/// Build a [`DataClassDecl`] directly from a bare Rust type. See [`ptr_class!`].
85#[macro_export]
86macro_rules! data_class {
87    ($t:ty) => {
88        $crate::DataClassDecl::new(prebindgen_registry::__macro_support::parse_type(
89            stringify!($t),
90        ))
91    };
92}
93
94/// Build a [`ConstDecl`] from a bare ident: `constant!(MAX_LEN)` is
95/// `ConstDecl::new(prebindgen_registry::ident!(MAX_LEN))`.
96///
97/// What the ident names depends on where the decl lands:
98/// * in `.constant(...)` it is always the Kotlin **`val` name**, and in the
99///   **bare** form (no source modifier) it is *additionally* the lookup key
100///   of the same-named `#[prebindgen]` const — `.fun(…)` / `.with(…)` /
101///   `.expr(…)` replace that lookup with the stated value source (see the
102///   four-source example on [`ConstDecl`](crate::ConstDecl));
103/// * in `.ignore(constant!(X))` it is *only* the `#[prebindgen]` const
104///   lookup key — nothing is emitted, so sources and `.name()` are rejected
105///   there.
106#[macro_export]
107macro_rules! constant {
108    ($name:ident) => {
109        $crate::ConstDecl::new(prebindgen_registry::ident!($name))
110    };
111}
112
113/// Build a [`PackageDecl`] directly: `package!("model")` is
114/// `PackageDecl::new("model")`; `package!()` (no args) is the base package
115/// (`PackageDecl::new("")`).
116#[macro_export]
117macro_rules! package {
118    () => {
119        $crate::PackageDecl::new("")
120    };
121    ($name:expr) => {
122        $crate::PackageDecl::new($name)
123    };
124}
125
126// ──────────────────────────────────────────────────────────────────────
127// Class-kind decls
128// ──────────────────────────────────────────────────────────────────────
129
130/// Declares a Rust type as an **opaque handle**. In Kotlin it becomes a
131/// closeable class holding a pointer to the real object, which keeps living
132/// in Rust; the object crosses the boundary as that pointer, never copied.
133/// Use this for types with identity and a lifecycle — sessions, subscribers,
134/// configs, key expressions — that you pass around and eventually `close()`,
135/// as opposed to plain data you copy across ([`data_class!`](crate::data_class)).
136///
137/// A type that never materializes in Kotlin needs **no class declaration at
138/// all**: give it boundary decls only ([`expand_param!`](prebindgen_registry::expand_param)
139/// / [`expand_return!`](prebindgen_registry::expand_return)) and it stays rust-side-only —
140/// built from ingredients on the way in, decomposed into fields on the way
141/// out.
142///
143/// Build one with [`ptr_class!`](crate::ptr_class), add it to a
144/// [`PackageDecl`], and hand that to [`JniGenBuilder::package`].
145///
146/// A `PtrClassDecl` defines the **Kotlin class only** — its name
147/// ([`name`](Self::name)), its instance methods ([`method`](Self::method)), and its
148/// companion-object factories ([`constructor`](Self::constructor)). How the
149/// type crosses the FFI boundary by default — accepted as which parameter
150/// variants, returned as which field set — is declared separately with
151/// [`expand_param!`](prebindgen_registry::expand_param) / [`expand_return!`](prebindgen_registry::expand_return)
152/// handed to [`JniGenBuilder::expand`]; any single
153/// function can override those defaults locally (see [`FunctionDecl`]).
154///
155/// ```
156/// // A KeyExpr handle exposing `str()` as an instance method.
157/// let _ = prebindgen_jni::ptr_class!(KeyExpr)
158///     .method(prebindgen_registry::fun!(keyexpr_get_str).name("str"));
159/// ```
160/// Deliberately has no verbatim type mapping: the generated typed-handle
161/// class OWNS a lifecycle contract — the `NativeHandle` base, the `ptr`
162/// slot, `close()`, the lock protocol, and the paired `freePtr` extern —
163/// that an arbitrary existing Kotlin type cannot be assumed to honor.
164/// Customize it from above instead: [`interface`](Self::interface) /
165/// [`implements`](Self::implements).
166pub struct PtrClassDecl {
167    pub(crate) key: TypeKey,
168    /// The type this declaration was **written with** — the `X` the macro
169    /// received. Kept because the declaration is where it came from: recovering
170    /// it later *from* the key was reasoning backwards from an identity (#291).
171    pub(crate) rust_type: Origin<syn::Type>,
172    pub(crate) name_override: Option<String>,
173    pub(crate) members: Vec<(FunctionDecl, MemberKind)>,
174    pub(crate) iface: IfaceOpts,
175    pub(crate) gc_managed: bool,
176}
177
178/// The interface-related options every class decl carries (see
179/// [`class_interface_methods!`]): the generated-interface switch + name
180/// override, and the `.implements(...)` list. The two features are
181/// orthogonal; used together, a user interface extends the generated one.
182#[derive(Clone, Default)]
183pub(crate) struct IfaceOpts {
184    pub(crate) enabled: bool,
185    pub(crate) name_override: Option<String>,
186    pub(crate) implements: Vec<String>,
187}
188
189/// The three interface methods shared verbatim by all four class decls —
190/// generated per decl so the panic messages name the right decl macro.
191macro_rules! class_interface_methods {
192    ($decl_macro:literal) => {
193        /// Emit a generated Kotlin **interface** mirroring this class's
194        /// public instance surface, and make the class implement it (every
195        /// class-body member gains the `override` modifier). The interface
196        /// is named by [`interface_name`](Self::interface_name), else the
197        /// [`JniGenBuilder::set_interface_name_mangle`] hook over the final class
198        /// name (default: append `"Api"`).
199        ///
200        /// This is the compiler-checked half of the integration hatch: a
201        /// hand-written interface that *extends* the generated one can build
202        /// default members over the class's real signatures — no
203        /// hand-replication. Pair it with [`implements`](Self::implements)
204        /// to attach that hand-written interface to the class. (For
205        /// behavior-only injection, a Kotlin extension function needs no
206        /// declaration at all.)
207        pub fn interface(mut self) -> Self {
208            self.iface.enabled = true;
209            self
210        }
211
212        /// Name the generated interface literally (relative, no dots),
213        /// bypassing the [`JniGenBuilder::set_interface_name_mangle`] hook.
214        /// Implies [`interface`](Self::interface).
215        pub fn interface_name(mut self, name: impl Into<String>) -> Self {
216            let name = name.into();
217            assert!(
218                !name.trim().is_empty(),
219                concat!($decl_macro, "!({}).interface_name(...): the name is empty"),
220                self.key.as_str()
221            );
222            self.iface.enabled = true;
223            self.iface.name_override = Some(name);
224            self
225        }
226
227        /// Add a Kotlin **interface** to the generated class's supertype
228        /// list — the class implements it *nominally*: its abstract members
229        /// must be satisfied by the generated surface or carry default
230        /// implementations. `iface` is an FQN (dotted names are imported and
231        /// shortened) or a same-package name; call again to add several.
232        ///
233        /// Orthogonal to [`interface`](Self::interface) — but to abstract
234        /// over the class's own members from your interface, enable the
235        /// generated interface and make yours extend it (that is what turns
236        /// mismatches into compile errors in YOUR file).
237        pub fn implements(mut self, iface: impl Into<String>) -> Self {
238            let iface = iface.into();
239            assert!(
240                !iface.trim().is_empty(),
241                concat!(
242                    $decl_macro,
243                    "!({}).implements(...): the interface name is empty"
244                ),
245                self.key.as_str()
246            );
247            assert!(
248                !self.iface.implements.contains(&iface),
249                concat!(
250                    $decl_macro,
251                    "!({}).implements(\"{}\"): the interface is already declared"
252                ),
253                self.key.as_str(),
254                iface
255            );
256            self.iface.implements.push(iface);
257            self
258        }
259    };
260}
261
262impl PtrClassDecl {
263    pub fn new(rust_type: syn::Type) -> Self {
264        Self {
265            key: TypeKey::from_type(&rust_type),
266            rust_type: declared_origin(rust_type),
267            name_override: None,
268            members: Vec::new(),
269            iface: IfaceOpts::default(),
270            gc_managed: false,
271        }
272    }
273
274    /// Make instances of this handle class **GC-managed**: an unreachable
275    /// handle whose native box was not otherwise released is freed by a
276    /// shared `java.lang.ref.Cleaner`.
277    ///
278    /// The pointer of a GC-managed handle lives in a separate atomic cell
279    /// (tag bit and all) so the cleaner action can settle the release after
280    /// the handle object itself is gone; the untagged→tagged transition is a
281    /// CAS and doubles as the once-only free ticket — explicit `close()`
282    /// frees eagerly, `take()`/by-value consumption void the ticket, the GC
283    /// action frees only if it wins. Address bits still never change, so the
284    /// lock-ordering key stays immutable, and `isClosed()`/Rust-side
285    /// tagged-pointer guards are unchanged.
286    ///
287    /// Opt in for handles whose owner may never close them — value-like
288    /// types (an `Encoding`) and long-lived resources where GC is the leak
289    /// backstop behind an explicit `close()`. Leave hot-path per-message
290    /// handles opted out: registration costs a few small allocations per
291    /// instance.
292    pub fn gc_managed(mut self) -> Self {
293        self.gc_managed = true;
294        self
295    }
296
297    /// Rename the generated Kotlin class. By default it is named after the
298    /// Rust type (via the [`JniGenBuilder::set_ptr_class_name_mangle`] hook); `.name("Foo")`
299    /// sets it literally instead. Relative name, no dots — the package comes
300    /// from the enclosing [`PackageDecl`].
301    pub fn name(mut self, name: impl Into<String>) -> Self {
302        self.name_override = Some(name.into());
303        self
304    }
305
306    class_interface_methods!("ptr_class");
307
308    /// Expose a `#[prebindgen]` method as a Kotlin **instance method** of this
309    /// class. `rust_fun` must take `&Self` first — that receiver becomes
310    /// Kotlin's `this` and drops out of the signature; any further parameters
311    /// become the method's arguments. Name it with
312    /// `fun!(rust_name).name("kotlinName")` (default: the Rust name
313    /// camel-cased).
314    pub fn method(mut self, rust_fun: FunctionDecl) -> Self {
315        self.members.push((rust_fun, MemberKind::Method));
316        self
317    }
318
319    /// Expose a `#[prebindgen]` factory as a Kotlin **companion-object
320    /// factory** — callers write `Class.name(...)`. `rust_fun` returns `Self`
321    /// (or `Result<Self, E>`) and its parameters become the factory's
322    /// arguments. A constructor can also serve as a build option in a
323    /// [`expand_param!`](prebindgen_registry::expand_param) variant list.
324    pub fn constructor(mut self, rust_fun: FunctionDecl) -> Self {
325        self.members.push((rust_fun, MemberKind::Constructor));
326        self
327    }
328}
329
330impl From<syn::Type> for PtrClassDecl {
331    fn from(rust_type: syn::Type) -> Self {
332        Self::new(rust_type)
333    }
334}
335
336/// Declares a Rust C-like `enum` as a Kotlin `enum class`. The variants
337/// cross the boundary as their `i32` discriminants and Kotlin gets a real
338/// `enum class` with a `fromInt(...)` companion. The enum must be
339/// unit-variant only and `#[repr(i32)]`-style with explicit discriminants,
340/// so both sides agree on the numbers.
341///
342/// A **data-carrying** enum is a different Kotlin surface — a `sealed
343/// interface` whose variants carry their payload — and is declared with
344/// `sealed_class!` instead. Handing one to `enum_class!` is a hard error,
345/// not a silent upgrade: the value would have to cross as a bare
346/// discriminant, dropping the payload.
347///
348/// Has no `.method`/`.constructor` by rule, not omission: members belong to
349/// class kinds whose instances can re-enter Rust as an object (handle /
350/// blob / field leaves). An enum value is a bare scalar with no object
351/// identity — a "method" on it is just a free function taking the enum.
352pub struct EnumClassDecl {
353    pub(crate) key: TypeKey,
354    /// The type this declaration was **written with** — the `X` the macro
355    /// received. Kept because the declaration is where it came from: recovering
356    /// it later *from* the key was reasoning backwards from an identity (#291).
357    pub(crate) rust_type: Origin<syn::Type>,
358    pub(crate) name_override: Option<String>,
359    pub(crate) iface: IfaceOpts,
360}
361
362impl EnumClassDecl {
363    pub fn new(rust_type: syn::Type) -> Self {
364        Self {
365            key: TypeKey::from_type(&rust_type),
366            rust_type: declared_origin(rust_type),
367            name_override: None,
368            iface: IfaceOpts::default(),
369        }
370    }
371
372    /// Override the Kotlin **class name** (relative, no dots).
373    pub fn name(mut self, name: impl Into<String>) -> Self {
374        self.name_override = Some(name.into());
375        self
376    }
377
378    class_interface_methods!("enum_class");
379}
380
381impl From<syn::Type> for EnumClassDecl {
382    fn from(rust_type: syn::Type) -> Self {
383        Self::new(rust_type)
384    }
385}
386
387/// Declares a Rust **data-carrying** enum as a Kotlin `sealed interface`
388/// whose variant classes are nested inside it — the surface a sum type gets
389/// where the target language has sums natively.
390///
391/// ```ignore
392/// .class(sealed_class!(RecoveryMode)
393///     .variant(variant!(PeriodicQueries).name("Periodic")))
394/// ```
395///
396/// ```kotlin
397/// public sealed interface RecoveryMode {
398///     public data class Periodic(val v0: Long) : RecoveryMode
399///     public data object Heartbeat : RecoveryMode
400///     public companion object { @JvmStatic public fun fromParts(…): RecoveryMode }
401/// }
402/// ```
403///
404/// A payload-less alternative becomes a `data object`; the variant classes
405/// are **nested** so their names cannot collide package-wide. Tuple payload
406/// fields surface as `v0`, `v1`, …; named fields keep their (camelCased)
407/// names.
408///
409/// The counterpart of [`enum_class!`](crate::enum_class), which is for the
410/// unit-variant-only case that crosses as a bare discriminant. Handing a
411/// payload enum to `enum_class!` — or a fieldless one here — is a hard
412/// error naming the other, never a silent upgrade.
413///
414/// Like `enum_class!` it has no `.method` / `.constructor`: a sum value has
415/// no object identity Rust-side, so a "method" on it is a free function
416/// taking it.
417pub struct SealedClassDecl {
418    pub(crate) key: TypeKey,
419    /// The type this declaration was **written with** — the `X` the macro
420    /// received. Kept because the declaration is where it came from: recovering
421    /// it later *from* the key was reasoning backwards from an identity (#291).
422    pub(crate) rust_type: Origin<syn::Type>,
423    pub(crate) name_override: Option<String>,
424    pub(crate) variants: Vec<VariantDecl>,
425    pub(crate) iface: IfaceOpts,
426}
427
428impl SealedClassDecl {
429    pub fn new(rust_type: syn::Type) -> Self {
430        Self {
431            key: TypeKey::from_type(&rust_type),
432            rust_type: declared_origin(rust_type),
433            name_override: None,
434            variants: Vec::new(),
435            iface: IfaceOpts::default(),
436        }
437    }
438
439    /// Override the Kotlin **interface name** (relative, no dots).
440    pub fn name(mut self, name: impl Into<String>) -> Self {
441        self.name_override = Some(name.into());
442        self
443    }
444
445    /// Configure one variant — currently its Kotlin class name. Undeclared
446    /// variants keep their Rust ident; declaring a variant that the enum
447    /// does not have is a hard error.
448    pub fn variant(mut self, decl: VariantDecl) -> Self {
449        self.variants.push(decl);
450        self
451    }
452
453    class_interface_methods!("sealed_class");
454}
455
456impl From<syn::Type> for SealedClassDecl {
457    fn from(rust_type: syn::Type) -> Self {
458        Self::new(rust_type)
459    }
460}
461
462/// One variant of a [`SealedClassDecl`]. Build it with
463/// [`variant!`](crate::variant).
464pub struct VariantDecl {
465    pub(crate) rust_ident: String,
466    pub(crate) name_override: Option<String>,
467}
468
469impl VariantDecl {
470    pub fn new(rust_ident: impl Into<String>) -> Self {
471        Self {
472            rust_ident: rust_ident.into(),
473            name_override: None,
474        }
475    }
476
477    /// Override this variant's Kotlin **class name** (relative, no dots).
478    pub fn name(mut self, name: impl Into<String>) -> Self {
479        self.name_override = Some(name.into());
480        self
481    }
482}
483
484/// Declares a Rust struct as a Kotlin `data class`. Its fields cross the
485/// boundary individually and Kotlin reassembles the object with a generated
486/// `fromParts(...)` — no Rust-side heap object, no handle to close. Use this
487/// for plain immutable data you copy across, as opposed to
488/// [`ptr_class!`](crate::ptr_class) handles.
489///
490/// Members work like every class kind whose instance can re-enter Rust —
491/// here the receiver re-enters as its **field leaves** (the same call-site
492/// destructuring a data-class parameter gets), just rebased to `this`.
493pub struct DataClassDecl {
494    pub(crate) key: TypeKey,
495    /// The type this declaration was **written with** — the `X` the macro
496    /// received. Kept because the declaration is where it came from: recovering
497    /// it later *from* the key was reasoning backwards from an identity (#291).
498    pub(crate) rust_type: Origin<syn::Type>,
499    pub(crate) name_override: Option<String>,
500    pub(crate) jobject_input: bool,
501    pub(crate) iface: IfaceOpts,
502    pub(crate) members: Vec<(FunctionDecl, MemberKind)>,
503}
504
505impl DataClassDecl {
506    pub fn new(rust_type: syn::Type) -> Self {
507        Self {
508            key: TypeKey::from_type(&rust_type),
509            rust_type: declared_origin(rust_type),
510            name_override: None,
511            jobject_input: false,
512            iface: IfaceOpts::default(),
513            members: Vec::new(),
514        }
515    }
516
517    /// Override the Kotlin **class name** (relative, no dots).
518    pub fn name(mut self, name: impl Into<String>) -> Self {
519        self.name_override = Some(name.into());
520        self
521    }
522
523    /// Explicitly keep this data class object-shaped on the Kotlin → Rust
524    /// boundary. By default a data class must flatten completely into its
525    /// transitive field leaves; generation fails rather than silently falling
526    /// back to Rust-side `JObject` field reads. This escape hatch is intended
527    /// for recursive/identity-bearing graphs, legacy ABI compatibility, or a
528    /// deliberately chosen object boundary.
529    ///
530    /// When the marked type is nested inside an unmarked data class, only the
531    /// marked branch crosses as a `JObject`; its siblings remain flattened.
532    /// Rust → Kotlin output construction is unaffected.
533    pub fn jobject_input(mut self) -> Self {
534        self.jobject_input = true;
535        self
536    }
537
538    class_interface_methods!("data_class");
539
540    /// Expose a `#[prebindgen]` reader (`f(&Self) -> R`) as an instance
541    /// method on the generated data class (see [`PtrClassDecl::method`]) — the
542    /// receiver crosses as `this`'s field leaves, exactly like a data-class
543    /// parameter.
544    pub fn method(mut self, rust_fun: FunctionDecl) -> Self {
545        self.members.push((rust_fun, MemberKind::Method));
546        self
547    }
548
549    /// Expose a `#[prebindgen]` factory as a companion-object factory
550    /// (see [`PtrClassDecl::constructor`]).
551    pub fn constructor(mut self, rust_fun: FunctionDecl) -> Self {
552        self.members.push((rust_fun, MemberKind::Constructor));
553        self
554    }
555}
556
557impl From<syn::Type> for DataClassDecl {
558    fn from(rust_type: syn::Type) -> Self {
559        Self::new(rust_type)
560    }
561}
562
563/// Unifies the four class-kind decls into one type so [`PackageDecl::class`]
564/// can expose a single entry point. Deliberately **no**
565/// `impl From<syn::Type> for ClassDecl` — a bare `syn::Type` alone doesn't
566/// say which of the four kinds it should become, so every declaration names
567/// its kind explicitly via the matching constructor macro:
568/// `.class(prebindgen::ptr_class!(Storage))`,
569/// `.class(prebindgen::enum_class!(Priority))`, etc.
570pub enum ClassDecl {
571    Ptr(PtrClassDecl),
572    Enum(EnumClassDecl),
573    Sealed(SealedClassDecl),
574    Data(DataClassDecl),
575}
576
577impl From<PtrClassDecl> for ClassDecl {
578    fn from(d: PtrClassDecl) -> Self {
579        Self::Ptr(d)
580    }
581}
582impl From<EnumClassDecl> for ClassDecl {
583    fn from(d: EnumClassDecl) -> Self {
584        Self::Enum(d)
585    }
586}
587impl From<SealedClassDecl> for ClassDecl {
588    fn from(d: SealedClassDecl) -> Self {
589        Self::Sealed(d)
590    }
591}
592impl From<DataClassDecl> for ClassDecl {
593    fn from(d: DataClassDecl) -> Self {
594        Self::Data(d)
595    }
596}
597
598/// A [`ConstDecl`]'s **value source** — where the constant's value comes
599/// from. Mirrors `convert!`'s source vocabulary at the nullary edge:
600/// prebindgen item (bare) / prebindgen fn (`.fun`) / binding-local named fn
601/// (`.with`) / expression (`.expr` — const-only: an expression binds no
602/// arguments only when there is no value flowing in).
603// Build-time declaration object, a handful per binding — the Expr variant's
604// size is irrelevant, same trade-off as `ConvertSpec`.
605#[allow(clippy::large_enum_variant)]
606pub(crate) enum ConstSource {
607    /// The same-named `#[prebindgen]` const (the bare `constant!(X)` form).
608    Item,
609    /// A **nullary** `#[prebindgen]` fn; the value type is read from its
610    /// registry signature and the result flows through the ordinary
611    /// generated wrapper, consumed as an eager `val`.
612    Fun(syn::Ident),
613    /// A binding-defined initializer expression with a **stated** value
614    /// type, evaluated once inside a generated nullary JNI getter (with a
615    /// glob import of every source module in scope). `.with(ty, path)`
616    /// lowers here as `path()`.
617    Expr { ty: syn::Type, expr: syn::Expr },
618}
619
620/// Declares one **constant** for emission: a lazily-initialized top-level
621/// Kotlin `val` (`by lazy`) in its package's `.kt` file, initialized on
622/// first use through a generated nullary JNI getter (the value type goes
623/// through the ordinary output-converter machinery, exactly like a function
624/// return; zero JNI calls at class-load).
625///
626/// Build one with [`constant!`](crate::constant) — the ident is the `val`
627/// name — and pick the value source:
628///
629/// ```rust,ignore
630/// .constant(constant!(MAX_LEN))                          // #[prebindgen] const MAX_LEN
631/// .constant(constant!(TAG_RUNTIME).fun(fun!(tag_runtime)))  // nullary #[prebindgen] fn
632/// .constant(constant!(VERSION).with(ty!(String), path!(crate::version)))  // binding-local fn
633/// .constant(constant!(BANNER).expr(ty!(String), expr!(format!("{A}:{B}"))))  // expression
634/// ```
635///
636/// Note the ident's role split: only the **bare** form also looks up the
637/// same-named `#[prebindgen]` const (`MAX_LEN` above); under a stated
638/// source the ident is purely the `val` name (`TAG_RUNTIME`, `VERSION`,
639/// `BANNER` name no Rust item). In `.ignore(constant!(X))` the ident is
640/// only the const lookup key.
641///
642/// For declaration loops build the subject at runtime with
643/// [`ConstDecl::named`]. Opaque-handle-typed (and `Result`-typed) constants
644/// are rejected for every source — expose a factory function instead.
645pub struct ConstDecl {
646    /// Subject ident: the default `val` name; for the [`ConstSource::Item`]
647    /// source also the `#[prebindgen]` const to look up.
648    pub(crate) rust_ident: syn::Ident,
649    pub(crate) kotlin_name_override: Option<String>,
650    pub(crate) source: ConstSource,
651}
652
653impl ConstDecl {
654    pub fn new(rust_ident: syn::Ident) -> Self {
655        Self {
656            rust_ident,
657            kotlin_name_override: None,
658            source: ConstSource::Item,
659        }
660    }
661
662    /// Runtime form of [`constant!`](crate::constant) for declaration
663    /// loops: `ConstDecl::named(format!("ENCODING_{n}")).expr(ty, expr)`.
664    /// The name must be a valid identifier (it seeds the extern symbol).
665    pub fn named(name: impl AsRef<str>) -> Self {
666        let name = name.as_ref();
667        let ident: syn::Ident = syn::parse_str(name)
668            .unwrap_or_else(|e| panic!("constant name `{name}` is not a valid identifier: {e}"));
669        Self::new(ident)
670    }
671
672    /// Set the Kotlin-side `val` name. Default: the subject ident verbatim
673    /// (`MAX_LEN` → `val MAX_LEN` — SCREAMING_SNAKE is the Kotlin constant
674    /// convention too).
675    pub fn name(mut self, kotlin_name: impl Into<String>) -> Self {
676        self.kotlin_name_override = Some(kotlin_name.into());
677        self
678    }
679
680    /// The declared `val` name (override, else the subject ident).
681    pub(crate) fn val_name(&self) -> String {
682        self.kotlin_name_override
683            .clone()
684            .unwrap_or_else(|| self.rust_ident.to_string())
685    }
686
687    fn set_source(mut self, source: ConstSource) -> Self {
688        assert!(
689            matches!(self.source, ConstSource::Item),
690            "constant `{}`: value source already set — a constant has exactly one source \
691             (.fun / .with / .expr)",
692            self.rust_ident
693        );
694        self.source = source;
695        self
696    }
697
698    /// Value source: a **nullary** `#[prebindgen]` fn (e.g. a value a Rust
699    /// `const` cannot express — a string only obtainable through a runtime
700    /// `Display`). The value type is read from the fn's signature; the fn
701    /// must take no parameters and must not return `Result`.
702    pub fn fun(self, decl: FunctionDecl) -> Self {
703        assert!(
704            decl.param_expands().is_empty() && decl.return_expand().is_none(),
705            "constant `{}`: expand overrides don't apply to a constant source fn `{}`",
706            self.rust_ident,
707            decl.rust_ident()
708        );
709        assert!(
710            decl.kotlin_name_override().is_none(),
711            "constant `{}`: the val name belongs on `constant!(…)` (or its `.name(…)`), \
712             not on the source fn `{}`",
713            self.rust_ident,
714            decl.rust_ident()
715        );
716        self.set_source(ConstSource::Fun(decl.rust_ident().clone()))
717    }
718
719    /// Value source: a **binding-local nullary fn** named by path —
720    /// `(stated value type, path)`, the const analog of
721    /// [`FunctionDecl::new_local`](prebindgen_registry::FunctionDecl::new_local).
722    /// The fn lives in the binding crate (callable because the generated file
723    /// compiles inside it):
724    /// `fn() -> T`.
725    pub fn with(self, ty: syn::Type, path: syn::Path) -> Self {
726        let expr: syn::Expr = syn::parse_quote!(#path());
727        self.set_source(ConstSource::Expr { ty, expr })
728    }
729
730    /// Value source: a binding-defined **expression** with a stated value
731    /// type, evaluated once inside the generated getter with a glob import
732    /// of every source module in scope — so it composes source-crate
733    /// `#[prebindgen]` items freely, e.g.
734    /// `expr!(encoding_to_string(encoding_const_text_plain()))`. This
735    /// source exists only for constants: an expression binds no arguments
736    /// exactly when nothing flows in (a unary conversion source must be a
737    /// named callable — see [`ConvertDecl`]). Fns referenced only inside
738    /// expressions are undeclared to the registry — acknowledge them via
739    /// [`JniGenBuilder::ignore`] (+ [`matching`](crate::matching)).
740    pub fn expr(self, ty: syn::Type, expr: syn::Expr) -> Self {
741        self.set_source(ConstSource::Expr { ty, expr })
742    }
743}
744
745/// Internal storage form of an expression-backed constant (the lowered
746/// `.with` / `.expr` sources of [`ConstDecl`]).
747#[derive(Clone)]
748pub(crate) struct ConstExprDecl {
749    pub(crate) kotlin_name: String,
750    pub(crate) ty: syn::Type,
751    pub(crate) expr: syn::Expr,
752}
753
754// ──────────────────────────────────────────────────────────────────────
755// IgnoreDecl — one acceptor for acknowledged-unbound items
756// ──────────────────────────────────────────────────────────────────────
757
758/// Declares a `#[prebindgen]` item this binding deliberately does NOT
759/// bind: nothing is emitted for it and the registry's per-item "skipping
760/// undeclared" warning is suppressed. One acceptor
761/// ([`JniGenBuilder::ignore`]), the kind carried by what you built:
762///
763/// ```rust,ignore
764/// .ignore(fun!(string_len))                                // a fn
765/// .ignore(ty!(InternalThing))                              // a struct/enum
766/// .ignore(constant!(INTERNAL_MAGIC))                       // a const
767/// .ignore(matching(|n| n.starts_with("encoding_const_")))  // a naming family
768/// ```
769pub struct IgnoreDecl(pub(crate) IgnoreKind);
770
771pub(crate) enum IgnoreKind {
772    Fun(syn::Ident),
773    Type(TypeKey),
774    Const(syn::Ident),
775    Matching(prebindgen_registry::NamePredicate),
776}
777
778impl From<FunctionDecl> for IgnoreDecl {
779    fn from(decl: FunctionDecl) -> Self {
780        assert!(
781            decl.kotlin_name_override().is_none()
782                && decl.param_expands().is_empty()
783                && decl.return_expand().is_none(),
784            "ignore(fun!({})): an ignored fn is never surfaced — \
785             .name()/expand overrides don't apply",
786            decl.rust_ident()
787        );
788        IgnoreDecl(IgnoreKind::Fun(decl.rust_ident().clone()))
789    }
790}
791
792impl From<syn::Type> for IgnoreDecl {
793    fn from(ty: syn::Type) -> Self {
794        IgnoreDecl(IgnoreKind::Type(TypeKey::from_type(&ty)))
795    }
796}
797
798impl From<ConstDecl> for IgnoreDecl {
799    fn from(decl: ConstDecl) -> Self {
800        assert!(
801            matches!(decl.source, ConstSource::Item) && decl.kotlin_name_override.is_none(),
802            "ignore(constant!({})): an ignore names a `#[prebindgen]` const — \
803             value sources/.name() don't apply",
804            decl.rust_ident
805        );
806        IgnoreDecl(IgnoreKind::Const(decl.rust_ident))
807    }
808}
809
810/// Bulk [`IgnoreDecl`]: acknowledge every `#[prebindgen]` item whose NAME
811/// matches the predicate — kind-agnostic (fn, struct/enum, const), since
812/// prebindgen items live in one flat namespace. E.g.
813/// `.ignore(matching(|n| n.starts_with("encoding_const_")))` instead of one
814/// line per member of a naming family. A *declared* item matching the
815/// predicate is unaffected (declaration wins), and unlike an exact-name
816/// ignore, a predicate matching nothing is silent — it is a filter, not a
817/// claim about a specific item (match counts vary across feature configs).
818pub fn matching<F>(f: F) -> IgnoreDecl
819where
820    F: Fn(&str) -> bool + Send + Sync + 'static,
821{
822    IgnoreDecl(IgnoreKind::Matching(std::sync::Arc::new(f)))
823}
824
825// ──────────────────────────────────────────────────────────────────────
826// PackageDecl — aggregates the package-scoped decls
827// ──────────────────────────────────────────────────────────────────────
828
829/// A batch of class, function and const declarations that land under one
830/// Kotlin subpackage. Build it with [`package!`](crate::package)
831/// (`package!("session")`, or `package!()` for the base package), fill it
832/// with [`class`](Self::class) / [`fun`](Self::fun) /
833/// [`constant`](Self::constant), and hand it to
834/// [`JniGenBuilder::package`]. Reopening the same subpackage across several
835/// `PackageDecl`s is fine — they merge.
836pub struct PackageDecl {
837    pub(crate) name: String,
838    pub(crate) classes: Vec<ClassDecl>,
839    pub(crate) functions: Vec<FunctionDecl>,
840    pub(crate) constants: Vec<ConstDecl>,
841}
842
843impl PackageDecl {
844    /// `name` is dot-separated, relative to the base package set by
845    /// [`JniGenBuilder::set_package_prefix`]; the empty string is the base
846    /// package itself. See [`crate::package!`] for the equivalent macro form
847    /// (`package!("model")` / `package!()`).
848    pub fn new(name: impl Into<String>) -> Self {
849        let name = name.into();
850        let trimmed = name.trim_matches('.').trim_matches('/').to_string();
851        // Sanitize each subpackage segment to a valid Kotlin identifier
852        // (issue #89); a no-op for already-legal names.
853        let name = crate::jni::mangle_kotlin_package(&trimmed);
854        if name != trimmed {
855            println!(
856                "cargo:warning=prebindgen: subpackage `{trimmed}` sanitized to `{name}` \
857                 (invalid Kotlin package identifier)"
858            );
859        }
860        Self {
861            name,
862            classes: Vec::new(),
863            functions: Vec::new(),
864            constants: Vec::new(),
865        }
866    }
867
868    /// Add a class to this package — any of [`ptr_class!`](crate::ptr_class) /
869    /// [`enum_class!`](crate::enum_class) / [`data_class!`](crate::data_class).
870    pub fn class(mut self, decl: impl Into<ClassDecl>) -> Self {
871        self.classes.push(decl.into());
872        self
873    }
874
875    /// Add a free function to this package. Take a bare name via
876    /// [`fun!`](prebindgen_registry::fun), or a customized [`FunctionDecl`] when you need
877    /// `.name(...)` or per-function overrides.
878    pub fn fun(mut self, decl: FunctionDecl) -> Self {
879        self.functions.push(decl);
880        self
881    }
882
883    /// Add a **constant** to this package: a top-level Kotlin `val` in the
884    /// package file, initialized through a generated nullary JNI getter.
885    /// Build the decl with [`constant!`](crate::constant) and pick its
886    /// value source (`#[prebindgen]` const by default, `.fun` / `.with` /
887    /// `.expr` otherwise) — see [`ConstDecl`].
888    pub fn constant(mut self, decl: ConstDecl) -> Self {
889        self.constants.push(decl);
890        self
891    }
892}