Skip to content

Commit 0f94259

Browse files
milyinclaude
andcommitted
kotlin_exception_class accepts syn::Type
The declaration and the closure binding now spell the exception the same way — `parse_quote!(zenoh_flat::errors::ZError)` at both ends: .kotlin_exception_class(parse_quote!(zenoh_flat::errors::ZError)) // ... Some(parse_quote!(zenoh_flat::errors::ZError)) // closure slot Internals: * `ExceptionConfig.rust_path: syn::Path` → `rust_type: syn::Type`. All readers (`find_exception` string-compare, `build_*_fn` err_type splice, `prerequisites` `__JniErr` alias) work via `ToTokens`, so only the field-name update needed. * `default_err_path()` → `default_err_type()`; returns `syn::Type` (`parse_quote!(__JniErr)`) so it shares the `err_type` binding with `ExceptionConfig.rust_type`. * `build_exception_config` takes a `syn::Type`, matches inside a `Type::Path`, and panics with a clear message on non-path inputs (refs, tuples, generics — exception classes are always paths). * `JniExt::new` framework slot construction uses `parse_quote!` for `::prebindgen_ext::jni::JniBindingError`. Generated `zenoh_flat_jni.rs` is byte-identical — the change is purely at the declaration surface, the resolved type that ends up in `Result<_, zenoh_flat::errors::ZError>`, `pub(crate) type __JniErr = ::prebindgen_ext::jni::JniBindingError;`, and `throw_<short>` derivation is the same. 22 prebindgen-ext tests + 97 zenoh-java JVM tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent bb845b3 commit 0f94259

2 files changed

Lines changed: 52 additions & 42 deletions

File tree

