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}