prebindgen-ext/src/jni/jni_ext.rs

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,15 @@ impl KotlinMeta {
112112
/// [`KotlinMeta::throws_action`].
113113
#[derive(Clone)]
114114
pub(crate) struct ExceptionConfig {
115-
/// Absolute Rust path of the error type (e.g.
116-
/// `zenoh_flat::errors::ZError`). Used both to splice the
115+
/// Absolute Rust path of the error type, as a `syn::Type::Path`
116+
/// (e.g. `zenoh_flat::errors::ZError`). Used both to splice the
117117
/// `pub(crate) type __JniErr = ...` alias and as the function-
118-
/// argument type of the generated `throw_<short>`.
119-
pub rust_path: syn::Path,
120-
/// Last path segment of `rust_path` (e.g. `"ZError"`). Used to
118+
/// argument type of the generated `throw_<short>`. Stored as a
119+
/// `syn::Type` so it round-trips identically with the
120+
/// closure-slot exception bindings in [`WrapperFn`] — both ends
121+
/// spell the type the same way.
122+
pub rust_type: syn::Type,
123+
/// Last path segment of `rust_type` (e.g. `"ZError"`). Used to
121124
/// derive the `throw_<short>` function name and to provide the
122125
/// default Kotlin class name when no `.kotlin_name(...)` override
123126
/// is supplied.
@@ -187,7 +190,7 @@ pub(crate) struct TypeConfig {
187190
/// composed as a value-inspecting stage onto that converter's chain.
188191
/// * `exc` — the bound exception **as a Rust type**, matched by exact
189192
/// canonical-form equality against a [`JniExt::kotlin_exception_class`]
190-
/// registration's `rust_path` (use the same full path the
193+
/// registration's `rust_type` (use the same full path the
191194
/// registration was declared with, e.g.
192195
/// `parse_quote!(zenoh_flat::errors::ZError)` — no short-name
193196
/// matching). `Some(...)` ⇒ throwing: the body evaluates to
@@ -406,7 +409,7 @@ impl JniExt {
406409
/// is called, then auto-rebases via [`Self::recompute_derived`].
407410
pub fn new() -> Self {
408411
let framework_exc = build_exception_config(
409-
"::prebindgen_ext::jni::JniBindingError",
412+
syn::parse_quote!(::prebindgen_ext::jni::JniBindingError),
410413
"",
411414
&[],
412415
);
@@ -454,19 +457,25 @@ impl JniExt {
454457
/// its own generated Kotlin class and its own `throw_<RustShortName>`
455458
/// free function.
456459
///
457-
/// `rust_path` is the absolute Rust path of the error type (e.g.
458-
/// `"zenoh_flat::errors::ZError"`); the type must impl `Display`.
459-
/// Bind it to converters by emitting `Some("<rust_path or short>")`
460-
/// in the closure's middle slot of [`Self::input_wrapper`] /
461-
/// [`Self::output_wrapper`]. The framework's own
460+
/// `rust_type` is the absolute Rust path of the error type as a
461+
/// `syn::Type::Path` (e.g.
462+
/// `parse_quote!(zenoh_flat::errors::ZError)`); the type must impl
463+
/// `Display`. Bind it to converters by emitting
464+
/// `Some(parse_quote!(<same path>))` in the closure's middle slot
465+
/// of [`Self::input_wrapper`] / [`Self::output_wrapper`] — both
466+
/// ends use the same form, matched by exact canonical-form equality
467+
/// (see [`Self::find_exception`]). The framework's own
462468
/// [`crate::jni::JniBindingError`] is pre-registered at
463469
/// `exceptions[0]` for built-in converters; user-declared exceptions
464470
/// land at 1+.
465471
///
472+
/// Panics when `rust_type` isn't path-shaped (exception classes are
473+
/// always paths; refs, tuples, and generics aren't valid here).
474+
///
466475
/// The default Kotlin class name is `<package>.<rust_short>`;
467476
/// chain [`Self::kotlin_name`] to override.
468-
pub fn kotlin_exception_class(mut self, rust_path: impl AsRef<str>) -> Self {
469-
let cfg = build_exception_config(rust_path.as_ref(), &self.package, &self.exceptions);
477+
pub fn kotlin_exception_class(mut self, rust_type: syn::Type) -> Self {
478+
let cfg = build_exception_config(rust_type, &self.package, &self.exceptions);
470479
self.exceptions.push(cfg);
471480
let idx = self.exceptions.len() - 1;
472481
self.last_exception_idx = Some(idx);
@@ -905,7 +914,7 @@ impl JniExt {
905914
/// * `exc = Some(<Rust type>)` ⇒ throwing: `body` evaluates to
906915
/// `Result<ty, <Rust type>>`; framework emits it verbatim. The
907916
/// type must match a [`Self::kotlin_exception_class`] declaration
908-
/// by **exact canonical-form equality** with its `rust_path` (see
917+
/// by **exact canonical-form equality** with its `rust_type` (see
909918
/// [`Self::find_exception`] — no short-name fallback). The match
910919
/// is validated at lookup time.
911920
///
@@ -978,14 +987,14 @@ impl JniExt {
978987

979988
/// Resolve an exception type against the registered
980989
/// [`Self::exceptions`] by **exact canonical-form equality** with the
981-
/// declaration's `rust_path`. No short-name fallback — the closure /
990+
/// declaration's `rust_type`. No short-name fallback — the closure /
982991
/// caller must spell the same full path
983992
/// `.kotlin_exception_class(...)` was declared with. Returns the
984993
/// index into the `exceptions` vec on match.
985994
fn find_exception(&self, ty: &syn::Type) -> Option<usize> {
986995
let needle = ty.to_token_stream().to_string();
987996
self.exceptions.iter().position(|e| {
988-
e.rust_path.to_token_stream().to_string() == needle
997+
e.rust_type.to_token_stream().to_string() == needle
989998
})
990999
}
9911000

@@ -1281,15 +1290,16 @@ pub(crate) fn exception_throw_path(exc: &ExceptionConfig) -> syn::Path {
12811290
syn::Path::from(ident)
12821291
}
12831292

1284-
/// Bare-ident path `__JniErr` — the generated file's alias for the
1293+
/// Bare-ident type `__JniErr` — the generated file's alias for the
12851294
/// framework `JniBindingError`. Non-throwing converters use this as
12861295
/// their `Result<…, _>` error type so their bodies' `<__JniErr as
12871296
/// From<String>>::from(...)` calls keep compiling, and so a
12881297
/// `?`-propagated framework failure surfaces as the framework
12891298
/// exception on the JVM. Throwing converters bypass this in favour of
1290-
/// their bound exception's own path (see [`JniExt::lookup_input`] /
1291-
/// [`JniExt::lookup_output`]).
1292-
pub(crate) fn default_err_path() -> syn::Path {
1299+
/// their bound exception's own type (see [`JniExt::lookup_input`] /
1300+
/// [`JniExt::lookup_output`]). Returned as `syn::Type` so it shares the
1301+
/// `err_type` binding with [`ExceptionConfig::rust_type`].
1302+
pub(crate) fn default_err_type() -> syn::Type {
12931303
syn::parse_quote!(__JniErr)
12941304
}
12951305

@@ -1317,30 +1327,30 @@ fn validate_path(who: &str, path: &str) -> String {
13171327
path.to_string()
13181328
}
13191329

1320-
/// Construct an [`ExceptionConfig`] from a Rust path string + the
1321-
/// current Kotlin package. Shared by [`JniExt::new`] (framework
1330+
/// Construct an [`ExceptionConfig`] from a path-shaped `syn::Type` and
1331+
/// the current Kotlin package. Shared by [`JniExt::new`] (framework
13221332
/// `JniBindingError` slot) and [`JniExt::kotlin_exception_class`]
1323-
/// (user-declared slots). Panics on invalid input + on collisions
1324-
/// against `existing`'s `throw_<short>` names.
1333+
/// (user-declared slots). Panics if `rust_type` isn't a `Type::Path`
1334+
/// or if its short-name collides with an already-registered exception.
13251335
fn build_exception_config(
1326-
rust_path_str: &str,
1336+
rust_type: syn::Type,
13271337
package: &str,
13281338
existing: &[ExceptionConfig],
13291339
) -> ExceptionConfig {
1330-
let path: syn::Path = syn::parse_str(rust_path_str).unwrap_or_else(|e| {
1331-
panic!(
1332-
"kotlin_exception_class: invalid rust path `{}`: {}",
1333-
rust_path_str, e
1334-
)
1335-
});
1336-
let short = path
1337-
.segments
1340+
let segs = match &rust_type {
1341+
syn::Type::Path(tp) => &tp.path.segments,
1342+
_ => panic!(
1343+
"kotlin_exception_class: expected a path-shaped type, got `{}`",
1344+
rust_type.to_token_stream()
1345+
),
1346+
};
1347+
let short = segs
13381348
.last()
13391349
.map(|s| s.ident.to_string())
13401350
.unwrap_or_else(|| {
13411351
panic!(
1342-
"kotlin_exception_class: rust path `{}` has no segments",
1343-
rust_path_str
1352+
"kotlin_exception_class: rust type `{}` has no path segments",
1353+
rust_type.to_token_stream()
13441354
)
13451355
});
13461356
let kotlin_fqn = if package.is_empty() {
@@ -1359,7 +1369,7 @@ fn build_exception_config(
13591369
);
13601370
}
13611371
ExceptionConfig {
1362-
rust_path: path,
1372+
rust_type,
13631373
rust_short: short,
13641374
kotlin_fqn,
13651375
throw_fn_name,
@@ -1431,7 +1441,7 @@ impl JniExt {
14311441
/// * `None` (non-throwing) → signature `Result<rust, __JniErr>` and
14321442
/// the body is wrapped `Ok(<body>)`; `?` inside propagates the
14331443
/// framework error.
1434-
/// * `Some(X)` (throwing) → signature `Result<rust, X::rust_path>`
1444+
/// * `Some(X)` (throwing) → signature `Result<rust, X::rust_type>`
14351445
/// and the body is emitted as-is — `<body>` already evaluates to
14361446
/// that `Result`, so no `Ok` wrap (and no cross-type `From`).
14371447
pub(crate) fn build_input_fn(
@@ -1444,7 +1454,7 @@ impl JniExt {
14441454
let name = input_name(rust, wire);
14451455
let rust_with_lifetime = annotate_borrow_with_lifetime(rust, "env");
14461456
let wire_with_lifetime = annotate_jobject_with_lifetime(wire, "v");
1447-
let err_type = exc.map(|e| e.rust_path.clone()).unwrap_or_else(default_err_path);
1457+
let err_type = exc.map(|e| e.rust_type.clone()).unwrap_or_else(default_err_type);
14481458
let ret_body = body_for_exc(body, exc);
14491459
if matches!(wire, syn::Type::Ptr(_)) {
14501460
syn::parse_quote!(
@@ -1478,7 +1488,7 @@ impl JniExt {
14781488
) -> syn::ItemFn {
14791489
let name = output_name(rust, wire);
14801490
let wire_with_lifetime = annotate_jobject_with_lifetime(wire, "a");
1481-
let err_type = exc.map(|e| e.rust_path.clone()).unwrap_or_else(default_err_path);
1491+
let err_type = exc.map(|e| e.rust_type.clone()).unwrap_or_else(default_err_type);
14821492
let ret_body = body_for_exc(body, exc);
14831493
syn::parse_quote!(
14841494
#[allow(non_snake_case, unused_mut, unused_variables, unused_braces, dead_code)]
@@ -1851,7 +1861,7 @@ impl PrebindgenExt for JniExt {
18511861
// typed `Result<…, X>` — they bypass `__JniErr` entirely so no
18521862
// cross-type bridge between the framework error and a domain
18531863
// error is needed (the orphan rule forbids one).
1854-
let error_type = &self.framework_exception().rust_path;
1864+
let error_type = &self.framework_exception().rust_type;
18551865
let alias: syn::Item = syn::parse_quote!(
18561866
#[allow(dead_code)]
18571867
pub(crate) type __JniErr = #error_type;

zenoh-jni/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ fn main() {
4040
// value-inspecting peel stage onto that type's own converter,
4141
// so a `ZResult<Session>` raises `ZError` on the peel and
4242
// `JniBindingError` on the jlong marshalling.
43-
.kotlin_exception_class("zenoh_flat::errors::ZError")
43+
.kotlin_exception_class(parse_quote!(zenoh_flat::errors::ZError))
4444
// ZResult<T> output: a throwing converter whose closure returns
4545
// the rust type `T` — so the framework auto-composes it as a
4646
// value-inspecting peel stage onto T's own output converter. The

0 commit comments

Comments
 (0)