diff --git a/.gitignore b/.gitignore index 78cf8934..4a56c1a8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,9 @@ .idea/ .kotlin/ build + +# Rust / Cargo +target/ +**/rust/src/kne_bridges.rs +**/rust/build.rs +Cargo.lock diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..16f9f32a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,297 @@ +# AGENTS.md — Nucleus Native Access + +## Project Overview + +**Nucleus Native Access** is a Gradle plugin that bridges native code (Kotlin/Native or Rust) to the JVM via FFM (Foreign Function & Memory API). No JNI, no annotations, no boilerplate. + +``` +Native code (Kotlin/Native or Rust) → FFM bridges → JVM proxy classes +``` + +## Key Directories + +``` +├── plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/ +│ ├── ir/KneIR.kt # Shared IR: types, signatures, typeId generation +│ ├── analysis/ +│ │ ├── RustdocJsonParser.kt # Parses rustdoc JSON → KneIR +│ │ └── RustWorkAction.kt # Orchestrates Rust pipeline +│ ├── codegen/ +│ │ ├── FfmProxyGenerator.kt # JVM proxies with FFM MethodHandle (shared) +│ │ ├── RustBridgeGenerator.kt # Rust #[no_mangle] extern "C" bridges +│ │ └── NativeBridgeGenerator.kt # Kotlin/Native @CName bridges +│ └── tasks/ +│ ├── GenerateRustBindingsTask.kt # Bridge generation task +│ └── CargoBuildTask.kt # cargo build invocation +├── examples/ +│ └── rust-calculator/ # Rust example + tests +│ ├── rust/src/lib.rs # Rust source +│ └── build/generated/kne/ # Generated code (bridges, proxies) +│ ├── rustBridges/kne_bridges.rs +│ └── jvmProxies/com/example/rustcalculator/ +└── tests/ # Integration tests +``` + +## Rust Bridge Pipeline + +``` +1. cargo rustdoc --output-format json → Extract public API +2. RustdocJsonParser.kt → Parse to KneIR +3. RustBridgeGenerator.kt → Generate #[no_mangle] pub extern "C" fn +4. FfmProxyGenerator.kt → Generate JVM proxies with FFM +5. cargo build --release → Compile Rust +6. Bundle .dylib into JAR → kne/native/{os}-{arch}/ +``` + +## KneType.TUPLE and typeId + +Tuples are represented as `KneType.TUPLE(elementTypes: List)` with a `typeId` property that generates unique signatures: + +```kotlin +// Format: T=Tuple, U=Unit, Z=Bool, B=Byte, S=Short, I=Int, J=Long, F=Float, D=Double, R=String +(i32, (String, bool)) → typeId = "TITRZ" +(i32, i32) → typeId = "TII" +(String, bool) → typeId = "TRZ" +``` + +## Rust Bridge Generation (RustBridgeGenerator.kt) + +### Tuple Return Handling + +For tuple returns, the bridge generates out-param code that writes each element to a buffer: + +```rust +// For (i32, String, bool): +unsafe { *out_t_0 = result.0 as i32; } +// String handling: copy to buffer +let _e_bytes = result.1.as_bytes(); +unsafe { std::ptr::copy_nonoverlapping(_e_bytes.as_ptr(), out_t_1, _e_bytes.len()); } +unsafe { *out_t_1.add(_e_bytes.len()) = 0; } +unsafe { out_t_2.write(if result.2 { 1 } else { 0 }); } +``` + +### Nested Tuple Handling + +Nested tuples are handled specially — for `isOutParam=true`, a nested tuple gets a single `ADDRESS` layout (pointer to boxed tuple), not recursively expanded. + +### IMPORTANT: Memory Layout Compatibility Issue + +**Rust `(String, bool)` has DIFFERENT memory layout than Kotlin expects!** + +Rust layout (32 bytes): +``` +Offset 0-7: bool (1 byte) + padding 7 bytes +Offset 8-15: String.ptr (8 bytes) +Offset 16-23: String.len (8 bytes) +Offset 24-31: String.cap (8 bytes) +``` + +Kotlin `readTuple2_TRZ` expects: +``` +Offset 0-7: String.ptr (8 bytes) +Offset 8-15: String.len (8 bytes) +Offset 16-23: String.cap (8 bytes) +Offset 24-31: bool (1 byte) + padding 7 bytes +``` + +**See NESTED_TUPLE_PROBLEM.md for full details and current status.** + +## FFM Conventions + +### ValueLayout mappings + +| KneType | FFM Layout | +|---------|------------| +| INT | `JAVA_INT` (4 bytes) | +| LONG | `JAVA_LONG` (8 bytes) | +| BOOLEAN | `JAVA_INT` (0/1) | +| STRING | `ADDRESS` (pointer) | +| TUPLE | `ADDRESS` (pointer to heap buffer) | + +### Function Descriptors + +Tuple returns use `FunctionDescriptor.ofVoid(...)` with out-param layouts: +```kotlin +// (i32, i32) return: +FunctionDescriptor.ofVoid(JAVA_LONG, JAVA_INT, JAVA_INT) +// handle out_t_0 out_t_1 + +// (i32, String, bool) return: +FunctionDescriptor.ofVoid(JAVA_LONG, JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT) +// handle out_t_0 out_t_1 out_t_1_len out_t_2 +``` + +## Important Code Patterns + +### When modifying RustBridgeGenerator.kt + +1. **String returns**: Use buffer pattern with null-termination +2. **Nested tuples**: Allocate a `Box::into_raw(Box::new([0u8; 32]))` buffer and write fields in Kotlin's expected layout +3. **Empty strings**: Check `if _s_len > 0` before using pointer (Rust empty strings have ptr=1, len=0) + +### When modifying FfmProxyGenerator.kt + +1. **expandTupleParamLayouts**: For out-params, nested tuples use `ADDRESS` (pointer to boxed). Primitives use their actual `ffmLayout`, not `ADDRESS`. +2. **buildMethodDescriptor**: Tuple returns become `UNIT` with out-param expansion. + +### Generated Files + +Files in `build/generated/kne/` are **regenerated on every build** from source: +- `rustBridges/kne_bridges.rs` — from Rustdoc JSON +- `jvmProxies/` — from KneIR + +**Do not edit generated files manually.** They are overwritten on next build. + +To force regeneration: +```bash +rm -rf examples/rust-calculator/build/generated/kne/ +./gradlew :examples:rust-calculator:assemble --no-build-cache +``` + +## Running Tests + +```bash +# All tuple tests +./gradlew :examples:rust-calculator:jvmTest --tests "com.example.rustcalculator.TupleTest" + +# Single test +./gradlew :examples:rust-calculator:jvmTest --tests "com.example.rustcalculator.TupleTest.get_nested_tuple*" + +# With debug output +./gradlew :examples:rust-calculator:jvmTest --tests "com.example.rustcalculator.TupleTest.get_nested_tuple*" --info + +# Force full rebuild (clears cache) +./gradlew :examples:rust-calculator:assemble --no-build-cache +./gradlew :examples:rust-calculator:jvmTest +``` + +## Debugging Tips + +1. **SIGSEGV crashes**: Usually indicates invalid memory address being dereferenced. Check: + - Is the pointer 0 or very small (like 6 = string length)? + - Is the segment size set correctly with `.reinterpret(size)`? + - Is Rust writing to the correct memory location? + +2. **WrongMethodTypeException**: FFM descriptor mismatch. Check: + - Does the Kotlin `FunctionDescriptor` match the Rust `extern "C"` signature? + - Did you change `expandTupleParamLayouts` without rebuilding native? + +3. **String issues**: Empty strings in Rust have `ptr=1, len=0`. Always check `if _s_len > 0` before using pointer. + +4. **Debug output**: Both Rust (`eprintln!`) and Kotlin (`System.err.println`) can be used for tracing. Rust debug goes to stderr captured by Gradle. + +## Debugging Log Practice (IMPORTANT) + +When fixing a complex bug, **maintain a debugging log** to avoid repeating the same mistakes. Before starting: + +``` +## Bug: [short description] + +### Symptom +- What the user sees +- Error type, logs + +### Root Cause (if known) +- What's actually happening + +### Attempts + +| # | Date | Change | Result | Notes | +|---|------|--------|--------|-------| +| 1 | ... | Modified X | Failed | SIGSEGV at ... | +| 2 | ... | Modified Y | WrongMethodTypeException | Descriptor mismatch | +| 3 | ... | Reverted Y, Modified Z | Partial | 7/8 tests pass | + +### Key Findings +- Finding 1 +- Finding 2 +``` + +**Why this matters**: Complex bugs (like nested tuple memory layouts) require many iterations. Without logging, you risk: +- Forgetting what you already tried +- Repeating failed approaches +- Losing track of which combination of fixes worked + +**Always document**: +- Every change attempted +- The exact error/log output after each attempt +- Why something failed +- What you learned about the problem + +**Update NESTED_TUPLE_PROBLEM.md** with findings as you progress. + +## Testing Philosophy + +This project bridges native code to JVM via FFM — every call crosses the native boundary. **Unit tests at the bridge level are useless.** Only end-to-end tests that actually cross the FFM boundary are meaningful. + +### Reference Implementation: Kotlin/Native Tests + +The `examples/calculator` module has **1700+ end-to-end tests** covering every feature. Use these as inspiration for testing patterns. + +**Key test categories** (from `EdgeCaseTest.kt`, `LoadAndConcurrencyTest.kt`): + +1. **Boundary values**: `Int.MAX_VALUE`, `Int.MIN_VALUE`, overflow behavior +2. **Empty/non-empty variants**: empty strings, empty collections, zero values +3. **Unicode**: emoji, international characters, null chars +4. **All enum values**: test every enum variant explicitly +5. **Nullable transitions**: null ↔ non-null for all nullable types +6. **Lifecycle**: create/use/close cycles, multiple objects +7. **Exception recovery**: verify object still works after exception +8. **Integration**: combinations of features in sequence +9. **Load tests**: 100K+ FFM calls on single instance +10. **Concurrency**: multiple threads hitting native lib simultaneously + +### Rust Tests: Current Status + +The `examples/rust-calculator` tests are **less comprehensive** than Kotlin/Native. They cover basic functionality but lack: +- Load tests (100K+ calls) +- Concurrency tests (multi-threaded) +- Full edge case coverage for all types +- Exception recovery tests + +### Test Naming Convention + +```kotlin +@Test fun `edge prim - Int MAX_VALUE`() = Calculator(Int.MAX_VALUE).use { ... } +@Test fun `load - 100K add calls single instance`() = Calculator(0).use { ... } +@Test fun `concurrent - 10 threads x 10K adds on separate instances`() = ... +``` + +Format: `[category] - [description]` + +Categories: `edge`, `str`, `enum`, `null`, `bytes`, `dc`, `obj`, `exc`, `cb`, `integration`, `load`, `concurrent` + +### When to Add Tests + +- **New type support**: Add edge cases for all boundary values +- **New return type**: Add empty/non-empty, null/non-null variants +- **Bug fix**: Add regression test that reproduces the bug +- **Memory issue**: Add load test that exercises the code path heavily + +### Test Infrastructure + +```kotlin +// AutoCloseable pattern for all native objects +class Calculator(autoCloseable) { ... } + +// Extension for automatic cleanup +fun T.use(block: (T) -> R): R { + return try { block(this) } finally { close() } +} + +// Usage +Calculator(0).use { calc -> + calc.add(5) + assertEquals(5, calc.current) +} // automatically closed +``` + +## Conventions + +- **Comments**: Write in English +- **Kotlin style**: Follow Kotlin conventions and idiomatic style +- **No Co-Authored-By**: Never add AI attribution in commits +- **Je ne cheat pas**: Implement fully regardless of complexity +- **Tests first**: Always run tests after changes, use `--no-build-cache` +- **Battle test edge cases**: Every new feature needs edge case tests, load tests, and concurrency tests +- **Update README**: After implementing a feature or fixing a bug, update the README.md to document the change (new supported type, limitation fixed, etc.) diff --git a/DEBUG_LOG_dyn_trait.md b/DEBUG_LOG_dyn_trait.md new file mode 100644 index 00000000..a18bad15 --- /dev/null +++ b/DEBUG_LOG_dyn_trait.md @@ -0,0 +1,213 @@ +# dyn Trait Implementation Debug Log + +## Date +April 4, 2026 + +## Goal +Implement full support for `dyn Trait` (trait objects) in the Rust bridge generation for the Nucleus Native Access Gradle plugin. + +## Context + +The plugin bridges Rust code to JVM via FFM (Foreign Function & Memory API). The challenge is that: +- `dyn Trait` in Rust is a "fat pointer" (16 bytes: data pointer + vtable) +- FFM can only pass 8 bytes (i64) across the boundary +- `Box` IS a thin pointer (8 bytes) - this CAN be passed + +## Key Concepts + +### What Works +1. **Factory functions returning `Box`** - Box pointer passed as handle +2. **`Option>` returns** - Same as above with 0 as null handle +3. **`Result, E>` returns** - Same with error handling +4. **`Vec>` returns** - Array of handles + +### What Doesn't Work +- **Functions taking `&dyn Trait` params** - Fat pointer exceeds FFM limit (16 bytes vs 8 bytes) + +## Problems Encountered + +### Problem 1: Type Erasure with Box + +**Initial approach**: Store `Box` in registry + +**Error**: +``` +error[E0308]: mismatched types + | expected: Box<(dyn Any + Send + 'static)>> + | found: Box> +``` + +**Root cause**: `Box` cannot be coerced to `Box` unless the trait implements `Any`. User traits like `Describable` don't implement `Any`. + +**Solution**: Use `std::mem::transmute` to extract raw fat pointer components: +```rust +let fat_ptr_words: [usize; 2] = unsafe { std::mem::transmute(result) }; +``` + +### Problem 2: Result Detection + +**Initial approach**: Check `rustRetType.startsWith("Result, String> (192 bits) + target type: [usize; 2] (128 bits) +``` + +**Root cause**: The parser extracts the inner type of `Result` and stores it in `returnRustType`. So `Result, String>` becomes `Box` in `returnRustType`. + +**Solution**: Use `fn.canFail` flag combined with `isBoxDynTrait`: +```kotlin +val isResultReturn = fn.canFail && isBoxDynTrait +``` + +### Problem 3: &dyn Trait Param Detection + +**Initial approach**: Check `rt.startsWith("&dyn ")` for params + +**Error**: Functions like `describe_trait_object(obj: &dyn Describable)` were being routed to dyn Trait handler but failing with: +``` +error[E0606]: cannot cast `i64` to a pointer that is wide +``` + +**Root cause**: `&dyn Trait` in rustdoc JSON has `rustType = "dyn Trait"` (WITHOUT the `&` prefix), and `type = INTERFACE`. + +**Solution**: Check `p.type is KneType.INTERFACE` to detect `&dyn Trait` params and skip those functions entirely: +```kotlin +if (fn.params.any { p -> p.type is KneType.INTERFACE }) { + return false // Skip - can't handle &dyn Trait params +} +``` + +### Problem 4: Slice Variable Not Generated + +**Error**: +``` +error[E0425]: cannot find value `values_slice` in this scope +``` + +**Root cause**: For `Vec>` returns, the `&[i32]` slice parameter was not being converted to `values_slice`. + +**Solution**: Add slice creation before calling the function: +```rust +for (p in params) { + if (p.type is KneType.LIST || p.type == KneType.BYTE_ARRAY) { + appendLine(" let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + } +} +``` + +### Problem 5: when Clause Ordering + +**Initial code**: +```kotlin +when { + isBoxDynTrait -> { ... } // This matched Result returns too! + isResultBoxDyn -> { ... } // Never reached +} +``` + +**Issue**: `isBoxDynTrait` checks `rustRetType.startsWith("Box>` because it starts with `Box { } // canFail + isBoxDynTrait + isOptionBoxDyn -> { } + isVecBoxDyn -> { } + isBoxDynTrait -> { } +} +``` + +### Problem 6: kne_trait_registry_get Lifetime Issue + +**Error**: +``` +error[E0515]: cannot return value referencing temporary value +``` + +**Solution**: We removed `kne_trait_registry_get` since we're using inline transmute instead. + +## Final Architecture + +### Registry Storage +```rust +thread_local! { + static KNE_TRAIT_REGISTRY: RefCell> = RefCell::new(HashMap::new()); + static KNE_NEXT_HANDLE: RefCell = RefCell::new(1); +} +``` + +### Registration (Inline) +```rust +let fat_ptr_words: [usize; 2] = unsafe { std::mem::transmute(result) }; +KNE_TRAIT_REGISTRY.with(|reg| { reg.borrow_mut().insert(handle, fat_ptr_words); }); +``` + +### Drop +```rust +fn kne_drop_trait_object(handle: u64) { + KNE_TRAIT_REGISTRY.with(|reg| { + if let Some(words) = reg.borrow_mut().remove(&handle) { + unsafe { + let fat_ptr: *mut dyn std::any::Any = std::mem::transmute(words); + let boxed: Box> = Box::from_raw(fat_ptr as *mut Box); + drop(boxed); + } + } + }); +} +``` + +## Working Functions + +| Function | Return Type | Status | +|----------|-------------|--------| +| `create_describable` | `Box` | ✓ | +| `create_measurable` | `Box` | ✓ | +| `create_resettable` | `Box` | ✓ | +| `maybe_create_describable` | `Option>` | ✓ | +| `create_describable_list` | `Vec>` | ✓ | +| `try_create_describable` | `Result, String>` | ✓ | + +## Excluded Functions + +| Function | Reason | +|----------|--------| +| `describe_trait_object` | `&dyn Trait` params - fat pointer can't cross FFM | +| `measure_trait_object` | `&dyn Trait` params - fat pointer can't cross FFM | +| `reset_trait_object` | `&dyn Trait` params - fat pointer can't cross FFM | + +## Key Files Modified + +- `plugin-build/plugin/src/main/kotlin/.../codegen/RustBridgeGenerator.kt` + - `isDynTraitFunction()`: Skip functions with INTERFACE params + - `appendDynTraitTopLevelFunction()`: Handle Box, Option, Result, Vec cases + - `appendPreamble()`: Updated registry to store `[usize; 2]` + +## Build Commands + +```bash +# Build +./gradlew :examples:rust-calculator:assemble + +# Test +./gradlew :examples:rust-calculator:jvmTest + +# Force regeneration +rm -rf examples/rust-calculator/build/generated/kne/ +./gradlew :examples:rust-calculator:assemble --no-build-cache +``` + +## Lessons Learned + +1. **Type erasure is fundamental**: Rust's `dyn Trait` loses its concrete type information. We can't recover `Box` from `Box`. + +2. **Transmute is powerful but dangerous**: We can convert fat pointer to `[usize; 2]` for storage, but need to be careful about sizes. + +3. **Parser behavior matters**: The rustdoc JSON parser extracts inner types for generics, so `Result, E>` becomes `Box` in `returnRustType`. + +4. **Filter at the right level**: Functions with `&dyn Trait` params must be filtered before code generation, not during. + +5. **Order matters in when clauses**: More specific checks must come before general ones. diff --git a/NESTED_TUPLE_PROBLEM.md b/NESTED_TUPLE_PROBLEM.md new file mode 100644 index 00000000..b18fe832 --- /dev/null +++ b/NESTED_TUPLE_PROBLEM.md @@ -0,0 +1,170 @@ +# Problème: Support des Tuples Imbriqués Rust/Kotlin via FFM + +## Architecture + +- **Projet**: Kotlin Multiplatform avec FFM (Foreign Function & Memory API) +- **Langage bridge**: Rust générant des fonctions `extern "C"` +- **Example**: `get_nested_tuple() -> (i32, (String, bool))` + +## Le Problème Fondamental: Incompatibilité des Layouts Mémoire + +### Layout Rust `(String, bool)` (32 bytes): + +``` +Offset 0-7: bool (1 byte) + padding 7 bytes +Offset 8-15: String.ptr (8 bytes) +Offset 16-23: String.len (8 bytes) +Offset 24-31: String.cap (8 bytes) +``` + +### Layout attendu par Kotlin `readTuple2_TRZ`: + +``` +Offset 0-7: String.ptr (8 bytes) +Offset 8-15: String.len (8 bytes) +Offset 16-23: String.cap (8 bytes) +Offset 24-31: bool (1 byte) + padding 7 bytes +``` + +**Ils sont incompatibles!** + +## Symptômes + +1. `strPtr` lit `6` (longueur de "nested") au lieu d'un vrai pointeur +2. SIGSEGV quand Kotlin essaie de lire à l'adresse `0x0000000000000006` +3. `WrongMethodTypeException` après modification de `expandTupleParamLayouts` + +## Tentatives de Résolution + +### Tentative 1: Box::into_raw + Kotlin lit le Box + +```rust +let _inner_box = Box::into_raw(Box::new(result.1)); +unsafe { out_t_1.write(_inner_box as i64); } +``` + +**Résultat**: Le Box pointe vers la mémoire avec le layout Rust, pas Kotlin. + +### Tentative 2: Écrire le buffer directement avec le layout Kotlin + +```rust +let _tuple_buf_ptr = Box::into_raw(Box::new([0u8; 32])) as *mut u8; +*(_tuple_buf_ptr as *mut usize) = if _s_len > 0 { _s_ptr as usize } else { 0 }; +*(_tuple_buf_ptr as *mut usize).add(1) = _s_len; +*(_tuple_buf_ptr as *mut usize).add(2) = _s_cap; +_tuple_buf_ptr.add(24).write(if result.1.1 { 1u8 } else { 0u8 }); +``` + +**Résultat**: Fonctionne pour `get_nested_tuple with empty label` (7/8 tests passent). + +### Tentative 3: Corriger `expandTupleParamLayouts` pour les types primitifs + +```kotlin +KneType.INT, KneType.LONG, KneType.BOOLEAN, ... -> builder.add(elemType.ffmLayout) +``` + +**Résultat**: Cassé - `WrongMethodTypeException` sur TOUS les tests de tuples. Le descripteur FFM ne correspond plus à la fonction Rust. + +## Problème Sous-jacent: Convention d'Appel Mismatch + +### Fonction Rust: + +```rust +pub extern "C" fn rustcalc_Calculator_get_coordinates( + handle: i64, + out_t_0: *mut i32, // ADDRESS en FFM (8 bytes) + out_t_1: *mut i32 // ADDRESS en FFM (8 bytes) +) -> () +``` + +### Descripteur FFM généré: + +```kotlin +FunctionDescriptor.ofVoid(JAVA_LONG, JAVA_INT, JAVA_INT) +// ^^^^^^^^^^ 4 bytes each +``` + +**Problème**: `*mut i32` est 4 bytes mais FFM utilise `JAVA_INT` qui est aussi 4 bytes - ça devrait matcher. + +Mais pour `get_triple`: + +```rust +pub extern "C" fn rustcalc_Calculator_get_triple( + handle: i64, + out_t_0: *mut i32, // JAVA_INT (4 bytes) + out_t_1: *mut u8, // ADDRESS (8 bytes) + out_t_1_len: i32, // JAVA_INT (4 bytes) + out_t_2: *mut i32 // JAVA_INT (4 bytes) +) -> () +``` + +Le problème est que `*mut u8` (1 byte) est représenté comme `ADDRESS` (8 bytes) en FFM. + +## Points Clés à Résoudre + +1. **Le layout de `(String, bool)` diffère entre Rust et Kotlin** - impossible à changer sans modifier le code Kotlin de lecture + +2. **Les types primitifs dans `expandTupleParamLayouts`** - utiliser `ADDRESS` vs le vrai layout cause des problèmes de taille + +3. **La cohérence du layout pour tous les tuples** - une modification pour les tuples imbriqués ne doit pas casser les tuples simples + +4. **String vide a un pointeur "dangling"** (`ptr=1`) - notre code gère ça mais c'est une source potentielle d'erreurs + +## Fichiers Impliqués + +### Plugin IR +- `plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/ir/KneIR.kt` + +### Plugin Parser +- `plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustdocJsonParser.kt` + +### Plugin Code Generators +- `plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/FfmProxyGenerator.kt` +- `plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/RustBridgeGenerator.kt` + +### Rust Example +- `examples/rust-calculator/rust/src/lib.rs` + +### Kotlin Tests +- `examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/TupleTest.kt` + +### Générés +- `examples/rust-calculator/build/generated/kne/rustBridges/kne_bridges.rs` +- `examples/rust-calculator/build/generated/kne/jvmProxies/com/example/rustcalculator/Calculator.kt` +- `examples/rust-calculator/build/generated/kne/jvmProxies/com/example/rustcalculator/KneRuntime.kt` + +## État Actuel — RÉSOLU (2026-04-03) + +**8/8 tests passent** pour les tuples. + +### Correctifs appliqués + +**1. Use-after-free sur les Strings dans les tuples imbriqués (RustBridgeGenerator.kt)** + +Le bridge Rust stockait un pointeur vers le buffer interne du `String` Rust, mais ce `String` était libéré à la fin de la closure `catch_unwind` (quand `result` est droppé). Le pointeur devenait dangling. + +**Fix**: copier les bytes du string dans une nouvelle allocation heap avec null-terminator via `Box::into_raw(vec![0u8; _s_len + 1].into_boxed_slice())`, au lieu de stocker le pointeur du String original. + +Le test "empty label" passait car `_s_len == 0` écrivait `0usize` comme pointeur, et Kotlin vérifiait `strPtr != 0L` avant de déréférencer. + +**2. Mismatch FFM descriptor pour les out-params primitifs (FfmProxyGenerator.kt)** + +`expandTupleParamLayouts` utilisait `elemType.ffmLayout` (ex: `JAVA_INT`) pour les out-params primitifs des tuples retournés, mais les out-params sont des pointeurs (`*mut i32`) qui nécessitent `ADDRESS` dans le descripteur FFM. + +**Fix**: vérifier `isOutParam` pour les types primitifs, comme le faisait déjà la branche `else`. + +**3. Generic nested tuple layout (RustBridgeGenerator.kt + FfmProxyGenerator.kt)** + +The nested tuple buffer used hardcoded 32-byte size and manually placed offsets only for the `(String, bool)` case. `readTuple` functions were also hardcoded per type. + +**Fix**: Uniform 8-byte-slot layout. Each element at offset `idx * 8`, buffer size = `count * 8`. A recursive `appendNestedTupleWrite` generates the Rust writer for any tuple structure at any nesting depth. `readTupleN_SIG` functions are generated for every collected tuple type. Removed all debug `System.err.println` logging. + +**4. Memory leak on nested tuple buffers and string copies (both generators)** + +Heap-allocated tuple buffers (`Box::into_raw(vec![...])`) and string copies were never freed. + +**Fix**: Added `kne_free_buf(ptr, size)` to the Rust bridge. Kotlin `readTuple` functions free string copies (by UTF-8 byte size + 1) and the tuple buffer itself after reading all elements. Nested tuples are freed bottom-up via recursive `readTuple` calls. + +### Limitations restantes + +- None for the tuple feature — all 4 original limitations are resolved diff --git a/NOKHWA_SUPPORT_CHECKLIST.md b/NOKHWA_SUPPORT_CHECKLIST.md new file mode 100644 index 00000000..5681b26b --- /dev/null +++ b/NOKHWA_SUPPORT_CHECKLIST.md @@ -0,0 +1,139 @@ +# Nokhwa Crate Support Checklist + +What remains to support [nokhwa](https://github.com/l1npengtul/nokhwa) natively, +without writing Rust and without any manual configuration. +NNA must infer everything from the rustdoc JSON. + +--- + +## 1. Auto-monomorphisation des generiques via scan des impls + +> **Bloquant** — `Camera::new` prend `RequestedFormat`, +> `Buffer::decode_image::()` est generique sur le format de sortie. + +- [x] **Trait-to-Type registry** — scanner tous les blocs `impl Trait for ConcreteType` + dans le rustdoc JSON et construire un index `Trait -> [ConcreteType]` +- [x] **Resolution des bornes generiques** — quand un param `T: SomeTrait` est rencontre, + consulter le registry pour trouver les types concrets qui implementent `SomeTrait` + (aujourd'hui seuls `Fn`/`AsRef`/`Into`/`From` sont geres) +- [x] **Instanciation des structs generiques** — pour `RequestedFormat`, generer + une classe JVM par type concret trouve (ex: `RequestedFormatRgb`, `RequestedFormatGray`) +- [x] **Instanciation des methodes generiques** — pour `buffer.decode_image::()`, + generer un bridge par monomorphisation avec turbofish (ex: `decode_image_rgb`) +- [x] **Multi-bound resolution** — gerer les contraintes combinees (`T: Trait1 + Trait2`) +- [x] **Constructeurs generiques** — `RequestedFormat::new::()` doit generer + un constructeur par impl concrete de `FormatDecoder` + +## 2. Sealed enum variants multi-champs + +> **Necessaire** — `NokhwaError` a 14 variants dont certains avec 2-3 champs. + +- [x] **Tuple variants a N champs** — `OpenDeviceError(String, String)`, + `SetPropertyError(property, value, error)` : le parsing existe deja + (`value0`, `value1`, ...) mais verifier que le bridge Rust + FFM proxy + generent correctement les constructeurs et getters pour N > 1 + ✓ Verifie end-to-end avec `ErrorInfo` enum (DeviceError(String,String), + PropertyError(String,i32,String), CodedMessage(i32,String)) — 25 tests + incluant edge cases, load 100K, concurrency 10 threads +- [x] **Struct variants** — `ProcessFrameError { src, destination, error }` : + le parsing existe, verifier la generation de bout en bout + (constructeur avec champs nommes, getters individuels) + ✓ Verifie avec `ProcessingStatus::FrameError { src, destination, error }` + et `ProcessingStatus::Progress { step, total, label, done }` — 29 tests +- [x] **Variants avec types complexes dans les champs** — certains champs de + `NokhwaError` referencent `ApiBackend` (enum), `FrameFormat` (enum), + etc. Verifier le support des enums imbriques dans les variants + ✓ Verifie avec `ProcessingStatus::OperationFailed { operation: Operation, code, message }` + — fix: enum fields behind `ref` binding need ptr-cast to read ordinal + (RustBridgeGenerator: sealedGetterBindingExpr + rustReturnExpr) + +## 3. Types opaques cross-crate + +> **Necessaire** — nokhwa reexporte des types de `nokhwa-core` et `nokhwa-types`. + +- [x] **Resolution des types reexportes** — quand rustdoc JSON reference un type + defini dans un sous-crate (`nokhwa_core::types::Resolution`), le parser + doit suivre le chemin et creer le `KneClass`/`KneDataClass` correspondant + ✓ Lazy cross-crate type resolution in RustdocJsonParser.tryLazyResolve(): + when resolveType encounters an unknown ID, looks up the index and discovers + structs (→ data class if all fields are primitive/String), enums (simple or + sealed). Tested with cross-crate-reexport.json fixture (8 tests). +- [x] **Structs de sous-crates comme data classes** — `Resolution { width: u32, height: u32 }` + et `CameraFormat { resolution, format, frame_rate }` doivent etre detectes + comme data classes (tous champs publics, pas de methodes complexes) + ✓ Tested with Resolution (primitive fields) and CameraFormat (nested data class + + enum + primitive fields). extractStructFields now passes knownDataClasses + for proper nested type resolution. Initial scan snapshots struct IDs to + avoid re-processing lazily-discovered types. +- [x] **Enums de sous-crates** — `FrameFormat`, `ApiBackend`, `CameraControl` + doivent etre resolus meme s'ils viennent de `nokhwa-types` + ✓ Tested with FrameFormat { Rgb, Yuv, Gray } and CaptureError (sealed). + Lazy resolution detects sealed vs simple enums by checking variant kinds. + +## 4. Support `&[u8]` en retour (borrowed slice) + +> **Necessaire** — `Buffer::buffer()` retourne `&[u8]`, pas `Vec`. + +- [x] **Mapping `&[u8]` -> `ByteArray`** — aujourd'hui `Vec` est mappe en + `BYTE_ARRAY`, mais `&[u8]` (borrowed slice) n'est probablement pas reconnu + par le parser ; ajouter la detection du pattern slice dans rustdoc JSON + ✓ Already supported: parser resolves borrowed_ref → slice → BYTE_ARRAY + with isBorrowed=true. No code change needed in parser. +- [x] **Bridge copie** — le bridge doit copier le slice dans un buffer alloue + cote JVM (le borrow ne peut pas survivre au-dela de l'appel FFI) + ✓ Already supported: bridge uses copy_nonoverlapping inside catch_unwind + where the borrow is still valid. Verified with label_bytes() -> &[u8] + (7 tests: empty, unicode, 10K bytes, load 100K, concurrency 10 threads) + +## 5. `Result` avec erreurs structurees + +> **Amelioration** — aujourd'hui `Result` est unwrap avec panic/error string. +> Pour nokhwa, propager `NokhwaError` de facon structuree serait preferable. + +- [ ] **Propagation du variant d'erreur** — au lieu de `.unwrap()` ou + `.map_err(|e| e.to_string())`, generer un bridge qui transmet + le discriminant + les champs de l'erreur au JVM +- [ ] **Mapping vers exception Kotlin typee** — generer une hierarchie + d'exceptions correspondant aux variants de `NokhwaError` + (ex: `NokhwaOpenDeviceException(msg1, msg2)`) + +## 6. Proprietes avec types non-primitifs + +> **Mineur** — `Camera` expose `index()`, `backend()`, `info()`, `resolution()`. + +- [x] **Getters retournant un enum** — `camera.backend() -> ApiBackend` : + verifier que le pattern getter (`fn backend(&self) -> ApiBackend`) + est detecte comme propriete et non comme methode + ✓ Already supported: ENUM was in isSimplePropertyType. Verified with + cross-crate test (get_format → val format: FrameFormat property). +- [x] **Getters retournant un struct/data class** — `camera.resolution() -> Resolution` : + verifier que le retour d'un data class par valeur fonctionne via getter + ✓ Fix: added DATA_CLASS to isSimplePropertyType + added out-params to + appendPropertyBridges for data class returns. get_point() → val point: Point, + get_named_value() → val named_value: NamedValue. All tests pass. + +## 7. Methodes `&mut self` + +> **Deja supporte** en theorie, mais a valider pour le volume de nokhwa. + +- [x] **Verifier la generation `&mut self`** pour les methodes comme + `open_stream()`, `stop_stream()`, `set_resolution()`, `set_frame_rate()` + ✓ Already fully supported: classifyReceiverKind detects &mut self → + BORROWED_MUT, bridge generates `&mut *(handle as *mut T)`. Validated + with existing add/subtract/multiply/divide tests (all &mut self). +- [x] **Methodes avec `Result` retour + `&mut self`** — `frame(&mut self) -> Result` + combine deux patterns, verifier que la composition fonctionne + ✓ Added try_divide_result(&mut self) -> Result to verify. + canFail and isMutating are independent flags. 4 tests including + success+mutation, error+no-mutation, recovery, and 100K load. + +--- + +## Ordre de priorite suggere + +1. **Auto-monomorphisation** (section 1) — debloque l'API principale +2. **Types cross-crate** (section 3) — debloque les types de parametre/retour +3. **Sealed enum multi-champs** (section 2) — debloque `NokhwaError` +4. **Borrowed slices** (section 4) — debloque `Buffer::buffer()` +5. **Result structure** (section 5) — ameliore l'ergonomie des erreurs +6. **Proprietes / &mut self** (sections 6-7) — polish diff --git a/README-KOTLIN-NATIVE.md b/README-KOTLIN-NATIVE.md new file mode 100644 index 00000000..5f8c333a --- /dev/null +++ b/README-KOTLIN-NATIVE.md @@ -0,0 +1,564 @@ +# Nucleus Native Access — Kotlin/Native Bridge + +← [Back to overview](README.md) + +> **Experimental.** The Kotlin/Native bridge is under active development. APIs and generated code structure may change between versions. + +Wrap **Kotlin/Native** code and use it from the JVM as if it were a regular Kotlin library. The plugin scans your `nativeMain` sources, generates `@CName` C-ABI bridge functions on the native side, and FFM proxy classes on the JVM side. + +``` +Kotlin/Native source Plugin generates JVM developer sees +────────────────── ──────────────── ────────────────── +class Calculator { → @CName bridges (native) → class Calculator : AutoCloseable { + fun add(value: Int) + StableRef lifecycle fun add(value: Int): Int + val current: Int + FFM MethodHandles val current: Int +} + output-buffer strings // backed by native, via FFM + } +``` + +**Pipeline:** + +1. Plugin parses your `nativeMain` sources via Kotlin PSI and extracts the public API +2. Generates `@CName` bridge functions with `StableRef` for object lifecycle (native side) +3. Generates JVM proxy classes with FFM `MethodHandle` downcalls (JVM side) +4. Compiles to a shared library (`.so` / `.dylib` / `.dll`) +5. Bundles the native library into the JAR under `kne/native/{os}-{arch}/` +6. Generates GraalVM reachability metadata (reflection, resources, FFM downcall descriptors) +7. JVM code calls the proxies transparently — every call crosses the FFM boundary into native + +## Quick start + +### 1. Apply the plugin + +```kotlin +// build.gradle.kts +plugins { + kotlin("multiplatform") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") version "0.1.0" +} +``` + +### 2. Configure targets + +```kotlin +kotlin { + jvmToolchain(25) // FFM is stable since JDK 22 (JEP 454), recommended JDK 25 + + linuxX64() // use the real platform name (KMP convention) + // macosArm64() // on macOS + // mingwX64() // on Windows + + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + // your JVM dependencies (Compose, Ktor, etc.) + } + } + } +} + +kotlinNativeExport { + nativeLibName = "mylib" // output: libmylib.so (release ~700KB) + // nativePackage is auto-detected from your Kotlin source package declarations + // nativePackage = "com.example" // override only if needed + buildType = "release" // "release" (default, optimized) or "debug" +} +``` + +### 3. Write Kotlin/Native code + +```kotlin +// src/nativeMain/kotlin/com/example/Calculator.kt +package com.example + +class Calculator(initial: Int = 0) { + private var acc = initial + + fun add(value: Int): Int { acc += value; return acc } + fun subtract(value: Int): Int { acc -= value; return acc } + val current: Int get() = acc + fun describe(): String = "Calculator(current=$acc)" +} +``` + +### 4. Use it from JVM as if it were a normal class + +```kotlin +// src/jvmMain/kotlin/com/example/Main.kt +package com.example + +fun main() { + val calc = Calculator(0) // allocates a Kotlin/Native object + calc.add(5) // FFM → native → StableRef → add() + calc.add(3) + println(calc.current) // 8 + println(calc.describe()) // "Calculator(current=8)" + calc.close() // releases the native object (also auto-GC'd via Cleaner) +} +``` + +No JNI. No annotations. No boilerplate. Just write Kotlin/Native and use it from JVM. + +### 5. Run + +```bash +./gradlew jvmTest # compiles native + generates bridges + runs JVM tests +./gradlew run # if using Compose Desktop / Nucleus +``` + +## What's supported + +### Types — test coverage (1700+ end-to-end FFM tests) + +Every test compiles Kotlin/Native → `libcalculator.so` (470+ exported symbols) → loads via FFM `MethodHandle` → verifies on JVM. Zero mocks — all 1700+ tests cross the real native boundary. Includes load tests (500K+ FFM calls), concurrent stress tests, 110+ suspend function tests with cancellation, 50+ Flow tests, and 300+ inheritance/interface/extension tests. + +| Feature | As param | As return | As property | CB param | CB return | Notes | +|---------|----------|-----------|-------------|----------|-----------|-------| +| `Int` | ✅ 5t | ✅ 5t | ✅ 2t | ✅ 3t | ✅ 2t | direct pass-through | +| `Long` | ✅ 2t | ✅ 2t | — | ✅ 2t | ✅ 2t | direct pass-through | +| `Double` | ✅ 2t | ✅ 2t | ✅ 1t | ✅ 2t | ✅ 2t | direct pass-through | +| `Float` | ✅ 1t | ✅ 1t | — | ✅ 1t | ✅ 1t | direct pass-through | +| `Boolean` | ✅ 3t | ✅ 2t | ✅ 1t | ✅ 3t | ✅ 1t | 0/1 convention over FFM | +| `Byte` | ✅ 1t | ✅ 1t | — | ✅ 1t | ✅ 1t | direct pass-through | +| `Short` | ✅ 1t | ✅ 1t | — | ✅ 1t | ✅ 1t | direct pass-through | +| `String` | ✅ 4t | ✅ 4t | ✅ 3t | ✅ 4t | ✅ 3t | output-buffer pattern | +| `Unit` | — | ✅ 1t | — | — | ✅ 3t | `FunctionDescriptor.ofVoid(...)` | +| `enum class` | ✅ 3t | ✅ 2t | ✅ 2t | ✅ 2t | ✅ 3t | ordinal mapping | +| Classes | ✅ 3t | ✅ 4t | — | ✅ 19t | ✅ 2t | opaque handle via `StableRef` (incl. Object in callbacks) | +| Nested classes | ✅ | ✅ | ✅ | — | — | exported as `Outer_Inner`, supports 3+ nesting levels | +| `T?` (nullable) | ✅ 3t | ✅ 8t | ✅ 3t | ❌ | — | sentinel-based null encoding (incl. `DataClass?`) | +| `data class` | ✅ 4t | ✅ 6t | — | ✅ 5t | ✅ 3t | all field types: primitive, String, Enum, Object, nested DC, List, Set, Map | +| `ByteArray` | ✅ 2t | ✅ 2t | — | ❌ | — | pointer + size pattern, suspend ✅ | +| `List` | ✅ 26t | ✅ 17t | — | ✅ 12t | ✅ 5t | Int, Long, Double, Float, Short, Byte, Boolean, String, Enum, Object | +| `List` | — | ✅ 15t | — | — | — | opaque handle + size/get/dispose bridges (Point, NamedValue, TaggedPoint) | +| `List?` | ✅ 7t | ✅ 8t | — | — | — | -1 count = null sentinel | +| `Set` | ✅ 9t | ✅ 13t | — | — | — | Int, String, Enum + intersect/empty edge cases | +| `Set?` | — | ✅ 5t | — | — | — | -1 count = null sentinel | +| `Map` | ✅ 12t | ✅ 12t | — | ✅ 2t | ✅ 2t | String→Int, Int→String, Int→Int, String→String + merge/empty | +| `Map?` | — | ✅ 4t | — | — | — | -1 count = null sentinel | +| `(T) -> R` (lambda) | ✅ 15t | — | — | — | — | persistent `Arena.ofShared()` | +| `Flow` | — | ✅ 50t+ | — | — | — | `channelFlow` + 3 callbacks (onNext, onError, onComplete), incl. `Flow` | + +### Declarations + +| Feature | Supported | Notes | +|---------|-----------|-------| +| Top-level classes | ✅ | `StableRef` lifecycle, `AutoCloseable` on JVM | +| Open / abstract classes | ✅ | `open class Shape` → JVM `open class Shape`, hierarchy mirrored | +| Inheritance | ✅ | `class Circle : Shape` → JVM `class Circle : Shape(handle)`, multi-level (3+) | +| Interfaces | ✅ | `interface Measurable` → JVM `interface Measurable`, multi-interface impl | +| Sealed classes | ✅ | `sealed class AppResult` → JVM `sealed class`, subclass ordinal bridges | +| Extension functions | ✅ | `fun Shape.displayName()` → real Kotlin extension on JVM proxy | +| Nested classes | ✅ | exported as `Outer_Inner`, qualified bridge symbols | +| Methods (fun) | ✅ | instance methods with any supported param/return types, `override` preserved | +| Properties (val/var) | ✅ | getters + setters, all supported types, including constructor `val`/`var` params | +| Constructors | ✅ | primary constructor with supported param types | +| Constructor default params | ✅ | generates overloads for trailing default parameters | +| Companion objects | ✅ | static methods and properties on JVM proxy | +| Top-level functions | ✅ | grouped into a singleton `object` on JVM | +| Enum classes | ✅ | auto-generated JVM enum with ordinal mapping | +| Data classes (nativeMain) | ✅ | auto-generates JVM data class + field marshalling | +| Data classes (commonMain) | ✅ | reuses existing JVM type, no proxy generated | +| Suspend functions | ✅ | `suspendCancellableCoroutine` + bidirectional cancellation (110+ tests) | +| Flow<T> return | ✅ | `channelFlow` + onNext/onError/onComplete callbacks (50+ tests) | +| Exception propagation | ✅ | `try/catch` wrapping, `KotlinNativeException` on JVM | +| Object lifecycle | ✅ | `Cleaner` for GC + `close()` for explicit release | + +### Suspend functions + +Kotlin/Native `suspend fun` is transparently mapped to JVM `suspend fun`. The developer writes coroutines on both sides — no callbacks, no `CompletableFuture`. + +```kotlin +// Kotlin/Native +suspend fun fetchData(query: String): String { + delay(100) + return "result for $query" +} + +// JVM — transparent, just a suspend fun +val result = calc.fetchData("test") // suspends the coroutine +``` + +**How it works**: the native bridge launches a `CoroutineScope` with a `Job`, passes continuation + exception callbacks as FFM upcall stubs. The JVM proxy uses `suspendCancellableCoroutine` to suspend until the native coroutine completes. + +**Cancellation**: JVM coroutine cancel → `Job.cancel()` on native side. Native `CancellationException` → JVM `CancellationException`. Bidirectional, automatic. + +**Supported return types**: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `ByteArray`, `Unit`, `enum class`, `Object`, `data class`, `List`, `Set`, `Map`, nullable variants. + +### Flow<T> + +Kotlin/Native `Flow` is transparently mapped to JVM `Flow` via `channelFlow`. Event streams, tickers, and reactive patterns work naturally. + +```kotlin +// Kotlin/Native +fun countUp(max: Int): Flow = flow { + for (i in 1..max) { delay(10); emit(i) } +} + +// JVM — transparent Flow collection +calc.countUp(5).collect { println(it) } // 1, 2, 3, 4, 5 +calc.countUp(100).toList() // [1, 2, ..., 100] +calc.infiniteFlow().take(3).toList() // [0, 1, 2] — auto-cancelled +``` + +**How it works**: 3 native callbacks (`onNext`, `onError`, `onComplete`) are passed as FFM upcall stubs. The native side collects the Flow in a `CoroutineScope` and calls `onNext` for each element. The JVM proxy uses `channelFlow { trySend(...); awaitClose { cancelJob() } }`. + +**Cancellation**: collecting only N elements (via `take`, `first`) automatically cancels the native Flow collection. Manual `Job.cancel()` also propagates. + +**Supported element types**: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `enum class`, `Object`, `data class` (including nested data classes). + +**Data class in Flow**: data classes are serialized element-by-element via `StableRef` + per-type reader bridges. Nested data classes (e.g. `Flow` where `Rect` contains two `Point`) are fully supported. + +```kotlin +// Kotlin/Native +data class MemoryInfo(val totalMB: Long, val availableMB: Long) + +fun memoryFlow(intervalMs: Long = 1000L): Flow = flow { + while (true) { emit(MemoryInfo(getTotalMemoryMB(), getAvailableMemoryMB())); delay(intervalMs) } +} + +// JVM — transparent Flow collection +desktop.memoryFlow(2000L).collect { info -> + println("${info.availableMB} MB / ${info.totalMB} MB") +} +``` + +### Callbacks & lambdas + +JVM lambdas cross the FFM boundary via upcall stubs. The plugin generates all the FFM infrastructure automatically. + +**Lifecycle**: each proxy object holds a persistent `Arena.ofShared()`. Upcall stubs live as long as the object — async callbacks (event handlers, listeners) work out of the box. The arena is freed on `close()` or GC. + +**Supported callback signatures**: +- Params: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `enum class`, `data class`, opaque objects +- Returns: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `Unit`, `enum class`, `data class`, opaque objects +- Multi-param: `(T, U) -> R` with any supported types +- Data class params are decomposed into individual fields at C ABI level + +```kotlin +// Kotlin/Native +fun onValueChanged(callback: (Int) -> Unit) { callback(accumulator) } +fun transform(fn: (Int) -> Int): Int { accumulator = fn(accumulator); return accumulator } +fun formatWith(formatter: (Int) -> String): String = formatter(accumulator) + +// JVM — transparent +calc.onValueChanged { value -> println("Value: $value") } +calc.transform { it * 2 } +calc.formatWith { "Result: $it" } + +// Async callbacks work (e.g. native event listeners) +desktop.setTrayClickCallback { index -> println("Clicked: $index") } +``` + +### Collections + +`List`, `Set`, and `Map` cross the FFM boundary using flat arrays (pointer + size), inspired by swift-java's `[UInt8]` lowering. + +**Supported element types**: `Int`, `Long`, `Double`, `Float`, `Short`, `Byte`, `Boolean`, `String`, `enum class` + +```kotlin +// Kotlin/Native +fun getScores(): List = listOf(accumulator, accumulator * 2, accumulator * 3) +fun sumAll(values: List): Int { accumulator = values.sum(); return accumulator } +fun getMetadata(): Map = mapOf("current" to accumulator, "scale" to scale.toInt()) + +// JVM — transparent +val scores = calc.getScores() // [10, 20, 30] +calc.sumAll(listOf(1, 2, 3, 4, 5)) // 15 +val meta = calc.getMetadata() // {current=42, scale=3} +``` + +| Collection | C ABI encoding | +|---|---| +| `List` | `CPointer` + `size: Int` | +| `List` | packed null-terminated buffer + count | +| `List` | ordinal array + count | +| `Set` | same as `List` (converted at boundary) | +| `Map` | parallel key + value arrays + count | + +### Data classes + +Data classes are marshalled **by value** (field decomposition) — each field becomes a separate C ABI argument. Supported field types: all primitives + `String`. + +```kotlin +// Can be in commonMain or nativeMain +data class Point(val x: Int, val y: Int) + +// nativeMain +fun getPoint(): Point = Point(accumulator, accumulator * 2) +fun addPoint(p: Point): Int { accumulator += p.x + p.y; return accumulator } + +// JVM — uses the real data class (not an opaque handle) +val p = calc.getPoint() // Point(x=5, y=10) +calc.addPoint(Point(3, 7)) // 10 +``` + +- **commonMain data classes**: the JVM already has the type — no proxy generated, field marshalling only +- **nativeMain data classes**: the plugin generates the JVM `data class` file automatically + +### Exception propagation + +All native bridge functions are wrapped in `try/catch`. When an exception occurs: + +1. The native side captures the error message in a `@ThreadLocal` variable +2. The JVM proxy calls `kne_hasError()` after every downcall +3. If an error is detected, `kne_getLastError()` retrieves the message +4. A `KotlinNativeException(message)` is thrown on the JVM side + +```kotlin +try { calc.divide(0) } catch (e: KotlinNativeException) { println(e.message) } +calc.add(5) // works normally after exception +``` + +### Nullable type encoding + +| Nullable type | Wire type | Null sentinel | +|---------------|-----------|---------------| +| `String?` | output-buffer `Int` | -1 = null | +| `Object?` | `JAVA_LONG` | 0L = null | +| `Enum?` | `JAVA_INT` | -1 = null | +| `Boolean?` | `JAVA_INT` | -1 = null, 0 = false, 1 = true | +| `Int?` | `JAVA_LONG` | `Long.MIN_VALUE` = null | +| `Long?` | `JAVA_LONG` | `Long.MIN_VALUE` = null | +| `Short?` | `JAVA_INT` | `Int.MIN_VALUE` = null | +| `Byte?` | `JAVA_INT` | `Int.MIN_VALUE` = null | +| `Float?` | `JAVA_LONG` (raw bits) | `Long.MIN_VALUE` = null | +| `Double?` | `JAVA_LONG` (raw bits) | `Long.MIN_VALUE` = null | + +## Benchmarks — Native (FFM) vs Pure JVM + +Measured on Intel Core i5-14600 (20 cores), 45 GB RAM, Ubuntu 25.10, JDK 25 (GraalVM), Kotlin 2.3.20. + +**Methodology**: each benchmark runs the operation in a tight loop. 3 warmup iterations are discarded, then 5 measured iterations are averaged. "Native" creates a proxy object via FFM and calls into the Kotlin/Native shared library (.so). "JVM" runs the equivalent Kotlin/JVM code directly. Ratio = native/jvm (>1 = native slower due to FFM overhead). Memory is measured via `Runtime.totalMemory() - freeMemory()` before/after with explicit GC. + +### Compute-bound (work stays in native, single FFM call) + +| Benchmark | Native | JVM | Ratio | Analysis | +|-----------|--------|-----|-------|----------| +| Fibonacci recursive (n=35) | 18.07 ms | 23.85 ms | **0.76x** | Native faster (no JIT warmup needed) | +| Fibonacci iterative (n=1M) | 0.30 ms | 0.21 ms | 1.43x | Near-equal, JVM JIT slightly ahead | +| Pi Leibniz series (10M iter) | 8.60 ms | 8.56 ms | **1.01x** | Identical performance | +| String concat loop (10K) | 21.65 ms | 17.62 ms | 1.23x | Near-equal | +| Bubble sort (5K elements) | 13.13 ms | 4.78 ms | 2.75x | JVM JIT optimizes array access better | + +### FFM call overhead (many small downcalls) + +| Benchmark | Native | JVM | Ratio | Analysis | +|-----------|--------|-----|-------|----------| +| 100K trivial calls | 4.94 ms | 0.31 ms | 16x | ~49 ns/call FFM overhead | +| 10K create+close cycles | 4.12 ms | 0.11 ms | 36x | StableRef alloc+dispose cost | +| 10K data class returns | 4.32 ms | 0.15 ms | 29x | Out-param marshaling cost | +| 10K string returns | 6.45 ms | 0.59 ms | 11x | Output-buffer + UTF-8 copy | +| 10K data class params | 0.96 ms | 0.01 ms | 65x | Field expansion overhead | +| 5K list params (100 elems) | 7.78 ms | 2.89 ms | 2.70x | Arena alloc + memcpy | + +### Concurrent (10 threads, separate instances) + +| Benchmark | Native | JVM | Ratio | +|-----------|--------|-----|-------| +| 10t × 1K fib(100) | 3.83 ms | 0.49 ms | 7.87x | +| 10t × 1K string reverse | 2.91 ms | 0.69 ms | 4.24x | +| 10t × 1K create+close | 2.25 ms | 0.44 ms | 5.07x | +| 10t × 1K DC roundtrip | 2.11 ms | 0.92 ms | 2.28x | + +### Memory allocation + +| Benchmark | Native | JVM | Analysis | +|-----------|--------|-----|----------| +| 100K point allocations | **0 KB** | 3,071 KB | Native: no JVM heap pressure | +| 10t × 10K points (concurrent) | **1,151 KB** | 5,124 KB | Native uses 4.5x less JVM memory | +| String concat (10K) | **0 KB** | 131,680 KB | Native: strings stay on native heap | + +**Key takeaways**: +- **Compute-bound workloads** (fibonacci, pi, sorting) run at near-native speed — the FFM boundary is crossed once, then all work happens in Kotlin/Native +- **FFM call overhead** is ~49 ns/call — negligible for methods that do real work, visible only in micro-benchmarks with 100K+ trivial calls +- **Memory advantage**: native allocations don't touch the JVM heap, reducing GC pressure significantly (0 KB vs 131 MB for string-heavy workloads) +- **Thread-safe**: all concurrent benchmarks pass with zero crashes (AtomicReference error state, idempotent dispose) + +## What's NOT supported + +### Not yet implemented + +| Feature | Notes | +|---------|-------| +| Generics (`class Box`) | Complex type erasure at FFM boundary — use concrete types | +| Interface / sealed class as return type | Methods must return the concrete type, not the interface/sealed parent | +| Operator overloading (`operator fun plus`) | Use named methods (`fun add()`) | +| Infix functions | Use regular method syntax | +| Extension functions on stdlib types | Only extensions on project classes are bridged | + +### By design + +| Feature | Reason | Alternative | +|---------|--------|-------------| +| Private/internal/protected members | Only public API is exported | Use `public` modifier | +| Expect/actual declarations | KMP's responsibility | Use platform-specific source sets | +| `ByteArray` in collections | Buffer lifecycle complexity across FFM | Use `List` or Base64 String | +| `ByteArray` as data class field | Out-param buffer not wired for DC fields | Use separate method or String | +| `ByteArray` as callback param | Buffer lifecycle across callback boundary | Use String (Base64) | +| Lambda as callback return type | Callbacks supported as parameters only | Return object with methods | +| CInterop types in public API (`CPointer`, `COpaque`) | Kotlin/Native-only types, not marshallable | Wrap behind a Kotlin API | +| Subclassing from JVM | JVM proxy classes are handles, not real native objects | Subclass on native side | + +### Scope limitations + +The bridge is designed for **Kotlin-level APIs** — clean classes, interfaces, data classes, functions. It is **not** a C FFI wrapper. Projects that expose raw C types in their public API (like GTK bindings with `CPointer`) are not compatible. Wrap them behind a clean Kotlin API first. + +## Configuration reference + +```kotlin +kotlinNativeExport { + // Name of the shared library (required) + // Produces: libmylib.so (Linux), libmylib.dylib (macOS), mylib.dll (Windows) + nativeLibName = "mylib" + + // Package for JVM proxies — auto-detected from your Kotlin source package declarations + // Only set this if you have multiple packages and want to override the auto-detection + // nativePackage = "com.example" + + // Build type: "release" (default, ~700KB .so) or "debug" (~6MB .so) + buildType = "release" +} +``` + +## JVM runtime requirements + +The generated FFM proxies require: + +- **JDK 22+** (FFM API finalized in JDK 22 via [JEP 454](https://openjdk.org/jeps/454), recommended JDK 25) +- **`--enable-native-access=ALL-UNNAMED`** JVM arg (auto-configured for tests by the plugin) + +The native library is automatically bundled in the JAR and extracted at runtime — no manual `java.library.path` configuration needed. + +## Zero-config native library loading + +The generated `KneRuntime` uses a three-tier loading strategy: + +1. **`java.library.path`** — for development, packaged apps, or manual override +2. **JAR extraction** — extracts from `kne/native/{os}-{arch}/` in the classpath to a persistent cache (`~/.cache/kne/`) +3. **Loader lookup** — fallback for GraalVM native-image (native lib beside the executable) + +## Build performance & Gradle Cache + +Currently, the bridge generation task is marked as `@DisableCachingByDefault` because the Kotlin PSI source analysis is not yet fully cacheable. + +- **Status**: The plugin performs a full re-scan and code generation on every build if sources change. +- **Future work**: Implement proper Gradle build caching and incremental compilation by mapping source files to specific IR outputs, allowing faster builds for large projects. + +## GraalVM native-image support + +The plugin auto-generates GraalVM reachability metadata under `META-INF/native-image/kne/{libName}/`: + +- `reflect-config.json` — all generated proxy classes +- `resource-config.json` — bundled native library resources +- `reachability-metadata.json` — FFM foreign downcall + upcall descriptors, reflection, and resources + +For GraalVM native-image builds, the native `.so`/`.dylib` must be placed next to the executable (the plugin bundles it in the JAR for JVM, but native-image can't extract at runtime). + +## Using with Compose Desktop / Nucleus + +The Compose compiler plugin and Nucleus Native Access both add Kotlin/Native targets, but Compose doesn't support arbitrary native compilations (e.g. `linuxX64`, `mingwX64` for FFM bridges). **The recommended approach is to put your native code in a separate Gradle module** without the Compose compiler plugin: + +``` +my-app/ +├── native/ ← Kotlin/Native + nucleusnativeaccess (no Compose) +│ └── build.gradle.kts +├── app/ ← Compose Desktop + depends on :native +│ └── build.gradle.kts +└── settings.gradle.kts +``` + +**`:native/build.gradle.kts`** — native bridge module: + +```kotlin +plugins { + kotlin("multiplatform") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + linuxX64() // or macosArm64(), mingwX64() + jvm() +} + +kotlinNativeExport { + nativeLibName = "mylib" + // nativePackage auto-detected from source package declarations +} +``` + +**`:app/build.gradle.kts`** — Compose Desktop module: + +```kotlin +plugins { + kotlin("multiplatform") version "2.3.20" + id("org.jetbrains.compose") version "1.10.2" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("io.github.kdroidfilter.nucleus") version "1.7.2" +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(project(":native")) + } + } + } +} + +nucleus.application { + mainClass = "com.example.MainKt" + jvmArgs += listOf("--enable-native-access=ALL-UNNAMED") +} +``` + +This avoids any conflict between the Compose compiler and Kotlin/Native targets used for FFM bridges. + +## Using with C interop (e.g. libnotify) + +You can combine the plugin with Kotlin/Native cinterop to wrap native C libraries and expose them to JVM: + +```kotlin +// build.gradle.kts +kotlin { + linuxX64().compilations["main"].cinterops { + val libnotify by creating { + defFile(project.file("src/nativeInterop/cinterop/libnotify.def")) + } + } +} +``` + +```kotlin +// src/nativeMain/kotlin/LinuxDesktop.kt +class LinuxDesktop { + fun sendNotification(title: String, body: String, icon: String): Boolean { + // calls libnotify via cinterop — impossible from JVM without JNI + notify_init("MyApp") + val n = notify_notification_new(title, body, icon) ?: return false + return notify_notification_show(n, null) != 0 + } + + fun getHostname(): String = memScoped { + val buf = allocArray(256) + gethostname(buf, 256u) + buf.toKString() + } +} +``` + +```kotlin +// src/jvmMain/kotlin/Main.kt — transparent usage +val desktop = LinuxDesktop() +desktop.sendNotification("Hello", "From Kotlin/Native via FFM!", "dialog-information") +println(desktop.getHostname()) +desktop.close() +``` diff --git a/README-RUST.md b/README-RUST.md new file mode 100644 index 00000000..f7b0f6cc --- /dev/null +++ b/README-RUST.md @@ -0,0 +1,675 @@ +# Nucleus Native Access — Rust Bridge + +← [Back to overview](README.md) + +> **Proof of concept.** The Rust bridge demonstrates that the NNA pipeline is language-agnostic: the same FFM proxy infrastructure works with any language that emits a C-ABI shared library. It is not intended for production use and may change significantly or be removed. + +Import **any Rust crate** and use it from Kotlin/JVM as if it were a Kotlin library. No modifications to the Rust crate required — unlike UniFFI/Gobley which require `#[uniffi::export]` annotations. + +``` +Rust crate (any) Plugin generates JVM developer sees +──────────────── ──────────────── ────────────────── +pub struct Calculator { → rustdoc JSON → parse → class Calculator : AutoCloseable { + pub fn add(&mut self, n) #[no_mangle] bridges fun add(n: Int): Int + pub fn get_current(&self) + FFM MethodHandles val current: Int +} + cargo build → .so // backed by Rust, via FFM + } +``` + +**Pipeline:** + +1. Plugin runs `cargo rustdoc --output-format json` to extract the crate's public API +2. Parses the rustdoc JSON — structs, enums, methods, functions, types +3. Generates Rust `#[no_mangle] pub extern "C" fn` bridge wrappers (same symbol convention as Kotlin/Native) +4. Reuses the existing `FfmProxyGenerator` to produce JVM proxy classes +5. Runs `cargo build --release` to produce the shared library +6. Bundles the `.so`/`.dylib`/`.dll` into the JAR + +## Quick start + +### 1. Configure the plugin + +```kotlin +// build.gradle.kts +plugins { + kotlin("multiplatform") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() +} + +rustImport { + libraryName = "mylib" + jvmPackage = "com.example.mylib" + cratePath("my-crate", "../rust") // local crate + // crate("some-crate", "1.0") // from crates.io + // crateGit("name", "https://...", branch = "main") // from git +} +``` + +### 2. Write normal Rust — no special annotations + +```rust +// rust/src/lib.rs +pub struct Calculator { + accumulator: i32, +} + +impl Calculator { + pub fn new(initial: i32) -> Self { Calculator { accumulator: initial } } + pub fn add(&mut self, value: i32) -> i32 { self.accumulator += value; self.accumulator } + pub fn get_current(&self) -> i32 { self.accumulator } + pub fn describe(&self) -> String { format!("Calculator(current={})", self.accumulator) } +} + +pub enum Operation { Add, Subtract, Multiply } + +pub fn greet(name: String) -> String { format!("Hello, {}!", name) } +``` + +### 3. Use it from JVM — same API as Kotlin/Native + +```kotlin +// src/jvmMain/kotlin/Main.kt +fun main() { + val calc = Calculator(0) + calc.add(5) + calc.add(3) + println(calc.current) // 8 + println(calc.describe()) // "Calculator(current=8)" + calc.close() +} +``` + +### 4. Run + +```bash +./gradlew jvmTest # generates bridges + cargo build + runs JVM tests +./gradlew run # if using Compose Desktop +``` + +## Object lifecycle + +Every generated proxy class implements `AutoCloseable`. Each instance registers itself with a JVM `Cleaner` at construction time — **`close()` is never mandatory**, the GC will always free the native memory eventually. + +```kotlin +val calc = Calculator(0) +calc.add(5) +// close() not called — the Cleaner will free the Rust Box on next GC +``` + +Call `close()` explicitly when you want **deterministic release**: + +| Situation | Recommendation | +|-----------|---------------| +| Short-lived object, GC runs frequently | `close()` optional | +| Object holds a costly resource (open camera, socket, large native buffer) | Always `close()` | +| Object created in a tight loop or at high frequency (e.g. `Disks.new_with_refreshed_list()` every 2s) | Always `close()` — native memory accumulates faster than GC collects | +| Long-running `Flow` or coroutine scope | `close()` in the `finally` block to release on cancellation | + +```kotlin +// Explicit release — deterministic, no waiting for GC +Calculator(0).use { calc -> + calc.add(5) + println(calc.get_current()) +} // close() called automatically by use {} + +// Flow: release in finally to free as soon as the flow is cancelled +val sys = System.new_all() +try { + while (isActive) { + sys.refresh_all() + val diskList = Disks.new_with_refreshed_list() + val disks = diskList.list().map { /* … */ } + diskList.close() // created every iteration — close immediately + emit(/* … */) + delay(2.seconds) + } +} finally { + sys.close() // released when the Flow collector cancels +} +``` + +## Real-world examples + +Three real crates imported directly from crates.io — no Rust modifications, no wrapper crate, just `rustImport { crate(...) }` in `build.gradle.kts`. Source: [`examples/`](examples/). + +### System information — `sysinfo 0.38` + +```kotlin +// build.gradle.kts +rustImport { + libraryName = "rustsysinfo" + jvmPackage = "com.example.rustsysinfo" + crate("sysinfo", "0.38.4") +} +``` + +The plugin maps `System`, `Disks`, `Networks`, `Components`, `Users`, `Groups`, and `Process` directly from the sysinfo API. + +**Data flow**: a Kotlin `Flow` polls `System.new_all()` every 2 seconds and emits structured state. The Compose UI collects it with `collectAsState`. + +```kotlin +// Polling flow — runs on Dispatchers.IO +fun dynamicStateFlow(interval: Duration = 2.seconds): Flow = flow { + val sys = System.new_all() + try { + while (true) { + sys.refresh_all() + + val cpus = sys.cpus().map { cpu -> + CpuInfo(name = cpu.name(), brand = cpu.brand(), usage = cpu.cpu_usage(), frequency = cpu.frequency()) + } + val memory = MemoryInfo( + totalMemory = sys.total_memory(), + usedMemory = sys.used_memory(), + availableMemory = sys.available_memory(), + totalSwap = sys.total_swap(), + usedSwap = sys.used_swap(), + ) + val processes = sys.processes().values + .sortedByDescending { it.cpu_usage() } + .take(30) + .map { proc -> + ProcessInfo( + pid = proc.pid().as_u32().toLong(), + name = proc.name(), + cpuUsage = proc.cpu_usage(), + memory = proc.memory(), + status = proc.status().tag.name, + cmd = proc.cmd(), + ) + } + val diskList = Disks.new_with_refreshed_list() + val disks = diskList.list().map { disk -> + DiskInfo(name = disk.name(), mountPoint = disk.mount_point(), totalSpace = disk.total_space(), availableSpace = disk.available_space()) + } + diskList.close() // recommended: created every 2s in a loop — don't wait for GC + + emit(DynamicState(memory = memory, cpus = cpus, processes = processes, disks = disks, globalCpuUsage = sys.global_cpu_usage())) + delay(interval) + } + } finally { + sys.close() // recommended: long-lived resource, release as soon as the Flow is cancelled + } +}.flowOn(Dispatchers.IO) +``` + +**Compose UI** — the state flows into `collectAsState` and drives the UI directly: + +```kotlin +@Composable +fun App() { + val state by remember { dynamicStateFlow() }.collectAsState(initial = null) + + when (selected) { + NavItem.Cpu -> CpuTab(state?.cpus ?: emptyList(), state?.globalCpuUsage ?: 0f) + NavItem.Memory -> MemoryTab(state?.memory) + NavItem.Disks -> DisksTab(state?.disks ?: emptyList()) + NavItem.Processes -> ProcessesTab(state?.processes ?: emptyList()) + // … + } +} + +@Composable +fun CpuTab(cpus: List, globalUsage: Float) { + LazyColumn(modifier = Modifier.fillMaxSize()) { + item { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Total Usage", "%.1f%%".format(globalUsage), Modifier.weight(1f)) + StatBox("Cores", "${cpus.size}", Modifier.weight(1f)) + StatBox("Frequency", "${cpus.firstOrNull()?.frequency} MHz", Modifier.weight(1f)) + } + } + items(cpus) { cpu -> + GaugeBar(label = cpu.name, fraction = cpu.usage / 100f, detail = "%.1f%% @ %d MHz".format(cpu.usage, cpu.frequency)) + } + } +} + +@Composable +fun MemoryTab(info: MemoryInfo?) { + LazyColumn(modifier = Modifier.fillMaxSize()) { + item { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Total", formatBytes(info.totalMemory), Modifier.weight(1f)) + StatBox("Used", formatBytes(info.usedMemory), Modifier.weight(1f)) + StatBox("Free", formatBytes(info.availableMemory), Modifier.weight(1f)) + } + } + item { + val usedPct = info.usedMemory.toFloat() / info.totalMemory + GaugeBar("RAM", usedPct, "${formatBytes(info.usedMemory)} / ${formatBytes(info.totalMemory)}") + } + } +} + +@Composable +fun ProcessesTab(processes: List) { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(processes) { proc -> + Column { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text(proc.name, fontWeight = FontWeight.Bold) + Text("PID ${proc.pid}") + } + GaugeBar("CPU", proc.cpuUsage / 100f, "%.1f%%".format(proc.cpuUsage)) + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Memory", formatBytes(proc.memory), Modifier.weight(1f)) + StatBox("Virtual", formatBytes(proc.virtualMemory), Modifier.weight(1f)) + } + Badge(proc.status) + } + } + } +} +``` + +Full Compose Desktop app with 9 tabs (System, CPU, Memory, Disks, Network, Processes, Sensors, Users, Groups): [`examples/rust-sysinfo/`](examples/rust-sysinfo/). + +```bash +./gradlew :examples:rust-sysinfo:run +``` + +--- + +### Native file dialogs — `rfd 0.17` + +[rfd](https://crates.io/crates/rfd) opens native OS file pickers and message dialogs on Linux (GTK), macOS, and Windows — no AWT, no Swing. + +```kotlin +// build.gradle.kts +rustImport { + libraryName = "rustrfd" + jvmPackage = "com.example.rustrfd" + crate("rfd", "0.17.2") +} +``` + +The plugin maps `FileDialog`, `MessageDialog`, `MessageLevel`, `MessageButtons`, and `MessageDialogResult`. All dialog methods are `async fn` on the Rust side → automatically mapped to `suspend fun`. In Compose, call them from a `rememberCoroutineScope`. + +`FileDialog` and `MessageDialog` are short-lived objects — no `close()` needed, the GC handles them automatically. + +```kotlin +@Composable +fun FilePickerTab() { + val scope = rememberCoroutineScope() + var busy by remember { mutableStateOf(false) } + var result by remember { mutableStateOf(null) } + + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + + // Single file picker with filters + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button(enabled = !busy, onClick = { + busy = true + scope.launch { + val path = FileDialog() + .set_title("Select an image") + .add_filter("Images", listOf("png", "jpg", "jpeg", "gif")) + .pick_file() + result = path ?: "Cancelled" + busy = false + } + }) { Text("Pick Image") } + + Button(enabled = !busy, onClick = { + busy = true + scope.launch { + val paths = FileDialog() + .set_title("Select source files") + .add_filter("Kotlin", listOf("kt", "kts")) + .add_filter("Rust", listOf("rs", "toml")) + .pick_files() + result = paths?.joinToString("\n") ?: "Cancelled" + busy = false + } + }) { Text("Pick Sources") } + + Button(enabled = !busy, onClick = { + busy = true + scope.launch { + val path = FileDialog() + .set_title("Save as") + .set_file_name("output.txt") + .add_filter("Text", listOf("txt")) + .save_file() + result = path ?: "Cancelled" + busy = false + } + }) { Text("Save File") } + } + + result?.let { Text(it) } + } +} + +@Composable +fun MessageTab() { + val scope = rememberCoroutineScope() + var answer by remember { mutableStateOf(null) } + + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + + // Warning dialog — Yes/No/Cancel + Button(onClick = { + scope.launch { + val r = MessageDialog() + .set_title("Save Changes?") + .set_description("You have unsaved changes. Do you want to save before closing?") + .set_level(MessageLevel.Warning) + .set_buttons(MessageButtons.yesNoCancelCustom("Save", "Discard", "Go Back")) + .show() + answer = r.tag.name // "Yes", "No", or "Cancel" + } + }) { Text("Unsaved changes…") } + + // Error dialog + Button(onClick = { + scope.launch { + val r = MessageDialog() + .set_title("Critical Error") + .set_description("A critical error was detected. Would you like to try again?") + .set_level(MessageLevel.Error) + .set_buttons(MessageButtons.okCancel()) + .show() + answer = r.tag.name + } + }) { Text("Show Error") } + + answer?.let { Text("Result: $it") } + } +} +``` + +Full Compose Desktop app with file picker, folder picker, save dialog, and message dialog tabs: [`examples/rust-rfd/`](examples/rust-rfd/). + +```bash +./gradlew :examples:rust-rfd:run +``` + +--- + +### Webcam capture — `nokhwa 0.10` + +[nokhwa](https://crates.io/crates/nokhwa) provides cross-platform webcam access (V4L2 on Linux, AVFoundation on macOS, DirectShow on Windows). + +```kotlin +// build.gradle.kts +rustImport { + libraryName = "rustcamera" + jvmPackage = "com.example.rustcamera" + crate("nokhwa", "0.10", features = listOf("input-native")) +} +``` + +The plugin maps `Camera`, `CameraInfo`, `CameraIndex`, `CameraFormat`, `Resolution`, `RequestedFormat`, `ApiBackend`, and format enums (`RgbFormat`, `YuyvFormat`, `LumaFormat`…). + +**Data flow**: a `channelFlow` opens the camera, captures frames continuously, and emits `CameraState`. The Compose UI renders the live feed via `Image(bitmap = ...)`. + +```kotlin +fun cameraStateFlow(): Flow = channelFlow { + val index = CameraIndex.new_idx(0) + val format = RequestedFormat.new_with(RequestedFormatType.AbsoluteHighestResolution) + val camera = Camera.new(index, format) + camera.open_stream() + + try { + while (isActive) { + val buffer = camera.frame_raw() + val fmt = camera.camera_format() + val w = fmt.width_x() + val h = fmt.height_y() + + val image = BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR).also { + it.raster.setDataElements(0, 0, w, h, buffer) + } + send(CameraState( + isOpen = true, + frame = image, + currentFormat = FormatInfo(w, h, fmt.frame_rate(), fmt.format().tag.name), + )) + } + } finally { + camera.stop_stream() + camera.close() // recommended: holds an open hardware stream — release on cancellation + } +}.flowOn(Dispatchers.IO) +``` + +**Compose UI** — the live frame is rendered as a `Bitmap`, format info displayed alongside: + +```kotlin +@Composable +fun App() { + val state by remember { cameraStateFlow() }.collectAsState(initial = null) + PreviewTab(state) +} + +@Composable +fun PreviewTab(state: CameraState?) { + Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) { + + // Stats bar + state?.currentFormat?.let { fmt -> + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Resolution", "${fmt.width}x${fmt.height}", Modifier.weight(1f)) + StatBox("Frame Rate", "${fmt.frameRate} fps", Modifier.weight(1f)) + StatBox("Format", fmt.format, Modifier.weight(1f)) + } + } + + // Live feed — BufferedImage → Compose ImageBitmap + state?.frame?.let { frame -> + Image( + bitmap = frame.toComposeImageBitmap(), + contentDescription = "Camera Feed", + modifier = Modifier.fillMaxWidth().clip(RoundedCornerShape(8.dp)), + contentScale = ContentScale.Fit, + ) + } + + // Stream status + state?.let { + MetricRow("Stream", if (it.isOpen) "Active" else "Closed") + } + } +} +``` + +Full Compose Desktop app with live preview, format selection, and camera controls: [`examples/rust-camera/`](examples/rust-camera/). + +```bash +./gradlew :examples:rust-camera:run +``` + +--- + +### System tray icon — `tray-icon 0.19` + +[tray-icon](https://crates.io/crates/tray-icon) provides cross-platform system tray icons with context menus (Windows, macOS, Linux/GTK). + +This example demonstrates the **limits of direct crate import** and the **local wrapper crate** pattern to work around them. + +#### Why a local wrapper? + +Importing `tray-icon` directly via `crate("tray-icon", "0.19")` works for codegen but the macOS main thread requirement cannot be solved at the bridge level: + +| Problem | Root cause | Status | +|---------|-----------|--------| +| macOS main thread requirement | `tray-icon` checks `NSThread.isMainThread` | Requires wrapper (`dispatch_sync_f`) | + +> `impl Trait` params and `Box` params are now bridged natively (see [What's supported](#whats-supported)). The wrapper is only needed for the platform thread constraint. + +The solution: a **local Rust wrapper crate** (`examples/rust-tray-icon/rust/`) that: +1. Exposes a flat API of top-level functions + **opaque `Icon` and `MenuItem` handles** for type-safe management +2. Manages the `TrayIcon` lifecycle internally via `thread_local!` +3. Dispatches all calls to the macOS main thread via `dispatch_sync_f` +4. Uses `Option` callbacks for events (`on_tray_event` / `on_menu_event`) + +```kotlin +// build.gradle.kts +rustImport { + libraryName = "tray_icon_wrapper" + jvmPackage = "com.example.rusttrayicon" + cratePath("tray-icon-wrapper", "${projectDir}/rust") +} +``` + +```rust +// rust/src/lib.rs — simplified excerpt +pub fn create_tray(icon_r: i32, ..., menu_items: Option<&str>) -> Result<(), String> { + on_main_sync(move || { /* ... */ }) +} + +// Opaque MenuItem: wraps muda::MenuItem::new (impl ToString + Option) +// into a bridgeable API using &str + bool +pub fn create_menu_item(label: &str, enabled: bool) -> tray_icon::menu::MenuItem { + MenuItem::new(label, enabled, None) +} +pub fn add_menu_item(item: &tray_icon::menu::MenuItem) { /* on_main_sync ... */ } +pub fn set_menu_item_text(item: &tray_icon::menu::MenuItem, text: &str) { item.set_text(text); } +pub fn get_menu_item_text(item: &tray_icon::menu::MenuItem) -> String { item.text() } +``` + +```kotlin +// Kotlin usage — opaque Icon and MenuItem handles +Tray_icon_wrapper.make_icon(108, 142, 255, 32).use { icon -> + Tray_icon_wrapper.create_tray_with_icon(icon, "My App", null, "Open|Quit") +} + +// Create and manage individual menu items +val item = Tray_icon_wrapper.create_menu_item("Settings", true) +Tray_icon_wrapper.add_menu_item(item) +Tray_icon_wrapper.set_menu_item_text(item, "Preferences") +Tray_icon_wrapper.set_menu_item_enabled(item, false) +item.close() // optional — GC handles cleanup +``` + +#### Lessons learned + +This example surfaces important architectural insights about the NNA bridge: + +1. **Nullable closures enable Rust-to-Kotlin callbacks**: `Option` params are bridged as nullable function pointers. Rust code can call the Kotlin lambda via FFM upcall stubs. Example: `on_menu_event(handler: ((String) -> Unit)?)` — pass `null` to clear the handler. + +2. **Opaque types block method generation**: Types with private fields (like `Icon`, `MenuItem`) become opaque handles with only `dispose()`. Their constructors and methods are not bridged. Workaround: **wrap in Rust functions** that accept simple types (`&str`, `bool`) and delegate to the opaque type's methods. See `create_menu_item`, `set_menu_item_text`, etc. + +3. **`impl Trait` params are now bridged**: `impl ToString`, `impl Into`, `impl AsRef`, etc. are resolved to concrete types at the bridge level. For example, `impl ToString` becomes `String`, which satisfies the trait bound. No wrapper needed. + +4. **`Box` params are now bridged for external traits**: When a concrete type implements the trait, the bridge monomorphizes the function — accepting the concrete type's opaque handle and upcasting to `Box` in Rust. For local traits, the existing registry-based mechanism handles ownership transfer. + +5. **Platform thread constraints need Rust-level solutions**: macOS requires AppKit main thread for tray icons. The JVM's AWT EDT is not the macOS main thread. Workaround: **`dispatch_sync_f` from Rust** to the main queue. + +6. **Callback-driven events**: With nullable closure support, the wrapper uses `on_tray_event` / `on_menu_event` callbacks instead of polling. Rust calls the Kotlin lambda directly when events fire. + +Full Compose Desktop app with tray icon control, color picker, context menu, and native event log: [`examples/rust-tray-icon/`](examples/rust-tray-icon/). + +```bash +./gradlew :examples:rust-tray-icon:run +``` + +--- + +## What's supported + +| Rust construct | Mapped to | Notes | +|----------------|-----------|-------| +| `pub struct` with methods | `KneClass` (opaque handle) | `Box::into_raw` / `Box::from_raw` lifecycle | +| `pub struct` (all-pub fields, no methods) | `KneDataClass` (field expansion) | Marshalled by value | +| `pub enum` (fieldless) | `KneEnum` | Ordinal mapping | +| `impl` methods (`&self`) | Instance methods | Immutable borrow | +| `impl` methods (`&mut self`) | Instance methods | Mutable borrow | +| `get_X()` / `set_X()` pattern | `val` / `var` properties | Auto-detected | +| `pub fn` (top-level) | Module-level functions | Grouped in singleton object | +| All primitives (`i32`, `i64`, `f64`, `f32`, `bool`, `i8`, `i16`) | Int, Long, Double, Float, Boolean, Byte, Short | Direct mapping | +| `String` / `&str` | String | Borrowed vs owned auto-detected | +| `Option` return | `T?` | Sentinel-based null encoding | +| `Vec` / `&[T]` return | `List` / `ByteArray` | Buffer pattern; supports `i32`, `i64`, `f64`, `f32`, `bool`, `String` element types | +| `&[u8]` / `&[i32]` params | `ByteArray` / `List` | Pointer + length expansion | +| `HashMap` | `Map` | Parallel arrays; nested collections supported (e.g. `Vec` values) | +| Error propagation | `KotlinNativeException` | `catch_unwind` + thread-local error | +| `(A, B)` / `(A, B, C)` tuples | `KneTupleN_` data class | Arity 0–16; nested tuples supported (e.g. `(i32, (String, bool))`) | +| Tuple as param | Expanded to individual parameters | `fn sum(coords: (i32, i32))` → `fun sum(coords: KneTuple2_TII)` | +| `!` (Never type) | Diverging functions (`panic!`, `std::process::exit`) | Returns `Unit`, throws `RuntimeException` on JVM with panic message | +| `impl Iterator` return | `List` | Collected via `.collect()` in bridge; also `ExactSizeIterator`, `IntoIterator`, `DoubleEndedIterator` | +| `impl Display` / `impl ToString` return | `String` | Materialized via `.to_string()` in bridge | +| `impl Into` return | `T` | Converted via `.into()` in bridge | +| `impl Trait` return | `T` | Resolved via known trait map (Display, ToString, IntoIterator, Iterator, ExactSizeIterator, DoubleEndedIterator, Future) | +| `async fn` / `impl Future` | `suspend fun` returning `T` | Rust side: `pollster::block_on()`; Kotlin side: `suspend fun` with `withContext(Dispatchers.IO)` | +| `impl Into` / `impl ToString` params | `String` | Synthetic `impl Trait` params in argument position are resolved; `&[impl ToString]` → `List` | +| Trait objects (`dyn Trait`) | Supported | `Box` returns via registry; `&dyn Trait` / `&mut dyn Trait` / `Box` params via handle; external traits monomorphized to concrete implementor | +| `fn(A) -> B` callbacks | Supported | Function pointers and `impl Fn`/`FnOnce` with primitive, enum, object, sealed enum, and `dyn Trait` types | +| Callbacks with handle types | Supported | `impl FnOnce(Object) -> SealedEnum`, `impl FnOnce(i32) -> Box`, etc. | + +### Supported with caveats + +| Construct | Behaviour | Notes | +|-----------|-----------|-------| +| `HashMap` / `BTreeMap` return | Mapped to `Map` | Keys/values serialized via dual-buffer pattern; supports nested collections as values (e.g. `HashMap>` → `Map>`) | +| `HashSet` / `BTreeSet` return | Mapped to `Set` | Serialized as list, deduplicated on JVM side via `.toSet()` | +| `Option` return | Mapped to `DataClass?` | Uses presence flag (0=null, 1=present) + per-field out-params | +| `Option>` return | Mapped to `ByteArray?` | Uses buffer pattern; returns `-1` for `None`, byte count for `Some` | +| `OsStr` / `OsString` / `Path` / `PathBuf` | Mapped to `String` | Uses `to_string_lossy()` on output, may lose non-UTF-8 data | +| `Vec` return | Elements returned as borrowed handles | Pointers into the parent collection; valid while parent lives | +| Borrowed returns (`&T`) | Returned as borrowed handle (no ownership) | JVM proxy won't dispose the native object | +| `unsafe fn` methods | Generated with `unsafe { }` wrapper | Caller is responsible for safety invariants | +| Tuple return with nested tuples | `(i32, (String, bool))` → `KneTuple2_TITRZ` | Inner tuples heap-allocated with 8-byte-slot layout; supports arbitrary nesting depth | +| `impl Iterator` return | Mapped to `List` | Collected via `.collect::>()` in bridge | +| `&[T]` return (borrowed slices) | Materialized to `List` | Borrowed slice is copied into a `Vec` in the bridge; safe but allocates | +| `Result` return | Combined with impl Trait | Result unwrapped first, then impl Trait conversion applied | +| `async fn` methods | `suspend fun` with `withContext(Dispatchers.IO)` | Rust bridge uses `pollster::block_on()`; Kotlin proxy emits private sync method + public `suspend fun` wrapper | + +## Current limitations + +Excluded functions are logged at generation time (stderr), so you know exactly which functions were skipped and why. + +| Category | Unsupported construct | Impact | Workaround | +|----------|----------------------|--------|------------| +| **Generics** | Generic **methods** with custom trait bounds (`fn process(...)`) | **Auto-monomorphised**: NNA scans `impl Trait for Type` blocks and generates one bridge per concrete implementor. Turbofish applied automatically. | — | +| **Generics** | Generic **structs** with custom trait bounds (`struct Foo`) | **Auto-monomorphised**: NNA generates one class per concrete implementor. | — | +| **Generics** | Generic types with lifetime parameters in args | Lifetime args in generic position are skipped | — | +| **Types** | Function pointer types (`fn(A) -> B`) as return | Skipped with log message | — | +| **Types** | Tuple parameters on standalone `pub fn` | Tuples as parameters not supported | Expand tuple fields into individual parameters | +| **Enums** | Tagged enum variants with collection fields | Constructors supported for `Vec`, `HashSet`, `HashMap` with **primitive** element types only | Use primitive element types | +| **Types** | Cross-crate re-exported types | **Lazy resolution**: types from sub-crates are auto-discovered from rustdoc JSON index | — | +| **Constructors** | Generic constructors (`fn new(...)`) on standalone structs | Skipped if generics can't be resolved | Use concrete types or non-generic factory methods | +| **Mutability** | Interior mutability (`Cell`, `RefCell`, `Mutex`) | No special handling; may cause UB if misused | — | +| **Concurrency** | `Send` / `Sync` bounds | Not enforced on JVM side | Be careful with multithreaded access | +| **Lifetimes** | Explicit lifetime parameters on structs (`struct Ref<'a>`) | Entire struct skipped with log message | Remove lifetime parameters or use owned types | +| **Mutability** | `&mut T` parameters on standalone `pub fn` | Treated as `&T` (immutable borrow) | Use `impl` methods with `&mut self` instead | +| **Opaque types** | Structs with private fields (e.g. `Icon`) | Only `dispose()` generated — no constructors or methods | Wrap in a Rust function that creates and uses the type internally | +| **Callbacks** | `Option` nullable closure params | Supported — bridged as nullable function pointers with FFM upcall stubs | Pass `null` to clear the handler | +| **Nullable collections** | `Option>` parameters | `appendNullableParamConversion` has no code path — method skipped | Use non-nullable params or wrap in Rust | +| **`Box` fields** | Struct fields typed `Box` (e.g. `TrayIconAttributes.menu`) | Constructor skipped because param is unbridgeable | Use the builder pattern or wrap in a Rust function | +| **`impl Trait` params** | `fn new(text: impl ToString, ...)` | Not monomorphisable in argument position for free functions | Call from Rust side | +| **Thread affinity** | APIs requiring specific threads (e.g. macOS AppKit main thread) | JVM main thread is not the OS main thread | Use `dispatch_sync_f` or equivalent from Rust | + +## Benchmarks — Rust (FFM) vs Pure JVM + +Same methodology as the Kotlin/Native benchmarks. Run with `./gradlew :examples:rust-benchmark:jvmTest`. + +| Benchmark | Rust (FFM) | JVM | Ratio | Analysis | +|-----------|-----------|-----|-------|----------| +| Fibonacci recursive (n=35) | 18.42 ms | 24.21 ms | **0.76x** | **Rust faster** (no JIT warmup) | +| Fibonacci iterative (n=1M) | 0.26 ms | 0.26 ms | **1.00x** | Identical | +| Pi Leibniz series (10M) | 8.44 ms | 8.40 ms | **1.00x** | Identical | +| Sum array (10M) | ~0 ms | 0.67 ms | **~0x** | **Rust much faster** | +| String concat (10K) | 0.14 ms | 18.74 ms | **0.01x** | **Rust 100x faster** (Rust string alloc) | +| Bubble sort (5K) | 13.90 ms | 5.99 ms | 2.32x | JVM JIT better at array access | +| FFM overhead (100K calls) | 1.86 ms | 0.24 ms | 7.63x | ~19 ns/call FFM overhead | +| Object create+close (10K) | 1.63 ms | 0.09 ms | 17x | Box alloc+drop cost | +| String return (10K) | 5.30 ms | 0.64 ms | 8.23x | Buffer copy overhead | +| Data class return (10K) | 2.06 ms | 0.04 ms | 48x | Out-param marshaling | +| Concurrent fib (10t×1K) | 0.94 ms | 0.45 ms | 2.07x | Thread contention | +| Concurrent string (10t×1K) | 0.97 ms | 1.24 ms | **0.78x** | **Rust faster** | + +**Rust vs Kotlin/Native comparison**: Rust and Kotlin/Native show similar FFM overhead profiles. Rust excels at string concatenation (~100x faster than JVM) and array summation. Both are competitive on heavy compute (fibonacci, pi). The FFM call overhead is lower for Rust (~19 ns/call vs ~49 ns/call for KN). + +## Requirements + +- **Rust** toolchain — `cargo` must be on PATH or in `~/.cargo/bin/` +- **JDK 22+** (FFM stable since [JEP 454](https://openjdk.org/jeps/454)), recommended JDK 25 +- **`--enable-native-access=ALL-UNNAMED`** JVM arg (auto-configured for tests by the plugin) diff --git a/README.md b/README.md index 4a68665b..7eacdadd 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,43 @@ [![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fplugins.gradle.org%2Fm2%2Fio%2Fgithub%2Fkdroidfilter%2Fnucleusnativeaccess%2Fio.github.kdroidfilter.nucleusnativeaccess.gradle.plugin%2Fmaven-metadata.xml&label=Gradle%20Plugin%20Portal)](https://plugins.gradle.org/plugin/io.github.kdroidfilter.nucleusnativeaccess) -A Gradle plugin that lets you use **Kotlin/Native code directly from the JVM** as if it were a regular JVM library. Classes, methods, properties, enums, nullable types, companion objects, exception propagation, callbacks — everything is transparent to the JVM developer. +A Gradle plugin that lets you use **Kotlin/Native or Rust code directly from the JVM** as if it were a regular JVM library. Classes, methods, properties, enums, nullable types, companion objects, exception propagation, callbacks — everything is transparent to the JVM developer. Under the hood, the plugin generates [FFM (Foreign Function & Memory API)](https://openjdk.org/jeps/454) bindings inspired by [swift-java](https://github.com/swiftlang/swift-java) and [swift-export-standalone](https://github.com/JetBrains/kotlin/tree/master/native/swift/swift-export-standalone). +## Two bridges, one plugin + +| Bridge | Source language | Gradle DSL | Status | Guide | +|--------|----------------|------------|--------|-------| +| **Kotlin/Native** | `nativeMain` Kotlin sources | `kotlinNativeExport { }` | Experimental | [README-KOTLIN-NATIVE.md](README-KOTLIN-NATIVE.md) | +| **Rust** | Any Rust crate — no annotations required | `rustImport { }` | Proof of concept | [README-RUST.md](README-RUST.md) | + +Both bridges produce the same result on the JVM: idiomatic Kotlin proxy classes backed by a native shared library loaded via FFM. No JNI. No annotations. No boilerplate. + ## How it works ``` -Kotlin/Native source Plugin generates JVM developer sees -────────────────── ──────────────── ────────────────── -class Calculator { → @CName bridges (native) → class Calculator : AutoCloseable { - fun add(value: Int) + StableRef lifecycle fun add(value: Int): Int - val current: Int + FFM MethodHandles val current: Int -} + output-buffer strings // backed by native, via FFM - } +Source (Kotlin/Native or Rust) Plugin generates JVM developer sees +────────────────────────────── ──────────────── ────────────────── +class Calculator { → C bridges (native) → class Calculator : AutoCloseable { + fun add(value: Int): Int + FFM MethodHandles fun add(value: Int): Int + val current: Int + object lifecycle val current: Int +} + shared library (.so) // backed by native, via FFM + } ``` -**Pipeline:** +**Shared pipeline**: -1. Plugin parses your `nativeMain` sources via Kotlin PSI and extracts the public API -2. Generates `@CName` bridge functions with `StableRef` for object lifecycle (native side) -3. Generates JVM proxy classes with FFM `MethodHandle` downcalls (JVM side) +1. Plugin analyses the source (Kotlin PSI for KN, rustdoc JSON for Rust) and extracts the public API +2. Generates C-ABI bridge functions on the native side +3. Generates JVM proxy classes with FFM `MethodHandle` downcalls on the JVM side 4. Compiles to a shared library (`.so` / `.dylib` / `.dll`) 5. Bundles the native library into the JAR under `kne/native/{os}-{arch}/` -6. Generates GraalVM reachability metadata (reflection, resources, FFM downcall descriptors) -7. JVM code calls the proxies transparently — every call crosses the FFM boundary into native +6. Generates GraalVM reachability metadata ## Quick start -### 1. Apply the plugin +### Apply the plugin ```kotlin // settings.gradle.kts @@ -50,592 +58,66 @@ plugins { } ``` -### 2. Configure targets - -```kotlin -kotlin { - jvmToolchain(25) // FFM is stable since JDK 22 (JEP 454), recommended JDK 25 - - linuxX64() // use the real platform name (KMP convention) - // macosArm64() // on macOS - // mingwX64() // on Windows - - jvm() - - sourceSets { - val jvmMain by getting { - dependencies { - // your JVM dependencies (Compose, Ktor, etc.) - } - } - } -} - -kotlinNativeExport { - nativeLibName = "mylib" // output: libmylib.so (release ~700KB) - // nativePackage is auto-detected from your Kotlin source package declarations - // nativePackage = "com.example" // override only if needed - buildType = "release" // "release" (default, optimized) or "debug" -} -``` - -### 3. Write Kotlin/Native code - -```kotlin -// src/nativeMain/kotlin/com/example/Calculator.kt -package com.example - -class Calculator(initial: Int = 0) { - private var acc = initial - - fun add(value: Int): Int { acc += value; return acc } - fun subtract(value: Int): Int { acc -= value; return acc } - val current: Int get() = acc - fun describe(): String = "Calculator(current=$acc)" -} -``` - -### 4. Use it from JVM as if it were a normal class - -```kotlin -// src/jvmMain/kotlin/com/example/Main.kt -package com.example - -fun main() { - val calc = Calculator(0) // allocates a Kotlin/Native object - calc.add(5) // FFM → native → StableRef → add() - calc.add(3) - println(calc.current) // 8 - println(calc.describe()) // "Calculator(current=8)" - calc.close() // releases the native object (also auto-GC'd via Cleaner) -} -``` - -No JNI. No annotations. No boilerplate. Just write Kotlin/Native and use it from JVM. - -### 5. Run - -```bash -./gradlew jvmTest # compiles native + generates bridges + runs JVM tests -./gradlew run # if using Compose Desktop / Nucleus -``` - -## What's supported - -### Types — test coverage (1700+ end-to-end FFM tests) - -Every test compiles Kotlin/Native → `libcalculator.so` (470+ exported symbols) → loads via FFM `MethodHandle` → verifies on JVM. Zero mocks — all 1700+ tests cross the real native boundary. Includes load tests (500K+ FFM calls), concurrent stress tests, 110+ suspend function tests with cancellation, 50+ Flow tests, and 300+ inheritance/interface/extension tests. - -| Feature | As param | As return | As property | CB param | CB return | Notes | -|---------|----------|-----------|-------------|----------|-----------|-------| -| `Int` | ✅ 5t | ✅ 5t | ✅ 2t | ✅ 3t | ✅ 2t | direct pass-through | -| `Long` | ✅ 2t | ✅ 2t | — | ✅ 2t | ✅ 2t | direct pass-through | -| `Double` | ✅ 2t | ✅ 2t | ✅ 1t | ✅ 2t | ✅ 2t | direct pass-through | -| `Float` | ✅ 1t | ✅ 1t | — | ✅ 1t | ✅ 1t | direct pass-through | -| `Boolean` | ✅ 3t | ✅ 2t | ✅ 1t | ✅ 3t | ✅ 1t | 0/1 convention over FFM | -| `Byte` | ✅ 1t | ✅ 1t | — | ✅ 1t | ✅ 1t | direct pass-through | -| `Short` | ✅ 1t | ✅ 1t | — | ✅ 1t | ✅ 1t | direct pass-through | -| `String` | ✅ 4t | ✅ 4t | ✅ 3t | ✅ 4t | ✅ 3t | output-buffer pattern | -| `Unit` | — | ✅ 1t | — | — | ✅ 3t | `FunctionDescriptor.ofVoid(...)` | -| `enum class` | ✅ 3t | ✅ 2t | ✅ 2t | ✅ 2t | ✅ 3t | ordinal mapping | -| Classes | ✅ 3t | ✅ 4t | — | ✅ 19t | ✅ 2t | opaque handle via `StableRef` (incl. Object in callbacks) | -| Nested classes | ✅ | ✅ | ✅ | — | — | exported as `Outer_Inner`, supports 3+ nesting levels | -| `T?` (nullable) | ✅ 3t | ✅ 8t | ✅ 3t | ❌ | — | sentinel-based null encoding (incl. `DataClass?`) | -| `data class` | ✅ 4t | ✅ 6t | — | ✅ 5t | ✅ 3t | all field types: primitive, String, Enum, Object, nested DC, List, Set, Map | -| `ByteArray` | ✅ 2t | ✅ 2t | — | ❌ | — | pointer + size pattern, suspend ✅ | -| `List` | ✅ 26t | ✅ 17t | — | ✅ 12t | ✅ 5t | Int, Long, Double, Float, Short, Byte, Boolean, String, Enum, Object | -| `List` | — | ✅ 15t | — | — | — | opaque handle + size/get/dispose bridges (Point, NamedValue, TaggedPoint) | -| `List?` | ✅ 7t | ✅ 8t | — | — | — | -1 count = null sentinel | -| `Set` | ✅ 9t | ✅ 13t | — | — | — | Int, String, Enum + intersect/empty edge cases | -| `Set?` | — | ✅ 5t | — | — | — | -1 count = null sentinel | -| `Map` | ✅ 12t | ✅ 12t | — | ✅ 2t | ✅ 2t | String→Int, Int→String, Int→Int, String→String + merge/empty | -| `Map?` | — | ✅ 4t | — | — | — | -1 count = null sentinel | -| `(T) -> R` (lambda) | ✅ 15t | — | — | — | — | persistent `Arena.ofShared()` | -| `Flow` | — | ✅ 50t+ | — | — | — | `channelFlow` + 3 callbacks (onNext, onError, onComplete), incl. `Flow` | - -### Declarations - -| Feature | Supported | Notes | -|---------|-----------|-------| -| Top-level classes | ✅ | `StableRef` lifecycle, `AutoCloseable` on JVM | -| Open / abstract classes | ✅ | `open class Shape` → JVM `open class Shape`, hierarchy mirrored | -| Inheritance | ✅ | `class Circle : Shape` → JVM `class Circle : Shape(handle)`, multi-level (3+) | -| Interfaces | ✅ | `interface Measurable` → JVM `interface Measurable`, multi-interface impl | -| Sealed classes | ✅ | `sealed class AppResult` → JVM `sealed class`, subclass ordinal bridges | -| Extension functions | ✅ | `fun Shape.displayName()` → real Kotlin extension on JVM proxy | -| Nested classes | ✅ | exported as `Outer_Inner`, qualified bridge symbols | -| Methods (fun) | ✅ | instance methods with any supported param/return types, `override` preserved | -| Properties (val/var) | ✅ | getters + setters, all supported types, including constructor `val`/`var` params | -| Constructors | ✅ | primary constructor with supported param types | -| Constructor default params | ✅ | generates overloads for trailing default parameters | -| Companion objects | ✅ | static methods and properties on JVM proxy | -| Top-level functions | ✅ | grouped into a singleton `object` on JVM | -| Enum classes | ✅ | auto-generated JVM enum with ordinal mapping | -| Data classes (nativeMain) | ✅ | auto-generates JVM data class + field marshalling | -| Data classes (commonMain) | ✅ | reuses existing JVM type, no proxy generated | -| Suspend functions | ✅ | `suspendCancellableCoroutine` + bidirectional cancellation (110+ tests) | -| Flow<T> return | ✅ | `channelFlow` + onNext/onError/onComplete callbacks (50+ tests) | -| Exception propagation | ✅ | `try/catch` wrapping, `KotlinNativeException` on JVM | -| Object lifecycle | ✅ | `Cleaner` for GC + `close()` for explicit release | - -### Suspend functions - -Kotlin/Native `suspend fun` is transparently mapped to JVM `suspend fun`. The developer writes coroutines on both sides — no callbacks, no `CompletableFuture`. - -```kotlin -// Kotlin/Native -suspend fun fetchData(query: String): String { - delay(100) - return "result for $query" -} - -// JVM — transparent, just a suspend fun -val result = calc.fetchData("test") // suspends the coroutine -``` - -**How it works**: the native bridge launches a `CoroutineScope` with a `Job`, passes continuation + exception callbacks as FFM upcall stubs. The JVM proxy uses `suspendCancellableCoroutine` to suspend until the native coroutine completes. - -**Cancellation**: JVM coroutine cancel → `Job.cancel()` on native side. Native `CancellationException` → JVM `CancellationException`. Bidirectional, automatic. - -**Supported return types**: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `ByteArray`, `Unit`, `enum class`, `Object`, `data class`, `List`, `Set`, `Map`, nullable variants. - -### Flow<T> - -Kotlin/Native `Flow` is transparently mapped to JVM `Flow` via `channelFlow`. Event streams, tickers, and reactive patterns work naturally. - -```kotlin -// Kotlin/Native -fun countUp(max: Int): Flow = flow { - for (i in 1..max) { delay(10); emit(i) } -} - -// JVM — transparent Flow collection -calc.countUp(5).collect { println(it) } // 1, 2, 3, 4, 5 -calc.countUp(100).toList() // [1, 2, ..., 100] -calc.infiniteFlow().take(3).toList() // [0, 1, 2] — auto-cancelled -``` - -**How it works**: 3 native callbacks (`onNext`, `onError`, `onComplete`) are passed as FFM upcall stubs. The native side collects the Flow in a `CoroutineScope` and calls `onNext` for each element. The JVM proxy uses `channelFlow { trySend(...); awaitClose { cancelJob() } }`. - -**Cancellation**: collecting only N elements (via `take`, `first`) automatically cancels the native Flow collection. Manual `Job.cancel()` also propagates. - -**Supported element types**: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `enum class`, `Object`, `data class` (including nested data classes). - -**Data class in Flow**: data classes are serialized element-by-element via `StableRef` + per-type reader bridges. Nested data classes (e.g. `Flow` where `Rect` contains two `Point`) are fully supported. - -```kotlin -// Kotlin/Native -data class MemoryInfo(val totalMB: Long, val availableMB: Long) - -fun memoryFlow(intervalMs: Long = 1000L): Flow = flow { - while (true) { emit(MemoryInfo(getTotalMemoryMB(), getAvailableMemoryMB())); delay(intervalMs) } -} - -// JVM — transparent Flow collection -desktop.memoryFlow(2000L).collect { info -> - println("${info.availableMB} MB / ${info.totalMB} MB") -} -``` - -### Callbacks & lambdas - -JVM lambdas cross the FFM boundary via upcall stubs. The plugin generates all the FFM infrastructure automatically. - -**Lifecycle**: each proxy object holds a persistent `Arena.ofShared()`. Upcall stubs live as long as the object — async callbacks (event handlers, listeners) work out of the box. The arena is freed on `close()` or GC. - -**Supported callback signatures**: -- Params: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `enum class`, `data class` -- Returns: `Int`, `Long`, `Double`, `Float`, `Boolean`, `Byte`, `Short`, `String`, `Unit`, `enum class`, `data class` -- Multi-param: `(T, U) -> R` with any supported types -- Data class params are decomposed into individual fields at C ABI level - -```kotlin -// Kotlin/Native -fun onValueChanged(callback: (Int) -> Unit) { callback(accumulator) } -fun transform(fn: (Int) -> Int): Int { accumulator = fn(accumulator); return accumulator } -fun formatWith(formatter: (Int) -> String): String = formatter(accumulator) - -// JVM — transparent -calc.onValueChanged { value -> println("Value: $value") } -calc.transform { it * 2 } -calc.formatWith { "Result: $it" } - -// Async callbacks work (e.g. native event listeners) -desktop.setTrayClickCallback { index -> println("Clicked: $index") } -``` - -### Collections - -`List`, `Set`, and `Map` cross the FFM boundary using flat arrays (pointer + size), inspired by swift-java's `[UInt8]` lowering. - -**Supported element types**: `Int`, `Long`, `Double`, `Float`, `Short`, `Byte`, `Boolean`, `String`, `enum class` - -```kotlin -// Kotlin/Native -fun getScores(): List = listOf(accumulator, accumulator * 2, accumulator * 3) -fun sumAll(values: List): Int { accumulator = values.sum(); return accumulator } -fun getMetadata(): Map = mapOf("current" to accumulator, "scale" to scale.toInt()) - -// JVM — transparent -val scores = calc.getScores() // [10, 20, 30] -calc.sumAll(listOf(1, 2, 3, 4, 5)) // 15 -val meta = calc.getMetadata() // {current=42, scale=3} -``` - -| Collection | C ABI encoding | -|---|---| -| `List` | `CPointer` + `size: Int` | -| `List` | packed null-terminated buffer + count | -| `List` | ordinal array + count | -| `Set` | same as `List` (converted at boundary) | -| `Map` | parallel key + value arrays + count | - -### Data classes - -Data classes are marshalled **by value** (field decomposition) — each field becomes a separate C ABI argument. Supported field types: all primitives + `String`. - -```kotlin -// Can be in commonMain or nativeMain -data class Point(val x: Int, val y: Int) - -// nativeMain -fun getPoint(): Point = Point(accumulator, accumulator * 2) -fun addPoint(p: Point): Int { accumulator += p.x + p.y; return accumulator } - -// JVM — uses the real data class (not an opaque handle) -val p = calc.getPoint() // Point(x=5, y=10) -calc.addPoint(Point(3, 7)) // 10 -``` - -- **commonMain data classes**: the JVM already has the type — no proxy generated, field marshalling only -- **nativeMain data classes**: the plugin generates the JVM `data class` file automatically - -### Exception propagation - -All native bridge functions are wrapped in `try/catch`. When an exception occurs: - -1. The native side captures the error message in a `@ThreadLocal` variable -2. The JVM proxy calls `kne_hasError()` after every downcall -3. If an error is detected, `kne_getLastError()` retrieves the message -4. A `KotlinNativeException(message)` is thrown on the JVM side - -```kotlin -try { calc.divide(0) } catch (e: KotlinNativeException) { println(e.message) } -calc.add(5) // works normally after exception -``` - -### Nullable type encoding - -| Nullable type | Wire type | Null sentinel | -|---------------|-----------|---------------| -| `String?` | output-buffer `Int` | -1 = null | -| `Object?` | `JAVA_LONG` | 0L = null | -| `Enum?` | `JAVA_INT` | -1 = null | -| `Boolean?` | `JAVA_INT` | -1 = null, 0 = false, 1 = true | -| `Int?` | `JAVA_LONG` | `Long.MIN_VALUE` = null | -| `Long?` | `JAVA_LONG` | `Long.MIN_VALUE` = null | -| `Short?` | `JAVA_INT` | `Int.MIN_VALUE` = null | -| `Byte?` | `JAVA_INT` | `Int.MIN_VALUE` = null | -| `Float?` | `JAVA_LONG` (raw bits) | `Long.MIN_VALUE` = null | -| `Double?` | `JAVA_LONG` (raw bits) | `Long.MIN_VALUE` = null | - -## Benchmarks — Native (FFM) vs Pure JVM - -Measured on Intel Core i5-14600 (20 cores), 45 GB RAM, Ubuntu 25.10, JDK 25 (GraalVM), Kotlin 2.3.20. +Then follow the guide for your use case: -**Methodology**: each benchmark runs the operation in a tight loop. 3 warmup iterations are discarded, then 5 measured iterations are averaged. "Native" creates a proxy object via FFM and calls into the Kotlin/Native shared library (.so). "JVM" runs the equivalent Kotlin/JVM code directly. Ratio = native/jvm (>1 = native slower due to FFM overhead). Memory is measured via `Runtime.totalMemory() - freeMemory()` before/after with explicit GC. - -### Compute-bound (work stays in native, single FFM call) - -| Benchmark | Native | JVM | Ratio | Analysis | -|-----------|--------|-----|-------|----------| -| Fibonacci recursive (n=35) | 18.07 ms | 23.85 ms | **0.76x** | Native faster (no JIT warmup needed) | -| Fibonacci iterative (n=1M) | 0.30 ms | 0.21 ms | 1.43x | Near-equal, JVM JIT slightly ahead | -| Pi Leibniz series (10M iter) | 8.60 ms | 8.56 ms | **1.01x** | Identical performance | -| String concat loop (10K) | 21.65 ms | 17.62 ms | 1.23x | Near-equal | -| Bubble sort (5K elements) | 13.13 ms | 4.78 ms | 2.75x | JVM JIT optimizes array access better | - -### FFM call overhead (many small downcalls) - -| Benchmark | Native | JVM | Ratio | Analysis | -|-----------|--------|-----|-------|----------| -| 100K trivial calls | 4.94 ms | 0.31 ms | 16x | ~49 ns/call FFM overhead | -| 10K create+close cycles | 4.12 ms | 0.11 ms | 36x | StableRef alloc+dispose cost | -| 10K data class returns | 4.32 ms | 0.15 ms | 29x | Out-param marshaling cost | -| 10K string returns | 6.45 ms | 0.59 ms | 11x | Output-buffer + UTF-8 copy | -| 10K data class params | 0.96 ms | 0.01 ms | 65x | Field expansion overhead | -| 5K list params (100 elems) | 7.78 ms | 2.89 ms | 2.70x | Arena alloc + memcpy | - -### Concurrent (10 threads, separate instances) - -| Benchmark | Native | JVM | Ratio | -|-----------|--------|-----|-------| -| 10t × 1K fib(100) | 3.83 ms | 0.49 ms | 7.87x | -| 10t × 1K string reverse | 2.91 ms | 0.69 ms | 4.24x | -| 10t × 1K create+close | 2.25 ms | 0.44 ms | 5.07x | -| 10t × 1K DC roundtrip | 2.11 ms | 0.92 ms | 2.28x | - -### Memory allocation - -| Benchmark | Native | JVM | Analysis | -|-----------|--------|-----|----------| -| 100K point allocations | **0 KB** | 3,071 KB | Native: no JVM heap pressure | -| 10t × 10K points (concurrent) | **1,151 KB** | 5,124 KB | Native uses 4.5x less JVM memory | -| String concat (10K) | **0 KB** | 131,680 KB | Native: strings stay on native heap | - -**Key takeaways**: -- **Compute-bound workloads** (fibonacci, pi, sorting) run at near-native speed — the FFM boundary is crossed once, then all work happens in Kotlin/Native -- **FFM call overhead** is ~49 ns/call — negligible for methods that do real work, visible only in micro-benchmarks with 100K+ trivial calls -- **Memory advantage**: native allocations don't touch the JVM heap, reducing GC pressure significantly (0 KB vs 131 MB for string-heavy workloads) -- **Thread-safe**: all concurrent benchmarks pass with zero crashes (AtomicReference error state, idempotent dispose) - -## What's NOT supported - -### Not yet implemented - -| Feature | Notes | -|---------|-------| -| Generics (`class Box`) | Complex type erasure at FFM boundary — use concrete types | -| Interface / sealed class as return type | Methods must return the concrete type, not the interface/sealed parent | -| Operator overloading (`operator fun plus`) | Use named methods (`fun add()`) | -| Infix functions | Use regular method syntax | -| Extension functions on stdlib types | Only extensions on project classes are bridged | - -### By design - -| Feature | Reason | Alternative | -|---------|--------|-------------| -| Private/internal/protected members | Only public API is exported | Use `public` modifier | -| Expect/actual declarations | KMP's responsibility | Use platform-specific source sets | -| `ByteArray` in collections | Buffer lifecycle complexity across FFM | Use `List` or Base64 String | -| `ByteArray` as data class field | Out-param buffer not wired for DC fields | Use separate method or String | -| `ByteArray` as callback param | Buffer lifecycle across callback boundary | Use String (Base64) | -| Lambda as callback return type | Callbacks supported as parameters only | Return object with methods | -| CInterop types in public API (`CPointer`, `COpaque`) | Kotlin/Native-only types, not marshallable | Wrap behind a Kotlin API | -| Subclassing from JVM | JVM proxy classes are handles, not real native objects | Subclass on native side | - -### Scope limitations - -The bridge is designed for **Kotlin-level APIs** — clean classes, interfaces, data classes, functions. It is **not** a C FFI wrapper. Projects that expose raw C types in their public API (like GTK bindings with `CPointer`) are not compatible. Wrap them behind a clean Kotlin API first. - - - -## Configuration reference - -```kotlin -kotlinNativeExport { - // Name of the shared library (required) - // Produces: libmylib.so (Linux), libmylib.dylib (macOS), mylib.dll (Windows) - nativeLibName = "mylib" - - // Package for JVM proxies — auto-detected from your Kotlin source package declarations - // Only set this if you have multiple packages and want to override the auto-detection - // nativePackage = "com.example" - - // Build type: "release" (default, ~700KB .so) or "debug" (~6MB .so) - buildType = "release" -} -``` - -### JVM runtime requirements - -The generated FFM proxies require: - -- **JDK 22+** (FFM API finalized in JDK 22 via [JEP 454](https://openjdk.org/jeps/454), recommended JDK 25) -- **`--enable-native-access=ALL-UNNAMED`** JVM arg (auto-configured for tests by the plugin) - -The native library is automatically bundled in the JAR and extracted at runtime — no manual `java.library.path` configuration needed. - -### Zero-config native library loading - -The generated `KneRuntime` uses a three-tier loading strategy: - -1. **`java.library.path`** — for development, packaged apps, or manual override -2. **JAR extraction** — extracts from `kne/native/{os}-{arch}/` in the classpath to a persistent cache (`~/.cache/kne/`) -3. **Loader lookup** — fallback for GraalVM native-image (native lib beside the executable) - -### Build performance & Gradle Cache - -Currently, the bridge generation task is marked as `@DisableCachingByDefault` because the Kotlin PSI source analysis is not yet fully cacheable. - -- **Status**: The plugin performs a full re-scan and code generation on every build if sources change. -- **Future work**: Implement proper Gradle build caching and incremental compilation by mapping source files to specific IR outputs, allowing faster builds for large projects. - -### GraalVM native-image support - -The plugin auto-generates GraalVM reachability metadata under `META-INF/native-image/kne/{libName}/`: - -- `reflect-config.json` — all generated proxy classes -- `resource-config.json` — bundled native library resources -- `reachability-metadata.json` — FFM foreign downcall + upcall descriptors, reflection, and resources - -For GraalVM native-image builds, the native `.so`/`.dylib` must be placed next to the executable (the plugin bundles it in the JAR for JVM, but native-image can't extract at runtime). - -### Using with Compose Desktop / Nucleus - -The Compose compiler plugin and Nucleus Native Access both add Kotlin/Native targets, but Compose doesn't support arbitrary native compilations (e.g. `linuxX64`, `mingwX64` for FFM bridges). **The recommended approach is to put your native code in a separate Gradle module** without the Compose compiler plugin: - -``` -my-app/ -├── native/ ← Kotlin/Native + nucleusnativeaccess (no Compose) -│ └── build.gradle.kts -├── app/ ← Compose Desktop + depends on :native -│ └── build.gradle.kts -└── settings.gradle.kts -``` - -**`:native/build.gradle.kts`** — native bridge module: - -```kotlin -plugins { - kotlin("multiplatform") version "2.3.20" - id("io.github.kdroidfilter.nucleusnativeaccess") -} - -kotlin { - jvmToolchain(25) - linuxX64() // or macosArm64(), mingwX64() - jvm() -} - -kotlinNativeExport { - nativeLibName = "mylib" - // nativePackage auto-detected from source package declarations -} -``` - -**`:app/build.gradle.kts`** — Compose Desktop module: - -```kotlin -plugins { - kotlin("multiplatform") version "2.3.20" - id("org.jetbrains.compose") version "1.10.2" - id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" - id("io.github.kdroidfilter.nucleus") version "1.7.2" -} - -kotlin { - jvmToolchain(25) - jvm() - - sourceSets { - val jvmMain by getting { - dependencies { - implementation(compose.desktop.currentOs) - implementation(project(":native")) - } - } - } -} - -nucleus.application { - mainClass = "com.example.MainKt" - jvmArgs += listOf("--enable-native-access=ALL-UNNAMED") -} -``` - -This avoids any conflict between the Compose compiler and Kotlin/Native targets used for FFM bridges. - -### Using with C interop (e.g. libnotify) - -You can combine the plugin with Kotlin/Native cinterop to wrap native C libraries and expose them to JVM: - -```kotlin -// build.gradle.kts -kotlin { - linuxX64().compilations["main"].cinterops { - val libnotify by creating { - defFile(project.file("src/nativeInterop/cinterop/libnotify.def")) - } - } -} -``` - -```kotlin -// src/nativeMain/kotlin/LinuxDesktop.kt -class LinuxDesktop { - fun sendNotification(title: String, body: String, icon: String): Boolean { - // calls libnotify via cinterop — impossible from JVM without JNI - notify_init("MyApp") - val n = notify_notification_new(title, body, icon) ?: return false - return notify_notification_show(n, null) != 0 - } - - fun getHostname(): String = memScoped { - val buf = allocArray(256) - gethostname(buf, 256u) - buf.toKString() - } -} -``` - -```kotlin -// src/jvmMain/kotlin/Main.kt — transparent usage -val desktop = LinuxDesktop() -desktop.sendNotification("Hello", "From Kotlin/Native via FFM!", "dialog-information") -println(desktop.getHostname()) -desktop.close() -``` +- **[Kotlin/Native bridge →](README-KOTLIN-NATIVE.md)** — wrap `nativeMain` Kotlin code +- **[Rust bridge →](README-RUST.md)** — import any Rust crate ## Examples -The repository includes two complete examples in [`examples/`](examples/): - -| Example | Description | -|---------|-------------| -| [`calculator/`](examples/calculator/) | Stateful Calculator class with 1700+ end-to-end tests: all types, callbacks, collections, suspend, Flow, nested classes, inheritance hierarchies, interfaces, sealed classes, extension functions, concurrency | -| [`systeminfo/`](examples/systeminfo/) | Linux system info (`/proc`, POSIX, `gethostname`) + native notifications via `libnotify` cinterop, with Compose Desktop UI | -| [`benchmark/`](examples/benchmark/) | Performance benchmarks: native vs JVM (fibonacci, pi, sort, string, allocation, concurrent) | - -Run them: +| Example | Bridge | Description | +|---------|--------|-------------| +| [`calculator/`](examples/calculator/) | Kotlin/Native | Calculator with 1700+ end-to-end tests: all types, callbacks, collections, suspend, Flow, nested classes, inheritance, interfaces, sealed classes, extensions, concurrency | +| [`systeminfo/`](examples/systeminfo/) | Kotlin/Native | Linux system info (`/proc`, POSIX, `gethostname`) + native notifications via `libnotify` cinterop, with Compose Desktop UI | +| [`benchmark/`](examples/benchmark/) | Kotlin/Native | Performance benchmarks: native vs JVM | +| [`rust-calculator/`](examples/rust-calculator/) | Rust | Calculator with Compose Desktop UI — same API as the Kotlin/Native calculator | +| [`rust-benchmark/`](examples/rust-benchmark/) | Rust | Performance benchmarks: Rust vs JVM | +| [`rust-tray-icon/`](examples/rust-tray-icon/) | Rust (local wrapper) | macOS/Windows/Linux system tray icon via `tray-icon 0.19` — demonstrates `dispatch_sync` main-thread bridging and event polling | ```bash -./gradlew :examples:calculator:run -./gradlew :examples:systeminfo:run +# Kotlin/Native examples ./gradlew :examples:calculator:jvmTest # 1700+ end-to-end FFM tests -./gradlew :examples:benchmark:jvmTest # Performance benchmarks (native vs JVM) +./gradlew :examples:benchmark:jvmTest # KN performance benchmarks + +# Rust examples +./gradlew :examples:rust-calculator:run # Compose Desktop UI powered by Rust +./gradlew :examples:rust-benchmark:jvmTest # Rust performance benchmarks +./gradlew :examples:rust-tray-icon:run # System tray icon via Rust ``` ## Architecture -The plugin is inspired by two projects: - -- **[swift-export-standalone](https://github.com/JetBrains/kotlin/tree/master/native/swift/swift-export-standalone)** (JetBrains) — how Kotlin exports its API to Swift via C bridges. We adapted the approach: scan Kotlin sources, generate `@CName` bridge functions with `StableRef` for object lifecycle. - -- **[swift-java](https://github.com/swiftlang/swift-java)** (Apple) — how Swift code is made callable from Java via FFM `MethodHandle` downcalls. We adapted the FFM binding generation: each method gets a `FunctionDescriptor` + `MethodHandle`, classes use `Cleaner` for GC safety. - ``` plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/ -├── ir/ # Intermediate representation (inspired by SirModule) -│ └── KneIR.kt # KneModule, KneClass, KneFunction, KneType... +├── ir/ +│ └── KneIR.kt # Shared IR: KneModule, KneClass, KneFunction, KneType... ├── analysis/ -│ ├── PsiSourceParser.kt # Kotlin PSI-based source parser (kotlin-compiler-embeddable) -│ └── PsiParseWorkAction.kt # Gradle Worker for isolated PSI classloader +│ ├── PsiSourceParser.kt # Kotlin PSI-based parser (Kotlin/Native path) +│ ├── PsiParseWorkAction.kt # Gradle Worker for isolated PSI classloader +│ ├── RustdocJsonParser.kt # Rustdoc JSON parser (Rust path) +│ └── RustWorkAction.kt # Rust pipeline orchestration ├── codegen/ -│ ├── NativeBridgeGenerator.kt # @CName + StableRef bridges (inspired by @_cdecl thunks) -│ └── FfmProxyGenerator.kt # JVM proxy classes with FFM (inspired by FFMSwift2JavaGenerator) +│ ├── NativeBridgeGenerator.kt # @CName + StableRef bridges (Kotlin/Native) +│ ├── RustBridgeGenerator.kt # #[no_mangle] extern "C" fn bridges (Rust) +│ └── FfmProxyGenerator.kt # JVM proxy classes with FFM (shared, language-agnostic) ├── tasks/ -│ └── GenerateNativeBridgesTask.kt # Single task: PSI parse + native bridges + JVM proxies + GraalVM metadata -├── KotlinNativeExportExtension.kt -└── KotlinNativeExportPlugin.kt # Task wiring, native lib JAR bundling, test configuration +│ ├── GenerateNativeBridgesTask.kt # Kotlin/Native bridge generation task +│ ├── GenerateRustBindingsTask.kt # Rust bridge + proxy generation task +│ └── CargoBuildTask.kt # Invokes cargo build +├── KotlinNativeExportExtension.kt # kotlinNativeExport { } DSL +├── RustImportExtension.kt # rustImport { } DSL +└── KotlinNativeExportPlugin.kt # Plugin entry: KMP + Rust wiring ``` -**Source analysis**: the plugin uses Kotlin PSI (`kotlin-compiler-embeddable`) for proper AST-based parsing, running in an isolated Gradle Worker classloader. This handles nested generics, function types, default parameters, multi-line constructors, and `suspend`/`Flow` detection natively — no regex. - -### Design philosophy - -This plugin bridges **Kotlin/Native code to the JVM transparently** — platform-specific APIs (cinterop, POSIX, C libraries), performance-critical native code, and types that cannot exist in common Kotlin. - -The bridge supports the full Kotlin OOP model: inheritance hierarchies are mirrored on the JVM side (`open class Shape` → `class Circle : Shape`), interfaces generate JVM interfaces with `val handle: Long`, and `override` modifiers are preserved. Extension functions become real Kotlin extensions on the JVM proxy. Sealed classes generate sealed JVM classes with subclass dispatch. - -Data classes are supported as value types (field marshalling), and `commonMain` data classes are reused directly without generating duplicates. +The IR layer (`KneIR.kt`) is the key abstraction: both the PSI parser (Kotlin/Native) and the rustdoc JSON parser (Rust) emit the same IR, which is then consumed by `FfmProxyGenerator` to produce identical JVM proxy code regardless of the source language. ## Requirements - **Kotlin** 2.3.20+ - **Gradle** 9.1+ (for JDK 25 support) -- **JDK** 22+ (FFM stable since JDK 22 / JEP 454), recommended 25 -- **Kotlin/Native** toolchain (bundled with KMP plugin) +- **JDK** 22+ (FFM stable since JDK 22 / [JEP 454](https://openjdk.org/jeps/454)), recommended 25 +- **Kotlin/Native** toolchain (bundled with KMP plugin) — for `kotlinNativeExport` +- **Rust** toolchain — `cargo` must be on PATH or in `~/.cargo/bin/` — for `rustImport` ## License diff --git a/RUST_BRIDGE_PLAN.md b/RUST_BRIDGE_PLAN.md new file mode 100644 index 00000000..d3a762a6 --- /dev/null +++ b/RUST_BRIDGE_PLAN.md @@ -0,0 +1,173 @@ +# Rust Bridge — Analysis & Improvement Plan + +## Architecture overview + +Three-layer pipeline: **Rust crate → C ABI → Java FFM → Kotlin proxy** + +``` +Rust crate + └─ cargo rustdoc --output-format json + └─ RustdocJsonParser → KneModule (IR) + ├─ RustBridgeGenerator → kne_bridges.rs (#[no_mangle] extern "C") + └─ FfmProxyGenerator → *.kt (MethodHandle / Java FFM) +``` + +Object lifecycle: `Box::into_raw` / `Box::from_raw` (Rust side), `Cleaner` (JVM side). +Error propagation: thread-local `KNE_LAST_ERROR`, `catch_unwind` on every call. +Generic monomorphisation: auto-detected from `impl Trait for Type` blocks in rustdoc JSON. + +--- + +## Strengths + +- Zero-copy for all primitive types across the FFI boundary +- `Option` → `T?` with sentinel encoding (no heap allocation) +- Automatic generic monomorphisation via turbofish (`Processor::::new(...)`) +- `dyn Trait` supported via fat-pointer registry (`[usize; 2]` stored in thread-local `HashMap`) +- `catch_unwind` on all bridge functions — panics become `KotlinNativeException` on JVM +- GraalVM native-image metadata generated automatically (downcalls + upcalls + reflection) +- `Cleaner` as fallback GC if user forgets `close()` + +--- + +## Issues & Improvement Plan + +### P0 — Correctness (data loss in production) + +#### [ ] 1. Two-pass string I/O +**Problem:** String output buffer is hardcoded at 8192 bytes. Strings longer than 8KB are silently +truncated — no exception, no warning, corrupt data returned. + +**Fix:** Implement two-pass pattern on the Rust side: +- First call with `null` / size `0` → returns required byte count +- JVM allocates exact size, second call fills the buffer +- Fallback: if first call returns `-1` (unknown size), use 8192 and retry on overflow + +Affects: all `String` return values and `String` parameters in generated bridges. + +#### [ ] 2. Collection size limit +**Problem:** `MAX_COLLECTION_SIZE = 4096` — `List`, `Set`, `Map` larger than 4096 +elements are silently truncated. + +**Fix:** Same two-pass approach — first call returns count, second call fills buffer. +Or at minimum: throw an exception when the limit is hit instead of silent truncation. + +--- + +### P1 — Stability (breaks on toolchain updates) + +#### [ ] 3. Dependency on `RUSTC_BOOTSTRAP=1` + unstable rustdoc JSON +**Problem:** +```kotlin +environment()["RUSTC_BOOTSTRAP"] = "1" +environment()["RUSTDOCFLAGS"] = "-Z unstable-options --output-format json" +``` +The rustdoc JSON format is unstable and has changed multiple times. Any Rust toolchain update +can silently break `RustdocJsonParser`. + +**Fix:** +- Track stabilisation of `--output-format json` (rust-lang/rust#76578) +- Pin the minimum supported rustdoc JSON format version in `RustdocJsonParser` +- Add a format version check at parse time with a clear error message on mismatch + +#### [ ] 4. Hand-rolled TOML parser in `resolveRustdocTargetName` +**Problem:** `Cargo.toml` is parsed line-by-line with string matching. Breaks on inline tables, +multiline strings, `[package.metadata]` sections, etc. + +**Fix:** Replace with `cargo metadata --format-version 1` (already called elsewhere in the +pipeline) — the `name` and `targets` fields give the exact lib target name reliably. + +--- + +### P2 — Robustness + +#### [ ] 5. `ensureLibRsInclude` mutates user source code +**Problem:** The plugin silently rewrites the user's `lib.rs` to inject the bridge `include!`. +This can corrupt files with `#![...]` crate attributes, conditional compilation blocks, etc. + +**Fix:** Inject the include only via `build.rs` (already generated by the plugin) so user source +is never touched. For local-path crates, emit a clear build error if the include is missing +rather than auto-patching. + +#### [ ] 6. Silent type collision in `mergeModules` +**Problem:** When merging rustdoc JSON from multiple crates, deduplication is by `simpleName`. +If two crates both export a type named `Error` or `Config`, the first one wins silently. + +**Fix:** Detect FQ name conflicts, log a warning with both FQ names, and prefer the richer +representation (sealed enum > data class > opaque class). + +#### [ ] 7. Thread safety of trait object registry +**Problem:** `KNE_TRAIT_REGISTRY` uses `thread_local! { RefCell> }`. +If trait object handles are passed to another thread before disposal (valid JVM pattern), +the registry lookup finds nothing → memory leak or invalid handle. + +**Fix:** Use a global `Mutex>` instead of thread-local `RefCell`. +The lock contention is negligible since trait objects are rare. + +#### [ ] 8. `transmute` on fat pointers +**Problem:** +```rust +let fat_ptr: *mut dyn std::any::Any = std::mem::transmute(words); +``` +Relies on fat pointer layout being `[usize; 2]` — true on all current targets but technically +undefined behaviour. + +**Fix:** Once `std::ptr::from_raw_parts` / `std::ptr::metadata` stabilise for trait objects +(tracking: rust-lang/rust#81513), replace the transmute. For now: acceptable risk, +document the assumption explicitly. + +--- + +### P3 — Maintainability + +#### [ ] 9. Refactor RustBridgeGenerator +**Problem:** `RustBridgeGenerator.kt` is a single file (~2000 lines) with deeply nested +`StringBuilder` extension functions. Adding new type support requires touching many +unrelated code paths. + +**Proposed split:** +``` +RustBridgeGenerator (orchestration only) +├── RustErrorInfraEmitter (thread-local error infra) +├── RustClassBridgeEmitter (constructor / dispose / methods / properties) +├── RustEnumBridgeEmitter (simple enums + sealed enums) +├── RustTupleEmitter (tuple heap allocation / free) +└── RustTraitObjectEmitter (dyn Trait registry + store/load/drop) +``` +Each emitter takes the relevant IR node(s) and returns a `String`. + +#### [ ] 10. Async / Future support +**Problem:** `isSuspend` and `KneType.FLOW` are present in the IR but the Rust bridge only has +a stub `appendSuspendHelpers`. Async Rust crates (`tokio`, `reqwest`, etc.) are completely +unsupported. + +**Fix (phased):** +- Phase 1: Generate a singleton `tokio::Runtime` in the bridge preamble when `hasSuspend` +- Phase 2: Map `async fn` → `Box::pin(async { ... })` with JVM callback on completion +- Phase 3: Map `impl Stream` → `KneType.FLOW` with channel-based delivery + +#### [ ] 11. Closure / callback support +**Problem:** `KneType.FUNCTION` exists in the IR but Rust-side upcall generation is incomplete. +Crates that take `Fn(T) -> R` callbacks cannot be bridged automatically. + +**Fix:** Use Java FFM upcalls (`Linker.nativeLinker().upcallStub(...)`) — the JVM side already +has the plumbing. The Rust bridge needs to accept a `*const c_void` function pointer and +cast it to the appropriate `extern "C" fn` type. + +--- + +## Priority summary + +| # | Issue | Impact | Effort | +|---|-------|--------|--------| +| 1 | Two-pass string I/O | Data loss in prod | Medium | +| 2 | Collection size limit | Data loss in prod | Medium | +| 3 | `RUSTC_BOOTSTRAP` / unstable JSON | Toolchain breakage | Low (monitoring) | +| 4 | Hand-rolled TOML parser | Build failures | Low | +| 5 | `ensureLibRsInclude` mutates user code | UX / correctness | Low | +| 6 | Silent type collisions in merge | Wrong types silently | Low | +| 7 | Thread safety of trait registry | Memory safety | Low | +| 8 | `transmute` on fat pointers | Theoretical UB | Low (document) | +| 9 | Refactor generator | Maintainability | High | +| 10 | Async / Future support | Feature coverage | High | +| 11 | Closure / callback support | Feature coverage | High | diff --git a/RUST_IMPORT_SUPPORT.md b/RUST_IMPORT_SUPPORT.md new file mode 100644 index 00000000..7065cce5 --- /dev/null +++ b/RUST_IMPORT_SUPPORT.md @@ -0,0 +1,123 @@ +# Rust `crate()` Import — Support Matrix + +Status of the `rustImport { crate(...) }` feature that auto-generates Kotlin bindings from Rust crates via rustdoc JSON. + +## Type Mapping + +| Rust Type | Kotlin Type | Status | +|-----------|-------------|--------| +| `i32` | `Int` | Supported | +| `i64` | `Long` | Supported | +| `f64` | `Double` | Supported | +| `f32` | `Float` | Supported | +| `bool` | `Boolean` | Supported | +| `i8` / `u8` | `Byte` | Supported | +| `i16` / `u16` | `Short` | Supported | +| `String` / `&str` | `String` | Supported | +| `()` | `Unit` | Supported | +| `Vec` / `&[u8]` | `ByteArray` | Supported | +| `Option` | `T?` | Supported | +| `Vec` | `List` | Supported | +| `HashSet` | `Set` | Supported | +| `HashMap` | `Map` | Supported | +| `(A, B)` tuples | `KneTuple2` | Supported | +| `fn(A) -> B` callbacks | `(A) -> B` | Supported | +| Structs (opaque) | Classes | Supported | +| Enums (C-like) | `enum class` | Supported | +| Enums (with data) | `sealed class` | Supported | +| Traits (as interfaces) | `interface` | Supported | +| `dyn Trait` returns | `DynXxx` wrapper class | Supported | +| `impl Trait` returns | `DynXxx` wrapper class | Supported (known crate traits) | +| `Box` params | Supported | Registry-based handle passing | +| Generics (`Struct`) | Monomorphized variants | Supported | +| `async fn` | `suspend fun` | Supported | +| Streams / iterators | `Flow` | Supported | + +## Struct / Class Features + +| Feature | Status | Notes | +|---------|--------|-------| +| Public methods (`&self`, `&mut self`) | Supported | Auto-bridged | +| Constructors (`fn new(...)`) | Supported | Mapped to `operator fun invoke(...)` | +| Companion / static methods | Supported | Methods without `self` param | +| Properties (getter-only fields) | Supported | Generated as `fun field_name(): T` | +| Mutable properties | Supported | Getter + setter | +| Builder pattern (returns `Self`) | Supported | Chaining works | +| `impl Trait for Struct` | Supported | Mapped to Kotlin interface impl | +| Multiple trait impls | Supported | Deduplicated | +| Lifetime params (`'a`) | Supported | Erased at bridge level | +| Generic structs | Supported | Expanded into concrete variants | +| `Drop` / resource cleanup | Supported | `AutoCloseable` + `Cleaner` | +| Borrowed returns (`&T`) | Supported | Non-owning handle wrapper | + +## Enum Features + +| Feature | Status | Notes | +|---------|--------|-------| +| C-like enums (no data) | Supported | `enum class` with `entries` | +| Enums with data variants | Supported | `sealed class` with subclasses | +| Variant constructors | Supported | `EnumName.variantName(...)` | +| Variant field access | Supported | Properties on variant subclass | +| Tag inspection | Supported | `.tag` property | + +## Trait / Interface Features + +| Feature | Status | Notes | +|---------------------------|--------|-------| +| Trait → Kotlin interface | Supported | Methods as abstract declarations | +| `dyn Trait` return types | Supported | `DynXxx` wrapper class with `fromNativeHandle` | +| `dyn Trait` as param | Supported | DynWrapper type used in signatures, registry-based handle passing | +| Trait with associated types | Not supported | Skipped | +| Super-traits | Partial | Interface inheritance not chained | + +## Callback / Function Pointer Features + +| Feature | Status | Notes | +|---------|--------|-------| +| `fn(primitives) -> primitive` | Supported | Upcall stub generated | +| `fn(Object) -> ()` | Supported | Object handle passed | +| `fn() -> Object` | Supported | Handle returned | +| `fn() -> dyn Trait` | Supported | Handle returned via upcall stub | +| `fn() -> SealedEnum` | Supported | Handle returned via upcall stub | +| Callbacks in methods | Supported | Stub allocated from object arena | +| Callbacks in constructors | Supported | Stub allocated from confined arena | +| Callbacks in sealed enum factories | Supported | Stub allocated from confined arena | + +## Multi-Crate Support + +| Feature | Status | Notes | +|---------|--------|-------| +| Multiple `crate()` declarations | Supported | Merged into single module | +| Cross-crate type references | Supported | Lazy resolution from rustdoc | +| Cross-crate trait impls | Supported | Deduplicated | +| Type name collisions | Handled | Ambiguous names detected and skipped | +| Cross-crate enum type mismatch | Handled | OBJECT→ENUM redirect at codegen | + +## Known Limitations + +### Not Yet Bridgeable + +1. **Top-level functions** — Free functions like `fn default_host()` or `fn get_probe()` are bridged when they have supported param/return types. Functions returning `impl Trait` (where Trait is a known crate trait) are bridged as `DynXxx` wrapper objects. Functions returning `impl Trait` for unknown/external traits are skipped. + +3. **Generic types without concrete instantiation** — If a generic struct `Foo` has no trait impl that binds `T` to a concrete type, it stays unresolved and is skipped. + +### Automatically Handled + +- **Duplicate trait impls** — When a type implements the same trait multiple times (e.g., `AudioBuffer` for different `T`), supertypes and methods are deduplicated. +- **Override validation** — Methods marked `override` are validated against the actual interface/superclass hierarchy. Invalid overrides are silently downgraded to regular methods. +- **Type mismatches** — When a type is resolved as `OBJECT` in one crate but exists as `ENUM` in the module, the codegen redirects to the correct pattern (ordinal-based). +- **Missing method implementations** — If a class can't implement all methods of a trait interface (due to unsupported types in signatures), the interface is dropped from the supertype list rather than generating a compile error. +- **Unknown types in signatures** — Methods, constructors, and interface declarations referencing types not present in the module are filtered out at codegen time. + +## Symphonia + cpal Example Stats + +Generated from `symphonia 0.5.5` + `cpal 0.15` with zero hand-written Rust: + +| Metric | Count | +|--------|-------| +| Kotlin proxy files | 187 | +| Classes | 129 | +| Interfaces (from traits) | 24 | +| Simple enums | 12 | +| Sealed enums | 19 | +| Compilation errors | 0 | diff --git a/SEALED_ENUM_COLLECTION_FIELDS.md b/SEALED_ENUM_COLLECTION_FIELDS.md new file mode 100644 index 00000000..fc1baa19 --- /dev/null +++ b/SEALED_ENUM_COLLECTION_FIELDS.md @@ -0,0 +1,81 @@ +# Sealed Enum Variants: Collection Field Support + +## Current Status + +Sealed enum variants with collection fields (LIST, SET, MAP) are now **fully supported** for **primitive element types**. + +``` + Primitive elements String/Object elements + ────────────────── ────────────────────── + LIST constructor SUPPORTED SKIPPED + SET constructor SUPPORTED SKIPPED + MAP constructor SUPPORTED SKIPPED + Field getters SUPPORTED SUPPORTED +``` + +## What Works + +### Factory Constructors (JVM → Rust) + +Constructors are generated for variants with LIST, SET, or MAP fields when all element types are primitives (`i32`, `i64`, `f64`, `f32`, `i16`, `i8`, `bool`). + +```kotlin +// LIST variant +DataPayload.scores(listOf(1, 2, 3)) + +// SET variant +DataPayload.uniqueIds(setOf(10, 20, 30)) + +// MAP variant +DataPayload.mapping(mapOf(1 to 10, 2 to 20)) +``` + +### Field Getters (Rust → JVM) + +All collection field types are readable, including String/Object elements: + +```kotlin +val scores: List = (payload as DataPayload.Scores).value +val ids: Set = (payload as DataPayload.UniqueIds).value +val data: Map = (payload as DataPayload.Mapping).value +``` + +## What Doesn't Work + +### String/Object elements in constructors + +Constructors are **skipped** when a collection field has String or Object element types because the C ABI slice protocol (`ptr + len`) can't marshal variable-length or handle-based data. + +```rust +// Constructor SKIPPED — String elements not supported +Tags(HashSet) + +// Constructor SKIPPED — Object elements not supported +ProcessesToUpdate::Some(&[Pid]) +``` + +**Root cause**: `slicePointerType` maps String collections to `*const u8` (wrong — each string needs individual marshalling). Object types would need handle arrays. + +### Potential fix + +Supporting String elements would require a different protocol: +1. Pass an array of null-terminated C string pointers (`*const *const c_char`) +2. Or pass a single concatenated buffer with length-prefix encoding + +This is the same fundamental limitation as `&[String]` parameters on regular functions. + +## Implementation Details + +### RustBridgeGenerator Changes + +1. **Skip guard** (`isSupportedCollectionElementForConstructor`): Only allows primitives in collection constructor fields +2. **SET in `slicePointerType`**: Added SET case matching LIST element type → pointer type mapping +3. **SET in `appendParamConversion`**: Always creates owned `HashSet` via `.iter().cloned().collect()` +4. **SET in `convertedParamName`**: Always returns `_set` (no `expectsOwnedVecLike` check) +5. **MAP in `appendParamConversion`**: Added `mut` keyword and `as usize` range conversion +6. **MAP in `convertedParamName`**: Added `_map` case + +### FfmProxyGenerator Changes + +1. **FunctionDescriptor**: MAP expands to `ADDRESS, ADDRESS, JAVA_INT` (keys, values, size) +2. **Invoke args**: Uses `buildExpandedInvokeArgs` instead of `buildJvmInvokeArg` — correctly expands LIST to `[Seg, size]`, SET to `[Seg, size]`, MAP to `[keysSeg, valuesSeg, size]` diff --git a/SYMPHONIA_REMAINING_ERRORS.md b/SYMPHONIA_REMAINING_ERRORS.md new file mode 100644 index 00000000..b00c7038 --- /dev/null +++ b/SYMPHONIA_REMAINING_ERRORS.md @@ -0,0 +1,77 @@ +# Symphonia Bridge — 4 Remaining Rust Compilation Errors + +## Summary + +The `rust-symphonia` example imports both `symphonia` (audio decoding) and `cpal` (audio output) as Rust crates via NNA's `crate()` DSL. The bridge generator successfully processes ~700 bridge functions with 0 warnings, but 4 methods on the `Fft` class fail to compile. + +All 4 errors have the **same root cause**: methods that take `&[Complex]` or `&mut [Complex]` slice parameters, where `Complex` is a Rust struct (`Complex { re: f32, im: f32 }`). + +## The 4 Failing Methods + +| Method | Rust Signature | Error | +|--------|---------------|-------| +| `Fft::ifft` | `fn ifft(&self, x: &[Complex], y: &mut [Complex])` | `expected &[Complex], found &[i64]` | +| `Fft::ifft_inplace` | `fn ifft_inplace(&self, x: &mut [Complex])` | `expected &mut [Complex], found &[i64]` | +| `Fft::fft_inplace` | `fn fft_inplace(&self, x: &mut [Complex])` | `expected &mut [Complex], found &[i64]` | +| `Fft::fft` | `fn fft(&self, x: &[Complex], y: &mut [Complex])` | `expected &[Complex], found &[i64]` | + +## Root Cause + +The bridge generator supports `List` as a **return type** (by writing i64 handles into an output buffer). But when `List` appears as a **parameter**, the bridge passes the slice as `&[i64]` (array of opaque handles), while Rust expects `&[Complex]` (array of actual struct values). + +The fundamental mismatch: +- **Return** `&[Complex]`: the bridge iterates the Rust slice, converts each `Complex` to a boxed handle (`Box::into_raw`), and writes the handle into an output i64 buffer. The JVM side reads handles and wraps them in proxy objects. This works. +- **Parameter** `&[Complex]`: the JVM sends an array of i64 handles. The bridge creates a `&[i64]` slice from the pointer. But Rust expects `&[Complex]` — an array of actual 8-byte structs (`re: f32, im: f32`), not an array of 8-byte pointers to heap-allocated boxes. + +## Why This Can't Be Fixed With the Current Approach + +For `&[Complex]` parameters, the bridge would need to: +1. Receive an array of i64 handles from the JVM +2. Dereference each handle to get the `Complex` struct (`*Box::from_raw(h as *mut Complex)`) +3. Collect them into a `Vec` +4. Pass `&vec` as the slice + +This is feasible but has ownership issues: +- Step 2 **consumes** the Box (takes ownership), so the JVM proxy object becomes invalid after the call +- For `&mut [Complex]` params, changes to the slice elements must be propagated back to the JVM side + +A proper implementation would require: +- A new bridge pattern: "object slice parameter" that borrows from handles without consuming them (`&**Box::from_raw(...)` pattern or `ManuallyDrop`) +- For `&mut [Complex]` params, a writeback mechanism to update the handles after the call + +## Affected Types + +Only `symphonia::core::dsp::complex::Complex` triggers this in the symphonia example. The `Complex` struct is: +```rust +pub struct Complex { + pub re: f32, + pub im: f32, +} +``` + +Other `List` parameters in symphonia (like `List`, `List`) are in **constructors** that have already been excluded by the `hasUnbridgeableParam` filter because their containing structs have other unbridgeable fields. + +## Workaround + +The 4 methods (`fft`, `ifft`, `fft_inplace`, `ifft_inplace`) are DSP-internal methods on the `Fft` struct. They are not part of the typical Symphonia audio decoding workflow (probe → format reader → decoder → sample buffer). The main Symphonia API for audio processing works correctly. + +## Fix Path + +To properly support `&[OBJECT]` parameters, `RustBridgeGenerator.kt` needs: + +1. In `appendParamConversion` for `KneType.LIST` with `OBJECT` element type: + ```rust + // Current (broken): passes &[i64] directly + let x_slice = unsafe { std::slice::from_raw_parts(x_ptr, x_len as usize) }; + + // Fix: dereference handles into a temporary Vec + let x_handles = unsafe { std::slice::from_raw_parts(x_ptr, x_len as usize) }; + let x_vec: Vec = x_handles.iter() + .map(|&h| unsafe { std::ptr::read(h as *const Complex) }) + .collect(); + let x_slice = &x_vec; + ``` + +2. For `&mut [OBJECT]` params, after the method call, write back modified values to the handles. + +3. The `FfmProxyGenerator.kt` already handles `List` params correctly on the JVM side (passing i64 handles). diff --git a/examples/rust-benchmark/build.gradle.kts b/examples/rust-benchmark/build.gradle.kts new file mode 100644 index 00000000..ac062e96 --- /dev/null +++ b/examples/rust-benchmark/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + kotlin("multiplatform") + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +tasks.withType { + testLogging { + showStandardStreams = true + } +} + +rustImport { + libraryName = "rustbench" + jvmPackage = "com.example.rustbenchmark" + buildType = "release" + cratePath("benchmark", "${projectDir}/rust") +} diff --git a/examples/rust-benchmark/rust/Cargo.toml b/examples/rust-benchmark/rust/Cargo.toml new file mode 100644 index 00000000..ab17757b --- /dev/null +++ b/examples/rust-benchmark/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "benchmark" +version = "0.1.0" +edition = "2021" + +[lib] +name = "rustbench" +crate-type = ["cdylib"] diff --git a/examples/rust-benchmark/rust/src/lib.rs b/examples/rust-benchmark/rust/src/lib.rs new file mode 100644 index 00000000..fde51c4b --- /dev/null +++ b/examples/rust-benchmark/rust/src/lib.rs @@ -0,0 +1,206 @@ +// Rust benchmark — mirrors the Kotlin/Native benchmark example. +// Same algorithms, same parameters, for fair Rust vs KN vs JVM comparison. + +// ── Fibonacci ─────────────────────────────────────────────────────────────── + +pub struct FibCalculator { + _dummy: i32, +} + +impl FibCalculator { + pub fn new() -> Self { + FibCalculator { _dummy: 0 } + } + + pub fn fib_recursive(&self, n: i32) -> i64 { + if n <= 1 { return n as i64; } + self.fib_recursive(n - 1) + self.fib_recursive(n - 2) + } + + pub fn fib_iterative(&self, n: i32) -> i64 { + if n <= 1 { return n as i64; } + let mut a: i64 = 0; + let mut b: i64 = 1; + for _ in 0..(n - 1) { + let tmp = a + b; + a = b; + b = tmp; + } + b + } +} + +// ── Pi calculation (Leibniz series) ───────────────────────────────────────── + +pub struct PiCalculator { + _dummy: i32, +} + +impl PiCalculator { + pub fn new() -> Self { + PiCalculator { _dummy: 0 } + } + + pub fn leibniz(&self, iterations: i32) -> f64 { + let mut sum: f64 = 0.0; + for i in 0..iterations { + sum += (if i % 2 == 0 { 1.0 } else { -1.0 }) / (2 * i + 1) as f64; + } + sum * 4.0 + } + + pub fn nilakantha(&self, iterations: i32) -> f64 { + let mut pi: f64 = 3.0; + let mut sign: f64 = 1.0; + for i in 1..=iterations { + let n = (2.0 * i as f64) * (2.0 * i as f64 + 1.0) * (2.0 * i as f64 + 2.0); + pi += sign * 4.0 / n; + sign = -sign; + } + pi + } + + pub fn monte_carlo_pi(&self, samples: i32, seed: i64) -> f64 { + let mut inside: i32 = 0; + let mut state: i64 = seed; + for _ in 0..samples { + state = state.wrapping_mul(6364136223846793005i64).wrapping_add(1442695040888963407i64); + let x = ((state as u64) >> 33) as f64 / i32::MAX as f64; + state = state.wrapping_mul(6364136223846793005i64).wrapping_add(1442695040888963407i64); + let y = ((state as u64) >> 33) as f64 / i32::MAX as f64; + if x * x + y * y <= 1.0 { inside += 1; } + } + 4.0 * inside as f64 / samples as f64 + } +} + +// ── Array/collection processing ───────────────────────────────────────────── + +pub struct ArrayProcessor { + _dummy: i32, +} + +impl ArrayProcessor { + pub fn new() -> Self { + ArrayProcessor { _dummy: 0 } + } + + pub fn sum_array(&self, size: i32) -> i64 { + let mut sum: i64 = 0; + for i in 0..size { + sum += i as i64; + } + sum + } + + pub fn bubble_sort_size(&self, size: i32) -> i32 { + let size = size as usize; + let mut arr: Vec = (0..size).map(|i| (size - i) as i32).collect(); + for i in 0..size { + for j in 0..(size - i - 1) { + if arr[j] > arr[j + 1] { + arr.swap(j, j + 1); + } + } + } + arr[0] + } +} + +// ── String processing ─────────────────────────────────────────────────────── + +pub struct StringProcessor { + _dummy: i32, +} + +impl StringProcessor { + pub fn new() -> Self { + StringProcessor { _dummy: 0 } + } + + pub fn concat_loop(&self, iterations: i32) -> i32 { + let mut s = String::new(); + for i in 0..iterations { + s.push_str(&i.to_string()); + } + s.len() as i32 + } + + pub fn reverse_string(&self, s: String) -> String { + s.chars().rev().collect() + } + + pub fn count_chars(&self, s: String, c: String) -> i32 { + let ch = c.chars().next().unwrap_or('\0'); + s.chars().filter(|&x| x == ch).count() as i32 + } +} + +// ── Object allocation stress ──────────────────────────────────────────────── + +pub struct Vec2 { + pub x: f64, + pub y: f64, +} + +pub struct AllocationBench { + _dummy: i32, +} + +impl AllocationBench { + pub fn new() -> Self { + AllocationBench { _dummy: 0 } + } + + pub fn allocate_points(&self, count: i32) -> f64 { + let mut sum_x: f64 = 0.0; + for i in 0..count { + let p = Vec2 { x: i as f64, y: i as f64 * 2.0 }; + sum_x += p.x; + } + sum_x + } + + pub fn get_vec(&self) -> Vec2 { + Vec2 { x: 1.0, y: 2.0 } + } + + pub fn sum_vec(&self, v: &Vec2) -> f64 { + v.x + v.y + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fib() { + let calc = FibCalculator::new(); + assert_eq!(calc.fib_recursive(10), 55); + assert_eq!(calc.fib_iterative(10), 55); + } + + #[test] + fn test_pi() { + let calc = PiCalculator::new(); + let pi = calc.leibniz(1_000_000); + assert!((pi - std::f64::consts::PI).abs() < 0.001); + } + + #[test] + fn test_array() { + let proc = ArrayProcessor::new(); + assert_eq!(proc.sum_array(100), 4950); + assert_eq!(proc.bubble_sort_size(10), 1); + } + + #[test] + fn test_string() { + let proc = StringProcessor::new(); + assert_eq!(proc.reverse_string("hello".to_string()), "olleh"); + assert_eq!(proc.count_chars("hello world".to_string(), "l".to_string()), 3); + } +} + +include!(concat!(env!("OUT_DIR"), "/kne_bridges.rs")); diff --git a/examples/rust-benchmark/src/jvmTest/kotlin/com/example/rustbenchmark/BenchmarkTest.kt b/examples/rust-benchmark/src/jvmTest/kotlin/com/example/rustbenchmark/BenchmarkTest.kt new file mode 100644 index 00000000..647e0c39 --- /dev/null +++ b/examples/rust-benchmark/src/jvmTest/kotlin/com/example/rustbenchmark/BenchmarkTest.kt @@ -0,0 +1,262 @@ +package com.example.rustbenchmark + +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Benchmarks comparing Rust FFM native calls vs pure JVM implementations. + * + * Mirrors the Kotlin/Native benchmark for direct comparison: + * Rust (via FFM) vs Kotlin/Native (via FFM) vs pure JVM + * + * Methodology: + * - 3 warmup iterations discarded, then 5 measured iterations averaged + * - Both native (Rust via FFM) and pure JVM (equivalent Kotlin code) are measured + * - Ratio = native_time / jvm_time (>1 means native is slower due to FFM overhead) + */ +class BenchmarkTest { + + companion object { + private const val WARMUP = 3 + private const val ITERATIONS = 5 + } + + private inline fun bench(warmup: Int = WARMUP, iters: Int = ITERATIONS, block: () -> Unit): Double { + repeat(warmup) { block() } + val times = (1..iters).map { + val start = System.nanoTime() + block() + (System.nanoTime() - start) / 1_000_000.0 + } + return times.average() + } + + private fun memUsedKB(block: () -> Unit): Long { + val rt = Runtime.getRuntime() + rt.gc(); Thread.sleep(50) + val before = rt.totalMemory() - rt.freeMemory() + block() + val after = rt.totalMemory() - rt.freeMemory() + return (after - before) / 1024 + } + + private fun report(name: String, nativeMs: Double, jvmMs: Double) { + val ratio = nativeMs / jvmMs + val label = if (ratio > 1) "Rust slower" else "Rust faster" + println(" %-40s rust=%7.2f ms jvm=%7.2f ms ratio=%.2fx (%s)".format(name, nativeMs, jvmMs, ratio, label)) + } + + // ── Pure JVM equivalents ──────────────────────────────────────────────── + + private fun jvmFibRecursive(n: Int): Long { + if (n <= 1) return n.toLong() + return jvmFibRecursive(n - 1) + jvmFibRecursive(n - 2) + } + + private fun jvmFibIterative(n: Int): Long { + if (n <= 1) return n.toLong() + var a = 0L; var b = 1L + repeat(n - 1) { val tmp = a + b; a = b; b = tmp } + return b + } + + private fun jvmLeibniz(iterations: Int): Double { + var sum = 0.0 + for (i in 0 until iterations) sum += (if (i % 2 == 0) 1.0 else -1.0) / (2 * i + 1) + return sum * 4 + } + + private fun jvmSumArray(size: Int): Long { + var sum = 0L; for (i in 0 until size) sum += i; return sum + } + + private fun jvmBubbleSort(size: Int): Int { + val arr = IntArray(size) { size - it } + for (i in 0 until size) for (j in 0 until size - i - 1) { + if (arr[j] > arr[j + 1]) { val tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp } + } + return arr[0] + } + + private fun jvmConcatLoop(iterations: Int): Int { + var s = ""; for (i in 0 until iterations) s += i.toString(); return s.length + } + + // ── Benchmark tests ───────────────────────────────────────────────────── + + @Test fun `benchmark - fibonacci recursive (n=35)`() { + println("\n=== RUST BENCHMARKS ===") + val n = 35 + val nativeMs = bench { FibCalculator().use { it.fib_recursive(n) } } + val jvmMs = bench { jvmFibRecursive(n) } + report("fib_recursive(35)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - fibonacci iterative (n=1M)`() { + val n = 1_000_000 + val nativeMs = bench { FibCalculator().use { it.fib_iterative(n) } } + val jvmMs = bench { jvmFibIterative(n) } + report("fib_iterative(1M)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - pi leibniz (10M iterations)`() { + val n = 10_000_000 + val nativeMs = bench { PiCalculator().use { it.leibniz(n) } } + val jvmMs = bench { jvmLeibniz(n) } + report("pi_leibniz(10M)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - sum array (10M)`() { + val n = 10_000_000 + val nativeMs = bench { ArrayProcessor().use { it.sum_array(n) } } + val jvmMs = bench { jvmSumArray(n) } + report("sum_array(10M)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - bubble sort (5K)`() { + val n = 5_000 + val nativeMs = bench { ArrayProcessor().use { it.bubble_sort_size(n) } } + val jvmMs = bench { jvmBubbleSort(n) } + report("bubble_sort(5K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - string concat (10K)`() { + val n = 10_000 + val nativeMs = bench { StringProcessor().use { it.concat_loop(n) } } + val jvmMs = bench { jvmConcatLoop(n) } + report("string_concat(10K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - FFM call overhead (100K simple calls)`() { + val nativeMs = bench { + FibCalculator().use { calc -> + repeat(100_000) { calc.fib_iterative(1) } + } + } + val jvmMs = bench { + repeat(100_000) { jvmFibIterative(1) } + } + report("ffm_overhead(100K calls)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - object create-close (10K cycles)`() { + val nativeMs = bench { + repeat(10_000) { FibCalculator().use { it.fib_iterative(10) } } + } + val jvmMs = bench { + repeat(10_000) { jvmFibIterative(10) } + } + report("create_close(10K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - string return (10K calls)`() { + val nativeMs = bench { + StringProcessor().use { proc -> + repeat(10_000) { proc.reverse_string("hello world benchmark test") } + } + } + val jvmMs = bench { + repeat(10_000) { "hello world benchmark test".reversed() } + } + report("string_return(10K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - data class return (10K calls)`() { + val nativeMs = bench { + AllocationBench().use { ab -> + repeat(10_000) { ab.get_vec() } + } + } + val jvmMs = bench { + repeat(10_000) { Vec2(1.0, 2.0) } + } + report("dc_return(10K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - data class param (10K calls)`() { + val nativeMs = bench { + AllocationBench().use { ab -> + val v = Vec2(3.0, 4.0) + repeat(10_000) { ab.sum_vec(v) } + } + } + val jvmMs = bench { + repeat(10_000) { 3.0 + 4.0 } + } + report("dc_param(10K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - memory allocation (point creation 100K)`() { + val nativeMem = memUsedKB { + AllocationBench().use { it.allocate_points(100_000) } + } + val jvmMem = memUsedKB { + var sum = 0.0 + repeat(100_000) { i -> sum += i.toDouble() } + } + println(" %-40s rust=%5d KB jvm=%5d KB".format("mem_alloc(100K points)", nativeMem, jvmMem)) + assertTrue(true) + } + + // ── Concurrent benchmarks ────────────────────────────────────────────── + + @Test fun `benchmark - concurrent fib (10 threads x 1K)`() { + val nativeMs = bench { + val threads = (1..10).map { Thread { FibCalculator().use { c -> repeat(1_000) { c.fib_iterative(100) } } } } + threads.forEach { it.start() }; threads.forEach { it.join() } + } + val jvmMs = bench { + val threads = (1..10).map { Thread { repeat(1_000) { jvmFibIterative(100) } } } + threads.forEach { it.start() }; threads.forEach { it.join() } + } + report("concurrent_fib(10t x 1K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - concurrent string (10 threads x 1K)`() { + val nativeMs = bench { + val threads = (1..10).map { Thread { StringProcessor().use { p -> repeat(1_000) { p.reverse_string("benchmark") } } } } + threads.forEach { it.start() }; threads.forEach { it.join() } + } + val jvmMs = bench { + val threads = (1..10).map { Thread { repeat(1_000) { "benchmark".reversed() } } } + threads.forEach { it.start() }; threads.forEach { it.join() } + } + report("concurrent_string(10t x 1K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - concurrent create-close (10 threads x 1K)`() { + val nativeMs = bench { + val threads = (1..10).map { Thread { repeat(1_000) { FibCalculator().use { it.fib_iterative(10) } } } } + threads.forEach { it.start() }; threads.forEach { it.join() } + } + val jvmMs = bench { + val threads = (1..10).map { Thread { repeat(1_000) { jvmFibIterative(10) } } } + threads.forEach { it.start() }; threads.forEach { it.join() } + } + report("concurrent_create(10t x 1K)", nativeMs, jvmMs) + assertTrue(true) + } + + @Test fun `benchmark - summary`() { + println("\n NOTE: Ratios >1 mean Rust (via FFM) is slower than pure JVM.") + println(" Heavy compute (fib, pi, sort) runs entirely in Rust — expect ratio ~1 or Rust faster.") + println(" Frequent small calls have FFM overhead — ratio >1.") + println(" String/DC marshaling has buffer copy overhead.") + println(" Compare these results with the Kotlin/Native benchmark for KN vs Rust comparison.") + println("=== END RUST BENCHMARKS ===\n") + assertTrue(true) + } +} diff --git a/examples/rust-calculator/build.gradle.kts b/examples/rust-calculator/build.gradle.kts new file mode 100644 index 00000000..e7c7c933 --- /dev/null +++ b/examples/rust-calculator/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") version "1.10.2" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(compose.runtime) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2") + } + } + } +} + +compose.desktop { + application { + mainClass = "com.example.rustcalculator.MainKt" + nativeDistributions { + packageName = "RustCalculator" + } + jvmArgs("--enable-native-access=ALL-UNNAMED") + } +} + +rustImport { + libraryName = "rustcalc" + jvmPackage = "com.example.rustcalculator" + buildType = "release" + cratePath("calculator", "${projectDir}/rust") +} diff --git a/examples/rust-calculator/rust/Cargo.toml b/examples/rust-calculator/rust/Cargo.toml new file mode 100644 index 00000000..5c3d008b --- /dev/null +++ b/examples/rust-calculator/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "calculator" +version = "0.1.0" +edition = "2021" + +[lib] +name = "rustcalc" +crate-type = ["cdylib"] diff --git a/examples/rust-calculator/rust/src/lib.rs b/examples/rust-calculator/rust/src/lib.rs new file mode 100644 index 00000000..19b97910 --- /dev/null +++ b/examples/rust-calculator/rust/src/lib.rs @@ -0,0 +1,1394 @@ +// Rust Calculator — mirrors the Kotlin/Native Calculator example. +// This serves as the reference implementation to validate NNA's Rust support. + +/// Arithmetic operations supported by the calculator. +pub enum Operation { + Add, + Subtract, + Multiply, +} + +/// Result of a calculator operation — demonstrates tagged enum (sealed class). +pub enum CalcResult { + /// A successful integer result. + Value(i32), + /// An error with a message. + Error(String), + /// A partial result with value and confidence. + Partial { value: i32, confidence: f64 }, + /// No result available. + Nothing, +} + +/// A data container demonstrating sealed enum variants with collection fields. +pub enum DataPayload { + /// A list of integer scores. + Scores(Vec), + /// A set of unique integers. + UniqueIds(std::collections::HashSet), + /// A key-value map (Int → Int). + Mapping(std::collections::HashMap), + /// A list of string tags. + Tags(Vec), + /// Empty payload. + Empty, +} + +/// Demonstrates sealed enum with multi-field TUPLE variants (N > 1). +pub enum ErrorInfo { + /// Two-string tuple variant, like OpenDeviceError(String, String). + DeviceError(String, String), + /// Three-field mixed-type tuple variant. + PropertyError(String, i32, String), + /// Two-field tuple: int + string. + CodedMessage(i32, String), + /// Single-field tuple for reference. + Simple(String), + /// Unit variant. + None, +} + +/// Demonstrates sealed enum with STRUCT variants (named fields) and enum-typed fields. +pub enum ProcessingStatus { + /// Struct variant with three String fields — like ProcessFrameError { src, destination, error }. + FrameError { + src: String, + destination: String, + error: String, + }, + /// Struct variant with an enum field — tests enum-inside-sealed-variant. + OperationFailed { + operation: Operation, + code: i32, + message: String, + }, + /// Struct variant with mixed primitives and string. + Progress { + step: i32, + total: i32, + label: String, + done: bool, + }, + /// Unit variant. + Idle, +} + +/// Simple 2D point (data class -- all public fields, no complex methods). +pub struct Point { + pub x: i32, + pub y: i32, +} + +/// A named value (data class -- mirrors Kotlin NamedValue). +pub struct NamedValue { + pub name: String, + pub value: i32, +} + +// ── Traits (→ Kotlin interfaces) ──────────────────────────────────────── + +/// Something that can describe itself. +pub trait Describable { + fn describe_self(&self) -> String; +} + +/// Something that can be reset to its initial state. +pub trait Resettable { + fn reset_to_default(&mut self); +} + +/// Something that can measure a numeric value. +pub trait Measurable { + fn measure(&self) -> f64; + fn unit(&self) -> String; +} + +/// A stateful calculator that accumulates a value. +/// +/// Mirrors the Kotlin/Native Calculator class: +/// - Mutable accumulator with arithmetic operations +/// - All primitive type conversions +/// - String operations +/// - Enum support +/// - Nullable returns (via Option) +/// - Error propagation (via panic) +pub struct Calculator { + accumulator: i32, + label: String, + scale: f64, + enabled: bool, + last_operation: Operation, + nickname: Option, +} + +impl Calculator { + // ── Constructor ───────────────────────────────────────────────────── + + pub fn new(initial: i32) -> Self { + Calculator { + accumulator: initial, + label: String::new(), + scale: 1.0, + enabled: true, + last_operation: Operation::Add, + nickname: None, + } + } + + // ── Int methods ───────────────────────────────────────────────────── + + pub fn add(&mut self, value: i32) -> i32 { + self.accumulator += value; + self.accumulator + } + + pub fn subtract(&mut self, value: i32) -> i32 { + self.accumulator -= value; + self.accumulator + } + + pub fn multiply(&mut self, value: i32) -> i32 { + self.accumulator *= value; + self.accumulator + } + + pub fn reset(&mut self) { + self.accumulator = 0; + } + + pub fn divide(&mut self, divisor: i32) -> i32 { + if divisor == 0 { + panic!("Division by zero"); + } + self.accumulator /= divisor; + self.accumulator + } + + /// Divides accumulator, returning Result. Tests &mut self + Result combination. + pub fn try_divide_result(&mut self, divisor: i32) -> Result { + if divisor == 0 { + Err("Division by zero".to_string()) + } else { + self.accumulator /= divisor; + Ok(self.accumulator) + } + } + + pub fn fail_always(&self) -> String { + panic!("Intentional error for testing"); + } + + /// A function that never returns normally (diverges). + /// Returns Rust's `!` (Never) type, bridged as `Nothing` in Kotlin. + pub fn panic_always(&self) -> ! { + panic!("This calculator has crashed"); + } + + pub fn get_current(&self) -> i32 { + self.accumulator + } + + // ── All primitive types as params and returns ─────────────────────── + + pub fn add_long(&self, value: i64) -> i64 { + (self.accumulator as i64) + value + } + + pub fn add_double(&self, value: f64) -> f64 { + (self.accumulator as f64) + value + } + + pub fn add_float(&self, value: f32) -> f32 { + (self.accumulator as f32) + value + } + + pub fn add_short(&self, value: i16) -> i16 { + (self.accumulator as i16).wrapping_add(value) + } + + pub fn add_byte(&self, value: i8) -> i8 { + (self.accumulator as i8).wrapping_add(value) + } + + pub fn is_positive(&self) -> bool { + self.accumulator > 0 + } + + pub fn check_flag(&self, flag: bool) -> bool { + flag && self.accumulator > 0 + } + + // ── String methods ────────────────────────────────────────────────── + + pub fn describe(&self) -> String { + format!("Calculator(current={})", self.accumulator) + } + + pub fn echo(&self, text: String) -> String { + text + } + + pub fn concat(&self, a: String, b: String) -> String { + format!("{}{}", a, b) + } + + // ── Property accessors ────────────────────────────────────────────── + + pub fn get_label(&self) -> String { + self.label.clone() + } + + pub fn set_label(&mut self, label: String) { + self.label = label; + } + + pub fn get_scale(&self) -> f64 { + self.scale + } + + pub fn set_scale(&mut self, scale: f64) { + self.scale = scale; + } + + pub fn get_enabled(&self) -> bool { + self.enabled + } + + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled = enabled; + } + + // ── Enum support ──────────────────────────────────────────────────── + + pub fn apply_op(&mut self, op: &Operation, value: i32) -> i32 { + self.last_operation = match op { + Operation::Add => Operation::Add, + Operation::Subtract => Operation::Subtract, + Operation::Multiply => Operation::Multiply, + }; + match op { + Operation::Add => self.add(value), + Operation::Subtract => self.subtract(value), + Operation::Multiply => self.multiply(value), + } + } + + // ── Nullable returns (Option) ──────────────────────────────────── + + pub fn divide_or_null(&self, divisor: i32) -> Option { + if divisor != 0 { + Some(self.accumulator / divisor) + } else { + None + } + } + + pub fn describe_or_null(&self) -> Option { + if self.accumulator > 0 { + Some(format!("Positive({})", self.accumulator)) + } else { + None + } + } + + pub fn is_positive_or_null(&self) -> Option { + if self.accumulator == 0 { + None + } else { + Some(self.accumulator > 0) + } + } + + pub fn to_long_or_null(&self) -> Option { + if self.accumulator != 0 { + Some(self.accumulator as i64) + } else { + None + } + } + + pub fn to_double_or_null(&self) -> Option { + if self.accumulator != 0 { + Some(self.accumulator as f64) + } else { + None + } + } + + // ── Nullable params (Option) ──────────────────────────────────── + + pub fn add_optional(&mut self, value: Option) -> i32 { + if let Some(v) = value { + self.accumulator += v; + } + self.accumulator + } + + pub fn set_nickname(&mut self, name: Option) { + self.nickname = name; + } + + pub fn get_nickname(&self) -> Option { + self.nickname.clone() + } + + pub fn add_point_or_null(&mut self, p: Option<&Point>) -> i32 { + if let Some(pt) = p { + self.accumulator += pt.x + pt.y; + } + self.accumulator + } + + // ── Sealed enum support ──────────────────────────────────────────── + + pub fn try_divide(&self, divisor: i32) -> CalcResult { + if divisor == 0 { + CalcResult::Error("Division by zero".to_string()) + } else if self.accumulator == 0 { + CalcResult::Nothing + } else if self.accumulator % divisor != 0 { + let value = self.accumulator / divisor; + let confidence = + 1.0 - ((self.accumulator % divisor) as f64 / self.accumulator as f64).abs(); + CalcResult::Partial { value, confidence } + } else { + CalcResult::Value(self.accumulator / divisor) + } + } + + pub fn last_result(&self) -> CalcResult { + if self.accumulator > 0 { + CalcResult::Value(self.accumulator) + } else if self.accumulator == 0 { + CalcResult::Nothing + } else { + CalcResult::Error(format!("Negative value: {}", self.accumulator)) + } + } + + // ── ErrorInfo sealed enum support ──────────────────────────────── + + /// Returns an ErrorInfo based on the current state. + pub fn get_error_info(&self) -> ErrorInfo { + if self.accumulator == 0 { + ErrorInfo::None + } else if self.accumulator < 0 { + ErrorInfo::DeviceError( + "calculator".to_string(), + format!("negative value: {}", self.accumulator), + ) + } else if self.accumulator > 1000 { + ErrorInfo::PropertyError( + "accumulator".to_string(), + self.accumulator, + "value too large".to_string(), + ) + } else if self.accumulator > 100 { + ErrorInfo::CodedMessage(self.accumulator, format!("code_{}", self.accumulator)) + } else { + ErrorInfo::Simple(format!("ok: {}", self.accumulator)) + } + } + + // ── ProcessingStatus sealed enum support ──────────────────────── + + /// Returns a ProcessingStatus based on the current state. + pub fn get_processing_status(&self) -> ProcessingStatus { + if self.accumulator == 0 { + ProcessingStatus::Idle + } else if self.accumulator < 0 { + ProcessingStatus::FrameError { + src: format!("input_{}", self.accumulator.abs()), + destination: "output".to_string(), + error: format!("negative value: {}", self.accumulator), + } + } else if self.accumulator > 100 { + ProcessingStatus::OperationFailed { + operation: match self.last_operation { + Operation::Add => Operation::Add, + Operation::Subtract => Operation::Subtract, + Operation::Multiply => Operation::Multiply, + }, + code: self.accumulator, + message: "overflow".to_string(), + } + } else { + ProcessingStatus::Progress { + step: self.accumulator, + total: 100, + label: self.label.clone(), + done: self.accumulator >= 100, + } + } + } + + // ── Callback support ──────────────────────────────────────────── + + pub fn transform_and_sum(&self, values: &[i32], transform: fn(i32) -> i32) -> i32 { + values.iter().map(|&v| transform(v)).sum() + } + + pub fn for_each_score(&self, count: i32, callback: fn(i32)) { + for i in 1..=count { + callback(self.accumulator * i); + } + } + + /// Runs a tick loop, calling on_tick for each iteration. + /// @kne:suspend + pub fn run_tick_loop(&self, count: i32, interval_ms: i32, on_tick: fn(i32)) { + for i in 1..=count { + std::thread::sleep(std::time::Duration::from_millis(interval_ms as u64)); + on_tick(self.accumulator + i); + } + } + + // ── Callbacks with handle-backed types ───────────────────────────── + + /// Callback returning a CalcResult (sealed enum). + pub fn map_to_result CalcResult>(&self, value: i32, mapper: F) -> CalcResult { + mapper(value) + } + + /// Callback returning a Calculator (object). + pub fn create_via_callback Calculator>(&self, factory: F) -> Calculator { + factory(self.accumulator) + } + + /// Callback taking a Calculator (object) and returning an i32. + pub fn apply_to_clone i32>(&self, processor: F) -> i32 { + let clone = Calculator::new(self.accumulator); + processor(clone) + } + + /// Callback taking a CalcResult (sealed enum) and returning a String. + pub fn format_result String>( + &self, + result: CalcResult, + formatter: F, + ) -> String { + formatter(result) + } + + // ── Nullable callback support ──────────────────────────────────── + + /// Applies an optional transform to the accumulator. Returns transformed value or accumulator if None. + pub fn maybe_transform(&self, transform: Option i32>) -> i32 { + match transform { + Some(f) => f(self.accumulator), + None => self.accumulator, + } + } + + /// Calls the optional callback with each value from 1..=count. Does nothing if callback is None. + pub fn maybe_for_each(&self, count: i32, callback: Option) { + if let Some(cb) = callback { + for i in 1..=count { + cb(self.accumulator * i); + } + } + } + + /// Registers an optional observer, applies an operation, then calls the observer with the result. + /// Returns the computed value. + pub fn compute_with_observer(&self, value: i32, observer: Option) -> i32 { + let result = self.accumulator + value; + if let Some(obs) = observer { + obs(format!("computed: {}", result)); + } + result + } + + // ── Data class support ──────────────────────────────────────────── + + pub fn get_point(&self) -> Point { + Point { + x: self.accumulator, + y: self.accumulator * 2, + } + } + + pub fn add_point(&mut self, p: &Point) -> i32 { + self.accumulator += p.x + p.y; + self.accumulator + } + + pub fn get_named_value(&self) -> NamedValue { + let name = if self.label.is_empty() { + "default".to_string() + } else { + self.label.clone() + }; + NamedValue { + name, + value: self.accumulator, + } + } + + pub fn set_from_named(&mut self, nv: &NamedValue) { + self.accumulator = nv.value; + self.label = nv.name.clone(); + } + + // ── ByteArray support ───────────────────────────────────────────── + + pub fn to_bytes(&self) -> Vec { + self.accumulator.to_string().into_bytes() + } + + pub fn sum_bytes(&mut self, data: &[u8]) -> i32 { + self.accumulator = data.iter().map(|&b| b as i32).sum(); + self.accumulator + } + + pub fn reverse_bytes(&self, data: &[u8]) -> Vec { + data.iter().rev().copied().collect() + } + + /// Returns the label as a borrowed byte slice (&[u8]). + pub fn label_bytes(&self) -> &[u8] { + self.label.as_bytes() + } + + // ── Collection support ────────────────────────────────────────────── + + pub fn get_recent_scores(&self) -> Vec { + vec![self.accumulator, self.accumulator * 2, self.accumulator * 3] + } + + /// Returns score names as a list of strings. + pub fn get_score_names(&self) -> Vec { + (1..=3) + .map(|i| format!("score_{}", self.accumulator * i)) + .collect() + } + + /// Returns ratios as a list of doubles. + pub fn get_ratios(&self) -> Vec { + vec![ + self.accumulator as f64, + self.accumulator as f64 / 2.0, + self.accumulator as f64 / 3.0, + ] + } + + /// Returns flags as a list of booleans. + pub fn get_flags(&self) -> Vec { + vec![ + self.accumulator > 0, + self.accumulator > 10, + self.accumulator > 100, + ] + } + + // ── Optional collection support ──────────────────────────────────────── + + /// Returns optional scores — Some if accumulator > 0, None otherwise. + pub fn get_optional_scores(&self) -> Option> { + if self.accumulator > 0 { + Some(vec![ + self.accumulator, + self.accumulator * 2, + self.accumulator * 3, + ]) + } else { + None + } + } + + /// Returns optional tags — Some if label is set, None otherwise. + pub fn get_optional_tags(&self) -> Option> { + if self.label.is_empty() { + None + } else { + Some(vec![self.label.clone(), format!("scale:{}", self.scale)]) + } + } + + /// Returns optional metadata map. + pub fn get_optional_metadata(&self) -> Option> { + if self.accumulator == 0 { + None + } else { + let mut map = std::collections::HashMap::new(); + map.insert("current".to_string(), self.accumulator); + map.insert("scale".to_string(), self.scale as i32); + Some(map) + } + } + + // ── Tuple support ───────────────────────────────────────────────── + + /// Returns coordinates as a tuple (x, y). + pub fn get_coordinates(&self) -> (i32, i32) { + (self.accumulator, self.accumulator * 2) + } + + /// Returns a triple: (count, label, enabled). + pub fn get_triple(&self) -> (i32, String, bool) { + (self.accumulator, self.label.clone(), self.enabled) + } + + /// Takes a tuple parameter and returns the sum. + pub fn sum_tuple(&self, coords: (i32, i32)) -> i32 { + self.accumulator + coords.0 + coords.1 + } + + /// Returns nested tuple. + pub fn get_nested_tuple(&self) -> (i32, (String, bool)) { + (self.accumulator, (self.label.clone(), self.enabled)) + } + + /// Returns deeply nested tuple (3 levels). + pub fn get_deep_tuple(&self) -> (i32, (String, (bool, i32))) { + ( + self.accumulator, + (self.label.clone(), (self.enabled, self.accumulator * 3)), + ) + } + + /// Returns tuple with two nested tuples. + pub fn get_double_nested(&self) -> ((i32, i32), (String, bool)) { + ( + (self.accumulator, self.accumulator * 2), + (self.label.clone(), self.enabled), + ) + } + + /// Returns nested tuple with all primitive types. + pub fn get_typed_nested(&self) -> (i64, (f64, i32)) { + ( + self.accumulator as i64 * 1000, + (self.scale, self.accumulator), + ) + } + + /// Returns tuple with a vector: (scores, label). + pub fn get_with_scores(&self) -> (Vec, String) { + ( + vec![self.accumulator, self.accumulator * 2, self.accumulator * 3], + self.label.clone(), + ) + } + + /// Returns tuple with metadata map: (count, metadata). + pub fn get_with_metadata(&self) -> (i32, std::collections::HashMap) { + let mut map = std::collections::HashMap::new(); + map.insert("current".to_string(), self.accumulator); + map.insert("scale".to_string(), self.scale as i32); + map.insert("double".to_string(), self.accumulator * 2); + (self.accumulator, map) + } + + /// Returns metadata map with nested list values. + pub fn get_metadata(&self) -> std::collections::HashMap> { + let mut map = std::collections::HashMap::new(); + map.insert( + "values".to_string(), + vec![self.accumulator, self.accumulator * 2, self.accumulator * 3], + ); + map.insert("factors".to_string(), vec![1, 2, 3, 5]); + map + } + + /// Returns tuple with map: (label, metadata). + pub fn get_with_metadata_map(&self) -> (String, std::collections::HashMap>) { + let mut map = std::collections::HashMap::new(); + map.insert( + "values".to_string(), + vec![self.accumulator, self.accumulator * 2], + ); + map.insert("labels".to_string(), vec![1, 2, 3]); + (self.label.clone(), map) + } + + // ── Async/suspend-like methods ──────────────────────────────────── + // Functions annotated with `@kne:suspend` in doc comments are bridged + // as Kotlin suspend functions. The bridge spawns a thread, calls the + // function, then invokes the continuation callback. + + /// Adds value after a delay and returns the new accumulator. + /// @kne:suspend + pub fn delayed_add(&mut self, value: i32, delay_ms: i32) -> i32 { + std::thread::sleep(std::time::Duration::from_millis(delay_ms as u64)); + self.accumulator += value; + self.accumulator + } + + /// Returns a description string after a delay. + /// @kne:suspend + pub fn delayed_describe(&self, delay_ms: i32) -> String { + std::thread::sleep(std::time::Duration::from_millis(delay_ms as u64)); + format!("Calculator(current={})", self.accumulator) + } + + /// Panics after a delay (tests suspend error propagation). + /// @kne:suspend + pub fn fail_after_delay(&self, delay_ms: i32) -> String { + std::thread::sleep(std::time::Duration::from_millis(delay_ms as u64)); + panic!("Intentional delayed error"); + } + + /// Does nothing after a delay (suspend returning Unit). + /// @kne:suspend + pub fn delayed_noop(&self, delay_ms: i32) { + std::thread::sleep(std::time::Duration::from_millis(delay_ms as u64)); + } + + /// Returns whether accumulator is positive, after a delay. + /// @kne:suspend + pub fn delayed_is_positive(&self, delay_ms: i32) -> bool { + std::thread::sleep(std::time::Duration::from_millis(delay_ms as u64)); + self.accumulator > 0 + } + // ── Flow-like methods ──────────────────────────────────────────── + // Functions annotated with `@kne:flow(ElementType)` are bridged as + // Kotlin Flow. The bridge iterates the returned Vec and calls + // onNext for each element. + + /// Emits integers from 1 to max with interval_ms delay between each. + /// @kne:flow(Int) + pub fn count_up(&self, max: i32, interval_ms: i32) -> Vec { + let mut result = Vec::new(); + for i in 1..=max { + std::thread::sleep(std::time::Duration::from_millis(interval_ms as u64)); + result.push(self.accumulator + i); + } + result + } + + /// Emits score labels as strings. + /// @kne:flow(String) + pub fn score_labels(&self, count: i32) -> Vec { + (1..=count) + .map(|i| format!("Score #{}: {}", i, self.accumulator * i)) + .collect() + } + + // ── impl Trait return types ───────────────────────────────────────── + + /// Returns an iterator over the recent scores (impl Iterator). + pub fn iter_scores(&self) -> impl Iterator { + let acc = self.accumulator; + (1..=3).map(move |i| acc * i) + } + + /// Returns an iterator over score labels (impl Iterator). + pub fn iter_labels(&self) -> impl Iterator { + let acc = self.accumulator; + (1..=3).map(move |i| format!("score_{}", acc * i)) + } + + /// Returns an empty iterator (edge case: zero elements). + pub fn iter_empty(&self) -> impl Iterator { + std::iter::empty() + } + + /// Returns a Display-able description (impl Display). + pub fn display_value(&self) -> impl std::fmt::Display { + format!("Calc({})", self.accumulator) + } + + /// Returns impl ToString (mapped via .to_string()). + pub fn as_string_repr(&self) -> impl ToString { + self.accumulator + } + + /// Returns an ExactSizeIterator (also recognized as Iterator). + pub fn exact_scores(&self) -> impl ExactSizeIterator { + let acc = self.accumulator; + vec![acc, acc * 2, acc * 3].into_iter() + } + + /// Returns impl Iterator with a Result-wrapped return (canFail + implTrait). + pub fn try_iter_scores(&self) -> Result, String> { + if self.accumulator < 0 { + Err("negative accumulator".to_string()) + } else { + let acc = self.accumulator; + Ok((1..=3).map(move |i| acc * i)) + } + } + + /// impl Iterator — tests boolean element type. + pub fn iter_flags(&self) -> impl Iterator { + let acc = self.accumulator; + vec![acc > 0, acc > 10, acc > 100].into_iter() + } + + /// impl Iterator — tests float element type. + pub fn iter_ratios(&self) -> impl Iterator { + let acc = self.accumulator as f64; + vec![acc / 2.0, acc / 3.0, acc / 4.0].into_iter() + } + + /// impl Iterator — tests Long element type. + pub fn iter_big_values(&self) -> impl Iterator { + let acc = self.accumulator as i64; + vec![acc * 1_000_000, acc * 2_000_000].into_iter() + } + + /// Large iterator — triggers buffer overflow/retry logic (>4096 elements). + pub fn iter_large(&self, count: i32) -> impl Iterator { + let acc = self.accumulator; + (0..count).map(move |i| acc + i) + } + + /// &mut self + impl Iterator — mutable receiver with impl Trait return. + pub fn drain_and_iter(&mut self, n: i32) -> impl Iterator { + self.accumulator += n; + let acc = self.accumulator; + (1..=3).map(move |i| acc * i) + } + + /// impl Display with unicode content. + pub fn display_unicode(&self) -> impl std::fmt::Display { + format!("計算機({})", self.accumulator) + } + + /// impl Display with very long string (>8192 bytes, triggers buffer retry). + pub fn display_long(&self) -> impl std::fmt::Display { + format!("x{}", "A".repeat(10_000)) + } + + /// impl DoubleEndedIterator — tested to ensure trait recognition. + pub fn iter_reversed(&self) -> impl DoubleEndedIterator { + let acc = self.accumulator; + vec![acc, acc * 2, acc * 3].into_iter() + } + + /// impl IntoIterator — tested to ensure trait recognition. + pub fn iter_into(&self) -> impl IntoIterator { + let acc = self.accumulator; + vec![acc, acc + 1, acc + 2] + } + + /// impl Iterator + Send — multiple bounds, should still work. + pub fn iter_sendable(&self) -> impl Iterator + Send { + let acc = self.accumulator; + vec![acc, acc * 10].into_iter() + } + + /// Result — fallible + Display. + pub fn try_display(&self) -> Result { + if self.accumulator < 0 { + Err("negative".to_string()) + } else { + Ok(format!("OK({})", self.accumulator)) + } + } + + /// Panic during .collect() — tests error propagation from inside iterator. + pub fn iter_panicking(&self) -> impl Iterator { + let acc = self.accumulator; + (0..3).map(move |i| { + if i == 2 && acc < 0 { + panic!("iterator panic at index 2"); + } + acc + i + }) + } + + /// Companion/static method returning impl Iterator (no &self). + pub fn fibonacci_iter(n: i32) -> impl Iterator { + let mut a = 0i32; + let mut b = 1i32; + (0..n).map(move |_| { + let val = a; + let next = a.wrapping_add(b); + a = b; + b = next; + val + }) + } + + /// Companion/static method returning impl Display (no &self). + pub fn static_label(prefix: String, value: i32) -> impl std::fmt::Display { + format!("{}={}", prefix, value) + } +} + +// ── Trait implementations ─────────────────────────────────────────────── + +impl Describable for Calculator { + fn describe_self(&self) -> String { + format!( + "Calculator(current={}, label={})", + self.accumulator, self.label + ) + } +} + +impl Resettable for Calculator { + fn reset_to_default(&mut self) { + self.accumulator = 0; + self.label = String::new(); + self.scale = 1.0; + self.enabled = true; + } +} + +impl Measurable for Calculator { + fn measure(&self) -> f64 { + self.accumulator as f64 * self.scale + } + + fn unit(&self) -> String { + "units".to_string() + } +} + +// ── Top-level functions ───────────────────────────────────────────────── + +/// Computes a binary operation on two integers. +pub fn compute(a: i32, b: i32, op: &Operation) -> i32 { + match op { + Operation::Add => a + b, + Operation::Subtract => a - b, + Operation::Multiply => a * b, + } +} + +/// Returns a greeting message. +pub fn greet(name: String) -> String { + format!("Hello, {}!", name) +} + +/// Adds all numbers in a slice. +pub fn sum_all(numbers: &[i32]) -> i32 { + numbers.iter().sum() +} + +/// Finds the maximum value in a slice, or None if empty. +pub fn find_max(numbers: &[i32]) -> Option { + numbers.iter().copied().max() +} + +/// Top-level function returning impl Iterator (generates a range). +pub fn generate_range(start: i32, end: i32) -> impl Iterator { + start..end +} + +/// Top-level function returning impl Display. +pub fn format_pair(a: i32, b: i32) -> impl std::fmt::Display { + format!("{} + {} = {}", a, b, a + b) +} + +/// Top-level impl Into. +pub fn into_greeting(name: String) -> impl Into { + format!("Hi, {}!", name) +} + +/// Top-level impl AsRef. +pub fn as_ref_label(value: i32) -> impl AsRef { + format!("label_{}", value) +} + +/// Top-level impl Iterator with String param. +pub fn repeat_str(text: String, count: i32) -> impl Iterator { + let t = text; + (0..count).map(move |_| t.clone()) +} + +/// Top-level Result. +pub fn try_format(a: i32, b: i32) -> Result { + if b == 0 { + Err("division by zero".to_string()) + } else { + Ok(format!("{} / {} = {}", a, b, a / b)) + } +} + +// ── impl Trait parameter functions ───────────────────────────────────── + +/// Takes impl ToString as parameter — exercises impl Trait param bridging. +pub fn greet_impl(name: impl ToString) -> String { + format!("Hello, {}!", name.to_string()) +} + +/// Takes impl Into as parameter. +pub fn into_upper(text: impl Into) -> String { + text.into().to_uppercase() +} + +/// Takes impl AsRef as parameter. +pub fn count_chars(text: impl AsRef) -> i32 { + text.as_ref().len() as i32 +} + +/// Takes a Box — exercises ownership transfer of trait object. +pub fn consume_describable(obj: Box) -> String { + format!("consumed: {}", obj.describe_self()) +} + +// ── dyn Trait functions (trait objects) ────────────────────────────────── + +/// Creates a Describable trait object from a Calculator. +pub fn create_describable(initial: i32) -> Box { + Box::new(Calculator::new(initial)) +} + +/// Creates a Measurable trait object from a Calculator. +pub fn create_measurable(initial: i32) -> Box { + Box::new(Calculator::new(initial)) +} + +/// Creates a Resettable trait object from a Calculator with mutations. +pub fn create_resettable(initial: i32) -> Box { + Box::new(Calculator::new(initial)) +} + +/// Takes a reference to a dyn Describable and returns its description. +pub fn describe_trait_object(obj: &dyn Describable) -> String { + obj.describe_self() +} + +/// Takes a Box and returns the measurement + unit. +pub fn measure_trait_object(obj: &dyn Measurable) -> String { + format!("{} {}", obj.measure(), obj.unit()) +} + +/// Factory: returns Option>. +pub fn maybe_create_describable(initial: i32) -> Option> { + if initial >= 0 { + Some(Box::new(Calculator::new(initial))) + } else { + None + } +} + +/// Returns a Vec of dyn Describable trait objects. +pub fn create_describable_list(values: &[i32]) -> Vec> { + values + .iter() + .map(|&v| Box::new(Calculator::new(v)) as Box) + .collect() +} + +/// Mutates a dyn Resettable trait object. +pub fn reset_trait_object(obj: &mut dyn Resettable) { + obj.reset_to_default(); +} + +/// Returns Result, String>. +pub fn try_create_describable(initial: i32) -> Result, String> { + if initial == i32::MIN { + Err("invalid initial value".to_string()) + } else { + Ok(Box::new(Calculator::new(initial))) + } +} + +// ── TraitConsumer: struct with dyn Trait constructor/method params ───────── + +/// A struct that stores a description obtained from a dyn Describable param. +pub struct TraitConsumer { + description: String, +} + +impl TraitConsumer { + /// Constructor taking &dyn Describable — exercises dyn Trait as constructor param. + pub fn new(source: &dyn Describable) -> Self { + TraitConsumer { + description: source.describe_self(), + } + } + + /// Returns the stored description. + pub fn get_description(&self) -> String { + self.description.clone() + } + + /// Method taking &dyn Describable — exercises dyn Trait as method param. + pub fn update_from(&mut self, source: &dyn Describable) { + self.description = source.describe_self(); + } + + /// Method taking &dyn Measurable — exercises different trait as method param. + pub fn measure_from(&self, source: &dyn Measurable) -> String { + format!( + "{}: {} {}", + self.description, + source.measure(), + source.unit() + ) + } +} + +// ── DataPayload factory functions ────────────────────────────────────────── + +/// Creates a Scores payload from a slice of ints. +pub fn create_scores_payload(values: &[i32]) -> DataPayload { + DataPayload::Scores(values.to_vec()) +} + +/// Creates a UniqueIds payload from a slice of ints. +pub fn create_unique_ids_payload(ids: &[i32]) -> DataPayload { + DataPayload::UniqueIds(ids.iter().cloned().collect()) +} + +/// Creates a Mapping payload from parallel key/value slices. +pub fn create_mapping_payload(keys: &[i32], values: &[i32]) -> DataPayload { + let map: std::collections::HashMap = keys + .iter() + .zip(values.iter()) + .map(|(k, v)| (*k, *v)) + .collect(); + DataPayload::Mapping(map) +} + +/// Creates a Tags payload from an array of C string pointers. +pub fn create_tags_payload(tags_ptr: *const *const std::ffi::c_char, tags_len: i32) -> DataPayload { + let tags_slice = unsafe { std::slice::from_raw_parts(tags_ptr, tags_len as usize) }; + let tags: Vec = tags_slice + .iter() + .map(|&p| { + if p.is_null() { + String::new() + } else { + unsafe { std::ffi::CStr::from_ptr(p) } + .to_string_lossy() + .into_owned() + } + }) + .collect(); + DataPayload::Tags(tags) +} + +/// Creates an Empty payload. +pub fn create_empty_payload() -> DataPayload { + DataPayload::Empty +} + +// ── Generic monomorphisation support ──────────────────────────────────── + +/// Trait for value transformation — used to test auto-monomorphisation. +pub trait ValueTransformer { + fn transform(&self, input: i32) -> i32; + fn transformer_name(&self) -> String; +} + +/// Doubles the input value. +pub struct Doubler; + +impl Doubler { + pub fn new() -> Self { + Doubler + } +} + +impl ValueTransformer for Doubler { + fn transform(&self, input: i32) -> i32 { + input * 2 + } + fn transformer_name(&self) -> String { + "Doubler".to_string() + } +} + +/// Triples the input value. +pub struct Tripler; + +impl Tripler { + pub fn new() -> Self { + Tripler + } +} + +impl ValueTransformer for Tripler { + fn transform(&self, input: i32) -> i32 { + input * 3 + } + fn transformer_name(&self) -> String { + "Tripler".to_string() + } +} + +impl Calculator { + /// Applies a generic transformer to the current accumulator. + /// NNA should monomorphise this into apply_transformer_doubler and apply_transformer_tripler. + pub fn apply_transformer(&self, transformer: &T) -> i32 { + transformer.transform(self.accumulator) + } + + /// Returns the name of a generic transformer. + pub fn get_transformer_name(&self, transformer: &T) -> String { + transformer.transformer_name() + } +} + +/// Top-level generic function — transforms a value using any ValueTransformer. +pub fn transform_value(value: i32, transformer: &T) -> i32 { + transformer.transform(value) +} + +// ── Generic struct monomorphisation support ───────────────────────────── + +/// A generic processor that wraps a ValueTransformer and adds an offset. +/// NNA should monomorphise this into `Processor_Doubler` and `Processor_Tripler`. +pub struct Processor { + transformer: T, + offset: i32, +} + +impl Processor { + pub fn new(transformer: T, offset: i32) -> Self { + Processor { + transformer, + offset, + } + } + + pub fn process(&self, value: i32) -> i32 { + self.transformer.transform(value) + self.offset + } + + pub fn get_offset(&self) -> i32 { + self.offset + } + + pub fn set_offset(&mut self, offset: i32) { + self.offset = offset; + } + + pub fn name(&self) -> String { + format!( + "Processor({}+{})", + self.transformer.transformer_name(), + self.offset + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calculator_basic() { + let mut calc = Calculator::new(0); + assert_eq!(calc.add(5), 5); + assert_eq!(calc.add(3), 8); + assert_eq!(calc.get_current(), 8); + } + + #[test] + fn test_calculator_subtract() { + let mut calc = Calculator::new(10); + assert_eq!(calc.subtract(3), 7); + } + + #[test] + fn test_calculator_multiply() { + let mut calc = Calculator::new(4); + assert_eq!(calc.multiply(3), 12); + } + + #[test] + fn test_calculator_describe() { + let calc = Calculator::new(42); + assert_eq!(calc.describe(), "Calculator(current=42)"); + } + + #[test] + fn test_calculator_echo() { + let calc = Calculator::new(0); + assert_eq!(calc.echo("hello".to_string()), "hello"); + } + + #[test] + fn test_calculator_all_primitives() { + let calc = Calculator::new(10); + assert_eq!(calc.add_long(5), 15); + assert!((calc.add_double(3.5) - 13.5).abs() < 0.001); + assert!((calc.add_float(2.5) - 12.5).abs() < 0.01); + assert_eq!(calc.add_short(5), 15); + assert_eq!(calc.add_byte(3), 13); + assert!(calc.is_positive()); + assert!(calc.check_flag(true)); + } + + #[test] + fn test_operation_enum() { + let mut calc = Calculator::new(10); + assert_eq!(calc.apply_op(&Operation::Add, 5), 15); + assert_eq!(calc.apply_op(&Operation::Subtract, 3), 12); + assert_eq!(calc.apply_op(&Operation::Multiply, 2), 24); + } + + #[test] + fn test_nullable_returns() { + let calc = Calculator::new(10); + assert_eq!(calc.divide_or_null(2), Some(5)); + assert_eq!(calc.divide_or_null(0), None); + assert_eq!(calc.describe_or_null(), Some("Positive(10)".to_string())); + } + + #[test] + fn test_greet() { + assert_eq!(greet("World".to_string()), "Hello, World!"); + } + + #[test] + fn test_find_max() { + assert_eq!(find_max(&[1, 5, 3]), Some(5)); + assert_eq!(find_max(&[]), None); + } + + #[test] + fn test_compute() { + assert_eq!(compute(3, 4, &Operation::Add), 7); + assert_eq!(compute(10, 3, &Operation::Subtract), 7); + assert_eq!(compute(3, 4, &Operation::Multiply), 12); + } + + #[test] + fn test_impl_trait_iter_scores() { + let calc = Calculator::new(5); + let scores: Vec = calc.iter_scores().collect(); + assert_eq!(scores, vec![5, 10, 15]); + } + + #[test] + fn test_impl_trait_iter_labels() { + let calc = Calculator::new(2); + let labels: Vec = calc.iter_labels().collect(); + assert_eq!(labels, vec!["score_2", "score_4", "score_6"]); + } + + #[test] + fn test_impl_trait_iter_empty() { + let calc = Calculator::new(1); + let empty: Vec = calc.iter_empty().collect(); + assert!(empty.is_empty()); + } + + #[test] + fn test_impl_trait_display_value() { + let calc = Calculator::new(42); + assert_eq!(calc.display_value().to_string(), "Calc(42)"); + } + + #[test] + fn test_impl_trait_generate_range() { + let range: Vec = generate_range(1, 4).collect(); + assert_eq!(range, vec![1, 2, 3]); + } + + #[test] + fn test_impl_trait_format_pair() { + assert_eq!(format_pair(3, 4).to_string(), "3 + 4 = 7"); + } + + #[test] + fn test_impl_trait_try_iter_scores() { + let calc = Calculator::new(5); + let scores: Vec = calc.try_iter_scores().unwrap().collect(); + assert_eq!(scores, vec![5, 10, 15]); + + let neg = Calculator::new(-1); + assert!(neg.try_iter_scores().is_err()); + } +} + +include!(concat!(env!("OUT_DIR"), "/kne_bridges.rs")); diff --git a/examples/rust-calculator/src/jvmMain/kotlin/com/example/rustcalculator/Main.kt b/examples/rust-calculator/src/jvmMain/kotlin/com/example/rustcalculator/Main.kt new file mode 100644 index 00000000..9cf67308 --- /dev/null +++ b/examples/rust-calculator/src/jvmMain/kotlin/com/example/rustcalculator/Main.kt @@ -0,0 +1,142 @@ +package com.example.rustcalculator + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.darkColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Rust Calculator (via FFM)", + state = rememberWindowState(width = 360.dp, height = 660.dp), + resizable = false, + ) { + MaterialTheme(colors = darkColors()) { + Surface(modifier = Modifier.fillMaxSize()) { + CalculatorScreen() + } + } + } +} + +@Composable +fun CalculatorScreen() { + var calc by remember { mutableStateOf(Calculator(0)) } + var accumulator by remember { mutableStateOf(0.0) } + var display by remember { mutableStateOf("0") } + var pendingOp by remember { mutableStateOf(null) } + var inputBuffer by remember { mutableStateOf("") } + + fun formatResult(v: Double): String = + if (v == v.toLong().toDouble()) v.toLong().toString() else v.toBigDecimal().stripTrailingZeros().toPlainString() + + fun refresh() { display = formatResult(accumulator) } + fun onDigit(d: String) { inputBuffer += d; display = inputBuffer } + fun applyOp() { + if (inputBuffer.isEmpty()) return + val value = inputBuffer.toDoubleOrNull() ?: return + inputBuffer = "" + when (pendingOp) { + "+" -> accumulator += value + "-" -> accumulator -= value + "x" -> accumulator *= value + "/" -> if (value == 0.0) { display = "Error: division by 0"; return } else accumulator /= value + null -> accumulator = value + } + // Sync Rust calculator with the integer part + calc.close(); calc = Calculator(accumulator.toInt()) + refresh() + } + fun onOperator(op: String) { applyOp(); pendingOp = op } + fun onEquals() { applyOp(); pendingOp = null } + fun onClear() { + calc.close(); calc = Calculator(0) + accumulator = 0.0; display = "0"; inputBuffer = ""; pendingOp = null + } + + Column(modifier = Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.SpaceBetween) { + Text( + "Powered by Rust via FFM", + fontSize = 11.sp, color = Color(0xFFFF9800), + modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(8.dp)) + + Surface(color = Color(0xFF1E1E1E), shape = MaterialTheme.shapes.medium, modifier = Modifier.fillMaxWidth()) { + Text( + display, fontSize = 48.sp, fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Light, color = Color.White, textAlign = TextAlign.End, + maxLines = 1, modifier = Modifier.padding(horizontal = 20.dp, vertical = 24.dp), + ) + } + Spacer(Modifier.height(16.dp)) + + val buttons = listOf( + listOf("C", "/"), + listOf("7", "8", "9", "x"), + listOf("4", "5", "6", "-"), + listOf("1", "2", "3", "+"), + listOf("0", "="), + ) + buttons.forEach { row -> + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + row.forEach { label -> + val isOp = label in listOf("+", "-", "x", "/") + val isEquals = label == "=" + Button( + onClick = { + when { + label == "C" -> onClear() + label == "=" -> onEquals() + isOp -> onOperator(label) + else -> onDigit(label) + } + }, + colors = ButtonDefaults.buttonColors( + backgroundColor = when { + isEquals -> Color(0xFFFF9800) + isOp -> Color(0xFF424242) + label == "C" -> Color(0xFF616161) + else -> Color(0xFF303030) + }, + ), + modifier = Modifier.weight(if (label == "0") 2f else 1f).height(64.dp), + ) { + Text(label, fontSize = 22.sp, color = Color.White, + fontWeight = if (isOp || isEquals) FontWeight.Bold else FontWeight.Normal) + } + } + } + Spacer(Modifier.height(8.dp)) + } + + Text(calc.describe(), fontSize = 12.sp, color = Color.Gray, + modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CallbackTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CallbackTest.kt new file mode 100644 index 00000000..07a87208 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CallbackTest.kt @@ -0,0 +1,583 @@ +package com.example.rustcalculator + +import kotlinx.coroutines.test.runTest +import java.util.concurrent.atomic.AtomicInteger +import java.util.Collections +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class CallbackTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Synchronous callbacks (fn() params) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `transform_and_sum doubles values`() { + Calculator(0).use { calc -> + val result = calc.transform_and_sum(listOf(1, 2, 3)) { it * 2 } + assertEquals(12, result) // 2 + 4 + 6 + } + } + + @Test fun `transform_and_sum squares values`() { + Calculator(0).use { calc -> + val result = calc.transform_and_sum(listOf(3, 4, 5)) { it * it } + assertEquals(50, result) // 9 + 16 + 25 + } + } + + @Test fun `transform_and_sum with empty array`() { + Calculator(0).use { calc -> + val result = calc.transform_and_sum(emptyList()) { it } + assertEquals(0, result) + } + } + + @Test fun `for_each_score calls callback N times`() { + Calculator(10).use { calc -> + val values = Collections.synchronizedList(mutableListOf()) + calc.for_each_score(3) { values.add(it) } + assertEquals(3, values.size) + assertEquals(listOf(10, 20, 30), values) + } + } + + @Test fun `for_each_score with zero count`() { + Calculator(5).use { calc -> + val counter = AtomicInteger(0) + calc.for_each_score(0) { counter.incrementAndGet() } + assertEquals(0, counter.get()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Suspend + callback combo (event loop pattern) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `run_tick_loop calls on_tick from background thread`() = runTest { + Calculator(100).use { calc -> + val values = Collections.synchronizedList(mutableListOf()) + calc.run_tick_loop(5, 10) { values.add(it) } + assertEquals(5, values.size) + // accumulator=100, so values are 101, 102, 103, 104, 105 + assertEquals(listOf(101, 102, 103, 104, 105), values) + } + } + + @Test fun `run_tick_loop with accumulator zero`() = runTest { + Calculator(0).use { calc -> + val sum = AtomicInteger(0) + calc.run_tick_loop(3, 10) { sum.addAndGet(it) } + assertEquals(6, sum.get()) // 1 + 2 + 3 + } + } + + // ════════════════════════════════════════════════════════════���══════════════ + // Edge cases + // ═════════���═════════════��═══════════════════════════════════════════════════ + + @Test fun `edge cb - transform_and_sum with identity`() { + Calculator(0).use { calc -> + val result = calc.transform_and_sum(listOf(1, 2, 3)) { it } + assertEquals(6, result) + } + } + + @Test fun `edge cb - transform_and_sum with negative values`() { + Calculator(0).use { calc -> + val result = calc.transform_and_sum(listOf(-1, -2, -3)) { it * 2 } + assertEquals(-12, result) + } + } + + @Test fun `edge cb - transform_and_sum with MAX_VALUE`() { + Calculator(0).use { calc -> + val result = calc.transform_and_sum(listOf(Int.MAX_VALUE)) { it } + assertEquals(Int.MAX_VALUE, result) + } + } + + @Test fun `edge cb - for_each_score captures all values`() { + Calculator(7).use { calc -> + val values = Collections.synchronizedList(mutableListOf()) + calc.for_each_score(5) { values.add(it) } + assertEquals(listOf(7, 14, 21, 28, 35), values) + } + } + + @Test fun `edge cb - transform_and_sum with large list`() { + Calculator(0).use { calc -> + val big = (1..1000).toList() + val result = calc.transform_and_sum(big) { it } + assertEquals(500500, result) // sum 1..1000 + } + } + + // ═══════════��═══════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K transform_and_sum calls`() { + Calculator(0).use { calc -> + repeat(100_000) { + val result = calc.transform_and_sum(listOf(1, 2, 3)) { it * 2 } + assertEquals(12, result) + } + } + } + + @Test fun `load - 100K for_each_score calls`() { + Calculator(1).use { calc -> + val counter = AtomicInteger(0) + repeat(100_000) { + calc.for_each_score(1) { counter.incrementAndGet() } + } + assertEquals(100_000, counter.get()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═════════════════════════════════════════════════��═════════════════════════ + + @Test fun `concurrent - 10 threads x 10K transform_and_sum`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(10_000) { + val result = calc.transform_and_sum(listOf(tid, tid)) { it * 2 } + assertEquals(tid * 4, result) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K for_each_score`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + val values = Collections.synchronizedList(mutableListOf()) + calc.for_each_score(1) { values.add(it) } + assertEquals(1, values.size) + assertEquals(tid, values[0]) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Callbacks returning sealed enum (CalcResult) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `cb sealed - map_to_result returns Value variant`() { + Calculator(0).use { calc -> + calc.map_to_result(42) { v -> CalcResult.value(v) }.use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(42, (result as CalcResult.Value).value) + } + } + } + + @Test fun `cb sealed - map_to_result returns Error variant`() { + Calculator(0).use { calc -> + calc.map_to_result(-1) { _ -> CalcResult.error("negative") }.use { result -> + assertTrue(result is CalcResult.Error) + assertEquals("negative", (result as CalcResult.Error).value) + } + } + } + + @Test fun `cb sealed - map_to_result returns Partial variant`() { + Calculator(0).use { calc -> + calc.map_to_result(7) { v -> CalcResult.partial(v, 0.75) }.use { result -> + assertTrue(result is CalcResult.Partial) + val partial = result as CalcResult.Partial + assertEquals(7, partial.value) + assertEquals(0.75, partial.confidence, 0.001) + } + } + } + + @Test fun `cb sealed - map_to_result returns Nothing variant`() { + Calculator(0).use { calc -> + calc.map_to_result(0) { _ -> CalcResult.nothing() }.use { result -> + assertTrue(result is CalcResult.Nothing) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Callbacks returning object (Calculator) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `cb obj - create_via_callback returns new Calculator`() { + Calculator(10).use { calc -> + calc.create_via_callback { initial -> Calculator(initial * 2) }.use { created -> + assertEquals(20, created.current) + } + } + } + + @Test fun `cb obj - create_via_callback with zero`() { + Calculator(0).use { calc -> + calc.create_via_callback { initial -> Calculator(initial) }.use { created -> + assertEquals(0, created.current) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Callbacks taking object param (Calculator) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `cb obj param - apply_to_clone reads accumulator`() { + Calculator(42).use { calc -> + val result = calc.apply_to_clone { c -> + c.use { it.current } + } + assertEquals(42, result) + } + } + + @Test fun `cb obj param - apply_to_clone with mutation`() { + Calculator(10).use { calc -> + val result = calc.apply_to_clone { c -> + c.use { + it.add(5) + it.current + } + } + assertEquals(15, result) + // Original calculator should not be affected + assertEquals(10, calc.current) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Callbacks taking sealed enum param (CalcResult) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `cb sealed param - format_result with Value`() { + Calculator(0).use { calc -> + CalcResult.value(99).use { input -> + val result = calc.format_result(input) { r -> + r.use { + when (it) { + is CalcResult.Value -> "got ${it.value}" + else -> "other" + } + } + } + assertEquals("got 99", result) + } + } + } + + @Test fun `cb sealed param - format_result with Error`() { + Calculator(0).use { calc -> + CalcResult.error("boom").use { input -> + val result = calc.format_result(input) { r -> + r.use { + when (it) { + is CalcResult.Error -> "err: ${it.value}" + else -> "other" + } + } + } + assertEquals("err: boom", result) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases for handle-backed callbacks + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge cb sealed - map_to_result with MAX_VALUE`() { + Calculator(0).use { calc -> + calc.map_to_result(Int.MAX_VALUE) { v -> CalcResult.value(v) }.use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(Int.MAX_VALUE, (result as CalcResult.Value).value) + } + } + } + + @Test fun `edge cb sealed - map_to_result with MIN_VALUE`() { + Calculator(0).use { calc -> + calc.map_to_result(Int.MIN_VALUE) { v -> CalcResult.value(v) }.use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(Int.MIN_VALUE, (result as CalcResult.Value).value) + } + } + } + + @Test fun `edge cb sealed param - format_result with empty error string`() { + Calculator(0).use { calc -> + CalcResult.error("").use { input -> + val result = calc.format_result(input) { r -> + r.use { + when (it) { + is CalcResult.Error -> "err:[${it.value}]" + else -> "other" + } + } + } + assertEquals("err:[]", result) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests for handle-backed callbacks + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 1K map_to_result calls`() { + Calculator(0).use { calc -> + repeat(1_000) { i -> + calc.map_to_result(i) { v -> CalcResult.value(v) }.use { result -> + assertTrue(result is CalcResult.Value) + } + } + } + } + + @Test fun `load - 1K create_via_callback calls`() { + Calculator(1).use { calc -> + repeat(1_000) { + calc.create_via_callback { v -> Calculator(v) }.use { created -> + assertEquals(1, created.current) + } + } + } + } + + @Test fun `load - 1K apply_to_clone calls`() { + Calculator(5).use { calc -> + repeat(1_000) { + val result = calc.apply_to_clone { c -> c.use { it.current } } + assertEquals(5, result) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests for handle-backed callbacks + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 1K map_to_result`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(1_000) { + calc.map_to_result(tid) { v -> CalcResult.value(v) }.use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(tid, (result as CalcResult.Value).value) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 1K create_via_callback`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(1_000) { + calc.create_via_callback { v -> Calculator(v) }.use { created -> + assertEquals(tid, created.current) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Nullable callbacks (Option) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `cb nullable - maybe_transform with Some`() { + Calculator(10).use { calc -> + val result = calc.maybe_transform { it * 3 } + assertEquals(30, result) + } + } + + @Test fun `cb nullable - maybe_transform with null returns accumulator`() { + Calculator(42).use { calc -> + val result = calc.maybe_transform(null) + assertEquals(42, result) + } + } + + @Test fun `cb nullable - maybe_for_each with Some`() { + Calculator(5).use { calc -> + val values = Collections.synchronizedList(mutableListOf()) + calc.maybe_for_each(3) { values.add(it) } + assertEquals(listOf(5, 10, 15), values) + } + } + + @Test fun `cb nullable - maybe_for_each with null does nothing`() { + Calculator(5).use { calc -> + calc.maybe_for_each(3, null) // should not crash + } + } + + @Test fun `cb nullable - compute_with_observer with Some`() { + Calculator(10).use { calc -> + var observed: String? = null + val result = calc.compute_with_observer(5) { observed = it } + assertEquals(15, result) + assertEquals("computed: 15", observed) + } + } + + @Test fun `cb nullable - compute_with_observer with null`() { + Calculator(10).use { calc -> + val result = calc.compute_with_observer(5, null) + assertEquals(15, result) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Nullable callback edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge cb nullable - maybe_transform with identity`() { + Calculator(Int.MAX_VALUE).use { calc -> + val result = calc.maybe_transform { it } + assertEquals(Int.MAX_VALUE, result) + } + } + + @Test fun `edge cb nullable - maybe_transform with MIN_VALUE`() { + Calculator(Int.MIN_VALUE).use { calc -> + val result = calc.maybe_transform { it + 1 } + assertEquals(Int.MIN_VALUE + 1, result) + } + } + + @Test fun `edge cb nullable - maybe_for_each with zero count`() { + Calculator(5).use { calc -> + val counter = AtomicInteger(0) + calc.maybe_for_each(0) { counter.incrementAndGet() } + assertEquals(0, counter.get()) + } + } + + @Test fun `edge cb nullable - compute_with_observer with empty string result`() { + Calculator(0).use { calc -> + var observed: String? = null + calc.compute_with_observer(0) { observed = it } + assertEquals("computed: 0", observed) + } + } + + @Test fun `edge cb nullable - alternate null and non-null`() { + Calculator(7).use { calc -> + assertEquals(7, calc.maybe_transform(null)) + assertEquals(14, calc.maybe_transform { it * 2 }) + assertEquals(7, calc.maybe_transform(null)) + assertEquals(49, calc.maybe_transform { it * it }) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests for nullable callbacks + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K maybe_transform with Some`() { + Calculator(3).use { calc -> + repeat(100_000) { + assertEquals(9, calc.maybe_transform { it * 3 }) + } + } + } + + @Test fun `load - 100K maybe_transform with null`() { + Calculator(3).use { calc -> + repeat(100_000) { + assertEquals(3, calc.maybe_transform(null)) + } + } + } + + @Test fun `load - 100K compute_with_observer alternating`() { + Calculator(1).use { calc -> + repeat(100_000) { i -> + if (i % 2 == 0) { + calc.compute_with_observer(i, null) + } else { + calc.compute_with_observer(i) { /* discard */ } + } + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests for nullable callbacks + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K maybe_transform`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + assertEquals(tid * 2, calc.maybe_transform { it * 2 }) + assertEquals(tid, calc.maybe_transform(null)) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K compute_with_observer`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { i -> + val result = if (i % 2 == 0) { + calc.compute_with_observer(1, null) + } else { + calc.compute_with_observer(1) { /* discard */ } + } + assertEquals(tid + 1, result) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 1K apply_to_clone`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(1_000) { + val result = calc.apply_to_clone { c -> c.use { it.current } } + assertEquals(tid, result) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CollectionTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CollectionTest.kt new file mode 100644 index 00000000..ea25473c --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CollectionTest.kt @@ -0,0 +1,510 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.assertFalse + +class CollectionTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // List return + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_recent_scores returns 3 elements`() { + Calculator(5).use { calc -> + val scores = calc.get_recent_scores() + assertEquals(3, scores.size) + assertEquals(5, scores[0]) + assertEquals(10, scores[1]) + assertEquals(15, scores[2]) + } + } + + @Test fun `get_recent_scores with zero`() { + Calculator(0).use { calc -> + val scores = calc.get_recent_scores() + assertEquals(listOf(0, 0, 0), scores) + } + } + + @Test fun `get_recent_scores after mutation`() { + Calculator(0).use { calc -> + calc.add(10) + val scores = calc.get_recent_scores() + assertEquals(listOf(10, 20, 30), scores) + } + } + + @Test fun `edge coll - get_recent_scores with negative`() { + Calculator(-5).use { calc -> + assertEquals(listOf(-5, -10, -15), calc.get_recent_scores()) + } + } + + @Test fun `edge coll - get_recent_scores with MAX_VALUE`() { + Calculator(Int.MAX_VALUE).use { calc -> + val scores = calc.get_recent_scores() + assertEquals(Int.MAX_VALUE, scores[0]) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // List return + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_score_names returns formatted strings`() { + Calculator(5).use { calc -> + val names = calc.get_score_names() + assertEquals(3, names.size) + assertEquals("score_5", names[0]) + assertEquals("score_10", names[1]) + assertEquals("score_15", names[2]) + } + } + + @Test fun `get_score_names with zero`() { + Calculator(0).use { calc -> + val names = calc.get_score_names() + assertEquals(listOf("score_0", "score_0", "score_0"), names) + } + } + + @Test fun `get_score_names with negative`() { + Calculator(-3).use { calc -> + val names = calc.get_score_names() + assertEquals("score_-3", names[0]) + assertEquals("score_-6", names[1]) + assertEquals("score_-9", names[2]) + } + } + + @Test fun `edge coll - get_score_names with MAX_VALUE`() { + Calculator(Int.MAX_VALUE).use { calc -> + val names = calc.get_score_names() + assertTrue(names[0].contains("${Int.MAX_VALUE}")) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // List return + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_ratios returns doubles`() { + Calculator(12).use { calc -> + val ratios = calc.get_ratios() + assertEquals(3, ratios.size) + assertEquals(12.0, ratios[0], 0.001) + assertEquals(6.0, ratios[1], 0.001) + assertEquals(4.0, ratios[2], 0.001) + } + } + + @Test fun `get_ratios with zero`() { + Calculator(0).use { calc -> + val ratios = calc.get_ratios() + assertEquals(0.0, ratios[0], 0.001) + assertEquals(0.0, ratios[1], 0.001) + assertEquals(0.0, ratios[2], 0.001) + } + } + + @Test fun `get_ratios with negative`() { + Calculator(-6).use { calc -> + val ratios = calc.get_ratios() + assertEquals(-6.0, ratios[0], 0.001) + assertEquals(-3.0, ratios[1], 0.001) + assertEquals(-2.0, ratios[2], 0.001) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // List return + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_flags returns booleans`() { + Calculator(50).use { calc -> + val flags = calc.get_flags() + assertEquals(3, flags.size) + assertTrue(flags[0]) // 50 > 0 + assertTrue(flags[1]) // 50 > 10 + assertFalse(flags[2]) // 50 > 100 + } + } + + @Test fun `get_flags with zero`() { + Calculator(0).use { calc -> + val flags = calc.get_flags() + assertFalse(flags[0]) // 0 > 0 + assertFalse(flags[1]) // 0 > 10 + assertFalse(flags[2]) // 0 > 100 + } + } + + @Test fun `get_flags with 200`() { + Calculator(200).use { calc -> + val flags = calc.get_flags() + assertTrue(flags[0]) + assertTrue(flags[1]) + assertTrue(flags[2]) + } + } + + @Test fun `get_flags with negative`() { + Calculator(-1).use { calc -> + val flags = calc.get_flags() + assertFalse(flags[0]) + assertFalse(flags[1]) + assertFalse(flags[2]) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // ByteArray return + param + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `to_bytes returns accumulator as byte string`() { + Calculator(42).use { calc -> + val bytes = calc.to_bytes() + assertEquals("42", String(bytes)) + } + } + + @Test fun `to_bytes with zero`() { + Calculator(0).use { calc -> + assertEquals("0", String(calc.to_bytes())) + } + } + + @Test fun `to_bytes with negative`() { + Calculator(-123).use { calc -> + assertEquals("-123", String(calc.to_bytes())) + } + } + + @Test fun `sum_bytes sums all byte values`() { + Calculator(0).use { calc -> + val result = calc.sum_bytes(byteArrayOf(1, 2, 3, 4, 5)) + assertEquals(15, result) + assertEquals(15, calc.current) + } + } + + @Test fun `sum_bytes with empty array`() { + Calculator(0).use { calc -> + val result = calc.sum_bytes(byteArrayOf()) + assertEquals(0, result) + } + } + + @Test fun `reverse_bytes reverses array`() { + Calculator(0).use { calc -> + val reversed = calc.reverse_bytes(byteArrayOf(1, 2, 3, 4, 5)) + assertEquals(listOf(5, 4, 3, 2, 1), reversed.toList()) + } + } + + @Test fun `reverse_bytes empty array`() { + Calculator(0).use { calc -> + val reversed = calc.reverse_bytes(byteArrayOf()) + assertTrue(reversed.isEmpty()) + } + } + + @Test fun `reverse_bytes single element`() { + Calculator(0).use { calc -> + val reversed = calc.reverse_bytes(byteArrayOf(42)) + assertEquals(1, reversed.size) + assertEquals(42.toByte(), reversed[0]) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Borrowed slice return (&[u8] -> ByteArray) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `label_bytes returns borrowed slice as ByteArray`() { + Calculator(0).use { calc -> + calc.label = "hello" + val bytes = calc.label_bytes() + assertEquals("hello", String(bytes)) + } + } + + @Test fun `label_bytes with empty label`() { + Calculator(0).use { calc -> + val bytes = calc.label_bytes() + assertTrue(bytes.isEmpty()) + } + } + + @Test fun `label_bytes with unicode`() { + Calculator(0).use { calc -> + calc.label = "日本語 🎉" + val bytes = calc.label_bytes() + assertEquals("日本語 🎉", String(bytes, Charsets.UTF_8)) + } + } + + @Test fun `label_bytes with long string`() { + Calculator(0).use { calc -> + val longLabel = "x".repeat(10_000) + calc.label = longLabel + val bytes = calc.label_bytes() + assertEquals(10_000, bytes.size) + assertEquals(longLabel, String(bytes)) + } + } + + @Test fun `label_bytes reflects latest label`() { + Calculator(0).use { calc -> + calc.label = "first" + assertEquals("first", String(calc.label_bytes())) + calc.label = "second" + assertEquals("second", String(calc.label_bytes())) + } + } + + @Test fun `load - 100K label_bytes calls`() { + Calculator(0).use { calc -> + calc.label = "test" + repeat(100_000) { + val bytes = calc.label_bytes() + assertEquals(4, bytes.size) + } + } + } + + @Test fun `concurrent - 10 threads x 10K label_bytes`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + calc.label = "t$tid" + repeat(10_000) { + val bytes = calc.label_bytes() + assertEquals("t$tid", String(bytes)) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Top-level functions with collections + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `sum_all adds all elements`() { + assertEquals(15, Rustcalc.sum_all(listOf(1, 2, 3, 4, 5))) + } + + @Test fun `sum_all empty list`() { + assertEquals(0, Rustcalc.sum_all(emptyList())) + } + + @Test fun `sum_all single element`() { + assertEquals(42, Rustcalc.sum_all(listOf(42))) + } + + @Test fun `find_max returns max element`() { + assertEquals(5, Rustcalc.find_max(listOf(1, 5, 3))) + } + + @Test fun `find_max empty list returns null`() { + assertEquals(null, Rustcalc.find_max(emptyList())) + } + + @Test fun `find_max single element`() { + assertEquals(42, Rustcalc.find_max(listOf(42))) + } + + @Test fun `find_max with negatives`() { + assertEquals(-1, Rustcalc.find_max(listOf(-3, -1, -5))) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Option> return + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_optional_scores returns Some when accumulator is positive`() { + Calculator(5).use { calc -> + val scores = calc.get_optional_scores() + assertEquals(listOf(5, 10, 15), scores) + } + } + + @Test fun `get_optional_scores returns null when accumulator is zero`() { + Calculator(0).use { calc -> + assertEquals(null, calc.get_optional_scores()) + } + } + + @Test fun `get_optional_tags returns Some when label is set`() { + Calculator(1).use { calc -> + calc.label = "test" + val tags = calc.get_optional_tags() + assertEquals(listOf("test", "scale:1"), tags) + } + } + + @Test fun `get_optional_tags returns null when label is empty`() { + Calculator(1).use { calc -> + assertEquals(null, calc.get_optional_tags()) + } + } + + @Test fun `get_optional_metadata returns Some when accumulator is not zero`() { + Calculator(10).use { calc -> + val metadata = calc.get_optional_metadata() + assertEquals(mapOf("current" to 10, "scale" to 1), metadata) + } + } + + @Test fun `get_optional_metadata returns null when accumulator is zero`() { + Calculator(0).use { calc -> + assertEquals(null, calc.get_optional_metadata()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K get_recent_scores calls`() { + Calculator(1).use { calc -> + repeat(100_000) { + val scores = calc.get_recent_scores() + assertEquals(3, scores.size) + } + } + } + + @Test fun `load - 100K get_score_names calls`() { + Calculator(1).use { calc -> + repeat(100_000) { + val names = calc.get_score_names() + assertEquals(3, names.size) + } + } + } + + @Test fun `load - 100K get_ratios calls`() { + Calculator(1).use { calc -> + repeat(100_000) { + val ratios = calc.get_ratios() + assertEquals(3, ratios.size) + } + } + } + + @Test fun `load - 100K get_flags calls`() { + Calculator(1).use { calc -> + repeat(100_000) { + val flags = calc.get_flags() + assertEquals(3, flags.size) + } + } + } + + @Test fun `load - 100K to_bytes calls`() { + Calculator(42).use { calc -> + repeat(100_000) { + val bytes = calc.to_bytes() + assertEquals("42", String(bytes)) + } + } + } + + @Test fun `load - 100K sum_all calls`() { + repeat(100_000) { + assertEquals(6, Rustcalc.sum_all(listOf(1, 2, 3))) + } + } + + @Test fun `load - 100K get_optional_scores calls`() { + Calculator(1).use { calc -> + repeat(100_000) { + val scores = calc.get_optional_scores() + assertEquals(3, scores!!.size) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K get_recent_scores`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + val scores = calc.get_recent_scores() + assertEquals(tid, scores[0]) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K get_score_names`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + val names = calc.get_score_names() + assertTrue(names[0].contains("$tid")) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K get_ratios`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + val ratios = calc.get_ratios() + assertEquals(tid.toDouble(), ratios[0], 0.001) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K get_flags`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid * 100).use { calc -> + repeat(10_000) { + val flags = calc.get_flags() + assertTrue(flags[0]) // always > 0 + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K sum_all`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + val result = Rustcalc.sum_all(listOf(tid, tid)) + assertEquals(tid * 2, result) + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CoreTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CoreTest.kt new file mode 100644 index 00000000..da6d1a8a --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/CoreTest.kt @@ -0,0 +1,438 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.assertFailsWith + +class CoreTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Constructor + Int arithmetic + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `constructor with Int parameter`() { + Calculator(42).use { calc -> assertEquals(42, calc.current) } + } + + @Test fun `constructor with zero`() { + Calculator(0).use { calc -> assertEquals(0, calc.current) } + } + + @Test fun `add returns accumulated value`() { + Calculator(0).use { calc -> + assertEquals(5, calc.add(5)) + assertEquals(8, calc.add(3)) + assertEquals(8, calc.current) + } + } + + @Test fun `add with negative values`() { + Calculator(10).use { calc -> assertEquals(7, calc.add(-3)) } + } + + @Test fun `subtract returns accumulated value`() { + Calculator(10).use { calc -> + assertEquals(7, calc.subtract(3)) + assertEquals(7, calc.current) + } + } + + @Test fun `multiply returns accumulated value`() { + Calculator(4).use { calc -> assertEquals(12, calc.multiply(3)) } + } + + @Test fun `multiply by zero`() { + Calculator(100).use { calc -> assertEquals(0, calc.multiply(0)) } + } + + @Test fun `reset clears accumulator`() { + Calculator(0).use { calc -> + calc.add(42) + calc.reset() + assertEquals(0, calc.current) + } + } + + @Test fun `val property current reads correctly`() { + Calculator(99).use { calc -> + assertEquals(99, calc.current) + calc.add(1) + assertEquals(100, calc.current) + } + } + + @Test fun `chain multiple operations`() { + Calculator(0).use { calc -> + calc.add(10) + calc.multiply(3) + calc.subtract(5) + assertEquals(25, calc.current) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // String type: param, return, edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `describe returns formatted string`() { + Calculator(7).use { calc -> assertEquals("Calculator(current=7)", calc.describe()) } + } + + @Test fun `echo returns same string`() { + Calculator(0).use { calc -> assertEquals("hello", calc.echo("hello")) } + } + + @Test fun `echo empty string`() { + Calculator(0).use { calc -> assertEquals("", calc.echo("")) } + } + + @Test fun `echo unicode string`() { + Calculator(0).use { calc -> assertEquals("café ☕ 日本語", calc.echo("café ☕ 日本語")) } + } + + @Test fun `concat two strings`() { + Calculator(0).use { calc -> assertEquals("helloworld", calc.concat("hello", "world")) } + } + + @Test fun `concat with empty strings`() { + Calculator(0).use { calc -> + assertEquals("hello", calc.concat("hello", "")) + assertEquals("world", calc.concat("", "world")) + assertEquals("", calc.concat("", "")) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // All primitive types: Long, Double, Float, Short, Byte, Boolean + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `Long param and return`() { + Calculator(10).use { calc -> assertEquals(15L, calc.add_long(5L)) } + } + + @Test fun `Long with large values`() { + Calculator(0).use { calc -> assertEquals(1_000_000L, calc.add_long(1_000_000L)) } + } + + @Test fun `Double param and return`() { + Calculator(10).use { calc -> assertEquals(13.5, calc.add_double(3.5), 0.001) } + } + + @Test fun `Float param and return`() { + Calculator(10).use { calc -> assertEquals(12.5f, calc.add_float(2.5f), 0.01f) } + } + + @Test fun `Short param and return`() { + Calculator(10).use { calc -> assertEquals(15.toShort(), calc.add_short(5.toShort())) } + } + + @Test fun `Byte param and return`() { + Calculator(10).use { calc -> assertEquals(13.toByte(), calc.add_byte(3.toByte())) } + } + + @Test fun `Boolean return true`() { + Calculator(5).use { calc -> assertTrue(calc.is_positive()) } + } + + @Test fun `Boolean return false`() { + Calculator(0).use { calc -> assertFalse(calc.is_positive()) } + } + + @Test fun `Boolean param true`() { + Calculator(5).use { calc -> assertTrue(calc.check_flag(true)) } + } + + @Test fun `Boolean param false`() { + Calculator(5).use { calc -> assertFalse(calc.check_flag(false)) } + } + + @Test fun `Boolean both false when accumulator zero`() { + Calculator(0).use { calc -> assertFalse(calc.check_flag(true)) } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Exception propagation + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `divide works normally`() { + Calculator(10).use { calc -> + assertEquals(5, calc.divide(2)) + assertEquals(5, calc.current) + } + } + + @Test fun `divide by zero throws KotlinNativeException`() { + Calculator(10).use { calc -> + val ex = assertFailsWith { calc.divide(0) } + assertTrue(ex.message!!.contains("Division by zero"), "Expected 'Division by zero' but got: ${ex.message}") + } + } + + @Test fun `calculator works normally after exception`() { + Calculator(10).use { calc -> + assertFailsWith { calc.divide(0) } + assertEquals(15, calc.add(5)) + assertEquals(15, calc.current) + } + } + + @Test fun `multiple exceptions in sequence`() { + Calculator(10).use { calc -> + assertFailsWith { calc.divide(0) } + assertFailsWith { calc.divide(0) } + assertEquals(5, calc.divide(2)) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // &mut self + Result combination (try_divide_result) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `try_divide_result succeeds and mutates`() { + Calculator(10).use { calc -> + assertEquals(5, calc.try_divide_result(2)) + assertEquals(5, calc.current) + } + } + + @Test fun `try_divide_result by zero throws`() { + Calculator(10).use { calc -> + assertFailsWith { calc.try_divide_result(0) } + assertEquals(10, calc.current) + } + } + + @Test fun `try_divide_result recovers after error`() { + Calculator(10).use { calc -> + assertFailsWith { calc.try_divide_result(0) } + assertEquals(5, calc.try_divide_result(2)) + assertEquals(5, calc.current) + } + } + + @Test fun `load - 100K try_divide_result calls`() { + Calculator(1_000_000).use { calc -> + repeat(100_000) { + calc.try_divide_result(1) + } + assertEquals(1_000_000, calc.current) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Mutable properties (var) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `var String property set and get`() { + Calculator(0).use { calc -> + assertEquals("", calc.label) + calc.label = "test" + assertEquals("test", calc.label) + } + } + + @Test fun `var String property unicode`() { + Calculator(0).use { calc -> + calc.label = "日本語テスト" + assertEquals("日本語テスト", calc.label) + } + } + + @Test fun `var Double property set and get`() { + Calculator(0).use { calc -> + assertEquals(1.0, calc.scale, 0.001) + calc.scale = 2.5 + assertEquals(2.5, calc.scale, 0.001) + } + } + + @Test fun `var Boolean property set and get`() { + Calculator(0).use { calc -> + assertTrue(calc.enabled) + calc.enabled = false + assertFalse(calc.enabled) + calc.enabled = true + assertTrue(calc.enabled) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge prim - Int MAX_VALUE`() { + Calculator(Int.MAX_VALUE).use { calc -> assertEquals(Int.MAX_VALUE, calc.current) } + } + + @Test fun `edge prim - Int MIN_VALUE`() { + Calculator(Int.MIN_VALUE).use { calc -> assertEquals(Int.MIN_VALUE, calc.current) } + } + + @Test fun `edge str - very long string`() { + Calculator(0).use { calc -> + val long = "A".repeat(10_000) + assertEquals(long, calc.echo(long)) + } + } + + @Test fun `edge str - emoji string`() { + Calculator(0).use { calc -> + assertEquals("🔥💯🚀🎉", calc.echo("🔥💯🚀🎉")) + } + } + + @Test fun `edge str - null char in string`() { + Calculator(0).use { calc -> + calc.label = "before" + assertEquals("before", calc.label) + } + } + + @Test fun `edge prim - Long MAX_VALUE`() { + Calculator(0).use { calc -> + assertEquals(Long.MAX_VALUE, calc.add_long(Long.MAX_VALUE)) + } + } + + @Test fun `edge prim - Double NaN`() { + Calculator(0).use { calc -> + val result = calc.add_double(Double.NaN) + assertTrue(result.isNaN()) + } + } + + @Test fun `edge prim - Double Infinity`() { + Calculator(0).use { calc -> + val result = calc.add_double(Double.POSITIVE_INFINITY) + assertEquals(Double.POSITIVE_INFINITY, result) + } + } + + @Test fun `edge exc - fail_always throws`() { + Calculator(0).use { calc -> + val ex = assertFailsWith { calc.fail_always() } + assertTrue(ex.message!!.contains("Intentional error")) + } + } + + @Test fun `edge exc - panic_always throws`() { + Calculator(0).use { calc -> + assertFailsWith { calc.panic_always() } + } + } + + @Test fun `edge obj - lifecycle create use close`() { + repeat(100) { i -> + Calculator(i).use { calc -> + assertEquals(i, calc.current) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K add calls single instance`() { + Calculator(0).use { calc -> + repeat(100_000) { calc.add(1) } + assertEquals(100_000, calc.current) + } + } + + @Test fun `load - 100K describe calls`() { + Calculator(42).use { calc -> + repeat(100_000) { + assertEquals("Calculator(current=42)", calc.describe()) + } + } + } + + @Test fun `load - 100K echo calls`() { + Calculator(0).use { calc -> + repeat(100_000) { + assertEquals("test", calc.echo("test")) + } + } + } + + @Test fun `load - 100K property reads`() { + Calculator(42).use { calc -> + repeat(100_000) { + assertEquals(42, calc.current) + } + } + } + + @Test fun `load - 100K label set and get`() { + Calculator(0).use { calc -> + repeat(100_000) { + calc.label = "x" + assertEquals("x", calc.label) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K add on separate instances`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(10_000) { calc.add(1) } + assertEquals(10_000, calc.current) + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K describe`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + val desc = calc.describe() + assertTrue(desc.contains("$tid")) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K echo`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(10_000) { + assertEquals("t$tid", calc.echo("t$tid")) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K property set and get`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(10_000) { + calc.label = "t$tid" + assertEquals("t$tid", calc.label) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/DataClassTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/DataClassTest.kt new file mode 100644 index 00000000..7feebc76 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/DataClassTest.kt @@ -0,0 +1,218 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals + +class DataClassTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Point data class (x: Int, y: Int) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_point returns correct values`() { + Calculator(5).use { calc -> + val p = calc.point + assertEquals(5, p.x) + assertEquals(10, p.y) + } + } + + @Test fun `get_point with zero`() { + Calculator(0).use { calc -> + val p = calc.point + assertEquals(0, p.x) + assertEquals(0, p.y) + } + } + + @Test fun `get_point with negative`() { + Calculator(-3).use { calc -> + val p = calc.point + assertEquals(-3, p.x) + assertEquals(-6, p.y) + } + } + + @Test fun `add_point accumulates x + y`() { + Calculator(0).use { calc -> + val result = calc.add_point(Point(3, 7)) + assertEquals(10, result) + assertEquals(10, calc.current) + } + } + + @Test fun `add_point with negative values`() { + Calculator(10).use { calc -> + val result = calc.add_point(Point(-3, -2)) + assertEquals(5, result) + } + } + + @Test fun `get_point then add_point roundtrip`() { + Calculator(5).use { calc -> + val p = calc.point + calc.add_point(p) // adds 5 + 10 = 15 + assertEquals(20, calc.current) // 5 + 15 + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // NamedValue data class (name: String, value: Int) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_named_value default label`() { + Calculator(5).use { calc -> + val nv = calc.named_value + assertEquals("default", nv.name) + assertEquals(5, nv.value) + } + } + + @Test fun `get_named_value custom label`() { + Calculator(5).use { calc -> + calc.label = "myCalc" + val nv = calc.named_value + assertEquals("myCalc", nv.name) + assertEquals(5, nv.value) + } + } + + @Test fun `set_from_named updates accumulator and label`() { + Calculator(0).use { calc -> + calc.set_from_named(NamedValue("imported", 42)) + assertEquals(42, calc.current) + assertEquals("imported", calc.label) + } + } + + @Test fun `set_from_named then get_named_value roundtrip`() { + Calculator(0).use { calc -> + val nv = NamedValue("test", 99) + calc.set_from_named(nv) + val got = calc.named_value + assertEquals("test", got.name) + assertEquals(99, got.value) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge dc - Point with MAX_VALUE`() { + Calculator(Int.MAX_VALUE).use { calc -> + val p = calc.point + assertEquals(Int.MAX_VALUE, p.x) + } + } + + @Test fun `edge dc - Point with MIN_VALUE`() { + Calculator(Int.MIN_VALUE).use { calc -> + val p = calc.point + assertEquals(Int.MIN_VALUE, p.x) + } + } + + @Test fun `edge dc - add_point with zero point`() { + Calculator(42).use { calc -> + val result = calc.add_point(Point(0, 0)) + assertEquals(42, result) + } + } + + @Test fun `edge dc - NamedValue with unicode name`() { + Calculator(0).use { calc -> + calc.set_from_named(NamedValue("日本語テスト 🚀", 7)) + val got = calc.named_value + assertEquals("日本語テスト 🚀", got.name) + assertEquals(7, got.value) + } + } + + @Test fun `edge dc - NamedValue with empty name`() { + Calculator(0).use { calc -> + calc.set_from_named(NamedValue("", 0)) + val got = calc.named_value + // empty label returns "default" + assertEquals("default", got.name) + assertEquals(0, got.value) + } + } + + @Test fun `edge dc - lifecycle create use close`() { + repeat(50) { i -> + Calculator(i).use { calc -> + val p = calc.point + assertEquals(i, p.x) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K get_point calls`() { + Calculator(7).use { calc -> + repeat(100_000) { + val p = calc.point + assertEquals(7, p.x) + assertEquals(14, p.y) + } + } + } + + @Test fun `load - 100K add_point calls`() { + Calculator(0).use { calc -> + repeat(100_000) { + calc.add_point(Point(0, 0)) + } + assertEquals(0, calc.current) + } + } + + @Test fun `load - 100K get_named_value calls`() { + Calculator(1).use { calc -> + calc.label = "load" + repeat(100_000) { + val nv = calc.named_value + assertEquals("load", nv.name) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K get_point`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + val p = calc.point + assertEquals(tid, p.x) + assertEquals(tid * 2, p.y) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K set_from_named`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(10_000) { + calc.set_from_named(NamedValue("t$tid", tid)) + assertEquals(tid, calc.current) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/DataPayloadTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/DataPayloadTest.kt new file mode 100644 index 00000000..af19f520 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/DataPayloadTest.kt @@ -0,0 +1,393 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class DataPayloadTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Scores variant (LIST field) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory Scores creates variant with LIST field`() { + DataPayload.scores(listOf(1, 2, 3)).use { result -> + assertTrue(result is DataPayload.Scores) + assertEquals(listOf(1, 2, 3), result.value) + assertEquals(DataPayload.Tag.Scores, result.tag) + } + } + + @Test fun `factory Scores with empty list`() { + DataPayload.scores(emptyList()).use { result -> + assertTrue(result is DataPayload.Scores) + assertEquals(emptyList(), result.value) + } + } + + @Test fun `factory Scores with single element`() { + DataPayload.scores(listOf(42)).use { result -> + assertEquals(listOf(42), (result as DataPayload.Scores).value) + } + } + + @Test fun `factory Scores with negative values`() { + DataPayload.scores(listOf(-1, -2, -3)).use { result -> + assertEquals(listOf(-1, -2, -3), (result as DataPayload.Scores).value) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // UniqueIds variant (SET field) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory UniqueIds creates variant with SET field`() { + DataPayload.uniqueIds(setOf(10, 20, 30)).use { result -> + assertTrue(result is DataPayload.UniqueIds) + assertEquals(setOf(10, 20, 30), result.value) + assertEquals(DataPayload.Tag.UniqueIds, result.tag) + } + } + + @Test fun `factory UniqueIds with empty set`() { + DataPayload.uniqueIds(emptySet()).use { result -> + assertTrue(result is DataPayload.UniqueIds) + assertEquals(emptySet(), result.value) + } + } + + @Test fun `factory UniqueIds with single element`() { + DataPayload.uniqueIds(setOf(99)).use { result -> + assertEquals(setOf(99), (result as DataPayload.UniqueIds).value) + } + } + + @Test fun `factory UniqueIds preserves uniqueness`() { + // Pass duplicates — SET on Rust side deduplicates + DataPayload.uniqueIds(setOf(1, 2, 3)).use { result -> + assertEquals(3, (result as DataPayload.UniqueIds).value.size) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Mapping variant (MAP field) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory Mapping creates variant with MAP field`() { + DataPayload.mapping(mapOf(1 to 10, 2 to 20)).use { result -> + assertTrue(result is DataPayload.Mapping) + assertEquals(mapOf(1 to 10, 2 to 20), result.value) + assertEquals(DataPayload.Tag.Mapping, result.tag) + } + } + + @Test fun `factory Mapping with empty map`() { + DataPayload.mapping(emptyMap()).use { result -> + assertTrue(result is DataPayload.Mapping) + assertEquals(emptyMap(), result.value) + } + } + + @Test fun `factory Mapping with single entry`() { + DataPayload.mapping(mapOf(42 to 99)).use { result -> + assertEquals(mapOf(42 to 99), (result as DataPayload.Mapping).value) + } + } + + @Test fun `factory Mapping with negative keys and values`() { + DataPayload.mapping(mapOf(-1 to -10, -2 to -20)).use { result -> + assertEquals(mapOf(-1 to -10, -2 to -20), (result as DataPayload.Mapping).value) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Tags variant (LIST field) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory Tags creates variant with LIST field`() { + DataPayload.tags(listOf("hello", "world")).use { result -> + assertTrue(result is DataPayload.Tags) + assertEquals(listOf("hello", "world"), result.value) + assertEquals(DataPayload.Tag.Tags, result.tag) + } + } + + @Test fun `factory Tags with empty list`() { + DataPayload.tags(emptyList()).use { result -> + assertTrue(result is DataPayload.Tags) + assertEquals(emptyList(), result.value) + } + } + + @Test fun `factory Tags with single element`() { + DataPayload.tags(listOf("only")).use { result -> + assertEquals(listOf("only"), (result as DataPayload.Tags).value) + } + } + + @Test fun `str - Tags with empty string`() { + DataPayload.tags(listOf("")).use { result -> + assertEquals(listOf(""), (result as DataPayload.Tags).value) + } + } + + @Test fun `str - Tags with unicode emoji`() { + DataPayload.tags(listOf("hello 👋 world", "🎉")).use { result -> + assertEquals(listOf("hello 👋 world", "🎉"), (result as DataPayload.Tags).value) + } + } + + @Test fun `str - Tags with international characters`() { + DataPayload.tags(listOf("日本語", "中文", "한국어", "Ελληνικά")).use { result -> + assertEquals(listOf("日本語", "中文", "한국어", "Ελληνικά"), (result as DataPayload.Tags).value) + } + } + + @Test fun `str - Tags with special characters`() { + DataPayload.tags(listOf("a\rb\nc", "with spaces", "tabs\there")).use { result -> + assertEquals(listOf("a\rb\nc", "with spaces", "tabs\there"), (result as DataPayload.Tags).value) + } + } + + @Test fun `str - Tags with long string`() { + val longString = "x".repeat(1000) + DataPayload.tags(listOf(longString)).use { result -> + assertEquals(listOf(longString), (result as DataPayload.Tags).value) + } + } + + @Test fun `create_tags_payload returns Tags`() { + val tags = listOf("rust", "kotlin", "java") + Rustcalc.create_tags_payload(tags, tags.size).use { result -> + assertTrue(result is DataPayload.Tags) + assertEquals(listOf("rust", "kotlin", "java"), result.value) + } + } + + @Test fun `create_tags_payload with empty list`() { + val tags = emptyList() + Rustcalc.create_tags_payload(tags, 0).use { result -> + assertTrue(result is DataPayload.Tags) + assertEquals(emptyList(), result.value) + } + } + + @Test fun `str - create_tags_payload with unicode`() { + val tags = listOf("🚀", "🎯", "💻") + Rustcalc.create_tags_payload(tags, tags.size).use { result -> + assertEquals(listOf("🚀", "🎯", "💻"), (result as DataPayload.Tags).value) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Empty variant + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory Empty creates variant`() { + DataPayload.empty().use { result -> + assertTrue(result is DataPayload.Empty) + assertEquals(DataPayload.Tag.Empty, result.tag) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Return from Rust functions + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `create_scores_payload returns Scores`() { + Rustcalc.create_scores_payload(listOf(1, 2, 3)).use { result -> + assertTrue(result is DataPayload.Scores) + assertEquals(listOf(1, 2, 3), result.value) + } + } + + @Test fun `create_unique_ids_payload returns UniqueIds`() { + Rustcalc.create_unique_ids_payload(listOf(10, 20, 10)).use { result -> + assertTrue(result is DataPayload.UniqueIds) + // Rust HashSet deduplicates + assertTrue(result.value.contains(10)) + assertTrue(result.value.contains(20)) + } + } + + @Test fun `create_mapping_payload returns Mapping`() { + Rustcalc.create_mapping_payload(listOf(1, 2), listOf(10, 20)).use { result -> + assertTrue(result is DataPayload.Mapping) + assertEquals(10, result.value[1]) + assertEquals(20, result.value[2]) + } + } + + @Test fun `create_empty_payload returns Empty`() { + Rustcalc.create_empty_payload().use { result -> + assertTrue(result is DataPayload.Empty) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge - Scores with MAX_VALUE`() { + DataPayload.scores(listOf(Int.MAX_VALUE)).use { result -> + assertEquals(listOf(Int.MAX_VALUE), (result as DataPayload.Scores).value) + } + } + + @Test fun `edge - Scores with MIN_VALUE`() { + DataPayload.scores(listOf(Int.MIN_VALUE)).use { result -> + assertEquals(listOf(Int.MIN_VALUE), (result as DataPayload.Scores).value) + } + } + + @Test fun `edge - UniqueIds with MAX_VALUE`() { + DataPayload.uniqueIds(setOf(Int.MAX_VALUE, Int.MIN_VALUE)).use { result -> + val s = (result as DataPayload.UniqueIds).value + assertTrue(s.contains(Int.MAX_VALUE)) + assertTrue(s.contains(Int.MIN_VALUE)) + } + } + + @Test fun `edge - Mapping with zero key`() { + DataPayload.mapping(mapOf(0 to 0)).use { result -> + assertEquals(mapOf(0 to 0), (result as DataPayload.Mapping).value) + } + } + + @Test fun `edge - all tags cycle`() { + DataPayload.scores(listOf(1)).use { assertEquals(DataPayload.Tag.Scores, it.tag) } + DataPayload.uniqueIds(setOf(1)).use { assertEquals(DataPayload.Tag.UniqueIds, it.tag) } + DataPayload.mapping(mapOf(1 to 1)).use { assertEquals(DataPayload.Tag.Mapping, it.tag) } + DataPayload.empty().use { assertEquals(DataPayload.Tag.Empty, it.tag) } + } + + @Test fun `edge - lifecycle create and close many`() { + repeat(50) { i -> + DataPayload.scores(listOf(i)).use { result -> + assertEquals(listOf(i), (result as DataPayload.Scores).value) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K Scores factory calls`() { + repeat(100_000) { + DataPayload.scores(listOf(1, 2, 3)).use { result -> + assertEquals(3, (result as DataPayload.Scores).value.size) + } + } + } + + @Test fun `load - 100K UniqueIds factory calls`() { + repeat(100_000) { + DataPayload.uniqueIds(setOf(1, 2, 3)).use { result -> + assertEquals(3, (result as DataPayload.UniqueIds).value.size) + } + } + } + + @Test fun `load - 100K Mapping factory calls`() { + repeat(100_000) { + DataPayload.mapping(mapOf(1 to 10)).use { result -> + assertEquals(1, (result as DataPayload.Mapping).value.size) + } + } + } + + @Test fun `load - 100K Tags factory calls`() { + repeat(100_000) { + DataPayload.tags(listOf("a", "b", "c")).use { result -> + assertEquals(3, (result as DataPayload.Tags).value.size) + } + } + } + + @Test fun `load - 1K create_tags_payload calls`() { + repeat(1_000) { + val tags = listOf("x", "y") + Rustcalc.create_tags_payload(tags, tags.size).use { result -> + assertEquals(2, (result as DataPayload.Tags).value.size) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K Scores factory`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + DataPayload.scores(listOf(tid, tid * 2)).use { result -> + assertEquals(listOf(tid, tid * 2), (result as DataPayload.Scores).value) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K UniqueIds factory`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + DataPayload.uniqueIds(setOf(tid)).use { result -> + assertTrue((result as DataPayload.UniqueIds).value.contains(tid)) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K Mapping factory`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + DataPayload.mapping(mapOf(tid to tid * 10)).use { result -> + assertEquals(tid * 10, (result as DataPayload.Mapping).value[tid]) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 1K Tags factory`() { + val threads = (1..10).map { tid -> + Thread { + repeat(1_000) { + DataPayload.tags(listOf("tag$tid-1", "tag$tid-2")).use { result -> + val tags = (result as DataPayload.Tags).value + assertTrue(tags.contains("tag$tid-1")) + assertTrue(tags.contains("tag$tid-2")) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 5 threads x 200 create_tags_payload`() { + val threads = (1..5).map { tid -> + Thread { + repeat(200) { + val tags = listOf("thread$tid") + Rustcalc.create_tags_payload(tags, tags.size).use { result -> + assertEquals("thread$tid", (result as DataPayload.Tags).value.first()) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/EnumTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/EnumTest.kt new file mode 100644 index 00000000..11a1acf9 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/EnumTest.kt @@ -0,0 +1,131 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals + +class EnumTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Enum structure + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `enum has correct entries`() { + assertEquals(3, Operation.entries.size) + assertEquals("Add", Operation.Add.name) + assertEquals("Subtract", Operation.Subtract.name) + assertEquals("Multiply", Operation.Multiply.name) + } + + @Test fun `enum ordinals match`() { + assertEquals(0, Operation.Add.ordinal) + assertEquals(1, Operation.Subtract.ordinal) + assertEquals(2, Operation.Multiply.ordinal) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Enum as parameter + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `enum as parameter - Add`() { + Calculator(0).use { calc -> assertEquals(5, calc.apply_op(Operation.Add, 5)) } + } + + @Test fun `enum as parameter - Subtract`() { + Calculator(10).use { calc -> assertEquals(7, calc.apply_op(Operation.Subtract, 3)) } + } + + @Test fun `enum as parameter - Multiply`() { + Calculator(4).use { calc -> assertEquals(12, calc.apply_op(Operation.Multiply, 3)) } + } + + @Test fun `enum roundtrip through all values`() { + Calculator(1).use { calc -> + for (op in Operation.entries) { + calc.apply_op(op, 1) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge enum - Add with zero`() { + Calculator(10).use { calc -> assertEquals(10, calc.apply_op(Operation.Add, 0)) } + } + + @Test fun `edge enum - Subtract with zero`() { + Calculator(10).use { calc -> assertEquals(10, calc.apply_op(Operation.Subtract, 0)) } + } + + @Test fun `edge enum - Multiply with zero`() { + Calculator(10).use { calc -> assertEquals(0, calc.apply_op(Operation.Multiply, 0)) } + } + + @Test fun `edge enum - Multiply with one`() { + Calculator(42).use { calc -> assertEquals(42, calc.apply_op(Operation.Multiply, 1)) } + } + + @Test fun `edge enum - Add with negative`() { + Calculator(10).use { calc -> assertEquals(5, calc.apply_op(Operation.Add, -5)) } + } + + @Test fun `edge enum - Subtract with negative`() { + Calculator(10).use { calc -> assertEquals(15, calc.apply_op(Operation.Subtract, -5)) } + } + + @Test fun `edge enum - Add with MAX_VALUE`() { + Calculator(0).use { calc -> assertEquals(Int.MAX_VALUE, calc.apply_op(Operation.Add, Int.MAX_VALUE)) } + } + + @Test fun `edge enum - sequential operations`() { + Calculator(0).use { calc -> + calc.apply_op(Operation.Add, 10) // 10 + calc.apply_op(Operation.Multiply, 3) // 30 + calc.apply_op(Operation.Subtract, 5) // 25 + calc.apply_op(Operation.Add, 75) // 100 + assertEquals(100, calc.current) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K apply_op calls`() { + Calculator(0).use { calc -> + repeat(100_000) { + calc.apply_op(Operation.Add, 1) + } + assertEquals(100_000, calc.current) + } + } + + @Test fun `load - 100K enum param roundtrip`() { + Calculator(0).use { calc -> + repeat(100_000) { i -> + val op = Operation.entries[i % 3] + calc.apply_op(op, 0) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K apply_op`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(10_000) { + val op = Operation.entries[tid % 3] + calc.apply_op(op, 1) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ErrorInfoTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ErrorInfoTest.kt new file mode 100644 index 00000000..2b2732f7 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ErrorInfoTest.kt @@ -0,0 +1,278 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ErrorInfoTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // get_error_info — returns different variants based on accumulator + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_error_info zero returns None`() { + Calculator(0).use { calc -> + calc.get_error_info().use { info -> + assertTrue(info is ErrorInfo.None) + assertEquals(ErrorInfo.Tag.None, info.tag) + } + } + } + + @Test fun `get_error_info negative returns DeviceError with two strings`() { + Calculator(-5).use { calc -> + calc.get_error_info().use { info -> + assertTrue(info is ErrorInfo.DeviceError) + val de = info as ErrorInfo.DeviceError + assertEquals("calculator", de.value0) + assertEquals("negative value: -5", de.value1) + } + } + } + + @Test fun `get_error_info large returns PropertyError with three fields`() { + Calculator(1500).use { calc -> + calc.get_error_info().use { info -> + assertTrue(info is ErrorInfo.PropertyError) + val pe = info as ErrorInfo.PropertyError + assertEquals("accumulator", pe.value0) + assertEquals(1500, pe.value1) + assertEquals("value too large", pe.value2) + } + } + } + + @Test fun `get_error_info medium returns CodedMessage with int and string`() { + Calculator(200).use { calc -> + calc.get_error_info().use { info -> + assertTrue(info is ErrorInfo.CodedMessage) + val cm = info as ErrorInfo.CodedMessage + assertEquals(200, cm.value0) + assertEquals("code_200", cm.value1) + } + } + } + + @Test fun `get_error_info small positive returns Simple`() { + Calculator(42).use { calc -> + calc.get_error_info().use { info -> + assertTrue(info is ErrorInfo.Simple) + assertEquals("ok: 42", (info as ErrorInfo.Simple).value) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Factory methods — construct from Kotlin side + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory DeviceError two strings`() { + ErrorInfo.deviceError("device1", "failed to open").use { info -> + assertTrue(info is ErrorInfo.DeviceError) + assertEquals("device1", info.value0) + assertEquals("failed to open", info.value1) + assertEquals(ErrorInfo.Tag.DeviceError, info.tag) + } + } + + @Test fun `factory PropertyError three fields`() { + ErrorInfo.propertyError("brightness", 75, "out of range").use { info -> + assertTrue(info is ErrorInfo.PropertyError) + assertEquals("brightness", info.value0) + assertEquals(75, info.value1) + assertEquals("out of range", info.value2) + assertEquals(ErrorInfo.Tag.PropertyError, info.tag) + } + } + + @Test fun `factory CodedMessage int and string`() { + ErrorInfo.codedMessage(404, "not found").use { info -> + assertTrue(info is ErrorInfo.CodedMessage) + assertEquals(404, info.value0) + assertEquals("not found", info.value1) + assertEquals(ErrorInfo.Tag.CodedMessage, info.tag) + } + } + + @Test fun `factory Simple single string`() { + ErrorInfo.simple("hello").use { info -> + assertTrue(info is ErrorInfo.Simple) + assertEquals("hello", info.value) + } + } + + @Test fun `factory None`() { + ErrorInfo.none().use { info -> + assertTrue(info is ErrorInfo.None) + assertEquals(ErrorInfo.Tag.None, info.tag) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge - DeviceError with empty strings`() { + ErrorInfo.deviceError("", "").use { info -> + assertTrue(info is ErrorInfo.DeviceError) + assertEquals("", info.value0) + assertEquals("", info.value1) + } + } + + @Test fun `edge - DeviceError with unicode`() { + ErrorInfo.deviceError("カメラ", "エラー 🔥").use { info -> + assertTrue(info is ErrorInfo.DeviceError) + assertEquals("カメラ", info.value0) + assertEquals("エラー 🔥", info.value1) + } + } + + @Test fun `edge - PropertyError with zero int`() { + ErrorInfo.propertyError("prop", 0, "msg").use { info -> + assertTrue(info is ErrorInfo.PropertyError) + assertEquals(0, info.value1) + } + } + + @Test fun `edge - PropertyError with MAX_VALUE`() { + ErrorInfo.propertyError("prop", Int.MAX_VALUE, "overflow").use { info -> + assertTrue(info is ErrorInfo.PropertyError) + assertEquals(Int.MAX_VALUE, info.value1) + } + } + + @Test fun `edge - PropertyError with MIN_VALUE`() { + ErrorInfo.propertyError("prop", Int.MIN_VALUE, "underflow").use { info -> + assertTrue(info is ErrorInfo.PropertyError) + assertEquals(Int.MIN_VALUE, info.value1) + } + } + + @Test fun `edge - CodedMessage with negative code`() { + ErrorInfo.codedMessage(-1, "negative").use { info -> + assertTrue(info is ErrorInfo.CodedMessage) + assertEquals(-1, info.value0) + assertEquals("negative", info.value1) + } + } + + @Test fun `edge - PropertyError with empty strings and zero`() { + ErrorInfo.propertyError("", 0, "").use { info -> + assertTrue(info is ErrorInfo.PropertyError) + assertEquals("", info.value0) + assertEquals(0, info.value1) + assertEquals("", info.value2) + } + } + + @Test fun `edge - DeviceError with long strings`() { + val longStr = "x".repeat(10_000) + ErrorInfo.deviceError(longStr, longStr).use { info -> + assertTrue(info is ErrorInfo.DeviceError) + assertEquals(longStr, info.value0) + assertEquals(longStr, info.value1) + } + } + + @Test fun `edge - all tags cycle`() { + ErrorInfo.deviceError("a", "b").use { assertEquals(ErrorInfo.Tag.DeviceError, it.tag) } + ErrorInfo.propertyError("a", 1, "b").use { assertEquals(ErrorInfo.Tag.PropertyError, it.tag) } + ErrorInfo.codedMessage(1, "a").use { assertEquals(ErrorInfo.Tag.CodedMessage, it.tag) } + ErrorInfo.simple("a").use { assertEquals(ErrorInfo.Tag.Simple, it.tag) } + ErrorInfo.none().use { assertEquals(ErrorInfo.Tag.None, it.tag) } + } + + @Test fun `edge - lifecycle create and close many`() { + repeat(50) { i -> + ErrorInfo.deviceError("dev$i", "err$i").use { info -> + assertTrue(info is ErrorInfo.DeviceError) + assertEquals("dev$i", info.value0) + assertEquals("err$i", info.value1) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K factory DeviceError calls`() { + repeat(100_000) { + ErrorInfo.deviceError("dev", "err").use { info -> + assertTrue(info is ErrorInfo.DeviceError) + } + } + } + + @Test fun `load - 100K factory PropertyError calls`() { + repeat(100_000) { + ErrorInfo.propertyError("prop", 42, "msg").use { info -> + assertTrue(info is ErrorInfo.PropertyError) + } + } + } + + @Test fun `load - 100K get_error_info calls`() { + Calculator(-1).use { calc -> + repeat(100_000) { + calc.get_error_info().use { info -> + assertTrue(info is ErrorInfo.DeviceError) + } + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K factory DeviceError`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + ErrorInfo.deviceError("dev$tid", "err$tid").use { info -> + assertTrue(info is ErrorInfo.DeviceError) + assertEquals("dev$tid", info.value0) + assertEquals("err$tid", info.value1) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K factory PropertyError`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + ErrorInfo.propertyError("p$tid", tid, "m$tid").use { info -> + assertTrue(info is ErrorInfo.PropertyError) + assertEquals("p$tid", info.value0) + assertEquals(tid, info.value1) + assertEquals("m$tid", info.value2) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K get_error_info on separate instances`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid * -10).use { calc -> + repeat(10_000) { + calc.get_error_info().use { info -> + assertTrue(info is ErrorInfo.DeviceError) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/FlowTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/FlowTest.kt new file mode 100644 index 00000000..01cecbc6 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/FlowTest.kt @@ -0,0 +1,150 @@ +package com.example.rustcalculator + +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class FlowTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Basic Flow and Flow + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `count_up emits correct sequence`() = runBlocking { + Calculator(0).use { calc -> + val items = calc.count_up(5, 10).toList() + assertEquals(5, items.size) + assertEquals(listOf(1, 2, 3, 4, 5), items) + } + } + + @Test fun `count_up with non-zero accumulator`() = runBlocking { + Calculator(10).use { calc -> + val items = calc.count_up(3, 10).toList() + assertEquals(listOf(11, 12, 13), items) + } + } + + @Test fun `score_labels emits strings`() = runBlocking { + Calculator(5).use { calc -> + val items = calc.score_labels(3).toList() + assertEquals(3, items.size) + assertEquals("Score #1: 5", items[0]) + assertEquals("Score #2: 10", items[1]) + assertEquals("Score #3: 15", items[2]) + } + } + + @Test fun `flow with single element`() = runBlocking { + Calculator(0).use { calc -> + val items = calc.count_up(1, 10).toList() + assertEquals(1, items.size) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge flow - count_up with zero accumulator`() = runBlocking { + Calculator(0).use { calc -> + val items = calc.count_up(3, 10).toList() + assertEquals(listOf(1, 2, 3), items) + } + } + + @Test fun `edge flow - count_up with negative accumulator`() = runBlocking { + Calculator(-10).use { calc -> + val items = calc.count_up(3, 10).toList() + assertEquals(listOf(-9, -8, -7), items) + } + } + + @Test fun `edge flow - score_labels with single element`() = runBlocking { + Calculator(1).use { calc -> + val items = calc.score_labels(1).toList() + assertEquals(1, items.size) + assertEquals("Score #1: 1", items[0]) + } + } + + @Test fun `edge flow - score_labels with large count`() = runBlocking { + Calculator(1).use { calc -> + val items = calc.score_labels(100).toList() + assertEquals(100, items.size) + assertEquals("Score #100: 100", items[99]) + } + } + + @Test fun `edge flow - count_up with MAX_VALUE accumulator`() = runBlocking { + Calculator(Int.MAX_VALUE).use { calc -> + val items = calc.count_up(1, 10).toList() + assertEquals(1, items.size) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 10K count_up flow collections`() = runBlocking { + Calculator(1).use { calc -> + repeat(10_000) { + val items = calc.count_up(1, 0).toList() + assertEquals(1, items.size) + assertEquals(2, items[0]) + } + } + } + + @Test fun `load - 10K score_labels flow collections`() = runBlocking { + Calculator(1).use { calc -> + repeat(10_000) { + val items = calc.score_labels(1).toList() + assertEquals(1, items.size) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 1K count_up flows`() { + val threads = (1..10).map { tid -> + Thread { + runBlocking { + Calculator(tid).use { calc -> + repeat(1_000) { + val items = calc.count_up(2, 0).toList() + assertEquals(2, items.size) + assertEquals(tid + 1, items[0]) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 1K score_labels flows`() { + val threads = (1..10).map { tid -> + Thread { + runBlocking { + Calculator(tid).use { calc -> + repeat(1_000) { + val items = calc.score_labels(1).toList() + assertEquals(1, items.size) + assertTrue(items[0].contains("$tid")) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/GenericMonomorphTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/GenericMonomorphTest.kt new file mode 100644 index 00000000..aa37749c --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/GenericMonomorphTest.kt @@ -0,0 +1,112 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals + +class GenericMonomorphTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Instance method monomorphisation — apply_transformer + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `apply_transformer_doubler doubles accumulator`() { + Calculator(5).use { calc -> + Doubler().use { d -> + assertEquals(10, calc.apply_transformer_doubler(d)) + } + } + } + + @Test fun `apply_transformer_tripler triples accumulator`() { + Calculator(5).use { calc -> + Tripler().use { t -> + assertEquals(15, calc.apply_transformer_tripler(t)) + } + } + } + + @Test fun `apply_transformer with zero accumulator`() { + Calculator(0).use { calc -> + Doubler().use { d -> assertEquals(0, calc.apply_transformer_doubler(d)) } + Tripler().use { t -> assertEquals(0, calc.apply_transformer_tripler(t)) } + } + } + + @Test fun `apply_transformer with negative accumulator`() { + Calculator(-3).use { calc -> + Doubler().use { d -> assertEquals(-6, calc.apply_transformer_doubler(d)) } + Tripler().use { t -> assertEquals(-9, calc.apply_transformer_tripler(t)) } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Instance method monomorphisation — get_transformer_name + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_transformer_name_doubler returns Doubler`() { + Calculator(0).use { calc -> + Doubler().use { d -> + assertEquals("Doubler", calc.get_transformer_name_doubler(d)) + } + } + } + + @Test fun `get_transformer_name_tripler returns Tripler`() { + Calculator(0).use { calc -> + Tripler().use { t -> + assertEquals("Tripler", calc.get_transformer_name_tripler(t)) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Top-level function monomorphisation — transform_value + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `top-level transform_value_doubler`() { + Doubler().use { d -> + assertEquals(20, Rustcalc.transform_value_doubler(10, d)) + } + } + + @Test fun `top-level transform_value_tripler`() { + Tripler().use { t -> + assertEquals(30, Rustcalc.transform_value_tripler(10, t)) + } + } + + @Test fun `top-level transform_value with zero`() { + Doubler().use { d -> assertEquals(0, Rustcalc.transform_value_doubler(0, d)) } + Tripler().use { t -> assertEquals(0, Rustcalc.transform_value_tripler(0, t)) } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge - Int MAX_VALUE with doubler overflows`() { + Calculator(Int.MAX_VALUE).use { calc -> + Doubler().use { d -> + assertEquals(Int.MAX_VALUE * 2, calc.apply_transformer_doubler(d)) + } + } + } + + @Test fun `edge - multiple transformers on same calculator`() { + Calculator(7).use { calc -> + Doubler().use { d -> + Tripler().use { t -> + assertEquals(14, calc.apply_transformer_doubler(d)) + assertEquals(21, calc.apply_transformer_tripler(t)) + } + } + } + } + + @Test fun `edge - transformer reuse across calculators`() { + Doubler().use { d -> + Calculator(3).use { calc1 -> assertEquals(6, calc1.apply_transformer_doubler(d)) } + Calculator(10).use { calc2 -> assertEquals(20, calc2.apply_transformer_doubler(d)) } + } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/GenericStructTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/GenericStructTest.kt new file mode 100644 index 00000000..0e511ad8 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/GenericStructTest.kt @@ -0,0 +1,90 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals + +class GenericStructTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Processor_Doubler — generic struct monomorphised with Doubler + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `Processor_Doubler constructs and processes`() { + Processor_Doubler(Doubler(), 10).use { p -> + // Doubler doubles the input, then adds offset + assertEquals(30, p.process(10)) // 10*2 + 10 + } + } + + @Test fun `Processor_Doubler name includes transformer and offset`() { + Processor_Doubler(Doubler(), 5).use { p -> + assertEquals("Processor(Doubler+5)", p.name()) + } + } + + @Test fun `Processor_Doubler offset property`() { + Processor_Doubler(Doubler(), 0).use { p -> + assertEquals(0, p.offset) + p.offset = 42 + assertEquals(42, p.offset) + } + } + + @Test fun `Processor_Doubler process with updated offset`() { + Processor_Doubler(Doubler(), 0).use { p -> + assertEquals(20, p.process(10)) // 10*2 + 0 + p.offset = 100 + assertEquals(120, p.process(10)) // 10*2 + 100 + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Processor_Tripler — generic struct monomorphised with Tripler + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `Processor_Tripler constructs and processes`() { + Processor_Tripler(Tripler(), 5).use { p -> + // Tripler triples the input, then adds offset + assertEquals(35, p.process(10)) // 10*3 + 5 + } + } + + @Test fun `Processor_Tripler name includes transformer and offset`() { + Processor_Tripler(Tripler(), 7).use { p -> + assertEquals("Processor(Tripler+7)", p.name()) + } + } + + @Test fun `Processor_Tripler offset property`() { + Processor_Tripler(Tripler(), 99).use { p -> + assertEquals(99, p.offset) + p.offset = 0 + assertEquals(0, p.offset) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge - zero input and zero offset`() { + Processor_Doubler(Doubler(), 0).use { p -> + assertEquals(0, p.process(0)) + } + } + + @Test fun `edge - negative values`() { + Processor_Tripler(Tripler(), -10).use { p -> + assertEquals(-16, p.process(-2)) // -2*3 + (-10) = -16 + } + } + + @Test fun `edge - multiple processors coexist`() { + Processor_Doubler(Doubler(), 1).use { pd -> + Processor_Tripler(Tripler(), 2).use { pt -> + assertEquals(21, pd.process(10)) // 10*2 + 1 + assertEquals(32, pt.process(10)) // 10*3 + 2 + } + } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ImplTraitParamTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ImplTraitParamTest.kt new file mode 100644 index 00000000..978a0b91 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ImplTraitParamTest.kt @@ -0,0 +1,200 @@ +package com.example.rustcalculator + +import kotlin.concurrent.thread +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * E2E tests for impl Trait as function PARAMETERS (not return types). + * Verifies that the bridge resolves well-known traits (ToString, Into, AsRef) + * to concrete types and passes them correctly. + */ +class ImplTraitParamTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // 1. impl ToString parameter — edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `impl - greet_impl with simple string`() { + assertEquals("Hello, World!", Rustcalc.greet_impl("World")) + } + + @Test fun `impl - greet_impl with empty string`() { + assertEquals("Hello, !", Rustcalc.greet_impl("")) + } + + @Test fun `str - greet_impl with unicode`() { + assertEquals("Hello, 日本語!", Rustcalc.greet_impl("日本語")) + } + + @Test fun `str - greet_impl with emoji`() { + assertEquals("Hello, 🎉!", Rustcalc.greet_impl("🎉")) + } + + @Test fun `str - greet_impl with long string`() { + val long = "x".repeat(10_000) + assertEquals("Hello, $long!", Rustcalc.greet_impl(long)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 2. impl Into parameter — edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `impl - into_upper with lowercase`() { + assertEquals("HELLO", Rustcalc.into_upper("hello")) + } + + @Test fun `impl - into_upper with empty`() { + assertEquals("", Rustcalc.into_upper("")) + } + + @Test fun `impl - into_upper with mixed case`() { + assertEquals("HELLO WORLD", Rustcalc.into_upper("Hello World")) + } + + @Test fun `str - into_upper with unicode`() { + assertEquals("日本", Rustcalc.into_upper("日本")) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 3. impl AsRef parameter — edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `impl - count_chars with ascii`() { + assertEquals(5, Rustcalc.count_chars("hello")) + } + + @Test fun `impl - count_chars with empty`() { + assertEquals(0, Rustcalc.count_chars("")) + } + + @Test fun `str - count_chars with unicode`() { + // UTF-8 byte count, not char count + val text = "日本" + assertEquals(text.toByteArray(Charsets.UTF_8).size, Rustcalc.count_chars(text)) + } + + @Test fun `edge - count_chars with null byte`() { + // CStr stops at null byte — this tests boundary behavior + assertEquals(0, Rustcalc.count_chars("\u0000abc")) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 4. Box parameter (ownership transfer) — edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `obj - consume_describable takes ownership`() { + val desc = Rustcalc.create_describable(42) as DynDescribable + val result = Rustcalc.consume_describable(desc) + assertEquals("consumed: Calculator(current=42, label=)", result) + } + + @Test fun `obj - consume_describable with zero`() { + val desc = Rustcalc.create_describable(0) as DynDescribable + assertEquals("consumed: Calculator(current=0, label=)", Rustcalc.consume_describable(desc)) + } + + @Test fun `edge - consume_describable with negative`() { + val desc = Rustcalc.create_describable(-99) as DynDescribable + assertEquals("consumed: Calculator(current=-99, label=)", Rustcalc.consume_describable(desc)) + } + + @Test fun `edge - consume_describable with MAX_VALUE`() { + val desc = Rustcalc.create_describable(Int.MAX_VALUE) as DynDescribable + assertEquals("consumed: Calculator(current=${Int.MAX_VALUE}, label=)", Rustcalc.consume_describable(desc)) + } + + @Test fun `edge - consume_describable with MIN_VALUE`() { + val desc = Rustcalc.create_describable(Int.MIN_VALUE) as DynDescribable + assertEquals("consumed: Calculator(current=${Int.MIN_VALUE}, label=)", Rustcalc.consume_describable(desc)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 5. Load tests — 100K+ FFM calls + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K greet_impl calls`() { + repeat(100_000) { + assertEquals("Hello, x!", Rustcalc.greet_impl("x")) + } + } + + @Test fun `load - 100K into_upper calls`() { + repeat(100_000) { + assertEquals("ABC", Rustcalc.into_upper("abc")) + } + } + + @Test fun `load - 100K count_chars calls`() { + repeat(100_000) { + assertEquals(5, Rustcalc.count_chars("hello")) + } + } + + @Test fun `load - 10K consume_describable create+consume cycles`() { + repeat(10_000) { + val desc = Rustcalc.create_describable(it) as DynDescribable + Rustcalc.consume_describable(desc) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 6. Concurrency tests — multi-threaded + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K greet_impl calls`() { + val errors = mutableListOf() + val threads = (1..10).map { i -> + thread { + try { + repeat(10_000) { + assertEquals("Hello, t$i!", Rustcalc.greet_impl("t$i")) + } + } catch (e: Throwable) { + synchronized(errors) { errors.add(e) } + } + } + } + threads.forEach { it.join() } + assertTrue(errors.isEmpty(), "Concurrent errors: ${errors.map { it.message }}") + } + + @Test fun `concurrent - 10 threads x 10K into_upper calls`() { + val errors = mutableListOf() + val threads = (1..10).map { i -> + thread { + try { + val input = "thread$i" + val expected = input.uppercase() + repeat(10_000) { + assertEquals(expected, Rustcalc.into_upper(input)) + } + } catch (e: Throwable) { + synchronized(errors) { errors.add(e) } + } + } + } + threads.forEach { it.join() } + assertTrue(errors.isEmpty(), "Concurrent errors: ${errors.map { it.message }}") + } + + @Test fun `concurrent - 10 threads x 1K consume_describable cycles`() { + val errors = mutableListOf() + val threads = (1..10).map { i -> + thread { + try { + repeat(1_000) { j -> + val desc = Rustcalc.create_describable(i * 1000 + j) as DynDescribable + val result = Rustcalc.consume_describable(desc) + assertTrue(result.startsWith("consumed:")) + } + } catch (e: Throwable) { + synchronized(errors) { errors.add(e) } + } + } + } + threads.forEach { it.join() } + assertTrue(errors.isEmpty(), "Concurrent errors: ${errors.map { it.message }}") + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ImplTraitTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ImplTraitTest.kt new file mode 100644 index 00000000..e7ddbd39 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ImplTraitTest.kt @@ -0,0 +1,518 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.assertFailsWith +import kotlin.concurrent.thread + +class ImplTraitTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // 1. impl Iterator — basic + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_scores returns collected list of 3 elements`() { + Calculator(5).use { calc -> + assertEquals(listOf(5, 10, 15), calc.iter_scores()) + } + } + + @Test fun `iter_scores with zero accumulator`() { + Calculator(0).use { calc -> + assertEquals(listOf(0, 0, 0), calc.iter_scores()) + } + } + + @Test fun `iter_scores with negative accumulator`() { + Calculator(-3).use { calc -> + assertEquals(listOf(-3, -6, -9), calc.iter_scores()) + } + } + + @Test fun `iter_scores with MAX_VALUE boundary`() { + Calculator(Int.MAX_VALUE).use { calc -> + val scores = calc.iter_scores() + assertEquals(3, scores.size) + assertEquals(Int.MAX_VALUE, scores[0]) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 2. impl Iterator + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_labels returns collected list of strings`() { + Calculator(2).use { calc -> + assertEquals(listOf("score_2", "score_4", "score_6"), calc.iter_labels()) + } + } + + @Test fun `iter_labels with zero`() { + Calculator(0).use { calc -> + assertEquals(listOf("score_0", "score_0", "score_0"), calc.iter_labels()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 3. impl Iterator — empty (zero elements) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_empty returns empty list`() { + Calculator(1).use { calc -> + val empty = calc.iter_empty() + assertTrue(empty.isEmpty()) + assertEquals(0, empty.size) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 4. impl Iterator — boolean element type + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_flags with positive accumulator`() { + Calculator(50).use { calc -> + assertEquals(listOf(true, true, false), calc.iter_flags()) + } + } + + @Test fun `iter_flags with zero`() { + Calculator(0).use { calc -> + assertEquals(listOf(false, false, false), calc.iter_flags()) + } + } + + @Test fun `iter_flags with large value`() { + Calculator(200).use { calc -> + assertEquals(listOf(true, true, true), calc.iter_flags()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 5. impl Iterator — float element type + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_ratios with 12`() { + Calculator(12).use { calc -> + val ratios = calc.iter_ratios() + assertEquals(3, ratios.size) + assertEquals(6.0, ratios[0]) + assertEquals(4.0, ratios[1]) + assertEquals(3.0, ratios[2]) + } + } + + @Test fun `iter_ratios with zero avoids NaN`() { + Calculator(0).use { calc -> + val ratios = calc.iter_ratios() + assertEquals(listOf(0.0, 0.0, 0.0), ratios) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 6. impl Iterator — Long element type + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_big_values returns longs`() { + Calculator(7).use { calc -> + assertEquals(listOf(7_000_000L, 14_000_000L), calc.iter_big_values()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 7. Large iterator — buffer overflow / retry + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_large with 10 elements`() { + Calculator(0).use { calc -> + val result = calc.iter_large(10) + assertEquals((0 until 10).toList(), result) + } + } + + @Test fun `iter_large with 5000 elements exceeds default buffer`() { + Calculator(1).use { calc -> + val result = calc.iter_large(5000) + assertEquals(5000, result.size) + assertEquals(1, result[0]) + assertEquals(5000, result[4999]) + } + } + + @Test fun `iter_large with zero elements`() { + Calculator(0).use { calc -> + assertTrue(calc.iter_large(0).isEmpty()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 8. impl ExactSizeIterator + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `exact_scores returns collected list`() { + Calculator(4).use { calc -> + assertEquals(listOf(4, 8, 12), calc.exact_scores()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 9. impl DoubleEndedIterator + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_reversed returns list`() { + Calculator(3).use { calc -> + assertEquals(listOf(3, 6, 9), calc.iter_reversed()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 10. impl IntoIterator + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_into returns list`() { + Calculator(10).use { calc -> + assertEquals(listOf(10, 11, 12), calc.iter_into()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 11. impl Iterator + Send (multiple bounds) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_sendable with multiple bounds`() { + Calculator(7).use { calc -> + assertEquals(listOf(7, 70), calc.iter_sendable()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 12. impl Display — basic + edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `display_value returns string representation`() { + Calculator(42).use { calc -> + assertEquals("Calc(42)", calc.display_value()) + } + } + + @Test fun `display_value with zero`() { + Calculator(0).use { calc -> + assertEquals("Calc(0)", calc.display_value()) + } + } + + @Test fun `display_value with negative`() { + Calculator(-999).use { calc -> + assertEquals("Calc(-999)", calc.display_value()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 13. impl Display with unicode + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `display_unicode returns CJK string`() { + Calculator(42).use { calc -> + assertEquals("計算機(42)", calc.display_unicode()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 14. impl Display with long string (>8192 bytes, triggers buffer retry) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `display_long returns string longer than buffer`() { + Calculator(0).use { calc -> + val result = calc.display_long() + assertTrue(result.length > 10_000, "Expected >10000 chars, got ${result.length}") + assertTrue(result.startsWith("x")) + assertTrue(result.endsWith("A")) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 15. impl ToString + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `as_string_repr returns string of accumulator`() { + Calculator(99).use { calc -> + assertEquals("99", calc.as_string_repr()) + } + } + + @Test fun `as_string_repr with negative`() { + Calculator(-42).use { calc -> + assertEquals("-42", calc.as_string_repr()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 16. &mut self + impl Trait + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `drain_and_iter mutates then returns iterator`() { + Calculator(10).use { calc -> + val result = calc.drain_and_iter(5) + // accumulator was 10 + 5 = 15 + assertEquals(listOf(15, 30, 45), result) + // verify state persists + assertEquals(15, calc.current) + } + } + + @Test fun `drain_and_iter called twice accumulates`() { + Calculator(0).use { calc -> + val first = calc.drain_and_iter(10) + assertEquals(listOf(10, 20, 30), first) + val second = calc.drain_and_iter(5) + // accumulator was 10 + 5 = 15 + assertEquals(listOf(15, 30, 45), second) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 17. Result — fallible + impl Trait + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `try_iter_scores succeeds for positive accumulator`() { + Calculator(5).use { calc -> + assertEquals(listOf(5, 10, 15), calc.try_iter_scores()) + } + } + + @Test fun `try_iter_scores succeeds for zero`() { + Calculator(0).use { calc -> + assertEquals(listOf(0, 0, 0), calc.try_iter_scores()) + } + } + + @Test fun `try_iter_scores throws for negative accumulator`() { + Calculator(-1).use { calc -> + assertFailsWith { + calc.try_iter_scores() + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 18. Result — fallible + Display + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `try_display succeeds for non-negative`() { + Calculator(42).use { calc -> + assertEquals("OK(42)", calc.try_display()) + } + } + + @Test fun `try_display throws for negative`() { + Calculator(-1).use { calc -> + assertFailsWith { + calc.try_display() + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 19. Panic during collect — error propagation + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `iter_panicking succeeds for positive accumulator`() { + Calculator(5).use { calc -> + assertEquals(listOf(5, 6, 7), calc.iter_panicking()) + } + } + + @Test fun `iter_panicking panics at index 2 for negative accumulator`() { + Calculator(-1).use { calc -> + assertFailsWith { + calc.iter_panicking() + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 20. Companion/static method returning impl Trait + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `fibonacci_iter returns first 6 fibonacci numbers`() { + val fibs = Calculator.fibonacci_iter(6) + assertEquals(listOf(0, 1, 1, 2, 3, 5), fibs) + } + + @Test fun `fibonacci_iter with zero count`() { + assertTrue(Calculator.fibonacci_iter(0).isEmpty()) + } + + @Test fun `fibonacci_iter with 1`() { + assertEquals(listOf(0), Calculator.fibonacci_iter(1)) + } + + @Test fun `static_label returns formatted string`() { + assertEquals("count=42", Calculator.static_label("count", 42)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 21. Top-level impl Iterator + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `generate_range returns collected list`() { + assertEquals(listOf(1, 2, 3), Rustcalc.generate_range(1, 4)) + } + + @Test fun `generate_range empty when start equals end`() { + assertTrue(Rustcalc.generate_range(5, 5).isEmpty()) + } + + @Test fun `generate_range single element`() { + assertEquals(listOf(0), Rustcalc.generate_range(0, 1)) + } + + @Test fun `generate_range negative values`() { + assertEquals(listOf(-3, -2, -1), Rustcalc.generate_range(-3, 0)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 22. Top-level impl Display + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `format_pair returns display string`() { + assertEquals("3 + 4 = 7", Rustcalc.format_pair(3, 4)) + } + + @Test fun `format_pair with negatives`() { + assertEquals("-1 + -2 = -3", Rustcalc.format_pair(-1, -2)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 23. Top-level impl Into + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `into_greeting returns converted string`() { + assertEquals("Hi, World!", Rustcalc.into_greeting("World")) + } + + @Test fun `into_greeting with unicode`() { + assertEquals("Hi, 日本!", Rustcalc.into_greeting("日本")) + } + + @Test fun `into_greeting with empty`() { + assertEquals("Hi, !", Rustcalc.into_greeting("")) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 24. Top-level impl AsRef + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `as_ref_label returns string`() { + assertEquals("label_42", Rustcalc.as_ref_label(42)) + } + + @Test fun `as_ref_label with zero`() { + assertEquals("label_0", Rustcalc.as_ref_label(0)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 25. Top-level impl Iterator with String param + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `repeat_str returns repeated strings`() { + assertEquals(listOf("abc", "abc", "abc"), Rustcalc.repeat_str("abc", 3)) + } + + @Test fun `repeat_str with zero count`() { + assertTrue(Rustcalc.repeat_str("x", 0).isEmpty()) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 26. Top-level Result + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `try_format succeeds for non-zero divisor`() { + assertEquals("10 / 2 = 5", Rustcalc.try_format(10, 2)) + } + + @Test fun `try_format throws for zero divisor`() { + assertFailsWith { + Rustcalc.try_format(10, 0) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 27. Concurrent calls with impl Trait + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent iter_scores from multiple threads`() { + Calculator(10).use { calc -> + val errors = mutableListOf() + val threads = (1..10).map { + thread { + try { + repeat(100) { + val result = calc.iter_scores() + assertEquals(listOf(10, 20, 30), result) + } + } catch (e: Throwable) { + synchronized(errors) { errors.add(e) } + } + } + } + threads.forEach { it.join() } + assertTrue(errors.isEmpty(), "Concurrent errors: ${errors.map { it.message }}") + } + } + + @Test fun `concurrent display_value from multiple threads`() { + Calculator(7).use { calc -> + val errors = mutableListOf() + val threads = (1..10).map { + thread { + try { + repeat(100) { + assertEquals("Calc(7)", calc.display_value()) + } + } catch (e: Throwable) { + synchronized(errors) { errors.add(e) } + } + } + } + threads.forEach { it.join() } + assertTrue(errors.isEmpty(), "Concurrent errors: ${errors.map { it.message }}") + } + } + + @Test fun `concurrent generate_range from multiple threads`() { + val expected = (0 until 100).toList() + val errors = mutableListOf() + val threads = (1..8).map { + thread { + try { + repeat(50) { + assertEquals(expected, Rustcalc.generate_range(0, 100)) + } + } catch (e: Throwable) { + synchronized(errors) { errors.add(e) } + } + } + } + threads.forEach { it.join() } + assertTrue(errors.isEmpty(), "Concurrent errors: ${errors.map { it.message }}") + } + + // ═══════════════════════════════════════════════════════════════════════════ + // 28. Sequential calls — verify no state leaks between calls + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `sequential impl trait calls dont leak state`() { + Calculator(5).use { calc -> + // Call different impl Trait methods in sequence + assertEquals(listOf(5, 10, 15), calc.iter_scores()) + assertEquals("Calc(5)", calc.display_value()) + assertEquals(listOf("score_5", "score_10", "score_15"), calc.iter_labels()) + assertEquals("5", calc.as_string_repr()) + assertEquals(listOf(5, 10, 15), calc.exact_scores()) + // Call again to ensure repeatable + assertEquals(listOf(5, 10, 15), calc.iter_scores()) + assertEquals("Calc(5)", calc.display_value()) + } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/InterfaceTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/InterfaceTest.kt new file mode 100644 index 00000000..3a883262 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/InterfaceTest.kt @@ -0,0 +1,382 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class InterfaceTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Describable trait + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `describe_self returns formatted string`() { + Calculator(42).use { calc -> + val desc = calc.describe_self() + assertTrue(desc.contains("42"), "Should contain accumulator value") + } + } + + @Test fun `describe_self with label`() { + Calculator(0).use { calc -> + calc.label = "test" + val desc = calc.describe_self() + assertTrue(desc.contains("test"), "Should contain label") + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Resettable trait + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `reset_to_default clears state`() { + Calculator(0).use { calc -> + calc.add(100) + calc.label = "test" + calc.scale = 2.5 + calc.enabled = false + calc.reset_to_default() + assertEquals(0, calc.current) + assertEquals("", calc.label) + assertEquals(1.0, calc.scale, 0.001) + assertTrue(calc.enabled) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Measurable trait + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `measure returns scaled value`() { + Calculator(10).use { calc -> + assertEquals(10.0, calc.measure(), 0.001) + calc.scale = 2.5 + assertEquals(25.0, calc.measure(), 0.001) + } + } + + @Test fun `unit returns constant string`() { + Calculator(0).use { calc -> + assertEquals("units", calc.unit()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Polymorphism via interfaces + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `Calculator is Describable`() { + Calculator(5).use { calc -> + val describable: Describable = calc + assertTrue(describable.describe_self().contains("5")) + } + } + + @Test fun `Calculator is Measurable`() { + Calculator(7).use { calc -> + val measurable: Measurable = calc + assertEquals(7.0, measurable.measure(), 0.001) + } + } + + @Test fun `Calculator is Resettable`() { + Calculator(99).use { calc -> + val resettable: Resettable = calc + resettable.reset_to_default() + assertEquals(0, calc.current) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // &dyn Trait param functions (registry-based handle passing) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `dyn - describe_trait_object returns description`() { + val obj = Rustcalc.create_describable(42) as DynDescribable + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("42"), "Should contain accumulator value, got: $desc") + } + + @Test fun `dyn - measure_trait_object returns measurement with unit`() { + val obj = Rustcalc.create_measurable(10) as DynMeasurable + val result = Rustcalc.measure_trait_object(obj) + assertTrue(result.contains("10"), "Should contain measurement, got: $result") + assertTrue(result.contains("units"), "Should contain unit, got: $result") + } + + @Test fun `dyn - reset_trait_object via &mut dyn Resettable`() { + val obj = Rustcalc.create_resettable(99) as DynResettable + Rustcalc.reset_trait_object(obj) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // &dyn Trait edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge dyn - describe with zero value`() { + val obj = Rustcalc.create_describable(0) as DynDescribable + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("0"), "Should contain 0, got: $desc") + } + + @Test fun `edge dyn - describe with negative value`() { + val obj = Rustcalc.create_describable(-999) as DynDescribable + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("-999"), "Should contain -999, got: $desc") + } + + @Test fun `edge dyn - describe with Int MAX_VALUE`() { + val obj = Rustcalc.create_describable(Int.MAX_VALUE) as DynDescribable + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("${Int.MAX_VALUE}"), "Should contain MAX_VALUE, got: $desc") + } + + @Test fun `edge dyn - describe with Int MIN_VALUE`() { + val obj = Rustcalc.create_describable(Int.MIN_VALUE) as DynDescribable + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("${Int.MIN_VALUE}"), "Should contain MIN_VALUE, got: $desc") + } + + @Test fun `edge dyn - measure with zero`() { + val obj = Rustcalc.create_measurable(0) as DynMeasurable + val result = Rustcalc.measure_trait_object(obj) + assertTrue(result.contains("0"), "Should contain 0, got: $result") + } + + @Test fun `edge dyn - multiple describe calls on same object`() { + val obj = Rustcalc.create_describable(7) as DynDescribable + repeat(10) { + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("7"), "Call $it failed, got: $desc") + } + } + + @Test fun `edge dyn - multiple different trait objects`() { + val objs = (1..5).map { Rustcalc.create_describable(it * 10) as DynDescribable } + objs.forEachIndexed { idx, obj -> + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("${(idx + 1) * 10}"), "Object $idx failed, got: $desc") + } + } + + @Test fun `edge dyn - reset then describe on different handles`() { + val resettable = Rustcalc.create_resettable(50) as DynResettable + val describable = Rustcalc.create_describable(100) as DynDescribable + Rustcalc.reset_trait_object(resettable) + val desc = Rustcalc.describe_trait_object(describable) + assertTrue(desc.contains("100"), "Describable should be unaffected, got: $desc") + } + + // ═══════════════════════════════════════════════════════════════════════════ + // &dyn Trait load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K describe_trait_object calls`() { + val obj = Rustcalc.create_describable(42) as DynDescribable + repeat(100_000) { + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("42")) + } + } + + @Test fun `load - 100K measure_trait_object calls`() { + val obj = Rustcalc.create_measurable(7) as DynMeasurable + repeat(100_000) { + val result = Rustcalc.measure_trait_object(obj) + assertTrue(result.contains("7")) + } + } + + @Test fun `load - 100K reset_trait_object calls`() { + val obj = Rustcalc.create_resettable(99) as DynResettable + repeat(100_000) { + Rustcalc.reset_trait_object(obj) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // &dyn Trait concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K describe_trait_object`() { + val threads = (1..10).map { tid -> + Thread { + val obj = Rustcalc.create_describable(tid * 100) as DynDescribable + repeat(10_000) { + val desc = Rustcalc.describe_trait_object(obj) + assertTrue(desc.contains("${tid * 100}"), "Thread $tid failed, got: $desc") + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K measure_trait_object`() { + val threads = (1..10).map { tid -> + Thread { + val obj = Rustcalc.create_measurable(tid) as DynMeasurable + repeat(10_000) { + val result = Rustcalc.measure_trait_object(obj) + assertTrue(result.contains("$tid")) + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TraitConsumer: dyn Trait as constructor and method params + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `dyn param - constructor with &dyn Describable`() { + val source = Rustcalc.create_describable(42) as DynDescribable + TraitConsumer(source).use { consumer -> + val desc = consumer.description + assertTrue(desc.contains("42"), "Should contain source value, got: $desc") + } + } + + @Test fun `dyn param - update_from method with &dyn Describable`() { + val source1 = Rustcalc.create_describable(10) as DynDescribable + val source2 = Rustcalc.create_describable(99) as DynDescribable + TraitConsumer(source1).use { consumer -> + assertTrue(consumer.description.contains("10")) + consumer.update_from(source2) + assertTrue(consumer.description.contains("99"), "Should be updated to 99") + } + } + + @Test fun `dyn param - measure_from method with &dyn Measurable`() { + val describable = Rustcalc.create_describable(5) as DynDescribable + val measurable = Rustcalc.create_measurable(7) as DynMeasurable + TraitConsumer(describable).use { consumer -> + val result = consumer.measure_from(measurable) + assertTrue(result.contains("7"), "Should contain measurement, got: $result") + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TraitConsumer edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge dyn param - constructor with zero value source`() { + val source = Rustcalc.create_describable(0) as DynDescribable + TraitConsumer(source).use { consumer -> + assertTrue(consumer.description.contains("0")) + } + } + + @Test fun `edge dyn param - constructor with negative value source`() { + val source = Rustcalc.create_describable(-500) as DynDescribable + TraitConsumer(source).use { consumer -> + assertTrue(consumer.description.contains("-500")) + } + } + + @Test fun `edge dyn param - constructor with MAX_VALUE source`() { + val source = Rustcalc.create_describable(Int.MAX_VALUE) as DynDescribable + TraitConsumer(source).use { consumer -> + assertTrue(consumer.description.contains("${Int.MAX_VALUE}")) + } + } + + @Test fun `edge dyn param - constructor with MIN_VALUE source`() { + val source = Rustcalc.create_describable(Int.MIN_VALUE) as DynDescribable + TraitConsumer(source).use { consumer -> + assertTrue(consumer.description.contains("${Int.MIN_VALUE}")) + } + } + + @Test fun `edge dyn param - multiple update_from calls`() { + val source = Rustcalc.create_describable(1) as DynDescribable + TraitConsumer(source).use { consumer -> + repeat(10) { i -> + val newSource = Rustcalc.create_describable((i + 1) * 100) as DynDescribable + consumer.update_from(newSource) + assertTrue(consumer.description.contains("${(i + 1) * 100}")) + } + } + } + + @Test fun `edge dyn param - different trait types on same consumer`() { + val desc = Rustcalc.create_describable(42) as DynDescribable + val meas = Rustcalc.create_measurable(7) as DynMeasurable + TraitConsumer(desc).use { consumer -> + val result = consumer.measure_from(meas) + assertTrue(result.contains("42"), "Should contain description, got: $result") + assertTrue(result.contains("7"), "Should contain measurement, got: $result") + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TraitConsumer load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K TraitConsumer constructor calls`() { + val source = Rustcalc.create_describable(42) as DynDescribable + repeat(100_000) { + TraitConsumer(source).use { consumer -> + assertTrue(consumer.description.contains("42")) + } + } + } + + @Test fun `load - 100K update_from calls on single instance`() { + val source = Rustcalc.create_describable(7) as DynDescribable + TraitConsumer(source).use { consumer -> + repeat(100_000) { + consumer.update_from(source) + assertTrue(consumer.description.contains("7")) + } + } + } + + @Test fun `load - 100K measure_from calls on single instance`() { + val source = Rustcalc.create_describable(1) as DynDescribable + val meas = Rustcalc.create_measurable(2) as DynMeasurable + TraitConsumer(source).use { consumer -> + repeat(100_000) { + val result = consumer.measure_from(meas) + assertTrue(result.contains("2")) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // TraitConsumer concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K TraitConsumer constructions`() { + val threads = (1..10).map { tid -> + Thread { + val source = Rustcalc.create_describable(tid * 100) as DynDescribable + repeat(10_000) { + TraitConsumer(source).use { consumer -> + assertTrue(consumer.description.contains("${tid * 100}")) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K measure_from calls`() { + val threads = (1..10).map { tid -> + Thread { + val desc = Rustcalc.create_describable(tid) as DynDescribable + val meas = Rustcalc.create_measurable(tid * 10) as DynMeasurable + TraitConsumer(desc).use { consumer -> + repeat(10_000) { + val result = consumer.measure_from(meas) + assertTrue(result.contains("${tid * 10}")) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/NullableTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/NullableTest.kt new file mode 100644 index 00000000..d5d5c7b1 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/NullableTest.kt @@ -0,0 +1,222 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.assertFalse + +class NullableTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Nullable returns (Option) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `nullable Int return - non-null`() { + Calculator(10).use { calc -> assertEquals(5, calc.divide_or_null(2)) } + } + + @Test fun `nullable Int return - null`() { + Calculator(10).use { calc -> assertNull(calc.divide_or_null(0)) } + } + + @Test fun `nullable String return - non-null`() { + Calculator(5).use { calc -> assertEquals("Positive(5)", calc.describe_or_null()) } + } + + @Test fun `nullable String return - null`() { + Calculator(0).use { calc -> assertNull(calc.describe_or_null()) } + } + + @Test fun `nullable Boolean return - true`() { + Calculator(5).use { calc -> assertEquals(true, calc.is_positive_or_null()) } + } + + @Test fun `nullable Boolean return - false`() { + Calculator(-1).use { calc -> assertEquals(false, calc.is_positive_or_null()) } + } + + @Test fun `nullable Boolean return - null`() { + Calculator(0).use { calc -> assertNull(calc.is_positive_or_null()) } + } + + @Test fun `nullable Long return - non-null`() { + Calculator(42).use { calc -> assertEquals(42L, calc.to_long_or_null()) } + } + + @Test fun `nullable Long return - null`() { + Calculator(0).use { calc -> assertNull(calc.to_long_or_null()) } + } + + @Test fun `nullable Double return - non-null`() { + Calculator(7).use { calc -> assertEquals(7.0, calc.to_double_or_null()!!, 0.001) } + } + + @Test fun `nullable Double return - null`() { + Calculator(0).use { calc -> assertNull(calc.to_double_or_null()) } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Nullable params (Option) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `nullable Int param - non-null`() { + Calculator(10).use { calc -> assertEquals(15, calc.add_optional(5)) } + } + + @Test fun `nullable Int param - null`() { + Calculator(10).use { calc -> assertEquals(10, calc.add_optional(null)) } + } + + @Test fun `nullable String param - set and get`() { + Calculator(0).use { calc -> + assertNull(calc.get_nickname()) + calc.set_nickname("Rusty") + assertEquals("Rusty", calc.get_nickname()) + calc.set_nickname(null) + assertNull(calc.get_nickname()) + } + } + + @Test fun `nullable String param - unicode`() { + Calculator(0).use { calc -> + calc.set_nickname("こんにちは") + assertEquals("こんにちは", calc.get_nickname()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge null - divide_or_null with 1`() { + Calculator(42).use { calc -> assertEquals(42, calc.divide_or_null(1)) } + } + + @Test fun `edge null - divide_or_null with negative divisor`() { + Calculator(10).use { calc -> assertEquals(-5, calc.divide_or_null(-2)) } + } + + @Test fun `edge null - describe_or_null with negative`() { + Calculator(-5).use { calc -> assertNull(calc.describe_or_null()) } + } + + @Test fun `edge null - describe_or_null with MAX_VALUE`() { + Calculator(Int.MAX_VALUE).use { calc -> + val desc = calc.describe_or_null() + assertTrue(desc!!.contains("${Int.MAX_VALUE}")) + } + } + + @Test fun `edge null - to_long_or_null with large value`() { + Calculator(Int.MAX_VALUE).use { calc -> + assertEquals(Int.MAX_VALUE.toLong(), calc.to_long_or_null()) + } + } + + @Test fun `edge null - nullable transitions`() { + Calculator(0).use { calc -> + assertNull(calc.get_nickname()) + calc.set_nickname("a") + assertEquals("a", calc.get_nickname()) + calc.set_nickname(null) + assertNull(calc.get_nickname()) + calc.set_nickname("b") + assertEquals("b", calc.get_nickname()) + } + } + + @Test fun `edge null - nullable DataClass param non-null`() { + Calculator(0).use { calc -> + val result = calc.add_point_or_null(Point(3, 7)) + assertEquals(10, result) + } + } + + @Test fun `edge null - nullable DataClass param null`() { + Calculator(5).use { calc -> + val result = calc.add_point_or_null(null) + assertEquals(5, result) + } + } + + @Test fun `edge null - nickname with emoji`() { + Calculator(0).use { calc -> + calc.set_nickname("🔥💯🚀") + assertEquals("🔥💯🚀", calc.get_nickname()) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K divide_or_null calls`() { + Calculator(100).use { calc -> + repeat(100_000) { + assertEquals(50, calc.divide_or_null(2)) + } + } + } + + @Test fun `load - 100K describe_or_null calls`() { + Calculator(1).use { calc -> + repeat(100_000) { + val desc = calc.describe_or_null() + assertTrue(desc!!.contains("1")) + } + } + } + + @Test fun `load - 100K nullable String transitions`() { + Calculator(0).use { calc -> + repeat(100_000) { i -> + if (i % 2 == 0) { + calc.set_nickname("n$i") + } else { + calc.set_nickname(null) + assertNull(calc.get_nickname()) + } + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K divide_or_null`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid * 100).use { calc -> + repeat(10_000) { + val result = calc.divide_or_null(2) + assertEquals(tid * 50, result) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K nullable String`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(0).use { calc -> + repeat(10_000) { i -> + if (i % 2 == 0) { + calc.set_nickname("t$tid") + assertEquals("t$tid", calc.get_nickname()) + } else { + calc.set_nickname(null) + assertNull(calc.get_nickname()) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ProcessingStatusTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ProcessingStatusTest.kt new file mode 100644 index 00000000..d36b2758 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/ProcessingStatusTest.kt @@ -0,0 +1,311 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ProcessingStatusTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // get_processing_status — struct variants returned from Rust + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `get_processing_status zero returns Idle`() { + Calculator(0).use { calc -> + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.Idle) + assertEquals(ProcessingStatus.Tag.Idle, status.tag) + } + } + } + + @Test fun `get_processing_status negative returns FrameError with named fields`() { + Calculator(-5).use { calc -> + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.FrameError) + val fe = status as ProcessingStatus.FrameError + assertEquals("input_5", fe.src) + assertEquals("output", fe.destination) + assertEquals("negative value: -5", fe.error) + } + } + } + + @Test fun `get_processing_status large returns OperationFailed with enum field`() { + Calculator(200).use { calc -> + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + val of = status as ProcessingStatus.OperationFailed + // Default last_operation is Add + assertEquals(Operation.Add, of.operation) + assertEquals(200, of.code) + assertEquals("overflow", of.message) + } + } + } + + @Test fun `get_processing_status small positive returns Progress`() { + Calculator(42).use { calc -> + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.Progress) + val p = status as ProcessingStatus.Progress + assertEquals(42, p.step) + assertEquals(100, p.total) + assertEquals("", p.label) + assertFalse(p.done) + } + } + } + + @Test fun `get_processing_status with label returns Progress with label`() { + Calculator(10).use { calc -> + calc.label = "processing" + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.Progress) + assertEquals("processing", (status as ProcessingStatus.Progress).label) + } + } + } + + @Test fun `get_processing_status after apply_op has correct operation`() { + Calculator(200).use { calc -> + calc.apply_op(Operation.Multiply, 1) + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(Operation.Multiply, (status as ProcessingStatus.OperationFailed).operation) + } + } + } + + @Test fun `get_processing_status after subtract has Subtract operation`() { + Calculator(500).use { calc -> + calc.apply_op(Operation.Subtract, 1) + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(Operation.Subtract, (status as ProcessingStatus.OperationFailed).operation) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Factory methods — construct struct variants from Kotlin + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory FrameError with named string fields`() { + ProcessingStatus.frameError("camera_0", "/tmp/output.raw", "permission denied").use { status -> + assertTrue(status is ProcessingStatus.FrameError) + assertEquals("camera_0", status.src) + assertEquals("/tmp/output.raw", status.destination) + assertEquals("permission denied", status.error) + assertEquals(ProcessingStatus.Tag.FrameError, status.tag) + } + } + + @Test fun `factory OperationFailed with enum field`() { + ProcessingStatus.operationFailed(Operation.Multiply, 42, "overflow").use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(Operation.Multiply, status.operation) + assertEquals(42, status.code) + assertEquals("overflow", status.message) + assertEquals(ProcessingStatus.Tag.OperationFailed, status.tag) + } + } + + @Test fun `factory OperationFailed with each Operation variant`() { + for (op in Operation.entries) { + ProcessingStatus.operationFailed(op, 1, "test").use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(op, status.operation) + } + } + } + + @Test fun `factory Progress with all field types`() { + ProcessingStatus.progress(3, 10, "step three", true).use { status -> + assertTrue(status is ProcessingStatus.Progress) + assertEquals(3, status.step) + assertEquals(10, status.total) + assertEquals("step three", status.label) + assertTrue(status.done) + } + } + + @Test fun `factory Progress with false done`() { + ProcessingStatus.progress(1, 100, "starting", false).use { status -> + assertTrue(status is ProcessingStatus.Progress) + assertFalse(status.done) + } + } + + @Test fun `factory Idle`() { + ProcessingStatus.idle().use { status -> + assertTrue(status is ProcessingStatus.Idle) + assertEquals(ProcessingStatus.Tag.Idle, status.tag) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge - FrameError with empty strings`() { + ProcessingStatus.frameError("", "", "").use { status -> + assertTrue(status is ProcessingStatus.FrameError) + assertEquals("", status.src) + assertEquals("", status.destination) + assertEquals("", status.error) + } + } + + @Test fun `edge - FrameError with unicode`() { + ProcessingStatus.frameError("ソース", "出力先", "エラー 🔥").use { status -> + assertTrue(status is ProcessingStatus.FrameError) + assertEquals("ソース", status.src) + assertEquals("出力先", status.destination) + assertEquals("エラー 🔥", status.error) + } + } + + @Test fun `edge - FrameError with long strings`() { + val longStr = "x".repeat(10_000) + ProcessingStatus.frameError(longStr, longStr, longStr).use { status -> + assertTrue(status is ProcessingStatus.FrameError) + assertEquals(longStr, status.src) + assertEquals(longStr, status.destination) + assertEquals(longStr, status.error) + } + } + + @Test fun `edge - OperationFailed with zero code`() { + ProcessingStatus.operationFailed(Operation.Add, 0, "none").use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(0, status.code) + } + } + + @Test fun `edge - OperationFailed with MAX_VALUE code`() { + ProcessingStatus.operationFailed(Operation.Add, Int.MAX_VALUE, "max").use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(Int.MAX_VALUE, status.code) + } + } + + @Test fun `edge - OperationFailed with MIN_VALUE code`() { + ProcessingStatus.operationFailed(Operation.Add, Int.MIN_VALUE, "min").use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(Int.MIN_VALUE, status.code) + } + } + + @Test fun `edge - Progress with boundary values`() { + ProcessingStatus.progress(0, 0, "", false).use { status -> + assertTrue(status is ProcessingStatus.Progress) + assertEquals(0, status.step) + assertEquals(0, status.total) + } + } + + @Test fun `edge - all tags cycle`() { + ProcessingStatus.frameError("a", "b", "c").use { assertEquals(ProcessingStatus.Tag.FrameError, it.tag) } + ProcessingStatus.operationFailed(Operation.Add, 1, "x").use { assertEquals(ProcessingStatus.Tag.OperationFailed, it.tag) } + ProcessingStatus.progress(1, 2, "l", true).use { assertEquals(ProcessingStatus.Tag.Progress, it.tag) } + ProcessingStatus.idle().use { assertEquals(ProcessingStatus.Tag.Idle, it.tag) } + } + + @Test fun `edge - lifecycle create and close many`() { + repeat(50) { i -> + ProcessingStatus.frameError("src$i", "dst$i", "err$i").use { status -> + assertTrue(status is ProcessingStatus.FrameError) + assertEquals("src$i", status.src) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K factory FrameError calls`() { + repeat(100_000) { + ProcessingStatus.frameError("src", "dst", "err").use { status -> + assertTrue(status is ProcessingStatus.FrameError) + } + } + } + + @Test fun `load - 100K factory OperationFailed with enum field`() { + repeat(100_000) { + ProcessingStatus.operationFailed(Operation.Multiply, 42, "msg").use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(Operation.Multiply, status.operation) + } + } + } + + @Test fun `load - 100K get_processing_status calls`() { + Calculator(-1).use { calc -> + repeat(100_000) { + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.FrameError) + } + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K factory FrameError`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + ProcessingStatus.frameError("src$tid", "dst$tid", "err$tid").use { status -> + assertTrue(status is ProcessingStatus.FrameError) + assertEquals("src$tid", status.src) + assertEquals("dst$tid", status.destination) + assertEquals("err$tid", status.error) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K factory OperationFailed with enum`() { + val ops = Operation.entries + val threads = (1..10).map { tid -> + Thread { + val op = ops[tid % ops.size] + repeat(10_000) { + ProcessingStatus.operationFailed(op, tid, "m$tid").use { status -> + assertTrue(status is ProcessingStatus.OperationFailed) + assertEquals(op, status.operation) + assertEquals(tid, status.code) + assertEquals("m$tid", status.message) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K get_processing_status`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid * -10).use { calc -> + repeat(10_000) { + calc.get_processing_status().use { status -> + assertTrue(status is ProcessingStatus.FrameError) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/SealedEnumTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/SealedEnumTest.kt new file mode 100644 index 00000000..998fea7f --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/SealedEnumTest.kt @@ -0,0 +1,280 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SealedEnumTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // CalcResult sealed class basics + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `try_divide exact returns Value`() { + Calculator(10).use { calc -> + val result = calc.try_divide(2) + assertTrue(result is CalcResult.Value, "Expected Value, got ${result::class.simpleName}") + assertEquals(5, (result as CalcResult.Value).value) + result.close() + } + } + + @Test fun `try_divide by zero returns Error`() { + Calculator(10).use { calc -> + val result = calc.try_divide(0) + assertTrue(result is CalcResult.Error, "Expected Error, got ${result::class.simpleName}") + assertEquals("Division by zero", (result as CalcResult.Error).value) + result.close() + } + } + + @Test fun `try_divide on zero accumulator returns Nothing`() { + Calculator(0).use { calc -> + val result = calc.try_divide(5) + assertTrue(result is CalcResult.Nothing, "Expected Nothing, got ${result::class.simpleName}") + result.close() + } + } + + @Test fun `try_divide inexact returns Partial`() { + Calculator(10).use { calc -> + val result = calc.try_divide(3) + assertTrue(result is CalcResult.Partial, "Expected Partial, got ${result::class.simpleName}") + val partial = result as CalcResult.Partial + assertEquals(3, partial.value) // 10 / 3 = 3 + assertTrue(partial.confidence > 0.0 && partial.confidence < 1.0) + result.close() + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Tag enum + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `tag returns correct enum value`() { + Calculator(10).use { calc -> + calc.try_divide(2).use { result -> + assertEquals(CalcResult.Tag.Value, result.tag) + } + calc.try_divide(0).use { result -> + assertEquals(CalcResult.Tag.Error, result.tag) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // last_result + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `last_result positive returns Value`() { + Calculator(42).use { calc -> + calc.last_result().use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(42, (result as CalcResult.Value).value) + } + } + } + + @Test fun `last_result zero returns Nothing`() { + Calculator(0).use { calc -> + calc.last_result().use { result -> + assertTrue(result is CalcResult.Nothing) + } + } + } + + @Test fun `last_result negative returns Error`() { + Calculator(-5).use { calc -> + calc.last_result().use { result -> + assertTrue(result is CalcResult.Error) + assertTrue((result as CalcResult.Error).value.contains("Negative")) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Factory methods (construct from Kotlin) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `factory method Value`() { + CalcResult.value(99).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(99, result.value) + } + } + + @Test fun `factory method Error`() { + CalcResult.error("test error").use { result -> + assertTrue(result is CalcResult.Error) + assertEquals("test error", result.value) + } + } + + @Test fun `factory method Partial`() { + CalcResult.partial(50, 0.75).use { result -> + assertTrue(result is CalcResult.Partial) + assertEquals(50, result.value) + assertEquals(0.75, result.confidence, 0.001) + } + } + + @Test fun `factory method Nothing`() { + CalcResult.nothing().use { result -> + assertTrue(result is CalcResult.Nothing) + assertEquals(CalcResult.Tag.Nothing, result.tag) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge sealed - try_divide with 1`() { + Calculator(42).use { calc -> + calc.try_divide(1).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(42, (result as CalcResult.Value).value) + } + } + } + + @Test fun `edge sealed - try_divide with -1`() { + Calculator(10).use { calc -> + calc.try_divide(-1).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(-10, (result as CalcResult.Value).value) + } + } + } + + @Test fun `edge sealed - Error with empty message`() { + CalcResult.error("").use { result -> + assertTrue(result is CalcResult.Error) + assertEquals("", result.value) + } + } + + @Test fun `edge sealed - Error with unicode message`() { + CalcResult.error("エラー 🔥").use { result -> + assertTrue(result is CalcResult.Error) + assertEquals("エラー 🔥", result.value) + } + } + + @Test fun `edge sealed - Partial with zero confidence`() { + CalcResult.partial(0, 0.0).use { result -> + assertTrue(result is CalcResult.Partial) + assertEquals(0, result.value) + assertEquals(0.0, result.confidence, 0.001) + } + } + + @Test fun `edge sealed - Partial with max confidence`() { + CalcResult.partial(100, 1.0).use { result -> + assertTrue(result is CalcResult.Partial) + assertEquals(1.0, result.confidence, 0.001) + } + } + + @Test fun `edge sealed - Value with MAX_VALUE`() { + CalcResult.value(Int.MAX_VALUE).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(Int.MAX_VALUE, result.value) + } + } + + @Test fun `edge sealed - Value with MIN_VALUE`() { + CalcResult.value(Int.MIN_VALUE).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(Int.MIN_VALUE, result.value) + } + } + + @Test fun `edge sealed - all tags cycle`() { + Calculator(10).use { calc -> + calc.try_divide(2).use { assertEquals(CalcResult.Tag.Value, it.tag) } + calc.try_divide(0).use { assertEquals(CalcResult.Tag.Error, it.tag) } + calc.try_divide(3).use { assertEquals(CalcResult.Tag.Partial, it.tag) } + } + Calculator(0).use { calc -> + calc.try_divide(1).use { assertEquals(CalcResult.Tag.Nothing, it.tag) } + } + } + + @Test fun `edge sealed - lifecycle create and close many`() { + repeat(50) { i -> + CalcResult.value(i).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(i, result.value) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K try_divide calls`() { + Calculator(100).use { calc -> + repeat(100_000) { + calc.try_divide(2).use { result -> + assertTrue(result is CalcResult.Value) + } + } + } + } + + @Test fun `load - 100K factory Value calls`() { + repeat(100_000) { + CalcResult.value(42).use { result -> + assertTrue(result is CalcResult.Value) + } + } + } + + @Test fun `load - 100K last_result calls`() { + Calculator(1).use { calc -> + repeat(100_000) { + calc.last_result().use { result -> + assertTrue(result is CalcResult.Value) + } + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K try_divide`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid * 10).use { calc -> + repeat(10_000) { + calc.try_divide(2).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(tid * 5, (result as CalcResult.Value).value) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K factory methods`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + CalcResult.value(tid).use { result -> + assertTrue(result is CalcResult.Value) + assertEquals(tid, result.value) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/SuspendTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/SuspendTest.kt new file mode 100644 index 00000000..c406abb0 --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/SuspendTest.kt @@ -0,0 +1,153 @@ +package com.example.rustcalculator + +import kotlinx.coroutines.runBlocking +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.assertFalse + +class SuspendTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // Basic suspend functions + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `suspend delayed_add returns correct result`() = runBlocking { + Calculator(10).use { calc -> + val result = calc.delayed_add(5, 50) + assertEquals(15, result) + } + } + + @Test fun `suspend delayed_describe returns string`() = runBlocking { + Calculator(42).use { calc -> + val result = calc.delayed_describe(50) + assertEquals("Calculator(current=42)", result) + } + } + + @Test fun `suspend fail_after_delay throws exception`() = runBlocking { + Calculator(0).use { calc -> + var threw = false + try { + calc.fail_after_delay(50) + } catch (e: KotlinNativeException) { + threw = true + } + assertTrue(threw, "Expected KotlinNativeException") + } + } + + @Test fun `suspend delayed_noop completes`() = runBlocking { + Calculator(0).use { calc -> + calc.delayed_noop(50) + } + } + + @Test fun `suspend delayed_is_positive returns bool`() = runBlocking { + Calculator(5).use { calc -> + assertTrue(calc.delayed_is_positive(50)) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge suspend - delayed_add with zero`() = runBlocking { + Calculator(0).use { calc -> + assertEquals(0, calc.delayed_add(0, 10)) + } + } + + @Test fun `edge suspend - delayed_add with negative`() = runBlocking { + Calculator(10).use { calc -> + assertEquals(5, calc.delayed_add(-5, 10)) + } + } + + @Test fun `edge suspend - delayed_is_positive false`() = runBlocking { + Calculator(-1).use { calc -> + assertFalse(calc.delayed_is_positive(10)) + } + } + + @Test fun `edge suspend - delayed_describe with zero`() = runBlocking { + Calculator(0).use { calc -> + assertEquals("Calculator(current=0)", calc.delayed_describe(10)) + } + } + + @Test fun `edge suspend - recovery after exception`() = runBlocking { + Calculator(10).use { calc -> + try { calc.fail_after_delay(10) } catch (_: KotlinNativeException) {} + val result = calc.delayed_add(5, 10) + assertEquals(15, result) + } + } + + @Test fun `edge suspend - sequential delayed_add calls`() = runBlocking { + Calculator(0).use { calc -> + assertEquals(1, calc.delayed_add(1, 10)) + assertEquals(3, calc.delayed_add(2, 10)) + assertEquals(6, calc.delayed_add(3, 10)) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K delayed_add calls`() = runBlocking { + Calculator(0).use { calc -> + repeat(100_000) { + calc.delayed_add(0, 0) + } + } + } + + @Test fun `load - 100K delayed_is_positive calls`() = runBlocking { + Calculator(1).use { calc -> + repeat(100_000) { + assertTrue(calc.delayed_is_positive(0)) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 1K delayed_add`() { + val threads = (1..10).map { tid -> + Thread { + runBlocking { + Calculator(tid).use { calc -> + repeat(1_000) { + calc.delayed_add(0, 0) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 1K delayed_describe`() { + val threads = (1..10).map { tid -> + Thread { + runBlocking { + Calculator(tid).use { calc -> + repeat(1_000) { + val desc = calc.delayed_describe(0) + assertTrue(desc.contains("$tid")) + } + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/TopLevelFunctionTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/TopLevelFunctionTest.kt new file mode 100644 index 00000000..e1de7a9a --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/TopLevelFunctionTest.kt @@ -0,0 +1,121 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class TopLevelFunctionTest { + + // ═══════════════════════════════════════════════════════════════════════════ + // compute + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `compute Add`() { + assertEquals(7, Rustcalc.compute(3, 4, Operation.Add)) + } + + @Test fun `compute Subtract`() { + assertEquals(7, Rustcalc.compute(10, 3, Operation.Subtract)) + } + + @Test fun `compute Multiply`() { + assertEquals(12, Rustcalc.compute(3, 4, Operation.Multiply)) + } + + @Test fun `compute with zero`() { + assertEquals(5, Rustcalc.compute(0, 5, Operation.Add)) + } + + @Test fun `compute with negative`() { + assertEquals(-7, Rustcalc.compute(-3, -4, Operation.Add)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // greet + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `greet returns formatted message`() { + assertEquals("Hello, World!", Rustcalc.greet("World")) + } + + @Test fun `greet with unicode`() { + assertEquals("Hello, 世界!", Rustcalc.greet("世界")) + } + + @Test fun `greet with empty string`() { + assertEquals("Hello, !", Rustcalc.greet("")) + } + + @Test fun `greet with emoji`() { + assertEquals("Hello, 🚀!", Rustcalc.greet("🚀")) + } + + @Test fun `greet with long string`() { + val name = "A".repeat(5000) + assertEquals("Hello, $name!", Rustcalc.greet(name)) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Edge cases + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `edge top - compute MAX_VALUE`() { + assertEquals(Int.MAX_VALUE, Rustcalc.compute(Int.MAX_VALUE, 0, Operation.Add)) + } + + @Test fun `edge top - compute Multiply by zero`() { + assertEquals(0, Rustcalc.compute(Int.MAX_VALUE, 0, Operation.Multiply)) + } + + @Test fun `edge top - compute all operations`() { + for (op in Operation.entries) { + Rustcalc.compute(10, 5, op) // should not throw + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Load tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `load - 100K compute calls`() { + repeat(100_000) { + assertEquals(7, Rustcalc.compute(3, 4, Operation.Add)) + } + } + + @Test fun `load - 100K greet calls`() { + repeat(100_000) { + assertEquals("Hello, World!", Rustcalc.greet("World")) + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Concurrency tests + // ═══════════════════════════════════════════════════════════════════════════ + + @Test fun `concurrent - 10 threads x 10K compute`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + val result = Rustcalc.compute(tid, tid, Operation.Add) + assertEquals(tid * 2, result) + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test fun `concurrent - 10 threads x 10K greet`() { + val threads = (1..10).map { tid -> + Thread { + repeat(10_000) { + val result = Rustcalc.greet("t$tid") + assertEquals("Hello, t$tid!", result) + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/TupleTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/TupleTest.kt new file mode 100644 index 00000000..694368ee --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/TupleTest.kt @@ -0,0 +1,550 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals + +class TupleTest { + + @Test + fun `get_coordinates returns correct tuple`() { + Calculator(5).use { calc -> + val coords = calc.get_coordinates() + assertEquals(5, coords._0) + assertEquals(10, coords._1) + } + } + + @Test + fun `get_coordinates with zero accumulator`() { + Calculator(0).use { calc -> + val coords = calc.get_coordinates() + assertEquals(0, coords._0) + assertEquals(0, coords._1) + } + } + + @Test + fun `get_triple returns correct tuple`() { + Calculator(5).use { calc -> + calc.label = "test" + val triple = calc.get_triple() + assertEquals(5, triple._0) + assertEquals("test", triple._1) + assertEquals(true, triple._2) + } + } + + @Test + fun `get_triple with empty label`() { + Calculator(5).use { calc -> + val triple = calc.get_triple() + assertEquals(5, triple._0) + assertEquals("", triple._1) + assertEquals(true, triple._2) + } + } + + @Test + fun `sum_tuple adds coordinates to accumulator`() { + Calculator(5).use { calc -> + val result = calc.sum_tuple(KneTuple2_TII(1, 2)) + assertEquals(8, result) + } + } + + @Test + fun `sum_tuple with zero coordinates`() { + Calculator(10).use { calc -> + val result = calc.sum_tuple(KneTuple2_TII(0, 0)) + assertEquals(10, result) + } + } + + // ── Nested tuple tests ────────────────────────────────────────────────── + + @Test + fun `get_nested_tuple returns correct nested tuple`() { + Calculator(5).use { calc -> + calc.label = "nested" + val result = calc.get_nested_tuple() + assertEquals(5, result._0) + assertEquals("nested", result._1._0) + assertEquals(true, result._1._1) + } + } + + @Test + fun `get_nested_tuple with empty label`() { + Calculator(5).use { calc -> + val result = calc.get_nested_tuple() + assertEquals(5, result._0) + assertEquals("", result._1._0) + assertEquals(true, result._1._1) + } + } + + @Test + fun `get_nested_tuple with disabled state`() { + Calculator(5).use { calc -> + calc.label = "test" + calc.enabled = false + val result = calc.get_nested_tuple() + assertEquals(5, result._0) + assertEquals("test", result._1._0) + assertEquals(false, result._1._1) + } + } + + @Test + fun `get_nested_tuple with unicode string`() { + Calculator(5).use { calc -> + calc.label = "héllo wörld 🌍" + val result = calc.get_nested_tuple() + assertEquals("héllo wörld 🌍", result._1._0) + } + } + + // ── 3-level deep nesting ──────────────────────────────────────────────── + + @Test + fun `get_deep_tuple returns correct 3-level nested tuple`() { + Calculator(7).use { calc -> + calc.label = "deep" + val result = calc.get_deep_tuple() + assertEquals(7, result._0) + assertEquals("deep", result._1._0) + assertEquals(true, result._1._1._0) + assertEquals(21, result._1._1._1) // 7 * 3 + } + } + + @Test + fun `get_deep_tuple with empty label`() { + Calculator(3).use { calc -> + val result = calc.get_deep_tuple() + assertEquals(3, result._0) + assertEquals("", result._1._0) + assertEquals(true, result._1._1._0) + assertEquals(9, result._1._1._1) + } + } + + @Test + fun `get_deep_tuple with disabled state`() { + Calculator(4).use { calc -> + calc.label = "x" + calc.enabled = false + val result = calc.get_deep_tuple() + assertEquals(4, result._0) + assertEquals("x", result._1._0) + assertEquals(false, result._1._1._0) + assertEquals(12, result._1._1._1) + } + } + + // ── Double nested (two sibling nested tuples) ─────────────────────────── + + @Test + fun `get_double_nested returns two nested tuples`() { + Calculator(5).use { calc -> + calc.label = "double" + val result = calc.get_double_nested() + assertEquals(5, result._0._0) + assertEquals(10, result._0._1) + assertEquals("double", result._1._0) + assertEquals(true, result._1._1) + } + } + + @Test + fun `get_double_nested with empty label`() { + Calculator(3).use { calc -> + val result = calc.get_double_nested() + assertEquals(3, result._0._0) + assertEquals(6, result._0._1) + assertEquals("", result._1._0) + assertEquals(true, result._1._1) + } + } + + @Test + fun `get_double_nested with zero accumulator`() { + Calculator(0).use { calc -> + calc.label = "zero" + val result = calc.get_double_nested() + assertEquals(0, result._0._0) + assertEquals(0, result._0._1) + assertEquals("zero", result._1._0) + assertEquals(true, result._1._1) + } + } + + // ── Typed nested (f64, i32 inside) ────────────────────────────────────── + + @Test + fun `get_typed_nested returns correct long and nested double+int`() { + Calculator(5).use { calc -> + calc.scale = 2.5 + val result = calc.get_typed_nested() + assertEquals(5000L, result._0) + assertEquals(2.5, result._1._0) + assertEquals(5, result._1._1) + } + } + + @Test + fun `get_typed_nested with zero scale`() { + Calculator(0).use { calc -> + calc.scale = 0.0 + val result = calc.get_typed_nested() + assertEquals(0L, result._0) + assertEquals(0.0, result._1._0) + assertEquals(0, result._1._1) + } + } + + @Test + fun `get_typed_nested with negative scale`() { + Calculator(3).use { calc -> + calc.scale = -1.5 + val result = calc.get_typed_nested() + assertEquals(3000L, result._0) + assertEquals(-1.5, result._1._0) + assertEquals(3, result._1._1) + } + } + + // ── Repeated calls (memory leak regression) ───────────────────────────── + + @Test + fun `repeated nested tuple calls do not crash`() { + Calculator(1).use { calc -> + calc.label = "repeat" + repeat(100) { + val result = calc.get_nested_tuple() + assertEquals("repeat", result._1._0) + } + } + } + + @Test + fun `repeated deep tuple calls do not crash`() { + Calculator(2).use { calc -> + calc.label = "stress" + repeat(100) { + val result = calc.get_deep_tuple() + assertEquals("stress", result._1._0) + assertEquals(6, result._1._1._1) + } + } + } + + @Test + fun `repeated double nested calls do not crash`() { + Calculator(3).use { calc -> + calc.label = "multi" + repeat(100) { + val result = calc.get_double_nested() + assertEquals("multi", result._1._0) + assertEquals(3, result._0._0) + } + } + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Tuple with Vec element + // ═══════════════════════════════════════════════════════════════════════════ + + @Test + fun `tuple - get_with_scores returns list and string`() { + Calculator(10).use { calc -> + calc.label = "test" + val result = calc.get_with_scores() + assertEquals(listOf(10, 20, 30), result._0) + assertEquals("test", result._1) + } + } + + @Test + fun `tuple - get_with_scores with zero`() { + Calculator(0).use { calc -> + calc.label = "" + val result = calc.get_with_scores() + assertEquals(listOf(0, 0, 0), result._0) + assertEquals("", result._1) + } + } + + @Test + fun `tuple - get_with_scores with negative`() { + Calculator(-5).use { calc -> + calc.label = "neg" + val result = calc.get_with_scores() + assertEquals(listOf(-5, -10, -15), result._0) + assertEquals("neg", result._1) + } + } + + @Test + fun `tuple - get_with_scores repeated calls`() { + Calculator(7).use { calc -> + calc.label = "repeat" + repeat(100) { + val result = calc.get_with_scores() + assertEquals(3, result._0.size) + assertEquals(7, result._0[0]) + assertEquals("repeat", result._1) + } + } + } + + @Test + fun `edge tuple - get_with_scores with MAX_VALUE`() { + Calculator(Int.MAX_VALUE).use { calc -> + calc.label = "max" + val result = calc.get_with_scores() + assertEquals(Int.MAX_VALUE, result._0[0]) + assertEquals("max", result._1) + } + } + + @Test + fun `edge tuple - get_with_scores with MIN_VALUE`() { + Calculator(Int.MIN_VALUE).use { calc -> + calc.label = "min" + val result = calc.get_with_scores() + assertEquals(Int.MIN_VALUE, result._0[0]) + assertEquals("min", result._1) + } + } + + @Test + fun `str tuple - get_with_scores with unicode label`() { + Calculator(1).use { calc -> + calc.label = "\u00e9\u00e0\u00fc\u00f1 \u4e16\u754c \ud83d\ude80" + val result = calc.get_with_scores() + assertEquals(listOf(1, 2, 3), result._0) + assertEquals("\u00e9\u00e0\u00fc\u00f1 \u4e16\u754c \ud83d\ude80", result._1) + } + } + + @Test + fun `edge tuple - get_with_scores lifecycle create use close`() { + repeat(50) { + Calculator(it).use { calc -> + calc.label = "iter$it" + val result = calc.get_with_scores() + assertEquals(it, result._0[0]) + assertEquals("iter$it", result._1) + } + } + } + + @Test + fun `load - 100K get_with_scores calls`() { + Calculator(1).use { calc -> + calc.label = "load" + repeat(100_000) { + val result = calc.get_with_scores() + assertEquals(listOf(1, 2, 3), result._0) + } + } + } + + @Test + fun `concurrent - 10 threads x 10K get_with_scores`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + calc.label = "t$tid" + repeat(10_000) { + val result = calc.get_with_scores() + assertEquals(listOf(tid, tid * 2, tid * 3), result._0) + assertEquals("t$tid", result._1) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + // ── MAP in tuple tests ────────────────────────────────────────────────── + + @Test + fun `tuple - get_with_metadata returns tuple with map`() { + Calculator(10).use { calc -> + val result = calc.get_with_metadata() + assertEquals(10, result._0) + assertEquals(mapOf("current" to 10, "scale" to 1, "double" to 20), result._1) + } + } + + @Test + fun `tuple - get_with_metadata with zero accumulator`() { + Calculator(0).use { calc -> + val result = calc.get_with_metadata() + assertEquals(0, result._0) + assertEquals(mapOf("current" to 0, "scale" to 1, "double" to 0), result._1) + } + } + + @Test + fun `tuple - get_with_metadata repeated calls`() { + Calculator(5).use { calc -> + repeat(100) { + val result = calc.get_with_metadata() + assertEquals(5, result._0) + assertEquals(mapOf("current" to 5, "scale" to 1, "double" to 10), result._1) + } + } + } + + // ── MAP with nested LIST values ───────────────────────────────────────── + + @Test + fun `get_metadata returns map with list values`() { + Calculator(5).use { calc -> + val result = calc.metadata + assertEquals(listOf(5, 10, 15), result["values"]) + assertEquals(listOf(1, 2, 3, 5), result["factors"]) + } + } + + @Test + fun `get_metadata with zero accumulator`() { + Calculator(0).use { calc -> + val result = calc.metadata + assertEquals(listOf(0, 0, 0), result["values"]) + assertEquals(listOf(1, 2, 3, 5), result["factors"]) + } + } + + @Test + fun `get_with_metadata_map returns tuple with nested map`() { + Calculator(3).use { calc -> + calc.label = "hello" + val result = calc.get_with_metadata_map() + assertEquals("hello", result._0) + assertEquals(listOf(3, 6), result._1["values"]) + assertEquals(listOf(1, 2, 3), result._1["labels"]) + } + } + + @Test + fun `get_with_metadata_map repeated calls`() { + Calculator(2).use { calc -> + calc.label = "test" + repeat(100) { + val result = calc.get_with_metadata_map() + assertEquals("test", result._0) + assertEquals(listOf(2, 4), result._1["values"]) + } + } + } + + @Test + fun `edge nested map - negative accumulator`() { + Calculator(-7).use { calc -> + val result = calc.metadata + assertEquals(listOf(-7, -14, -21), result["values"]) + assertEquals(2, result.size) + } + } + + @Test + fun `edge nested map - MAX_VALUE accumulator`() { + Calculator(Int.MAX_VALUE).use { calc -> + val result = calc.metadata + assertEquals(Int.MAX_VALUE, result["values"]!![0]) + assertEquals(listOf(1, 2, 3, 5), result["factors"]) + } + } + + @Test + fun `edge nested map - key count and content`() { + Calculator(1).use { calc -> + val result = calc.metadata + assertEquals(setOf("values", "factors"), result.keys) + assertEquals(3, result["values"]!!.size) + assertEquals(4, result["factors"]!!.size) + } + } + + @Test + fun `edge nested tuple map - empty label`() { + Calculator(1).use { calc -> + calc.label = "" + val result = calc.get_with_metadata_map() + assertEquals("", result._0) + assertEquals(listOf(1, 2), result._1["values"]) + } + } + + @Test + fun `edge nested tuple map - unicode label`() { + Calculator(1).use { calc -> + calc.label = "日本語テスト🎉" + val result = calc.get_with_metadata_map() + assertEquals("日本語テスト🎉", result._0) + assertEquals(listOf(1, 2, 3), result._1["labels"]) + } + } + + @Test + fun `load - 100K metadata calls`() { + Calculator(42).use { calc -> + repeat(100_000) { + val result = calc.metadata + assertEquals(listOf(42, 84, 126), result["values"]) + } + } + } + + @Test + fun `load - 100K get_with_metadata_map calls`() { + Calculator(3).use { calc -> + calc.label = "load" + repeat(100_000) { + val result = calc.get_with_metadata_map() + assertEquals("load", result._0) + assertEquals(listOf(3, 6), result._1["values"]) + } + } + } + + @Test + fun `concurrent - 10 threads x 10K metadata calls`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + repeat(10_000) { + val result = calc.metadata + assertEquals(listOf(tid, tid * 2, tid * 3), result["values"]) + assertEquals(listOf(1, 2, 3, 5), result["factors"]) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } + + @Test + fun `concurrent - 10 threads x 10K get_with_metadata_map calls`() { + val threads = (1..10).map { tid -> + Thread { + Calculator(tid).use { calc -> + calc.label = "t$tid" + repeat(10_000) { + val result = calc.get_with_metadata_map() + assertEquals("t$tid", result._0) + assertEquals(listOf(tid, tid * 2), result._1["values"]) + } + } + } + } + threads.forEach { it.start() } + threads.forEach { it.join() } + } +} diff --git a/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/UnitMethodTest.kt b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/UnitMethodTest.kt new file mode 100644 index 00000000..f1ab2d0c --- /dev/null +++ b/examples/rust-calculator/src/jvmTest/kotlin/com/example/rustcalculator/UnitMethodTest.kt @@ -0,0 +1,166 @@ +package com.example.rustcalculator + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +/** + * E2E tests for unit-returning methods. + * + * These validate the fix for the regression where Rust functions with + * `output = null` (unit/`()` return type) were incorrectly skipped as + * "unsupported return type" during rustdoc JSON parsing. + * + * The methods below cover all unit-returning patterns: setters, mutating + * operations, trait impls returning unit, and suspend+unit combinations. + */ +class UnitMethodTest { + + // ── Unit-returning setter methods ──────────────────────────────────────── + + @Test fun `set_label changes the label property`() { + Calculator(0).use { calc -> + assertEquals("", calc.label) + calc.label = "MyCalc" + assertEquals("MyCalc", calc.label) + } + } + + @Test fun `set_label accepts empty string`() { + Calculator(0).use { calc -> + calc.label = "" + assertEquals("", calc.label) + } + } + + @Test fun `set_scale changes the scale property`() { + Calculator(100).use { calc -> + assertEquals(1.0, calc.scale, 0.001) + calc.scale = 2.5 + assertEquals(2.5, calc.scale, 0.001) + } + } + + @Test fun `set_enabled toggles the enabled property`() { + Calculator(0).use { calc -> + assertTrue(calc.enabled) + calc.enabled = false + assertFalse(calc.enabled) + calc.enabled = true + assertTrue(calc.enabled) + } + } + + @Test fun `set_nickname changes the nickname`() { + Calculator(0).use { calc -> + assertNull(calc.get_nickname()) + calc.set_nickname("Bob") + assertEquals("Bob", calc.get_nickname()) + } + } + + @Test fun `set_nickname accepts null to clear nickname`() { + Calculator(0).use { calc -> + calc.set_nickname("Bob") + calc.set_nickname(null) + assertNull(calc.get_nickname()) + } + } + + // ── Unit-returning mutating methods ────────────────────────────────────── + + @Test fun `reset clears accumulator to zero`() { + Calculator(0).use { calc -> + calc.add(999) + calc.reset() + assertEquals(0, calc.current) + } + } + + @Test fun `reset multiple times is idempotent`() { + Calculator(0).use { calc -> + calc.add(5) + calc.reset() + calc.reset() + calc.reset() + assertEquals(0, calc.current) + } + } + + @Test fun `set_from_named updates accumulator and label from data class`() { + Calculator(0).use { calc -> + val named = NamedValue("Counter", 42) + calc.set_from_named(named) + assertEquals(42, calc.current) + assertEquals("Counter", calc.label) + } + } + + @Test fun `set_from_named preserves accumulator when called with zero-value`() { + Calculator(100).use { calc -> + val named = NamedValue("Zero", 0) + calc.set_from_named(named) + assertEquals(0, calc.current) + assertEquals("Zero", calc.label) + } + } + + // ── Trait impl: Resettable.reset_to_default() ───────────────────────────── + + @Test fun `reset_to_default restores all fields to defaults`() { + Calculator(0).use { calc -> + calc.add(42) + calc.label = "temp" + calc.scale = 9.99 + calc.enabled = false + + calc.reset_to_default() + + assertEquals(0, calc.current) + assertEquals("", calc.label) + assertEquals(1.0, calc.scale, 0.001) + assertTrue(calc.enabled) + } + } + + // ── Chained unit-returning calls ───────────────────────────────────────── + + @Test fun `multiple unit-returning calls chain correctly`() { + Calculator(0).use { calc -> + calc.label = "Test" + calc.scale = 3.14 + calc.enabled = true + calc.add(100) + calc.reset() + + assertEquals(0, calc.current) + assertEquals("Test", calc.label) + assertEquals(3.14, calc.scale, 0.001) + assertTrue(calc.enabled) + } + } + + // ── Never-returning functions (Rust `!` type) ─────────────────────────── + + @Test + fun `panic_always throws and never returns`() { + Calculator(0).use { calc -> + assertFailsWith { + calc.panic_always() + } + } + } + + @Test + fun `panic_always does not return a value`() { + Calculator(0).use { calc -> + val thrown = assertFailsWith { + calc.panic_always() + } + assertTrue(thrown.message?.contains("crashed") == true) + } + } +} diff --git a/examples/rust-camera/build.gradle.kts b/examples/rust-camera/build.gradle.kts new file mode 100644 index 00000000..83f92b34 --- /dev/null +++ b/examples/rust-camera/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") version "1.10.2" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(compose.runtime) + implementation(compose.materialIconsExtended) + } + } + } +} + +compose.desktop { + application { + mainClass = "com.example.rustcamera.MainKt" + nativeDistributions { + packageName = "RustCamera" + } + jvmArgs("--enable-native-access=ALL-UNNAMED") + } +} + +rustImport { + libraryName = "rustcamera" + jvmPackage = "com.example.rustcamera" + buildType = "release" + crate("nokhwa", "0.10", features = listOf("input-native")) +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/App.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/App.kt new file mode 100644 index 00000000..d7c75dbb --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/App.kt @@ -0,0 +1,174 @@ +package com.example.rustcamera + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rustcamera.tabs.* + +enum class NavItem(val label: String, val icon: ImageVector) { + Preview("Preview", Icons.Default.Videocam), + Info("Info", Icons.Default.Info), + Format("Format", Icons.Default.Settings), + Formats("Formats", Icons.Default.List), + Controls("Controls", Icons.Default.Tune), +} + +@Composable +fun App() { + var selected by remember { mutableStateOf(NavItem.Preview) } + + val cameraFlow = remember { cameraStateFlow() } + val state by cameraFlow.collectAsState(initial = null) + + Row(Modifier.fillMaxSize().background(AppColors.bg)) { + Sidebar(selected) { selected = it } + + Box( + Modifier + .fillMaxSize() + .padding(start = 0.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) + ) { + when (selected) { + NavItem.Preview -> PreviewTab(state) + NavItem.Info -> InfoTab(state) + NavItem.Format -> FormatTab(state) + NavItem.Formats -> FormatsTab( + state?.compatibleFormats ?: emptyList(), + state?.currentFormat, + ) + NavItem.Controls -> ControlsTab(state?.controls ?: emptyList()) + } + } + } +} + +@Composable +private fun Sidebar(selected: NavItem, onSelect: (NavItem) -> Unit) { + Column( + modifier = Modifier + .fillMaxHeight() + .width(200.dp) + .background(AppColors.sidebarBg) + .padding(vertical = 16.dp, horizontal = 8.dp), + ) { + // App title + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .size(8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(AppColors.green) + ) + Spacer(Modifier.width(10.dp)) + Text( + "Camera", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = AppColors.textPrimary, + ) + } + Text( + " nokhwa FFM Bridge", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + + Spacer(Modifier.height(20.dp)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(12.dp)) + + NavItem.entries.forEach { item -> + SidebarItem( + item = item, + isSelected = item == selected, + onClick = { onSelect(item) }, + ) + } + + Spacer(Modifier.weight(1f)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(8.dp)) + Text( + " Powered by nokhwa 0.10", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + } +} + +@Composable +private fun SidebarItem(item: NavItem, isSelected: Boolean, onClick: () -> Unit) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + + val bgColor = when { + isSelected -> AppColors.sidebarSelected + isHovered -> AppColors.sidebarHover + else -> Color.Transparent + } + val contentColor = when { + isSelected -> AppColors.accent + isHovered -> AppColors.textPrimary + else -> AppColors.textSecondary + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .hoverable(interactionSource) + .clickable(interactionSource = interactionSource, indication = null, onClick = onClick) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = item.icon, + contentDescription = item.label, + tint = contentColor, + modifier = Modifier.size(18.dp), + ) + Spacer(Modifier.width(10.dp)) + Text( + item.label, + fontSize = 13.sp, + fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, + color = contentColor, + ) + + if (isSelected) { + Spacer(Modifier.weight(1f)) + Box( + Modifier + .width(3.dp) + .height(16.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + } + } +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/CameraData.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/CameraData.kt new file mode 100644 index 00000000..75a72d03 --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/CameraData.kt @@ -0,0 +1,45 @@ +package com.example.rustcamera + +import java.awt.image.BufferedImage + +data class CameraDeviceInfo( + val humanName: String, + val description: String, + val misc: String, + val indexDescription: String, +) + +data class FormatInfo( + val width: Int, + val height: Int, + val frameRate: Int, + val format: String, +) + +data class CompatibleFormat( + val width: Int, + val height: Int, + val frameRate: Int, + val format: String, +) + +data class ControlInfo( + val name: String, + val controlType: String, + val active: Boolean, + val flags: String, + val descriptionTag: String, + val currentValue: String, + val valueDetails: String, +) + +data class CameraState( + val isOpen: Boolean, + val fps: Int, + val frame: BufferedImage?, + val deviceInfo: CameraDeviceInfo?, + val currentFormat: FormatInfo?, + val compatibleFormats: List, + val supportedFourcc: List, + val controls: List, +) diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/CameraFlows.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/CameraFlows.kt new file mode 100644 index 00000000..58d189aa --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/CameraFlows.kt @@ -0,0 +1,169 @@ +package com.example.rustcamera + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.withContext +import java.awt.image.BufferedImage +import java.awt.image.DataBufferByte + +fun cameraStateFlow(): Flow = flow { + val format = RequestedFormatType.absoluteHighestFrameRate() + val requested = RequestedFormat.new_rgb_format(format) + val index = CameraIndex.index(0) + val cam = Camera(index, requested) + + try { + cam.open_stream() + + // Collect static info once + val deviceInfo = runCatching { + val info = cam.info() + val idx = info.index() + val idxDesc = when (idx) { + is CameraIndex.Index -> "Index(${idx.value})" + is CameraIndex.StringValue -> "String(${idx.value})" + else -> idx.toString() + } + CameraDeviceInfo( + humanName = info.human_name(), + description = info.description(), + misc = info.misc(), + indexDescription = idxDesc, + ) + }.getOrNull() + + val compatibleFormats = runCatching { + cam.compatible_camera_formats().map { fmt -> + val res = fmt.resolution() + val cf = CompatibleFormat( + width = res.width(), + height = res.height(), + frameRate = fmt.frame_rate(), + format = fmt.format().name, + ) + res.close() + cf + } + }.getOrElse { emptyList() } + + val supportedFourcc = runCatching { + cam.compatible_fourcc().map { it.name } + }.getOrElse { emptyList() } + + val controls = readControls(cam) + + val camRes = cam.resolution() + val w = camRes.width() + val h = camRes.height() + camRes.close() + + var frameCount = 0 + var lastFpsTime = System.currentTimeMillis() + var currentFps = 0 + + while (cam.is_stream_open()) { + val currentFormat = runCatching { + val fmt = cam.camera_format() + val fmtRes = fmt.resolution() + val fi = FormatInfo( + width = fmtRes.width(), + height = fmtRes.height(), + frameRate = fmt.frame_rate(), + format = fmt.format().name, + ) + fmtRes.close() + fmt.close() + fi + }.getOrNull() + + val buf = withContext(Dispatchers.Default) { cam.frame() } + val rgbBytes = ByteArray(w * h * 3) + buf.decode_image_to_buffer_rgb_format(rgbBytes) + buf.close() + + val img = BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR) + val data = (img.raster.dataBuffer as DataBufferByte).data + val pixelCount = minOf(rgbBytes.size / 3, data.size / 3) + for (i in 0 until pixelCount) { + data[i * 3] = rgbBytes[i * 3 + 2] // B + data[i * 3 + 1] = rgbBytes[i * 3 + 1] // G + data[i * 3 + 2] = rgbBytes[i * 3] // R + } + + frameCount++ + val now = System.currentTimeMillis() + if (now - lastFpsTime >= 1000) { + currentFps = frameCount + frameCount = 0 + lastFpsTime = now + } + + emit( + CameraState( + isOpen = true, + fps = currentFps, + frame = img, + deviceInfo = deviceInfo, + currentFormat = currentFormat, + compatibleFormats = compatibleFormats, + supportedFourcc = supportedFourcc, + controls = controls, + ) + ) + + delay(16) + } + } finally { + runCatching { cam.stop_stream() } + cam.close() + } +}.flowOn(Dispatchers.IO) + +private fun readControls(cam: Camera): List = runCatching { + cam.camera_controls().map { ctrl -> + val desc = ctrl.description() + val value = ctrl.value() + val descTag = desc.tag.name + val currentValue = formatControlValue(value) + val valueDetails = formatControlDetails(desc) + + val info = ControlInfo( + name = ctrl.name(), + controlType = ctrl.control().tag.name, + active = ctrl.active(), + flags = ctrl.flag().joinToString(", ") { it.name }, + descriptionTag = descTag, + currentValue = currentValue, + valueDetails = valueDetails, + ) + info + } +}.getOrElse { emptyList() } + +private fun formatControlValue(value: ControlValueSetter): String = runCatching { + when (value) { + is ControlValueSetter.Integer -> "${value.value}" + is ControlValueSetter.FloatValue -> "%.2f".format(value.value) + is ControlValueSetter.BooleanValue -> if (value.value) "On" else "Off" + is ControlValueSetter.StringValue -> value.value + is ControlValueSetter.None -> "N/A" + else -> value.tag.name + } +}.getOrElse { "N/A" } + +private fun formatControlDetails(desc: ControlValueDescription): String = runCatching { + when (desc) { + is ControlValueDescription.IntegerRange -> "Range: ${desc.min}..${desc.max} (step ${desc.step}, default ${desc.default})" + is ControlValueDescription.Integer -> "Value: ${desc.value} (step ${desc.step}, default ${desc.default})" + is ControlValueDescription.FloatRange -> "Range: %.2f..%.2f (step %.2f, default %.2f)".format(desc.min, desc.max, desc.step, desc.default) + is ControlValueDescription.FloatValue -> "Value: %.2f (step %.2f, default %.2f)".format(desc.value, desc.step, desc.default) + is ControlValueDescription.BooleanValue -> "Default: ${if (desc.default) "On" else "Off"}" + is ControlValueDescription.StringValue -> "Value: ${desc.value}" + is ControlValueDescription.Enum -> "Enum value: ${desc.value}, possible: ${desc.possible}" + is ControlValueDescription.None -> "No description" + else -> desc.tag.name + } +}.getOrElse { "N/A" } diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Components.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Components.kt new file mode 100644 index 00000000..0641dd7c --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Components.kt @@ -0,0 +1,160 @@ +package com.example.rustcamera + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SectionHeader(title: String, count: Int? = null) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .width(3.dp) + .height(18.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + Spacer(Modifier.width(10.dp)) + Text( + title, + style = MaterialTheme.typography.h6, + ) + if (count != null) { + Spacer(Modifier.width(8.dp)) + Badge("$count") + } + } +} + +@Composable +fun Badge(text: String, color: Color = AppColors.accent) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .background(color.copy(alpha = 0.15f)) + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, fontSize = 11.sp, color = color, fontWeight = FontWeight.SemiBold) + } +} + +@Composable +fun InfoCard( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(AppColors.card) + .padding(14.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + content = content, + ) +} + +@Composable +fun MetricRow(label: String, value: String) { + Row( + Modifier.fillMaxWidth().padding(vertical = 1.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text(label, style = MaterialTheme.typography.body2) + Text(value, style = MaterialTheme.typography.body1, fontWeight = FontWeight.Medium) + } +} + +@Composable +fun GaugeBar( + label: String, + fraction: Float, + detail: String, + height: Dp = 6.dp, + colors: Pair = barColors(fraction), +) { + val animatedFraction by animateFloatAsState( + targetValue = fraction.coerceIn(0f, 1f), + animationSpec = tween(500), + ) + Column(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text(label, style = MaterialTheme.typography.caption) + Text(detail, style = MaterialTheme.typography.caption, fontWeight = FontWeight.Medium) + } + Spacer(Modifier.height(3.dp)) + Box( + Modifier + .fillMaxWidth() + .height(height) + .clip(RoundedCornerShape(height / 2)) + .background(AppColors.border) + ) { + Box( + Modifier + .fillMaxHeight() + .fillMaxWidth(animatedFraction) + .clip(RoundedCornerShape(height / 2)) + .background(Brush.horizontalGradient(listOf(colors.first, colors.second))) + ) + } + } +} + +fun barColors(fraction: Float): Pair = when { + fraction > 0.9f -> AppColors.red to AppColors.red + fraction > 0.7f -> AppColors.orange to AppColors.red + fraction > 0.5f -> AppColors.accent to AppColors.orange + else -> AppColors.accent to AppColors.accent +} + +@Composable +fun StatBox( + label: String, + value: String, + modifier: Modifier = Modifier, + accentColor: Color = AppColors.accent, +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(10.dp)) + .background(AppColors.card) + .padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(value, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = accentColor) + Spacer(Modifier.height(2.dp)) + Text(label, style = MaterialTheme.typography.caption) + } +} + +@Composable +fun MiniLabel(text: String, color: Color = AppColors.textMuted) { + Text( + text, + style = MaterialTheme.typography.caption, + color = color, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Formatting.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Formatting.kt new file mode 100644 index 00000000..37117723 --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Formatting.kt @@ -0,0 +1,15 @@ +package com.example.rustcamera + +fun formatResolution(w: Int, h: Int): String = "${w}x${h}" + +fun formatPixelCount(w: Int, h: Int): String { + val mp = (w.toLong() * h) / 1_000_000.0 + return if (mp >= 1.0) "%.1f MP".format(mp) else "${w * h} px" +} + +fun formatAspectRatio(w: Int, h: Int): String { + val gcd = gcd(w, h) + return if (gcd > 0) "${w / gcd}:${h / gcd}" else "N/A" +} + +private fun gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Main.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Main.kt new file mode 100644 index 00000000..820a0dba --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Main.kt @@ -0,0 +1,18 @@ +package com.example.rustcamera + +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Camera — nokhwa Rust FFM Bridge", + state = rememberWindowState(width = 1100.dp, height = 750.dp), + ) { + CameraTheme { + App() + } + } +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Theme.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Theme.kt new file mode 100644 index 00000000..dcfae7fe --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/Theme.kt @@ -0,0 +1,72 @@ +package com.example.rustcamera + +import androidx.compose.material.Colors +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Dark palette inspired by modern system monitors +object AppColors { + val bg = Color(0xFF0F1117) + val surface = Color(0xFF181A20) + val surfaceLight = Color(0xFF1E2028) + val card = Color(0xFF1C1E26) + val cardHover = Color(0xFF22242E) + val border = Color(0xFF2A2D38) + val accent = Color(0xFF6C8EFF) + val accentDim = Color(0xFF3D5299) + val green = Color(0xFF4ADE80) + val greenDim = Color(0xFF166534) + val orange = Color(0xFFFBBF24) + val orangeDim = Color(0xFF92400E) + val red = Color(0xFFEF4444) + val redDim = Color(0xFF991B1B) + val cyan = Color(0xFF22D3EE) + val purple = Color(0xFFA78BFA) + val textPrimary = Color(0xFFE4E4E7) + val textSecondary = Color(0xFF9CA3AF) + val textMuted = Color(0xFF6B7280) + val sidebarBg = Color(0xFF13141A) + val sidebarSelected = Color(0xFF1E2235) + val sidebarHover = Color(0xFF1A1C24) + val divider = Color(0xFF2A2D38) +} + +val DarkColorPalette = Colors( + primary = AppColors.accent, + primaryVariant = AppColors.accentDim, + secondary = AppColors.cyan, + secondaryVariant = AppColors.purple, + background = AppColors.bg, + surface = AppColors.surface, + error = AppColors.red, + onPrimary = Color.White, + onSecondary = Color.White, + onBackground = AppColors.textPrimary, + onSurface = AppColors.textPrimary, + onError = Color.White, + isLight = false, +) + +val AppTypography = Typography( + h5 = TextStyle(fontWeight = FontWeight.Bold, fontSize = 20.sp, color = AppColors.textPrimary), + h6 = TextStyle(fontWeight = FontWeight.SemiBold, fontSize = 16.sp, color = AppColors.textPrimary), + subtitle1 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 14.sp, color = AppColors.textPrimary), + subtitle2 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp, color = AppColors.textSecondary), + body1 = TextStyle(fontSize = 13.sp, color = AppColors.textPrimary), + body2 = TextStyle(fontSize = 12.sp, color = AppColors.textSecondary), + caption = TextStyle(fontSize = 11.sp, color = AppColors.textMuted), +) + +@Composable +fun CameraTheme(content: @Composable () -> Unit) { + MaterialTheme( + colors = DarkColorPalette, + typography = AppTypography, + content = content, + ) +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/ControlsTab.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/ControlsTab.kt new file mode 100644 index 00000000..8b16e36a --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/ControlsTab.kt @@ -0,0 +1,54 @@ +package com.example.rustcamera.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustcamera.* + +@Composable +fun ControlsTab(controls: List) { + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Camera Controls", controls.size) + + if (controls.isEmpty()) { + InfoCard { + Text("No camera controls available", style = MaterialTheme.typography.body2) + } + return@Column + } + + controls.forEach { ctrl -> + InfoCard { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Badge(ctrl.controlType, AppColors.accent) + Badge( + if (ctrl.active) "Active" else "Inactive", + if (ctrl.active) AppColors.green else AppColors.textMuted, + ) + if (ctrl.flags.isNotBlank()) { + Badge(ctrl.flags, AppColors.orange) + } + } + + MetricRow("Name", ctrl.name) + MetricRow("Current Value", ctrl.currentValue) + MetricRow("Value Type", ctrl.descriptionTag) + + if (ctrl.valueDetails != "N/A") { + MiniLabel(ctrl.valueDetails, AppColors.textSecondary) + } + } + } + } +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/FormatTab.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/FormatTab.kt new file mode 100644 index 00000000..c880bd5b --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/FormatTab.kt @@ -0,0 +1,64 @@ +package com.example.rustcamera.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustcamera.* + +@Composable +fun FormatTab(state: CameraState?) { + if (state?.currentFormat == null) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Loading format info...", style = MaterialTheme.typography.body1) + } + return + } + + val fmt = state.currentFormat + + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Active Format") + + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Width", "${fmt.width} px", Modifier.weight(1f)) + StatBox("Height", "${fmt.height} px", Modifier.weight(1f)) + StatBox("Frame Rate", "${fmt.frameRate} fps", Modifier.weight(1f), accentColor = AppColors.cyan) + StatBox("Pixel Format", fmt.format, Modifier.weight(1f), accentColor = AppColors.purple) + } + + InfoCard { + MetricRow("Resolution", formatResolution(fmt.width, fmt.height)) + MetricRow("Pixel Count", formatPixelCount(fmt.width, fmt.height)) + MetricRow("Aspect Ratio", formatAspectRatio(fmt.width, fmt.height)) + MetricRow("Effective FPS", "${state.fps}") + } + + SectionHeader("Supported FourCC Codes", state.supportedFourcc.size) + + if (state.supportedFourcc.isNotEmpty()) { + InfoCard { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + state.supportedFourcc.forEach { fourcc -> + Badge(fourcc, AppColors.purple) + } + } + } + } else { + InfoCard { + Text("No FourCC codes reported", style = MaterialTheme.typography.body2) + } + } + } +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/FormatsTab.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/FormatsTab.kt new file mode 100644 index 00000000..726912f3 --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/FormatsTab.kt @@ -0,0 +1,65 @@ +package com.example.rustcamera.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustcamera.* + +@Composable +fun FormatsTab(formats: List, currentFormat: FormatInfo?) { + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Compatible Formats", formats.size) + + if (formats.isEmpty()) { + InfoCard { + Text("No compatible formats reported", style = MaterialTheme.typography.body2) + } + return@Column + } + + // Group by resolution + val byResolution = formats.groupBy { formatResolution(it.width, it.height) } + + byResolution.forEach { (resolution, fmts) -> + SectionHeader(resolution, fmts.size) + + fmts.forEach { fmt -> + val isCurrent = currentFormat != null && + fmt.width == currentFormat.width && + fmt.height == currentFormat.height && + fmt.frameRate == currentFormat.frameRate && + fmt.format == currentFormat.format + + InfoCard { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Badge(fmt.format, AppColors.purple) + Badge("${fmt.frameRate} fps", AppColors.cyan) + Badge(formatAspectRatio(fmt.width, fmt.height), AppColors.textSecondary) + if (isCurrent) { + Badge("ACTIVE", AppColors.green) + } + } + Text( + formatPixelCount(fmt.width, fmt.height), + style = MaterialTheme.typography.caption, + ) + } + } + } + } + } +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/InfoTab.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/InfoTab.kt new file mode 100644 index 00000000..08928e22 --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/InfoTab.kt @@ -0,0 +1,71 @@ +package com.example.rustcamera.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustcamera.* + +@Composable +fun InfoTab(state: CameraState?) { + if (state?.deviceInfo == null) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Loading camera info...", style = MaterialTheme.typography.body1) + } + return + } + + val info = state.deviceInfo + + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Camera Device") + + InfoCard { + MetricRow("Name", info.humanName) + MetricRow("Description", info.description.ifBlank { "N/A" }) + MetricRow("Misc", info.misc.ifBlank { "N/A" }) + MetricRow("Index", info.indexDescription) + } + + SectionHeader("Stream Status") + + InfoCard { + MetricRow("Stream", if (state.isOpen) "Active" else "Closed") + MetricRow("FPS", "${state.fps}") + } + + val fmt = state.currentFormat + if (fmt != null) { + SectionHeader("Current Configuration") + + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Width", "${fmt.width} px", Modifier.weight(1f)) + StatBox("Height", "${fmt.height} px", Modifier.weight(1f)) + StatBox("Frame Rate", "${fmt.frameRate} fps", Modifier.weight(1f), accentColor = AppColors.cyan) + StatBox("Format", fmt.format, Modifier.weight(1f), accentColor = AppColors.purple) + } + + InfoCard { + MetricRow("Resolution", formatResolution(fmt.width, fmt.height)) + MetricRow("Pixel Count", formatPixelCount(fmt.width, fmt.height)) + MetricRow("Aspect Ratio", formatAspectRatio(fmt.width, fmt.height)) + } + } + + SectionHeader("Capabilities Summary") + + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Formats", "${state.compatibleFormats.size}", Modifier.weight(1f), accentColor = AppColors.green) + StatBox("FourCC Codes", "${state.supportedFourcc.size}", Modifier.weight(1f), accentColor = AppColors.orange) + StatBox("Controls", "${state.controls.size}", Modifier.weight(1f), accentColor = AppColors.cyan) + } + } +} diff --git a/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/PreviewTab.kt b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/PreviewTab.kt new file mode 100644 index 00000000..f1cf2ab7 --- /dev/null +++ b/examples/rust-camera/src/jvmMain/kotlin/com/example/rustcamera/tabs/PreviewTab.kt @@ -0,0 +1,70 @@ +package com.example.rustcamera.tabs + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.toComposeImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import com.example.rustcamera.* + +@Composable +fun PreviewTab(state: CameraState?) { + if (state == null) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Opening camera...", style = MaterialTheme.typography.body1) + } + return + } + + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Live Preview") + + // Stats row + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("FPS", "${state.fps}", Modifier.weight(1f), accentColor = AppColors.green) + val fmt = state.currentFormat + if (fmt != null) { + StatBox("Resolution", formatResolution(fmt.width, fmt.height), Modifier.weight(1f)) + StatBox("Frame Rate", "${fmt.frameRate} fps", Modifier.weight(1f), accentColor = AppColors.cyan) + StatBox("Format", fmt.format, Modifier.weight(1f), accentColor = AppColors.purple) + } + } + + // Live feed + val frame = state.frame + if (frame != null) { + InfoCard { + Image( + bitmap = frame.toComposeImageBitmap(), + contentDescription = "Camera Feed", + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)), + contentScale = ContentScale.Fit, + ) + } + } + + // Stream status + InfoCard { + MetricRow("Stream", if (state.isOpen) "Active" else "Closed") + val fmt = state.currentFormat + if (fmt != null) { + MetricRow("Pixel Count", formatPixelCount(fmt.width, fmt.height)) + MetricRow("Aspect Ratio", formatAspectRatio(fmt.width, fmt.height)) + } + } + } +} diff --git a/examples/rust-rfd/build.gradle.kts b/examples/rust-rfd/build.gradle.kts new file mode 100644 index 00000000..4ce63cd1 --- /dev/null +++ b/examples/rust-rfd/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") version "1.10.2" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(compose.runtime) + implementation(compose.materialIconsExtended) + } + } + } +} + +compose.desktop { + application { + mainClass = "com.example.rustrfd.MainKt" + nativeDistributions { + packageName = "RustRfd" + } + jvmArgs("--enable-native-access=ALL-UNNAMED") + } +} + +rustImport { + libraryName = "rustrfd" + jvmPackage = "com.example.rustrfd" + buildType = "release" + crate("rfd", "0.17.2") +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/App.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/App.kt new file mode 100644 index 00000000..f6b558d5 --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/App.kt @@ -0,0 +1,175 @@ +package com.example.rustrfd + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rustrfd.tabs.* + +enum class NavItem(val label: String, val icon: ImageVector) { + FilePicker("File Picker", Icons.Default.InsertDriveFile), + FolderPicker("Folder Picker", Icons.Default.Folder), + SaveFile("Save File", Icons.Default.Save), + Message("Messages", Icons.Default.Chat), + History("History", Icons.Default.History), +} + +@Composable +fun App() { + var selected by remember { mutableStateOf(NavItem.FilePicker) } + val history = remember { mutableStateListOf() } + + val onResult: (DialogResult) -> Unit = { history.add(it) } + + Row(Modifier.fillMaxSize().background(AppColors.bg)) { + Sidebar(selected, history.size) { selected = it } + + Box( + Modifier + .fillMaxSize() + .padding(start = 0.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) + ) { + when (selected) { + NavItem.FilePicker -> FilePickerTab(onResult) + NavItem.FolderPicker -> FolderPickerTab(onResult) + NavItem.SaveFile -> SaveFileTab(onResult) + NavItem.Message -> MessageTab(onResult) + NavItem.History -> HistoryTab(history) + } + } + } +} + +@Composable +private fun Sidebar(selected: NavItem, historyCount: Int, onSelect: (NavItem) -> Unit) { + Column( + modifier = Modifier + .fillMaxHeight() + .width(200.dp) + .background(AppColors.sidebarBg) + .padding(vertical = 16.dp, horizontal = 8.dp), + ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .size(8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(AppColors.green) + ) + Spacer(Modifier.width(10.dp)) + Text( + "File Dialogs", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = AppColors.textPrimary, + ) + } + Text( + " rfd FFM Bridge", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + + Spacer(Modifier.height(20.dp)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(12.dp)) + + NavItem.entries.forEach { item -> + SidebarItem( + item = item, + isSelected = item == selected, + badge = if (item == NavItem.History && historyCount > 0) "$historyCount" else null, + onClick = { onSelect(item) }, + ) + } + + Spacer(Modifier.weight(1f)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(8.dp)) + Text( + " Powered by rfd 0.17", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + } +} + +@Composable +private fun SidebarItem(item: NavItem, isSelected: Boolean, badge: String? = null, onClick: () -> Unit) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + + val bgColor = when { + isSelected -> AppColors.sidebarSelected + isHovered -> AppColors.sidebarHover + else -> Color.Transparent + } + val contentColor = when { + isSelected -> AppColors.accent + isHovered -> AppColors.textPrimary + else -> AppColors.textSecondary + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .hoverable(interactionSource) + .clickable(interactionSource = interactionSource, indication = null, onClick = onClick) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = item.icon, + contentDescription = item.label, + tint = contentColor, + modifier = Modifier.size(18.dp), + ) + Spacer(Modifier.width(10.dp)) + Text( + item.label, + fontSize = 13.sp, + fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, + color = contentColor, + ) + + if (badge != null) { + Spacer(Modifier.width(6.dp)) + Badge(badge, AppColors.cyan) + } + + if (isSelected) { + Spacer(Modifier.weight(1f)) + Box( + Modifier + .width(3.dp) + .height(16.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + } + } +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Components.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Components.kt new file mode 100644 index 00000000..a68e705b --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Components.kt @@ -0,0 +1,150 @@ +package com.example.rustrfd + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SectionHeader(title: String, count: Int? = null) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .width(3.dp) + .height(18.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + Spacer(Modifier.width(10.dp)) + Text(title, style = MaterialTheme.typography.h6) + if (count != null) { + Spacer(Modifier.width(8.dp)) + Badge("$count") + } + } +} + +@Composable +fun Badge(text: String, color: Color = AppColors.accent) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .background(color.copy(alpha = 0.15f)) + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, fontSize = 11.sp, color = color, fontWeight = FontWeight.SemiBold) + } +} + +@Composable +fun InfoCard( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(AppColors.card) + .padding(14.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + content = content, + ) +} + +@Composable +fun MetricRow(label: String, value: String) { + Row( + Modifier.fillMaxWidth().padding(vertical = 1.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text(label, style = MaterialTheme.typography.body2) + Text(value, style = MaterialTheme.typography.body1, fontWeight = FontWeight.Medium) + } +} + +@Composable +fun StatBox( + label: String, + value: String, + modifier: Modifier = Modifier, + accentColor: Color = AppColors.accent, +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(10.dp)) + .background(AppColors.card) + .padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(value, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = accentColor) + Spacer(Modifier.height(2.dp)) + Text(label, style = MaterialTheme.typography.caption) + } +} + +@Composable +fun MiniLabel(text: String, color: Color = AppColors.textMuted) { + Text( + text, + style = MaterialTheme.typography.caption, + color = color, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) +} + +@Composable +fun ActionButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + color: Color = AppColors.accent, + enabled: Boolean = true, +) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val bgColor = if (enabled) { + if (isHovered) color.copy(alpha = 0.25f) else color.copy(alpha = 0.15f) + } else { + AppColors.border.copy(alpha = 0.3f) + } + val textColor = if (enabled) color else AppColors.textMuted + + Box( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .hoverable(interactionSource) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + onClick = onClick, + ) + .padding(horizontal = 16.dp, vertical = 10.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = textColor) + } +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Main.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Main.kt new file mode 100644 index 00000000..4c6825ae --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Main.kt @@ -0,0 +1,18 @@ +package com.example.rustrfd + +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Rusty File Dialogs — rfd FFM Bridge", + state = rememberWindowState(width = 1100.dp, height = 750.dp), + ) { + RfdTheme { + App() + } + } +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/RfdActions.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/RfdActions.kt new file mode 100644 index 00000000..37e2a5b1 --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/RfdActions.kt @@ -0,0 +1,118 @@ +package com.example.rustrfd + +// All rfd dialog calls are suspend funs (generated with withContext(Dispatchers.IO)). +// No manual dispatcher wrapping needed. + +suspend fun pickFile( + title: String = "Select a file", + directory: String? = null, + filters: List>> = emptyList(), +): DialogResult = runCatching { + val path = FileDialog().applyCommon(title, directory, filters).pick_file() + if (path != null) DialogResult(type = DialogType.PickFile, paths = listOf(path)) + else DialogResult(type = DialogType.PickFile, cancelled = true) +}.getOrElse { DialogResult(type = DialogType.PickFile, cancelled = true) } + +suspend fun pickFiles( + title: String = "Select files", + directory: String? = null, + filters: List>> = emptyList(), +): DialogResult = runCatching { + val paths = FileDialog().applyCommon(title, directory, filters).pick_files() + if (!paths.isNullOrEmpty()) DialogResult(type = DialogType.PickFiles, paths = paths) + else DialogResult(type = DialogType.PickFiles, cancelled = true) +}.getOrElse { DialogResult(type = DialogType.PickFiles, cancelled = true) } + +suspend fun pickFolder( + title: String = "Select a folder", + directory: String? = null, +): DialogResult = runCatching { + var dialog = FileDialog().set_title(title) + if (directory != null) dialog = dialog.set_directory(directory) + val path = dialog.pick_folder() + if (path != null) DialogResult(type = DialogType.PickFolder, paths = listOf(path)) + else DialogResult(type = DialogType.PickFolder, cancelled = true) +}.getOrElse { DialogResult(type = DialogType.PickFolder, cancelled = true) } + +suspend fun pickFolders( + title: String = "Select folders", + directory: String? = null, +): DialogResult = runCatching { + var dialog = FileDialog().set_title(title) + if (directory != null) dialog = dialog.set_directory(directory) + val paths = dialog.pick_folders() + if (!paths.isNullOrEmpty()) DialogResult(type = DialogType.PickFolders, paths = paths) + else DialogResult(type = DialogType.PickFolders, cancelled = true) +}.getOrElse { DialogResult(type = DialogType.PickFolders, cancelled = true) } + +suspend fun pickFileOrFolder( + title: String = "Select a file or folder", + directory: String? = null, +): DialogResult = runCatching { + var dialog = FileDialog().set_title(title) + if (directory != null) dialog = dialog.set_directory(directory) + val path = dialog.pick_file_or_folder() + if (path != null) DialogResult(type = DialogType.PickFile, paths = listOf(path)) + else DialogResult(type = DialogType.PickFile, cancelled = true) +}.getOrElse { DialogResult(type = DialogType.PickFile, cancelled = true) } + +suspend fun pickFilesOrFolders( + title: String = "Select files or folders", + directory: String? = null, +): DialogResult = runCatching { + var dialog = FileDialog().set_title(title) + if (directory != null) dialog = dialog.set_directory(directory) + val paths = dialog.pick_files_or_folders() + if (!paths.isNullOrEmpty()) DialogResult(type = DialogType.PickFiles, paths = paths) + else DialogResult(type = DialogType.PickFiles, cancelled = true) +}.getOrElse { DialogResult(type = DialogType.PickFiles, cancelled = true) } + +suspend fun saveFile( + title: String = "Save file", + fileName: String? = null, + directory: String? = null, + filters: List>> = emptyList(), +): DialogResult = runCatching { + var dialog = FileDialog().applyCommon(title, directory, filters) + if (fileName != null) dialog = dialog.set_file_name(fileName) + val path = dialog.save_file() + if (path != null) DialogResult(type = DialogType.SaveFile, paths = listOf(path)) + else DialogResult(type = DialogType.SaveFile, cancelled = true) +}.getOrElse { DialogResult(type = DialogType.SaveFile, cancelled = true) } + +suspend fun showMessage( + title: String = "Message", + description: String = "", + level: MessageLevel, + buttons: MessageButtons, +): DialogResult { + val dialogType = when (level) { + MessageLevel.Info -> DialogType.MessageInfo + MessageLevel.Warning -> DialogType.MessageWarning + MessageLevel.Error -> DialogType.MessageError + } + return runCatching { + val dialog = MessageDialog() + .set_title(title) + .set_description(description) + .set_level(level) + .set_buttons(buttons) + val result = dialog.show() + DialogResult(type = dialogType, messageResult = result.tag.name) + }.getOrElse { + DialogResult(type = dialogType, messageResult = "Error: ${it.message}") + } +} + +private fun FileDialog.applyCommon( + title: String, + directory: String?, + filters: List>>, +): FileDialog { + var d = set_title(title) + if (directory != null) d = d.set_directory(directory) + for ((name, extensions) in filters) { + d = d.add_filter(name, extensions) + } + return d +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/RfdData.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/RfdData.kt new file mode 100644 index 00000000..be75485c --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/RfdData.kt @@ -0,0 +1,35 @@ +package com.example.rustrfd + +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +enum class DialogType(val label: String) { + PickFile("Pick File"), + PickFiles("Pick Files"), + PickFolder("Pick Folder"), + PickFolders("Pick Folders"), + SaveFile("Save File"), + MessageInfo("Message (Info)"), + MessageWarning("Message (Warning)"), + MessageError("Message (Error)"), +} + +data class DialogResult( + val type: DialogType, + val timestamp: LocalDateTime = LocalDateTime.now(), + val paths: List = emptyList(), + val messageResult: String? = null, + val cancelled: Boolean = false, +) { + val summary: String + get() = when { + cancelled -> "Cancelled" + messageResult != null -> messageResult + paths.size == 1 -> paths.first().substringAfterLast('/') + paths.isNotEmpty() -> "${paths.size} items selected" + else -> "No result" + } + + val formattedTime: String + get() = timestamp.format(DateTimeFormatter.ofPattern("HH:mm:ss")) +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Theme.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Theme.kt new file mode 100644 index 00000000..83ae2445 --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/Theme.kt @@ -0,0 +1,71 @@ +package com.example.rustrfd + +import androidx.compose.material.Colors +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +object AppColors { + val bg = Color(0xFF0F1117) + val surface = Color(0xFF181A20) + val surfaceLight = Color(0xFF1E2028) + val card = Color(0xFF1C1E26) + val cardHover = Color(0xFF22242E) + val border = Color(0xFF2A2D38) + val accent = Color(0xFF6C8EFF) + val accentDim = Color(0xFF3D5299) + val green = Color(0xFF4ADE80) + val greenDim = Color(0xFF166534) + val orange = Color(0xFFFBBF24) + val orangeDim = Color(0xFF92400E) + val red = Color(0xFFEF4444) + val redDim = Color(0xFF991B1B) + val cyan = Color(0xFF22D3EE) + val purple = Color(0xFFA78BFA) + val textPrimary = Color(0xFFE4E4E7) + val textSecondary = Color(0xFF9CA3AF) + val textMuted = Color(0xFF6B7280) + val sidebarBg = Color(0xFF13141A) + val sidebarSelected = Color(0xFF1E2235) + val sidebarHover = Color(0xFF1A1C24) + val divider = Color(0xFF2A2D38) +} + +val DarkColorPalette = Colors( + primary = AppColors.accent, + primaryVariant = AppColors.accentDim, + secondary = AppColors.cyan, + secondaryVariant = AppColors.purple, + background = AppColors.bg, + surface = AppColors.surface, + error = AppColors.red, + onPrimary = Color.White, + onSecondary = Color.White, + onBackground = AppColors.textPrimary, + onSurface = AppColors.textPrimary, + onError = Color.White, + isLight = false, +) + +val AppTypography = Typography( + h5 = TextStyle(fontWeight = FontWeight.Bold, fontSize = 20.sp, color = AppColors.textPrimary), + h6 = TextStyle(fontWeight = FontWeight.SemiBold, fontSize = 16.sp, color = AppColors.textPrimary), + subtitle1 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 14.sp, color = AppColors.textPrimary), + subtitle2 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp, color = AppColors.textSecondary), + body1 = TextStyle(fontSize = 13.sp, color = AppColors.textPrimary), + body2 = TextStyle(fontSize = 12.sp, color = AppColors.textSecondary), + caption = TextStyle(fontSize = 11.sp, color = AppColors.textMuted), +) + +@Composable +fun RfdTheme(content: @Composable () -> Unit) { + MaterialTheme( + colors = DarkColorPalette, + typography = AppTypography, + content = content, + ) +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/FilePickerTab.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/FilePickerTab.kt new file mode 100644 index 00000000..808596b9 --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/FilePickerTab.kt @@ -0,0 +1,147 @@ +package com.example.rustrfd.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustrfd.* +import kotlinx.coroutines.launch + +@Composable +fun FilePickerTab(onResult: (DialogResult) -> Unit) { + val scope = rememberCoroutineScope() + var busy by remember { mutableStateOf(false) } + var lastResult by remember { mutableStateOf(null) } + + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Pick Single File") + + InfoCard { + Text("Open a native file picker with optional filters (via add_filter).", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Any File", enabled = !busy, onClick = { + busy = true + scope.launch { + val r = pickFile(title = "Select any file") + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Images", enabled = !busy, color = AppColors.green, onClick = { + busy = true + scope.launch { + val r = pickFile( + title = "Select an image", + filters = listOf("Images" to listOf("png", "jpg", "jpeg", "gif", "webp", "bmp")), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Documents", enabled = !busy, color = AppColors.purple, onClick = { + busy = true + scope.launch { + val r = pickFile( + title = "Select a document", + filters = listOf( + "Documents" to listOf("pdf", "doc", "docx", "txt", "md"), + "Spreadsheets" to listOf("xls", "xlsx", "csv"), + ), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Source Code", enabled = !busy, color = AppColors.cyan, onClick = { + busy = true + scope.launch { + val r = pickFile( + title = "Select source code", + filters = listOf( + "Rust" to listOf("rs", "toml"), + "Kotlin" to listOf("kt", "kts"), + "All Source" to listOf("rs", "kt", "java", "py", "js", "ts"), + ), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + SectionHeader("Pick Multiple Files") + + InfoCard { + Text("Select multiple files with filters.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Any Files", enabled = !busy, onClick = { + busy = true + scope.launch { + val r = pickFiles(title = "Select files") + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Images", enabled = !busy, color = AppColors.green, onClick = { + busy = true + scope.launch { + val r = pickFiles( + title = "Select images", + filters = listOf("Images" to listOf("png", "jpg", "jpeg", "gif", "webp")), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + SectionHeader("Pick File or Folder") + + InfoCard { + Text("Native picker allowing both files and folders.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("File or Folder", enabled = !busy, color = AppColors.orange, onClick = { + busy = true + scope.launch { + val r = pickFileOrFolder(title = "Select a file or folder") + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Files or Folders", enabled = !busy, color = AppColors.orange, onClick = { + busy = true + scope.launch { + val r = pickFilesOrFolders(title = "Select files or folders") + lastResult = r; onResult(r); busy = false + } + }) + } + } + + lastResult?.let { result -> + SectionHeader("Last Result") + InfoCard { + MetricRow("Type", result.type.label) + MetricRow("Status", if (result.cancelled) "Cancelled" else "Selected") + if (result.paths.isNotEmpty()) { + MetricRow("Count", "${result.paths.size}") + Spacer(Modifier.height(4.dp)) + result.paths.forEach { path -> + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Badge(path.substringAfterLast('/'), AppColors.green) + MiniLabel(path, AppColors.textSecondary) + } + } + } + } + } + } +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/FolderPickerTab.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/FolderPickerTab.kt new file mode 100644 index 00000000..33e01211 --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/FolderPickerTab.kt @@ -0,0 +1,96 @@ +package com.example.rustrfd.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustrfd.* +import kotlinx.coroutines.launch + +@Composable +fun FolderPickerTab(onResult: (DialogResult) -> Unit) { + val scope = rememberCoroutineScope() + var busy by remember { mutableStateOf(false) } + var lastResult by remember { mutableStateOf(null) } + + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Pick Single Folder") + + InfoCard { + Text("Open a native folder picker to select one directory.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Select Folder", enabled = !busy, onClick = { + busy = true + scope.launch { + val r = pickFolder(title = "Select a folder") + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("From Home", enabled = !busy, color = AppColors.cyan, onClick = { + busy = true + scope.launch { + val r = pickFolder( + title = "Select a folder", + directory = System.getProperty("user.home"), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("From Desktop", enabled = !busy, color = AppColors.purple, onClick = { + busy = true + scope.launch { + val r = pickFolder( + title = "Select a folder", + directory = System.getProperty("user.home") + "/Desktop", + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + SectionHeader("Pick Multiple Folders") + + InfoCard { + Text("Open a native folder picker to select multiple directories.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Select Folders", enabled = !busy, onClick = { + busy = true + scope.launch { + val r = pickFolders(title = "Select folders") + lastResult = r; onResult(r); busy = false + } + }) + } + } + + lastResult?.let { result -> + SectionHeader("Last Result") + InfoCard { + MetricRow("Type", result.type.label) + MetricRow("Status", if (result.cancelled) "Cancelled" else "Selected") + if (result.paths.isNotEmpty()) { + MetricRow("Count", "${result.paths.size}") + Spacer(Modifier.height(4.dp)) + result.paths.forEach { path -> + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Badge(path.substringAfterLast('/'), AppColors.cyan) + MiniLabel(path, AppColors.textSecondary) + } + } + } + } + } + } +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/HistoryTab.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/HistoryTab.kt new file mode 100644 index 00000000..ffe9ba18 --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/HistoryTab.kt @@ -0,0 +1,78 @@ +package com.example.rustrfd.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustrfd.* + +@Composable +fun HistoryTab(history: List) { + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Dialog History", history.size) + + if (history.isEmpty()) { + InfoCard { + Text("No dialogs opened yet. Use the other tabs to trigger native dialogs.", style = MaterialTheme.typography.body2) + } + return@Column + } + + // Summary stats + val fileDialogs = history.count { it.type in listOf(DialogType.PickFile, DialogType.PickFiles) } + val folderDialogs = history.count { it.type in listOf(DialogType.PickFolder, DialogType.PickFolders) } + val saveDialogs = history.count { it.type == DialogType.SaveFile } + val messageDialogs = history.count { it.type in listOf(DialogType.MessageInfo, DialogType.MessageWarning, DialogType.MessageError) } + val cancelled = history.count { it.cancelled } + + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("File", "$fileDialogs", Modifier.weight(1f), accentColor = AppColors.green) + StatBox("Folder", "$folderDialogs", Modifier.weight(1f), accentColor = AppColors.cyan) + StatBox("Save", "$saveDialogs", Modifier.weight(1f), accentColor = AppColors.orange) + StatBox("Message", "$messageDialogs", Modifier.weight(1f), accentColor = AppColors.purple) + StatBox("Cancelled", "$cancelled", Modifier.weight(1f), accentColor = AppColors.red) + } + + // Entries (most recent first) + history.reversed().forEach { result -> + val typeColor = when (result.type) { + DialogType.PickFile, DialogType.PickFiles -> AppColors.green + DialogType.PickFolder, DialogType.PickFolders -> AppColors.cyan + DialogType.SaveFile -> AppColors.orange + DialogType.MessageInfo -> AppColors.accent + DialogType.MessageWarning -> AppColors.orange + DialogType.MessageError -> AppColors.red + } + + InfoCard { + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Badge(result.type.label, typeColor) + if (result.cancelled) { + Badge("Cancelled", AppColors.red) + } + } + MiniLabel(result.formattedTime, AppColors.textSecondary) + } + + MetricRow("Result", result.summary) + + if (result.paths.isNotEmpty()) { + result.paths.forEach { path -> + MiniLabel(path, AppColors.textSecondary) + } + } + } + } + } +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/MessageTab.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/MessageTab.kt new file mode 100644 index 00000000..0834ada3 --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/MessageTab.kt @@ -0,0 +1,208 @@ +package com.example.rustrfd.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustrfd.* +import kotlinx.coroutines.launch + +@Composable +fun MessageTab(onResult: (DialogResult) -> Unit) { + val scope = rememberCoroutineScope() + var busy by remember { mutableStateOf(false) } + var lastResult by remember { mutableStateOf(null) } + + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Info Messages") + + InfoCard { + Text( + "Show native message dialogs with Info level. Demonstrates different button configurations.", + style = MaterialTheme.typography.body2, + ) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("OK", enabled = !busy, color = AppColors.accent, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Information", + description = "This is an informational message from the rfd crate, displayed through a Rust FFM bridge.", + level = MessageLevel.Info, + buttons = MessageButtons.ok(), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("OK / Cancel", enabled = !busy, color = AppColors.accent, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Confirm Action", + description = "Do you want to proceed with this action?", + level = MessageLevel.Info, + buttons = MessageButtons.okCancel(), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Yes / No", enabled = !busy, color = AppColors.accent, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Question", + description = "Would you like to enable this feature?", + level = MessageLevel.Info, + buttons = MessageButtons.yesNo(), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + SectionHeader("Warning Messages") + + InfoCard { + Text("Show native warning dialogs.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Warning OK", enabled = !busy, color = AppColors.orange, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Warning", + description = "This operation may take a long time. Are you sure?", + level = MessageLevel.Warning, + buttons = MessageButtons.ok(), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Warning Yes/No", enabled = !busy, color = AppColors.orange, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Warning", + description = "Unsaved changes will be lost. Continue?", + level = MessageLevel.Warning, + buttons = MessageButtons.yesNo(), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Warning Yes/No/Cancel", enabled = !busy, color = AppColors.orange, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Save Changes?", + description = "You have unsaved changes. Do you want to save before closing?", + level = MessageLevel.Warning, + buttons = MessageButtons.yesNoCancel(), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + SectionHeader("Error Messages") + + InfoCard { + Text("Show native error dialogs.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Error OK", enabled = !busy, color = AppColors.red, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Error", + description = "An unexpected error occurred. The operation could not be completed.", + level = MessageLevel.Error, + buttons = MessageButtons.ok(), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Error OK/Cancel", enabled = !busy, color = AppColors.red, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Critical Error", + description = "A critical error was detected. Would you like to try again?", + level = MessageLevel.Error, + buttons = MessageButtons.okCancel(), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + SectionHeader("Custom Buttons") + + InfoCard { + Text("Message dialogs with custom button labels.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Custom OK", enabled = !busy, color = AppColors.purple, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Custom Dialog", + description = "This dialog has a custom button label.", + level = MessageLevel.Info, + buttons = MessageButtons.okCustom("Got it!"), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Custom OK/Cancel", enabled = !busy, color = AppColors.purple, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Custom Confirm", + description = "Choose your action.", + level = MessageLevel.Info, + buttons = MessageButtons.okCancelCustom("Proceed", "Abort"), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Custom Yes/No/Cancel", enabled = !busy, color = AppColors.purple, onClick = { + busy = true + scope.launch { + val r = showMessage( + title = "Three Choices", + description = "Pick one of three custom options.", + level = MessageLevel.Warning, + buttons = MessageButtons.yesNoCancelCustom("Save", "Discard", "Go Back"), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + lastResult?.let { result -> + SectionHeader("Last Result") + InfoCard { + MetricRow("Type", result.type.label) + MetricRow("Result", result.messageResult ?: "N/A") + MetricRow("Time", result.formattedTime) + } + } + } +} diff --git a/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/SaveFileTab.kt b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/SaveFileTab.kt new file mode 100644 index 00000000..87f2abfc --- /dev/null +++ b/examples/rust-rfd/src/jvmMain/kotlin/com/example/rustrfd/tabs/SaveFileTab.kt @@ -0,0 +1,132 @@ +package com.example.rustrfd.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustrfd.* +import kotlinx.coroutines.launch + +@Composable +fun SaveFileTab(onResult: (DialogResult) -> Unit) { + val scope = rememberCoroutineScope() + var busy by remember { mutableStateOf(false) } + var lastResult by remember { mutableStateOf(null) } + + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Save File Dialog") + + InfoCard { + Text( + "Native save dialog with set_file_name, set_directory, and add_filter.", + style = MaterialTheme.typography.body2, + ) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Save Any", enabled = !busy, onClick = { + busy = true + scope.launch { + val r = saveFile(title = "Save file") + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Save .txt", enabled = !busy, color = AppColors.green, onClick = { + busy = true + scope.launch { + val r = saveFile( + title = "Save text file", + fileName = "untitled.txt", + filters = listOf("Text Files" to listOf("txt", "md")), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Save .json", enabled = !busy, color = AppColors.cyan, onClick = { + busy = true + scope.launch { + val r = saveFile( + title = "Save JSON", + fileName = "data.json", + filters = listOf("JSON" to listOf("json")), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Save Image", enabled = !busy, color = AppColors.purple, onClick = { + busy = true + scope.launch { + val r = saveFile( + title = "Save image", + fileName = "screenshot.png", + filters = listOf( + "PNG" to listOf("png"), + "JPEG" to listOf("jpg", "jpeg"), + "All Images" to listOf("png", "jpg", "jpeg", "webp", "bmp"), + ), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + SectionHeader("Save with Starting Directory") + + InfoCard { + Text("Save dialog that opens in a specific directory.", style = MaterialTheme.typography.body2) + Spacer(Modifier.height(4.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton("Save to Home", enabled = !busy, color = AppColors.orange, onClick = { + busy = true + scope.launch { + val r = saveFile( + title = "Save to home directory", + fileName = "export.csv", + directory = System.getProperty("user.home"), + filters = listOf("CSV" to listOf("csv")), + ) + lastResult = r; onResult(r); busy = false + } + }) + ActionButton("Save to Desktop", enabled = !busy, color = AppColors.orange, onClick = { + busy = true + scope.launch { + val r = saveFile( + title = "Save to desktop", + fileName = "report.pdf", + directory = System.getProperty("user.home") + "/Desktop", + filters = listOf("PDF" to listOf("pdf")), + ) + lastResult = r; onResult(r); busy = false + } + }) + } + } + + lastResult?.let { result -> + SectionHeader("Last Result") + InfoCard { + MetricRow("Type", result.type.label) + MetricRow("Status", if (result.cancelled) "Cancelled" else "Path chosen") + if (result.paths.isNotEmpty()) { + Spacer(Modifier.height(4.dp)) + result.paths.forEach { path -> + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Badge(path.substringAfterLast('/'), AppColors.orange) + MiniLabel(path, AppColors.textSecondary) + } + } + } + } + } + } +} diff --git a/examples/rust-symphonia/build.gradle.kts b/examples/rust-symphonia/build.gradle.kts new file mode 100644 index 00000000..aac29aeb --- /dev/null +++ b/examples/rust-symphonia/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") version "1.10.2" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(compose.runtime) + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + } + } + val jvmTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +compose.desktop { + application { + mainClass = "com.example.rustsymphonia.MainKt" + nativeDistributions { + packageName = "RustSymphonia" + } + jvmArgs("--enable-native-access=ALL-UNNAMED") + } +} + +rustImport { + libraryName = "rustsymphonia" + jvmPackage = "com.example.rustsymphonia" + buildType = "release" + // symphonia: pure-Rust audio decoding (MP3, FLAC, OGG, WAV, AAC/M4A, AIFF…) + crate("symphonia", "0.5.5", features = listOf("mp3", "aac", "alac", "isomp4", "aiff", "all-codecs")) + // cpal: cross-platform audio output (CoreAudio on macOS, WASAPI on Windows, ALSA on Linux) + crate("cpal", "0.15") +} diff --git a/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/App.kt b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/App.kt new file mode 100644 index 00000000..9805d7e7 --- /dev/null +++ b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/App.kt @@ -0,0 +1,161 @@ +package com.example.rustsymphonia + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rustsymphonia.tabs.* + +enum class NavItem(val label: String, val icon: ImageVector) { + Player("Player", Icons.Default.MusicNote), + BridgeTests("Bridge Tests", Icons.Default.Science), +} + +@Composable +fun App() { + var selected by remember { mutableStateOf(NavItem.Player) } + + Row(Modifier.fillMaxSize().background(AppColors.bg)) { + Sidebar(selected) { selected = it } + + Box( + Modifier + .fillMaxSize() + .padding(start = 0.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) + ) { + when (selected) { + NavItem.Player -> PlayerTab() + NavItem.BridgeTests -> BridgeTestsTab() + } + } + } +} + +@Composable +private fun Sidebar(selected: NavItem, onSelect: (NavItem) -> Unit) { + Column( + modifier = Modifier + .fillMaxHeight() + .width(200.dp) + .background(AppColors.sidebarBg) + .padding(vertical = 16.dp, horizontal = 8.dp), + ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .size(8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(AppColors.accent) + ) + Spacer(Modifier.width(10.dp)) + Text( + "RustSymphonia", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = AppColors.textPrimary, + ) + } + Text( + " Symphonia + cpal via NNA", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + + Spacer(Modifier.height(20.dp)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(12.dp)) + + NavItem.entries.forEach { item -> + SidebarItem(item = item, isSelected = item == selected, onClick = { onSelect(item) }) + } + + Spacer(Modifier.weight(1f)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(8.dp)) + Text( + " symphonia 0.5 + cpal 0.15", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + Text( + " zero Rust code written", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + } +} + +@Composable +private fun SidebarItem(item: NavItem, isSelected: Boolean, onClick: () -> Unit) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + + val bgColor = when { + isSelected -> AppColors.sidebarSelected + isHovered -> AppColors.sidebarHover + else -> Color.Transparent + } + val contentColor = when { + isSelected -> AppColors.accent + isHovered -> AppColors.textPrimary + else -> AppColors.textSecondary + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .hoverable(interactionSource) + .clickable(interactionSource = interactionSource, indication = null, onClick = onClick) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = item.icon, + contentDescription = item.label, + tint = contentColor, + modifier = Modifier.size(18.dp), + ) + Spacer(Modifier.width(10.dp)) + Text( + item.label, + fontSize = 13.sp, + fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, + color = contentColor, + ) + if (isSelected) { + Spacer(Modifier.weight(1f)) + Box( + Modifier + .width(3.dp) + .height(16.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + } + } +} diff --git a/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Components.kt b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Components.kt new file mode 100644 index 00000000..d6ed05c3 --- /dev/null +++ b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Components.kt @@ -0,0 +1,88 @@ +package com.example.rustsymphonia + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SectionHeader(title: String, count: Int? = null) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .width(3.dp) + .height(18.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + Spacer(Modifier.width(10.dp)) + Text(title, style = MaterialTheme.typography.h6) + if (count != null) { + Spacer(Modifier.width(8.dp)) + Badge("$count") + } + } +} + +@Composable +fun Badge(text: String, color: Color = AppColors.accent) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .background(color.copy(alpha = 0.15f)) + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, fontSize = 11.sp, color = color, fontWeight = FontWeight.SemiBold) + } +} + +@Composable +fun InfoCard( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(AppColors.card) + .padding(14.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + content = content, + ) +} + +@Composable +fun MetricRow(label: String, value: String) { + Row( + Modifier.fillMaxWidth().padding(vertical = 1.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text(label, style = MaterialTheme.typography.body2) + Text(value, style = MaterialTheme.typography.body1, fontWeight = FontWeight.Medium) + } +} + +@Composable +fun StatusDot(ok: Boolean) { + Box( + Modifier + .size(8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(if (ok) AppColors.green else AppColors.red) + ) +} diff --git a/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Main.kt b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Main.kt new file mode 100644 index 00000000..57807c51 --- /dev/null +++ b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Main.kt @@ -0,0 +1,18 @@ +package com.example.rustsymphonia + +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "RustSymphonia — Symphonia + cpal via NNA", + state = rememberWindowState(width = 1000.dp, height = 700.dp), + ) { + SymphoniaTheme { + App() + } + } +} diff --git a/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Theme.kt b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Theme.kt new file mode 100644 index 00000000..acda048c --- /dev/null +++ b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/Theme.kt @@ -0,0 +1,69 @@ +package com.example.rustsymphonia + +import androidx.compose.material.Colors +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +object AppColors { + val bg = Color(0xFF0D0E12) + val surface = Color(0xFF15161C) + val card = Color(0xFF1A1B23) + val cardHover = Color(0xFF1F2029) + val border = Color(0xFF2A2D38) + val accent = Color(0xFF8B5CF6) + val accentDim = Color(0xFF5B3A9E) + val green = Color(0xFF4ADE80) + val greenDim = Color(0xFF166534) + val orange = Color(0xFFFBBF24) + val red = Color(0xFFEF4444) + val cyan = Color(0xFF22D3EE) + val purple = Color(0xFFA78BFA) + val pink = Color(0xFFF472B6) + val textPrimary = Color(0xFFE4E4E7) + val textSecondary = Color(0xFF9CA3AF) + val textMuted = Color(0xFF6B7280) + val sidebarBg = Color(0xFF101118) + val sidebarSelected = Color(0xFF1C1D2A) + val sidebarHover = Color(0xFF16171F) + val divider = Color(0xFF2A2D38) +} + +val DarkColorPalette = Colors( + primary = AppColors.accent, + primaryVariant = AppColors.accentDim, + secondary = AppColors.cyan, + secondaryVariant = AppColors.purple, + background = AppColors.bg, + surface = AppColors.surface, + error = AppColors.red, + onPrimary = Color.White, + onSecondary = Color.White, + onBackground = AppColors.textPrimary, + onSurface = AppColors.textPrimary, + onError = Color.White, + isLight = false, +) + +val AppTypography = Typography( + h5 = TextStyle(fontWeight = FontWeight.Bold, fontSize = 20.sp, color = AppColors.textPrimary), + h6 = TextStyle(fontWeight = FontWeight.SemiBold, fontSize = 16.sp, color = AppColors.textPrimary), + subtitle1 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 14.sp, color = AppColors.textPrimary), + subtitle2 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp, color = AppColors.textSecondary), + body1 = TextStyle(fontSize = 13.sp, color = AppColors.textPrimary), + body2 = TextStyle(fontSize = 12.sp, color = AppColors.textSecondary), + caption = TextStyle(fontSize = 11.sp, color = AppColors.textMuted), +) + +@Composable +fun SymphoniaTheme(content: @Composable () -> Unit) { + MaterialTheme( + colors = DarkColorPalette, + typography = AppTypography, + content = content, + ) +} diff --git a/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/tabs/BridgeTestsTab.kt b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/tabs/BridgeTestsTab.kt new file mode 100644 index 00000000..2d7c259e --- /dev/null +++ b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/tabs/BridgeTestsTab.kt @@ -0,0 +1,215 @@ +package com.example.rustsymphonia.tabs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rustsymphonia.* + +data class BridgeTestResult( + val label: String, + val value: String, + val success: Boolean = true, +) + +@Composable +fun BridgeTestsTab() { + var results by remember { mutableStateOf>(emptyList()) } + var hasRun by remember { mutableStateOf(false) } + + fun runAll() { + results = buildBridgeTests() + hasRun = true + } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + item { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + SectionHeader("Bridge Tests", count = if (hasRun) results.count { it.success } else null) + Spacer(Modifier.weight(1f)) + androidx.compose.material.Button(onClick = { runAll() }) { + Icon(Icons.Default.PlayArrow, contentDescription = null, modifier = Modifier.size(18.dp)) + Spacer(Modifier.width(6.dp)) + Text("Run Tests") + } + } + } + + if (!hasRun) { + item { + InfoCard { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + Icon(Icons.Default.Science, contentDescription = null, tint = AppColors.textMuted) + Text( + "Click 'Run Tests' to exercise the Rust bridge — creates symphonia + cpal types, calls methods, verifies round-trips.", + style = MaterialTheme.typography.body2, + ) + } + } + } + } + + itemsIndexed(results) { _, result -> + TestResultRow(result) + } + + if (hasRun) { + val passed = results.count { it.success } + val total = results.size + item { + Spacer(Modifier.height(4.dp)) + InfoCard { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + StatusDot(ok = passed == total) + Text( + "$passed / $total tests passed", + style = MaterialTheme.typography.subtitle1, + fontWeight = FontWeight.SemiBold, + ) + if (passed < total) { + Badge("${total - passed} failed", color = AppColors.red) + } + } + } + } + } + } +} + +@Composable +private fun TestResultRow(result: BridgeTestResult) { + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(AppColors.card) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + StatusDot(ok = result.success) + Column(modifier = Modifier.weight(1f)) { + Text(result.label, style = MaterialTheme.typography.body1) + Text(result.value, style = MaterialTheme.typography.caption, maxLines = 2) + } + } +} + +private fun buildBridgeTests(): List { + val results = mutableListOf() + + results += test("Hint — create + set extension") { + val hint = Hint() + hint.with_extension("mp3") + hint.mime_type("audio/mpeg") + hint.close() + "extension=mp3, mime=audio/mpeg" + } + results += test("FormatOptions — gapless") { + val opts = FormatOptions(prebuild_seek_index = false, seek_index_fill_rate = 20, enable_gapless = true) + opts.close() + "gapless=true, fill_rate=20" + } + results += test("MetadataOptions — limits") { + val opts = MetadataOptions(limit_metadata_bytes = Limit.default(), limit_visual_bytes = Limit.none()) + opts.close() + "metadata=Default, visuals=None" + } + results += test("DecoderOptions") { + val opts = DecoderOptions(verify = true) + opts.close() + "verify=true" + } + results += test("CodecParameters — builder") { + val cp = CodecParameters() + .with_sample_rate(44100) + .with_bits_per_sample(16) + .with_channel_layout(Layout.Stereo) + cp.close() + "44100 Hz, 16-bit, Stereo" + } + results += test("Track — create") { + val cp = CodecParameters().with_sample_rate(48000) + val track = Track(id = 0, codec_params = cp) + track.close() + "Track(id=0)" + } + results += test("SignalSpec — with layout") { + val spec = SignalSpec.new_with_layout(rate = 44100, layout = Layout.Stereo) + spec.close() + "rate=44100, layout=Stereo" + } + results += test("Time — from h/m/s") { + val t = Time(seconds = 120, frac = 0.5) + t.close() + val t2 = Time.from_hhmmss(1, 30, 45, 0) + val ok = t2 != null + t2?.close() + "Time(120.5s), from_hhmmss=${if (ok) "OK" else "null"}" + } + results += test("SeekMode — enum") { + "Coarse=${SeekMode.Coarse.ordinal}, Accurate=${SeekMode.Accurate.ordinal}" + } + results += test("SampleFormat — enum") { + val names = SampleFormat.entries.joinToString { it.name } + "${SampleFormat.entries.size} formats: $names" + } + results += test("Packet — create + read") { + val pkt = Packet.new_from_slice(track_id = 1, ts = 44100L, dur = 1024L, buf = ByteArray(512) { it.toByte() }) + val info = "track_id=${pkt.track_id()}, ts=${pkt.ts()}, dur=${pkt.dur()}, buf=${pkt.buf().size}B" + pkt.close() + info + } + results += test("CodecRegistry — create") { + val reg = CodecRegistry() + reg.close() + "empty registry created" + } + results += test("Limit — sealed enum") { + val none = Limit.none() + val def = Limit.default() + val info = "None=${none.tag}, Default=${def.tag}" + none.close(); def.close() + info + } + results += test("cpal types") { + "Host, Device, Stream types bridged" + } + + return results +} + +private fun test(label: String, block: () -> String): BridgeTestResult { + return try { + BridgeTestResult(label = label, value = block(), success = true) + } catch (e: Throwable) { + e.printStackTrace() + BridgeTestResult(label = label, value = e.message ?: e.toString(), success = false) + } +} diff --git a/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/tabs/PlayerTab.kt b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/tabs/PlayerTab.kt new file mode 100644 index 00000000..604ffac2 --- /dev/null +++ b/examples/rust-symphonia/src/jvmMain/kotlin/com/example/rustsymphonia/tabs/PlayerTab.kt @@ -0,0 +1,261 @@ +package com.example.rustsymphonia.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.example.rustsymphonia.* + +// --------------------------------------------------------------------------- +// Demonstrates what the Symphonia + cpal bridge can do today. +// +// The full file→decode→play pipeline needs MediaSourceStream (takes dyn +// MediaSource) and Probe (no public ctor), which aren't bridgeable yet. +// This tab exercises every bridged API that IS available. +// --------------------------------------------------------------------------- + +@Composable +fun PlayerTab() { + var probeResults by remember { mutableStateOf>?>(null) } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + // --- Pipeline status --- + item { + SectionHeader("Audio Pipeline") + InfoCard { + Text( + "Symphonia decode → cpal output", + style = MaterialTheme.typography.subtitle1, + fontWeight = FontWeight.SemiBold, + ) + Spacer(Modifier.height(4.dp)) + PipelineStep("Hint", "Set format hints for the prober", true) + PipelineStep("FormatOptions", "Configure gapless decoding", true) + PipelineStep("MetadataOptions", "Set metadata read limits", true) + PipelineStep("MediaSourceStream", "Wrap a file as a byte source", false, + "takes dyn MediaSource — interface params not bridgeable yet") + PipelineStep("Probe → FormatReader", "Auto-detect container format", false, + "needs MediaSourceStream + no public ctor") + PipelineStep("CodecRegistry → Decoder", "Decode packets to PCM", true, + note = "registry + decoder types available") + PipelineStep("cpal Host → Device → Stream", "Platform audio output", false, + "default_host() is a top-level fn not bridged yet") + } + } + + // --- Exercice live APIs --- + item { + SectionHeader("Live API Demo") + InfoCard { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + androidx.compose.material.Button(onClick = { probeResults = exercisePipeline() }) { + Icon(Icons.Default.PlayArrow, contentDescription = null, modifier = Modifier.size(18.dp)) + Spacer(Modifier.width(6.dp)) + Text("Run Pipeline Demo") + } + Text( + "Creates real Rust objects via FFM, exercises the bridged API surface", + style = MaterialTheme.typography.caption, + ) + } + } + } + + if (probeResults != null) { + item { + InfoCard { + probeResults!!.forEach { (label, value) -> + MetricRow(label, value) + } + } + } + } + + // --- Codec info --- + item { + SectionHeader("Supported Codecs") + InfoCard { + Text( + "Symphonia built-in codecs (compiled into the native library)", + style = MaterialTheme.typography.caption, + ) + Spacer(Modifier.height(4.dp)) + val codecs = listOf( + "MP3" to "MPEG-1/2 Audio Layer III", + "AAC" to "Advanced Audio Coding (LC)", + "ALAC" to "Apple Lossless", + "FLAC" to "Free Lossless Audio Codec", + "Vorbis" to "OGG Vorbis", + "WAV" to "RIFF Waveform (PCM/IEEE)", + "AIFF" to "Audio Interchange File Format", + "MKV/WebM" to "Matroska container", + "ISO MP4" to "MP4/M4A container", + ) + codecs.forEach { (name, desc) -> MetricRow(name, desc) } + } + } + + // --- Sample format + enum info --- + item { + SectionHeader("Sample Formats") + InfoCard { + SampleFormat.entries.forEach { + MetricRow(it.name, "ordinal=${it.ordinal}") + } + } + } + + item { + SectionHeader("Seek Modes") + InfoCard { + MetricRow("Coarse", "Fast — lands on nearest keyframe") + MetricRow("Accurate", "Sample-accurate — full decode from keyframe") + } + } + } +} + +@Composable +private fun PipelineStep( + name: String, + description: String, + available: Boolean, + note: String? = null, +) { + Row( + modifier = Modifier.fillMaxWidth().padding(vertical = 3.dp), + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.spacedBy(10.dp), + ) { + StatusDot(ok = available) + Column(modifier = Modifier.weight(1f)) { + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Text(name, style = MaterialTheme.typography.body1, fontWeight = FontWeight.Medium) + if (!available) Badge("not yet", color = AppColors.orange) + } + Text(description, style = MaterialTheme.typography.caption) + if (note != null) { + Text(note, style = MaterialTheme.typography.caption, color = AppColors.orange) + } + } + } +} + +private fun exercisePipeline(): List> { + val results = mutableListOf>() + + // Step 1: Hint + run { + val hint = Hint() + val h2 = hint.with_extension("mp3") + val h3 = h2.mime_type("audio/mpeg") + h3.close() + results += "1. Hint" to "extension=mp3, mime=audio/mpeg" + } + + // Step 2: FormatOptions + run { + val opts = FormatOptions( + prebuild_seek_index = false, + seek_index_fill_rate = 20, + enable_gapless = true, + ) + opts.close() + results += "2. FormatOptions" to "gapless=true, seek_fill_rate=20" + } + + // Step 3: MetadataOptions + run { + val metaOpts = MetadataOptions( + limit_metadata_bytes = Limit.default(), + limit_visual_bytes = Limit.none(), + ) + metaOpts.close() + results += "3. MetadataOptions" to "metadata=Default, visuals=None" + } + + // Step 4: CodecParameters builder + run { + val cp = CodecParameters() + .with_sample_rate(44100) + .with_bits_per_sample(16) + .with_n_frames(44100 * 300) + .with_channel_layout(Layout.Stereo) + cp.close() + results += "4. CodecParameters" to "44100Hz, 16-bit, Stereo, 5min" + } + + // Step 5: Track + run { + val cp = CodecParameters().with_sample_rate(48000) + val track = Track(id = 0, codec_params = cp) + track.close() + results += "5. Track" to "id=0, sample_rate=48000" + } + + // Step 6: SignalSpec + run { + val spec = SignalSpec.new_with_layout(rate = 44100, layout = Layout.Stereo) + spec.close() + results += "6. SignalSpec" to "rate=44100, layout=Stereo" + } + + // Step 7: Packet round-trip + run { + val data = ByteArray(1024) { (it % 256).toByte() } + val pkt = Packet.new_from_slice(track_id = 1, ts = 88200L, dur = 1024L, buf = data) + val readBack = "track_id=${pkt.track_id()}, ts=${pkt.ts()}, dur=${pkt.dur()}, buf=${pkt.buf().size}B" + pkt.close() + results += "7. Packet" to readBack + } + + // Step 8: Time + run { + val t = Time(seconds = 185, frac = 0.5) + t.close() + val t2 = Time.from_hhmmss(0, 3, 5, 500_000_000) + t2?.close() + results += "8. Time" to "185.5s, from_hhmmss=${if (t2 != null) "OK" else "null"}" + } + + // Step 9: DecoderOptions + run { + val opts = DecoderOptions(verify = false) + opts.close() + results += "9. DecoderOptions" to "verify=false" + } + + // Step 10: CodecRegistry + run { + val reg = CodecRegistry() + reg.close() + results += "10. CodecRegistry" to "empty registry created" + } + + // Step 11: Limit sealed enum + run { + val none = Limit.none() + val def = Limit.default() + val info = "none.tag=${none.tag}, default.tag=${def.tag}" + none.close(); def.close() + results += "11. Limit" to info + } + + results += "Pipeline" to "${results.size} steps executed via Rust FFM bridge" + return results +} diff --git a/examples/rust-sysinfo/build.gradle.kts b/examples/rust-sysinfo/build.gradle.kts new file mode 100644 index 00000000..f5071937 --- /dev/null +++ b/examples/rust-sysinfo/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") version "1.10.2" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(compose.runtime) + implementation(compose.materialIconsExtended) + } + } + } +} + +compose.desktop { + application { + mainClass = "com.example.rustsysinfo.MainKt" + nativeDistributions { + packageName = "RustSysInfo" + } + jvmArgs("--enable-native-access=ALL-UNNAMED") + } +} + +rustImport { + libraryName = "test" + jvmPackage = "com.example.rustsysinfo" + buildType = "release" + crate("sysinfo", "0.38.4") +// cratePath("rust-sysinfo", "${projectDir}/rust") +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/App.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/App.kt new file mode 100644 index 00000000..5a4d796f --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/App.kt @@ -0,0 +1,184 @@ +package com.example.rustsysinfo + +import androidx.compose.animation.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rustsysinfo.tabs.* + +enum class NavItem(val label: String, val icon: ImageVector) { + System("System", Icons.Default.Info), + Cpu("CPU", Icons.Default.Memory), + Memory("Memory", Icons.Default.Storage), + Disks("Disks", Icons.Default.Album), + Network("Network", Icons.Default.Wifi), + Processes("Processes", Icons.Default.List), + Sensors("Sensors", Icons.Default.Thermostat), + Users("Users", Icons.Default.Person), + Groups("Groups", Icons.Default.Group), +} + +@Composable +fun App() { + var selected by remember { mutableStateOf(NavItem.System) } + + val systemFlow = remember { systemInfoFlow() } + val dynamicFlow = remember { dynamicStateFlow() } + val systemInfo by systemFlow.collectAsState(initial = null) + val state by dynamicFlow.collectAsState(initial = null) + + Row(Modifier.fillMaxSize().background(AppColors.bg)) { + // Sidebar + Sidebar(selected) { selected = it } + + // Content + Box( + Modifier + .fillMaxSize() + .padding(start = 0.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) + ) { + when (selected) { + NavItem.System -> SystemTab(systemInfo, state?.loadAvg) + NavItem.Cpu -> CpuTab(state?.cpus ?: emptyList(), state?.globalCpuUsage ?: 0f) + NavItem.Memory -> MemoryTab(state?.memory) + NavItem.Disks -> DisksTab(state?.disks ?: emptyList()) + NavItem.Network -> NetworkTab(state?.networks ?: emptyList()) + NavItem.Processes -> ProcessesTab(state?.processes ?: emptyList()) + NavItem.Sensors -> SensorsTab(state?.sensors ?: emptyList()) + NavItem.Users -> UsersTab(state?.users ?: emptyList()) + NavItem.Groups -> GroupsTab(state?.groups ?: emptyList()) + } + } + } +} + +@Composable +private fun Sidebar(selected: NavItem, onSelect: (NavItem) -> Unit) { + Column( + modifier = Modifier + .fillMaxHeight() + .width(200.dp) + .background(AppColors.sidebarBg) + .padding(vertical = 16.dp, horizontal = 8.dp), + ) { + // App title + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .size(8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(AppColors.green) + ) + Spacer(Modifier.width(10.dp)) + Text( + "Sysinfo", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = AppColors.textPrimary, + ) + } + Text( + " Rust FFM Bridge", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + + Spacer(Modifier.height(20.dp)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(12.dp)) + + NavItem.entries.forEach { item -> + SidebarItem( + item = item, + isSelected = item == selected, + onClick = { onSelect(item) }, + ) + } + + Spacer(Modifier.weight(1f)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(8.dp)) + Text( + " Powered by sysinfo 0.38", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + } +} + +@Composable +private fun SidebarItem(item: NavItem, isSelected: Boolean, onClick: () -> Unit) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + + val bgColor = when { + isSelected -> AppColors.sidebarSelected + isHovered -> AppColors.sidebarHover + else -> Color.Transparent + } + val contentColor = when { + isSelected -> AppColors.accent + isHovered -> AppColors.textPrimary + else -> AppColors.textSecondary + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .hoverable(interactionSource) + .clickable(interactionSource = interactionSource, indication = null, onClick = onClick) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = item.icon, + contentDescription = item.label, + tint = contentColor, + modifier = Modifier.size(18.dp), + ) + Spacer(Modifier.width(10.dp)) + Text( + item.label, + fontSize = 13.sp, + fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, + color = contentColor, + ) + + if (isSelected) { + Spacer(Modifier.weight(1f)) + Box( + Modifier + .width(3.dp) + .height(16.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Components.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Components.kt new file mode 100644 index 00000000..fd0b2ed2 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Components.kt @@ -0,0 +1,160 @@ +package com.example.rustsysinfo + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SectionHeader(title: String, count: Int? = null) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .width(3.dp) + .height(18.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + Spacer(Modifier.width(10.dp)) + Text( + title, + style = MaterialTheme.typography.h6, + ) + if (count != null) { + Spacer(Modifier.width(8.dp)) + Badge("$count") + } + } +} + +@Composable +fun Badge(text: String, color: Color = AppColors.accent) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .background(color.copy(alpha = 0.15f)) + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, fontSize = 11.sp, color = color, fontWeight = FontWeight.SemiBold) + } +} + +@Composable +fun InfoCard( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(AppColors.card) + .padding(14.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + content = content, + ) +} + +@Composable +fun MetricRow(label: String, value: String) { + Row( + Modifier.fillMaxWidth().padding(vertical = 1.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text(label, style = MaterialTheme.typography.body2) + Text(value, style = MaterialTheme.typography.body1, fontWeight = FontWeight.Medium) + } +} + +@Composable +fun GaugeBar( + label: String, + fraction: Float, + detail: String, + height: Dp = 6.dp, + colors: Pair = barColors(fraction), +) { + val animatedFraction by animateFloatAsState( + targetValue = fraction.coerceIn(0f, 1f), + animationSpec = tween(500), + ) + Column(Modifier.fillMaxWidth()) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text(label, style = MaterialTheme.typography.caption) + Text(detail, style = MaterialTheme.typography.caption, fontWeight = FontWeight.Medium) + } + Spacer(Modifier.height(3.dp)) + Box( + Modifier + .fillMaxWidth() + .height(height) + .clip(RoundedCornerShape(height / 2)) + .background(AppColors.border) + ) { + Box( + Modifier + .fillMaxHeight() + .fillMaxWidth(animatedFraction) + .clip(RoundedCornerShape(height / 2)) + .background(Brush.horizontalGradient(listOf(colors.first, colors.second))) + ) + } + } +} + +fun barColors(fraction: Float): Pair = when { + fraction > 0.9f -> AppColors.red to AppColors.red + fraction > 0.7f -> AppColors.orange to AppColors.red + fraction > 0.5f -> AppColors.accent to AppColors.orange + else -> AppColors.accent to AppColors.accent +} + +@Composable +fun StatBox( + label: String, + value: String, + modifier: Modifier = Modifier, + accentColor: Color = AppColors.accent, +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(10.dp)) + .background(AppColors.card) + .padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(value, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = accentColor) + Spacer(Modifier.height(2.dp)) + Text(label, style = MaterialTheme.typography.caption) + } +} + +@Composable +fun MiniLabel(text: String, color: Color = AppColors.textMuted) { + Text( + text, + style = MaterialTheme.typography.caption, + color = color, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Formatting.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Formatting.kt new file mode 100644 index 00000000..30d3b5e6 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Formatting.kt @@ -0,0 +1,28 @@ +package com.example.rustsysinfo + +fun formatBytes(bytes: Long): String { + if (bytes < 1024) return "$bytes B" + val kb = bytes / 1024.0 + if (kb < 1024) return "%.1f KB".format(kb) + val mb = kb / 1024.0 + if (mb < 1024) return "%.1f MB".format(mb) + val gb = mb / 1024.0 + if (gb < 1024) return "%.2f GB".format(gb) + val tb = gb / 1024.0 + return "%.2f TB".format(tb) +} + +fun formatDuration(seconds: Long): String { + val days = seconds / 86400 + val hours = (seconds % 86400) / 3600 + val minutes = (seconds % 3600) / 60 + val secs = seconds % 60 + return buildString { + if (days > 0) append("${days}d ") + if (hours > 0) append("${hours}h ") + if (minutes > 0) append("${minutes}m ") + if (days == 0L) append("${secs}s") + }.trim() +} + +fun formatNumber(n: Long): String = "%,d".format(n) diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Main.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Main.kt new file mode 100644 index 00000000..9c786bca --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Main.kt @@ -0,0 +1,18 @@ +package com.example.rustsysinfo + +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Sysinfo — Rust FFM Bridge", + state = rememberWindowState(width = 1100.dp, height = 750.dp), + ) { + SysInfoTheme { + App() + } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/SysInfoData.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/SysInfoData.kt new file mode 100644 index 00000000..fccb43eb --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/SysInfoData.kt @@ -0,0 +1,120 @@ +package com.example.rustsysinfo + +data class SystemInfo( + val name: String, + val osVersion: String, + val longOsVersion: String?, + val kernelVersion: String, + val kernelLongVersion: String, + val hostname: String, + val cpuArch: String, + val uptime: Long, + val physicalCores: Long?, + val distributionId: String, + val distributionIdLike: List, + val openFilesLimit: Long?, +) + +data class MemoryInfo( + val totalMemory: Long, + val usedMemory: Long, + val freeMemory: Long, + val availableMemory: Long, + val totalSwap: Long, + val usedSwap: Long, + val freeSwap: Long, + val cgroupLimits: CGroupLimits?, +) + +data class CpuInfo( + val name: String, + val brand: String, + val vendorId: String, + val frequency: Long, + val usage: Float, +) + +data class DiskInfo( + val name: String, + val mountPoint: String, + val fileSystem: String, + val totalSpace: Long, + val availableSpace: Long, + val isRemovable: Boolean, + val kind: String, + val isReadOnly: Boolean, + val readBytes: Long, + val writtenBytes: Long, +) + +data class NetworkInfo( + val name: String, + val received: Long, + val totalReceived: Long, + val transmitted: Long, + val totalTransmitted: Long, + val packetsReceived: Long, + val packetsTransmitted: Long, + val totalPacketsReceived: Long, + val totalPacketsTransmitted: Long, + val errorsReceived: Long, + val errorsTransmitted: Long, + val totalErrorsReceived: Long, + val totalErrorsTransmitted: Long, + val mtu: Long, +) + +data class ProcessInfo( + val pid: Long, + val name: String, + val cpuUsage: Float, + val memory: Long, + val virtualMemory: Long, + val status: String, + val startTime: Long, + val runTime: Long, + val exe: String?, + val cwd: String?, + val root: String?, + val diskReadBytes: Long, + val diskWrittenBytes: Long, + val cmd: List, + val environ: List, + val parentPid: Long?, + val sessionId: Long?, + val threadKind: String?, + val openFiles: Long?, + val openFilesLimit: Long?, + val accumulatedCpuTime: Long, + val taskCount: Int?, +) + +data class SensorInfo( + val label: String, + val id: String?, + val temperature: Float?, + val max: Float?, + val critical: Float?, +) + +data class UserInfo( + val name: String, + val groups: List, +) + +data class GroupInfo( + val name: String, +) + +data class DynamicState( + val memory: MemoryInfo, + val globalCpuUsage: Float, + val cpus: List, + val disks: List, + val loadAvg: LoadAvg, + val networks: List, + val processes: List, + val sensors: List, + val users: List, + val groups: List, +) diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/SysInfoFlows.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/SysInfoFlows.kt new file mode 100644 index 00000000..fcf5c594 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/SysInfoFlows.kt @@ -0,0 +1,174 @@ +package com.example.rustsysinfo + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +fun systemInfoFlow(): Flow = flow { + emit( + SystemInfo( + name = System.name() ?: "Unknown", + osVersion = System.os_version() ?: "Unknown", + longOsVersion = System.long_os_version(), + kernelVersion = System.kernel_version() ?: "Unknown", + kernelLongVersion = System.kernel_long_version(), + hostname = System.host_name() ?: "Unknown", + cpuArch = System.cpu_arch(), + uptime = System.uptime(), + physicalCores = System.physical_core_count(), + distributionId = System.distribution_id(), + distributionIdLike = System.distribution_id_like(), + openFilesLimit = System.open_files_limit(), + ) + ) +}.flowOn(Dispatchers.IO) + +fun dynamicStateFlow(interval: Duration = 2.seconds): Flow = flow { + val sys = System.new_all() + try { + while (true) { + sys.refresh_all() + + val memory = MemoryInfo( + totalMemory = sys.total_memory(), + usedMemory = sys.used_memory(), + freeMemory = sys.free_memory(), + availableMemory = sys.available_memory(), + totalSwap = sys.total_swap(), + usedSwap = sys.used_swap(), + freeSwap = sys.free_swap(), + cgroupLimits = sys.cgroup_limits(), + ) + + val cpus = sys.cpus().map { cpu -> + CpuInfo( + name = cpu.name(), + brand = cpu.brand(), + vendorId = cpu.vendor_id(), + frequency = cpu.frequency(), + usage = cpu.cpu_usage(), + ) + } + + val diskList = Disks.new_with_refreshed_list() + val disks = diskList.list().map { disk -> + val usage = disk.usage() + DiskInfo( + name = disk.name(), + mountPoint = disk.mount_point(), + fileSystem = disk.file_system(), + totalSpace = disk.total_space(), + availableSpace = disk.available_space(), + isRemovable = disk.is_removable(), + kind = disk.kind().tag.name, + isReadOnly = disk.is_read_only(), + readBytes = usage.total_read_bytes, + writtenBytes = usage.total_written_bytes, + ) + } + diskList.close() + + val netList = Networks.new_with_refreshed_list() + val networks = netList.list().map { (name, data) -> + NetworkInfo( + name = name, + received = data.received(), + totalReceived = data.total_received(), + transmitted = data.transmitted(), + totalTransmitted = data.total_transmitted(), + packetsReceived = data.packets_received(), + packetsTransmitted = data.packets_transmitted(), + totalPacketsReceived = data.total_packets_received(), + totalPacketsTransmitted = data.total_packets_transmitted(), + errorsReceived = data.errors_on_received(), + errorsTransmitted = data.errors_on_transmitted(), + totalErrorsReceived = data.total_errors_on_received(), + totalErrorsTransmitted = data.total_errors_on_transmitted(), + mtu = data.mtu(), + ) + } + netList.close() + + val processes = sys.processes().values + .sortedByDescending { it.cpu_usage() } + .take(30) + .map { proc -> + val du = proc.disk_usage() + ProcessInfo( + pid = proc.pid().as_u32().toLong(), + name = proc.name(), + cpuUsage = proc.cpu_usage(), + memory = proc.memory(), + virtualMemory = proc.virtual_memory(), + status = proc.status().tag.name, + startTime = proc.start_time(), + runTime = proc.run_time(), + exe = proc.exe(), + cwd = proc.cwd(), + root = proc.root(), + diskReadBytes = du.total_read_bytes, + diskWrittenBytes = du.total_written_bytes, + cmd = proc.cmd(), + environ = proc.environ(), + parentPid = proc.parent()?.as_u32()?.toLong(), + sessionId = proc.session_id()?.as_u32()?.toLong(), + threadKind = proc.thread_kind()?.name, + openFiles = proc.open_files(), + openFilesLimit = proc.open_files_limit(), + accumulatedCpuTime = proc.accumulated_cpu_time(), + taskCount = proc.tasks()?.size, + ) + } + + val compList = Components.new_with_refreshed_list() + val sensors = compList.list().map { comp -> + SensorInfo( + label = comp.label(), + id = comp.id(), + temperature = comp.temperature(), + max = comp.max(), + critical = comp.critical(), + ) + } + compList.close() + + val userList = Users.new_with_refreshed_list() + val users = userList.list().map { user -> + UserInfo( + name = user.name(), + groups = user.groups().map { it.name() }, + ) + } + userList.close() + + val groupList = Groups.new_with_refreshed_list() + val groups = groupList.list().map { group -> + GroupInfo(name = group.name()) + } + groupList.close() + + emit( + DynamicState( + memory = memory, + globalCpuUsage = sys.global_cpu_usage(), + cpus = cpus, + disks = disks, + loadAvg = System.load_average(), + networks = networks, + processes = processes, + sensors = sensors, + users = users, + groups = groups, + ) + ) + + delay(interval) + } + } finally { + sys.close() + } +}.flowOn(Dispatchers.IO) diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Theme.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Theme.kt new file mode 100644 index 00000000..ce0aead5 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/Theme.kt @@ -0,0 +1,72 @@ +package com.example.rustsysinfo + +import androidx.compose.material.Colors +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Dark palette inspired by modern system monitors +object AppColors { + val bg = Color(0xFF0F1117) + val surface = Color(0xFF181A20) + val surfaceLight = Color(0xFF1E2028) + val card = Color(0xFF1C1E26) + val cardHover = Color(0xFF22242E) + val border = Color(0xFF2A2D38) + val accent = Color(0xFF6C8EFF) + val accentDim = Color(0xFF3D5299) + val green = Color(0xFF4ADE80) + val greenDim = Color(0xFF166534) + val orange = Color(0xFFFBBF24) + val orangeDim = Color(0xFF92400E) + val red = Color(0xFFEF4444) + val redDim = Color(0xFF991B1B) + val cyan = Color(0xFF22D3EE) + val purple = Color(0xFFA78BFA) + val textPrimary = Color(0xFFE4E4E7) + val textSecondary = Color(0xFF9CA3AF) + val textMuted = Color(0xFF6B7280) + val sidebarBg = Color(0xFF13141A) + val sidebarSelected = Color(0xFF1E2235) + val sidebarHover = Color(0xFF1A1C24) + val divider = Color(0xFF2A2D38) +} + +val DarkColorPalette = Colors( + primary = AppColors.accent, + primaryVariant = AppColors.accentDim, + secondary = AppColors.cyan, + secondaryVariant = AppColors.purple, + background = AppColors.bg, + surface = AppColors.surface, + error = AppColors.red, + onPrimary = Color.White, + onSecondary = Color.White, + onBackground = AppColors.textPrimary, + onSurface = AppColors.textPrimary, + onError = Color.White, + isLight = false, +) + +val AppTypography = Typography( + h5 = TextStyle(fontWeight = FontWeight.Bold, fontSize = 20.sp, color = AppColors.textPrimary), + h6 = TextStyle(fontWeight = FontWeight.SemiBold, fontSize = 16.sp, color = AppColors.textPrimary), + subtitle1 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 14.sp, color = AppColors.textPrimary), + subtitle2 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp, color = AppColors.textSecondary), + body1 = TextStyle(fontSize = 13.sp, color = AppColors.textPrimary), + body2 = TextStyle(fontSize = 12.sp, color = AppColors.textSecondary), + caption = TextStyle(fontSize = 11.sp, color = AppColors.textMuted), +) + +@Composable +fun SysInfoTheme(content: @Composable () -> Unit) { + MaterialTheme( + colors = DarkColorPalette, + typography = AppTypography, + content = content, + ) +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/CpuTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/CpuTab.kt new file mode 100644 index 00000000..6ac1d502 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/CpuTab.kt @@ -0,0 +1,52 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun CpuTab(cpus: List, globalUsage: Float) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { SectionHeader("Overview") } + item { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Total Usage", "%.1f%%".format(globalUsage), Modifier.weight(1f), AppColors.accent) + if (cpus.isNotEmpty()) { + StatBox("Cores", "${cpus.size}", Modifier.weight(1f), AppColors.cyan) + StatBox("Frequency", "${cpus[0].frequency} MHz", Modifier.weight(1f), AppColors.purple) + } + } + } + + if (cpus.isNotEmpty()) { + item { + InfoCard { + MetricRow("Brand", cpus[0].brand) + MetricRow("Vendor", cpus[0].vendorId) + } + } + } + + item { SectionHeader("Per-Core Usage", cpus.size) } + + items(cpus) { cpu -> + InfoCard { + GaugeBar( + label = cpu.name, + fraction = cpu.usage / 100f, + detail = "%.1f%% @ %d MHz".format(cpu.usage, cpu.frequency), + ) + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/DisksTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/DisksTab.kt new file mode 100644 index 00000000..d73966fc --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/DisksTab.kt @@ -0,0 +1,64 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun DisksTab(disks: List) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { SectionHeader("Disks", disks.size) } + + items(disks) { disk -> + InfoCard { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Badge( + disk.name.ifEmpty { disk.mountPoint }, + color = AppColors.textPrimary, + ) + Badge( + disk.kind, + color = when (disk.kind) { + "SSD" -> AppColors.green + "HDD" -> AppColors.orange + else -> AppColors.textMuted + }, + ) + } + Spacer(Modifier.height(4.dp)) + + val usedSpace = disk.totalSpace - disk.availableSpace + val usedPct = if (disk.totalSpace > 0) usedSpace.toFloat() / disk.totalSpace else 0f + GaugeBar("Space", usedPct, "${formatBytes(usedSpace)} / ${formatBytes(disk.totalSpace)}") + + MetricRow("Mount", disk.mountPoint) + MetricRow("Filesystem", disk.fileSystem) + + val flags = buildList { + if (disk.isRemovable) add("Removable") + if (disk.isReadOnly) add("Read-only") + } + if (flags.isNotEmpty()) { + MetricRow("Flags", flags.joinToString(", ")) + } + + if (disk.readBytes > 0 || disk.writtenBytes > 0) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Read", formatBytes(disk.readBytes), Modifier.weight(1f), AppColors.cyan) + StatBox("Written", formatBytes(disk.writtenBytes), Modifier.weight(1f), AppColors.orange) + } + } + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/GroupsTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/GroupsTab.kt new file mode 100644 index 00000000..ed021adc --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/GroupsTab.kt @@ -0,0 +1,34 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun GroupsTab(groups: List) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + item { SectionHeader("System Groups", groups.size) } + + if (groups.isEmpty()) { + item { + InfoCard { MiniLabel("No groups found") } + } + } + + items(groups) { group -> + InfoCard { + Badge(group.name, color = AppColors.purple) + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/MemoryTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/MemoryTab.kt new file mode 100644 index 00000000..9b711fd1 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/MemoryTab.kt @@ -0,0 +1,74 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun MemoryTab(info: MemoryInfo?) { + if (info == null) { + CircularProgressIndicator(color = AppColors.accent) + return + } + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { SectionHeader("RAM") } + item { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Total", formatBytes(info.totalMemory), Modifier.weight(1f), AppColors.cyan) + StatBox("Used", formatBytes(info.usedMemory), Modifier.weight(1f), AppColors.accent) + StatBox("Available", formatBytes(info.availableMemory), Modifier.weight(1f), AppColors.green) + } + } + item { + InfoCard { + val usedPct = if (info.totalMemory > 0) info.usedMemory.toFloat() / info.totalMemory else 0f + GaugeBar("Usage", usedPct, "${formatBytes(info.usedMemory)} / ${formatBytes(info.totalMemory)}") + MetricRow("Free", formatBytes(info.freeMemory)) + } + } + + item { SectionHeader("Swap") } + if (info.totalSwap > 0) { + item { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Total", formatBytes(info.totalSwap), Modifier.weight(1f), AppColors.purple) + StatBox("Used", formatBytes(info.usedSwap), Modifier.weight(1f), AppColors.orange) + StatBox("Free", formatBytes(info.freeSwap), Modifier.weight(1f), AppColors.green) + } + } + item { + InfoCard { + val swapPct = info.usedSwap.toFloat() / info.totalSwap + GaugeBar("Usage", swapPct, "${formatBytes(info.usedSwap)} / ${formatBytes(info.totalSwap)}") + } + } + } else { + item { + InfoCard { MiniLabel("No swap configured") } + } + } + + val cgroup = info.cgroupLimits + if (cgroup != null) { + item { SectionHeader("CGroup Limits") } + item { + InfoCard { + MetricRow("Total Memory", formatBytes(cgroup.total_memory)) + MetricRow("Free Memory", formatBytes(cgroup.free_memory)) + MetricRow("Free Swap", formatBytes(cgroup.free_swap)) + MetricRow("RSS", formatBytes(cgroup.rss)) + } + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/NetworkTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/NetworkTab.kt new file mode 100644 index 00000000..6eb91cd5 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/NetworkTab.kt @@ -0,0 +1,54 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun NetworkTab(networks: List) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { SectionHeader("Network Interfaces", networks.size) } + + items(networks) { net -> + InfoCard { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Badge(net.name, color = AppColors.textPrimary) + Badge("MTU ${net.mtu}", color = AppColors.textMuted) + } + Spacer(Modifier.height(4.dp)) + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Rx Total", formatBytes(net.totalReceived), Modifier.weight(1f), AppColors.green) + StatBox("Tx Total", formatBytes(net.totalTransmitted), Modifier.weight(1f), AppColors.cyan) + } + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Rx/s", "${formatBytes(net.received)}/s", Modifier.weight(1f), AppColors.green) + StatBox("Tx/s", "${formatBytes(net.transmitted)}/s", Modifier.weight(1f), AppColors.cyan) + } + + MetricRow("Packets Rx (delta)", formatNumber(net.packetsReceived)) + MetricRow("Packets Tx (delta)", formatNumber(net.packetsTransmitted)) + MetricRow("Total Packets Rx", formatNumber(net.totalPacketsReceived)) + MetricRow("Total Packets Tx", formatNumber(net.totalPacketsTransmitted)) + + if (net.errorsReceived > 0 || net.errorsTransmitted > 0) { + MetricRow("Errors Rx/Tx (delta)", "${net.errorsReceived} / ${net.errorsTransmitted}") + } + if (net.totalErrorsReceived > 0 || net.totalErrorsTransmitted > 0) { + MetricRow("Total Errors Rx/Tx", "${net.totalErrorsReceived} / ${net.totalErrorsTransmitted}") + } + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/ProcessesTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/ProcessesTab.kt new file mode 100644 index 00000000..63faa192 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/ProcessesTab.kt @@ -0,0 +1,96 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rustsysinfo.* + +@Composable +fun ProcessesTab(processes: List) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + item { SectionHeader("Top Processes by CPU", processes.size) } + + items(processes) { proc -> + InfoCard { + // Header: name + PID + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + proc.name, + style = MaterialTheme.typography.subtitle1, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + Badge("PID ${proc.pid}", color = AppColors.textMuted) + } + + // CPU gauge + GaugeBar("CPU", proc.cpuUsage / 100f, "%.1f%%".format(proc.cpuUsage)) + + // Memory stats + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Memory", formatBytes(proc.memory), Modifier.weight(1f), AppColors.accent) + StatBox("Virtual", formatBytes(proc.virtualMemory), Modifier.weight(1f), AppColors.purple) + } + + // Disk I/O + if (proc.diskReadBytes > 0 || proc.diskWrittenBytes > 0) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Disk R", formatBytes(proc.diskReadBytes), Modifier.weight(1f), AppColors.cyan) + StatBox("Disk W", formatBytes(proc.diskWrittenBytes), Modifier.weight(1f), AppColors.orange) + } + } + + // Status row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Badge(proc.status, color = when (proc.status) { + "Run" -> AppColors.green + "Sleep" -> AppColors.textMuted + "Zombie" -> AppColors.red + else -> AppColors.orange + }) + Badge(formatDuration(proc.runTime), color = AppColors.textSecondary) + Badge("CPU ${formatDuration(proc.accumulatedCpuTime)}", color = AppColors.textSecondary) + } + + // Details row + val details = buildList { + proc.parentPid?.let { add("Parent: $it") } + proc.openFiles?.let { add("FDs: $it") } + proc.openFilesLimit?.let { add("Limit: $it") } + proc.taskCount?.let { add("Threads: $it") } + proc.threadKind?.let { add("Kind: $it") } + proc.sessionId?.let { add("Session: $it") } + } + if (details.isNotEmpty()) { + MiniLabel(details.joinToString(" | ")) + } + + // Command / exe / root + if (proc.cmd.isNotEmpty()) { + MiniLabel("CMD: ${proc.cmd.joinToString(" ")}") + } else if (proc.exe != null) { + MiniLabel("EXE: ${proc.exe}") + } + if (proc.root != null) { + MiniLabel("Root: ${proc.root}") + } + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/SensorsTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/SensorsTab.kt new file mode 100644 index 00000000..1d842501 --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/SensorsTab.kt @@ -0,0 +1,67 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun SensorsTab(sensors: List) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { SectionHeader("Hardware Sensors", sensors.size) } + + if (sensors.isEmpty()) { + item { + InfoCard { MiniLabel("No sensors detected on this system") } + } + } + + items(sensors) { sensor -> + InfoCard { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Badge(sensor.label, color = AppColors.textPrimary) + if (sensor.id != null) { + Badge(sensor.id, color = AppColors.textMuted) + } + } + Spacer(Modifier.height(4.dp)) + + if (sensor.temperature != null) { + val critical = sensor.critical + val max = sensor.max + val fraction = when { + critical != null && critical > 0 -> (sensor.temperature / critical).coerceIn(0f, 1f) + max != null && max > 0 -> (sensor.temperature / max).coerceIn(0f, 1f) + else -> 0f + } + val tempColors = when { + fraction > 0.85f -> AppColors.red to AppColors.red + fraction > 0.6f -> AppColors.orange to AppColors.red + else -> AppColors.green to AppColors.cyan + } + GaugeBar( + label = "Temperature", + fraction = fraction, + detail = buildString { + append("%.1f\u00B0C".format(sensor.temperature)) + if (max != null) append(" max %.0f\u00B0C".format(max)) + if (critical != null) append(" crit %.0f\u00B0C".format(critical)) + }, + colors = tempColors, + ) + } else { + MiniLabel("Temperature: N/A") + } + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/SystemTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/SystemTab.kt new file mode 100644 index 00000000..47b8eb1c --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/SystemTab.kt @@ -0,0 +1,97 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun SystemTab(info: SystemInfo?, loadAvg: LoadAvg?) { + if (info == null) { + CircularProgressIndicator(color = AppColors.accent) + return + } + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { + SectionHeader("Operating System") + } + item { + InfoCard { + MetricRow("Name", info.name) + MetricRow("OS Version", info.osVersion) + if (info.longOsVersion != null) { + MetricRow("Full OS Version", info.longOsVersion) + } + MetricRow("Kernel", info.kernelVersion) + MetricRow("Kernel (full)", info.kernelLongVersion) + MetricRow("Hostname", info.hostname) + MetricRow("Architecture", info.cpuArch) + if (info.distributionId.isNotEmpty()) { + MetricRow("Distribution", info.distributionId) + } + if (info.distributionIdLike.isNotEmpty()) { + MetricRow("Based On", info.distributionIdLike.joinToString(", ")) + } + } + } + + item { + SectionHeader("Runtime") + } + item { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("Uptime", formatDuration(info.uptime), Modifier.weight(1f), AppColors.green) + StatBox("Cores", info.physicalCores?.toString() ?: "N/A", Modifier.weight(1f), AppColors.cyan) + StatBox("FD Limit", info.openFilesLimit?.let { formatNumber(it) } ?: "N/A", Modifier.weight(1f), AppColors.purple) + } + } + + if (loadAvg != null) { + item { SectionHeader("Load Average") } + item { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + StatBox("1 min", "%.2f".format(loadAvg.one), Modifier.weight(1f), AppColors.accent) + StatBox("5 min", "%.2f".format(loadAvg.five), Modifier.weight(1f), AppColors.accent) + StatBox("15 min", "%.2f".format(loadAvg.fifteen), Modifier.weight(1f), AppColors.accent) + } + } + } + + item { SectionHeader("Product") } + item { + InfoCard { + MetricRow("Product", Product.name() ?: "N/A") + MetricRow("Family", Product.family() ?: "N/A") + MetricRow("Vendor", Product.vendor_name() ?: "N/A") + MetricRow("Version", Product.version() ?: "N/A") + } + } + + item { SectionHeader("Motherboard") } + item { + val mb = Motherboard.new() + if (mb != null) { + InfoCard { + MetricRow("Name", mb.name() ?: "N/A") + MetricRow("Vendor", mb.vendor_name() ?: "N/A") + MetricRow("Version", mb.version() ?: "N/A") + MetricRow("Serial", mb.serial_number() ?: "N/A") + } + mb.close() + } else { + InfoCard { + MiniLabel("Not available") + } + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/UsersTab.kt b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/UsersTab.kt new file mode 100644 index 00000000..c79f8ecb --- /dev/null +++ b/examples/rust-sysinfo/src/jvmMain/kotlin/com/example/rustsysinfo/tabs/UsersTab.kt @@ -0,0 +1,42 @@ +package com.example.rustsysinfo.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.example.rustsysinfo.* + +@Composable +fun UsersTab(users: List) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(4.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + item { SectionHeader("System Users", users.size) } + + if (users.isEmpty()) { + item { + InfoCard { MiniLabel("No users found") } + } + } + + items(users) { user -> + InfoCard { + Badge(user.name, color = AppColors.cyan) + if (user.groups.isNotEmpty()) { + Spacer(Modifier.height(4.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + user.groups.forEach { group -> + Badge(group, color = AppColors.textMuted) + } + } + } + } + } + + item { Spacer(Modifier.height(8.dp)) } + } +} diff --git a/examples/rust-tray-icon/build.gradle.kts b/examples/rust-tray-icon/build.gradle.kts new file mode 100644 index 00000000..3965b935 --- /dev/null +++ b/examples/rust-tray-icon/build.gradle.kts @@ -0,0 +1,38 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") version "1.10.2" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" + id("io.github.kdroidfilter.nucleusnativeaccess") +} + +kotlin { + jvmToolchain(25) + jvm() + + sourceSets { + val jvmMain by getting { + dependencies { + implementation(compose.desktop.currentOs) + implementation(compose.runtime) + implementation(compose.materialIconsExtended) + } + } + } +} + +compose.desktop { + application { + mainClass = "com.example.rusttrayicon.MainKt" + nativeDistributions { + packageName = "RustTrayIcon" + } + jvmArgs("--enable-native-access=ALL-UNNAMED") + } +} + +rustImport { + libraryName = "tray_icon_wrapper" + jvmPackage = "com.example.rusttrayicon" + buildType = "release" + cratePath("tray-icon-wrapper", "${projectDir}/rust") +} diff --git a/examples/rust-tray-icon/rust/Cargo.toml b/examples/rust-tray-icon/rust/Cargo.toml new file mode 100644 index 00000000..f1106b23 --- /dev/null +++ b/examples/rust-tray-icon/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tray-icon-wrapper" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +tray-icon = "0.19" diff --git a/examples/rust-tray-icon/rust/build.rs b/examples/rust-tray-icon/rust/build.rs new file mode 100644 index 00000000..21638f0c --- /dev/null +++ b/examples/rust-tray-icon/rust/build.rs @@ -0,0 +1,11 @@ +fn main() { + let src = "/Users/elie/IdeaProjects/NucleusNativeAccess/examples/rust-tray-icon/build/generated/kne/rustBridges/kne_bridges.rs"; + let out_dir = std::env::var("OUT_DIR").unwrap(); + let dest = format!("{}/kne_bridges.rs", out_dir); + if std::path::Path::new(src).exists() { + std::fs::copy(src, &dest).expect("Failed to copy kne_bridges.rs"); + } else { + std::fs::write(&dest, "// placeholder\n").expect("Failed to write placeholder"); + } + println!("cargo:rerun-if-changed={}", src); +} \ No newline at end of file diff --git a/examples/rust-tray-icon/rust/src/lib.rs b/examples/rust-tray-icon/rust/src/lib.rs new file mode 100644 index 00000000..6aa59306 --- /dev/null +++ b/examples/rust-tray-icon/rust/src/lib.rs @@ -0,0 +1,295 @@ +use tray_icon::menu::{Menu, MenuItem, PredefinedMenuItem, MenuEvent}; +use tray_icon::TrayIconEvent; + +thread_local! { + static TRAY: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; + static MENU: std::cell::RefCell> = const { std::cell::RefCell::new(None) }; +} + +// ── Icon factory ─────────────────────────────────────────────────────────── + +/// Creates a solid-color RGBA icon. Returns an opaque `Icon` handle. +pub fn make_icon(r: i32, g: i32, b: i32, size: i32) -> tray_icon::Icon { + let pixel = [r as u8, g as u8, b as u8, 255u8]; + let rgba: Vec = pixel.iter().copied().cycle().take((size as usize * size as usize * 4) as usize).collect(); + tray_icon::Icon::from_rgba(rgba, size as u32, size as u32).expect("valid icon") +} + +// ── Tray lifecycle (all dispatch_sync — macOS main thread required) ──────── + +/// Creates a tray icon from RGB values with optional tooltip, title, and menu. +/// menu_items: |-separated labels (e.g. "Open|Settings|About"). +pub fn create_tray( + icon_r: i32, icon_g: i32, icon_b: i32, + tooltip: Option<&str>, title: Option<&str>, menu_items: Option<&str>, +) -> Result<(), String> { + let tooltip = tooltip.map(|s| s.to_string()); + let title = title.map(|s| s.to_string()); + let menu_items = menu_items.map(|s| s.to_string()); + + on_main_sync(move || { + let icon = make_icon(icon_r, icon_g, icon_b, 32); + let mut builder = tray_icon::TrayIconBuilder::new().with_icon(icon); + if let Some(ref t) = tooltip { builder = builder.with_tooltip(t); } + if let Some(ref t) = title { builder = builder.with_title(t); } + + let menu = Menu::new(); + if let Some(ref items_str) = menu_items { + for label in items_str.split('|') { + let label = label.trim(); + if !label.is_empty() { + let _ = menu.append(&MenuItem::new(label, true, None)); + } + } + let _ = menu.append(&PredefinedMenuItem::separator()); + } + let _ = menu.append(&MenuItem::new("Quit", true, None)); + builder = builder.with_menu(Box::new(menu.clone())); + MENU.with(|cell| *cell.borrow_mut() = Some(menu)); + + let tray = builder.build().map_err(|e| e.to_string())?; + TRAY.with(|cell| *cell.borrow_mut() = Some(tray)); + Ok(()) + }) +} + +/// Creates a tray icon using an opaque `Icon` handle. +pub fn create_tray_with_icon( + icon: &tray_icon::Icon, + tooltip: Option<&str>, title: Option<&str>, menu_items: Option<&str>, +) -> Result<(), String> { + let icon = icon.clone(); + let tooltip = tooltip.map(|s| s.to_string()); + let title = title.map(|s| s.to_string()); + let menu_items = menu_items.map(|s| s.to_string()); + + on_main_sync(move || { + let mut builder = tray_icon::TrayIconBuilder::new().with_icon(icon); + if let Some(ref t) = tooltip { builder = builder.with_tooltip(t); } + if let Some(ref t) = title { builder = builder.with_title(t); } + + let menu = Menu::new(); + if let Some(ref items_str) = menu_items { + for label in items_str.split('|') { + let label = label.trim(); + if !label.is_empty() { + let _ = menu.append(&MenuItem::new(label, true, None)); + } + } + let _ = menu.append(&PredefinedMenuItem::separator()); + } + let _ = menu.append(&MenuItem::new("Quit", true, None)); + builder = builder.with_menu(Box::new(menu.clone())); + MENU.with(|cell| *cell.borrow_mut() = Some(menu)); + + let tray = builder.build().map_err(|e| e.to_string())?; + TRAY.with(|cell| *cell.borrow_mut() = Some(tray)); + Ok(()) + }) +} + +pub fn destroy_tray() { + on_main_sync(|| { + TRAY.with(|cell| *cell.borrow_mut() = None); + MENU.with(|cell| *cell.borrow_mut() = None); + }); +} + +pub fn is_active() -> bool { + on_main_sync(|| TRAY.with(|cell| cell.borrow().is_some())) +} + +pub fn update_icon(icon: &tray_icon::Icon) { + let icon = icon.clone(); + on_main_sync(move || { + TRAY.with(|cell| { + if let Some(ref tray) = *cell.borrow() { let _ = tray.set_icon(Some(icon)); } + }); + }); +} + +pub fn set_icon_color(r: i32, g: i32, b: i32) { + on_main_sync(move || { + TRAY.with(|cell| { + if let Some(ref tray) = *cell.borrow() { + let icon = make_icon(r, g, b, 32); + let _ = tray.set_icon(Some(icon)); + } + }); + }); +} + +pub fn set_tooltip(tooltip: &str) -> Result<(), String> { + let s = tooltip.to_string(); + on_main_sync(move || { + TRAY.with(|cell| { + if let Some(ref tray) = *cell.borrow() { + tray.set_tooltip(Some(s)).map_err(|e| e.to_string()) + } else { Ok(()) } + }) + }) +} + +pub fn set_title(title: &str) { + let s = if title.is_empty() { None } else { Some(title.to_string()) }; + on_main_sync(move || { + TRAY.with(|cell| { + if let Some(ref tray) = *cell.borrow() { tray.set_title(s.as_deref()); } + }); + }); +} + +pub fn set_visible(visible: bool) { + on_main_sync(move || { + TRAY.with(|cell| { + if let Some(ref tray) = *cell.borrow() { tray.set_visible(visible); } + }); + }); +} + +pub fn set_icon_as_template(is_template: bool) { + on_main_sync(move || { + TRAY.with(|cell| { + if let Some(ref tray) = *cell.borrow() { tray.set_icon_as_template(is_template); } + }); + }); +} + +// ── Event callbacks ──────────────────────────────────────────────────────── + +/// Registers a callback for tray icon events. Receives a description string. +pub fn on_tray_event(handler: Option) { + match handler { + Some(h) => { + TrayIconEvent::set_event_handler(Some(move |event: TrayIconEvent| { + let desc = match event { + TrayIconEvent::Click { button, button_state, .. } => + format!("click|{:?}|{:?}", button, button_state), + TrayIconEvent::DoubleClick { button, .. } => + format!("double_click|{:?}", button), + TrayIconEvent::Enter { .. } => "enter".to_string(), + TrayIconEvent::Leave { .. } => "leave".to_string(), + TrayIconEvent::Move { .. } => "move".to_string(), + _ => format!("{:?}", event), + }; + h(desc); + })); + } + None => { TrayIconEvent::set_event_handler(None::); } + } +} + +/// Registers a callback for menu item clicks. Receives the menu item label. +pub fn on_menu_event(handler: Option) { + match handler { + Some(h) => { + MenuEvent::set_event_handler(Some(move |event: MenuEvent| { + let id = event.id; + let label = MENU.with(|cell| { + if let Some(ref menu) = *cell.borrow() { + for item in menu.items() { + if item.id() == &id { + return match &item { + tray_icon::menu::MenuItemKind::MenuItem(m) => m.text(), + tray_icon::menu::MenuItemKind::Check(m) => m.text(), + tray_icon::menu::MenuItemKind::Submenu(m) => m.text(), + _ => id.0.clone(), + }; + } + } + } + id.0.clone() + }); + h(label); + })); + } + None => { MenuEvent::set_event_handler(None::); } + } +} + +// ── MenuItem management (dispatch_sync for menu mutations) ───────────────── + +/// Creates a menu item. Wrapper provides None for the Accelerator param. +pub fn create_menu_item(label: &str, enabled: bool) -> tray_icon::menu::MenuItem { + MenuItem::new(label, enabled, None) +} + +/// Adds a menu item to the live context menu (dispatch_sync). +pub fn add_menu_item(item: &tray_icon::menu::MenuItem) { + // SAFETY: on_main_sync is synchronous — the reference stays valid. + let ptr = item as *const tray_icon::menu::MenuItem as usize; + on_main_sync(move || { + let item = unsafe { &*(ptr as *const tray_icon::menu::MenuItem) }; + MENU.with(|cell| { + if let Some(ref menu) = *cell.borrow() { let _ = menu.append(item); } + }); + }); +} + +/// Removes a menu item from the live context menu (dispatch_sync). +pub fn remove_menu_item(item: &tray_icon::menu::MenuItem) { + let ptr = item as *const tray_icon::menu::MenuItem as usize; + on_main_sync(move || { + let item = unsafe { &*(ptr as *const tray_icon::menu::MenuItem) }; + MENU.with(|cell| { + if let Some(ref menu) = *cell.borrow() { let _ = menu.remove(item); } + }); + }); +} + +/// Adds a separator to the live context menu (dispatch_sync). +pub fn add_separator() { + on_main_sync(|| { + MENU.with(|cell| { + if let Some(ref menu) = *cell.borrow() { + let _ = menu.append(&PredefinedMenuItem::separator()); + } + }); + }); +} + +/// MenuItem accessors — thin wrappers so the bridge can call them on the opaque handle. +pub fn get_menu_item_text(item: &tray_icon::menu::MenuItem) -> String { item.text() } +pub fn set_menu_item_text(item: &tray_icon::menu::MenuItem, text: &str) { item.set_text(text); } +pub fn is_menu_item_enabled(item: &tray_icon::menu::MenuItem) -> bool { item.is_enabled() } +pub fn set_menu_item_enabled(item: &tray_icon::menu::MenuItem, enabled: bool) { item.set_enabled(enabled); } + +// ── macOS: dispatch_sync on the main queue ───────────────────────────────── + +#[cfg(target_os = "macos")] +fn on_main_sync(f: F) -> R +where + F: FnOnce() -> R + Send, + R: Send, +{ + use std::ffi::c_void; + extern "C" { + #[link_name = "_dispatch_main_q"] + static _dispatch_main_q: c_void; + fn dispatch_sync_f(queue: *const c_void, context: *mut c_void, work: extern "C" fn(*mut c_void)); + } + #[allow(unused_unsafe)] + let main_queue = unsafe { &raw const _dispatch_main_q }; + struct Payload { func: Option, result: Option } + extern "C" fn trampoline(ctx: *mut c_void) where F: FnOnce() -> R { + let payload = unsafe { &mut *(ctx as *mut Payload) }; + let func = payload.func.take().unwrap(); + payload.result = Some(func()); + } + let mut payload = Payload:: { func: Some(f), result: None }; + unsafe { + dispatch_sync_f(main_queue, &mut payload as *mut _ as *mut c_void, trampoline::); + } + payload.result.unwrap() +} + +#[cfg(not(target_os = "macos"))] +fn on_main_sync(f: F) -> R +where + F: FnOnce() -> R + Send, + R: Send, +{ + f() +} + +include!(concat!(env!("OUT_DIR"), "/kne_bridges.rs")); diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/App.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/App.kt new file mode 100644 index 00000000..2172d7ab --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/App.kt @@ -0,0 +1,176 @@ +package com.example.rusttrayicon + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rusttrayicon.tabs.* + +enum class NavItem(val label: String, val icon: ImageVector) { + TrayControl("Tray Control", Icons.Default.DesktopWindows), + TrayInfo("Info", Icons.Default.Info), + EventLog("Event Log", Icons.Default.History), +} + +@Composable +fun App() { + var selected by remember { mutableStateOf(NavItem.TrayControl) } + val manager = remember { TrayIconManager() } + val events = remember { mutableStateListOf() } + + val onEvent: (TrayEvent) -> Unit = { events.add(0, it) } + + DisposableEffect(Unit) { + onDispose { manager.destroy() } + } + + Row(Modifier.fillMaxSize().background(AppColors.bg)) { + Sidebar(selected, events.size, manager.isActive) { selected = it } + + Box( + Modifier + .fillMaxSize() + .padding(start = 0.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) + ) { + when (selected) { + NavItem.TrayControl -> TrayControlTab(manager, onEvent) + NavItem.TrayInfo -> TrayInfoTab(manager, onEvent) + NavItem.EventLog -> EventLogTab(events) + } + } + } +} + +@Composable +private fun Sidebar(selected: NavItem, eventCount: Int, trayActive: Boolean, onSelect: (NavItem) -> Unit) { + Column( + modifier = Modifier + .fillMaxHeight() + .width(200.dp) + .background(AppColors.sidebarBg) + .padding(vertical = 16.dp, horizontal = 8.dp), + ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .size(8.dp) + .clip(RoundedCornerShape(4.dp)) + .background(if (trayActive) AppColors.green else AppColors.red) + ) + Spacer(Modifier.width(10.dp)) + Text( + "Tray Icon", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = AppColors.textPrimary, + ) + } + Text( + " tray-icon FFM Bridge", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + + Spacer(Modifier.height(20.dp)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(12.dp)) + + NavItem.entries.forEach { item -> + SidebarItem( + item = item, + isSelected = item == selected, + badge = if (item == NavItem.EventLog && eventCount > 0) "$eventCount" else null, + onClick = { onSelect(item) }, + ) + } + + Spacer(Modifier.weight(1f)) + Divider(color = AppColors.divider, thickness = 1.dp) + Spacer(Modifier.height(8.dp)) + Text( + " Powered by tray-icon 0.19", + fontSize = 10.sp, + color = AppColors.textMuted, + modifier = Modifier.padding(horizontal = 10.dp), + ) + } +} + +@Composable +private fun SidebarItem(item: NavItem, isSelected: Boolean, badge: String? = null, onClick: () -> Unit) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + + val bgColor = when { + isSelected -> AppColors.sidebarSelected + isHovered -> AppColors.sidebarHover + else -> Color.Transparent + } + val contentColor = when { + isSelected -> AppColors.accent + isHovered -> AppColors.textPrimary + else -> AppColors.textSecondary + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .hoverable(interactionSource) + .clickable(interactionSource = interactionSource, indication = null, onClick = onClick) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = item.icon, + contentDescription = item.label, + tint = contentColor, + modifier = Modifier.size(18.dp), + ) + Spacer(Modifier.width(10.dp)) + Text( + item.label, + fontSize = 13.sp, + fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal, + color = contentColor, + ) + + if (badge != null) { + Spacer(Modifier.width(6.dp)) + Badge(badge, AppColors.cyan) + } + + if (isSelected) { + Spacer(Modifier.weight(1f)) + Box( + Modifier + .width(3.dp) + .height(16.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + } + } +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Components.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Components.kt new file mode 100644 index 00000000..398263fa --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Components.kt @@ -0,0 +1,150 @@ +package com.example.rusttrayicon + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun SectionHeader(title: String, count: Int? = null) { + Row( + modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + Modifier + .width(3.dp) + .height(18.dp) + .clip(RoundedCornerShape(2.dp)) + .background(AppColors.accent) + ) + Spacer(Modifier.width(10.dp)) + Text(title, style = MaterialTheme.typography.h6) + if (count != null) { + Spacer(Modifier.width(8.dp)) + Badge("$count") + } + } +} + +@Composable +fun Badge(text: String, color: Color = AppColors.accent) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(6.dp)) + .background(color.copy(alpha = 0.15f)) + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, fontSize = 11.sp, color = color, fontWeight = FontWeight.SemiBold) + } +} + +@Composable +fun InfoCard( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Column( + modifier = modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(AppColors.card) + .padding(14.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + content = content, + ) +} + +@Composable +fun MetricRow(label: String, value: String) { + Row( + Modifier.fillMaxWidth().padding(vertical = 1.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text(label, style = MaterialTheme.typography.body2) + Text(value, style = MaterialTheme.typography.body1, fontWeight = FontWeight.Medium) + } +} + +@Composable +fun StatBox( + label: String, + value: String, + modifier: Modifier = Modifier, + accentColor: Color = AppColors.accent, +) { + Column( + modifier = modifier + .clip(RoundedCornerShape(10.dp)) + .background(AppColors.card) + .padding(12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(value, fontSize = 18.sp, fontWeight = FontWeight.Bold, color = accentColor) + Spacer(Modifier.height(2.dp)) + Text(label, style = MaterialTheme.typography.caption) + } +} + +@Composable +fun MiniLabel(text: String, color: Color = AppColors.textMuted) { + Text( + text, + style = MaterialTheme.typography.caption, + color = color, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) +} + +@Composable +fun ActionButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + color: Color = AppColors.accent, + enabled: Boolean = true, +) { + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val bgColor = if (enabled) { + if (isHovered) color.copy(alpha = 0.25f) else color.copy(alpha = 0.15f) + } else { + AppColors.border.copy(alpha = 0.3f) + } + val textColor = if (enabled) color else AppColors.textMuted + + Box( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .background(bgColor) + .hoverable(interactionSource) + .clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + onClick = onClick, + ) + .padding(horizontal = 16.dp, vertical = 10.dp), + contentAlignment = Alignment.Center, + ) { + Text(text, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = textColor) + } +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Main.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Main.kt new file mode 100644 index 00000000..dca949b2 --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Main.kt @@ -0,0 +1,18 @@ +package com.example.rusttrayicon + +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "Rusty Tray Icon — tray-icon FFM Bridge", + state = rememberWindowState(width = 1100.dp, height = 750.dp), + ) { + TrayIconTheme { + App() + } + } +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Theme.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Theme.kt new file mode 100644 index 00000000..8535dbd9 --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/Theme.kt @@ -0,0 +1,71 @@ +package com.example.rusttrayicon + +import androidx.compose.material.Colors +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +object AppColors { + val bg = Color(0xFF0F1117) + val surface = Color(0xFF181A20) + val surfaceLight = Color(0xFF1E2028) + val card = Color(0xFF1C1E26) + val cardHover = Color(0xFF22242E) + val border = Color(0xFF2A2D38) + val accent = Color(0xFF6C8EFF) + val accentDim = Color(0xFF3D5299) + val green = Color(0xFF4ADE80) + val greenDim = Color(0xFF166534) + val orange = Color(0xFFFBBF24) + val orangeDim = Color(0xFF92400E) + val red = Color(0xFFEF4444) + val redDim = Color(0xFF991B1B) + val cyan = Color(0xFF22D3EE) + val purple = Color(0xFFA78BFA) + val textPrimary = Color(0xFFE4E4E7) + val textSecondary = Color(0xFF9CA3AF) + val textMuted = Color(0xFF6B7280) + val sidebarBg = Color(0xFF13141A) + val sidebarSelected = Color(0xFF1E2235) + val sidebarHover = Color(0xFF1A1C24) + val divider = Color(0xFF2A2D38) +} + +val DarkColorPalette = Colors( + primary = AppColors.accent, + primaryVariant = AppColors.accentDim, + secondary = AppColors.cyan, + secondaryVariant = AppColors.purple, + background = AppColors.bg, + surface = AppColors.surface, + error = AppColors.red, + onPrimary = Color.White, + onSecondary = Color.White, + onBackground = AppColors.textPrimary, + onSurface = AppColors.textPrimary, + onError = Color.White, + isLight = false, +) + +val AppTypography = Typography( + h5 = TextStyle(fontWeight = FontWeight.Bold, fontSize = 20.sp, color = AppColors.textPrimary), + h6 = TextStyle(fontWeight = FontWeight.SemiBold, fontSize = 16.sp, color = AppColors.textPrimary), + subtitle1 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 14.sp, color = AppColors.textPrimary), + subtitle2 = TextStyle(fontWeight = FontWeight.Medium, fontSize = 12.sp, color = AppColors.textSecondary), + body1 = TextStyle(fontSize = 13.sp, color = AppColors.textPrimary), + body2 = TextStyle(fontSize = 12.sp, color = AppColors.textSecondary), + caption = TextStyle(fontSize = 11.sp, color = AppColors.textMuted), +) + +@Composable +fun TrayIconTheme(content: @Composable () -> Unit) { + MaterialTheme( + colors = DarkColorPalette, + typography = AppTypography, + content = content, + ) +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/TrayIconData.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/TrayIconData.kt new file mode 100644 index 00000000..5ed7916e --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/TrayIconData.kt @@ -0,0 +1,23 @@ +package com.example.rusttrayicon + +data class TrayIconState( + val isActive: Boolean = false, + val tooltip: String = "Rust Tray Icon Demo", + val title: String = "", + val menuItems: List = emptyList(), + val eventLog: List = emptyList(), +) + +data class MenuItemState( + val id: String, + val label: String, + val enabled: Boolean = true, + val isCheckable: Boolean = false, + val isChecked: Boolean = false, +) + +data class TrayEvent( + val timestamp: Long = System.currentTimeMillis(), + val type: String, + val details: String, +) diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/TrayIconManager.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/TrayIconManager.kt new file mode 100644 index 00000000..91169298 --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/TrayIconManager.kt @@ -0,0 +1,127 @@ +package com.example.rusttrayicon + +/** + * Manages the lifecycle of a Rust tray icon instance. + * The Rust wrapper dispatches all calls to the macOS main thread internally via dispatch_sync. + * + * Demonstrates two approaches: + * - RGB-based: `create()` takes color values directly (simple flat API) + * - Icon-based: `createWithIcon()` takes an opaque [Icon] handle (opaque type bridging) + */ +class TrayIconManager { + + val isActive: Boolean get() = Tray_icon_wrapper.is_active() + + /** Creates a tray icon from RGB color values (flat API). */ + fun create(tooltip: String, title: String, menuItems: String, onEvent: (TrayEvent) -> Unit) { + try { + Tray_icon_wrapper.create_tray( + icon_r = 108, icon_g = 142, icon_b = 255, + tooltip = tooltip.ifEmpty { null }, + title = title.ifEmpty { null }, + menu_items = menuItems.ifEmpty { null }, + ) + registerEventCallbacks(onEvent) + onEvent(TrayEvent(type = "Created", details = "Tray icon with menu: $menuItems")) + } catch (e: Exception) { + onEvent(TrayEvent(type = "Error", details = e.message ?: e.toString())) + } + } + + /** Creates a tray icon using an opaque [Icon] handle (demonstrates opaque type bridging). */ + fun createWithIcon(icon: Icon, tooltip: String, title: String, menuItems: String, onEvent: (TrayEvent) -> Unit) { + try { + Tray_icon_wrapper.create_tray_with_icon( + icon = icon, + tooltip = tooltip.ifEmpty { null }, + title = title.ifEmpty { null }, + menu_items = menuItems.ifEmpty { null }, + ) + registerEventCallbacks(onEvent) + onEvent(TrayEvent(type = "Created", details = "Tray icon (opaque Icon) with menu: $menuItems")) + } catch (e: Exception) { + onEvent(TrayEvent(type = "Error", details = e.message ?: e.toString())) + } + } + + fun destroy() { + try { + Tray_icon_wrapper.on_tray_event(null) + Tray_icon_wrapper.on_menu_event(null) + Tray_icon_wrapper.destroy_tray() + } catch (_: Exception) {} + } + + fun setTooltip(tooltip: String) { + try { Tray_icon_wrapper.set_tooltip(tooltip) } catch (_: Exception) {} + } + + fun setTitle(title: String) { + try { Tray_icon_wrapper.set_title(title) } catch (_: Exception) {} + } + + fun setVisible(visible: Boolean) { + try { Tray_icon_wrapper.set_visible(visible) } catch (_: Exception) {} + } + + fun setIconAsTemplate(isTemplate: Boolean) { + try { Tray_icon_wrapper.set_icon_as_template(isTemplate) } catch (_: Exception) {} + } + + fun setIconColor(r: Int, g: Int, b: Int) { + try { Tray_icon_wrapper.set_icon_color(r, g, b) } catch (_: Exception) {} + } + + /** Updates the tray icon using an opaque [Icon] handle. */ + fun updateIcon(icon: Icon) { + try { Tray_icon_wrapper.update_icon(icon) } catch (_: Exception) {} + } + + // ── MenuItem management (opaque MenuItem handles) ── + + /** Creates a new menu item with the given label and enabled state. */ + fun createMenuItem(label: String, enabled: Boolean = true): MenuItem = + Tray_icon_wrapper.create_menu_item(label, enabled) + + /** Adds a menu item to the current tray context menu. */ + fun addMenuItem(item: MenuItem) { + try { Tray_icon_wrapper.add_menu_item(item) } catch (_: Exception) {} + } + + /** Removes a menu item from the current tray context menu. */ + fun removeMenuItem(item: MenuItem) { + try { Tray_icon_wrapper.remove_menu_item(item) } catch (_: Exception) {} + } + + /** Adds a separator to the current tray context menu. */ + fun addSeparator() { + try { Tray_icon_wrapper.add_separator() } catch (_: Exception) {} + } + + /** Gets the text of a menu item. */ + fun getMenuItemText(item: MenuItem): String = + Tray_icon_wrapper.get_menu_item_text(item) + + /** Sets the text of a menu item. */ + fun setMenuItemText(item: MenuItem, text: String) { + try { Tray_icon_wrapper.set_menu_item_text(item, text) } catch (_: Exception) {} + } + + /** Returns whether a menu item is enabled. */ + fun isMenuItemEnabled(item: MenuItem): Boolean = + Tray_icon_wrapper.is_menu_item_enabled(item) + + /** Enables or disables a menu item. */ + fun setMenuItemEnabled(item: MenuItem, enabled: Boolean) { + try { Tray_icon_wrapper.set_menu_item_enabled(item, enabled) } catch (_: Exception) {} + } + + private fun registerEventCallbacks(onEvent: (TrayEvent) -> Unit) { + Tray_icon_wrapper.on_tray_event { desc -> + onEvent(TrayEvent(type = "TrayEvent", details = desc)) + } + Tray_icon_wrapper.on_menu_event { label -> + onEvent(TrayEvent(type = "MenuEvent", details = label)) + } + } +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/EventLogTab.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/EventLogTab.kt new file mode 100644 index 00000000..4305175b --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/EventLogTab.kt @@ -0,0 +1,90 @@ +package com.example.rusttrayicon.tabs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rusttrayicon.* +import java.text.SimpleDateFormat +import java.util.* + +private val timeFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()) + +@Composable +fun EventLogTab(events: List) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(start = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + SectionHeader("Event Log", count = events.size) + + if (events.isEmpty()) { + InfoCard { + Text( + "No events yet. Create a tray icon and interact with it.", + fontSize = 13.sp, + color = AppColors.textMuted, + ) + } + } else { + LazyColumn( + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + itemsIndexed(events) { _, event -> + EventRow(event) + } + } + } + } +} + +@Composable +private fun EventRow(event: TrayEvent) { + val typeColor = when (event.type) { + "Created" -> AppColors.green + "Destroyed" -> AppColors.red + "Tooltip", "Title" -> AppColors.accent + "Visibility" -> AppColors.orange + "Icon" -> AppColors.purple + "TrayEvent" -> AppColors.cyan + "MenuEvent" -> AppColors.green + "Error" -> AppColors.red + else -> AppColors.textSecondary + } + + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(AppColors.card) + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + timeFormat.format(Date(event.timestamp)), + fontSize = 11.sp, + color = AppColors.textMuted, + fontWeight = FontWeight.Medium, + ) + Spacer(Modifier.width(10.dp)) + Badge(event.type, typeColor) + Spacer(Modifier.width(10.dp)) + Text( + event.details, + fontSize = 12.sp, + color = AppColors.textSecondary, + modifier = Modifier.weight(1f), + ) + } +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/SimpleTextField.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/SimpleTextField.kt new file mode 100644 index 00000000..2f3d6fe3 --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/SimpleTextField.kt @@ -0,0 +1,47 @@ +package com.example.rusttrayicon.tabs + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rusttrayicon.AppColors + +@Composable +fun SimpleTextField( + value: String, + onValueChange: (String) -> Unit, + placeholder: String = "", + modifier: Modifier = Modifier, +) { + BasicTextField( + value = value, + onValueChange = onValueChange, + singleLine = true, + textStyle = TextStyle(fontSize = 13.sp, color = AppColors.textPrimary), + cursorBrush = SolidColor(AppColors.accent), + decorationBox = { innerTextField -> + Box( + modifier = modifier + .clip(RoundedCornerShape(8.dp)) + .background(AppColors.surface) + .padding(horizontal = 12.dp, vertical = 10.dp), + contentAlignment = Alignment.CenterStart, + ) { + if (value.isEmpty()) { + Text(placeholder, fontSize = 13.sp, color = AppColors.textMuted) + } + innerTextField() + } + }, + ) +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/TrayControlTab.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/TrayControlTab.kt new file mode 100644 index 00000000..3520883a --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/TrayControlTab.kt @@ -0,0 +1,290 @@ +package com.example.rusttrayicon.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rusttrayicon.* + +@Composable +fun TrayControlTab(manager: TrayIconManager, onEvent: (TrayEvent) -> Unit) { + var tooltip by remember { mutableStateOf("Rust Tray Icon Demo") } + var title by remember { mutableStateOf("") } + var menuItems by remember { mutableStateOf("Open|Settings|About") } + var isVisible by remember { mutableStateOf(true) } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(start = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + SectionHeader("Tray Icon Control") + + InfoCard { + Text("Status", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(4.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + StatBox( + label = "State", + value = if (manager.isActive) "Active" else "Inactive", + accentColor = if (manager.isActive) AppColors.green else AppColors.red, + modifier = Modifier.weight(1f), + ) + StatBox( + label = "Visible", + value = if (isVisible) "Yes" else "No", + accentColor = if (isVisible) AppColors.green else AppColors.orange, + modifier = Modifier.weight(1f), + ) + } + } + + InfoCard { + Text("Create / Destroy", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(4.dp)) + MiniLabel("Context menu items (|-separated)") + Spacer(Modifier.height(4.dp)) + SimpleTextField( + value = menuItems, + onValueChange = { menuItems = it }, + placeholder = "Open|Settings|About", + ) + Spacer(Modifier.height(8.dp)) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton( + text = "Create (RGB)", + color = AppColors.green, + enabled = !manager.isActive, + onClick = { + manager.create( + tooltip = tooltip, + title = title, + menuItems = menuItems, + onEvent = onEvent, + ) + }, + ) + ActionButton( + text = "Create (Icon)", + color = AppColors.accent, + enabled = !manager.isActive, + onClick = { + // Demonstrates opaque type bridging: + // make_icon returns an opaque Icon handle, passed to create_tray_with_icon + Tray_icon_wrapper.make_icon(108, 142, 255, 32).use { icon -> + manager.createWithIcon( + icon = icon, + tooltip = tooltip, + title = title, + menuItems = menuItems, + onEvent = onEvent, + ) + } + }, + ) + ActionButton( + text = "Destroy", + color = AppColors.red, + enabled = manager.isActive, + onClick = { + manager.destroy() + onEvent(TrayEvent(type = "Destroyed", details = "Tray icon removed")) + }, + ) + } + } + + InfoCard { + Text("Tooltip", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(4.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + SimpleTextField( + value = tooltip, + onValueChange = { tooltip = it }, + placeholder = "Enter tooltip...", + modifier = Modifier.weight(1f), + ) + ActionButton( + text = "Apply", + enabled = manager.isActive, + onClick = { + manager.setTooltip(tooltip) + onEvent(TrayEvent(type = "Tooltip", details = "Set to '$tooltip'")) + }, + ) + } + } + + InfoCard { + Text("Title", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(4.dp)) + MiniLabel("Text next to the icon (macOS/Linux)") + Spacer(Modifier.height(4.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + SimpleTextField( + value = title, + onValueChange = { title = it }, + placeholder = "Enter title...", + modifier = Modifier.weight(1f), + ) + ActionButton( + text = "Apply", + enabled = manager.isActive, + onClick = { + manager.setTitle(title) + onEvent(TrayEvent(type = "Title", details = "Set to '$title'")) + }, + ) + } + } + + InfoCard { + Text("Menu Items (opaque MenuItem handles)", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(4.dp)) + MiniLabel("Demonstrates opaque MenuItem bridging: create, add, modify, remove") + Spacer(Modifier.height(8.dp)) + + var newItemLabel by remember { mutableStateOf("Custom Item") } + val menuItemHandles = remember { mutableStateListOf() } + var refreshKey by remember { mutableIntStateOf(0) } + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + SimpleTextField( + value = newItemLabel, + onValueChange = { newItemLabel = it }, + placeholder = "Menu item label...", + modifier = Modifier.weight(1f), + ) + ActionButton( + text = "Add", + color = AppColors.green, + enabled = manager.isActive && newItemLabel.isNotBlank(), + onClick = { + val item = manager.createMenuItem(newItemLabel) + manager.addMenuItem(item) + menuItemHandles.add(item) + onEvent(TrayEvent(type = "MenuItem", details = "Added '$newItemLabel'")) + }, + ) + ActionButton( + text = "+ Separator", + color = AppColors.accent, + enabled = manager.isActive, + onClick = { + manager.addSeparator() + onEvent(TrayEvent(type = "MenuItem", details = "Separator added")) + }, + ) + } + Spacer(Modifier.height(4.dp)) + + // Show created menu items with edit/remove controls + @Suppress("UNUSED_EXPRESSION") refreshKey + for ((index, item) in menuItemHandles.withIndex()) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.padding(vertical = 2.dp), + ) { + val text = manager.getMenuItemText(item) + val enabled = manager.isMenuItemEnabled(item) + Text( + "#${index + 1}: \"$text\" ${if (enabled) "(enabled)" else "(disabled)"}", + color = AppColors.textSecondary, + fontSize = 12.sp, + modifier = Modifier.weight(1f), + ) + ActionButton( + text = if (enabled) "Disable" else "Enable", + color = if (enabled) AppColors.orange else AppColors.green, + enabled = manager.isActive, + onClick = { + manager.setMenuItemEnabled(item, !enabled) + refreshKey++ + onEvent(TrayEvent(type = "MenuItem", details = "Toggled '$text' → ${if (!enabled) "enabled" else "disabled"}")) + }, + ) + ActionButton( + text = "Rename", + enabled = manager.isActive, + onClick = { + val newName = "${text}*" + manager.setMenuItemText(item, newName) + refreshKey++ + onEvent(TrayEvent(type = "MenuItem", details = "Renamed '$text' → '$newName'")) + }, + ) + ActionButton( + text = "Remove", + color = AppColors.red, + enabled = manager.isActive, + onClick = { + manager.removeMenuItem(item) + menuItemHandles.removeAt(index) + item.close() + onEvent(TrayEvent(type = "MenuItem", details = "Removed '$text'")) + }, + ) + } + } + } + + InfoCard { + Text("Visibility & Icon", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(4.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton(text = "Show", color = AppColors.green, enabled = manager.isActive && !isVisible, onClick = { + manager.setVisible(true); isVisible = true + onEvent(TrayEvent(type = "Visibility", details = "Shown")) + }) + ActionButton(text = "Hide", color = AppColors.orange, enabled = manager.isActive && isVisible, onClick = { + manager.setVisible(false); isVisible = false + onEvent(TrayEvent(type = "Visibility", details = "Hidden")) + }) + } + Spacer(Modifier.height(4.dp)) + MiniLabel("Via set_icon_color (flat API)") + Spacer(Modifier.height(4.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton(text = "Red", color = AppColors.red, enabled = manager.isActive, onClick = { + manager.setIconColor(239, 68, 68) + onEvent(TrayEvent(type = "Icon", details = "Color changed to Red (flat)")) + }) + ActionButton(text = "Green", color = AppColors.green, enabled = manager.isActive, onClick = { + manager.setIconColor(74, 222, 128) + onEvent(TrayEvent(type = "Icon", details = "Color changed to Green (flat)")) + }) + ActionButton(text = "Blue", color = AppColors.accent, enabled = manager.isActive, onClick = { + manager.setIconColor(108, 142, 255) + onEvent(TrayEvent(type = "Icon", details = "Color changed to Blue (flat)")) + }) + } + Spacer(Modifier.height(4.dp)) + MiniLabel("Via update_icon (opaque Icon handle)") + Spacer(Modifier.height(4.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ActionButton(text = "Red", color = AppColors.red, enabled = manager.isActive, onClick = { + Tray_icon_wrapper.make_icon(239, 68, 68, 32).use { manager.updateIcon(it) } + onEvent(TrayEvent(type = "Icon", details = "Color changed to Red (opaque)")) + }) + ActionButton(text = "Green", color = AppColors.green, enabled = manager.isActive, onClick = { + Tray_icon_wrapper.make_icon(74, 222, 128, 32).use { manager.updateIcon(it) } + onEvent(TrayEvent(type = "Icon", details = "Color changed to Green (opaque)")) + }) + ActionButton(text = "Blue", color = AppColors.accent, enabled = manager.isActive, onClick = { + Tray_icon_wrapper.make_icon(108, 142, 255, 32).use { manager.updateIcon(it) } + onEvent(TrayEvent(type = "Icon", details = "Color changed to Blue (opaque)")) + }) + } + } + + Spacer(Modifier.height(8.dp)) + } +} diff --git a/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/TrayInfoTab.kt b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/TrayInfoTab.kt new file mode 100644 index 00000000..d34fd850 --- /dev/null +++ b/examples/rust-tray-icon/src/jvmMain/kotlin/com/example/rusttrayicon/tabs/TrayInfoTab.kt @@ -0,0 +1,66 @@ +package com.example.rusttrayicon.tabs + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.rusttrayicon.* + +@Composable +fun TrayInfoTab(manager: TrayIconManager, onEvent: (TrayEvent) -> Unit) { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(start = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + SectionHeader("Tray Icon Info") + + InfoCard { + Text("Platform Notes", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(8.dp)) + + MiniLabel("macOS", color = AppColors.accent) + Text( + "Supports tooltip, title, template icons. Title appears next to the icon in the menu bar. " + + "Calls are dispatched to the main thread automatically via dispatch_sync.", + fontSize = 12.sp, + color = AppColors.textSecondary, + ) + + Spacer(Modifier.height(8.dp)) + MiniLabel("Windows", color = AppColors.green) + Text( + "Supports tooltip and icon. Title is not displayed. Tray icons appear in the notification area.", + fontSize = 12.sp, + color = AppColors.textSecondary, + ) + + Spacer(Modifier.height(8.dp)) + MiniLabel("Linux", color = AppColors.orange) + Text( + "Tooltip is not supported. Title is displayed. Requires gtk and libappindicator.", + fontSize = 12.sp, + color = AppColors.textSecondary, + ) + } + + InfoCard { + Text("API Surface", fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = AppColors.textPrimary) + Spacer(Modifier.height(8.dp)) + + MetricRow("Crate", "tray-icon 0.19 (local wrapper)") + MetricRow("Bridge", "FFM (Java Foreign Function & Memory API)") + MetricRow("Thread safety", "dispatch_sync to main queue (macOS)") + MetricRow("Lifecycle", "Managed via Rust thread_local + Kotlin proxy") + } + + Spacer(Modifier.height(8.dp)) + } +} diff --git a/plugin-build/plugin/build.gradle.kts b/plugin-build/plugin/build.gradle.kts index 935ea5b7..f417cfa2 100644 --- a/plugin-build/plugin/build.gradle.kts +++ b/plugin-build/plugin/build.gradle.kts @@ -9,6 +9,7 @@ plugins { dependencies { implementation(kotlin("stdlib")) implementation(gradleApi()) + implementation("com.google.code.gson:gson:2.13.1") // kotlin-compiler-embeddable for PSI parsing (compileOnly — loaded at runtime via isolated Worker classloader) compileOnly(libs.kotlin.compiler.embeddable) diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/CargoResolver.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/CargoResolver.kt new file mode 100644 index 00000000..9782535b --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/CargoResolver.kt @@ -0,0 +1,41 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin + +import org.gradle.api.GradleException +import java.io.File + +/** + * Resolves the path to the `cargo` binary, checking CARGO_HOME and ~/.cargo/bin + * before falling back to PATH. Throws a clear error if cargo is not found. + */ +internal fun findCargo(): String { + val cargoHome = System.getenv("CARGO_HOME") + if (cargoHome != null) { + val cargo = File(cargoHome, "bin/cargo") + if (cargo.exists()) return cargo.absolutePath + } + val homeCargo = File(System.getProperty("user.home"), ".cargo/bin/cargo") + if (homeCargo.exists()) return homeCargo.absolutePath + // Check if cargo is available on PATH + val onPath = try { + val process = ProcessBuilder("cargo", "--version") + .redirectErrorStream(true) + .start() + val exitCode = process.waitFor() + exitCode == 0 + } catch (_: Exception) { + false + } + if (onPath) return "cargo" + throw GradleException( + """ + | + | Rust toolchain not found — 'cargo' is not installed or not on the PATH. + | + | Install Rust with: + | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + | + | Then restart your terminal (or run 'source ~/.cargo/env') and try again. + | If running from an IDE, restart it so the updated PATH is picked up. + """.trimMargin() + ) +} diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt index 4b19199c..46228f49 100644 --- a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/KotlinNativeExportPlugin.kt @@ -1,6 +1,8 @@ package io.github.kdroidfilter.nucleusnativeaccess.plugin +import io.github.kdroidfilter.nucleusnativeaccess.plugin.tasks.CargoBuildTask import io.github.kdroidfilter.nucleusnativeaccess.plugin.tasks.GenerateNativeBridgesTask +import io.github.kdroidfilter.nucleusnativeaccess.plugin.tasks.GenerateRustBindingsTask import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.tasks.testing.Test @@ -36,8 +38,31 @@ class KotlinNativeExportPlugin : Plugin { extension.nativePackage.convention("") extension.buildType.convention("release") + // Rust import extension (opt-in) + val rustExtension = project.extensions.create( + "rustImport", + RustImportExtension::class.java, + ) + rustExtension.libraryName.convention("") + rustExtension.jvmPackage.convention("") + rustExtension.buildType.convention("release") + project.plugins.withId("org.jetbrains.kotlin.multiplatform") { - project.afterEvaluate { configureKmp(project, extension) } + project.afterEvaluate { + configureKmp(project, extension) + if (rustExtension.crates.isPresent && rustExtension.crates.get().isNotEmpty()) { + configureRust(project, rustExtension) + } + } + } + + // Also support pure JVM projects with Rust + project.plugins.withId("org.jetbrains.kotlin.jvm") { + project.afterEvaluate { + if (rustExtension.crates.isPresent && rustExtension.crates.get().isNotEmpty()) { + configureRustJvm(project, rustExtension) + } + } } } @@ -250,4 +275,128 @@ class KotlinNativeExportPlugin : Plugin { ) } } + + // ── Rust import support (KMP projects) ────────────────────────────── + + private fun configureRust(project: Project, rustExt: RustImportExtension) { + val kotlin = project.extensions.getByType(KotlinMultiplatformExtension::class.java) + val libName = rustExt.libraryName.get().ifEmpty { + rustExt.crates.get().firstOrNull()?.name?.replace('-', '_') ?: "rustlib" + } + configureRustCommon(project, rustExt, libName) { jvmProxiesDir, jvmResourcesDir, nativeLibDir -> + kotlin.sourceSets.findByName("jvmMain")?.kotlin?.srcDir(jvmProxiesDir) + kotlin.sourceSets.findByName("jvmMain")?.resources?.srcDir(jvmResourcesDir) + kotlin.sourceSets.findByName("jvmMain")?.resources?.srcDir(nativeLibDir) + } + } + + // ── Rust import support (pure JVM projects) ───────────────────────── + + private fun configureRustJvm(project: Project, rustExt: RustImportExtension) { + val libName = rustExt.libraryName.get().ifEmpty { + rustExt.crates.get().firstOrNull()?.name?.replace('-', '_') ?: "rustlib" + } + configureRustCommon(project, rustExt, libName) { jvmProxiesDir, jvmResourcesDir, nativeLibDir -> + val mainSourceSet = project.extensions.findByType( + org.gradle.api.plugins.JavaPluginExtension::class.java + )?.sourceSets?.findByName("main") + mainSourceSet?.java?.srcDir(jvmProxiesDir) + mainSourceSet?.resources?.srcDir(jvmResourcesDir) + mainSourceSet?.resources?.srcDir(nativeLibDir) + } + } + + private fun configureRustCommon( + project: Project, + rustExt: RustImportExtension, + libName: String, + wireSourceSets: (jvmProxiesDir: Any, jvmResourcesDir: Any, nativeLibDir: Any) -> Unit, + ) { + val jvmPackage = rustExt.jvmPackage.get() + val buildType = rustExt.buildType.get() + + val rustProjectDir = project.layout.buildDirectory.dir("generated/kne/rust") + val rustBridgesDir = project.layout.buildDirectory.dir("generated/kne/rustBridges") + val jvmProxiesDir = project.layout.buildDirectory.dir("generated/kne/jvmProxies") + val jvmResourcesDir = project.layout.buildDirectory.dir("generated/kne/jvmResources") + val nativeLibDir = project.layout.buildDirectory.dir("generated/kne/nativeLib") + + // Task 1: Generate Rust bridges + JVM proxies + val generateBindings = project.tasks.register( + "generateKneRustBindings", + GenerateRustBindingsTask::class.java, + ) { task -> + task.group = "kne" + task.description = "Generate Rust FFI bridges and JVM FFM proxies" + task.libName.set(libName) + task.jvmPackage.set(jvmPackage) + task.crates.set(rustExt.crates) + task.rustProjectDir.set(rustProjectDir) + task.rustBridgesDir.set(rustBridgesDir) + task.jvmProxiesDir.set(jvmProxiesDir) + task.jvmResourcesDir.set(jvmResourcesDir) + } + + // Resolve the actual Cargo project directory + val localCrate = rustExt.crates.get().singleOrNull { it.path != null } + val resolvedCargoDir = if (localCrate != null) { + val dir = File(localCrate.path!!) + val resolved = if (dir.isAbsolute) dir else project.projectDir.resolve(dir.path) + project.layout.projectDirectory.dir(resolved.absolutePath) + } else { + rustProjectDir.get() + } + + // Task 2: Cargo build + val cargoBuild = project.tasks.register( + "cargoKneBuild", + CargoBuildTask::class.java, + ) { task -> + task.group = "kne" + task.description = "Build Rust shared library with cargo" + task.buildType.set(buildType) + task.cargoProjectDir.set(resolvedCargoDir) + task.nativeLibOutputDir.set(nativeLibDir) + task.dependsOn(generateBindings) + } + + // Wire source sets + wireSourceSets(jvmProxiesDir, jvmResourcesDir, nativeLibDir) + + // Ensure compilation waits for generation + project.tasks.configureEach { task -> + val name = task.name + if (name == "compileKotlinJvm" || name == "compileKotlinJvmMain" || + name == "compileKotlin" || name == "compileJava" + ) { + task.dependsOn(generateBindings) + } + if (name == "jvmProcessResources" || name == "processJvmMainResources" || name == "processResources") { + task.dependsOn(cargoBuild) + task.dependsOn(generateBindings) + // Also depend on KMP native bridges task if it exists (shared jvmResourcesDir) + project.tasks.findByName("generateKneNativeBridges")?.let { task.dependsOn(it) } + } + } + + // Configure JVM test tasks + val cargoTargetDir = if (localCrate != null) { + val dir = File(localCrate.path!!) + val resolved = if (dir.isAbsolute) dir else project.projectDir.resolve(dir.path) + resolved.resolve("target/${buildType.lowercase()}") + } else { + rustProjectDir.get().asFile.resolve("target/${buildType.lowercase()}") + } + project.tasks.withType(Test::class.java).configureEach { testTask -> + testTask.dependsOn(cargoBuild) + testTask.jvmArgs( + "--enable-native-access=ALL-UNNAMED", + ) + testTask.doFirst { + val libPath = cargoTargetDir.absolutePath + val nativeLibPath = nativeLibDir.get().asFile.absolutePath + testTask.jvmArgs("-Djava.library.path=$libPath${File.pathSeparator}$nativeLibPath") + } + } + } } diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/RustImportExtension.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/RustImportExtension.kt new file mode 100644 index 00000000..2c7d1962 --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/RustImportExtension.kt @@ -0,0 +1,56 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin + +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import java.io.Serializable + +/** + * DSL extension for importing Rust crates as Kotlin libraries. + * + * Usage: + * ```kotlin + * rustImport { + * libraryName = "calculator" + * jvmPackage = "com.example.calculator" + * crate("calculator", path = "../rust") + * } + * ``` + */ +abstract class RustImportExtension { + + /** Name of the native shared library (e.g. "calculator" → libcalculator.so). */ + abstract val libraryName: Property + + /** Package for generated JVM proxy classes. */ + abstract val jvmPackage: Property + + /** Build type: "debug" or "release". */ + abstract val buildType: Property + + /** Registered crate dependencies. */ + abstract val crates: ListProperty + + /** Add a crate from crates.io. */ + fun crate(name: String, version: String, features: List = emptyList()) { + crates.add(CrateDependency(name = name, version = version, features = features)) + } + + /** Add a crate from a local path. */ + fun cratePath(name: String, path: String, features: List = emptyList()) { + crates.add(CrateDependency(name = name, path = path, features = features)) + } + + /** Add a crate from a git repository. */ + fun crateGit(name: String, repository: String, branch: String = "main", features: List = emptyList()) { + crates.add(CrateDependency(name = name, gitUrl = repository, gitBranch = branch, features = features)) + } +} + +data class CrateDependency( + val name: String, + val version: String? = null, + val path: String? = null, + val gitUrl: String? = null, + val gitBranch: String? = null, + val features: List = emptyList(), +) : Serializable diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustWorkAction.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustWorkAction.kt new file mode 100644 index 00000000..d001231e --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustWorkAction.kt @@ -0,0 +1,704 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.analysis + +import com.google.gson.JsonParser +import io.github.kdroidfilter.nucleusnativeaccess.plugin.CrateDependency +import io.github.kdroidfilter.nucleusnativeaccess.plugin.findCargo +import io.github.kdroidfilter.nucleusnativeaccess.plugin.codegen.FfmProxyGenerator +import io.github.kdroidfilter.nucleusnativeaccess.plugin.codegen.RustBridgeGenerator +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneConstructorKind +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneModule +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneType +import java.io.File + +/** + * Orchestrates the Rust import pipeline: + * 1. Generate wrapper Cargo.toml + lib.rs for the target crate(s) + * 2. Run `cargo rustdoc --output-format json` to extract the public API + * 3. Parse the JSON with [RustdocJsonParser] → [KneModule] + * 4. Generate Rust bridge code with [RustBridgeGenerator] → kne_bridges.rs + * 5. Generate JVM FFM proxies with [FfmProxyGenerator] (reused as-is) + * 6. Generate GraalVM metadata + */ +object RustWorkAction { + + fun execute( + crates: List, + libName: String, + jvmPackage: String, + rustProjectDir: File, + rustBridgesDir: File, + jvmProxiesDir: File, + jvmResourcesDir: File, + logger: org.gradle.api.logging.Logger, + ) { + // Step 1: Resolve Cargo project directory + val cargoProjectDir = resolveCargoProject(crates, rustProjectDir, libName, logger) + val rustdocProjectDir = resolveRustdocProject(crates, cargoProjectDir, logger) + val crateSrcDir = cargoProjectDir.resolve("src") + + // Step 2: Write an empty bridge file so rustdoc doesn't fail + // The bridge is included via build.rs which points to the Gradle build dir + rustBridgesDir.mkdirs() + val bridgeFile = rustBridgesDir.resolve("kne_bridges.rs") + bridgeFile.writeText("// placeholder\n") + + // Generate build.rs that tells Cargo where to find the bridges + ensureBuildRs(cargoProjectDir, rustBridgesDir, logger) + + // Ensure lib.rs includes the bridge via OUT_DIR + ensureLibRsInclude(crateSrcDir, logger) + + // Step 3: Run cargo rustdoc to produce JSON + val rustdocJson = runCargoRustdoc(rustdocProjectDir, libName, crates, logger) + ?: throw org.gradle.api.GradleException("Failed to generate rustdoc JSON for '$libName'") + + // Step 4: Parse JSON → KneModule + val unsupported = mutableListOf() + val isWrapper = crates.isNotEmpty() && crates.none { it.path != null } + + val module = if (isWrapper) { + // For wrapper crates, parse the main crate JSON + only sub-crates that are + // publicly re-exported. Parsing internal sub-crates (like symphonia_codec_pcm) + // would pull in types that are not accessible from the wrapper scope. + val docDir = rustdocProjectDir.resolve("target/doc") + val primaryCrateName = crates.first().name.replace('-', '_') + val mainJsonFile = docDir.resolve("$primaryCrateName.json") + + // Detect which sub-crates are re-exported by the main crate via `pub use` + val reExportedSubCrates = mutableSetOf() + if (mainJsonFile.exists()) { + val mainDoc = com.google.gson.JsonParser.parseString(mainJsonFile.readText()).asJsonObject + val mainIndex = mainDoc.getAsJsonObject("index") + val rootId = mainDoc.get("root")?.asInt?.toString() + val rootItems = rootId?.let { mainIndex?.get(it)?.asJsonObject } + ?.getAsJsonObject("inner")?.getAsJsonObject("module")?.getAsJsonArray("items") + if (rootItems != null) { + for (itemId in rootItems) { + val item = mainIndex.get(itemId.asInt.toString())?.asJsonObject ?: continue + val inner = item.getAsJsonObject("inner") ?: continue + if (inner.has("use")) { + val sourceElem = inner.getAsJsonObject("use").get("source") + if (sourceElem != null && !sourceElem.isJsonNull) { + // Extract crate name from source path (e.g. "nokhwa_core::pixel_format::FormatDecoder" → "nokhwa_core") + val source = sourceElem.asString.replace('-', '_') + val crateName = source.substringBefore("::") + reExportedSubCrates.add(crateName) + } + } + } + } + } + + // Collect JSONs to parse: main crate + referenced sub-crates + additional crates. + // Only sub-crates that are referenced by the main crate (via pub use) are included. + val depJsons = mutableListOf() + if (mainJsonFile.exists()) depJsons.add(mainJsonFile) + for (subCrate in reExportedSubCrates) { + val subJson = docDir.resolve("$subCrate.json") + if (subJson.exists() && subJson !in depJsons) depJsons.add(subJson) + } + for (crate in crates.drop(1)) { + val additionalJson = docDir.resolve("${crate.name.replace('-', '_')}.json") + if (additionalJson.exists() && additionalJson !in depJsons) depJsons.add(additionalJson) + } + + val mainJson = depJsons.find { it.nameWithoutExtension == primaryCrateName } + val subJsons = depJsons.filter { it != mainJson } + logger.lifecycle("kne-rust: Parsing main: ${mainJson?.name}, sub: ${subJsons.map { it.name }}") + + val parser = RustdocJsonParser() + val mainModule = if (mainJson != null) { + parser.parse(mainJson.readText(), libName) { unsupported.add(it) } + } else null + val subModules = subJsons.map { jsonFile -> + parser.parse(jsonFile.readText(), libName) { unsupported.add(it) } + } + mergeModules(listOfNotNull(mainModule) + subModules, libName) + } else { + RustdocJsonParser().parse(rustdocJson.readText(), libName) { unsupported.add(it) } + } + + logger.lifecycle("kne-rust: Parsed ${module.classes.size} classes, ${module.dataClasses.size} data classes, ${module.enums.size} enums, ${module.functions.size} functions") + if (unsupported.isNotEmpty()) { + logger.warn("kne-rust: Skipped unsupported API elements:") + unsupported.take(20).forEach { logger.warn("kne-rust: $it") } + if (unsupported.size > 20) logger.warn("kne-rust: ... and ${unsupported.size - 20} more") + } + + // Step 4b: For wrapper crates, rewrite lib.rs with proper imports based on rustdoc analysis + if (isWrapper) { + val docDir = rustdocProjectDir.resolve("target/doc") + rewriteWrapperLibRs(crateSrcDir, crates, docDir, logger) + } + + // Step 5: Generate Rust bridges (into Gradle build dir, NOT into crate src) + if (module.classes.isNotEmpty() || module.enums.isNotEmpty() || module.functions.isNotEmpty() || module.dataClasses.isNotEmpty()) { + val bridgeCode = RustBridgeGenerator().generate(module) + bridgeFile.writeText(bridgeCode) + logger.lifecycle("kne-rust: Generated Rust bridges → ${bridgeFile.absolutePath}") + } else { + logger.lifecycle("kne-rust: No supported public API found; keeping placeholder bridges") + } + + // Step 5: Generate JVM proxies + val resolvedPackage = jvmPackage.ifEmpty { module.packages.firstOrNull() ?: "" } + if (resolvedPackage.isNotEmpty() && (module.classes.isNotEmpty() || module.enums.isNotEmpty() || module.functions.isNotEmpty())) { + val pkgDir = jvmProxiesDir.resolve(resolvedPackage.replace('.', '/')) + pkgDir.mkdirs() + val generator = FfmProxyGenerator() + generator.generate(module, resolvedPackage).forEach { (filename, content) -> + pkgDir.resolve(filename).writeText(content) + } + logger.lifecycle("kne-rust: Generated JVM proxies → $resolvedPackage") + + // Step 6: GraalVM metadata + generateGraalVmMetadata(jvmResourcesDir, module, resolvedPackage, generator) + } + } + + private fun resolveCargoProject( + crates: List, + rustProjectDir: File, + libName: String, + logger: org.gradle.api.logging.Logger, + ): File { + // If there's a single local path crate, use it directly + val localCrate = crates.singleOrNull { it.path != null } + if (localCrate != null) { + val dir = File(localCrate.path!!) + val resolved = if (dir.isAbsolute) dir else rustProjectDir.resolve(dir.path) + if (resolved.resolve("Cargo.toml").exists()) { + logger.lifecycle("kne-rust: Using local crate at ${resolved.absolutePath}") + return resolved + } + } + + // Otherwise, generate a wrapper Cargo project + rustProjectDir.mkdirs() + val cargoToml = buildString { + appendLine("[package]") + appendLine("name = \"kne-$libName-wrapper\"") + appendLine("version = \"0.1.0\"") + appendLine("edition = \"2021\"") + appendLine() + appendLine("[lib]") + appendLine("name = \"${libName}\"") + appendLine("crate-type = [\"cdylib\"]") + appendLine() + appendLine("[dependencies]") + for (crate in crates) { + val featuresList = if (crate.features.isNotEmpty()) + ", features = [${crate.features.joinToString(", ") { "\"$it\"" }}]" + else "" + when { + crate.version != null && featuresList.isEmpty() -> appendLine("${crate.name} = \"${crate.version}\"") + crate.version != null -> appendLine("${crate.name} = { version = \"${crate.version}\"$featuresList }") + crate.path != null -> appendLine("${crate.name} = { path = \"${crate.path}\"$featuresList }") + crate.gitUrl != null -> appendLine("${crate.name} = { git = \"${crate.gitUrl}\", branch = \"${crate.gitBranch}\"$featuresList }") + } + } + // pollster is used by generated bridges to block on async Rust methods + appendLine("pollster = \"0.4\"") + } + rustProjectDir.resolve("Cargo.toml").writeText(cargoToml) + + val srcDir = rustProjectDir.resolve("src") + srcDir.mkdirs() + srcDir.resolve("lib.rs").writeText("// placeholder\n") + // Placeholder lib.rs — will be rewritten after rustdoc analysis in rewriteWrapperLibRs + + logger.lifecycle("kne-rust: Generated wrapper Cargo project at ${rustProjectDir.absolutePath}") + return rustProjectDir + } + + private fun resolveRustdocProject( + crates: List, + cargoProjectDir: File, + logger: org.gradle.api.logging.Logger, + ): File { + val localCrate = crates.singleOrNull { it.path != null } + if (localCrate != null) return cargoProjectDir + + // For external crates, run rustdoc on the wrapper project. + // The wrapper has `pub use crate::*;` which makes rustdoc inline re-exported types. + // Running directly on the dependency source doesn't apply features from the wrapper. + return cargoProjectDir + } + + private fun resolveDependencySourceDir( + cargoProjectDir: File, + crate: CrateDependency, + logger: org.gradle.api.logging.Logger, + ): File? { + val cargo = findCargo() + val process = ProcessBuilder(cargo, "metadata", "--format-version", "1", "--quiet") + .directory(cargoProjectDir) + .redirectErrorStream(true) + .start() + + val output = process.inputStream.bufferedReader().readText() + val exitCode = process.waitFor() + if (exitCode != 0) { + logger.warn("kne-rust: cargo metadata failed while resolving '${crate.name}':\n$output") + return null + } + + return findPackageManifestDir(output, crate)?.parentFile + } + + private fun ensureBuildRs(cargoDir: File, bridgesDir: File, logger: org.gradle.api.logging.Logger) { + val buildRs = cargoDir.resolve("build.rs") + val bridgePath = bridgesDir.resolve("kne_bridges.rs").absolutePath.replace("\\", "/") + val content = """ + |fn main() { + | let src = "$bridgePath"; + | let out_dir = std::env::var("OUT_DIR").unwrap(); + | let dest = format!("{}/kne_bridges.rs", out_dir); + | if std::path::Path::new(src).exists() { + | std::fs::copy(src, &dest).expect("Failed to copy kne_bridges.rs"); + | } else { + | std::fs::write(&dest, "// placeholder\n").expect("Failed to write placeholder"); + | } + | println!("cargo:rerun-if-changed={}", src); + |} + """.trimMargin() + if (!buildRs.exists() || buildRs.readText() != content) { + buildRs.writeText(content) + logger.lifecycle("kne-rust: Generated build.rs for bridge inclusion") + } + } + + private fun mergeModules(modules: List, libName: String): KneModule { + if (modules.isEmpty()) return KneModule(libName = libName, packages = emptySet(), classes = emptyList(), dataClasses = emptyList(), enums = emptyList(), functions = emptyList()) + if (modules.size == 1) return modules.single() + + // Collect all type names from richer representations (sealed enums, data classes, enums) + // to exclude their opaque class counterparts + val sealedEnumNames = modules.flatMap { it.sealedEnums }.map { it.simpleName }.toSet() + val dataClassNames = modules.flatMap { it.dataClasses }.map { it.simpleName }.toSet() + val enumNames = modules.flatMap { it.enums }.map { it.simpleName }.toSet() + val richTypeNames = sealedEnumNames + dataClassNames + enumNames + + // Detect type names that appear in multiple crates (different fqName prefixes) + data class TypeNameInfo(val simpleName: String, val fqName: String) + val allTypeInfos = mutableListOf() + modules.forEach { m -> + m.classes.forEach { allTypeInfos.add(TypeNameInfo(it.simpleName, it.fqName)) } + m.enums.forEach { allTypeInfos.add(TypeNameInfo(it.simpleName, it.fqName)) } + m.sealedEnums.forEach { allTypeInfos.add(TypeNameInfo(it.simpleName, it.fqName)) } + m.dataClasses.forEach { allTypeInfos.add(TypeNameInfo(it.simpleName, it.fqName)) } + } + // Only flag types as ambiguous if they come from truly independent crate families. + // Sub-crate re-exports (e.g. nokhwa re-exporting nokhwa_core types) are not ambiguous + // because they are the same type — only flag when the root crate names differ + // (e.g. "symphonia_core" vs "cpal" are different families, but "nokhwa" vs "nokhwa_core" are the same). + val ambiguousNames = allTypeInfos.groupBy { it.simpleName } + .filter { (_, infos) -> + val crateRoots = infos.map { it.fqName.substringBefore('.').substringBefore('_') }.toSet() + crateRoots.size > 1 + } + .keys + + val seenClasses = mutableSetOf() + val seenDataClasses = mutableSetOf() + val seenEnums = mutableSetOf() + val seenFunctions = mutableSetOf() + val seenInterfaces = mutableSetOf() + val seenSealedEnums = mutableSetOf() + return KneModule( + libName = libName, + packages = modules.flatMap { it.packages }.toSet(), + // Skip opaque classes that have a richer representation as sealed enum / data class / enum. + // When the same class name appears from multiple crates, prefer the version with the + // most methods (e.g. nokhwa_core's Resolution with new/width/height over nokhwa's opaque). + classes = modules.flatMap { it.classes } + .filter { it.simpleName !in richTypeNames } + .groupBy { it.simpleName } + .values.map { variants -> + if (variants.size == 1) variants.single() + else { + // Pick the richest variant (most methods/properties/constructor) + val richest = variants.maxByOrNull { + it.methods.size + it.companionMethods.size + it.properties.size + + if (it.constructor.kind != KneConstructorKind.NONE) 1 else 0 + }!! + // Merge interfaces from all variants so trait impls aren't lost + val allInterfaces = variants.flatMap { it.interfaces }.distinct() + if (allInterfaces != richest.interfaces) richest.copy(interfaces = allInterfaces) + else richest + } + }, + dataClasses = modules.flatMap { it.dataClasses }.filter { seenDataClasses.add(it.simpleName) }, + enums = modules.flatMap { it.enums }.filter { seenEnums.add(it.simpleName) }, + functions = modules.flatMap { it.functions }.filter { seenFunctions.add(it.name) }, + interfaces = modules.flatMap { it.interfaces }.filter { seenInterfaces.add(it.simpleName) }, + sealedEnums = modules.flatMap { it.sealedEnums }.filter { seenSealedEnums.add(it.simpleName) }, + traitImpls = modules.fold(mutableMapOf>()) { acc, m -> + m.traitImpls.forEach { (k, v) -> acc.getOrPut(k) { mutableListOf() }.addAll(v) } + acc + }, + ambiguousTypeNames = ambiguousNames, + ) + } + + /** + * Rewrites the wrapper crate's lib.rs with proper use statements based on the dependency's + * rustdoc JSON. This ensures all sub-module types are in scope for the generated bridges. + */ + private fun rewriteWrapperLibRs( + srcDir: File, + crates: List, + docDir: File, + logger: org.gradle.api.logging.Logger, + ) { + // Helper: recursively collect all module paths from a rustdoc JSON index. + // Also follows glob re-exports (`pub use crate::*;`) into their target crate's JSON + // to discover submodules that are re-exported through the current module. + fun collectModulePaths(index: com.google.gson.JsonObject, items: com.google.gson.JsonArray, parentPath: String): List { + val paths = mutableListOf() + for (itemId in items) { + val item = index.get(itemId.asInt.toString())?.asJsonObject ?: continue + val inner = item.getAsJsonObject("inner") ?: continue + if (inner.has("module")) { + val name = item.get("name")?.asString ?: continue + val path = if (parentPath.isEmpty()) name else "$parentPath::$name" + paths.add(path) + val subItems = inner.getAsJsonObject("module")?.getAsJsonArray("items") + if (subItems != null) { + paths.addAll(collectModulePaths(index, subItems, path)) + } + } else if (inner.has("use")) { + // Handle glob re-exports like `pub use muda::*;` inside a module + val useItem = inner.getAsJsonObject("use") + val isGlob = useItem.get("is_glob")?.asBoolean == true + if (isGlob) { + val source = useItem.get("source")?.let { if (it.isJsonNull) null else it.asString } ?: continue + val subCrateName = source.replace('-', '_') + val subJson = docDir.resolve("$subCrateName.json") + if (subJson.exists()) { + // Parse the glob-re-exported crate's modules under the current path + val subDoc = com.google.gson.JsonParser.parseString(subJson.readText()).asJsonObject + val subIndex = subDoc.getAsJsonObject("index") ?: continue + val subRootId = subDoc.get("root")?.asInt?.toString() ?: continue + val subRoot = subIndex.get(subRootId)?.asJsonObject ?: continue + val subItems = subRoot.getAsJsonObject("inner") + ?.getAsJsonObject("module")?.getAsJsonArray("items") ?: continue + paths.addAll(collectModulePaths(subIndex, subItems, parentPath)) + } + } + } + } + return paths + } + + // Helper: extract root module items from a rustdoc JSON file + fun parseModuleTree(jsonFile: File): List { + val json = com.google.gson.JsonParser.parseString(jsonFile.readText()).asJsonObject + val index = json.getAsJsonObject("index") ?: return emptyList() + val rootId = json.get("root")?.asInt?.toString() ?: return emptyList() + val rootModule = index.get(rootId)?.asJsonObject ?: return emptyList() + val rootItems = rootModule.getAsJsonObject("inner") + ?.getAsJsonObject("module") + ?.getAsJsonArray("items") ?: return emptyList() + return collectModulePaths(index, rootItems, "") + } + + val primaryCrateName = crates.first().name.replace('-', '_') + + // Collect pub use statements + val useStatements = mutableListOf() + + // 1. Parse main crate JSON: discover direct sub-modules AND re-exported crates + val mainJsonFile = docDir.resolve("$primaryCrateName.json") + val reExportedCrates = mutableMapOf() // sub-crate name → re-export alias + + if (mainJsonFile.exists()) { + useStatements.add("pub use ${primaryCrateName}::*;") + for (modPath in parseModuleTree(mainJsonFile)) { + useStatements.add("pub use ${primaryCrateName}::${modPath}::*;") + } + + // Detect re-exported sub-crates and modules (e.g. `pub use symphonia_core;` or `pub use muda::dpi;`) + val mainJson = com.google.gson.JsonParser.parseString(mainJsonFile.readText()).asJsonObject + val mainIndex = mainJson.getAsJsonObject("index") + val mainPaths = mainJson.getAsJsonObject("paths") + val rootId = mainJson.get("root")?.asInt?.toString() + val rootItems = rootId?.let { mainIndex?.get(it)?.asJsonObject } + ?.getAsJsonObject("inner")?.getAsJsonObject("module")?.getAsJsonArray("items") + if (rootItems != null) { + for (itemId in rootItems) { + val item = mainIndex.get(itemId.asInt.toString())?.asJsonObject ?: continue + val inner = item.getAsJsonObject("inner") ?: continue + if (inner.has("use")) { + val useItem = inner.getAsJsonObject("use") + val sourceElem = useItem.get("source") + if (sourceElem == null || sourceElem.isJsonNull) continue + val source = sourceElem.asString + + // Check if target is a module (via paths map) — handles `pub use muda::dpi;` + val targetId = useItem.get("id")?.asInt?.toString() + val targetPath = targetId?.let { mainPaths?.get(it)?.asJsonObject } + val targetKind = targetPath?.get("kind")?.asString + val useAlias = useItem.get("name")?.let { if (it.isJsonNull) null else it.asString } + + if (targetKind == "module" && useAlias != null) { + // Module re-export: add `pub use primary::alias::*;` + useStatements.add("pub use ${primaryCrateName}::${useAlias}::*;") + continue + } + + // Re-exported crate: source is the crate name (e.g. "symphonia_core") + // The alias in the parent crate is derived from the crate name + // e.g. symphonia_core is accessible as symphonia::core + val nameElem = item.get("name") + val alias = if (nameElem != null && !nameElem.isJsonNull) nameElem.asString + else source.removePrefix("${primaryCrateName}_") + reExportedCrates[source.replace('-', '_')] = alias + } + } + } + } + + // 2. For re-exported sub-crates, parse their modules and map to the correct path + // e.g. symphonia_core is re-exported → symphonia::core::io::*, symphonia::core::audio::* + for ((subCrateName, alias) in reExportedCrates) { + val subJson = docDir.resolve("$subCrateName.json") + if (!subJson.exists()) continue + val reExportPath = "${primaryCrateName}::${alias}" + useStatements.add("pub use ${reExportPath}::*;") + for (modPath in parseModuleTree(subJson)) { + useStatements.add("pub use ${reExportPath}::${modPath}::*;") + } + } + + // 3. Parse additional crate JSONs (separate dependencies like cpal) + for (crate in crates.drop(1)) { + val additionalCrateName = crate.name.replace('-', '_') + val additionalJson = docDir.resolve("$additionalCrateName.json") + if (additionalJson.exists()) { + useStatements.add("pub use ${additionalCrateName}::*;") + for (modPath in parseModuleTree(additionalJson)) { + useStatements.add("pub use ${additionalCrateName}::${modPath}::*;") + } + } + } + + val uniqueStatements = useStatements.distinct() + val libRs = buildString { + for (stmt in uniqueStatements) { + appendLine(stmt) + } + appendLine() + appendLine("include!(concat!(env!(\"OUT_DIR\"), \"/kne_bridges.rs\"));") + } + srcDir.resolve("lib.rs").writeText(libRs) + logger.lifecycle("kne-rust: Rewrote wrapper lib.rs with ${uniqueStatements.size} use statements (recursive, multi-crate)") + } + + private fun ensureLibRsInclude(srcDir: File, logger: org.gradle.api.logging.Logger) { + val libRs = srcDir.resolve("lib.rs") + if (!libRs.exists()) return + val content = libRs.readText() + val oldInclude = "include!(\"kne_bridges.rs\");" + val outDirInclude = "include!(concat!(env!(\"OUT_DIR\"), \"/kne_bridges.rs\"));" + val cleaned = content.replace("\n$oldInclude\n", "\n").replace(oldInclude, "") + if (!cleaned.contains(outDirInclude)) { + libRs.writeText(cleaned.trimEnd() + "\n\n$outDirInclude\n") + logger.lifecycle("kne-rust: Injected OUT_DIR bridge include into lib.rs") + } else if (cleaned != content) { + libRs.writeText(cleaned) + } + } + + private fun runCargoRustdoc(cargoDir: File, libName: String, crates: List, logger: org.gradle.api.logging.Logger): File? { + val cargo = findCargo() + + val isWrapper = cargoDir.resolve("Cargo.toml").readText().contains("kne-") + val cmd = if (isWrapper) { + // For wrapper crates, omit --no-deps so re-exported dependency types are inlined + listOf(cargo, "doc") + } else { + listOf(cargo, "doc", "--no-deps") + } + val process = ProcessBuilder(cmd) + .directory(cargoDir) + .apply { + environment()["RUSTC_BOOTSTRAP"] = "1" + environment()["RUSTDOCFLAGS"] = "-Z unstable-options --output-format json" + } + .redirectErrorStream(true) + .start() + + val output = process.inputStream.bufferedReader().readText() + val exitCode = process.waitFor() + + if (exitCode != 0) { + logger.error("kne-rust: cargo rustdoc failed (exit $exitCode):\n$output") + return null + } + + // Find the JSON file in target/doc/ + val docDir = cargoDir.resolve("target/doc") + return selectRustdocJson(docDir, cargoDir, libName, crates) + } + + internal fun selectRustdocJson(docDir: File, cargoDir: File, libName: String, crates: List = emptyList()): File? { + val jsonFiles = docDir.listFiles()?.filter { it.extension == "json" } ?: return null + if (jsonFiles.isEmpty()) return null + + // For wrapper crates (no local path), prefer the dependency crate's JSON directly. + // Wrapper's glob re-export (pub use dep::*) doesn't inline types in rustdoc JSON. + val isWrapper = crates.isNotEmpty() && crates.none { it.path != null } + if (isWrapper) { + for (crate in crates) { + val depName = crate.name.replace('-', '_') + val depJson = docDir.resolve("$depName.json") + if (depJson.exists()) return depJson + } + } + + val expectedNames = linkedSetOf() + resolveRustdocTargetName(cargoDir)?.let { expectedNames.add(it) } + expectedNames.add(libName.replace('-', '_')) + + for (name in expectedNames) { + val expected = docDir.resolve("$name.json") + if (expected.exists()) return expected + } + + return if (jsonFiles.size == 1) { + jsonFiles.single() + } else { + jsonFiles.maxByOrNull { it.lastModified() } + } + } + + internal fun resolveRustdocTargetName(cargoDir: File): String? { + val cargoToml = cargoDir.resolve("Cargo.toml") + if (!cargoToml.exists()) return null + + var inPackage = false + var inLib = false + var packageName: String? = null + var libTargetName: String? = null + + cargoToml.forEachLine { rawLine -> + val line = rawLine.substringBefore('#').trim() + when { + line == "[package]" -> { + inPackage = true + inLib = false + } + line == "[lib]" -> { + inPackage = false + inLib = true + } + line.startsWith("[") && line.endsWith("]") -> { + inPackage = false + inLib = false + } + inPackage && line.startsWith("name") -> { + packageName = extractTomlStringValue(line) + } + inLib && line.startsWith("name") -> { + libTargetName = extractTomlStringValue(line) + } + } + } + + return (libTargetName ?: packageName)?.replace('-', '_') + } + + private fun extractTomlStringValue(line: String): String? = + Regex("""^\s*\w+\s*=\s*"([^"]+)"""").find(line)?.groupValues?.getOrNull(1) + + internal fun findPackageManifestDir(metadataJson: String, crate: CrateDependency): File? { + val jsonStart = metadataJson.indexOf('{') + if (jsonStart < 0) return null + val root = JsonParser.parseString(metadataJson.substring(jsonStart)).asJsonObject + val packages = root.getAsJsonArray("packages") ?: return null + for (pkg in packages) { + val pkgObj = pkg.asJsonObject + val name = pkgObj.get("name")?.asString ?: continue + if (name != crate.name) continue + val version = pkgObj.get("version")?.asString + if (crate.version != null && version != crate.version) continue + val manifestPath = pkgObj.get("manifest_path")?.asString ?: continue + return File(manifestPath) + } + return null + } + + private fun generateGraalVmMetadata( + resourcesRoot: File, + module: KneModule, + jvmPackage: String, + generator: FfmProxyGenerator, + ) { + val metaDir = resourcesRoot.resolve("META-INF/native-image/kne/${module.libName}") + metaDir.mkdirs() + + val classNames = mutableListOf() + classNames.add("$jvmPackage.KneRuntime") + classNames.add("$jvmPackage.KotlinNativeException") + module.classes.forEach { classNames.add("$jvmPackage.${it.simpleName}") } + module.dataClasses.filter { !it.isCommon }.forEach { classNames.add("$jvmPackage.${it.simpleName}") } + module.enums.forEach { classNames.add("$jvmPackage.${it.simpleName}") } + if (module.functions.isNotEmpty()) { + classNames.add("$jvmPackage.${module.libName.replaceFirstChar { it.uppercaseChar() }}") + } + + val downcalls = generator.collectGraalVmDowncalls(module) + + val reflectEntries = classNames.joinToString(",\n") { name -> + """ { + "name": "$name", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }""" + } + metaDir.resolve("reflect-config.json").writeText("[\n$reflectEntries\n]\n") + + metaDir.resolve("resource-config.json").writeText("""{ + "resources": { + "includes": [ + { "pattern": "\\Qkne/native/\\E.*" } + ] + } +} +""") + + fun formatEntries(descriptors: Set, String?>>): String = + descriptors.joinToString(",\n") { (params, ret) -> + val paramStr = params.joinToString(", ") { "\"$it\"" } + val retStr = ret ?: "void" + """ { "parameterTypes": [$paramStr], "returnType": "$retStr" }""" + } + + val downcallEntries = formatEntries(downcalls) + val upcalls = generator.collectGraalVmUpcalls(module) + val upcallSection = if (upcalls.isNotEmpty()) { + val upcallEntries = formatEntries(upcalls) + """, + "upcalls": [ +$upcallEntries + ]""" + } else "" + + metaDir.resolve("reachability-metadata.json").writeText("""{ + "reflection": [ +${classNames.joinToString(",\n") { """ { "type": "$it", "allDeclaredConstructors": true, "allDeclaredMethods": true }""" }} + ], + "resources": [ + { "glob": "kne/native/**" } + ], + "foreign": { + "downcalls": [ +$downcallEntries + ]$upcallSection + } +} +""") + } +} diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustdocJsonParser.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustdocJsonParser.kt new file mode 100644 index 00000000..51fc6f10 --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustdocJsonParser.kt @@ -0,0 +1,2344 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.analysis + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.* + +/** + * Parses a rustdoc JSON file (produced by `cargo rustdoc --output-format json`) + * and builds a [KneModule] suitable for FFM proxy generation. + * + * The parser accepts a subset of generics when they can be lowered to stable bridgeable + * Kotlin types, for example `AsRef`, `AsRef`, `Into` and `Fn(...)`. + */ +class RustdocJsonParser { + + companion object { + /** Standard Rust traits that should not be bridged to Kotlin. */ + private val SKIPPED_RUST_TRAITS = setOf( + "Drop", "Clone", "Copy", "Send", "Sync", "Unpin", "Sized", + "Debug", "Display", "Default", "Hash", "Eq", "PartialEq", "Ord", "PartialOrd", + "From", "Into", "TryFrom", "TryInto", "AsRef", "AsMut", + "Borrow", "BorrowMut", "ToOwned", "ToString", + "Any", "Freeze", "RefUnwindSafe", "UnwindSafe", + "StructuralPartialEq", "CloneToUninit", + "Serialize", "Deserialize", + "Iterator", "IntoIterator", "ExactSizeIterator", "DoubleEndedIterator", + "FormatDecoder", // nokhwa-specific: complex trait with associated types, not bridgeable as interface + ) + } + + /** Set during [parse]; used by [resolveType] to build fqNames for struct/enum references. */ + private var currentCrateName: String = "" + + /** Set during [parse]; enum IDs that have data variants (→ SEALED_ENUM, not ENUM). */ + private var currentSealedEnumIds: Set = emptySet() + + /** Opaque external/current-crate types referenced by supported signatures. */ + private var encounteredOpaqueClasses: LinkedHashMap = linkedMapOf() + + /** Public type names exported by the parsed crate root. Used to avoid Kotlin name collisions. */ + private var reservedTopLevelTypeNames: Set = emptySet() + + /** Known trait IDs → names, set during [parse] for `dyn Trait` resolution. */ + private var currentKnownTraits: Map = emptyMap() + + /** Trait names that appear as `dyn Trait` in function signatures (need synthetic wrapper classes). */ + private val dynTraitNames = mutableSetOf() + + /** Trait -> [ConcreteType] registry built from impl blocks. Used for generic monomorphisation. */ + private var traitImpls: Map> = emptyMap() + + /** Tracks generic params with user-defined trait bounds (e.g., `F: FormatDecoder`). + * Set during method building to enable monomorphisation. */ + private var currentUnresolvedBounds: Map> = emptyMap() + + // -- Lazy cross-crate type resolution state -- + + /** Set during [parse]; the full rustdoc JSON index for lazy type lookups. */ + private var currentIndex: JsonObject? = null + + /** Mutable aliases set during [parse]; lazy-discovered types are registered here. */ + private var currentKnownStructs: MutableMap = mutableMapOf() + private var currentKnownDataClasses: MutableMap = mutableMapOf() + private var currentKnownEnums: MutableMap = mutableMapOf() + + /** The `paths` field from rustdoc JSON — maps IDs to {crate_id, path, kind} for all referenced types. */ + private var currentPaths: JsonObject? = null + + /** Recursion guard: IDs currently being lazily resolved (prevents infinite loops). */ + private val lazyResolutionInProgress: MutableSet = mutableSetOf() + + private fun JsonElement?.safeString(): String? { + if (this == null || this.isJsonNull) return null + if (!this.isJsonPrimitive) return null + return this.asString + } + + /** + * Result of resolving a rustdoc JSON type: the mapped [KneType], whether the original type was + * borrowed, and a best-effort Rust type hint used by Rust bridge generation for casts. + */ + private data class ResolvedType( + val type: KneType, + val isBorrowed: Boolean = false, + val rustType: String? = null, + /** Rust expression suffix to apply in bridge code for `impl Trait` return types (e.g. `.collect::>()`). */ + val implTraitConversion: String? = null, + /** True when the return type was `impl Future` — the bridge must block on the future. */ + val isFuture: Boolean = false, + ) + + /** + * Result of resolving generic type parameters, including both resolved types + * (for built-in traits like Fn, AsRef) and unresolved bounds (for user-defined traits + * that require monomorphisation via traitImpls). + */ + private data class GenericResolution( + /** Maps generic param name -> resolved type (for Fn, AsRef, Into, etc.) */ + val resolvedTypes: Map, + /** Maps generic param name -> trait bounds that could not be resolved to a concrete type. + * These require monomorphisation using traitImpls. */ + val unresolvedBounds: Map>, + ) + + fun parse( + json: String, + libName: String, + onUnsupported: (String) -> Unit = {}, + ): KneModule { + encounteredOpaqueClasses = linkedMapOf() + reservedTopLevelTypeNames = emptySet() + dynTraitNames.clear() + lazyResolutionInProgress.clear() + + val root = JsonParser.parseString(json).asJsonObject + val index = root.getAsJsonObject("index") + currentIndex = index + currentPaths = root.getAsJsonObject("paths") + val rootModuleId = root.get("root").asInt + + val rootModule = index.get(rootModuleId.toString())?.asJsonObject + val crateName = rootModule?.get("name").safeString() ?: libName + currentCrateName = crateName + reservedTopLevelTypeNames = rootModule + ?.getAsJsonObject("inner") + ?.getAsJsonObject("module") + ?.getAsJsonArray("items") + ?.mapNotNull { itemId -> + index.get(itemId.asInt.toString())?.asJsonObject?.get("name").safeString() + } + ?.toSet() + ?: emptySet() + + val knownStructs = mutableMapOf() + val knownEnums = mutableMapOf() + val knownTraits = mutableMapOf() + + // Build the set of type IDs that are directly accessible as bare names after + // `pub use crate_name::*`. We traverse the root module's exported items: + // - direct struct/enum/trait definitions in the root → their own ID + // - explicit `pub use X` re-exports (non-glob) → the target ID + // - glob `pub use mod::*` re-exports → all public struct/enum/trait IDs in that module + // This correctly excludes types like `sysinfo::windows::sid::Sid` that are `pub` within + // their module but only `pub(crate)` re-exported, and therefore not in scope after + // `pub use sysinfo::*`. + val rootModuleItems = rootModule + ?.getAsJsonObject("inner") + ?.getAsJsonObject("module") + ?.getAsJsonArray("items") + val rootExportedIds: Set = if (rootModuleItems != null) + buildRootExportedIds(rootModuleItems, index) + else + emptySet() + + for ((id, item) in index.entrySet()) { + val intId = id.toIntOrNull() ?: continue + if (rootExportedIds.isNotEmpty() && intId !in rootExportedIds) continue + val inner = item.asJsonObject.getAsJsonObject("inner") ?: continue + val name = item.asJsonObject.get("name").safeString() ?: continue + val vis = item.asJsonObject.get("visibility").safeString() ?: continue + if (vis != "public") continue + when { + inner.has("struct") -> knownStructs[intId] = name + inner.has("enum") -> knownEnums[intId] = name + inner.has("trait") -> knownTraits[intId] = name + } + } + currentKnownTraits = knownTraits + // Wire up mutable map aliases for lazy cross-crate type resolution. + // Must be set before impl-scanning so lazy resolution can register types. + currentKnownStructs = knownStructs + currentKnownEnums = knownEnums + + val sealedEnumIds = mutableSetOf() + for ((id, _) in knownEnums.toMap()) { + val enumItem = index.get(id.toString())?.asJsonObject ?: continue + val innerEnum = enumItem.getAsJsonObject("inner")?.getAsJsonObject("enum") ?: continue + val variantIds = innerEnum.getAsJsonArray("variants") ?: continue + for (variantId in variantIds) { + val variantItem = index.get(variantId.asInt.toString())?.asJsonObject ?: continue + val variantInner = variantItem.getAsJsonObject("inner") ?: continue + if (!variantInner.has("variant")) continue + val kind = variantInner.getAsJsonObject("variant").get("kind") + if (kind != null && kind.isJsonObject) { + sealedEnumIds.add(id) + break + } + } + } + currentSealedEnumIds = sealedEnumIds + + data class MethodEntry( + val item: JsonObject, + val receiverKind: KneReceiverKind, + val docs: String?, + val isOverride: Boolean = false, + ) + + val implMethods = mutableMapOf>() + val implConstructors = mutableMapOf() + val implCompanionMethods = mutableMapOf>() + val structTraitImpls = mutableMapOf>() + /** Trait impls from skipped traits — used only for monomorphisation, not interface generation. */ + val allTraitImpls = mutableMapOf>() + /** Impl-block-level generics per struct type ID (for `impl Struct`). */ + val implGenerics = mutableMapOf() + + for ((_, item) in index.entrySet()) { + val inner = item.asJsonObject.getAsJsonObject("inner") ?: continue + if (!inner.has("impl")) continue + val implObj = inner.getAsJsonObject("impl") + val traitField = implObj.get("trait") + val isTraitImpl = traitField != null && !traitField.isJsonNull && traitField.isJsonObject + val forType = implObj.getAsJsonObject("for") ?: continue + val typeId = resolveTypeId(forType) ?: continue + if (!knownStructs.containsKey(typeId)) continue + + // Store impl-level generics for struct-level expansion (e.g., impl Struct) + val implGenObj = implObj.getAsJsonObject("generics") + if (implGenObj != null && !isTraitImpl) { + val implParams = implGenObj.getAsJsonArray("params") ?: JsonArray() + val hasTypeParams = implParams.any { p -> + val kind = p.asJsonObject.getAsJsonObject("kind") + kind != null && kind.has("type") + } + if (hasTypeParams) implGenerics[typeId] = implGenObj + } + + if (isTraitImpl) { + val traitName = traitField.asJsonObject.get("path")?.asString ?: continue + if (traitName in SKIPPED_RUST_TRAITS) { + // Still register for monomorphisation, but don't add as interface impl + val structName = knownStructs[typeId] ?: continue + allTraitImpls.getOrPut(traitName) { mutableListOf() }.add(KneType.OBJECT("$crateName.$structName", structName)) + continue + } + structTraitImpls.getOrPut(typeId) { mutableListOf() }.add(traitName) + val items = implObj.getAsJsonArray("items") ?: continue + for (methodIdElem in items) { + val methodItem = index.get(methodIdElem.asInt.toString())?.asJsonObject ?: continue + val methodInner = methodItem.getAsJsonObject("inner") ?: continue + if (!methodInner.has("function")) continue + val sig = methodInner.getAsJsonObject("function").getAsJsonObject("sig") + val inputs = sig.getAsJsonArray("inputs") + if (!hasSelfParam(inputs)) continue + implMethods.getOrPut(typeId) { mutableListOf() }.add( + MethodEntry( + item = methodItem, + receiverKind = classifyReceiverKind(inputs), + docs = methodItem.get("docs").safeString(), + isOverride = true, + ) + ) + } + } else { + val selfType = knownStructs[typeId]?.let { name -> KneType.OBJECT("$crateName.$name", name) } + val implGenObj = implObj.getAsJsonObject("generics") + val items = implObj.getAsJsonArray("items") ?: continue + for (methodIdElem in items) { + val methodItem = index.get(methodIdElem.asInt.toString())?.asJsonObject ?: continue + val methodInner = methodItem.getAsJsonObject("inner") ?: continue + if (!methodInner.has("function")) continue + val methodVis = methodItem.get("visibility").safeString() ?: continue + if (methodVis != "public") continue + val methodName = methodItem.get("name").safeString() ?: continue + val fn = methodInner.getAsJsonObject("function") + val sig = fn.getAsJsonObject("sig") + val inputs = sig.getAsJsonArray("inputs") + // Merge impl-level generics with function-level generics + val mergedGenerics = mergeGenerics(implGenObj, fn.getAsJsonObject("generics")) + val genericResolution = resolveGenericMappings(mergedGenerics, knownStructs, knownEnums, emptyMap(), selfType) + if (hasUnsupportedGenerics(mergedGenerics, genericResolution)) { + onUnsupported("Skipped constructor '${methodName}' for ${typeDisplayName(selfType)}: unsupported generic signature") + continue + } + // Constructors with unresolved generic bounds (e.g., fn new) + // where the struct itself is NOT generic: treat as companion methods for expansion. + // If the struct IS generic (like Processor), the struct-level expansion handles it. + if (genericResolution.unresolvedBounds.isNotEmpty() && !hasSelfParam(inputs)) { + val structGenericObj = implObj.getAsJsonObject("generics") + val structHasTypeParams = structGenericObj?.getAsJsonArray("params")?.any { p -> + val kind = p.asJsonObject.getAsJsonObject("kind") + kind != null && kind.has("type") + } == true + if (!structHasTypeParams) { + implCompanionMethods.getOrPut(typeId) { mutableListOf() }.add(methodItem) + continue + } + } + val returnType = resolveTypeWithBorrow(sig.get("output"), knownStructs, knownEnums, emptyMap(), genericResolution.resolvedTypes, selfType) + val isConstructor = methodName == "new" && !hasSelfParam(inputs) && returnType?.type == selfType + + when { + isConstructor -> implConstructors[typeId] = methodItem + hasSelfParam(inputs) -> implMethods.getOrPut(typeId) { mutableListOf() }.add( + MethodEntry( + item = methodItem, + receiverKind = classifyReceiverKind(inputs), + docs = methodItem.get("docs").safeString(), + ) + ) + else -> implCompanionMethods.getOrPut(typeId) { mutableListOf() }.add(methodItem) + } + } + } + } + + val traitToImplTypes = mutableMapOf>() + for ((typeId, traitNames) in structTraitImpls) { + val structName = knownStructs[typeId] ?: continue + val implementingType = KneType.OBJECT("$crateName.$structName", structName) + for (traitName in traitNames) { + val fqTraitName = "$crateName.$traitName" + traitToImplTypes.getOrPut(fqTraitName) { mutableListOf() }.add(implementingType) + } + } + // Merge skipped-trait impls into the registry for monomorphisation + for ((traitName, impls) in allTraitImpls) { + traitToImplTypes.getOrPut(traitName) { mutableListOf() }.addAll(impls) + } + traitImpls = traitToImplTypes + + val knownDataClasses = mutableMapOf() + // Snapshot the initial struct IDs to avoid processing lazily-discovered cross-crate types + val initialStructIds = knownStructs.keys.toSet() + for (id in initialStructIds) { + val name = knownStructs[id] ?: continue + val hasMethods = implMethods[id]?.isNotEmpty() == true || implCompanionMethods[id]?.isNotEmpty() == true + if (hasMethods) continue + + val structItem = index.get(id.toString())?.asJsonObject ?: continue + val fields = extractStructFields(structItem, index, knownStructs, knownEnums, knownDataClasses) + if (fields == null || fields.isEmpty()) continue + if (!fields.all { isDataClassFieldSupported(it.type) }) continue + + knownDataClasses[id] = KneDataClass( + simpleName = name, + fqName = "$crateName.$name", + fields = fields, + ) + } + + currentKnownDataClasses = knownDataClasses + + val classes = mutableListOf() + for ((id, name) in knownStructs.toMap()) { + if (knownDataClasses.containsKey(id)) continue + val structItem = index.get(id.toString())?.asJsonObject ?: continue + + // Note: structs with generic type params (e.g. Processor, ReadOnlySource) + // are NOT skipped here — the monomorphisation step below may produce concrete + // variants. The bridge generator handles unresolved generics at code gen time. + val selfType = KneType.OBJECT("$crateName.$name", name) + val constructor = buildConstructor( + newFn = implConstructors[id], + structItem = structItem, + index = index, + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + selfType = selfType, + implGenericJson = implGenerics[id], + onUnsupported = { onUnsupported("Class '$name': $it") }, + ) + + val structImplGenerics = implGenerics[id] + val allMethods = (implMethods[id] ?: emptyList()).flatMap { entry -> + buildMethod( + methodItem = entry.item, + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + receiverKind = entry.receiverKind, + docs = entry.docs, + ownerType = selfType, + implGenericJson = structImplGenerics, + onUnsupported = { onUnsupported("Class '${name}': $it") }, + ).map { if (entry.isOverride) it.copy(isOverride = true) else it } + } + + val companionMethods = (implCompanionMethods[id] ?: emptyList()).flatMap { methodItem -> + buildMethod( + methodItem = methodItem, + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + receiverKind = KneReceiverKind.NONE, + docs = methodItem.get("docs").safeString(), + ownerType = selfType, + implGenericJson = structImplGenerics, + onUnsupported = { onUnsupported("Class '${name}': $it") }, + ) + } + + // Deduplicate methods by signature — same trait may be impl'd multiple times + // for different type params (e.g., AsAudioBufferRef for AudioBuffer) + val deduplicatedMethods = allMethods.distinctBy { fn -> + fn.name + "(" + fn.params.joinToString(",") { it.type.toString() } + ")" + } + val (methods, properties) = extractProperties(deduplicatedMethods) + // Only add traits that are known (exported at root level) as Kotlin interfaces + val traitNames = structTraitImpls[id] + ?.filter { it in knownTraits.values } + ?.distinct() + ?.map { "$crateName.$it" } + ?: emptyList() + val hasLifetimeParams = structHasLifetimeParams(structItem) + val hasTypeParams = structHasTypeParams(structItem) + // If any methods are marked as overrides but no interfaces are known, + // clear the override flag (the trait wasn't resolved in this crate's scope) + val cleanedMethods = if (traitNames.isEmpty() && methods.any { it.isOverride }) { + methods.map { if (it.isOverride) it.copy(isOverride = false) else it } + } else methods + val klass = KneClass( + simpleName = name, + fqName = "$crateName.$name", + constructor = constructor, + methods = cleanedMethods, + properties = properties, + companionMethods = companionMethods, + interfaces = traitNames, + hasLifetimeParams = hasLifetimeParams, + hasUnresolvedGenericTypeParams = hasTypeParams, + ) + val expandedClasses = expandClassWithGenerics(klass, structItem, crateName) + classes.addAll(expandedClasses) + } + + val enums = mutableListOf() + val sealedEnums = mutableListOf() + for ((id, name) in knownEnums.toMap()) { + val enumItem = index.get(id.toString())?.asJsonObject ?: continue + val inner = enumItem.getAsJsonObject("inner")?.getAsJsonObject("enum") ?: continue + val variantIds = inner.getAsJsonArray("variants") ?: continue + + if (id in sealedEnumIds) { + val variants = mutableListOf() + for (variantId in variantIds) { + val variantItem = index.get(variantId.asInt.toString())?.asJsonObject ?: continue + val variantName = variantItem.get("name").safeString() ?: continue + val variantInner = variantItem.getAsJsonObject("inner") ?: continue + if (!variantInner.has("variant")) continue + val variantData = variantInner.getAsJsonObject("variant") + val parsed = parseVariantFields( + kind = variantData.get("kind"), + index = index, + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + context = "${name}::${variantName}", + onUnsupported = onUnsupported, + ) ?: continue + variants.add(KneSealedVariant(variantName, parsed.first, parsed.second)) + } + sealedEnums.add( + KneSealedEnum( + simpleName = name, + fqName = "$crateName.$name", + variants = variants, + ) + ) + } else { + val entries = mutableListOf() + for (variantId in variantIds) { + val variantItem = index.get(variantId.asInt.toString())?.asJsonObject ?: continue + val variantName = variantItem.get("name").safeString() ?: continue + entries.add(variantName) + } + enums.add(KneEnum(simpleName = name, fqName = "$crateName.$name", entries = entries)) + } + } + + val rootItems = rootModule?.getAsJsonObject("inner") + ?.getAsJsonObject("module") + ?.getAsJsonArray("items") ?: JsonArray() + val topLevelFunctions = mutableListOf() + // Use rootExportedIds (which traverses submodules via pub use) to find + // functions that are accessible from the crate root, not just direct root items. + val functionItemIds: Iterable = if (rootExportedIds.isNotEmpty()) { + rootExportedIds + } else { + rootItems.map { it.asInt } + } + for (itemId in functionItemIds) { + val item = index.get(itemId.toString())?.asJsonObject ?: continue + val inner = item.getAsJsonObject("inner") ?: continue + if (!inner.has("function")) continue + if (isGeneratedBridgeFunction(item)) continue + val vis = item.get("visibility").safeString() ?: continue + if (vis != "public") continue + val name = item.get("name").safeString() ?: continue + if (name.startsWith("${libName}_") || name.startsWith("kne_")) continue + val sig = inner.getAsJsonObject("function").getAsJsonObject("sig") + if (hasSelfParam(sig.getAsJsonArray("inputs"))) continue + buildMethod( + methodItem = item, + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + receiverKind = KneReceiverKind.NONE, + docs = item.get("docs").safeString(), + ownerType = null, + onUnsupported = { onUnsupported("Top-level function '$name': $it") }, + ).let(topLevelFunctions::addAll) + } + + val interfaces = mutableListOf() + for ((id, traitName) in knownTraits) { + val traitItem = index.get(id.toString())?.asJsonObject ?: continue + val traitInner = traitItem.getAsJsonObject("inner")?.getAsJsonObject("trait") ?: continue + val traitItems = traitInner.getAsJsonArray("items") ?: continue + val selfType = KneType.INTERFACE("$crateName.$traitName", traitName) + val traitMethods = traitItems.flatMap { methodId -> + val methodItem = index.get(methodId.asInt.toString())?.asJsonObject ?: return@flatMap emptyList() + val methodInner = methodItem.getAsJsonObject("inner") ?: return@flatMap emptyList() + if (!methodInner.has("function")) return@flatMap emptyList() + val sig = methodInner.getAsJsonObject("function").getAsJsonObject("sig") + buildMethod( + methodItem = methodItem, + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + receiverKind = if (hasSelfParam(sig.getAsJsonArray("inputs"))) classifyReceiverKind(sig.getAsJsonArray("inputs")) else KneReceiverKind.NONE, + ownerType = selfType, + onUnsupported = onUnsupported, + ) + } + interfaces.add( + KneInterface( + simpleName = traitName, + fqName = "$crateName.$traitName", + methods = traitMethods, + properties = emptyList(), + ) + ) + } + + val occupiedTopLevelNames = ( + classes.map { it.simpleName } + + knownDataClasses.values.map { it.simpleName } + + enums.map { it.simpleName } + + sealedEnums.map { it.simpleName } + + interfaces.map { it.simpleName } + ).toMutableSet() + val opaqueRenames = mutableMapOf() + val renamedOpaqueClasses = encounteredOpaqueClasses.values.map { opaque -> + val uniqueName = if (opaque.simpleName in occupiedTopLevelNames) { + uniqueOpaqueSimpleName(opaque.simpleName, opaque.fqName, occupiedTopLevelNames) + } else { + opaque.simpleName + } + occupiedTopLevelNames += uniqueName + opaqueRenames[opaque.fqName] = uniqueName + opaque.copy(simpleName = uniqueName) + } + + fun renameType(type: KneType): KneType = when (type) { + is KneType.OBJECT -> opaqueRenames[type.fqName]?.let { type.copy(simpleName = it) } ?: type + is KneType.INTERFACE -> opaqueRenames[type.fqName]?.let { type.copy(simpleName = it) } ?: type + is KneType.SEALED_ENUM -> opaqueRenames[type.fqName]?.let { type.copy(simpleName = it) } ?: type + is KneType.NULLABLE -> type.copy(inner = renameType(type.inner)) + is KneType.FUNCTION -> type.copy( + paramTypes = type.paramTypes.map(::renameType), + returnType = renameType(type.returnType), + ) + is KneType.DATA_CLASS -> type.copy(fields = type.fields.map { it.copy(type = renameType(it.type)) }) + is KneType.LIST -> type.copy(elementType = renameType(type.elementType)) + is KneType.SET -> type.copy(elementType = renameType(type.elementType)) + is KneType.MAP -> type.copy(keyType = renameType(type.keyType), valueType = renameType(type.valueType)) + is KneType.FLOW -> type.copy(elementType = renameType(type.elementType)) + else -> type + } + + fun renameParam(param: KneParam): KneParam = param.copy(type = renameType(param.type)) + fun renameProperty(property: KneProperty): KneProperty = property.copy(type = renameType(property.type)) + fun renameFunction(function: KneFunction): KneFunction = function.copy( + params = function.params.map(::renameParam), + returnType = renameType(function.returnType), + receiverType = function.receiverType?.let(::renameType), + ) + fun renameClass(cls: KneClass): KneClass = cls.copy( + constructor = cls.constructor.copy(params = cls.constructor.params.map(::renameParam)), + methods = cls.methods.map(::renameFunction), + properties = cls.properties.map(::renameProperty), + companionMethods = cls.companionMethods.map(::renameFunction), + companionProperties = cls.companionProperties.map(::renameProperty), + ) + fun renameInterface(iface: KneInterface): KneInterface = iface.copy( + methods = iface.methods.map(::renameFunction), + properties = iface.properties.map(::renameProperty), + ) + fun renameSealedEnum(sealed: KneSealedEnum): KneSealedEnum = sealed.copy( + variants = sealed.variants.map { variant -> + variant.copy(fields = variant.fields.map(::renameParam)) + } + ) + + val renamedClasses = classes.map(::renameClass).toMutableList() + val renamedInterfaces = interfaces.map(::renameInterface) + val renamedTopLevelFunctions = topLevelFunctions.map(::renameFunction) + val renamedSealedEnums = sealedEnums.map(::renameSealedEnum) + + val existingFqNames = renamedClasses.map { it.fqName }.toMutableSet() + for (opaque in renamedOpaqueClasses) { + if (opaque.fqName !in existingFqNames) { + existingFqNames.add(opaque.fqName) + renamedClasses.add(opaque) + } + } + + // Create synthetic wrapper classes for each trait used as `dyn Trait` + for (iface in renamedInterfaces) { + if (iface.simpleName !in dynTraitNames) continue + val dynName = "Dyn${iface.simpleName}" + val dynFqName = "${iface.fqName.substringBeforeLast('.')}.$dynName" + if (dynFqName in existingFqNames) continue + existingFqNames.add(dynFqName) + val dynClass = KneClass( + simpleName = dynName, + fqName = dynFqName, + constructor = KneConstructor(emptyList(), KneConstructorKind.NONE), + methods = iface.methods.map { m -> + m.copy(isOverride = true) + }, + properties = iface.properties.map { p -> + p.copy(isOverride = true) + }, + interfaces = listOf(iface.fqName), + isDynTrait = true, + rustTypeName = "Box", + ) + renamedClasses.add(dynClass) + } + + // Clean up lazy resolution state + currentIndex = null + currentPaths = null + lazyResolutionInProgress.clear() + + val pkg = crateName.replace('-', '.').replace('_', '.') + return KneModule( + libName = libName, + packages = setOf(pkg), + classes = renamedClasses, + interfaces = renamedInterfaces, + dataClasses = knownDataClasses.values.toList(), + enums = enums, + sealedEnums = renamedSealedEnums, + functions = renamedTopLevelFunctions, + traitImpls = traitToImplTypes, + ) + } + + private fun isGeneratedBridgeFunction(item: JsonObject): Boolean { + val span = item.getAsJsonObject("span") ?: return false + val filename = span.get("filename").safeString() ?: return false + return filename.endsWith("/kne_bridges.rs") || filename.endsWith("\\kne_bridges.rs") || filename == "kne_bridges.rs" + } + + private fun parseVariantFields( + kind: JsonElement?, + index: JsonObject, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + context: String = "unknown variant", + onUnsupported: (String) -> Unit = {}, + ): Pair, Boolean>? { + if (kind == null || kind.isJsonNull) return emptyList() to false + if (kind.isJsonPrimitive && kind.asString == "plain") return emptyList() to false + if (!kind.isJsonObject) { + onUnsupported("Skipped $context: unsupported variant kind") + return null + } + val kindObj = kind.asJsonObject + + if (kindObj.has("tuple")) { + val fieldIds = kindObj.getAsJsonArray("tuple") ?: return null + val fields = mutableListOf() + for ((i, fieldId) in fieldIds.withIndex()) { + val fieldItem = index.get(fieldId.asInt.toString())?.asJsonObject ?: return null + val fieldTypeJson = fieldItem.getAsJsonObject("inner")?.getAsJsonObject("struct_field") ?: return null + val resolved = resolveTypeWithBorrow(fieldTypeJson, knownStructs, knownEnums, knownDataClasses) ?: return null + val fieldName = if (fieldIds.size() == 1) "value" else "value$i" + fields.add(KneParam(fieldName, resolved.type, isBorrowed = resolved.isBorrowed, rustType = resolved.rustType)) + } + return fields to true + } + + if (kindObj.has("struct")) { + val structObj = kindObj.getAsJsonObject("struct") + val fieldIds = structObj.getAsJsonArray("fields") ?: return null + val fields = mutableListOf() + for (fieldId in fieldIds) { + val fieldItem = index.get(fieldId.asInt.toString())?.asJsonObject ?: return null + val fieldName = fieldItem.get("name").safeString() ?: return null + val fieldTypeJson = fieldItem.getAsJsonObject("inner")?.getAsJsonObject("struct_field") ?: return null + val resolved = resolveTypeWithBorrow(fieldTypeJson, knownStructs, knownEnums, knownDataClasses) ?: return null + fields.add(KneParam(fieldName, resolved.type, isBorrowed = resolved.isBorrowed, rustType = resolved.rustType)) + } + return fields to false + } + + onUnsupported("Skipped $context: unsupported variant structure") + return null + } + + private fun resolveTypeId(typeObj: JsonObject): Int? { + if (typeObj.has("resolved_path")) { + val idElem = typeObj.getAsJsonObject("resolved_path").get("id") + if (idElem == null || idElem.isJsonNull) return null + return idElem.asInt + } + return null + } + + private fun hasSelfParam(inputs: JsonArray): Boolean { + if (inputs.size() == 0) return false + val firstParam = inputs[0].asJsonArray + return firstParam[0].asString == "self" + } + + /** Merges impl-level generics with function-level generics (params + where_predicates). */ + private fun mergeGenerics(implGenerics: JsonObject?, fnGenerics: JsonObject?): JsonObject? { + if (implGenerics == null) return fnGenerics + if (fnGenerics == null) return implGenerics + val merged = JsonObject() + val mergedParams = JsonArray() + implGenerics.getAsJsonArray("params")?.forEach { mergedParams.add(it) } + fnGenerics.getAsJsonArray("params")?.forEach { mergedParams.add(it) } + merged.add("params", mergedParams) + val mergedWhere = JsonArray() + implGenerics.getAsJsonArray("where_predicates")?.forEach { mergedWhere.add(it) } + fnGenerics.getAsJsonArray("where_predicates")?.forEach { mergedWhere.add(it) } + merged.add("where_predicates", mergedWhere) + return merged + } + + private fun classifyReceiverKind(inputs: JsonArray): KneReceiverKind { + if (!hasSelfParam(inputs)) return KneReceiverKind.NONE + val typeObj = inputs[0].asJsonArray[1].asJsonObject + if (!typeObj.has("borrowed_ref")) return KneReceiverKind.OWNED + val borrowed = typeObj.getAsJsonObject("borrowed_ref") + val isMutable = borrowed.get("is_mutable")?.takeIf { !it.isJsonNull }?.asBoolean == true + return if (isMutable) KneReceiverKind.BORROWED_MUT else KneReceiverKind.BORROWED_SHARED + } + + private fun resolveGenericMappings( + generics: JsonObject?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + selfType: KneType? = null, + ): GenericResolution { + if (generics == null) return GenericResolution(emptyMap(), emptyMap()) + val resolved = mutableMapOf() + val unresolvedBounds = mutableMapOf>() + + val params = generics.getAsJsonArray("params") ?: JsonArray() + for (param in params) { + val paramObj = param.asJsonObject + val name = paramObj.get("name").safeString() ?: continue + val kind = paramObj.getAsJsonObject("kind") ?: continue + if (kind.has("lifetime")) continue + val typeKind = kind.getAsJsonObject("type") ?: continue + // Skip synthetic generics created by `impl Trait` params (e.g., `impl Into`, `impl ToString`). + // These are desugared by rustdoc; the actual types are resolved via `impl_trait` in the signature. + if (typeKind.get("is_synthetic")?.asBoolean == true) continue + val result = resolveGenericMappingFromBoundsWithTracking(typeKind, knownStructs, knownEnums, knownDataClasses, resolved, selfType) + if (result.resolvedType != null) { + resolved[name] = result.resolvedType + } + if (result.unresolvedBounds.isNotEmpty()) { + unresolvedBounds.getOrPut(name) { mutableListOf() }.addAll(result.unresolvedBounds) + } + } + + val wherePredicates = generics.getAsJsonArray("where_predicates") ?: JsonArray() + for (predicate in wherePredicates) { + val boundPredicate = predicate.asJsonObject.getAsJsonObject("bound_predicate") ?: continue + val target = boundPredicate.getAsJsonObject("type") ?: continue + if (!target.has("generic")) continue + val name = target.get("generic").asString + if (name in resolved) continue + val pseudoTypeKind = JsonObject().apply { add("bounds", boundPredicate.getAsJsonArray("bounds") ?: JsonArray()) } + val result = resolveGenericMappingFromBoundsWithTracking(pseudoTypeKind, knownStructs, knownEnums, knownDataClasses, resolved, selfType) + if (result.resolvedType != null) { + resolved[name] = result.resolvedType + } + if (result.unresolvedBounds.isNotEmpty()) { + unresolvedBounds.getOrPut(name) { mutableListOf() }.addAll(result.unresolvedBounds) + } + } + + return GenericResolution(resolved, unresolvedBounds) + } + + private data class BoundResolution( + val resolvedType: ResolvedType?, + val unresolvedBounds: List, + ) + + private fun resolveGenericMappingFromBounds( + typeKind: JsonObject, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map, + genericTypes: Map, + selfType: KneType?, + ): ResolvedType? = resolveGenericMappingFromBoundsWithTracking(typeKind, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType).resolvedType + + private fun resolveGenericMappingFromBoundsWithTracking( + typeKind: JsonObject, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map, + genericTypes: Map, + selfType: KneType?, + ): BoundResolution { + val bounds = typeKind.getAsJsonArray("bounds") ?: return BoundResolution(null, emptyList()) + val unresolvedBounds = mutableListOf() + for (bound in bounds) { + val boundObj = bound.asJsonObject + val traitBound = boundObj.getAsJsonObject("trait_bound") ?: continue + val trait = traitBound.getAsJsonObject("trait") ?: continue + val traitPath = lastPathSegment(trait.get("path")?.asString ?: continue) + val traitArgs = trait.get("args") + + when (traitPath) { + "Fn", "FnMut", "FnOnce" -> { + val parenthesized = traitArgs?.asJsonObject?.getAsJsonObject("parenthesized") ?: continue + val inputs = parenthesized.getAsJsonArray("inputs") ?: continue + val paramTypes = inputs.mapNotNull { + resolveTypeWithBorrow(it, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType)?.type + } + if (paramTypes.size != inputs.size()) continue + val outputResolved = resolveTypeWithBorrow(parenthesized.get("output"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + return BoundResolution( + ResolvedType( + type = KneType.FUNCTION(paramTypes, outputResolved?.type ?: KneType.UNIT), + rustType = traitPath, + ), + emptyList(), + ) + } + + "AsRef", "Into", "From" -> { + val target = extractFirstGenericArg(traitArgs, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: continue + val targetSegment = lastPathSegment(target.rustType ?: renderRustType(target.type)) + if (traitPath == "AsRef" && (targetSegment == "str" || targetSegment == "Path" || targetSegment == "PathBuf")) { + val rustTarget = if (targetSegment == "Path" || targetSegment == "PathBuf") "Path" else "str" + return BoundResolution(ResolvedType(type = KneType.STRING, rustType = "AsRef<$rustTarget>"), emptyList()) + } + return BoundResolution(target.copy(rustType = "$traitPath<${target.rustType ?: renderRustType(target.type)}>"), emptyList()) + } + + else -> { + val traitFullPath = trait.get("path")?.asString ?: continue + val fqTraitName = traitFullPath.replace("::", ".") + unresolvedBounds.add(GenericBound(fqTraitName, traitPath)) + } + } + } + return if (unresolvedBounds.isNotEmpty()) BoundResolution(null, unresolvedBounds) else BoundResolution(null, emptyList()) + } + + private fun hasUnsupportedGenerics(generics: JsonObject?, genericResolution: GenericResolution): Boolean { + if (generics == null) return false + val resolved = genericResolution.resolvedTypes + val unresolved = genericResolution.unresolvedBounds + val params = generics.getAsJsonArray("params") ?: return false + for (param in params) { + val paramObj = param.asJsonObject + val name = paramObj.get("name").safeString() ?: continue + val kind = paramObj.getAsJsonObject("kind") ?: continue + when { + kind.has("lifetime") -> continue + kind.has("type") -> { + val typeKind = kind.getAsJsonObject("type") + if (typeKind?.get("is_synthetic")?.asBoolean == true) continue + if (name !in resolved && name !in unresolved) return true + } + else -> return true + } + } + return false + } + + private fun extractStructFields( + structItem: JsonObject, + index: JsonObject, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + ): List? { + val structData = structItem.getAsJsonObject("inner")?.getAsJsonObject("struct") ?: return null + val kindElem = structData.get("kind") ?: return null + if (!kindElem.isJsonObject) return null + val kind = kindElem.asJsonObject + if (!kind.has("plain")) return null + val fieldIds = kind.getAsJsonObject("plain").getAsJsonArray("fields") ?: return null + val params = mutableListOf() + for (fieldId in fieldIds) { + val fieldItem = index.get(fieldId.asInt.toString())?.asJsonObject ?: return null + val fieldVis = fieldItem.get("visibility").safeString() ?: return null + if (fieldVis != "public") return null + val fieldName = fieldItem.get("name").safeString() ?: return null + val fieldType = fieldItem.getAsJsonObject("inner")?.getAsJsonObject("struct_field") ?: return null + val resolved = resolveTypeWithBorrow(fieldType, knownStructs, knownEnums, knownDataClasses) ?: return null + params.add(KneParam(fieldName, resolved.type, isBorrowed = resolved.isBorrowed, rustType = resolved.rustType)) + } + return params + } + + private fun buildConstructor( + newFn: JsonObject?, + structItem: JsonObject, + index: JsonObject, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map, + selfType: KneType.OBJECT, + implGenericJson: JsonObject? = null, + onUnsupported: (String) -> Unit = {}, + ): KneConstructor { + if (newFn != null) { + val function = newFn.getAsJsonObject("inner")?.getAsJsonObject("function") ?: return KneConstructor(emptyList(), KneConstructorKind.NONE) + val mergedGen = mergeGenerics(implGenericJson, function.getAsJsonObject("generics")) + val generics = resolveGenericMappings(mergedGen, knownStructs, knownEnums, knownDataClasses, selfType) + if (hasUnsupportedGenerics(mergedGen, generics)) { + onUnsupported("Skipped constructor '${selfType.simpleName}::new': unsupported generic signature") + return KneConstructor(emptyList(), KneConstructorKind.NONE) + } + + // Allow unresolved generic bounds through so struct-level expansion can substitute them + val prevUnresolvedBounds = currentUnresolvedBounds + currentUnresolvedBounds = generics.unresolvedBounds + val sig = function.getAsJsonObject("sig") + val params = buildParams( + inputs = sig.getAsJsonArray("inputs"), + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + genericTypes = generics.resolvedTypes, + selfType = selfType, + context = "constructor ${selfType.simpleName}", + onUnsupported = onUnsupported, + ) + currentUnresolvedBounds = prevUnresolvedBounds + if (params != null) { + return KneConstructor(params = params, kind = KneConstructorKind.FUNCTION, canFail = isResultType(sig.get("output"))) + } + onUnsupported("Skipped constructor '${selfType.simpleName}::new': unsupported parameter type") + return KneConstructor(emptyList(), KneConstructorKind.NONE) + } + + val structData = structItem.getAsJsonObject("inner")?.getAsJsonObject("struct") ?: return KneConstructor(emptyList(), KneConstructorKind.NONE) + val kindElem = structData.get("kind") ?: return KneConstructor(emptyList(), KneConstructorKind.NONE) + if (!kindElem.isJsonObject) return KneConstructor(emptyList(), KneConstructorKind.NONE) + val kind = kindElem.asJsonObject + if (!kind.has("plain")) return KneConstructor(emptyList(), KneConstructorKind.NONE) + + val fieldIds = kind.getAsJsonObject("plain").getAsJsonArray("fields") ?: return KneConstructor(emptyList(), KneConstructorKind.NONE) + val params = mutableListOf() + for (fieldId in fieldIds) { + val fieldItem = index.get(fieldId.asInt.toString())?.asJsonObject ?: continue + val fieldVis = fieldItem.get("visibility").safeString() ?: continue + if (fieldVis != "public") continue + val fieldName = fieldItem.get("name").safeString() ?: continue + val fieldType = fieldItem.getAsJsonObject("inner")?.getAsJsonObject("struct_field") ?: continue + val resolved = resolveTypeWithBorrow(fieldType, knownStructs, knownEnums, knownDataClasses) ?: continue + params.add(KneParam(fieldName, resolved.type, isBorrowed = resolved.isBorrowed, rustType = resolved.rustType)) + } + return if (params.isNotEmpty()) KneConstructor(params, KneConstructorKind.STRUCT_LITERAL) else KneConstructor(emptyList(), KneConstructorKind.NONE) + } + + private fun buildMethod( + methodItem: JsonObject, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + receiverKind: KneReceiverKind = KneReceiverKind.NONE, + docs: String? = null, + ownerType: KneType? = null, + implGenericJson: JsonObject? = null, + onUnsupported: (String) -> Unit = {}, + ): List { + val name = methodItem.get("name").safeString() ?: return emptyList() + val inner = methodItem.getAsJsonObject("inner")?.getAsJsonObject("function") ?: return emptyList() + // Resolve function generics first; impl-level generics are handled separately + // via currentUnresolvedBounds (for type resolution) and struct-level expansion. + val fnGenerics = inner.getAsJsonObject("generics") + val mergedGen = mergeGenerics(implGenericJson, fnGenerics) + val genericResolution = resolveGenericMappings(mergedGen, knownStructs, knownEnums, knownDataClasses, ownerType) + if (hasUnsupportedGenerics(mergedGen, genericResolution)) { + onUnsupported("Skipped method '$name': unsupported generic signature") + return emptyList() + } + + currentUnresolvedBounds = genericResolution.unresolvedBounds + val sig = inner.getAsJsonObject("sig") + val inputs = sig.getAsJsonArray("inputs") + val params = buildParams( + inputs = inputs, + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + genericTypes = genericResolution.resolvedTypes, + selfType = ownerType, + skipSelf = hasSelfParam(inputs), + context = "method '$name'", + onUnsupported = onUnsupported, + ) ?: run { + currentUnresolvedBounds = emptyMap() + onUnsupported("Skipped method '$name': unsupported parameter type") + return emptyList() + } + val returnResolved = resolveReturnTypeOrUnit( + output = sig.get("output"), + knownStructs = knownStructs, + knownEnums = knownEnums, + knownDataClasses = knownDataClasses, + genericTypes = genericResolution.resolvedTypes, + selfType = ownerType, + ) + if (returnResolved == null) { + currentUnresolvedBounds = emptyMap() + onUnsupported("Skipped method '$name': unsupported return type") + return emptyList() + } + currentUnresolvedBounds = emptyMap() + val returnType = returnResolved.type + + val isSuspend = docs?.contains("@kne:suspend") == true + val flowMatch = docs?.let { Regex("@kne:flow\\((\\w+)\\)").find(it) } + val actualReturnType = if (flowMatch != null) { + when (flowMatch.groupValues[1]) { + "Int" -> KneType.FLOW(KneType.INT) + "Long" -> KneType.FLOW(KneType.LONG) + "Double" -> KneType.FLOW(KneType.DOUBLE) + "Float" -> KneType.FLOW(KneType.FLOAT) + "Boolean" -> KneType.FLOW(KneType.BOOLEAN) + "String" -> KneType.FLOW(KneType.STRING) + "Byte" -> KneType.FLOW(KneType.BYTE) + "Short" -> KneType.FLOW(KneType.SHORT) + else -> KneType.FLOW(KneType.INT) + } + } else { + returnType + } + + // Only expand method-level generic params (not impl-level ones — those are handled by struct expansion) + val fnOnlyBoundNames = resolveGenericMappings(fnGenerics, knownStructs, knownEnums, knownDataClasses, ownerType) + .unresolvedBounds.keys + val genericParams = genericResolution.unresolvedBounds + .filter { (paramName, _) -> paramName in fnOnlyBoundNames } + .map { (paramName, bounds) -> + val concreteTypes = if (bounds.size == 1) { + lookupTraitImpls(bounds[0].traitFqName) + } else { + bounds.map { bound -> lookupTraitImpls(bound.traitFqName).toSet() } + .reduceOrNull { acc, types -> acc.intersect(types) }?.toList() ?: emptyList() + } + GenericParamInfo(paramName, bounds, concreteTypes) + } + + return KneFunction( + name = name, + params = params, + returnType = actualReturnType, + isSuspend = isSuspend, + isMutating = receiverKind == KneReceiverKind.BORROWED_MUT, + receiverKind = receiverKind, + canFail = isResultType(sig.get("output")), + returnsBorrowed = returnResolved.isBorrowed, + returnRustType = returnResolved.rustType, + isUnsafe = inner.getAsJsonObject("header")?.get("is_unsafe")?.asBoolean == true, + isAsync = inner.getAsJsonObject("header")?.get("is_async")?.asBoolean == true || returnResolved.isFuture, + returnConversion = returnResolved.implTraitConversion, + genericParams = genericParams, + ).let { method -> + expandMethodWithGenerics(method, genericParams) + } + } + + /** Looks up trait implementors, trying both the raw fqName and the crate-prefixed form. */ + private fun lookupTraitImpls(traitFqName: String): List { + traitImpls[traitFqName]?.let { return it } + // Rustdoc may emit bare trait names (e.g. "ValueTransformer") while the registry + // uses crate-prefixed keys (e.g. "rustcalc.ValueTransformer"). Try all prefixes. + for (key in traitImpls.keys) { + if (key.endsWith(".$traitFqName")) return traitImpls[key]!! + } + return emptyList() + } + + private fun expandMethodWithGenerics(method: KneFunction, genericParams: List): List { + if (genericParams.isEmpty()) return listOf(method) + val unresolvedToConcrete = genericParams.associate { gp -> + "__unresolved_generic__${gp.paramName}" to gp.concreteTypes + } + val firstGenericParam = unresolvedToConcrete.keys.firstOrNull() ?: return listOf(method) + val concreteTypes = unresolvedToConcrete[firstGenericParam] ?: return listOf(method) + if (concreteTypes.isEmpty()) return emptyList() + val restGenericParams = genericParams.drop(1) + return concreteTypes.flatMap { concreteType -> + val substitutedMethod = substituteUnresolvedGeneric(method, firstGenericParam.removePrefix("__unresolved_generic__"), concreteType) + if (restGenericParams.isEmpty()) { + listOf(substitutedMethod) + } else { + expandMethodWithGenerics(substitutedMethod, restGenericParams) + } + } + } + + private fun substituteUnresolvedGeneric(method: KneFunction, paramName: String, concreteType: KneType.OBJECT): KneFunction { + val concreteRustName = concreteType.simpleName + fun subType(type: KneType) = substituteGenericType(type, paramName, concreteType) + fun subParam(param: KneParam) = param.copy( + type = subType(param.type), + rustType = substituteRustType(param.rustType, paramName, concreteRustName), + ) + val suffix = "_${toSnakeCase(concreteRustName)}" + val originalName = method.rustMethodName ?: method.name + val existingTurbofish = method.turbofish + val newTurbofish = if (existingTurbofish != null) { + existingTurbofish.removeSuffix(">") + ", $concreteRustName>" + } else { + "::<$concreteRustName>" + } + return method.copy( + name = method.name + suffix, + params = method.params.map(::subParam), + returnType = subType(method.returnType), + returnRustType = substituteRustType(method.returnRustType, paramName, concreteRustName), + receiverType = method.receiverType?.let(::subType), + genericParams = emptyList(), + rustMethodName = originalName, + turbofish = newTurbofish, + ) + } + + private fun toSnakeCase(name: String): String = + name.replace(Regex("([a-z])([A-Z])"), "$1_$2") + .replace(Regex("([A-Z]+)([A-Z][a-z])"), "$1_$2") + .lowercase() + + /** Substitutes all occurrences of `__unresolved_generic__${paramName}` with [concreteType] in a type tree. */ + private fun substituteGenericType(type: KneType, paramName: String, concreteType: KneType.OBJECT): KneType = when (type) { + is KneType.OBJECT -> if (type.fqName == "__unresolved_generic__$paramName") concreteType else type + is KneType.NULLABLE -> type.copy(inner = substituteGenericType(type.inner, paramName, concreteType)) + is KneType.FUNCTION -> type.copy( + paramTypes = type.paramTypes.map { substituteGenericType(it, paramName, concreteType) }, + returnType = substituteGenericType(type.returnType, paramName, concreteType), + ) + is KneType.DATA_CLASS -> type.copy(fields = type.fields.map { it.copy(type = substituteGenericType(it.type, paramName, concreteType)) }) + is KneType.LIST -> type.copy(elementType = substituteGenericType(type.elementType, paramName, concreteType)) + is KneType.SET -> type.copy(elementType = substituteGenericType(type.elementType, paramName, concreteType)) + is KneType.MAP -> type.copy( + keyType = substituteGenericType(type.keyType, paramName, concreteType), + valueType = substituteGenericType(type.valueType, paramName, concreteType), + ) + is KneType.FLOW -> type.copy(elementType = substituteGenericType(type.elementType, paramName, concreteType)) + is KneType.TUPLE -> type.copy(elementTypes = type.elementTypes.map { substituteGenericType(it, paramName, concreteType) }) + else -> type + } + + /** Substitutes the generic param name in a rustType string (handles `&T`, `&mut T`, etc.). */ + private fun substituteRustType(rustType: String?, paramName: String, concreteRustName: String): String? = + rustType?.replace(Regex("\\b${Regex.escape(paramName)}\\b"), concreteRustName) + + /** Returns true if the struct has any lifetime parameters (e.g. `BufReader<'a>`). */ + private fun structHasLifetimeParams(structItem: JsonObject): Boolean { + val structData = structItem.getAsJsonObject("inner")?.getAsJsonObject("struct") ?: return false + val generics = structData.getAsJsonObject("generics") ?: return false + val params = generics.getAsJsonArray("params") ?: return false + return params.any { param -> + val kind = param.asJsonObject.getAsJsonObject("kind") + kind != null && kind.has("lifetime") + } + } + + /** Returns true if the struct has type parameters that are NOT resolved to concrete types. */ + private fun structHasTypeParams(structItem: JsonObject): Boolean { + val structData = structItem.getAsJsonObject("inner")?.getAsJsonObject("struct") ?: return false + val generics = structData.getAsJsonObject("generics") ?: return false + val params = generics.getAsJsonArray("params") ?: return false + return params.any { param -> + val kind = param.asJsonObject.getAsJsonObject("kind") + kind != null && kind.has("type") + } + } + + private fun extractStructGenerics(structItem: JsonObject): List { + val structData = structItem.getAsJsonObject("inner")?.getAsJsonObject("struct") ?: return emptyList() + val generics = structData.getAsJsonObject("generics") ?: return emptyList() + val unresolvedBounds = mutableMapOf>() + val params = generics.getAsJsonArray("params") ?: JsonArray() + for (param in params) { + val paramObj = param.asJsonObject + val name = paramObj.get("name").safeString() ?: continue + val kind = paramObj.getAsJsonObject("kind") ?: continue + if (kind.has("lifetime")) continue + if (!kind.has("type")) continue + val typeKind = kind.getAsJsonObject("type") ?: continue + val bounds = typeKind.getAsJsonArray("bounds") ?: JsonArray() + val paramBounds = mutableListOf() + for (bound in bounds) { + val boundObj = bound.asJsonObject + // Rustdoc JSON structure: bound → trait_bound → trait → path + val traitBound = boundObj.getAsJsonObject("trait_bound") ?: continue + val trait = traitBound.getAsJsonObject("trait") ?: continue + val traitFullPath = trait.get("path")?.asString ?: continue + val traitSimpleName = lastPathSegment(traitFullPath) + val fqTraitName = traitFullPath.replace("::", ".") + paramBounds.add(GenericBound(fqTraitName, traitSimpleName)) + } + if (paramBounds.isNotEmpty()) { + unresolvedBounds.getOrPut(name) { mutableListOf() }.addAll(paramBounds) + } + } + return unresolvedBounds.map { (paramName, bounds) -> + val concreteTypes = if (bounds.size == 1) { + lookupTraitImpls(bounds[0].traitFqName) + } else { + bounds.map { bound -> lookupTraitImpls(bound.traitFqName).toSet() } + .reduceOrNull { acc, types -> acc.intersect(types) }?.toList() ?: emptyList() + } + GenericParamInfo(paramName, bounds, concreteTypes) + } + } + + private fun expandClassWithGenerics(klass: KneClass, structItem: JsonObject, crateName: String): List { + val genericParams = extractStructGenerics(structItem) + if (genericParams.isEmpty()) return listOf(klass) + return expandClassGenericParams(klass, genericParams, crateName) + } + + private fun expandClassGenericParams(klass: KneClass, remainingParams: List, crateName: String): List { + if (remainingParams.isEmpty()) return listOf(klass) + val firstParam = remainingParams.first() + if (firstParam.concreteTypes.isEmpty()) return listOf(klass) + val restParams = remainingParams.drop(1) + return firstParam.concreteTypes.flatMap { concreteType -> + val substituted = substituteClassGeneric(klass, firstParam.paramName, concreteType, crateName) + expandClassGenericParams(substituted, restParams, crateName) + } + } + + private fun substituteClassGeneric(klass: KneClass, paramName: String, concreteType: KneType.OBJECT, crateName: String): KneClass { + val concreteRustName = concreteType.simpleName + val suffix = "_$concreteRustName" + val newSimpleName = klass.simpleName + suffix + val newFqName = "$crateName.${klass.simpleName}$suffix" + // The Rust type for the bridge: original struct name with turbofish + val newRustTypeName = "${klass.rustTypeName}<$concreteRustName>" + + fun subType(type: KneType) = substituteGenericType(type, paramName, concreteType) + fun subParam(param: KneParam) = param.copy( + type = subType(param.type), + rustType = substituteRustType(param.rustType, paramName, concreteRustName), + ) + fun subProp(property: KneProperty) = property.copy(type = subType(property.type)) + fun subFn(fn: KneFunction) = fn.copy( + params = fn.params.map(::subParam), + returnType = subType(fn.returnType), + returnRustType = substituteRustType(fn.returnRustType, paramName, concreteRustName), + receiverType = fn.receiverType?.let(::subType), + ) + // Filter out methods that were already monomorphized for THIS generic param + // (method-level expansion happened earlier in buildMethod) + val filteredMethods = klass.methods.filter { fn -> + fn.genericParams.none { gp -> gp.paramName == paramName } + } + return klass.copy( + simpleName = newSimpleName, + fqName = newFqName, + rustTypeName = newRustTypeName, + constructor = klass.constructor.copy(params = klass.constructor.params.map(::subParam)), + methods = filteredMethods.map(::subFn), + properties = klass.properties.map(::subProp), + companionMethods = klass.companionMethods.map(::subFn), + genericParams = emptyList(), + ) + } + + private fun buildParams( + inputs: JsonArray, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + skipSelf: Boolean = false, + context: String = "unknown item", + onUnsupported: (String) -> Unit = {}, + ): List? { + val params = mutableListOf() + val usedNames = mutableMapOf() + for (input in inputs) { + val arr = input.asJsonArray + var paramName = arr[0].asString + if (skipSelf && paramName == "self") continue + // Disambiguate duplicate or anonymous parameter names (e.g. Rust's `_`) + val count = usedNames.getOrDefault(paramName, 0) + usedNames[paramName] = count + 1 + if (paramName == "_") paramName = "_arg$count" + else if (count > 0) paramName = "${paramName}_$count" + val resolved = resolveTypeWithBorrow(arr[1], knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + if (resolved == null) { + onUnsupported("$context has unsupported param '$paramName'") + return null + } + params.add( + KneParam( + name = paramName, + type = resolved.type, + isBorrowed = resolved.isBorrowed, + rustType = resolved.rustType, + ) + ) + } + return params + } + + private fun resolveTypeWithBorrow( + typeJson: JsonElement?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + ): ResolvedType? { + if (typeJson == null || typeJson.isJsonNull) return null + val obj = typeJson.asJsonObject + + if (obj.has("borrowed_ref")) { + val ref = obj.getAsJsonObject("borrowed_ref") + val innerResolved = resolveType(ref.get("type"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: return null + val lifetime = ref.get("lifetime").safeString() + val isMutable = ref.get("is_mutable")?.takeIf { !it.isJsonNull }?.asBoolean == true + val refPrefix = if (isMutable) "&mut " else "&" + val rustType = innerResolved.rustType?.let { + if (lifetime != null) "$refPrefix$lifetime $it" else "$refPrefix$it" + } + return ResolvedType(type = innerResolved.type, isBorrowed = true, rustType = rustType) + } + + return resolveType(obj, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + } + + private fun resolveReturnTypeOrUnit( + output: JsonElement?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + ): ResolvedType? { + if (output == null || output.isJsonNull) { + return ResolvedType(type = KneType.UNIT, rustType = "()") + } + return resolveTypeWithBorrow(output, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + } + + private fun resolveType( + typeJson: JsonElement?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + ): ResolvedType? { + if (typeJson == null || typeJson.isJsonNull) return null + val obj = typeJson.asJsonObject + + if (obj.has("primitive")) { + val primitive = obj.get("primitive").asString + val type = when (primitive) { + "i32", "u32" -> KneType.INT + "i64", "u64", "usize", "isize" -> KneType.LONG + "f64" -> KneType.DOUBLE + "f32" -> KneType.FLOAT + "bool" -> KneType.BOOLEAN + "i8", "u8" -> KneType.BYTE + "i16", "u16" -> KneType.SHORT + "str" -> KneType.STRING + "!" -> KneType.NEVER + "never" -> KneType.NEVER + else -> null + } ?: return null + return ResolvedType(type = type, rustType = primitive) + } + + if (obj.has("borrowed_ref")) { + return resolveTypeWithBorrow(obj, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + } + + if (obj.has("resolved_path")) { + val rp = obj.getAsJsonObject("resolved_path") + val path = rp.get("path").asString + val pathSegment = lastPathSegment(path) + val id = rp.get("id")?.takeIf { !it.isJsonNull }?.asInt + val args = rp.get("args") + + // Handle Box — unwrap Box, resolve inner dyn_trait + if (pathSegment == "Box") { + val innerTypeObj = extractFirstGenericArgRaw(args) + if (innerTypeObj != null && innerTypeObj.has("dyn_trait")) { + val inner = resolveType(innerTypeObj, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + if (inner != null) { + return inner.copy( + rustType = "Box<${inner.rustType ?: renderRustType(inner.type)}>", + ) + } + } + // Non-dyn Box: fall through to normal handling (will be opaque) + } + + return when (pathSegment) { + "String" -> ResolvedType(KneType.STRING, rustType = pathSegment) + "PathBuf", "Path", "OsStr", "OsString" -> ResolvedType(KneType.STRING, rustType = path) + + // Cow<[u8]> → BYTE_ARRAY, Cow → STRING, others → unwrap inner + "Cow" -> { + val inner = extractFirstGenericArg(args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + inner?.copy(rustType = "Cow<${inner.rustType ?: renderRustType(inner.type)}>") + } + + "Result" -> extractFirstGenericArg(args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + + "Vec" -> { + val elem = extractFirstGenericArg(args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: return null + val rustType = "Vec<${elem.rustType ?: renderRustType(elem.type)}>" + if (elem.type == KneType.BYTE) ResolvedType(KneType.BYTE_ARRAY, rustType = rustType) + else ResolvedType(KneType.LIST(elem.type), rustType = rustType) + } + + "Option" -> { + val inner = extractFirstGenericArg(args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: return null + ResolvedType(KneType.NULLABLE(inner.type), rustType = "Option<${inner.rustType ?: renderRustType(inner.type)}>") + } + + "HashSet", "BTreeSet" -> { + val elem = extractFirstGenericArg(args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: return null + ResolvedType(KneType.SET(elem.type), rustType = "$pathSegment<${elem.rustType ?: renderRustType(elem.type)}>") + } + + "HashMap", "BTreeMap" -> { + val (keyType, valueType) = extractTwoGenericArgs(args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + ?: return null + ResolvedType( + KneType.MAP(keyType.type, valueType.type), + rustType = "$pathSegment<${keyType.rustType ?: renderRustType(keyType.type)}, ${valueType.rustType ?: renderRustType(valueType.type)}>", + ) + } + + else -> { + // Use a qualified Rust path as rustType only for standard library types + // (std::io::Error, std::time::Duration, etc.) to avoid ambiguity. + // For all other types, use the simple name — they are accessible via + // pub use re-exports in the wrapper lib.rs. + val basePath = if (id != null) lookupFullPath(id) ?: pathSegment + else pathSegment + // Render with generic args from the call site to preserve type params + // (e.g. PhysicalSize, PhysicalPosition) + val qualifiedRustType = renderResolvedPathType(basePath, args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + when { + id != null && knownEnums.containsKey(id) -> { + val name = knownEnums[id]!! + if (id in currentSealedEnumIds) { + ResolvedType(KneType.SEALED_ENUM("$currentCrateName.$name", name), rustType = qualifiedRustType) + } else { + ResolvedType(KneType.ENUM("$currentCrateName.$name", name), rustType = qualifiedRustType) + } + } + + id != null && knownDataClasses.containsKey(id) -> { + val dc = knownDataClasses[id]!! + ResolvedType(KneType.DATA_CLASS(dc.fqName, dc.simpleName, dc.fields), rustType = qualifiedRustType) + } + + id != null && knownStructs.containsKey(id) -> { + val name = knownStructs[id]!! + ResolvedType(KneType.OBJECT("$currentCrateName.$name", name), rustType = qualifiedRustType) + } + + else -> { + // Try lazy cross-crate resolution before falling back to opaque + if (id != null) { + when (val lazy = tryLazyResolve(id)) { + is LazyResolveResult.AsDataClass -> { + val dc = lazy.dc + return ResolvedType(KneType.DATA_CLASS(dc.fqName, dc.simpleName, dc.fields), rustType = qualifiedRustType) + } + is LazyResolveResult.AsEnum -> { + val fq = "$currentCrateName.${lazy.name}" + // Prefer the public path from the function signature (path) over + // the internal crate path from the paths table (lazy.fullPath), + // since the paths table may reference transitive dependencies + // (e.g. muda::items::normal::MenuItem vs tray_icon::menu::MenuItem). + val basePath = if (path.contains("::")) path + else if (lazy.fullPath != null && lazy.fullPath.contains("::")) lazy.fullPath + else qualifiedRustType + val lazyRustType = renderResolvedPathType(basePath, args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + return if (lazy.isSealed) ResolvedType(KneType.SEALED_ENUM(fq, lazy.name), rustType = lazyRustType) + else ResolvedType(KneType.ENUM(fq, lazy.name), rustType = lazyRustType) + } + is LazyResolveResult.AsStruct -> { + val fq = "$currentCrateName.${lazy.name}" + // Prefer the public path from the function signature (path) over + // the internal crate path from the paths table (lazy.fullPath). + val basePath = if (path.contains("::")) path + else if (lazy.fullPath != null && lazy.fullPath.contains("::")) lazy.fullPath + else qualifiedRustType + // Render with generic args from the call site (e.g. PhysicalSize) + val lazyRustType = renderResolvedPathType(basePath, args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + // Only create an opaque proxy class for types not already + // in knownStructs (to avoid shadowing richer versions from sub-crates) + recordOpaqueClass(lazy.name, fq, lazyRustType) + return ResolvedType(KneType.OBJECT(fq, lazy.name), rustType = lazyRustType) + } + null -> { /* fall through to opaque */ } + } + } + val simpleName = pathSegment + val fqName = path.replace("::", ".") + val rustType = renderResolvedPathType(path, args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + recordOpaqueClass(simpleName, fqName, rustType) + ResolvedType(KneType.OBJECT(fqName, simpleName), rustType = rustType) + } + } + } + } + } + + if (obj.has("slice")) { + val elem = resolveType(obj.get("slice"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: return null + val rustType = "[${elem.rustType ?: renderRustType(elem.type)}]" + return if (elem.type == KneType.BYTE) ResolvedType(KneType.BYTE_ARRAY, rustType = rustType) + else ResolvedType(KneType.LIST(elem.type), rustType = rustType) + } + + // Handle raw pointers: *const *const c_char represents LIST (array of C strings) + if (obj.has("raw_pointer")) { + val ptrObj = obj.getAsJsonObject("raw_pointer") + val innerType = ptrObj.get("type") ?: return null + val innerObj = innerType.asJsonObject + + fun isCChar(typeObj: JsonObject): Boolean { + // Check if primitive c_char + if (typeObj.has("primitive") && typeObj.get("primitive").asString == "c_char") return true + // Check if resolved_path to c_char or ffi::c_char + if (typeObj.has("resolved_path")) { + val rp = typeObj.getAsJsonObject("resolved_path") + val path = rp.get("path").asString + return path == "c_char" || path.endsWith("::c_char") + } + return false + } + + // Check for double pointer: *const *const c_char + if (innerObj.has("raw_pointer")) { + val innerPtrObj = innerObj.getAsJsonObject("raw_pointer") + val innermostType = innerPtrObj.get("type") + if (innermostType != null && isCChar(innermostType.asJsonObject)) { + return ResolvedType(KneType.LIST(KneType.STRING), rustType = "*const *const c_char") + } + } + // Single pointer to c_char could be treated as STRING + if (isCChar(innerObj)) { + return ResolvedType(KneType.STRING, rustType = "*const c_char") + } + // For other raw pointers, return null (unsupported) + return null + } + + if (obj.has("tuple")) { + val elems = obj.getAsJsonArray("tuple") + if (elems.size() == 0) return ResolvedType(KneType.UNIT, rustType = "()") + val elementTypes = mutableListOf() + val rustTypeParts = mutableListOf() + for (elem in elems) { + val resolved = resolveType(elem, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: return null + elementTypes.add(resolved.type) + rustTypeParts.add(resolved.rustType ?: renderRustType(resolved.type)) + } + val rustType = rustTypeParts.joinToString(", ", "(", ")") + return ResolvedType(KneType.TUPLE(elementTypes), rustType = rustType) + } + + if (obj.has("function_pointer")) { + val fp = obj.getAsJsonObject("function_pointer") + val sig = fp.getAsJsonObject("sig") + val inputs = sig.getAsJsonArray("inputs") + val paramTypes = mutableListOf() + val paramRustTypes = mutableListOf() + for (input in inputs) { + val arr = input.asJsonArray + val paramType = resolveTypeWithBorrow(arr[1], knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: return null + paramTypes.add(paramType.type) + paramRustTypes.add(paramType.rustType ?: renderRustType(paramType.type)) + } + val output = resolveTypeWithBorrow(sig.get("output"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + val outputRustType = output?.rustType ?: renderRustType(output?.type ?: KneType.UNIT) + val fullRustType = "fn(${paramRustTypes.joinToString(", ")}) -> $outputRustType" + return ResolvedType(KneType.FUNCTION(paramTypes, output?.type ?: KneType.UNIT), rustType = fullRustType) + } + + if (obj.has("dyn_trait")) { + val traits = obj.getAsJsonObject("dyn_trait").getAsJsonArray("traits") ?: return null + for (traitEntry in traits) { + val traitObj = traitEntry.asJsonObject.getAsJsonObject("trait") ?: continue + val path = lastPathSegment(traitObj.get("path")?.asString ?: continue) + if (path !in listOf("Fn", "FnMut", "FnOnce")) continue + val args = traitObj.getAsJsonObject("args") ?: continue + val parenthesized = args.getAsJsonObject("parenthesized") ?: continue + val inputs = parenthesized.getAsJsonArray("inputs") ?: continue + val paramTypes = inputs.mapNotNull { + resolveTypeWithBorrow(it, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType)?.type + } + if (paramTypes.size != inputs.size()) return null + val output = resolveTypeWithBorrow(parenthesized.get("output"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + return ResolvedType(KneType.FUNCTION(paramTypes, output?.type ?: KneType.UNIT), rustType = path) + } + // Check for known user-defined traits (dyn Trait → INTERFACE) + for (traitEntry in traits) { + val traitObj = traitEntry.asJsonObject.getAsJsonObject("trait") ?: continue + val traitId = traitObj.get("id")?.takeIf { !it.isJsonNull }?.asInt + val traitPath = traitObj.get("path")?.asString ?: continue + val traitName = if (traitId != null && currentKnownTraits.containsKey(traitId)) { + currentKnownTraits[traitId]!! + } else { + val seg = lastPathSegment(traitPath) + if (currentKnownTraits.values.contains(seg)) seg else null + } + if (traitName != null) { + dynTraitNames.add(traitName) + val fqName = "$currentCrateName.$traitName" + return ResolvedType(KneType.INTERFACE(fqName, traitName), rustType = "dyn $traitName") + } + // Fallback: check traitImpls for external traits (e.g., Box) + // If a concrete type implements this trait, resolve as that concrete type instead + val traitSimpleName = lastPathSegment(traitPath) + val implementors = traitImpls.entries + .filter { (key, _) -> lastPathSegment(key.replace("::", ".")) == traitSimpleName } + .flatMap { it.value } + .distinct() + if (implementors.size == 1) { + return ResolvedType(implementors.first(), rustType = "dyn $traitSimpleName") + } + } + return null + } + + if (obj.has("impl_trait")) { + return resolveImplTrait( + obj.getAsJsonArray("impl_trait"), + knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) + } + + if (obj.has("generic")) { + val name = obj.get("generic").asString + if (name == "Self" && selfType != null) { + return ResolvedType(selfType, rustType = renderRustType(selfType)) + } + if (name in genericTypes) { + return genericTypes[name] + } + if (name in currentUnresolvedBounds) { + return ResolvedType( + type = KneType.OBJECT("__unresolved_generic__$name", name), + rustType = name, + ) + } + return null + } + + return null + } + + /** + * Resolves `impl Trait` return types by mapping well-known traits to concrete KneTypes. + * The returned [ResolvedType.implTraitConversion] carries the Rust expression suffix + * the bridge generator must append to materialise the value (e.g. `.collect::>()`). + */ + private fun resolveImplTrait( + bounds: JsonArray, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map, + genericTypes: Map, + selfType: KneType?, + ): ResolvedType? { + for (bound in bounds) { + val boundObj = bound.asJsonObject + if (!boundObj.has("trait_bound")) continue + val traitBound = boundObj.getAsJsonObject("trait_bound") + val traitObj = traitBound.getAsJsonObject("trait") ?: continue + val path = traitObj.get("path")?.asString ?: continue + val traitName = lastPathSegment(path) + val args = traitObj.get("args") + + when (traitName) { + "Iterator", "ExactSizeIterator", "DoubleEndedIterator" -> { + val itemType = extractAssociatedTypeBinding( + args, "Item", knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) ?: return null + val rustType = "Vec<${itemType.rustType ?: renderRustType(itemType.type)}>" + val kneType = if (itemType.type == KneType.BYTE) KneType.BYTE_ARRAY else KneType.LIST(itemType.type) + return ResolvedType(kneType, rustType = rustType, implTraitConversion = ".collect::>()") + } + + "IntoIterator" -> { + val itemType = extractAssociatedTypeBinding( + args, "Item", knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) ?: return null + val rustType = "Vec<${itemType.rustType ?: renderRustType(itemType.type)}>" + val kneType = if (itemType.type == KneType.BYTE) KneType.BYTE_ARRAY else KneType.LIST(itemType.type) + return ResolvedType(kneType, rustType = rustType, implTraitConversion = ".into_iter().collect::>()") + } + + "Future" -> { + val outputType = extractAssociatedTypeBinding( + args, "Output", knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) ?: return null + return ResolvedType( + outputType.type, + isBorrowed = outputType.isBorrowed, + rustType = outputType.rustType, + implTraitConversion = outputType.implTraitConversion, + isFuture = true, + ) + } + + "Display", "ToString" -> { + return ResolvedType(KneType.STRING, rustType = "String", implTraitConversion = ".to_string()") + } + + "AsRef" -> { + val innerType = extractFirstGenericArg( + args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) + if (innerType?.type == KneType.STRING) { + return ResolvedType(KneType.STRING, rustType = "String", implTraitConversion = ".as_ref().to_string()") + } + return null + } + + "Into" -> { + val innerType = extractFirstGenericArg( + args, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) ?: return null + return ResolvedType(innerType.type, rustType = innerType.rustType, implTraitConversion = ".into()") + } + + "Fn", "FnMut", "FnOnce" -> { + val parenthesized = args?.asJsonObject?.getAsJsonObject("parenthesized") ?: continue + val inputs = parenthesized.getAsJsonArray("inputs") ?: continue + val paramTypes = inputs.mapNotNull { + resolveTypeWithBorrow(it, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType)?.type + } + if (paramTypes.size != inputs.size()) continue + val outputResolved = resolveTypeWithBorrow( + parenthesized.get("output"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) + val fnRustType = "impl $traitName(${paramTypes.joinToString(", ") { renderRustType(it) }})" + return ResolvedType( + type = KneType.FUNCTION(paramTypes, outputResolved?.type ?: KneType.UNIT), + rustType = fnRustType, + ) + } + } + + // Check for known crate-local traits: impl Trait → bridge as Box + val traitId = traitObj.get("id")?.takeIf { !it.isJsonNull }?.asInt + val knownTraitName = if (traitId != null && currentKnownTraits.containsKey(traitId)) { + currentKnownTraits[traitId]!! + } else if (currentKnownTraits.values.contains(traitName)) { + traitName + } else { + null + } + if (knownTraitName != null) { + dynTraitNames.add(knownTraitName) + val fqName = "$currentCrateName.$knownTraitName" + return ResolvedType( + KneType.INTERFACE(fqName, knownTraitName), + rustType = "impl dyn $knownTraitName", + ) + } + } + return null + } + + /** + * Extracts an associated type binding from trait args (e.g. `Item = i32` in `Iterator`). + * Looks for `angle_bracketed.bindings[].name == bindingName` and resolves the equality type. + */ + private fun extractAssociatedTypeBinding( + args: JsonElement?, + bindingName: String, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map, + genericTypes: Map, + selfType: KneType?, + ): ResolvedType? { + if (args == null || args.isJsonNull) return null + val ab = args.asJsonObject.getAsJsonObject("angle_bracketed") ?: return null + // rustdoc JSON uses "constraints" (newer) or "bindings" (older) for associated types + val bindings = ab.getAsJsonArray("constraints") ?: ab.getAsJsonArray("bindings") ?: return null + for (binding in bindings) { + val bindingObj = binding.asJsonObject + val name = bindingObj.get("name")?.asString ?: continue + if (name != bindingName) continue + val bindingKind = bindingObj.getAsJsonObject("binding") ?: continue + val equalityType = bindingKind.get("equality") ?: continue + if (equalityType.isJsonObject && equalityType.asJsonObject.has("type")) { + return resolveTypeWithBorrow( + equalityType.asJsonObject.get("type"), + knownStructs, knownEnums, knownDataClasses, genericTypes, selfType, + ) + } + } + return null + } + + private fun extractFirstGenericArg( + args: JsonElement?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + ): ResolvedType? { + if (args == null || args.isJsonNull) return null + val ab = args.asJsonObject.getAsJsonObject("angle_bracketed") ?: return null + val argsList = ab.getAsJsonArray("args") ?: return null + if (argsList.size() == 0) return null + val firstArg = argsList[0] + if (!firstArg.isJsonObject) return null + if (firstArg.asJsonObject.has("type")) { + return resolveTypeWithBorrow(firstArg.asJsonObject.get("type"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + } + return null + } + + /** Returns the raw JSON type object of the first generic arg without resolving it. */ + private fun extractFirstGenericArgRaw(args: JsonElement?): JsonObject? { + if (args == null || args.isJsonNull) return null + val ab = args.asJsonObject.getAsJsonObject("angle_bracketed") ?: return null + val argsList = ab.getAsJsonArray("args") ?: return null + if (argsList.size() == 0) return null + val firstArg = argsList[0] + if (!firstArg.isJsonObject) return null + return firstArg.asJsonObject.getAsJsonObject("type") + } + + private fun extractTwoGenericArgs( + args: JsonElement?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + ): Pair? { + if (args == null || args.isJsonNull) return null + val ab = args.asJsonObject.getAsJsonObject("angle_bracketed") ?: return null + val argsList = ab.getAsJsonArray("args") ?: return null + if (argsList.size() < 2) return null + val first = argsList[0] + val second = argsList[1] + if (!first.isJsonObject || !second.isJsonObject) return null + val keyType = if (first.asJsonObject.has("type")) resolveTypeWithBorrow(first.asJsonObject.get("type"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) else null + val valueType = if (second.asJsonObject.has("type")) resolveTypeWithBorrow(second.asJsonObject.get("type"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) else null + if (keyType == null || valueType == null) return null + return keyType to valueType + } + + private fun isResultType(typeJson: JsonElement?): Boolean { + if (typeJson == null || typeJson.isJsonNull) return false + val obj = typeJson.asJsonObject + if (obj.has("borrowed_ref")) { + return isResultType(obj.getAsJsonObject("borrowed_ref").get("type")) + } + val resolvedPath = obj.getAsJsonObject("resolved_path") ?: return false + return lastPathSegment(resolvedPath.get("path")?.asString ?: return false) == "Result" + } + + private fun extractProperties(methods: List): Pair, List> { + val getters = mutableMapOf() + val setters = mutableMapOf() + + for (fn in methods) { + if (fn.name.startsWith("get_") && fn.params.isEmpty()) { + val propName = fn.name.removePrefix("get_") + if (isSimplePropertyType(fn.returnType)) getters[propName] = fn + } else if (fn.name.startsWith("set_") && fn.params.size == 1 && fn.returnType == KneType.UNIT) { + setters[fn.name.removePrefix("set_")] = fn + } + } + + val properties = mutableListOf() + val consumedMethods = mutableSetOf() + for ((propName, getter) in getters) { + val setter = setters[propName] + val mutable = setter != null && setter.params[0].type == getter.returnType + properties.add(KneProperty(propName, getter.returnType, mutable)) + consumedMethods.add(getter.name) + if (mutable) consumedMethods.add(setter!!.name) + } + + return methods.filter { it.name !in consumedMethods } to properties + } + + private fun isSimplePropertyType(type: KneType): Boolean = when (type) { + KneType.INT, KneType.LONG, KneType.DOUBLE, KneType.FLOAT, + KneType.BOOLEAN, KneType.BYTE, KneType.SHORT, KneType.STRING -> true + is KneType.ENUM -> true + is KneType.DATA_CLASS -> true + is KneType.MAP -> true + else -> false + } + + private fun isDataClassFieldSupported(type: KneType): Boolean = when (type) { + KneType.INT, KneType.LONG, KneType.DOUBLE, KneType.FLOAT, + KneType.BOOLEAN, KneType.BYTE, KneType.SHORT, KneType.STRING, + KneType.UNIT, KneType.BYTE_ARRAY -> true + is KneType.ENUM -> true + is KneType.NULLABLE -> isDataClassFieldSupported(type.inner) + is KneType.LIST -> isDataClassFieldSupported(type.elementType) + is KneType.SET -> isDataClassFieldSupported(type.elementType) + is KneType.MAP -> isDataClassFieldSupported(type.keyType) && isDataClassFieldSupported(type.valueType) + is KneType.DATA_CLASS -> type.fields.all { isDataClassFieldSupported(it.type) } + else -> false + } + + private fun typeDisplayName(type: KneType?): String = when (type) { + null -> "unknown" + is KneType.OBJECT -> type.fqName + is KneType.INTERFACE -> type.fqName + is KneType.ENUM -> type.fqName + is KneType.SEALED_ENUM -> type.fqName + else -> type.toString() + } + + // -- Lazy cross-crate type resolution -- + + private sealed class LazyResolveResult { + data class AsDataClass(val dc: KneDataClass) : LazyResolveResult() + data class AsStruct(val name: String, val fullPath: String? = null) : LazyResolveResult() + data class AsEnum(val name: String, val isSealed: Boolean, val fullPath: String? = null) : LazyResolveResult() + } + + /** + * Attempts to lazily discover a type from the rustdoc JSON index when it's not in the + * known maps. This handles cross-crate re-exported types whose full definition is + * available in the index but wasn't traversed during the initial root-exported scan. + */ + private fun tryLazyResolve(id: Int): LazyResolveResult? { + val index = currentIndex ?: return null + if (id in lazyResolutionInProgress) return null + lazyResolutionInProgress.add(id) + try { + val item = index.get(id.toString())?.asJsonObject + if (item == null) { + // Item not in index — check paths for basic type info (kind: enum/struct) + return tryLazyResolveFromPaths(id) + } + val name = item.get("name").safeString() ?: return null + val inner = item.getAsJsonObject("inner") ?: return null + + when { + inner.has("struct") -> { + // Register in knownStructs before field resolution (breaks cycles) + currentKnownStructs[id] = name + + val fields = extractStructFields(item, index, currentKnownStructs, currentKnownEnums, currentKnownDataClasses) + if (fields != null && fields.isNotEmpty() && fields.all { isDataClassFieldSupported(it.type) }) { + val dc = KneDataClass( + simpleName = name, + fqName = "$currentCrateName.$name", + fields = fields, + ) + currentKnownDataClasses[id] = dc + return LazyResolveResult.AsDataClass(dc) + } + return LazyResolveResult.AsStruct(name) + } + inner.has("enum") -> { + currentKnownEnums[id] = name + val enumData = inner.getAsJsonObject("enum") + val variantIds = enumData?.getAsJsonArray("variants") + val isSealed = variantIds?.any { vid -> + val vi = index.get(vid.asInt.toString())?.asJsonObject + val vInner = vi?.getAsJsonObject("inner")?.getAsJsonObject("variant") + val kind = vInner?.get("kind") + kind != null && kind.isJsonObject + } ?: false + if (isSealed) { + currentSealedEnumIds = currentSealedEnumIds + id + } + return LazyResolveResult.AsEnum(name, isSealed) + } + else -> return null + } + } finally { + lazyResolutionInProgress.remove(id) + } + } + + /** + * Fallback when type ID is not in the index: use the `paths` field to determine + * if it's an enum or struct. This provides basic type identity (enum ordinal vs + * opaque handle) even without the full definition. + */ + private fun tryLazyResolveFromPaths(id: Int): LazyResolveResult? { + val paths = currentPaths ?: return null + val pathEntry = paths.get(id.toString())?.asJsonObject ?: return null + val kind = pathEntry.get("kind")?.asString ?: return null + val pathSegments = pathEntry.getAsJsonArray("path") ?: return null + val name = pathSegments.last().asString + + val rawFullPath = pathSegments.map { it.asString }.joinToString("::") + // For std/core library paths, the paths table may include private modules + // (e.g. std::io::error::Error). Collapse to the public form (std::io::Error). + val fullPath = canonicalizeStdPath(rawFullPath) + return when (kind) { + // Without the full definition in the index, we can't determine if an enum is + // unit-only (simple) or has data variants (sealed). Default to opaque struct + // which is always safe (passed as handle). The actual type from the dependency's + // JSON will override this during module merging. + "enum", "struct" -> { + currentKnownStructs[id] = name + LazyResolveResult.AsStruct(name, fullPath) + } + else -> null + } + } + + /** + * Look up the full Rust path for a type ID from the `paths` table. + * Returns null if not found or if the type is from the current crate (no qualification needed). + * The result is canonicalized for std/core types. + */ + private fun lookupFullPath(id: Int): String? { + val paths = currentPaths ?: return null + val pathEntry = paths.get(id.toString())?.asJsonObject ?: return null + // crate_id 0 = current crate, skip (no qualification needed for local types) + val crateId = pathEntry.get("crate_id")?.asInt ?: return null + if (crateId == 0) return null + val pathSegments = pathEntry.getAsJsonArray("path") ?: return null + val rawPath = pathSegments.map { it.asString }.joinToString("::") + // Only qualify types from standard library crates (std, core, alloc). + // Types from dependency crates (nokhwa_core, symphonia_core, cpal, etc.) + // are re-exported and accessible by their simple name via pub use. + val rootCrate = pathSegments.firstOrNull()?.asString ?: return null + if (rootCrate !in standardLibraryCrates) return null + return canonicalizeStdPath(rawPath) + } + + private val standardLibraryCrates = setOf("std", "core", "alloc") + + /** + * Canonicalize standard library paths from rustdoc's internal representation to the + * public API form. E.g. `std::io::error::Error` -> `std::io::Error`, + * `core::time::Duration` -> `std::time::Duration`. + */ + private fun canonicalizeStdPath(path: String): String { + val segments = path.split("::") + if (segments.size < 2) return path + val crate = segments[0] + if (crate != "std" && crate != "core" && crate != "alloc") return path + val typeName = segments.last() + // For core:: types, prefer std:: as the canonical import path + val prefix = if (crate == "core") "std" else crate + // Build canonical path: crate::module::TypeName (skip private sub-modules) + // Standard form is usually max 3 segments: std::module::Type + return if (segments.size <= 3) { + "$prefix::${segments.drop(1).joinToString("::")}" + } else { + // Collapse: std::io::error::Error -> std::io::Error + "$prefix::${segments[1]}::$typeName" + } + } + + private fun recordOpaqueClass(simpleName: String, fqName: String, rustTypeName: String): KneClass { + return encounteredOpaqueClasses.getOrPut(fqName) { + val uniqueSimpleName = uniqueOpaqueSimpleName(simpleName, fqName) + KneClass( + simpleName = uniqueSimpleName, + fqName = fqName, + rustTypeName = rustTypeName, + constructor = KneConstructor(emptyList(), KneConstructorKind.NONE), + methods = emptyList(), + properties = emptyList(), + isOpaque = true, + ) + } + } + + private fun uniqueOpaqueSimpleName(baseName: String, fqName: String): String { + val reserved = reservedTopLevelTypeNames + encounteredOpaqueClasses.values.map { it.simpleName } + return uniqueOpaqueSimpleName(baseName, fqName, reserved) + } + + private fun uniqueOpaqueSimpleName(baseName: String, fqName: String, reserved: Set): String { + if (baseName !in reserved) return baseName + + val segments = fqName.split('.', ':').filter { it.isNotBlank() } + for (prefixStart in (segments.size - 2) downTo 0) { + val prefix = segments.subList(prefixStart, segments.size - 1) + .joinToString("") { it.replaceFirstChar(Char::uppercaseChar) } + val candidate = prefix + baseName + if (candidate !in reserved) return candidate + } + val opaqueCandidate = "Opaque$baseName" + if (opaqueCandidate !in reserved) return opaqueCandidate + return "Opaque" + fqName.split('.', ':') + .filter { it.isNotBlank() } + .joinToString("") { it.replaceFirstChar(Char::uppercaseChar) } + } + + private fun renderResolvedPathType( + path: String, + args: JsonElement?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + ): String { + if (args == null || args.isJsonNull) return path + val angle = args.asJsonObject.getAsJsonObject("angle_bracketed") ?: return path + val renderedArgs = angle.getAsJsonArray("args")?.mapNotNull { arg -> + if (!arg.isJsonObject) return@mapNotNull null + val argObj = arg.asJsonObject + when { + argObj.has("lifetime") -> argObj.get("lifetime").safeString() + argObj.has("type") -> resolveTypeWithBorrow( + argObj.get("type"), + knownStructs, + knownEnums, + knownDataClasses, + genericTypes, + selfType, + )?.let { it.rustType ?: renderRustType(it.type) } + ?: renderRawType(argObj.get("type"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + else -> null + } + } ?: return path + if (renderedArgs.isEmpty()) return path + return "$path<${renderedArgs.joinToString(", ")}>" + } + + private fun renderRawType( + typeJson: JsonElement?, + knownStructs: Map, + knownEnums: Map, + knownDataClasses: Map = emptyMap(), + genericTypes: Map = emptyMap(), + selfType: KneType? = null, + ): String? { + if (typeJson == null || typeJson.isJsonNull) return null + val obj = typeJson.asJsonObject + + return when { + obj.has("primitive") -> obj.get("primitive").asString + + obj.has("borrowed_ref") -> { + val ref = obj.getAsJsonObject("borrowed_ref") + val lifetime = ref.get("lifetime").safeString()?.let { "$it " } ?: "" + renderRawType(ref.get("type"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + ?.let { "&$lifetime$it" } + } + + obj.has("resolved_path") -> { + val rp = obj.getAsJsonObject("resolved_path") + renderResolvedPathType( + rp.get("path").asString, + rp.get("args"), + knownStructs, + knownEnums, + knownDataClasses, + genericTypes, + selfType, + ) + } + + obj.has("dyn_trait") -> { + val dynTrait = obj.getAsJsonObject("dyn_trait") + val traitNames = dynTrait.getAsJsonArray("traits")?.mapNotNull { traitEntry -> + traitEntry.asJsonObject + .getAsJsonObject("trait") + ?.get("path") + ?.safeString() + } ?: emptyList() + if (traitNames.isEmpty()) null else { + val lifetime = dynTrait.get("lifetime").safeString()?.let { " + $it" } ?: "" + "dyn ${traitNames.joinToString(" + ")}$lifetime" + } + } + + obj.has("generic") -> { + val name = obj.get("generic").asString + when { + name == "Self" && selfType != null -> renderRustType(selfType) + name in genericTypes -> genericTypes[name]?.rustType ?: genericTypes[name]?.type?.let(::renderRustType) + else -> name + } + } + + obj.has("tuple") -> { + val elems = obj.getAsJsonArray("tuple") + if (elems.size() == 0) { + "()" + } else { + elems.joinToString(", ", "(", ")") { + renderRawType(it, knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) ?: "_" + } + } + } + + obj.has("slice") -> { + renderRawType(obj.get("slice"), knownStructs, knownEnums, knownDataClasses, genericTypes, selfType) + ?.let { "[$it]" } + } + + else -> null + } + } + + /** + * Collects the IDs of types (struct/enum/trait) directly accessible as bare names from the + * crate root, i.e. the set of IDs that would be in scope after `pub use crate_name::*`. + * + * Rules: + * - A public struct/enum/trait defined directly in the root module → its own ID. + * - A public non-glob `use` re-export with a resolved target ID → that target ID. + * - A public glob `use module::*` → all public struct/enum/trait IDs from that module + * (one level only; nested globs are not expanded). + */ + private fun buildRootExportedIds(rootItems: JsonArray, index: JsonObject): Set { + val result = mutableSetOf() + for (itemIdElem in rootItems) { + val itemId = itemIdElem.asInt + val item = index.get(itemId.toString())?.asJsonObject ?: continue + if (item.get("visibility").safeString() != "public") continue + val inner = item.getAsJsonObject("inner") ?: continue + when { + inner.has("struct") || inner.has("enum") || inner.has("trait") || inner.has("function") -> result.add(itemId) + inner.has("use") -> { + val useData = inner.getAsJsonObject("use") + val isGlob = useData.get("is_glob")?.asBoolean ?: false + val targetId = useData.get("id") + ?.takeIf { !it.isJsonNull } + ?.asInt + if (!isGlob && targetId != null) { + result.add(targetId) + } else if (isGlob && targetId != null) { + expandGlobModule(targetId, index, result) + } + } + // For crates whose root only has modules (e.g. symphonia_core with + // modules: io, audio, formats, ...), recursively expand each public + // module to discover the exported types. + inner.has("module") -> { + expandGlobModule(itemId, index, result) + } + } + } + return result + } + + private fun expandGlobModule(moduleId: Int, index: JsonObject, result: MutableSet, depth: Int = 0) { + if (depth > 5) return // Prevent infinite recursion + val moduleItem = index.get(moduleId.toString())?.asJsonObject ?: return + // Only follow modules that have an entry in the rustdoc paths table. + // Private/internal modules (like `bit` in symphonia_core::io::bit) have no + // path entry and their types are not accessible from outside the crate. + val hasPathEntry = currentPaths?.has(moduleId.toString()) == true + if (depth > 0 && !hasPathEntry) return + val moduleItems = moduleItem.getAsJsonObject("inner") + ?.getAsJsonObject("module") + ?.getAsJsonArray("items") ?: return + for (itemIdElem in moduleItems) { + val itemId = itemIdElem.asInt + val item = index.get(itemId.toString())?.asJsonObject ?: continue + if (item.get("visibility").safeString() != "public") continue + val inner = item.getAsJsonObject("inner") ?: continue + when { + inner.has("struct") || inner.has("enum") || inner.has("trait") || inner.has("function") -> result.add(itemId) + inner.has("use") -> { + val useData = inner.getAsJsonObject("use") + val isGlob = useData.get("is_glob")?.asBoolean ?: false + val targetId = useData.get("id") + ?.takeIf { !it.isJsonNull } + ?.asInt ?: continue + if (isGlob) { + expandGlobModule(targetId, index, result, depth + 1) + } else { + result.add(targetId) + } + } + inner.has("module") -> { + // Expand public sub-modules to find types declared within them + expandGlobModule(itemId, index, result, depth + 1) + } + } + } + } + + private fun lastPathSegment(path: String): String = path.substringAfterLast("::").substringAfterLast('.') + + private fun renderRustType(type: KneType): String = when (type) { + KneType.INT -> "i32" + KneType.LONG -> "i64" + KneType.DOUBLE -> "f64" + KneType.FLOAT -> "f32" + KneType.BOOLEAN -> "bool" + KneType.BYTE -> "i8" + KneType.SHORT -> "i16" + KneType.STRING -> "String" + KneType.UNIT -> "()" + KneType.NEVER -> "!" + is KneType.OBJECT -> type.simpleName + is KneType.INTERFACE -> type.simpleName + is KneType.ENUM -> type.simpleName + is KneType.SEALED_ENUM -> type.simpleName + is KneType.NULLABLE -> "Option<${renderRustType(type.inner)}>" + is KneType.FUNCTION -> "Fn" + is KneType.DATA_CLASS -> type.simpleName + KneType.BYTE_ARRAY -> "Vec" + is KneType.LIST -> "Vec<${renderRustType(type.elementType)}>" + is KneType.SET -> "HashSet<${renderRustType(type.elementType)}>" + is KneType.MAP -> "HashMap<${renderRustType(type.keyType)}, ${renderRustType(type.valueType)}>" + is KneType.FLOW -> "Flow" + is KneType.TUPLE -> "(${type.elementTypes.joinToString(", ") { renderRustType(it) }})" + } +} diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/FfmProxyGenerator.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/FfmProxyGenerator.kt index 204c6aba..0c8732be 100644 --- a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/FfmProxyGenerator.kt +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/FfmProxyGenerator.kt @@ -1,12 +1,15 @@ package io.github.kdroidfilter.nucleusnativeaccess.plugin.codegen import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneClass +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneConstructorKind import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneDataClass import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneEnum import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneFunction +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneSealedEnum import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneModule import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneParam import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneProperty +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneReceiverKind import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneType /** @@ -26,11 +29,100 @@ import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneType */ class FfmProxyGenerator { + /** Maps interface fqName → dyn wrapper class simpleName (e.g. "crate.Describable" → "DynDescribable"). */ + private var dynWrapperLookup: Map = emptyMap() + + /** Resolves the JVM type name for a param, using DynWrapper for INTERFACE types (which have .handle). */ + private fun paramJvmTypeName(type: KneType): String = if (type is KneType.INTERFACE) { + dynWrapperLookup[type.fqName] ?: type.jvmTypeName + } else { + type.jvmTypeName + } + + /** Set of all type names in the current module — used to skip methods referencing unknown types. */ + private var knownTypes: Set = emptySet() + private var enumTypeNames: Set = emptySet() + private var simpleEnumTypeNames: Set = emptySet() + + /** Returns true if the given type (and all nested types) are known in the module. */ + private fun isKnownType(type: KneType): Boolean = when (type) { + is KneType.OBJECT -> type.simpleName in knownTypes + is KneType.INTERFACE -> type.simpleName in knownTypes + is KneType.ENUM -> type.simpleName in knownTypes + is KneType.SEALED_ENUM -> type.simpleName in knownTypes + is KneType.DATA_CLASS -> type.simpleName in knownTypes + is KneType.NULLABLE -> isKnownType(type.inner) + is KneType.LIST -> isBridgeableCollectionElement(type.elementType) + is KneType.SET -> isBridgeableCollectionElement(type.elementType) + is KneType.MAP -> isBridgeableCollectionElement(type.keyType) && isBridgeableCollectionElement(type.valueType) + is KneType.TUPLE -> type.elementTypes.all { isKnownType(it) } + is KneType.FUNCTION -> type.paramTypes.all { isKnownType(it) } && isKnownType(type.returnType) + is KneType.FLOW -> isKnownType(type.elementType) + else -> true + } + + private fun isBridgeableCollectionElement(type: KneType): Boolean = when (type) { + KneType.INT, KneType.LONG, KneType.DOUBLE, KneType.FLOAT, + KneType.SHORT, KneType.BYTE, KneType.BOOLEAN, KneType.STRING, + KneType.BYTE_ARRAY -> true + is KneType.OBJECT -> type.simpleName in knownTypes + is KneType.INTERFACE -> type.simpleName in knownTypes + is KneType.SEALED_ENUM -> type.simpleName in knownTypes + is KneType.ENUM -> true + is KneType.LIST -> isBridgeableCollectionElement(type.elementType) + is KneType.SET -> isBridgeableCollectionElement(type.elementType) + else -> false + } + + private fun moduleHasNestedCollections(module: KneModule): Boolean { + fun typeHasNestedCollection(type: KneType): Boolean = when (type) { + is KneType.MAP -> type.valueType is KneType.LIST || type.valueType is KneType.SET + is KneType.LIST -> type.elementType is KneType.LIST || type.elementType is KneType.SET || type.elementType is KneType.MAP + is KneType.SET -> type.elementType is KneType.LIST || type.elementType is KneType.SET || type.elementType is KneType.MAP + is KneType.TUPLE -> type.elementTypes.any { typeHasNestedCollection(it) } + is KneType.NULLABLE -> typeHasNestedCollection(type.inner) + else -> false + } + fun fnHasNestedCollection(fn: KneFunction) = + typeHasNestedCollection(fn.returnType) || fn.params.any { typeHasNestedCollection(it.type) } + return module.functions.any { fnHasNestedCollection(it) } || + module.classes.any { cls -> + cls.methods.any { fnHasNestedCollection(it) } || + cls.companionMethods.any { fnHasNestedCollection(it) } + } + } + + /** Returns true if a function only references types known in the module + * AND no type has a representation mismatch (e.g., referenced as OBJECT but generated as ENUM). */ + private fun hasOnlyKnownTypes(fn: KneFunction): Boolean = + isKnownType(fn.returnType) && fn.params.all { isKnownType(it.type) } && + !hasTypeMismatch(fn) + + /** Detects when a param/return type is OBJECT but the module has it as an enum or sealed enum. */ + private fun hasTypeMismatch(fn: KneFunction): Boolean { + val allTypes = fn.params.map { it.type } + fn.returnType + return allTypes.any { type -> + type is KneType.OBJECT && type.simpleName in enumTypeNames + } + } + companion object { private const val STRING_BUF_SIZE = 8192 private const val ERR_BUF_SIZE = 8192 private const val MAX_COLLECTION_SIZE = 4096 + /** Kotlin hard keywords that must be escaped when used as identifiers. */ + private val KOTLIN_KEYWORDS = setOf( + "as", "break", "class", "continue", "do", "else", "false", "for", "fun", + "if", "in", "interface", "is", "null", "object", "package", "return", + "super", "this", "throw", "true", "try", "typealias", "typeof", "val", + "var", "when", "while", + ) + + /** Sanitizes a param name — appends '_' if it's a Kotlin keyword. */ + fun sanitizeParamName(name: String): String = + if (name in KOTLIN_KEYWORDS) "${name}_" else name + /** Map FFM layout constants to GraalVM reachability-metadata type names. */ private val LAYOUT_TO_GRAAL = mapOf( "JAVA_INT" to "int", @@ -243,12 +335,13 @@ class FfmProxyGenerator { sig.paramTypes.forEach { t -> when (t) { is KneType.DATA_CLASS -> t.fields.forEach { f -> flatLayouts.add(upcallLayout(f.type)) } + is KneType.TUPLE -> t.elementTypes.forEach { flatLayouts.add(upcallLayout(it)) } is KneType.LIST, is KneType.SET -> { flatLayouts.add("ADDRESS"); flatLayouts.add("JAVA_INT") } is KneType.MAP -> { flatLayouts.add("ADDRESS"); flatLayouts.add("ADDRESS"); flatLayouts.add("JAVA_INT") } else -> flatLayouts.add(upcallLayout(t)) } } - val ret = if (sig.returnType == KneType.UNIT) null else upcallLayout(sig.returnType) + val ret = if (sig.returnType == KneType.UNIT || sig.returnType == KneType.NEVER) null else upcallLayout(sig.returnType) reg(flatLayouts, ret) } @@ -263,7 +356,7 @@ class FfmProxyGenerator { this == KneType.STRING || (this is KneType.NULLABLE && inner == KneType.STRING) private fun KneType.isByteArrayType(): Boolean = - this == KneType.BYTE_ARRAY + this == KneType.BYTE_ARRAY || (this is KneType.NULLABLE && inner == KneType.BYTE_ARRAY) private fun KneType.isFunctionType(): Boolean = this is KneType.FUNCTION @@ -292,9 +385,12 @@ class FfmProxyGenerator { else -> null } + private fun paramIsFunction(p: KneParam): Boolean = + p.type is KneType.FUNCTION || (p.type is KneType.NULLABLE && (p.type as KneType.NULLABLE).inner is KneType.FUNCTION) + private fun classHasCallbacks(cls: KneClass): Boolean = - cls.methods.any { fn -> fn.params.any { it.type is KneType.FUNCTION } || fn.isSuspend || fn.returnType is KneType.FLOW } || - cls.companionMethods.any { fn -> fn.params.any { it.type is KneType.FUNCTION } || fn.isSuspend } + cls.methods.any { fn -> fn.params.any { paramIsFunction(it) } || fn.isSuspend || fn.returnType is KneType.FLOW } || + cls.companionMethods.any { fn -> fn.params.any { paramIsFunction(it) } || fn.isSuspend } /** * Generates all proxy files for the module. @@ -303,12 +399,35 @@ class FfmProxyGenerator { fun generate(module: KneModule, jvmPackage: String): Map { val files = mutableMapOf() + // Build set of all known type names in the module — used to filter out + // methods referencing external types not in the module (e.g. bytes::Bytes) + val knownTypeNames = buildSet { + module.classes.forEach { add(it.simpleName) } + module.dataClasses.forEach { add(it.simpleName) } + module.enums.forEach { add(it.simpleName) } + module.sealedEnums.forEach { add(it.simpleName) } + module.interfaces.forEach { add(it.simpleName) } + } + knownTypes = knownTypeNames + enumTypeNames = buildSet { + module.enums.forEach { add(it.simpleName) } + module.sealedEnums.forEach { add(it.simpleName) } + } + simpleEnumTypeNames = module.enums.map { it.simpleName }.toSet() + + // Build dyn wrapper lookup: interface fqName → Dyn wrapper class simpleName + dynWrapperLookup = module.classes + .filter { it.isDynTrait } + .flatMap { cls -> cls.interfaces.map { ifaceFq -> ifaceFq to cls.simpleName } } + .toMap() + // Collect all unique callback signatures used across the module val callbackSignatures = collectCallbackSignatures(module) val moduleSuspend = module.classes.any { cls -> cls.methods.any { it.isSuspend } } || module.functions.any { it.isSuspend } val moduleFlow = module.classes.any { cls -> cls.methods.any { it.returnType is KneType.FLOW } } || module.functions.any { it.returnType is KneType.FLOW } - files["KneRuntime.kt"] = generateRuntime(module.libName, jvmPackage, callbackSignatures, moduleSuspend || moduleFlow, moduleFlow) + val tupleTypes = collectTupleTypes(module) + files["KneRuntime.kt"] = generateRuntime(module.libName, jvmPackage, callbackSignatures, moduleSuspend || moduleFlow, moduleFlow, tupleTypes, moduleHasNestedCollections(module)) files["KotlinNativeException.kt"] = generateException(jvmPackage) module.dataClasses.filter { !it.isCommon }.forEach { dc -> @@ -316,17 +435,36 @@ class FfmProxyGenerator { } module.classes.filter { !it.isCommon }.forEach { cls -> - files["${cls.simpleName}.kt"] = generateClassProxy(cls, module, jvmPackage) + // Filter out methods/properties referencing external types not in the module + val ctorHasUnknownTypes = cls.constructor.params.any { + !isKnownType(it.type) + } + val filteredCls = cls.copy( + methods = cls.methods.filter { hasOnlyKnownTypes(it) }, + companionMethods = cls.companionMethods.filter { hasOnlyKnownTypes(it) }, + properties = cls.properties.filter { isKnownType(it.type) }, + constructor = if (ctorHasUnknownTypes) cls.constructor.copy(kind = KneConstructorKind.NONE) else cls.constructor, + ) + files["${filteredCls.simpleName}.kt"] = generateClassProxy(filteredCls, module, jvmPackage) } module.interfaces.filter { !it.isCommon }.forEach { iface -> - files["${iface.simpleName}.kt"] = generateInterfaceProxy(iface, module, jvmPackage) + // Filter out interface methods referencing unknown types + val filteredIface = iface.copy( + methods = iface.methods.filter { hasOnlyKnownTypes(it) }, + properties = iface.properties.filter { isKnownType(it.type) }, + ) + files["${filteredIface.simpleName}.kt"] = generateInterfaceProxy(filteredIface, module, jvmPackage) } module.enums.forEach { enum -> files["${enum.simpleName}.kt"] = generateEnumProxy(enum, module, jvmPackage) } + module.sealedEnums.forEach { sealed -> + files["${sealed.simpleName}.kt"] = generateSealedEnumProxy(sealed, module, jvmPackage) + } + // Non-extension top-level functions val regularFunctions = module.functions.filter { !it.isExtension } if (regularFunctions.isNotEmpty()) { @@ -340,9 +478,167 @@ class FfmProxyGenerator { files["Extensions.kt"] = generateExtensionsFile(extensionFunctions, module, jvmPackage) } + // Generate tuple data classes + tupleTypes.forEach { tuple -> + val sig = tuple.typeId + files["KneTuple${tuple.elementTypes.size}_$sig.kt"] = generateTupleClass(tuple, jvmPackage) + } + return files } + /** Collect all unique TUPLE types used in function returns and params. */ + private fun collectTupleTypes(module: KneModule): Set { + val tuples = mutableSetOf() + fun scanType(type: KneType) { + when (type) { + is KneType.TUPLE -> { + tuples.add(type) + type.elementTypes.forEach { scanType(it) } + } + is KneType.NULLABLE -> scanType(type.inner) + is KneType.LIST -> scanType(type.elementType) + is KneType.SET -> scanType(type.elementType) + is KneType.MAP -> { scanType(type.keyType); scanType(type.valueType) } + is KneType.FLOW -> scanType(type.elementType) + is KneType.FUNCTION -> { type.paramTypes.forEach { scanType(it) }; scanType(type.returnType) } + else -> {} + } + } + fun scanParams(params: List) { + params.forEach { scanType(it.type) } + } + module.classes.forEach { cls -> + cls.methods.forEach { fn -> scanType(fn.returnType); scanParams(fn.params) } + cls.companionMethods.forEach { fn -> scanType(fn.returnType); scanParams(fn.params) } + } + module.functions.forEach { fn -> scanType(fn.returnType); scanParams(fn.params) } + return tuples + } + + /** Compute the jvmTypeName for a type within a tuple context (handles nested tuples). */ + private fun tupleElementJvmTypeName(type: KneType): String = when (type) { + is KneType.TUPLE -> "KneTuple${type.elementTypes.size}_${type.typeId}" + else -> type.jvmTypeName + } + + /** Generate a KneTupleN data class for the given tuple type. */ + private fun generateTupleClass(tuple: KneType.TUPLE, pkg: String): String { + val sig = tuple.typeId + val fields = tuple.elementTypes.mapIndexed { idx, type -> + "val _$idx: ${tupleElementJvmTypeName(type)}" + } + return buildString { + appendLine("// Auto-generated by kotlin-native-export plugin. Do not modify.") + appendLine("package $pkg") + appendLine() + appendLine("data class KneTuple${tuple.elementTypes.size}_$sig(${fields.joinToString(", ")})") + } + } + + // ── Tuple reader generation ───────────────────────────────────────────── + + /** Default expression for a tuple element type (used when ptr is 0). */ + private fun tupleElementDefault(type: KneType): String = when (type) { + KneType.STRING -> "\"\"" + KneType.BOOLEAN -> "false" + KneType.BYTE -> "0" + KneType.SHORT -> "0" + KneType.INT -> "0" + KneType.LONG -> "0L" + KneType.FLOAT -> "0.0f" + KneType.DOUBLE -> "0.0" + is KneType.ENUM -> "${type.simpleName}.entries[0]" + is KneType.LIST, is KneType.SET -> "emptyList<${when (type) { is KneType.LIST -> (type as KneType.LIST).elementType; is KneType.SET -> (type as KneType.SET).elementType; else -> KneType.INT }.jvmTypeName}>()" + is KneType.MAP -> "emptyMap<${type.keyType.jvmTypeName}, ${type.valueType.jvmTypeName}>()" + is KneType.TUPLE -> { + val innerN = type.elementTypes.size + val innerSig = type.typeId + val innerDefaults = type.elementTypes.joinToString(", ") { tupleElementDefault(it) } + "KneTuple${innerN}_$innerSig($innerDefaults)" + } + else -> "null" + } + + /** + * Read expression for element at [idx] from a MemorySegment named [segVar]. + * Uses uniform 8-byte slots: element i is at byte offset i * 8. + */ + private fun tupleElementReadExpr(type: KneType, segVar: String, idx: Int): String { + val offset = idx * 8L + return when (type) { + KneType.STRING -> + "run { val _sp = $segVar.get(JAVA_LONG, $offset); if (_sp != 0L) { val _ss = MemorySegment.ofAddress(_sp).reinterpret(8192).getString(0); FREE_BUF_HANDLE.invoke(_sp, _ss.toByteArray(Charsets.UTF_8).size.toLong() + 1L); _ss } else \"\" }" + KneType.BOOLEAN -> "$segVar.get(JAVA_BYTE, $offset).toInt() != 0" + KneType.BYTE -> "$segVar.get(JAVA_BYTE, $offset)" + KneType.SHORT -> "$segVar.get(JAVA_SHORT, $offset)" + KneType.INT -> "$segVar.get(JAVA_INT, $offset)" + KneType.LONG -> "$segVar.get(JAVA_LONG, $offset)" + KneType.FLOAT -> "$segVar.get(JAVA_FLOAT, $offset)" + KneType.DOUBLE -> "$segVar.get(JAVA_DOUBLE, $offset)" + is KneType.ENUM -> "${type.simpleName}.entries[$segVar.get(JAVA_INT, $offset)]" + is KneType.OBJECT -> "${type.simpleName}.fromBorrowedHandle($segVar.get(JAVA_LONG, $offset))" + is KneType.TUPLE -> { + val innerN = type.elementTypes.size + val innerSig = type.typeId + "readTuple${innerN}_$innerSig($segVar.get(JAVA_LONG, $offset))" + } + is KneType.MAP -> { + // MAP in nested tuples is not fully supported - it returns a raw handle + // Proper deserialization requires arena context which readTuple functions don't have + "$segVar.get(JAVA_LONG, $offset)" + } + else -> "$segVar.get(JAVA_LONG, $offset)" + } + } + + /** + * Generate a readTupleN_SIG function for the given tuple type. + * After reading, frees the heap-allocated buffer via FREE_BUF_HANDLE. + */ + private fun StringBuilder.appendGeneratedReadTupleFunction(tuple: KneType.TUPLE) { + val n = tuple.elementTypes.size + val sig = tuple.typeId + val className = "KneTuple${n}_$sig" + val bufSize = n * 8L + + val defaults = tuple.elementTypes.joinToString(", ") { tupleElementDefault(it) } + appendLine(" private fun readTuple${n}_$sig(ptr: Long): $className {") + appendLine(" if (ptr == 0L) return $className($defaults)") + appendLine(" val seg = MemorySegment.ofAddress(ptr).reinterpret($bufSize)") + + val readExprs = tuple.elementTypes.mapIndexed { idx, type -> + when (type) { + is KneType.LIST, is KneType.SET, is KneType.MAP -> null // Handle specially below + else -> tupleElementReadExpr(type, "seg", idx) + } + } + // Read all elements + tuple.elementTypes.forEachIndexed { idx, type -> + when (type) { + is KneType.LIST, is KneType.SET -> { + val elemType = when (type) { is KneType.LIST -> type.elementType; is KneType.SET -> (type as KneType.SET).elementType; else -> KneType.INT } + val collType = if (type is KneType.SET) "Set" else "List" + appendLine(" val _e${idx}_ptr = seg.get(JAVA_LONG, ${idx * 8}L)") + val defaultVal = if (collType == "Set") "emptySet<${elemType.jvmTypeName}>()" else "emptyList<${elemType.jvmTypeName}>()" + appendLine(" val _e$idx: ${if (collType == "Set") "Set" else "List"}<${elemType.jvmTypeName}> = if (_e${idx}_ptr == 0L) $defaultVal else $defaultVal // TODO: LIST/SET in nested tuples not fully implemented") + } + is KneType.MAP -> { + val mapType = type as KneType.MAP + appendLine(" val _e${idx}_ptr = seg.get(JAVA_LONG, ${idx * 8}L)") + appendLine(" val _e$idx: Map<${mapType.keyType.jvmTypeName}, ${mapType.valueType.jvmTypeName}> = if (_e${idx}_ptr == 0L) emptyMap() else emptyMap() // TODO: MAP in nested tuples not fully implemented") + } + else -> appendLine(" val _e$idx = ${readExprs[idx]}") + } + } + // Free the buffer + appendLine(" FREE_BUF_HANDLE.invoke(ptr, ${bufSize}L)") + // Construct and return + val ctorArgs = (0 until n).joinToString(", ") { "_e$it" } + appendLine(" return $className($ctorArgs)") + appendLine(" }") + } + // ── Runtime helper ──────────────────────────────────────────────────────── /** Collect all unique KneType.FUNCTION signatures used as parameters in the module (including nullable). */ @@ -350,16 +646,31 @@ class FfmProxyGenerator { val signatures = mutableSetOf() fun scanParams(params: List) { params.forEach { p -> - if (p.type is KneType.FUNCTION) signatures.add(p.type) - else if (p.type is KneType.NULLABLE && (p.type as KneType.NULLABLE).inner is KneType.FUNCTION) - signatures.add((p.type as KneType.NULLABLE).inner as KneType.FUNCTION) + val fnType = when { + p.type is KneType.FUNCTION -> p.type + p.type is KneType.NULLABLE && (p.type as KneType.NULLABLE).inner is KneType.FUNCTION -> + (p.type as KneType.NULLABLE).inner as KneType.FUNCTION + else -> null + } + // Only include callback signatures whose types are all known + if (fnType != null && isKnownType(fnType)) signatures.add(fnType) } } module.classes.forEach { cls -> - cls.methods.forEach { scanParams(it.params) } - cls.companionMethods.forEach { scanParams(it.params) } + cls.methods.filter { hasOnlyKnownTypes(it) }.forEach { scanParams(it.params) } + cls.companionMethods.filter { hasOnlyKnownTypes(it) }.forEach { scanParams(it.params) } + // Scan constructor params for callback signatures + if (cls.constructor.params.all { isKnownType(it.type) }) { + scanParams(cls.constructor.params) + } + } + module.functions.filter { hasOnlyKnownTypes(it) }.forEach { scanParams(it.params) } + // Scan sealed enum variant fields for callback signatures + module.sealedEnums.forEach { sealed -> + sealed.variants.forEach { variant -> + scanParams(variant.fields) + } } - module.functions.forEach { scanParams(it.params) } return signatures } @@ -373,7 +684,7 @@ class FfmProxyGenerator { private fun fnInvokeId(fnType: KneType.FUNCTION): String = callbackId(fnType) - private fun generateRuntime(libName: String, pkg: String, callbackSignatures: Set = emptySet(), hasSuspend: Boolean = false, hasFlow: Boolean = false): String = buildString { + private fun generateRuntime(libName: String, pkg: String, callbackSignatures: Set = emptySet(), hasSuspend: Boolean = false, hasFlow: Boolean = false, tupleTypes: Set = emptySet(), hasNestedCollections: Boolean = false): String = buildString { appendLine("// Auto-generated by kotlin-native-export plugin. Do not modify.") appendLine("package $pkg") appendLine() @@ -404,12 +715,12 @@ class FfmProxyGenerator { appendLine(" val library: SymbolLookup by lazy { loadLibrary(\"$libName\") }") appendLine() appendLine(" private fun loadLibrary(name: String): SymbolLookup {") - appendLine(" val fileName = System.mapLibraryName(name)") + appendLine(" val fileName = java.lang.System.mapLibraryName(name)") appendLine() appendLine(" // Tier 1: Search java.library.path + working dir") - appendLine(" val sep = if (System.getProperty(\"os.name\").lowercase().contains(\"win\")) \";\" else \":\"") - appendLine(" val basePaths = System.getProperty(\"java.library.path\", \"\").split(sep)") - appendLine(" val extraDirs = mutableListOf(System.getProperty(\"user.dir\", \".\"))") + appendLine(" val sep = if (java.lang.System.getProperty(\"os.name\").lowercase().contains(\"win\")) \";\" else \":\"") + appendLine(" val basePaths = java.lang.System.getProperty(\"java.library.path\", \"\").split(sep)") + appendLine(" val extraDirs = mutableListOf(java.lang.System.getProperty(\"user.dir\", \".\"))") appendLine(" try { ProcessHandle.current().info().command().ifPresent { cmd -> Paths.get(cmd).parent?.toString()?.let { extraDirs.add(it) } } } catch (_: Exception) {}") appendLine(" for (dir in (basePaths + extraDirs).distinct().filter { it.isNotBlank() }) {") appendLine(" val file = Paths.get(dir, fileName).toFile()") @@ -428,7 +739,8 @@ class FfmProxyGenerator { appendLine(" val target = cacheDir.resolve(fileName)") appendLine(" stream.use { input ->") appendLine(" val bytes = input.readAllBytes()") - appendLine(" if (!Files.exists(target) || Files.size(target) != bytes.size.toLong()) {") + appendLine(" val needsUpdate = !Files.exists(target) || Files.size(target) != bytes.size.toLong() || !Files.readAllBytes(target).contentEquals(bytes)") + appendLine(" if (needsUpdate) {") appendLine(" val tmp = Files.createTempFile(cacheDir, name, \".tmp\")") appendLine(" Files.write(tmp, bytes)") appendLine(" Files.move(tmp, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)") @@ -442,18 +754,18 @@ class FfmProxyGenerator { appendLine(" }") appendLine() appendLine(" private fun detectPlatform(): String {") - appendLine(" val os = System.getProperty(\"os.name\", \"\").lowercase()") - appendLine(" val arch = System.getProperty(\"os.arch\").let { if (it == \"aarch64\" || it == \"arm64\") \"aarch64\" else \"x64\" }") + appendLine(" val os = java.lang.System.getProperty(\"os.name\", \"\").lowercase()") + appendLine(" val arch = java.lang.System.getProperty(\"os.arch\").let { if (it == \"aarch64\" || it == \"arm64\") \"aarch64\" else \"x64\" }") appendLine(" val osName = when { os.contains(\"mac\") || os.contains(\"darwin\") -> \"darwin\"; os.contains(\"win\") -> \"win32\"; else -> \"linux\" }") appendLine(" return \"\$osName-\$arch\"") appendLine(" }") appendLine() appendLine(" private fun resolveCacheDir(platform: String): java.nio.file.Path {") - appendLine(" val os = System.getProperty(\"os.name\", \"\").lowercase()") + appendLine(" val os = java.lang.System.getProperty(\"os.name\", \"\").lowercase()") appendLine(" val base = when {") - appendLine(" os.contains(\"mac\") -> Paths.get(System.getProperty(\"user.home\"), \"Library\", \"Caches\")") - appendLine(" os.contains(\"win\") -> Paths.get(System.getenv(\"LOCALAPPDATA\") ?: System.getProperty(\"user.home\"))") - appendLine(" else -> Paths.get(System.getenv(\"XDG_CACHE_HOME\") ?: \"\${System.getProperty(\"user.home\")}/.cache\")") + appendLine(" os.contains(\"mac\") -> Paths.get(java.lang.System.getProperty(\"user.home\"), \"Library\", \"Caches\")") + appendLine(" os.contains(\"win\") -> Paths.get(java.lang.System.getenv(\"LOCALAPPDATA\") ?: java.lang.System.getProperty(\"user.home\"))") + appendLine(" else -> Paths.get(java.lang.System.getenv(\"XDG_CACHE_HOME\") ?: \"\${java.lang.System.getProperty(\"user.home\")}/.cache\")") appendLine(" }") appendLine(" return base.resolve(\"kne\").resolve(\"native\").resolve(platform)") appendLine(" }") @@ -483,6 +795,17 @@ class FfmProxyGenerator { appendLine(" }") appendLine(" }") appendLine(" }") + appendLine() + appendLine(" val errorMessage: String?") + appendLine(" get() = try {") + appendLine(" val hasError = HAS_ERROR_HANDLE.invoke() as Int") + appendLine(" if (hasError == 0) null") + appendLine(" else Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate($ERR_BUF_SIZE.toLong())") + appendLine(" GET_LAST_ERROR_HANDLE.invoke(buf, $ERR_BUF_SIZE)") + appendLine(" buf.getString(0)") + appendLine(" }") + appendLine(" } catch (e: Throwable) { null }") // Generate upcall infrastructure for each callback signature if (callbackSignatures.isNotEmpty()) { @@ -569,6 +892,28 @@ class FfmProxyGenerator { appendLine(" return _buf.asSlice(0, _len.toLong()).toArray(JAVA_BYTE)") appendLine(" }") appendLine(" }") + // FREE_BUF_HANDLE for cleaning up heap-allocated tuple buffers + appendLine(" val FREE_BUF_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_free_buf\", FunctionDescriptor.ofVoid(JAVA_LONG, JAVA_LONG))") + appendLine(" }") + + // Generate readTupleFromRef dispatcher and per-type readers + if (tupleTypes.isNotEmpty()) { + appendLine(" fun readTupleFromRef(typeId: String, ptr: Long): Any {") + appendLine(" return when (typeId) {") + for (tuple in tupleTypes) { + val sig = tuple.typeId + val n = tuple.elementTypes.size + appendLine(" \"$sig\" -> readTuple${n}_$sig(ptr)") + } + appendLine(" else -> throw UnsupportedOperationException(\"Unknown tuple type: \$typeId\")") + appendLine(" }") + appendLine(" }") + + for (tuple in tupleTypes) { + appendGeneratedReadTupleFunction(tuple) + } + } } // Generate flow infrastructure (onNext reuses suspendExc stub, onComplete is new) @@ -599,6 +944,107 @@ class FfmProxyGenerator { appendLine(" }") } + if (hasNestedCollections) { + appendLine() + appendLine(" // ── Boxed list reader handles ───────────────────────────────────────────") + appendLine() + appendLine(" private val KNE_READ_BOX_LIST_I32_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_i32\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine(" private val KNE_READ_BOX_LIST_I64_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_i64\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine(" private val KNE_READ_BOX_LIST_F32_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_f32\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine(" private val KNE_READ_BOX_LIST_F64_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_f64\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine(" private val KNE_READ_BOX_LIST_BOOL_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_bool\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine(" private val KNE_READ_BOX_LIST_I16_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_i16\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine(" private val KNE_READ_BOX_LIST_I8_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_i8\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine(" private val KNE_READ_BOX_LIST_STRING_HANDLE: MethodHandle by lazy {") + appendLine(" handle(\"${libName}_kne_read_box_list_string\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + appendLine(" }") + appendLine() + appendLine(" fun readBoxedListI32(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate(JAVA_INT, $MAX_COLLECTION_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_I32_HANDLE.invoke(h, buf, $MAX_COLLECTION_SIZE) as Int") + appendLine(" return List(count) { buf.getAtIndex(JAVA_INT, it.toLong()) }") + appendLine(" }") + appendLine(" }") + appendLine(" fun readBoxedListI64(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate(JAVA_LONG, $MAX_COLLECTION_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_I64_HANDLE.invoke(h, buf, $MAX_COLLECTION_SIZE) as Int") + appendLine(" return List(count) { buf.getAtIndex(JAVA_LONG, it.toLong()) }") + appendLine(" }") + appendLine(" }") + appendLine(" fun readBoxedListF32(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate(JAVA_FLOAT, $MAX_COLLECTION_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_F32_HANDLE.invoke(h, buf, $MAX_COLLECTION_SIZE) as Int") + appendLine(" return List(count) { buf.getAtIndex(JAVA_FLOAT, it.toLong()) }") + appendLine(" }") + appendLine(" }") + appendLine(" fun readBoxedListF64(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate(JAVA_DOUBLE, $MAX_COLLECTION_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_F64_HANDLE.invoke(h, buf, $MAX_COLLECTION_SIZE) as Int") + appendLine(" return List(count) { buf.getAtIndex(JAVA_DOUBLE, it.toLong()) }") + appendLine(" }") + appendLine(" }") + appendLine(" fun readBoxedListBool(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate(JAVA_INT, $MAX_COLLECTION_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_BOOL_HANDLE.invoke(h, buf, $MAX_COLLECTION_SIZE) as Int") + appendLine(" return List(count) { buf.getAtIndex(JAVA_INT, it.toLong()) != 0 }") + appendLine(" }") + appendLine(" }") + appendLine(" fun readBoxedListI16(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate(JAVA_SHORT, $MAX_COLLECTION_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_I16_HANDLE.invoke(h, buf, $MAX_COLLECTION_SIZE) as Int") + appendLine(" return List(count) { buf.getAtIndex(JAVA_SHORT, it.toLong()) }") + appendLine(" }") + appendLine(" }") + appendLine(" fun readBoxedListI8(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate(JAVA_BYTE, $MAX_COLLECTION_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_I8_HANDLE.invoke(h, buf, $MAX_COLLECTION_SIZE) as Int") + appendLine(" return List(count) { buf.getAtIndex(JAVA_BYTE, it.toLong()) }") + appendLine(" }") + appendLine(" }") + appendLine(" fun readBoxedListString(h: Long): List {") + appendLine(" if (h == 0L) return emptyList()") + appendLine(" Arena.ofConfined().use { arena ->") + appendLine(" val buf = arena.allocate($STRING_BUF_SIZE.toLong())") + appendLine(" val count = KNE_READ_BOX_LIST_STRING_HANDLE.invoke(h, buf, $STRING_BUF_SIZE) as Int") + appendLine(" val result = mutableListOf()") + appendLine(" var off = 0L") + appendLine(" repeat(count) {") + appendLine(" result.add(buf.getString(off))") + appendLine(" off += result.last().toByteArray(Charsets.UTF_8).size + 1") + appendLine(" }") + appendLine(" return result.toList()") + appendLine(" }") + appendLine(" }") + } + appendLine("}") } @@ -637,6 +1083,7 @@ class FfmProxyGenerator { flatParams.add(FlatParam("p${i}_size", KneType.INT)) } is KneType.DATA_CLASS -> t.fields.forEach { f -> flatParams.add(FlatParam("p${i}_${f.name}", f.type)) } + is KneType.TUPLE -> t.elementTypes.forEachIndexed { idx, et -> flatParams.add(FlatParam("p${i}_$idx", et)) } is KneType.LIST, is KneType.SET -> { flatParams.add(FlatParam("p${i}_ptr", KneType.STRING)) // MemorySegment (ADDRESS) flatParams.add(FlatParam("p${i}_size", KneType.INT)) @@ -656,7 +1103,7 @@ class FfmProxyGenerator { }.joinToString(", ") val returnJvmType = upcallJvmType(sig.returnType) - val returnDecl = if (sig.returnType == KneType.UNIT) "" else ": $returnJvmType" + val returnDecl = if (sig.returnType == KneType.UNIT || sig.returnType == KneType.NEVER) "" else ": $returnJvmType" appendLine() appendLine(" @JvmStatic") @@ -711,7 +1158,12 @@ class FfmProxyGenerator { KneType.BYTE_ARRAY -> "_ba$i" KneType.STRING -> "p$i.reinterpret(Long.MAX_VALUE).getString(0)" is KneType.ENUM -> "${t.simpleName}.entries[p$i]" - is KneType.OBJECT -> "${t.simpleName}.fromNativeHandle(p$i)" + is KneType.OBJECT -> "${t.simpleName}.fromBorrowedHandle(p$i)" + is KneType.INTERFACE -> { + val wrapper = dynWrapperLookup[t.fqName] ?: t.simpleName + "$wrapper.fromBorrowedHandle(p$i)" + } + is KneType.SEALED_ENUM -> "${t.simpleName}.fromBorrowedHandle(p$i)" is KneType.DATA_CLASS -> { val fieldArgs = t.fields.joinToString(", ") { f -> val pName = "p${i}_${f.name}" @@ -730,7 +1182,7 @@ class FfmProxyGenerator { } }.joinToString(", ") - if (sig.returnType == KneType.UNIT) { + if (sig.returnType == KneType.UNIT || sig.returnType == KneType.NEVER) { appendLine(" _fn.invoke($invokeConvertedArgs)") } else if (sig.returnType == KneType.BOOLEAN) { appendLine(" return if (_fn.invoke($invokeConvertedArgs)) 1 else 0") @@ -748,6 +1200,11 @@ class FfmProxyGenerator { appendLine(" return _fn.invoke($invokeConvertedArgs).ordinal") } else if (sig.returnType is KneType.OBJECT) { appendLine(" return _fn.invoke($invokeConvertedArgs).handle") + } else if (sig.returnType is KneType.INTERFACE) { + val wrapper = dynWrapperLookup[(sig.returnType as KneType.INTERFACE).fqName] ?: sig.returnType.simpleName + appendLine(" return (_fn.invoke($invokeConvertedArgs) as $wrapper).handle") + } else if (sig.returnType is KneType.SEALED_ENUM) { + appendLine(" return _fn.invoke($invokeConvertedArgs).handle") } else if (sig.returnType is KneType.DATA_CLASS) { val dc = sig.returnType appendLine(" val _result = _fn.invoke($invokeConvertedArgs)") @@ -800,7 +1257,7 @@ class FfmProxyGenerator { // FunctionDescriptor for the callback's C ABI (DATA_CLASS expanded to fields) val descLayouts = flatParams.joinToString(", ") { upcallLayout(it.type) } - val descExpr = if (sig.returnType == KneType.UNIT) { + val descExpr = if (sig.returnType == KneType.UNIT || sig.returnType == KneType.NEVER) { if (descLayouts.isEmpty()) "FunctionDescriptor.ofVoid()" else "FunctionDescriptor.ofVoid($descLayouts)" } else { @@ -938,9 +1395,13 @@ class FfmProxyGenerator { KneType.SHORT -> "Short" KneType.STRING -> "MemorySegment" KneType.UNIT -> "Unit" + KneType.NEVER -> "Nothing" KneType.BYTE_ARRAY -> "MemorySegment" // packed buffer for return; expanded to ADDRESS+INT for params is KneType.OBJECT -> "Long" // opaque StableRef handle + is KneType.INTERFACE -> "Long" // opaque handle (dyn trait) + is KneType.SEALED_ENUM -> "Long" // opaque handle is KneType.DATA_CLASS -> "MemorySegment" // returns struct pointer + is KneType.TUPLE -> "MemorySegment" // returns tuple pointer is KneType.LIST, is KneType.SET, is KneType.MAP -> "MemorySegment" // packed buffer else -> "Int" } @@ -948,10 +1409,14 @@ class FfmProxyGenerator { /** The MethodType argument for a callback param/return type. */ private fun upcallMethodTypeArg(type: KneType): String = when (type) { KneType.UNIT -> "Void.TYPE" + KneType.NEVER -> "Void.TYPE" KneType.STRING -> "java.lang.foreign.MemorySegment::class.java" KneType.BYTE_ARRAY -> "java.lang.foreign.MemorySegment::class.java" is KneType.OBJECT -> "Long::class.javaPrimitiveType" + is KneType.INTERFACE -> "Long::class.javaPrimitiveType" + is KneType.SEALED_ENUM -> "Long::class.javaPrimitiveType" is KneType.DATA_CLASS -> "java.lang.foreign.MemorySegment::class.java" + is KneType.TUPLE -> "java.lang.foreign.MemorySegment::class.java" is KneType.LIST, is KneType.SET, is KneType.MAP -> "java.lang.foreign.MemorySegment::class.java" else -> "${upcallJvmType(type)}::class.javaPrimitiveType" } @@ -968,7 +1433,10 @@ class FfmProxyGenerator { KneType.STRING -> "ADDRESS" KneType.BYTE_ARRAY -> "ADDRESS" // packed buffer for return; expanded for params is KneType.OBJECT -> "JAVA_LONG" // opaque handle + is KneType.INTERFACE -> "JAVA_LONG" // opaque handle (dyn trait) + is KneType.SEALED_ENUM -> "JAVA_LONG" // opaque handle is KneType.DATA_CLASS -> "ADDRESS" // struct pointer + is KneType.TUPLE -> "ADDRESS" // tuple pointer is KneType.LIST, is KneType.SET, is KneType.MAP -> "ADDRESS" // packed buffer else -> "JAVA_INT" } @@ -1008,6 +1476,11 @@ class FfmProxyGenerator { appendLine("import kotlinx.coroutines.flow.channelFlow") appendLine("import kotlinx.coroutines.channels.awaitClose") } + val classHasAsync = cls.methods.any { it.isAsync && !it.isSuspend } || cls.companionMethods.any { it.isAsync && !it.isSuspend } + if (classHasAsync) { + appendLine("import kotlinx.coroutines.Dispatchers") + appendLine("import kotlinx.coroutines.withContext") + } appendLine() appendLine("/**") @@ -1027,8 +1500,10 @@ class FfmProxyGenerator { else -> "" } - val isInstantiable = !cls.isAbstract && !cls.isSealed - val hasHierarchy = hasSuperClass || cls.isOpen || cls.isAbstract || cls.isSealed || cls.interfaces.isNotEmpty() + val canConstruct = cls.constructor.kind != KneConstructorKind.NONE && !cls.isAbstract && !cls.isSealed + val hasHierarchy = hasSuperClass || cls.isOpen || cls.isAbstract || cls.isSealed + // Every concrete (non-abstract, non-sealed) class needs its own factory/wrapHandle + val needsOwnFactory = !cls.isAbstract && !cls.isSealed // Constructor visibility: private for flat classes (original behavior), internal/protected for hierarchy val ctorVisibility = when { @@ -1045,25 +1520,52 @@ class FfmProxyGenerator { } else { superParts.add("AutoCloseable") } - cls.interfaces.forEach { ifaceFq -> + val classMethodNames = cls.methods.map { it.name }.toSet() + val classPropertyNames = cls.properties.map { it.name }.toSet() + cls.interfaces.distinct().forEach { ifaceFq -> val ifaceSimple = ifaceFq.substringAfterLast(".") + if (ifaceSimple !in knownTypes || ifaceSimple in superParts) return@forEach + // Only add interface if the class implements all its abstract methods/properties + val iface = module.interfaces.find { it.fqName == ifaceFq } + if (iface != null) { + val missingMethods = iface.methods.any { it.name !in classMethodNames } + val missingProps = iface.properties.any { it.name !in classPropertyNames } + if (missingMethods || missingProps) return@forEach + } superParts.add(ifaceSimple) } val superClause = superParts.joinToString(", ") + // Collect method/property names from implemented interfaces AND superclass. + // Methods marked isOverride whose name is not in any kept interface/parent should lose the flag. + val keptInterfaces = superParts.drop(1) // drop AutoCloseable / parent class + val interfaceMethodNames = buildSet { + module.interfaces + .filter { it.simpleName in keptInterfaces } + .forEach { iface -> iface.methods.forEach { add(it.name) }; iface.properties.forEach { add(it.name) } } + // Also include superclass methods (walk full hierarchy) + var current = cls.superClass + while (current != null) { + val parentSimple = current.substringAfterLast(".") + val parent = module.classes.find { it.simpleName == parentSimple } + parent?.methods?.forEach { add(it.name) } + parent?.properties?.forEach { add(it.name) } + current = parent?.superClass + } + } + // Handle declaration: only on root class. Preserve original visibility for flat classes. - val implementsInterface = cls.interfaces.isNotEmpty() val handleDecl = when { !isRoot -> "handle: Long" // subclass: just a param, inherited from parent - implementsInterface -> "override val handle: Long" // implements interface with handle hasHierarchy -> "val handle: Long" // open/abstract/sealed: public for subclass access else -> "internal val handle: Long" // flat class: original behavior } appendLine("${modifier}class $n $ctorVisibility constructor($handleDecl) : $superClause {") if (isRoot) { - val disposedVisibility = if (hasHierarchy) "protected" else "private" - appendLine(" @Volatile $disposedVisibility var _disposed = false") + val stateVisibility = if (hasHierarchy) "protected" else "private" + appendLine(" @Volatile $stateVisibility var _disposed = false") + appendLine(" @Volatile $stateVisibility var _ownsHandle = true") } if (hasCallbacks) { appendLine(" internal val _callbackArena: Arena = Arena.ofShared()") @@ -1073,11 +1575,13 @@ class FfmProxyGenerator { } appendLine() - val companionHasCallbacks = cls.companionMethods.any { fn -> fn.params.any { it.type is KneType.FUNCTION } } + val companionHasCallbacks = cls.companionMethods.any { fn -> fn.params.any { + it.type is KneType.FUNCTION || (it.type is KneType.NULLABLE && (it.type as KneType.NULLABLE).inner is KneType.FUNCTION) + } } // Companion: MethodHandles + factory appendLine(" companion object {") - if (isInstantiable) { + if (needsOwnFactory) { appendLine(" private val CLEANER = Cleaner.create()") } if (companionHasCallbacks) { @@ -1085,7 +1589,7 @@ class FfmProxyGenerator { } appendLine() - if (isInstantiable) { + if (canConstruct) { val ctorDescriptor = buildCtorDescriptor(cls.constructor.params) appendLine(" private val NEW_HANDLE: MethodHandle by lazy {") appendLine(" KneRuntime.handle(\"${p}_${n}_new\",") @@ -1103,19 +1607,24 @@ class FfmProxyGenerator { appendLine(" $overloadDescriptor)") appendLine(" }") } + } + if (needsOwnFactory) { appendLine(" private val DISPOSE_HANDLE: MethodHandle by lazy {") appendLine(" KneRuntime.handle(\"${p}_${n}_dispose\",") appendLine(" FunctionDescriptor.ofVoid(JAVA_LONG))") appendLine(" }") } + val emittedHandles = mutableSetOf() cls.methods.forEach { method -> val handleName = "${method.name.uppercase()}_HANDLE" - val descriptor = buildMethodDescriptor(method) - appendLine(" private val $handleName: MethodHandle by lazy {") - appendLine(" KneRuntime.handle(\"${p}_${n}_${method.name}\",") - appendLine(" $descriptor)") - appendLine(" }") + if (emittedHandles.add(handleName)) { + val descriptor = buildMethodDescriptor(method) + appendLine(" private val $handleName: MethodHandle by lazy {") + appendLine(" KneRuntime.handle(\"${p}_${n}_${method.name}\",") + appendLine(" $descriptor)") + appendLine(" }") + } } cls.properties.forEach { prop -> @@ -1232,10 +1741,9 @@ class FfmProxyGenerator { } } // Scan DC params for collection fields + mutable collection properties + collection params with ByteArray/nested elements - cls.methods.forEach { m -> - m.params.forEach { pp -> + fun scanCollectionParams(params: List) { + params.forEach { pp -> extractDataClass(pp.type)?.let { scanDcFieldColls(it) } - // Scan collection params for ByteArray/nested collection element wrapping val inner = pp.type.unwrapCollection() when (inner) { is KneType.LIST -> if (inner.elementType == KneType.BYTE_ARRAY || inner.elementType is KneType.LIST || inner.elementType is KneType.SET || inner.elementType is KneType.MAP) dcFieldCollKeys.add(suspendCollElemKey(inner.elementType)) @@ -1244,6 +1752,9 @@ class FfmProxyGenerator { } } } + cls.methods.forEach { m -> scanCollectionParams(m.params) } + // Also scan constructor params for collection element wrapping + scanCollectionParams(cls.constructor.params) cls.properties.filter { it.mutable && it.type.isCollection() }.forEach { prop -> val inner = prop.type.unwrapCollection() when (inner) { @@ -1356,6 +1867,8 @@ class FfmProxyGenerator { } } cls.companionMethods.forEach { m -> extractDataClass(m.returnType)?.let { scanDcFieldsForCollReaders(it) } } + // MAP properties now use the direct out-param approach, not StableRef handles. + // No need to scan properties for suspendMapKeys. for (key in suspendCollKeys) { val uk = key.uppercase() val layout = when (key) { @@ -1412,8 +1925,8 @@ class FfmProxyGenerator { } // Factory (only for instantiable classes) - if (isInstantiable) { - val ctorParams = cls.constructor.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + if (canConstruct) { + val ctorParams = cls.constructor.params.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } appendLine() appendLine(" operator fun invoke($ctorParams): $n {") appendCtorInvokeBody(" ", cls.constructor.params, "NEW_HANDLE") @@ -1425,7 +1938,7 @@ class FfmProxyGenerator { for (drop in 1..trailingDefaults) { val requiredParams = cls.constructor.params.dropLast(drop) val suffix = requiredParams.size.toString() - val overloadParams = requiredParams.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + val overloadParams = requiredParams.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } appendLine() appendLine(" operator fun invoke($overloadParams): $n {") appendCtorInvokeBody(" ", requiredParams, "NEW_HANDLE_$suffix") @@ -1434,22 +1947,55 @@ class FfmProxyGenerator { } appendLine() - if (isInstantiable) { - appendLine(" internal fun fromNativeHandle(h: Long): $n {") + if (needsOwnFactory) { + appendLine(" private fun wrapHandle(h: Long, ownsHandle: Boolean): $n {") appendLine(" val obj = $n(h)") + appendLine(" obj._ownsHandle = ownsHandle") if (hasCallbacks) { appendLine(" val cbArena = obj._callbackArena") + appendLine(" if (ownsHandle) {") if (classHasSuspend) { - appendLine(" val inFlight = obj._suspendInFlight") - appendLine(" CLEANER.register(obj) { if (!obj._disposed) { obj._disposed = true; repeat(1000) { if (inFlight.get() <= 0) return@repeat; Thread.sleep(1) }; runCatching { cbArena.close() }; runCatching { DISPOSE_HANDLE.invoke(h) } } }") + appendLine(" val inFlight = obj._suspendInFlight") + appendLine(" CLEANER.register(obj) {") + appendLine(" if (!obj._disposed) {") + appendLine(" val shouldDispose = obj._ownsHandle") + appendLine(" obj._disposed = true") + appendLine(" obj._ownsHandle = false") + appendLine(" repeat(1000) { if (inFlight.get() <= 0) return@repeat; Thread.sleep(1) }") + appendLine(" runCatching { cbArena.close() }") + appendLine(" if (shouldDispose) runCatching { DISPOSE_HANDLE.invoke(h) }") + appendLine(" }") + appendLine(" }") } else { - appendLine(" CLEANER.register(obj) { if (!obj._disposed) { obj._disposed = true; runCatching { cbArena.close() }; runCatching { DISPOSE_HANDLE.invoke(h) } } }") + appendLine(" CLEANER.register(obj) {") + appendLine(" if (!obj._disposed) {") + appendLine(" val shouldDispose = obj._ownsHandle") + appendLine(" obj._disposed = true") + appendLine(" obj._ownsHandle = false") + appendLine(" runCatching { cbArena.close() }") + appendLine(" if (shouldDispose) runCatching { DISPOSE_HANDLE.invoke(h) }") + appendLine(" }") + appendLine(" }") } + appendLine(" }") } else { - appendLine(" CLEANER.register(obj) { if (!obj._disposed) { obj._disposed = true; runCatching { DISPOSE_HANDLE.invoke(h) } } }") + appendLine(" if (ownsHandle) {") + appendLine(" CLEANER.register(obj) {") + appendLine(" if (!obj._disposed) {") + appendLine(" val shouldDispose = obj._ownsHandle") + appendLine(" obj._disposed = true") + appendLine(" obj._ownsHandle = false") + appendLine(" if (shouldDispose) runCatching { DISPOSE_HANDLE.invoke(h) }") + appendLine(" }") + appendLine(" }") + appendLine(" }") } appendLine(" return obj") appendLine(" }") + appendLine() + appendLine(" internal fun fromNativeHandle(h: Long): $n = wrapHandle(h, ownsHandle = true)") + appendLine() + appendLine(" internal fun fromBorrowedHandle(h: Long): $n = wrapHandle(h, ownsHandle = false)") } // Companion methods @@ -1459,30 +2005,61 @@ class FfmProxyGenerator { appendLine(" }") appendLine() + if (isRoot) { + appendLine(" internal fun _consumeHandle(): Long {") + appendLine(" if (_disposed) error(\"$n handle already disposed\")") + appendLine(" if (_ownsHandle) {") + appendLine(" _disposed = true") + appendLine(" _ownsHandle = false") + appendLine(" }") + appendLine(" return handle") + appendLine(" }") + appendLine() + } + // Methods (dispatch suspend / flow / regular) + // Fix override flags: strip isOverride for methods not actually in any implemented interface cls.methods.forEach { method -> - if (method.isSuspend) appendSuspendMethodProxy(method, cls, p) - else if (method.returnType is KneType.FLOW) appendFlowMethodProxy(method, cls, p) - else appendMethodProxy(method, cls, p) + val fixedMethod = if (method.isOverride && method.name !in interfaceMethodNames) + method.copy(isOverride = false) else method + if (fixedMethod.isSuspend) appendSuspendMethodProxy(fixedMethod, cls, p) + else if (fixedMethod.returnType is KneType.FLOW) appendFlowMethodProxy(fixedMethod, cls, p) + else if (fixedMethod.isAsync) appendAsyncMethodProxy(fixedMethod, cls, p) + else appendMethodProxy(fixedMethod, cls, p) + } + cls.properties.forEach { prop -> + val fixedProp = if (prop.isOverride && prop.name !in interfaceMethodNames) + prop.copy(isOverride = false) else prop + appendPropertyProxy(fixedProp, cls) } - cls.properties.forEach { prop -> appendPropertyProxy(prop, cls) } // close() — idempotent, thread-safe, waits for in-flight suspend calls if (isRoot) { val openClose = if (cls.isOpen || cls.isAbstract || cls.isSealed) "open " else "" appendLine(" ${openClose}override fun close() {") appendLine(" if (_disposed) return") + appendLine(" val shouldDispose = _ownsHandle") appendLine(" _disposed = true") + appendLine(" _ownsHandle = false") if (classHasSuspend) { appendLine(" while (_suspendInFlight.get() > 0) { Thread.sleep(1) }") } if (hasCallbacks) { appendLine(" runCatching { _callbackArena.close() }") } - if (isInstantiable) { - appendLine(" runCatching { DISPOSE_HANDLE.invoke(handle) }") + if (needsOwnFactory) { + appendLine(" if (shouldDispose) runCatching { DISPOSE_HANDLE.invoke(handle) }") } appendLine(" }") + } else if (needsOwnFactory) { + // Non-root concrete subclasses override close() to use their own DISPOSE_HANDLE + appendLine(" override fun close() {") + appendLine(" if (_disposed) return") + appendLine(" val shouldDispose = _ownsHandle") + appendLine(" _disposed = true") + appendLine(" _ownsHandle = false") + appendLine(" if (shouldDispose) runCatching { DISPOSE_HANDLE.invoke(handle) }") + appendLine(" }") } appendLine("}") } @@ -1497,13 +2074,12 @@ class FfmProxyGenerator { appendLine(" * JVM interface proxy for Kotlin/Native interface [${iface.simpleName}].") appendLine(" */") appendLine("interface ${iface.simpleName} {") - appendLine(" val handle: Long") appendLine() // Interface methods as abstract declarations iface.methods.forEach { method -> val params = method.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } val returnDecl = if (method.returnType == KneType.UNIT) "" else ": ${method.returnType.jvmTypeName}" - val suspendMod = if (method.isSuspend) "suspend " else "" + val suspendMod = if (method.isSuspend || method.isAsync) "suspend " else "" appendLine(" ${suspendMod}fun ${method.name}($params)$returnDecl") } // Interface properties as abstract declarations @@ -1557,7 +2133,7 @@ class FfmProxyGenerator { else -> return@forEach } val handleName = "EXT_${receiverSimpleName.uppercase()}_${fn.name.uppercase()}_HANDLE" - val params = fn.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + val params = fn.params.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } val returnDecl = if (fn.returnType == KneType.UNIT) "" else ": ${fn.returnType.jvmTypeName}" appendLine("fun $receiverSimpleName.${fn.name}($params)$returnDecl {") @@ -1590,6 +2166,10 @@ class FfmProxyGenerator { appendLine(" $handleName.invoke($invokeArgs)") appendLine(" KneRuntime.checkError()") } + KneType.NEVER -> { + appendLine(" $handleName.invoke($invokeArgs)") + appendLine(" throw RuntimeException(KneRuntime.errorMessage ?: \"Rust panic\")") + } KneType.STRING -> { appendLine(" val _buf = arena.allocate(${STRING_BUF_SIZE}.toLong())") appendLine(" val _len = $handleName.invoke(this.handle${if (fn.params.isNotEmpty()) ", " + fn.params.joinToString(", ") { when (it.type) { KneType.STRING -> "${it.name}Seg"; KneType.BOOLEAN -> "if (${it.name}) 1 else 0"; else -> it.name } } else ""}, _buf, $STRING_BUF_SIZE) as Int") @@ -1620,6 +2200,10 @@ class FfmProxyGenerator { appendLine(" $handleName.invoke($invokeArgs)") appendLine(" KneRuntime.checkError()") } + KneType.NEVER -> { + appendLine(" $handleName.invoke($invokeArgs)") + appendLine(" throw RuntimeException(KneRuntime.errorMessage ?: \"Rust panic\")") + } KneType.BOOLEAN -> { appendLine(" val _r = $handleName.invoke($invokeArgs) as Int") appendLine(" KneRuntime.checkError()") @@ -1677,9 +2261,11 @@ class FfmProxyGenerator { private fun StringBuilder.appendCtorInvokeBody(indent: String, params: List, handleName: String) { val hasDc = params.any { extractDataClass(it.type) != null } val hasCollection = params.any { it.type.isCollection() } - val needsArena = params.any { it.type.isStringLike() || it.type.isByteArrayType() || it.type.isFunctionType() } || hasDc || hasCollection + val hasCallbacks = params.any { it.type.isFunctionType() } + val needsArena = params.any { it.type.isStringLike() || it.type.isByteArrayType() } || hasDc || hasCollection || hasCallbacks if (needsArena) { appendLine("${indent}Arena.ofConfined().use { arena ->") + if (hasCallbacks) appendCallbackStubAlloc("$indent ", params, "arena") appendStringInvokeArgsAlloc("$indent ", params) appendCollectionParamAlloc("$indent ", params) val args = buildList { params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } }.joinToString(", ") @@ -1688,7 +2274,7 @@ class FfmProxyGenerator { appendLine("$indent return fromNativeHandle(h)") appendLine("${indent}}") } else { - val args = params.joinToString(", ") { buildJvmInvokeArg(it.name, it.type) } + val args = params.joinToString(", ") { buildJvmInvokeArg(it) } appendLine("${indent}val h = $handleName.invoke($args) as Long") appendLine("${indent}KneRuntime.checkError()") appendLine("${indent}return fromNativeHandle(h)") @@ -1700,7 +2286,7 @@ class FfmProxyGenerator { val handleName = "${fn.name.uppercase()}_HANDLE" val flowType = fn.returnType as KneType.FLOW val elemType = flowType.elementType - val params = fn.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + val params = fn.params.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } appendLine(" fun ${fn.name}($params): Flow<${elemType.jvmTypeName}> = channelFlow {") appendLine(" _suspendInFlight.incrementAndGet()") @@ -1728,7 +2314,7 @@ class FfmProxyGenerator { appendLine(" val _cancelOut = _callArena.allocate(JAVA_LONG)") val invokeArgs = buildList { - add("handle") + add(buildReceiverInvokeArg(fn)) fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } add("_nextStub"); add("_errorStub"); add("_completeStub"); add("_cancelOut") }.joinToString(", ") @@ -1833,8 +2419,14 @@ class FfmProxyGenerator { val key = suspendCollElemKey(innerElem) appendLine("${indent}Arena.ofConfined().use { _collArena ->") if (innerElem == KneType.STRING) { - appendLine("${indent} val _outBuf = _collArena.allocate($STRING_BUF_SIZE.toLong())") - appendLine("${indent} val _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_value, _outBuf, $STRING_BUF_SIZE) as Int") + appendLine("${indent} var _bufSize = $STRING_BUF_SIZE") + appendLine("${indent} var _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine("${indent} var _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_value, _outBuf, _bufSize) as Int") + appendLine("${indent} if (_count < 0) {") + appendLine("${indent} _bufSize = -_count") + appendLine("${indent} _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine("${indent} _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_value, _outBuf, _bufSize) as Int") + appendLine("${indent} }") appendLine("${indent} val _list = mutableListOf()") appendLine("${indent} var _off = 0L") appendLine("${indent} repeat(_count) { _list.add(_outBuf.getString(_off)); _off += _list.last().toByteArray(Charsets.UTF_8).size + 1 }") @@ -1845,7 +2437,7 @@ class FfmProxyGenerator { when (innerElem) { KneType.BOOLEAN -> appendLine("${indent} val _list = List(_count) { _outBuf.getAtIndex(JAVA_INT, it.toLong()) != 0 }") is KneType.ENUM -> appendLine("${indent} val _list = List(_count) { ${innerElem.simpleName}.entries[_outBuf.getAtIndex(JAVA_INT, it.toLong())] }") - is KneType.OBJECT -> appendLine("${indent} val _list = List(_count) { ${innerElem.simpleName}.fromNativeHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") + is KneType.OBJECT -> appendLine("${indent} val _list = List(_count) { ${innerElem.simpleName}.fromBorrowedHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") KneType.BYTE_ARRAY -> appendLine("${indent} val _list = List(_count) { KneRuntime.readByteArrayFromRef(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") is KneType.LIST, is KneType.SET, is KneType.MAP -> { appendLine("${indent} val _list = List(_count) { _idx ->") @@ -1895,7 +2487,7 @@ class FfmProxyGenerator { /** Generate a suspend method proxy using suspendCancellableCoroutine. */ private fun StringBuilder.appendSuspendMethodProxy(fn: KneFunction, cls: KneClass, prefix: String) { val handleName = "${fn.name.uppercase()}_HANDLE" - val params = fn.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + val params = fn.params.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } val retType = fn.returnType.jvmTypeName val overrideMod = if (fn.isOverride) "override " else "" val openMod = if (!fn.isOverride && (cls.isOpen || cls.isAbstract)) "open " else "" @@ -1916,6 +2508,9 @@ class FfmProxyGenerator { appendLine(" } finally { _suspendInFlight.decrementAndGet() }") appendLine(" }, _callbackArena)") + // Allocate callback stubs (persistent arena — survive async) + appendCallbackStubAlloc(" ", fn.params, "_callbackArena") + // Invoke native bridge appendLine(" Arena.ofConfined().use { _callArena ->") @@ -1927,7 +2522,7 @@ class FfmProxyGenerator { // Build invoke args val invokeArgs = buildList { - add("handle") + add(buildReceiverInvokeArg(fn)) fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } add("_contStub"); add("_excStub"); add("_cancelOut") }.joinToString(", ") @@ -2056,8 +2651,14 @@ class FfmProxyGenerator { val key = suspendCollElemKey(elemType) appendLine("${indent}Arena.ofConfined().use { _collArena ->") if (elemType == KneType.STRING) { - appendLine("${indent} val _outBuf = _collArena.allocate($STRING_BUF_SIZE.toLong())") - appendLine("${indent} val _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_value, _outBuf, $STRING_BUF_SIZE) as Int") + appendLine("${indent} var _bufSize = $STRING_BUF_SIZE") + appendLine("${indent} var _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine("${indent} var _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_value, _outBuf, _bufSize) as Int") + appendLine("${indent} if (_count < 0) {") + appendLine("${indent} _bufSize = -_count") + appendLine("${indent} _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine("${indent} _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_value, _outBuf, _bufSize) as Int") + appendLine("${indent} }") appendLine("${indent} val _list = mutableListOf()") appendLine("${indent} var _off = 0L") appendLine("${indent} repeat(_count) { _list.add(_outBuf.getString(_off)); _off += _list.last().toByteArray(Charsets.UTF_8).size + 1 }") @@ -2072,7 +2673,7 @@ class FfmProxyGenerator { is KneType.ENUM -> appendLine("${indent} val _list = List(_count) { ${elemType.simpleName}.entries[_outBuf.getAtIndex(JAVA_INT, it.toLong())] }") is KneType.OBJECT -> - appendLine("${indent} val _list = List(_count) { ${elemType.simpleName}.fromNativeHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") + appendLine("${indent} val _list = List(_count) { ${elemType.simpleName}.fromBorrowedHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") KneType.BYTE_ARRAY -> appendLine("${indent} val _list = List(_count) { KneRuntime.readByteArrayFromRef(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") is KneType.LIST, is KneType.SET, is KneType.MAP -> { @@ -2145,7 +2746,7 @@ class FfmProxyGenerator { private fun StringBuilder.appendMethodProxy(fn: KneFunction, cls: KneClass, prefix: String) { val handleName = "${fn.name.uppercase()}_HANDLE" - val params = fn.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + val params = fn.params.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } val overrideMod = if (fn.isOverride) "override " else "" val openMod = if (!fn.isOverride && (cls.isOpen || cls.isAbstract)) "open " else "" @@ -2156,31 +2757,99 @@ class FfmProxyGenerator { val returnDc = extractDataClass(fn.returnType) val returnsNullableDc = fn.returnType is KneType.NULLABLE && fn.returnType.inner is KneType.DATA_CLASS + val returnsTuple = fn.returnType is KneType.TUPLE + val returnsNullableTuple = fn.returnType is KneType.NULLABLE && fn.returnType.inner is KneType.TUPLE val hasAnyDcParams = fn.params.any { extractDataClass(it.type) != null } val returnsCollection = fn.returnType.isCollection() - val needsConfinedArena = needsConfinedArena(fn.params, fn.returnType) || returnDc != null || + val needsConfinedArena = needsConfinedArena(fn.params, fn.returnType) || returnDc != null || returnsTuple || hasAnyDcParams && fn.params.any { dc -> val d = extractDataClass(dc.type); d != null && d.fields.any { f -> f.type == KneType.STRING } } - if (needsConfinedArena || returnDc != null || returnsCollection) { + if (needsConfinedArena || returnDc != null || returnsTuple || returnsCollection) { appendLine(" Arena.ofConfined().use { arena ->") appendStringInvokeArgsAlloc(" ", fn.params) appendCollectionParamAlloc(" ", fn.params) if (returnDc != null) { appendDataClassReturnProxy(" ", fn, handleName, returnsNullableDc) + } else if (returnsTuple) { + appendTupleReturnProxy(" ", fn, handleName, returnsNullableTuple) } else if (returnsCollection) { appendCollectionReturnProxy(" ", fn, handleName) } else { val invokeArgs = buildClassInvokeArgsExpanded(fn) - appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs) + appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs, fn.returnsBorrowed) + appendByteArrayCopyBack(" ", fn.params) + } + appendLine(" }") + } else { + val invokeArgs = buildClassInvokeArgsExpandedDirect(fn) + appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs, fn.returnsBorrowed) + } + + appendLine(" }") + appendLine() + } + + /** + * Generate a suspend fun wrapper for Rust async methods. + * Emits the sync method as private, then a public suspend fun that calls it via withContext(Dispatchers.IO). + */ + private fun StringBuilder.appendAsyncMethodProxy(fn: KneFunction, cls: KneClass, prefix: String) { + // Generate the sync implementation as a private method + val syncName = "_${fn.name}_blocking" + val syncFn = fn.copy(name = syncName, isOverride = false, isAsync = false) + val params = fn.params.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } + val handleName = "${syncName.uppercase()}_HANDLE" + + // Emit private sync method (reuse appendMethodProxy logic but with private visibility) + val overrideMod = if (fn.isOverride) "override " else "" + val openMod = if (!fn.isOverride && (cls.isOpen || cls.isAbstract)) "open " else "" + + // We need to rename the handle reference — just generate the sync body inline as private + appendLine(" private fun $syncName($params): ${fn.returnType.jvmTypeName} {") + appendCallbackStubAlloc(" ", fn.params, "_callbackArena") + + val returnDc = extractDataClass(fn.returnType) + val returnsNullableDc = fn.returnType is KneType.NULLABLE && fn.returnType.inner is KneType.DATA_CLASS + val returnsTuple = fn.returnType is KneType.TUPLE + val returnsNullableTuple = fn.returnType is KneType.NULLABLE && fn.returnType.inner is KneType.TUPLE + val hasAnyDcParams = fn.params.any { extractDataClass(it.type) != null } + val returnsCollection = fn.returnType.isCollection() + val needsConfinedArena = needsConfinedArena(fn.params, fn.returnType) || returnDc != null || returnsTuple || + hasAnyDcParams && fn.params.any { dc -> val d = extractDataClass(dc.type); d != null && d.fields.any { f -> f.type == KneType.STRING } } + + // Use the ORIGINAL method name for the handle (not the sync alias) + val realHandleName = "${fn.name.uppercase()}_HANDLE" + + if (needsConfinedArena || returnDc != null || returnsTuple || returnsCollection) { + appendLine(" Arena.ofConfined().use { arena ->") + appendStringInvokeArgsAlloc(" ", fn.params) + appendCollectionParamAlloc(" ", fn.params) + if (returnDc != null) { + appendDataClassReturnProxy(" ", fn, realHandleName, returnsNullableDc) + } else if (returnsTuple) { + appendTupleReturnProxy(" ", fn, realHandleName, returnsNullableTuple) + } else if (returnsCollection) { + appendCollectionReturnProxy(" ", fn, realHandleName) + } else { + val invokeArgs = buildClassInvokeArgsExpanded(fn) + appendCallAndReturn(" ", fn.returnType, realHandleName, invokeArgs, fn.returnsBorrowed) + appendByteArrayCopyBack(" ", fn.params) } appendLine(" }") } else { val invokeArgs = buildClassInvokeArgsExpandedDirect(fn) - appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs) + appendCallAndReturn(" ", fn.returnType, realHandleName, invokeArgs, fn.returnsBorrowed) } appendLine(" }") appendLine() + + // Emit public suspend fun wrapper + val callArgs = fn.params.joinToString(", ") { it.name } + appendLine(" ${overrideMod}${openMod}suspend fun ${fn.name}($params): ${fn.returnType.jvmTypeName} = withContext(Dispatchers.IO) {") + appendLine(" $syncName($callArgs)") + appendLine(" }") + appendLine() } /** Flatten data class into out-param (name, type) pairs (recursive for nested). */ @@ -2207,7 +2876,7 @@ class FfmProxyGenerator { // Build invoke args: handle + expanded params + out-params val paramArgs = buildList { - add("handle") + add(buildReceiverInvokeArg(fn)) fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } flatFields.forEach { (name, type) -> when (type) { @@ -2234,28 +2903,70 @@ class FfmProxyGenerator { appendLine("${indent}return ${buildDcCtorFromOutParams(dc, "out")}") } - /** Generate the return-via-out-params pattern for DATA_CLASS property types. */ - private fun StringBuilder.appendDataClassReturnProxyForProperty(indent: String, prop: KneProperty, handleName: String, nullable: Boolean = false) { - val dc = extractDataClass(prop.type)!! - val flatFields = flattenDcFields(dc, "out") + /** Generate the return-via-out-params pattern for TUPLE return types. */ + private fun StringBuilder.appendTupleReturnProxy(indent: String, fn: KneFunction, handleName: String, nullable: Boolean = false) { + val tupleType = if (fn.returnType is KneType.TUPLE) fn.returnType as KneType.TUPLE else (fn.returnType as KneType.NULLABLE).inner as KneType.TUPLE + val elements = tupleType.elementTypes - // Allocate out-params for each flat field - flatFields.forEach { (name, type) -> + // Allocate out-params for each tuple element + // STRING: buffer pointer + size (2 params) + // ByteArray/LIST/SET/MAP: pointer + size (2 params) + // Primitives/enums/objects: single value + elements.forEachIndexed { idx, type -> when (type) { - KneType.STRING -> appendLine("${indent}val $name = arena.allocate($STRING_BUF_SIZE.toLong())") - KneType.BYTE_ARRAY -> appendLine("${indent}val $name = arena.allocate(JAVA_LONG)") // StableRef handle - else -> appendLine("${indent}val $name = arena.allocate(${type.ffmLayout})") + KneType.STRING -> { + appendLine("${indent}val t_${idx}_buf = arena.allocate($STRING_BUF_SIZE.toLong())") + } + is KneType.LIST, is KneType.SET -> { + val innerElem = when (type) { is KneType.LIST -> type.elementType; is KneType.SET -> (type as KneType.SET).elementType; else -> KneType.INT } + val layout = KneType.collectionElementLayout(innerElem) + appendLine("${indent}val t_${idx}_buf = arena.allocate($layout, $MAX_COLLECTION_SIZE.toLong())") + appendLine("${indent}val t_${idx}_size = arena.allocate(JAVA_INT)") + } + KneType.BYTE_ARRAY -> { + appendLine("${indent}val t_${idx}_ptr = arena.allocate(ADDRESS)") + appendLine("${indent}val t_${idx}_size = arena.allocate(JAVA_INT)") + } + is KneType.MAP -> { + val mapType = type as KneType.MAP + val keyLayout = KneType.collectionElementLayout(mapType.keyType) + val valLayout = KneType.collectionElementLayout(mapType.valueType) + val isKeyString = mapType.keyType == KneType.STRING + val isValString = mapType.valueType == KneType.STRING + if (isKeyString) appendLine("${indent}val t_${idx}_keys = arena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine("${indent}val t_${idx}_keys = arena.allocate($keyLayout, $MAX_COLLECTION_SIZE.toLong())") + if (isValString) appendLine("${indent}val t_${idx}_values = arena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine("${indent}val t_${idx}_values = arena.allocate($valLayout, $MAX_COLLECTION_SIZE.toLong())") + appendLine("${indent}val t_${idx}_size = arena.allocate(JAVA_INT)") + } + else -> appendLine("${indent}val t_$idx = arena.allocate(${type.ffmLayout})") } } - // Build invoke args: handle + out-params (no method params for properties) + // Build invoke args: handle + params + out-params val paramArgs = buildList { - add("handle") - flatFields.forEach { (name, type) -> + add(buildReceiverInvokeArg(fn)) + fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } + elements.forEachIndexed { idx, type -> when (type) { - KneType.STRING -> { add(name); add("$STRING_BUF_SIZE") } - KneType.BYTE_ARRAY -> add(name) // StableRef handle, no size needed - else -> add(name) + KneType.STRING -> { add("t_${idx}_buf"); add("$STRING_BUF_SIZE") } + is KneType.LIST, is KneType.SET -> { + add("t_${idx}_buf"); add("$MAX_COLLECTION_SIZE"); add("t_${idx}_size") + } + KneType.BYTE_ARRAY -> { + add("t_${idx}_ptr"); add("t_${idx}_size") + } + is KneType.MAP -> { + val mapType = type as KneType.MAP + val isKeyString = mapType.keyType == KneType.STRING + val isValString = mapType.valueType == KneType.STRING + add("t_${idx}_keys") + if (isKeyString) add("$STRING_BUF_SIZE") + add("t_${idx}_values") + if (isValString) add("$STRING_BUF_SIZE") else add("$MAX_COLLECTION_SIZE") + add("t_${idx}_size") + } + else -> add("t_$idx") } } }.joinToString(", ") @@ -2269,23 +2980,203 @@ class FfmProxyGenerator { appendLine("${indent}KneRuntime.checkError()") } - // Read collection fields from StableRef handles, then reconstruct the data class - if (dcHasCollectionFields(dc)) { - appendDcCollectionFieldReads(indent, dc, "out") - } - appendLine("${indent}return ${buildDcCtorFromOutParams(dc, "out")}") + // Read tuple element values + val ctorArgs = elements.mapIndexed { idx, type -> + when (type) { + KneType.STRING -> "t_${idx}_buf.getString(0)" + KneType.BOOLEAN -> "t_$idx.get(JAVA_INT, 0) != 0" + KneType.BYTE -> "t_$idx.get(JAVA_BYTE, 0)" + KneType.SHORT -> "t_$idx.get(JAVA_SHORT, 0)" + KneType.INT -> "t_$idx.get(JAVA_INT, 0)" + KneType.LONG -> "t_$idx.get(JAVA_LONG, 0)" + KneType.FLOAT -> "t_$idx.get(JAVA_FLOAT, 0)" + KneType.DOUBLE -> "t_$idx.get(JAVA_DOUBLE, 0)" + KneType.BYTE_ARRAY -> "KneRuntime.readByteArrayFromRef(t_${idx}_ptr.get(JAVA_LONG, 0))" + is KneType.ENUM -> "${type.simpleName}.entries[t_$idx.get(JAVA_INT, 0)]" + is KneType.OBJECT -> "${type.simpleName}.fromBorrowedHandle(t_$idx.get(JAVA_LONG, 0))" + is KneType.LIST, is KneType.SET -> buildListCollectionReadExpr(idx, type as KneType) + is KneType.MAP -> buildMapCollectionReadExpr(idx, type) + is KneType.TUPLE -> "KneRuntime.readTupleFromRef(\"${type.typeId}\", t_${idx}.get(JAVA_LONG, 0)) as KneTuple${type.elementTypes.size}_${type.typeId}" + is KneType.NULLABLE -> "null /* TODO: nullable element */" + else -> "t_$idx.getLong(0)" + } + }.joinToString(", ") + + val tupleSig = tupleType.typeId + appendLine("${indent}return KneTuple${elements.size}_$tupleSig($ctorArgs)") } - /** Build a constructor call that reads from out-params (recursive for nested data classes). */ - /** Check if a DC (or nested DCs) has any collection fields. */ - private fun dcHasCollectionFields(dc: KneType.DATA_CLASS): Boolean = - dc.fields.any { f -> - f.type == KneType.BYTE_ARRAY || - f.type is KneType.LIST || f.type is KneType.SET || f.type is KneType.MAP || - (f.type is KneType.DATA_CLASS && dcHasCollectionFields(f.type)) + private fun buildListCollectionReadExpr(idx: Int, type: KneType): String { + val elemType = when (type) { is KneType.LIST -> type.elementType; is KneType.SET -> (type as KneType.SET).elementType; else -> KneType.INT } + val collType = if (type is KneType.SET) "Set" else "List" + val layout = KneType.collectionElementLayout(elemType) + val buf = "t_${idx}_buf" + val countExpr = "t_${idx}_size.get(JAVA_INT, 0)" + return when (elemType) { + KneType.INT -> "List($countExpr) { $buf.getAtIndex(JAVA_INT, it.toLong()) }" + KneType.LONG -> "List($countExpr) { $buf.getAtIndex(JAVA_LONG, it.toLong()) }" + KneType.DOUBLE -> "List($countExpr) { $buf.getAtIndex(JAVA_DOUBLE, it.toLong()) }" + KneType.FLOAT -> "List($countExpr) { $buf.getAtIndex(JAVA_FLOAT, it.toLong()) }" + KneType.BOOLEAN -> "List($countExpr) { $buf.getAtIndex(JAVA_INT, it.toLong()) != 0 }" + KneType.BYTE -> "List($countExpr) { $buf.getAtIndex(JAVA_BYTE, it.toLong()) }" + KneType.SHORT -> "List($countExpr) { $buf.getAtIndex(JAVA_SHORT, it.toLong()) }" + is KneType.ENUM -> "List($countExpr) { ${elemType.simpleName}.entries[$buf.getAtIndex(JAVA_INT, it.toLong())] }" + is KneType.OBJECT -> "List($countExpr) { ${elemType.simpleName}.fromBorrowedHandle($buf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }" + KneType.STRING -> { + "run { val _sb = mutableListOf(); var _off = 0L; repeat($countExpr) { _sb.add($buf.getString(_off)); _off += _sb.last().toByteArray(Charsets.UTF_8).size + 1 }; _sb.toList() }" + } + is KneType.LIST, is KneType.SET -> { + val innerElem = when (elemType) { is KneType.LIST -> (elemType as KneType.LIST).elementType; is KneType.SET -> (elemType as KneType.SET).elementType; else -> KneType.INT } + val innerLayout = KneType.collectionElementLayout(innerElem) + val innerRead = buildListElementReadExpr(buf, innerElem, "it.toLong()", innerLayout) + "List($countExpr) { $innerRead }" + } + is KneType.MAP -> "List($countExpr) { readMapFromTuple($buf.getAtIndex(ADDRESS, it.toLong()), 0) }" + is KneType.TUPLE -> "List($countExpr) { KneRuntime.readTupleFromRef(\"${(elemType as KneType.TUPLE).typeId}\", $buf.getAtIndex(ADDRESS, it.toLong())) }" + is KneType.DATA_CLASS, is KneType.NULLABLE, KneType.BYTE_ARRAY -> "List($countExpr) { $buf.getAtIndex(ADDRESS, it.toLong()) }" + else -> "emptyList() /* unsupported LIST element type in tuple */" + }.let { expr -> if (collType == "Set") "$expr.toSet()" else expr } + } + + private fun buildMapCollectionReadExpr(idx: Int, mapType: KneType.MAP): String { + val keyType = mapType.keyType + val valType = mapType.valueType + val kLayout = KneType.collectionElementLayout(keyType) + val vLayout = KneType.collectionElementLayout(valType) + val isKeyString = keyType == KneType.STRING + val isValString = valType == KneType.STRING + val countExpr = "t_${idx}_size.get(JAVA_INT, 0)" + val keysExpr = if (isKeyString) { + "run { val _sb = mutableListOf(); var _off = 0L; repeat($countExpr) { _sb.add(t_${idx}_keys.getString(_off)); _off += _sb.last().toByteArray(Charsets.UTF_8).size + 1 }; _sb.toList() }" + } else { + buildMapElementReadExpr("t_${idx}_keys", keyType, countExpr, kLayout) } + val valuesExpr = if (isValString) { + "run { val _sb = mutableListOf(); var _off = 0L; repeat($countExpr) { _sb.add(t_${idx}_values.getString(_off)); _off += _sb.last().toByteArray(Charsets.UTF_8).size + 1 }; _sb.toList() }" + } else { + buildMapElementReadExpr("t_${idx}_values", valType, countExpr, vLayout) + } + return "run { val _cnt = $countExpr; val _keys = $keysExpr; val _vals = $valuesExpr; mutableMapOf<${keyType.jvmTypeName}, ${valType.jvmTypeName}>().apply { repeat(_cnt) { this[_keys[it]] = _vals[it] } } }" + } - /** Emit local variables for reading collection/ByteArray fields from StableRef handles before constructing the DC. */ + private fun buildMapElementReadExpr(bufExpr: String, elemType: KneType, countExpr: String, layout: String): String { + return when (elemType) { + KneType.INT -> "List($countExpr) { $bufExpr.getAtIndex(JAVA_INT, it.toLong()) }" + KneType.LONG -> "List($countExpr) { $bufExpr.getAtIndex(JAVA_LONG, it.toLong()) }" + KneType.BOOLEAN -> "List($countExpr) { $bufExpr.getAtIndex(JAVA_INT, it.toLong()) != 0 }" + KneType.BYTE -> "List($countExpr) { $bufExpr.getAtIndex(JAVA_BYTE, it.toLong()) }" + KneType.SHORT -> "List($countExpr) { $bufExpr.getAtIndex(JAVA_SHORT, it.toLong()) }" + KneType.FLOAT -> "List($countExpr) { $bufExpr.getAtIndex(JAVA_FLOAT, it.toLong()) }" + KneType.DOUBLE -> "List($countExpr) { $bufExpr.getAtIndex(JAVA_DOUBLE, it.toLong()) }" + is KneType.ENUM -> "List($countExpr) { ${elemType.simpleName}.entries[$bufExpr.getAtIndex(JAVA_INT, it.toLong())] }" + is KneType.OBJECT -> "List($countExpr) { ${elemType.simpleName}.fromBorrowedHandle($bufExpr.getAtIndex($layout, it.toLong())) }" + is KneType.INTERFACE -> { + val wrapperName = dynWrapperLookup[elemType.fqName] ?: elemType.simpleName + "List($countExpr) { $wrapperName.fromBorrowedHandle($bufExpr.getAtIndex($layout, it.toLong())) }" + } + is KneType.SEALED_ENUM -> "List($countExpr) { ${elemType.simpleName}.fromBorrowedHandle($bufExpr.getAtIndex($layout, it.toLong())) }" + is KneType.LIST, is KneType.SET -> { + val innerElem = when (elemType) { is KneType.LIST -> (elemType as KneType.LIST).elementType; is KneType.SET -> (elemType as KneType.SET).elementType; else -> KneType.INT } + val readMethod = boxedListReadMethod(innerElem) + "List($countExpr) { KneRuntime.$readMethod($bufExpr.getAtIndex(JAVA_LONG, it.toLong())) }" + } + is KneType.MAP -> "List($countExpr) { readMapFromTuple($bufExpr.getAtIndex(ADDRESS, it.toLong()), 0) }" + is KneType.TUPLE, is KneType.DATA_CLASS, is KneType.NULLABLE, KneType.BYTE_ARRAY -> "List($countExpr) { $bufExpr.getAtIndex(ADDRESS, it.toLong()) }" + else -> "List($countExpr) { $bufExpr.getAtIndex($layout, it.toLong()) }" + } + } + + private fun buildListElementReadExpr(bufExpr: String, elemType: KneType, indexExpr: String, layout: String): String { + return when (elemType) { + KneType.INT -> "$bufExpr.getAtIndex(JAVA_INT, $indexExpr)" + KneType.LONG -> "$bufExpr.getAtIndex(JAVA_LONG, $indexExpr)" + KneType.BOOLEAN -> "$bufExpr.getAtIndex(JAVA_INT, $indexExpr) != 0" + KneType.BYTE -> "$bufExpr.getAtIndex(JAVA_BYTE, $indexExpr)" + KneType.SHORT -> "$bufExpr.getAtIndex(JAVA_SHORT, $indexExpr)" + KneType.FLOAT -> "$bufExpr.getAtIndex(JAVA_FLOAT, $indexExpr)" + KneType.DOUBLE -> "$bufExpr.getAtIndex(JAVA_DOUBLE, $indexExpr)" + is KneType.ENUM -> "${elemType.simpleName}.entries[$bufExpr.getAtIndex(JAVA_INT, $indexExpr)]" + is KneType.OBJECT -> "${elemType.simpleName}.fromBorrowedHandle($bufExpr.getAtIndex($layout, $indexExpr))" + is KneType.INTERFACE -> { + val wrapperName = dynWrapperLookup[elemType.fqName] ?: elemType.simpleName + "$wrapperName.fromBorrowedHandle($bufExpr.getAtIndex($layout, $indexExpr))" + } + is KneType.SEALED_ENUM -> "${elemType.simpleName}.fromBorrowedHandle($bufExpr.getAtIndex($layout, $indexExpr))" + is KneType.LIST, is KneType.SET -> { + val innerElem = when (elemType) { is KneType.LIST -> (elemType as KneType.LIST).elementType; is KneType.SET -> (elemType as KneType.SET).elementType; else -> KneType.INT } + val readMethod = boxedListReadMethod(innerElem) + "KneRuntime.$readMethod($bufExpr.getAtIndex(JAVA_LONG, $indexExpr))" + } + is KneType.MAP -> "readMapFromTuple($bufExpr.getAtIndex(ADDRESS, $indexExpr), 0)" + is KneType.TUPLE, is KneType.DATA_CLASS, is KneType.NULLABLE, KneType.BYTE_ARRAY -> "$bufExpr.getAtIndex(ADDRESS, $indexExpr)" + else -> "$bufExpr.getAtIndex($layout, $indexExpr)" + } + } + + private fun boxedListReadMethod(elemType: KneType): String = when (elemType) { + KneType.INT -> "readBoxedListI32" + KneType.LONG -> "readBoxedListI64" + KneType.FLOAT -> "readBoxedListF32" + KneType.DOUBLE -> "readBoxedListF64" + KneType.BOOLEAN -> "readBoxedListBool" + KneType.BYTE -> "readBoxedListI8" + KneType.SHORT -> "readBoxedListI16" + KneType.STRING -> "readBoxedListString" + else -> "readBoxedListI32" + } + + /** Generate the return-via-out-params pattern for DATA_CLASS property types. */ + private fun StringBuilder.appendDataClassReturnProxyForProperty(indent: String, prop: KneProperty, handleName: String, nullable: Boolean = false) { + val dc = extractDataClass(prop.type)!! + val flatFields = flattenDcFields(dc, "out") + + // Allocate out-params for each flat field + flatFields.forEach { (name, type) -> + when (type) { + KneType.STRING -> appendLine("${indent}val $name = arena.allocate($STRING_BUF_SIZE.toLong())") + KneType.BYTE_ARRAY -> appendLine("${indent}val $name = arena.allocate(JAVA_LONG)") // StableRef handle + else -> appendLine("${indent}val $name = arena.allocate(${type.ffmLayout})") + } + } + + // Build invoke args: handle + out-params (no method params for properties) + val paramArgs = buildList { + add("handle") + flatFields.forEach { (name, type) -> + when (type) { + KneType.STRING -> { add(name); add("$STRING_BUF_SIZE") } + KneType.BYTE_ARRAY -> add(name) // StableRef handle, no size needed + else -> add(name) + } + } + }.joinToString(", ") + + if (nullable) { + appendLine("${indent}val _isPresent = $handleName.invoke($paramArgs) as Int") + appendLine("${indent}KneRuntime.checkError()") + appendLine("${indent}if (_isPresent == 0) return null") + } else { + appendLine("${indent}$handleName.invoke($paramArgs)") + appendLine("${indent}KneRuntime.checkError()") + } + + // Read collection fields from StableRef handles, then reconstruct the data class + if (dcHasCollectionFields(dc)) { + appendDcCollectionFieldReads(indent, dc, "out") + } + appendLine("${indent}return ${buildDcCtorFromOutParams(dc, "out")}") + } + + /** Build a constructor call that reads from out-params (recursive for nested data classes). */ + /** Check if a DC (or nested DCs) has any collection fields. */ + private fun dcHasCollectionFields(dc: KneType.DATA_CLASS): Boolean = + dc.fields.any { f -> + f.type == KneType.BYTE_ARRAY || + f.type is KneType.LIST || f.type is KneType.SET || f.type is KneType.MAP || + (f.type is KneType.DATA_CLASS && dcHasCollectionFields(f.type)) + } + + /** Emit local variables for reading collection/ByteArray fields from StableRef handles before constructing the DC. */ private fun StringBuilder.appendDcCollectionFieldReads(indent: String, dc: KneType.DATA_CLASS, prefix: String) { dc.fields.forEach { f -> val name = "${prefix}_${f.name}" @@ -2385,8 +3276,14 @@ class FfmProxyGenerator { appendLine("${indent}val ${name}_collVal = run {") appendLine("${indent} Arena.ofConfined().use { _collArena ->") if (elemType == KneType.STRING) { - appendLine("${indent} val _outBuf = _collArena.allocate($STRING_BUF_SIZE.toLong())") - appendLine("${indent} val _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke($handle, _outBuf, $STRING_BUF_SIZE) as Int") + appendLine("${indent} var _bufSize = $STRING_BUF_SIZE") + appendLine("${indent} var _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine("${indent} var _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke($handle, _outBuf, _bufSize) as Int") + appendLine("${indent} if (_count < 0) {") + appendLine("${indent} _bufSize = -_count") + appendLine("${indent} _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine("${indent} _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke($handle, _outBuf, _bufSize) as Int") + appendLine("${indent} }") appendLine("${indent} val _list = mutableListOf()") appendLine("${indent} var _off = 0L") appendLine("${indent} repeat(_count) { _list.add(_outBuf.getString(_off)); _off += _list.last().toByteArray(Charsets.UTF_8).size + 1 }") @@ -2399,7 +3296,7 @@ class FfmProxyGenerator { when (elemType) { KneType.BOOLEAN -> appendLine("${indent} val _list = List(_count) { _outBuf.getAtIndex(JAVA_INT, it.toLong()) != 0 }") is KneType.ENUM -> appendLine("${indent} val _list = List(_count) { ${elemType.simpleName}.entries[_outBuf.getAtIndex(JAVA_INT, it.toLong())] }") - is KneType.OBJECT -> appendLine("${indent} val _list = List(_count) { ${elemType.simpleName}.fromNativeHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") + is KneType.OBJECT -> appendLine("${indent} val _list = List(_count) { ${elemType.simpleName}.fromBorrowedHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") else -> appendLine("${indent} val _list = List(_count) { _outBuf.getAtIndex($layout, it.toLong()) as ${elemType.jvmTypeName} }") } if (isSet) appendLine("${indent} _list.toSet()") @@ -2429,7 +3326,7 @@ class FfmProxyGenerator { /** Build invoke args with DATA_CLASS params expanded into individual fields (without output buffer args). */ private fun buildClassInvokeArgsExpanded(fn: KneFunction): String { val args = buildList { - add("handle") + add(buildReceiverInvokeArg(fn)) fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } } return args.joinToString(", ") @@ -2437,7 +3334,7 @@ class FfmProxyGenerator { private fun buildClassInvokeArgsExpandedDirect(fn: KneFunction): String { val args = buildList { - add("handle") + add(buildReceiverInvokeArg(fn)) fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } } return args.joinToString(", ") @@ -2448,33 +3345,77 @@ class FfmProxyGenerator { if (p.type == KneType.BYTE_ARRAY) return listOf("${p.name}Seg", "${p.name}.size") val isNullableColl = p.type is KneType.NULLABLE && (p.type as KneType.NULLABLE).inner.let { it is KneType.LIST || it is KneType.SET || it is KneType.MAP } if (p.type is KneType.LIST) { - return if ((p.type as KneType.LIST).elementType is KneType.DATA_CLASS) listOf("${p.name}Handle") - else listOf("${p.name}Seg", "${p.name}.size") + val elemType = (p.type as KneType.LIST).elementType + return when { + elemType is KneType.DATA_CLASS -> listOf("${p.name}Handle") + elemType == KneType.STRING -> listOf("${p.name}PtrSeg", "${p.name}.size") + else -> listOf("${p.name}Seg", "${p.name}.size") + } } if (p.type is KneType.SET) { - return if ((p.type as KneType.SET).elementType is KneType.DATA_CLASS) listOf("${p.name}Handle") - else listOf("${p.name}Seg", "${p.name}.size") + val elemType = (p.type as KneType.SET).elementType + return when { + elemType is KneType.DATA_CLASS -> listOf("${p.name}Handle") + elemType == KneType.STRING -> listOf("${p.name}PtrSeg", "${p.name}.size") + else -> listOf("${p.name}Seg", "${p.name}.size") + } + } + if (p.type is KneType.MAP) { + val mapType = p.type as KneType.MAP + val keySuffix = if (mapType.keyType == KneType.STRING) "PtrSeg" else "Seg" + val valueSuffix = if (mapType.valueType == KneType.STRING) "PtrSeg" else "Seg" + return listOf("${p.name}_keys${keySuffix}", "${p.name}_values${valueSuffix}", "${p.name}.size") } - if (p.type is KneType.MAP) return listOf("${p.name}_keysSeg", "${p.name}_valuesSeg", "${p.name}.size") if (isNullableColl) { val inner = (p.type as KneType.NULLABLE).inner return when (inner) { - is KneType.LIST -> if (inner.elementType is KneType.DATA_CLASS) listOf("${p.name}Handle") - else listOf("${p.name}Seg", "if (${p.name} == null) -1 else ${p.name}.size") - is KneType.SET -> if (inner.elementType is KneType.DATA_CLASS) listOf("${p.name}Handle") - else listOf("${p.name}Seg", "if (${p.name} == null) -1 else ${p.name}.size") - is KneType.MAP -> listOf("${p.name}_keysSeg", "${p.name}_valuesSeg", "if (${p.name} == null) -1 else ${p.name}.size") - else -> listOf(buildJvmInvokeArg(p.name, p.type)) + is KneType.LIST -> when { + inner.elementType is KneType.DATA_CLASS -> listOf("${p.name}Handle") + inner.elementType == KneType.STRING -> listOf("${p.name}PtrSeg", "if (${p.name} == null) -1 else ${p.name}.size") + else -> listOf("${p.name}Seg", "if (${p.name} == null) -1 else ${p.name}.size") + } + is KneType.SET -> when { + inner.elementType is KneType.DATA_CLASS -> listOf("${p.name}Handle") + inner.elementType == KneType.STRING -> listOf("${p.name}PtrSeg", "if (${p.name} == null) -1 else ${p.name}.size") + else -> listOf("${p.name}Seg", "if (${p.name} == null) -1 else ${p.name}.size") + } + is KneType.MAP -> { + val keySuffix = if (inner.keyType == KneType.STRING) "PtrSeg" else "Seg" + val valueSuffix = if (inner.valueType == KneType.STRING) "PtrSeg" else "Seg" + listOf("${p.name}_keys${keySuffix}", "${p.name}_values${valueSuffix}", "if (${p.name} == null) -1 else ${p.name}.size") + } + is KneType.TUPLE -> buildTupleInvokeArgs(p.name, inner as KneType.TUPLE, true) + else -> listOf(buildJvmInvokeArg(p)) } } + if (p.type is KneType.TUPLE) { + return buildTupleInvokeArgs(p.name, p.type as KneType.TUPLE, false) + } val dc = extractDataClass(p.type) - if (dc == null) return listOf(buildJvmInvokeArg(p.name, p.type)) + if (dc == null) return listOf(buildJvmInvokeArg(p)) val isNullable = p.type is KneType.NULLABLE val objExpr = p.name val flatArgs = buildFlatInvokeArgs(dc, objExpr, p.name, isNullable) return if (isNullable) listOf("if ($objExpr == null) 1 else 0") + flatArgs else flatArgs } + private fun buildTupleInvokeArgs(name: String, tuple: KneType.TUPLE, nullable: Boolean): List { + val objExpr = name + return tuple.elementTypes.flatMapIndexed { idx, type -> + val access = if (nullable) "$objExpr?._$idx" else "$objExpr._$idx" + when (type) { + KneType.STRING -> listOf("${name}_${idx}Seg") + KneType.BYTE_ARRAY -> listOf("${name}_${idx}Seg", "$access?.size ?: 0") + KneType.BOOLEAN -> listOf("if ($access == true) 1 else 0") + is KneType.ENUM -> listOf("$access?.ordinal ?: 0") + is KneType.OBJECT -> listOf("$access?.handle ?: 0L") + is KneType.LIST, is KneType.SET -> listOf("${name}_${idx}Seg", "$access?.size ?: 0") + is KneType.MAP -> listOf("${name}_${idx}keysSeg", "${name}_${idx}valuesSeg", "$access?.size ?: 0") + else -> listOf("$access ?: 0") + } + } + } + private fun buildFlatInvokeArgs(dc: KneType.DATA_CLASS, objExpr: String, prefix: String, nullable: Boolean): List = dc.fields.flatMap { f -> val access = if (nullable) "$objExpr?.${f.name}" else "$objExpr.${f.name}" @@ -2505,8 +3446,46 @@ class FfmProxyGenerator { } appendLine(" get() {") if (isCollProp) { - // Collection property getter: read StableRef handle, deserialize, dispose val inner = prop.type.unwrapCollection() + if (inner is KneType.MAP) { + // MAP property: direct approach (out-params), no StableRef handle + val mapType = inner as KneType.MAP + val keyIsString = mapType.keyType == KneType.STRING + val valIsString = mapType.valueType == KneType.STRING + appendLine(" Arena.ofConfined().use { _mapArena ->") + if (keyIsString) appendLine(" val _keysBuf = _mapArena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine(" val _keysBuf = _mapArena.allocate(${KneType.collectionElementLayout(mapType.keyType)}, $MAX_COLLECTION_SIZE.toLong())") + if (valIsString) appendLine(" val _valsBuf = _mapArena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine(" val _valsBuf = _mapArena.allocate(${KneType.collectionElementLayout(mapType.valueType)}, $MAX_COLLECTION_SIZE.toLong())") + val invokeArgs = buildList { + add("handle"); add("_keysBuf") + if (keyIsString) add("$STRING_BUF_SIZE") + add("_valsBuf") + if (valIsString) add("$STRING_BUF_SIZE") + add("$MAX_COLLECTION_SIZE") + }.joinToString(", ") + appendLine(" val _count = $getHandleName.invoke($invokeArgs) as Int") + appendLine(" KneRuntime.checkError()") + appendLine(" val _map = mutableMapOf<${mapType.keyType.jvmTypeName}, ${mapType.valueType.jvmTypeName}>()") + if (keyIsString) { + appendLine(" val _keys = mutableListOf()") + appendLine(" var _kOff = 0L") + appendLine(" repeat(_count) { _keys.add(_keysBuf.getString(_kOff)); _kOff += _keys.last().toByteArray(Charsets.UTF_8).size + 1 }") + } else { + appendMapElementRead(" ", "_keys", mapType.keyType, "_count", "_keysBuf") + } + if (valIsString) { + appendLine(" val _values = mutableListOf()") + appendLine(" var _vOff = 0L") + appendLine(" repeat(_count) { _values.add(_valsBuf.getString(_vOff)); _vOff += _values.last().toByteArray(Charsets.UTF_8).size + 1 }") + } else { + appendMapElementRead(" ", "_values", mapType.valueType, "_count", "_valsBuf") + } + appendLine(" repeat(_count) { _map[_keys[it]] = _values[it] }") + appendLine(" return _map") + appendLine(" }") + } else { + // LIST/SET collection property getter: read StableRef handle, deserialize, dispose appendLine(" val _handle = $getHandleName.invoke(handle) as Long") appendLine(" KneRuntime.checkError()") when (inner) { @@ -2539,8 +3518,14 @@ class FfmProxyGenerator { val key = suspendCollElemKey(inner.elementType) appendLine(" Arena.ofConfined().use { _collArena ->") if (inner.elementType == KneType.STRING) { - appendLine(" val _outBuf = _collArena.allocate($STRING_BUF_SIZE.toLong())") - appendLine(" val _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_handle, _outBuf, $STRING_BUF_SIZE) as Int") + appendLine(" var _bufSize = $STRING_BUF_SIZE") + appendLine(" var _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine(" var _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_handle, _outBuf, _bufSize) as Int") + appendLine(" if (_count < 0) {") + appendLine(" _bufSize = -_count") + appendLine(" _outBuf = _collArena.allocate(_bufSize.toLong())") + appendLine(" _count = SUSPEND_READCOLL_${key.uppercase()}_HANDLE.invoke(_handle, _outBuf, _bufSize) as Int") + appendLine(" }") appendLine(" val _list = mutableListOf()") appendLine(" var _off = 0L") appendLine(" repeat(_count) { _list.add(_outBuf.getString(_off)); _off += _list.last().toByteArray(Charsets.UTF_8).size + 1 }") @@ -2572,36 +3557,9 @@ class FfmProxyGenerator { } appendLine(" }") } - is KneType.MAP -> { - val kk = suspendCollElemKey(inner.keyType) - val vk = suspendCollElemKey(inner.valueType) - appendLine(" Arena.ofConfined().use { _mapArena ->") - val isKeyString = inner.keyType == KneType.STRING - val isValString = inner.valueType == KneType.STRING - if (isKeyString) appendLine(" val _keysBuf = _mapArena.allocate($STRING_BUF_SIZE.toLong())") - else appendLine(" val _keysBuf = _mapArena.allocate(${KneType.collectionElementLayout(inner.keyType)}, $MAX_COLLECTION_SIZE.toLong())") - if (isValString) appendLine(" val _valsBuf = _mapArena.allocate($STRING_BUF_SIZE.toLong())") - else appendLine(" val _valsBuf = _mapArena.allocate(${KneType.collectionElementLayout(inner.valueType)}, $MAX_COLLECTION_SIZE.toLong())") - val keySizeArg = if (isKeyString) "$STRING_BUF_SIZE" else "$MAX_COLLECTION_SIZE" - val valSizeArg = if (isValString) "$STRING_BUF_SIZE" else "$MAX_COLLECTION_SIZE" - appendLine(" val _count = SUSPEND_READMAP_${kk.uppercase()}_${vk.uppercase()}_HANDLE.invoke(_handle, _keysBuf, $keySizeArg, _valsBuf, $valSizeArg) as Int") - if (isKeyString) { - appendLine(" val _keys = mutableListOf(); var _kOff = 0L") - appendLine(" repeat(_count) { _keys.add(_keysBuf.getString(_kOff)); _kOff += _keys.last().toByteArray(Charsets.UTF_8).size + 1 }") - } else { - appendLine(" val _keys = List(_count) { _keysBuf.getAtIndex(${KneType.collectionElementLayout(inner.keyType)}, it.toLong()) as ${inner.keyType.jvmTypeName} }") - } - if (isValString) { - appendLine(" val _vals = mutableListOf(); var _vOff = 0L") - appendLine(" repeat(_count) { _vals.add(_valsBuf.getString(_vOff)); _vOff += _vals.last().toByteArray(Charsets.UTF_8).size + 1 }") - } else { - appendLine(" val _vals = List(_count) { _valsBuf.getAtIndex(${KneType.collectionElementLayout(inner.valueType)}, it.toLong()) as ${inner.valueType.jvmTypeName} }") - } - appendLine(" return _keys.zip(_vals).toMap()") - appendLine(" }") - } else -> appendCallAndReturn(" ", prop.type, getHandleName, "handle") } + } // end LIST/SET else branch } else if (isDcProp) { // Data class property getter: use out-params pattern val returnsNullableDc = prop.type is KneType.NULLABLE && prop.type.inner is KneType.DATA_CLASS @@ -2666,23 +3624,36 @@ class FfmProxyGenerator { private fun StringBuilder.appendCompanionMethodProxy(fn: KneFunction) { val handleName = "COMPANION_${fn.name.uppercase()}_HANDLE" - val params = fn.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + val params = fn.params.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } appendLine() appendLine(" fun ${fn.name}($params): ${fn.returnType.jvmTypeName} {") appendCallbackStubAlloc(" ", fn.params, "_companionCallbackArena") - val arenaNeeded = needsConfinedArena(fn.params, fn.returnType) + val returnDc = extractDataClass(fn.returnType) + val returnsNullableDc = fn.returnType is KneType.NULLABLE && fn.returnType.inner is KneType.DATA_CLASS + val returnsCollection = fn.returnType.isCollection() + val hasAnyDcParams = fn.params.any { extractDataClass(it.type) != null } + val arenaNeeded = needsConfinedArena(fn.params, fn.returnType) || returnDc != null || returnsCollection || + hasAnyDcParams && fn.params.any { dc -> val d = extractDataClass(dc.type); d != null && d.fields.any { f -> f.type == KneType.STRING } } + if (arenaNeeded) { appendLine(" Arena.ofConfined().use { arena ->") appendStringInvokeArgsAlloc(" ", fn.params) - val invokeArgs = buildTopLevelInvokeArgs(fn) - appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs) + appendCollectionParamAlloc(" ", fn.params) + if (returnDc != null) { + appendCompanionDataClassReturnProxy(" ", fn, handleName, returnsNullableDc) + } else if (returnsCollection) { + appendCompanionCollectionReturnProxy(" ", fn, handleName) + } else { + val invokeArgs = buildTopLevelInvokeArgs(fn) + appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs, fn.returnsBorrowed) + } appendLine(" }") } else { - val invokeArgs = fn.params.joinToString(", ") { p -> buildJvmInvokeArg(p.name, p.type) } - appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs) + val invokeArgs = fn.params.joinToString(", ") { p -> buildJvmInvokeArg(p) } + appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs, fn.returnsBorrowed) } appendLine(" }") @@ -2720,6 +3691,169 @@ class FfmProxyGenerator { } } + /** DATA_CLASS return for companion/top-level methods (no handle receiver). */ + private fun StringBuilder.appendCompanionDataClassReturnProxy(indent: String, fn: KneFunction, handleName: String, nullable: Boolean = false) { + val dc = extractDataClass(fn.returnType)!! + val flatFields = flattenDcFields(dc, "out") + + flatFields.forEach { (name, type) -> + when (type) { + KneType.STRING -> appendLine("${indent}val $name = arena.allocate($STRING_BUF_SIZE.toLong())") + KneType.BYTE_ARRAY -> appendLine("${indent}val $name = arena.allocate(JAVA_LONG)") + else -> appendLine("${indent}val $name = arena.allocate(${type.ffmLayout})") + } + } + + val paramArgs = buildList { + fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } + flatFields.forEach { (name, type) -> + when (type) { + KneType.STRING -> { add(name); add("$STRING_BUF_SIZE") } + KneType.BYTE_ARRAY -> add(name) + else -> add(name) + } + } + }.joinToString(", ") + + if (nullable) { + appendLine("${indent}val _isPresent = $handleName.invoke($paramArgs) as Int") + appendLine("${indent}KneRuntime.checkError()") + appendLine("${indent}if (_isPresent == 0) return null") + } else { + appendLine("${indent}$handleName.invoke($paramArgs)") + appendLine("${indent}KneRuntime.checkError()") + } + + if (dcHasCollectionFields(dc)) { + appendDcCollectionFieldReads(indent, dc, "out") + } + appendLine("${indent}return ${buildDcCtorFromOutParams(dc, "out")}") + } + + /** Collection return for companion/top-level methods (no handle receiver). */ + private fun StringBuilder.appendCompanionCollectionReturnProxy(indent: String, fn: KneFunction, handleName: String) { + val isNullable = fn.returnType is KneType.NULLABLE + val inner = fn.returnType.unwrapCollection() + when (inner) { + is KneType.LIST -> appendCompanionListReturnProxy(indent, fn, handleName, inner.elementType, "List", isNullable) + is KneType.SET -> appendCompanionListReturnProxy(indent, fn, handleName, inner.elementType, "Set", isNullable) + is KneType.MAP -> appendCompanionMapReturnProxy(indent, fn, handleName, inner, isNullable) + else -> {} + } + } + + private fun StringBuilder.appendCompanionListReturnProxy(indent: String, fn: KneFunction, handleName: String, elemType: KneType, collType: String, nullable: Boolean = false) { + if (elemType is KneType.DATA_CLASS) { + val invokeArgs = buildTopLevelInvokeArgs(fn) + appendLine("${indent}val _listHandle = $handleName.invoke($invokeArgs) as Long") + appendLine("${indent}KneRuntime.checkError()") + if (nullable) appendLine("${indent}if (_listHandle == 0L) return null") + appendLine("${indent}try {") + appendLine("${indent} val _size = LIST_${elemType.simpleName.uppercase()}_SIZE_HANDLE.invoke(_listHandle) as Int") + appendLine("${indent} val _list = ArrayList<${elemType.simpleName}>(_size)") + val flatFields = flattenDcFields(elemType, "out") + appendLine("${indent} Arena.ofConfined().use { dcArena ->") + flatFields.forEach { (name, type) -> + when (type) { + KneType.STRING, KneType.BYTE_ARRAY -> appendLine("${indent} val $name = dcArena.allocate($STRING_BUF_SIZE.toLong())") + else -> appendLine("${indent} val $name = dcArena.allocate(${type.ffmLayout})") + } + } + appendLine("${indent} for (_i in 0 until _size) {") + val getArgs = buildList { + add("_listHandle"); add("_i") + flatFields.forEach { (name, type) -> + when (type) { KneType.STRING, KneType.BYTE_ARRAY -> { add(name); add("$STRING_BUF_SIZE") }; else -> add(name) } + } + }.joinToString(", ") + appendLine("${indent} LIST_${elemType.simpleName.uppercase()}_GET_HANDLE.invoke($getArgs)") + if (dcHasCollectionFields(elemType)) appendDcCollectionFieldReads("${indent} ", elemType, "out") + appendLine("${indent} _list.add(${buildDcCtorFromOutParams(elemType, "out")})") + appendLine("${indent} }") + appendLine("${indent} }") + if (collType == "Set") appendLine("${indent} return _list.toSet()") + else appendLine("${indent} return _list") + appendLine("${indent}} finally {") + appendLine("${indent} LIST_${elemType.simpleName.uppercase()}_DISPOSE_HANDLE.invoke(_listHandle)") + appendLine("${indent}}") + return + } + when (elemType) { + KneType.STRING -> { + val topArgs = buildTopLevelInvokeArgs(fn) + appendLine("${indent}var _bufSize = $STRING_BUF_SIZE") + appendLine("${indent}var _outBuf = arena.allocate(_bufSize.toLong())") + val invokeArgs = if (topArgs.isEmpty()) "_outBuf, _bufSize" else "$topArgs, _outBuf, _bufSize" + appendLine("${indent}var _count = $handleName.invoke($invokeArgs) as Int") + appendLine("${indent}KneRuntime.checkError()") + if (nullable) appendLine("${indent}if (_count == -1) return null") + appendLine("${indent}if (_count < 0) {") + appendLine("${indent} _bufSize = -_count") + appendLine("${indent} _outBuf = arena.allocate(_bufSize.toLong())") + val retryArgs = if (topArgs.isEmpty()) "_outBuf, _bufSize" else "$topArgs, _outBuf, _bufSize" + appendLine("${indent} _count = $handleName.invoke($retryArgs) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent}}") + appendLine("${indent}val _list = mutableListOf()") + appendLine("${indent}var _off = 0L") + appendLine("${indent}repeat(_count) { _list.add(_outBuf.getString(_off)); _off += _list.last().toByteArray(Charsets.UTF_8).size + 1 }") + if (collType == "Set") appendLine("${indent}return _list.toSet()") + else appendLine("${indent}return _list") + } + else -> { + val layout = KneType.collectionElementLayout(elemType) + appendLine("${indent}val _outBuf = arena.allocate($layout, $MAX_COLLECTION_SIZE.toLong())") + val invokeArgs = buildTopLevelInvokeArgs(fn).let { if (it.isEmpty()) "_outBuf, $MAX_COLLECTION_SIZE" else "$it, _outBuf, $MAX_COLLECTION_SIZE" } + appendLine("${indent}val _count = $handleName.invoke($invokeArgs) as Int") + appendLine("${indent}KneRuntime.checkError()") + if (nullable) appendLine("${indent}if (_count < 0) return null") + appendCollectionElementRead(indent, elemType, "_count", collType) + } + } + } + + private fun StringBuilder.appendCompanionMapReturnProxy(indent: String, fn: KneFunction, handleName: String, mapType: KneType.MAP, nullable: Boolean = false) { + val invokeArgs = buildTopLevelInvokeArgs(fn) + val keyLayout = KneType.collectionElementLayout(mapType.keyType) + val valLayout = KneType.collectionElementLayout(mapType.valueType) + val isKeyString = mapType.keyType == KneType.STRING + val isValString = mapType.valueType == KneType.STRING + if (isKeyString) appendLine("${indent}val _keyBuf = arena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine("${indent}val _keyBuf = arena.allocate($keyLayout, $MAX_COLLECTION_SIZE.toLong())") + if (isValString) appendLine("${indent}val _valBuf = arena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine("${indent}val _valBuf = arena.allocate($valLayout, $MAX_COLLECTION_SIZE.toLong())") + val extra = buildList { + if (invokeArgs.isNotEmpty()) add(invokeArgs) + add("_keyBuf") + if (isKeyString) add("$STRING_BUF_SIZE") + add("_valBuf") + if (isValString) add("$STRING_BUF_SIZE") + add("$MAX_COLLECTION_SIZE") + }.joinToString(", ") + appendLine("${indent}val _count = $handleName.invoke($extra) as Int") + appendLine("${indent}KneRuntime.checkError()") + if (nullable) appendLine("${indent}if (_count < 0) return null") + appendLine("${indent}val _map = mutableMapOf<${mapType.keyType.jvmTypeName}, ${mapType.valueType.jvmTypeName}>()") + // Read keys + if (isKeyString) { + appendLine("${indent}val _keys = mutableListOf()") + appendLine("${indent}var _kOff = 0L") + appendLine("${indent}repeat(_count) { _keys.add(_keyBuf.getString(_kOff)); _kOff += _keys.last().toByteArray(Charsets.UTF_8).size + 1 }") + } else { + appendMapElementRead(indent, "_keys", mapType.keyType, "_count", "_keyBuf") + } + // Read values + if (isValString) { + appendLine("${indent}val _values = mutableListOf()") + appendLine("${indent}var _vOff = 0L") + appendLine("${indent}repeat(_count) { _values.add(_valBuf.getString(_vOff)); _vOff += _values.last().toByteArray(Charsets.UTF_8).size + 1 }") + } else { + appendMapElementRead(indent, "_values", mapType.valueType, "_count", "_valBuf") + } + appendLine("${indent}repeat(_count) { _map[_keys[it]] = _values[it] }") + appendLine("${indent}return _map") + } + // ── Data class file ─────────────────────────────────────────────────────── private fun generateDataClassFile(dc: KneDataClass, pkg: String): String = buildString { @@ -2744,6 +3878,381 @@ class FfmProxyGenerator { appendLine("}") } + // ── Sealed enum (tagged enum with data variants) ───────────────────────── + + // Only types that cause real shadowing issues inside sealed class bodies. + // String/Boolean/Int etc. are commonly used as return types in getters, + // so inner classes with these names cause "expected X, found SealedClass.X" errors. + private val KOTLIN_BUILTIN_NAMES = setOf( + "String", "Boolean", "Int", "Long", "Double", "Float", "Byte", "Short", "Char", + "Unit", "Array", "List", "Set", "Map", + ) + + private fun escapeSealedVariantName(name: String): String = + if (name in KOTLIN_BUILTIN_NAMES) "${name}Value" else name + + private fun generateSealedEnumProxy(sealed: KneSealedEnum, module: KneModule, pkg: String): String = buildString { + val lib = module.libName + val sym = "${lib}_${sealed.simpleName}" + + appendLine("// Auto-generated by kotlin-native-export plugin. Do not modify.") + appendLine("package $pkg") + appendLine() + appendLine("import java.lang.foreign.Arena") + appendLine("import java.lang.foreign.FunctionDescriptor") + appendLine("import java.lang.foreign.MemorySegment") + appendLine("import java.lang.foreign.ValueLayout.*") + appendLine() + appendLine("sealed class ${sealed.simpleName}(internal val handle: Long, ownsHandle: Boolean = true) : AutoCloseable {") + appendLine(" @Volatile private var _disposed = false") + appendLine(" @Volatile private var _ownsHandle = ownsHandle") + appendLine() + + // Tag constants + appendLine(" enum class Tag { ${sealed.variants.joinToString(", ") { escapeSealedVariantName(it.name) }}; }") + appendLine() + appendLine(" val tag: Tag get() = Tag.entries[TAG_HANDLE.invoke(handle) as Int]") + appendLine() + + // Variant subclasses + for (variant in sealed.variants) { + if (variant.fields.isEmpty()) { + appendLine(" class ${escapeSealedVariantName(variant.name)}(handle: Long, ownsHandle: Boolean = true) : ${sealed.simpleName}(handle, ownsHandle)") + } else { + appendLine(" class ${escapeSealedVariantName(variant.name)}(handle: Long, ownsHandle: Boolean = true) : ${sealed.simpleName}(handle, ownsHandle) {") + for (f in variant.fields) { + if (f.type is KneType.TUPLE) continue // Skip tuple fields (not yet bridgeable) + val getterHandle = "${variant.name.uppercase()}_GET_${f.name.uppercase()}_HANDLE" + appendSealedEnumFieldGetter(" ", f, getterHandle, pkg) + } + appendLine(" }") + } + appendLine() + } + + // close / dispose + appendLine(" internal fun _consumeHandle(): Long {") + appendLine(" if (_disposed) error(\"${sealed.simpleName} handle already disposed\")") + appendLine(" if (_ownsHandle) {") + appendLine(" _disposed = true") + appendLine(" _ownsHandle = false") + appendLine(" }") + appendLine(" return handle") + appendLine(" }") + appendLine() + appendLine(" override fun close() {") + appendLine(" if (_disposed) return") + appendLine(" val shouldDispose = _ownsHandle") + appendLine(" _disposed = true") + appendLine(" _ownsHandle = false") + appendLine(" if (shouldDispose) DISPOSE_HANDLE.invoke(handle)") + appendLine(" }") + appendLine() + + // companion object with fromHandle and factory methods + appendLine(" companion object {") + appendLine(" private fun create(handle: Long, ownsHandle: Boolean): ${sealed.simpleName} {") + appendLine(" val tag = TAG_HANDLE.invoke(handle) as Int") + appendLine(" return when (tag) {") + for ((i, variant) in sealed.variants.withIndex()) { + appendLine(" $i -> ${escapeSealedVariantName(variant.name)}(handle, ownsHandle)") + } + appendLine(" else -> error(\"Unknown ${sealed.simpleName} tag: \$tag\")") + appendLine(" }") + appendLine(" }") + appendLine() + appendLine(" internal fun fromHandle(handle: Long): ${sealed.simpleName} = create(handle, ownsHandle = true)") + appendLine() + appendLine(" internal fun fromBorrowedHandle(handle: Long): ${sealed.simpleName} = create(handle, ownsHandle = false)") + appendLine() + + // Factory methods for creating variants (skip those with tuple-typed fields) + for (variant in sealed.variants.filter { v -> v.fields.none { it.type is KneType.TUPLE } }) { + if (variant.fields.isEmpty()) { + appendLine(" fun ${variant.name.replaceFirstChar { it.lowercase() }}(): ${escapeSealedVariantName(variant.name)} {") + appendLine(" val h = NEW_${variant.name.uppercase()}_HANDLE.invoke() as Long") + appendLine(" KneRuntime.checkError()") + appendLine(" return ${escapeSealedVariantName(variant.name)}(h)") + appendLine(" }") + } else { + val params = variant.fields.joinToString(", ") { "${it.name}: ${paramJvmTypeName(it.type)}" } + appendLine(" fun ${variant.name.replaceFirstChar { it.lowercase() }}($params): ${escapeSealedVariantName(variant.name)} {") + val hasCallbacks = variant.fields.any { it.type.isFunctionType() } + val needsArena = needsConfinedArena(variant.fields, KneType.UNIT) || + variant.fields.any { it.type == KneType.BYTE_ARRAY || it.type.isCollection() } || hasCallbacks + if (needsArena) { + appendLine(" Arena.ofConfined().use { arena ->") + if (hasCallbacks) appendCallbackStubAlloc(" ", variant.fields, "arena") + appendStringInvokeArgsAlloc(" ", variant.fields) + appendCollectionParamAlloc(" ", variant.fields) + val invokeArgs = variant.fields.flatMap { f -> buildExpandedInvokeArgs(f) }.joinToString(", ") + appendLine(" val h = NEW_${variant.name.uppercase()}_HANDLE.invoke($invokeArgs) as Long") + appendLine(" KneRuntime.checkError()") + appendLine(" return ${escapeSealedVariantName(variant.name)}(h)") + appendLine(" }") + } else { + val invokeArgs = variant.fields.flatMap { f -> buildExpandedInvokeArgs(f) }.joinToString(", ") + appendLine(" val h = NEW_${variant.name.uppercase()}_HANDLE.invoke($invokeArgs) as Long") + appendLine(" KneRuntime.checkError()") + appendLine(" return ${escapeSealedVariantName(variant.name)}(h)") + } + appendLine(" }") + } + appendLine() + } + + // MethodHandles via KneRuntime + appendLine(" private val TAG_HANDLE = KneRuntime.handle(") + appendLine(" \"${sym}_tag\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG))") + appendLine() + appendLine(" private val DISPOSE_HANDLE = KneRuntime.handle(") + appendLine(" \"${sym}_dispose\", FunctionDescriptor.ofVoid(JAVA_LONG))") + appendLine() + + // Constructor handles (skip variants with tuple fields) + for (variant in sealed.variants.filter { v -> v.fields.none { it.type is KneType.TUPLE } }) { + val newSym = "${sym}_new_${variant.name}" + if (variant.fields.isEmpty()) { + appendLine(" private val NEW_${variant.name.uppercase()}_HANDLE = KneRuntime.handle(") + appendLine(" \"$newSym\", FunctionDescriptor.of(JAVA_LONG))") + } else { + val layouts = variant.fields.flatMap { f -> + when (f.type) { + KneType.STRING -> listOf("ADDRESS") + is KneType.LIST, is KneType.SET -> listOf("ADDRESS", "JAVA_INT") + is KneType.MAP -> { + val mapType = f.type as KneType.MAP + buildList { + add("ADDRESS") + if (mapType.keyType == KneType.STRING) add("JAVA_INT") + add("ADDRESS") + if (mapType.valueType == KneType.STRING) add("JAVA_INT") + add("JAVA_INT") + } + } + else -> listOf(f.type.ffmLayout) + } + } + appendLine(" private val NEW_${variant.name.uppercase()}_HANDLE = KneRuntime.handle(") + appendLine(" \"$newSym\", FunctionDescriptor.of(JAVA_LONG, ${layouts.joinToString(", ")}))") + } + appendLine() + } + + // Field getter handles + for (variant in sealed.variants) { + for (f in variant.fields) { + val getSym = "${sym}_${variant.name}_get_${f.name}" + val handleName = "${variant.name.uppercase()}_GET_${f.name.uppercase()}_HANDLE" + when (f.type) { + KneType.STRING -> { + appendLine(" private val $handleName = KneRuntime.handle(") + appendLine(" \"$getSym\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + } + is KneType.LIST, is KneType.SET -> { + appendLine(" private val $handleName = KneRuntime.handle(") + appendLine(" \"$getSym\", FunctionDescriptor.of(JAVA_INT, JAVA_LONG, ADDRESS, JAVA_INT))") + } + is KneType.MAP -> { + val mapType = f.type as KneType.MAP + val params = buildList { + add("JAVA_LONG") + add("ADDRESS") // out_keys + if (mapType.keyType == KneType.STRING) add("JAVA_INT") + add("ADDRESS") // out_values + if (mapType.valueType == KneType.STRING) add("JAVA_INT") + add("JAVA_INT") // out_max_len + }.joinToString(", ") + appendLine(" private val $handleName = KneRuntime.handle(") + appendLine(" \"$getSym\", FunctionDescriptor.of(JAVA_INT, $params))") + } + else -> { + appendLine(" private val $handleName = KneRuntime.handle(") + appendLine(" \"$getSym\", FunctionDescriptor.of(${f.type.ffmLayout}, JAVA_LONG))") + } + } + appendLine() + } + } + + appendLine(" }") // end companion + appendLine("}") // end sealed class + } + + private fun StringBuilder.appendSealedEnumFieldGetter( + indent: String, + field: KneParam, + getterHandle: String, + pkg: String = "", + ) { + when (field.type) { + KneType.STRING -> { + appendLine("${indent}val ${field.name}: ${field.type.jvmTypeName}") + appendLine("${indent} get() {") + appendLine("${indent} var _bufSize = 256") + appendLine("${indent} while (true) {") + appendLine("${indent} val _arena = Arena.ofConfined()") + appendLine("${indent} val _buf = _arena.allocate(_bufSize.toLong())") + appendLine("${indent} val _len = $getterHandle.invoke(handle, _buf, _bufSize) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} if (_len <= _bufSize) { val s = _buf.getString(0); _arena.close(); return s }") + appendLine("${indent} _arena.close()") + appendLine("${indent} _bufSize = _len") + appendLine("${indent} }") + appendLine("${indent} }") + } + is KneType.LIST, is KneType.SET -> { + val collType = when (field.type) { + is KneType.LIST -> field.type as KneType.LIST + is KneType.SET -> KneType.LIST((field.type as KneType.SET).elementType) + else -> KneType.LIST(KneType.INT) + } + val collTypeStr = if (field.type is KneType.SET) "Set" else "List" + appendLine("${indent}val ${field.name}: ${field.type.jvmTypeName}") + appendLine("${indent} get() {") + appendLine("${indent} Arena.ofConfined().use { arena ->") + when (collType.elementType) { + KneType.STRING -> { + appendLine("${indent} var _bufSize = $STRING_BUF_SIZE") + appendLine("${indent} var _outBuf = arena.allocate(_bufSize.toLong())") + appendLine("${indent} var _count = $getterHandle.invoke(handle, _outBuf, _bufSize) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} if (_count < 0) {") + appendLine("${indent} _bufSize = -_count") + appendLine("${indent} _outBuf = arena.allocate(_bufSize.toLong())") + appendLine("${indent} _count = $getterHandle.invoke(handle, _outBuf, _bufSize) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} }") + appendLine("${indent} val _list = mutableListOf()") + appendLine("${indent} var _off = 0L") + appendLine("${indent} repeat(_count) { _list.add(_outBuf.getString(_off)); _off += _list.last().toByteArray(Charsets.UTF_8).size + 1 }") + if (collTypeStr == "Set") appendLine("${indent} return _list.toSet()") + else appendLine("${indent} return _list") + } + else -> { + val layout = KneType.collectionElementLayout(collType.elementType) + appendLine("${indent} val _outBuf = arena.allocate($layout, $MAX_COLLECTION_SIZE.toLong())") + appendLine("${indent} val _count = $getterHandle.invoke(handle, _outBuf, $MAX_COLLECTION_SIZE) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendCollectionElementRead("${indent} ", collType.elementType, "_count", collTypeStr) + } + } + appendLine("${indent} }") + appendLine("${indent} }") + } + is KneType.MAP -> { + val mapType = field.type as KneType.MAP + val isKeyString = mapType.keyType == KneType.STRING + val isValString = mapType.valueType == KneType.STRING + appendLine("${indent}val ${field.name}: ${field.type.jvmTypeName}") + appendLine("${indent} get() {") + appendLine("${indent} Arena.ofConfined().use { arena ->") + val kLayout = KneType.collectionElementLayout(mapType.keyType) + val vLayout = KneType.collectionElementLayout(mapType.valueType) + if (isKeyString) appendLine("${indent} val _keysBuf = arena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine("${indent} val _keysBuf = arena.allocate($kLayout, $MAX_COLLECTION_SIZE.toLong())") + if (isValString) appendLine("${indent} val _valuesBuf = arena.allocate($STRING_BUF_SIZE.toLong())") + else appendLine("${indent} val _valuesBuf = arena.allocate($vLayout, $MAX_COLLECTION_SIZE.toLong())") + val invokeArgs = buildList { + add("handle") + add("_keysBuf") + if (isKeyString) add("$STRING_BUF_SIZE") + add("_valuesBuf") + if (isValString) add("$STRING_BUF_SIZE") + add("$MAX_COLLECTION_SIZE") + }.joinToString(", ") + appendLine("${indent} val _count = $getterHandle.invoke($invokeArgs) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} val _map = mutableMapOf<${mapType.keyType.jvmTypeName}, ${mapType.valueType.jvmTypeName}>()") + // Read keys + if (isKeyString) { + appendLine("${indent} val _keys = mutableListOf()") + appendLine("${indent} var _kOff = 0L") + appendLine("${indent} repeat(_count) { _keys.add(_keysBuf.getString(_kOff)); _kOff += _keys.last().toByteArray(Charsets.UTF_8).size + 1 }") + } else { + appendMapElementRead("${indent} ", "_keys", mapType.keyType, "_count", "_keysBuf") + } + // Read values + if (isValString) { + appendLine("${indent} val _values = mutableListOf()") + appendLine("${indent} var _vOff = 0L") + appendLine("${indent} repeat(_count) { _values.add(_valuesBuf.getString(_vOff)); _vOff += _values.last().toByteArray(Charsets.UTF_8).size + 1 }") + } else { + appendMapElementRead("${indent} ", "_values", mapType.valueType, "_count", "_valuesBuf") + } + appendLine("${indent} repeat(_count) { _map[_keys[it]] = _values[it] }") + appendLine("${indent} return _map") + appendLine("${indent} }") + appendLine("${indent} }") + } + KneType.BOOLEAN -> { + appendLine("${indent}val ${field.name}: Boolean") + appendLine("${indent} get() {") + appendLine("${indent} val _r = $getterHandle.invoke(handle) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} return _r != 0") + appendLine("${indent} }") + } + KneType.INT -> appendPrimitiveSealedGetter(indent, field.name, getterHandle, "Int", "Int") + KneType.LONG -> appendPrimitiveSealedGetter(indent, field.name, getterHandle, "Long", "Long") + KneType.DOUBLE -> appendPrimitiveSealedGetter(indent, field.name, getterHandle, "Double", "Double") + KneType.FLOAT -> appendPrimitiveSealedGetter(indent, field.name, getterHandle, "Float", "Float") + KneType.BYTE -> appendPrimitiveSealedGetter(indent, field.name, getterHandle, "Byte", "Byte") + KneType.SHORT -> appendPrimitiveSealedGetter(indent, field.name, getterHandle, "Short", "Short") + is KneType.OBJECT -> { + // Use FQ name to avoid shadowing by inner variant classes + val fqRef = if (pkg.isNotEmpty()) "$pkg.${field.type.simpleName}" else field.type.simpleName + appendLine("${indent}val ${field.name}: $fqRef") + appendLine("${indent} get() {") + appendLine("${indent} val _r = $getterHandle.invoke(handle) as Long") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} return $fqRef.fromBorrowedHandle(_r)") + appendLine("${indent} }") + } + is KneType.SEALED_ENUM -> { + val fqRef = if (pkg.isNotEmpty()) "$pkg.${field.type.simpleName}" else field.type.simpleName + appendLine("${indent}val ${field.name}: $fqRef") + appendLine("${indent} get() {") + appendLine("${indent} val _r = $getterHandle.invoke(handle) as Long") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} return $fqRef.fromBorrowedHandle(_r)") + appendLine("${indent} }") + } + is KneType.ENUM -> { + val fqRef = if (pkg.isNotEmpty()) "$pkg.${field.type.simpleName}" else field.type.simpleName + appendLine("${indent}val ${field.name}: $fqRef") + appendLine("${indent} get() {") + appendLine("${indent} val _r = $getterHandle.invoke(handle) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} return $fqRef.entries[_r]") + appendLine("${indent} }") + } + is KneType.NULLABLE -> { + appendLine("${indent}val ${field.name}: ${field.type.jvmTypeName}") + appendLine("${indent} get() = error(\"Nullable sealed enum fields are not supported yet\")") + } + else -> { + appendLine("${indent}val ${field.name}: ${field.type.jvmTypeName}") + appendLine("${indent} get() = error(\"Unsupported sealed enum field type: ${field.type.jvmTypeName}\")") + } + } + } + + private fun StringBuilder.appendPrimitiveSealedGetter( + indent: String, + fieldName: String, + getterHandle: String, + typeName: String, + castType: String, + ) { + appendLine("${indent}val $fieldName: $typeName") + appendLine("${indent} get() {") + appendLine("${indent} val _r = $getterHandle.invoke(handle) as $castType") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent} return _r") + appendLine("${indent} }") + } + // ── Top-level function object ──────────────────────────────────────────── private fun generateFunctionObject( @@ -2764,7 +4273,7 @@ class FfmProxyGenerator { appendLine("import java.lang.invoke.MethodHandle") appendLine() - val objectHasCallbacks = fns.any { fn -> fn.params.any { it.type is KneType.FUNCTION } } + val objectHasCallbacks = fns.any { fn -> fn.params.any { paramIsFunction(it) } } appendLine("object $objectName {") if (objectHasCallbacks) { @@ -2781,25 +4290,38 @@ class FfmProxyGenerator { } appendLine() - fns.forEach { fn -> + fns.forEach { origFn -> + // Sanitize param names that are Kotlin keywords (e.g. "val" → "val_") + val fn = if (origFn.params.any { it.name in KOTLIN_KEYWORDS }) { + origFn.copy(params = origFn.params.map { it.copy(name = sanitizeParamName(it.name)) }) + } else origFn val handleName = "${fn.name.uppercase()}_HANDLE" - val params = fn.params.joinToString(", ") { "${it.name}: ${it.type.jvmTypeName}" } + val params = fn.params.joinToString(", ") { p -> + "${p.name}: ${paramJvmTypeName(p.type)}" + } appendLine(" fun ${fn.name}($params): ${fn.returnType.jvmTypeName} {") appendCallbackStubAlloc(" ", fn.params, "_callbackArena") val arenaNeeded = needsConfinedArena(fn.params, fn.returnType) - if (arenaNeeded) { + val hasCollectionParams = fn.params.any { it.type == KneType.BYTE_ARRAY || it.type is KneType.LIST || it.type is KneType.SET } + val returnsCollection = fn.returnType.isCollection() + if (arenaNeeded || hasCollectionParams || returnsCollection) { appendLine(" Arena.ofConfined().use { arena ->") appendStringInvokeArgsAlloc(" ", fn.params) + appendCollectionParamAlloc(" ", fn.params) val invokeArgs = buildTopLevelInvokeArgs(fn) - appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs) + if (returnsCollection) { + appendCollectionReturnProxy(" ", fn, handleName, invokeArgs) + } else { + appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs, fn.returnsBorrowed) + } appendLine(" }") } else { val invokeArgs = fn.params.joinToString(", ") { fp -> - buildJvmInvokeArg(fp.name, fp.type) + buildJvmInvokeArg(fp) } - appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs) + appendCallAndReturn(" ", fn.returnType, handleName, invokeArgs, fn.returnsBorrowed) } appendLine(" }") @@ -2811,6 +4333,38 @@ class FfmProxyGenerator { // ── Descriptor builders ────────────────────────────────────────────────── + private fun expandTupleParamLayouts(tuple: KneType.TUPLE, builder: MutableList, isOutParam: Boolean = false) { + for (elemType in tuple.elementTypes) { + when (elemType) { + is KneType.TUPLE -> { + if (isOutParam) { + builder.add("ADDRESS") // nested tuple: single pointer to the boxed tuple + } else { + expandTupleParamLayouts(elemType, builder, isOutParam) + } + } + KneType.STRING -> { builder.add("ADDRESS"); builder.add("JAVA_INT") } + KneType.BYTE_ARRAY -> { builder.add("ADDRESS"); builder.add("JAVA_INT") } + is KneType.LIST, is KneType.SET -> { + if (isOutParam) { + builder.add("ADDRESS"); builder.add("JAVA_INT"); builder.add("ADDRESS") // buf, cap, len_out + } else { + builder.add("ADDRESS"); builder.add("JAVA_INT") // ptr, len + } + } + is KneType.MAP -> { + if (isOutParam) { + builder.add("ADDRESS"); builder.add("JAVA_INT"); builder.add("ADDRESS"); builder.add("JAVA_INT"); builder.add("ADDRESS") + } else { + builder.add("ADDRESS"); builder.add("JAVA_INT"); builder.add("ADDRESS"); builder.add("JAVA_INT"); builder.add("JAVA_INT") + } + } + KneType.INT, KneType.LONG, KneType.BOOLEAN, KneType.BYTE, KneType.SHORT, KneType.FLOAT, KneType.DOUBLE -> builder.add(if (isOutParam) "ADDRESS" else elemType.ffmLayout) + else -> builder.add(if (isOutParam) "ADDRESS" else elemType.ffmLayout) + } + } + } + private fun buildMethodDescriptor(fn: KneFunction): String { val returnDc = extractDataClass(fn.returnType) val returnsNullableDc = fn.returnType is KneType.NULLABLE && fn.returnType.inner is KneType.DATA_CLASS @@ -2830,7 +4384,7 @@ class FfmProxyGenerator { } } else if (p.type == KneType.BYTE_ARRAY) { add("ADDRESS"); add("JAVA_INT") - } else if (p.type.isCollection()) { + } else if (p.type.isCollection()) { val inner = p.type.unwrapCollection() when (inner) { is KneType.LIST -> if (inner.elementType is KneType.DATA_CLASS) add("JAVA_LONG") else { add("ADDRESS"); add("JAVA_INT") } @@ -2838,6 +4392,8 @@ class FfmProxyGenerator { is KneType.MAP -> { add("ADDRESS"); add("ADDRESS"); add("JAVA_INT") } else -> {} } + } else if (p.type is KneType.TUPLE) { + expandTupleParamLayouts(p.type as KneType.TUPLE, this) } else { add(p.type.ffmLayout) } @@ -2888,6 +4444,13 @@ class FfmProxyGenerator { else -> {} } } + // Tuple return out-params — expand elements to individual params + val tupleReturn = if (fn.returnType is KneType.TUPLE) fn.returnType as KneType.TUPLE + else if (fn.returnType is KneType.NULLABLE && (fn.returnType as KneType.NULLABLE).inner is KneType.TUPLE) (fn.returnType as KneType.NULLABLE).inner as KneType.TUPLE + else null + if (!skipReturnParams && tupleReturn != null) { + expandTupleParamLayouts(tupleReturn, this, isOutParam = true) + } } if (fn.isSuspend) return buildDescriptor(KneType.UNIT, paramLayouts) if (fn.returnType is KneType.FLOW) return buildDescriptor(KneType.UNIT, paramLayouts) @@ -2903,6 +4466,9 @@ class FfmProxyGenerator { returnDc != null -> KneType.UNIT isDcColl -> KneType.LONG // opaque handle fn.returnType.isCollection() -> KneType.INT // element count + fn.returnType == KneType.NEVER -> KneType.UNIT // diverging - never returns normally + fn.returnType is KneType.TUPLE -> KneType.UNIT // tuple returned via out-params + fn.returnType is KneType.NULLABLE && (fn.returnType as KneType.NULLABLE).inner is KneType.TUPLE -> KneType.INT // nullable tuple: 0=null, 1=present else -> fn.returnType } return buildDescriptor(effectiveReturn, paramLayouts) @@ -2912,6 +4478,7 @@ class FfmProxyGenerator { val isCollProp = prop.type.isCollection() val returnDc = extractDataClass(prop.type) val returnsNullableDc = prop.type is KneType.NULLABLE && prop.type.inner is KneType.DATA_CLASS + val isMapProp = isCollProp && prop.type.unwrapCollection() is KneType.MAP val paramLayouts = buildList { add("JAVA_LONG") if (!isCollProp && prop.type.returnsViaBuffer()) { @@ -2927,12 +4494,21 @@ class FfmProxyGenerator { } } } + // MAP property: direct approach — add key/value buffer out-params + if (isMapProp) { + val mapType = prop.type.unwrapCollection() as KneType.MAP + add("ADDRESS") // keys buf + if (mapType.keyType == KneType.STRING) add("JAVA_INT") + add("ADDRESS") // values buf + if (mapType.valueType == KneType.STRING) add("JAVA_INT") + add("JAVA_INT") // max len + } } - // Collection getters return JAVA_LONG (StableRef handle) val effectiveReturn = when { returnDc != null && returnsNullableDc -> KneType.INT // 0=null, 1=present returnDc != null -> KneType.UNIT - isCollProp -> KneType.LONG + isMapProp -> KneType.INT // MAP property: direct approach, returns count + isCollProp -> KneType.LONG // LIST/SET: StableRef handle else -> prop.type } return buildDescriptor(effectiveReturn, paramLayouts) @@ -2948,18 +4524,83 @@ class FfmProxyGenerator { } private fun buildTopLevelDescriptor(fn: KneFunction): String { + val returnDc = extractDataClass(fn.returnType) val paramLayouts = buildList { - fn.params.forEach { p -> add(p.type.ffmLayout) } + fn.params.forEach { p -> + if (p.type == KneType.BYTE_ARRAY) { + add("ADDRESS"); add("JAVA_INT") + } else if (p.type is KneType.LIST || p.type is KneType.SET) { + add("ADDRESS"); add("JAVA_INT") + } else if (p.type is KneType.OBJECT && (p.type as KneType.OBJECT).simpleName in simpleEnumTypeNames) { + add("JAVA_INT") // OBJECT→ENUM redirect: use ordinal layout + } else { + add(p.type.ffmLayout) + } + } if (fn.returnType.returnsViaBuffer()) { add("ADDRESS"); add("JAVA_INT") } + // Data class return: add per-field out-param layouts + if (returnDc != null) { + flattenDcFields(returnDc, "").forEach { (_, type) -> + when (type) { + KneType.STRING -> { add("ADDRESS"); add("JAVA_INT") } + KneType.BYTE_ARRAY -> add("ADDRESS") + else -> add("ADDRESS") + } + } + } + // Collection return out-params + if (fn.returnType.isCollection()) { + val collInner = fn.returnType.unwrapCollection() + when (collInner) { + is KneType.LIST, is KneType.SET -> { + if (collInner is KneType.LIST && collInner.elementType is KneType.DATA_CLASS || + collInner is KneType.SET && collInner.elementType is KneType.DATA_CLASS) { + // opaque handle + } else { + add("ADDRESS"); add("JAVA_INT") + } + } + is KneType.MAP -> { + add("ADDRESS") // outKeys + if (collInner.keyType == KneType.STRING) add("JAVA_INT") + add("ADDRESS") // outValues + if (collInner.valueType == KneType.STRING) add("JAVA_INT") + add("JAVA_INT") // outLen + } + else -> {} + } + } + } + // Compute effective return type: collections return element count, DC returns void + val isDcColl = fn.returnType.isCollection() && run { + val ci = fn.returnType.unwrapCollection() + val ce = when (ci) { is KneType.LIST -> ci.elementType; is KneType.SET -> ci.elementType; else -> null } + ce is KneType.DATA_CLASS + } + // OBJECT→ENUM redirect: if the return type is OBJECT but it's actually an enum, + // the Rust bridge returns i32 (ordinal), so use ENUM layout in the descriptor. + val redirectedReturn = when { + fn.returnType is KneType.OBJECT && (fn.returnType as KneType.OBJECT).simpleName in simpleEnumTypeNames -> + KneType.ENUM((fn.returnType as KneType.OBJECT).fqName, (fn.returnType as KneType.OBJECT).simpleName) + fn.returnType is KneType.NULLABLE && fn.returnType.inner is KneType.OBJECT && (fn.returnType.inner as KneType.OBJECT).simpleName in simpleEnumTypeNames -> + KneType.NULLABLE(KneType.ENUM((fn.returnType.inner as KneType.OBJECT).fqName, (fn.returnType.inner as KneType.OBJECT).simpleName)) + else -> fn.returnType + } + val effectiveReturn = when { + returnDc != null -> KneType.UNIT + isDcColl -> KneType.LONG // opaque handle + redirectedReturn.isCollection() -> KneType.INT // element count + redirectedReturn == KneType.NEVER -> KneType.UNIT // diverging - never returns normally + else -> redirectedReturn } - return buildDescriptor(fn.returnType, paramLayouts) + return buildDescriptor(effectiveReturn, paramLayouts) } private fun buildDescriptor(returnType: KneType, paramLayouts: List): String { val params = paramLayouts.filter { it.isNotEmpty() }.joinToString(", ") - return if (returnType == KneType.UNIT || returnType.returnsViaBuffer()) { + return if (returnType == KneType.UNIT || returnType == KneType.NEVER || returnType.returnsViaBuffer() || returnType is KneType.DATA_CLASS || returnType is KneType.TUPLE) { val retLayout = if (returnType.returnsViaBuffer()) "JAVA_INT" else "" if (retLayout.isEmpty()) "FunctionDescriptor.ofVoid($params)" else "FunctionDescriptor.of($retLayout${if (params.isNotEmpty()) ", $params" else ""})" @@ -2998,13 +4639,23 @@ class FfmProxyGenerator { // ── Invoke arg builders ────────────────────────────────────────────────── - private fun buildJvmInvokeArg(name: String, type: KneType): String = when (type) { + private fun buildReceiverInvokeArg(fn: KneFunction): String = "handle" + + private fun buildOwnedHandleArg(name: String, isBorrowed: Boolean): String = + if (isBorrowed) "$name.handle" else "$name._consumeHandle()" + + private fun buildJvmInvokeArg(param: KneParam): String = + buildJvmInvokeArg(param.name, param.type, param.isBorrowed) + + private fun buildJvmInvokeArg(name: String, type: KneType, isBorrowed: Boolean = false): String = when (type) { KneType.STRING -> "${name}Seg" KneType.BYTE_ARRAY -> "${name}Seg" KneType.BOOLEAN -> "if ($name) 1 else 0" - is KneType.OBJECT -> "$name.handle" + is KneType.OBJECT -> if (type.simpleName in simpleEnumTypeNames) "$name.ordinal" else buildOwnedHandleArg(name, isBorrowed) + is KneType.INTERFACE -> "$name.handle" // dyn Trait param: pass registry handle + is KneType.SEALED_ENUM -> buildOwnedHandleArg(name, isBorrowed) is KneType.ENUM -> "$name.ordinal" - is KneType.NULLABLE -> buildNullableJvmInvokeArg(name, type) + is KneType.NULLABLE -> buildNullableJvmInvokeArg(name, type, isBorrowed) is KneType.FUNCTION -> "${name}Stub" is KneType.LIST -> "${name}Seg" is KneType.SET -> "${name}Seg" @@ -3012,7 +4663,7 @@ class FfmProxyGenerator { else -> name } - private fun buildNullableJvmInvokeArg(name: String, type: KneType.NULLABLE): String = when (type.inner) { + private fun buildNullableJvmInvokeArg(name: String, type: KneType.NULLABLE, isBorrowed: Boolean = false): String = when (type.inner) { KneType.STRING -> "${name}Seg" KneType.BOOLEAN -> "if ($name == null) -1 else if ($name) 1 else 0" KneType.INT -> "$name?.toLong() ?: Long.MIN_VALUE" @@ -3021,7 +4672,8 @@ class FfmProxyGenerator { KneType.BYTE -> "$name?.toInt() ?: Int.MIN_VALUE" KneType.FLOAT -> "if ($name != null) $name.toRawBits().toLong() else Long.MIN_VALUE" KneType.DOUBLE -> "if ($name != null) $name.toRawBits() else Long.MIN_VALUE" - is KneType.OBJECT -> "$name?.handle ?: 0L" + is KneType.OBJECT -> if ((type.inner as KneType.OBJECT).simpleName in simpleEnumTypeNames) "$name?.ordinal ?: -1" else if (isBorrowed) "$name?.handle ?: 0L" else "$name?._consumeHandle() ?: 0L" + is KneType.SEALED_ENUM -> if (isBorrowed) "$name?.handle ?: 0L" else "$name?._consumeHandle() ?: 0L" is KneType.ENUM -> "$name?.ordinal ?: -1" is KneType.FUNCTION -> "${name}Stub" else -> name @@ -3029,34 +4681,72 @@ class FfmProxyGenerator { private fun buildCtorInvokeArgs(params: List): String { if (params.isEmpty()) return "" - return params.joinToString(", ") { p -> buildJvmInvokeArg(p.name, p.type) } + return params.joinToString(", ") { p -> buildJvmInvokeArg(p) } } private fun buildClassInvokeArgs(fn: KneFunction): String { val args = buildList { - add("handle") - fn.params.forEach { p -> add(buildJvmInvokeArg(p.name, p.type)) } + add(buildReceiverInvokeArg(fn)) + fn.params.forEach { p -> add(buildJvmInvokeArg(p)) } } return args.joinToString(", ") } private fun buildClassInvokeArgsDirect(fn: KneFunction): String { val args = buildList { - add("handle") - fn.params.forEach { p -> add(buildJvmInvokeArg(p.name, p.type)) } + add(buildReceiverInvokeArg(fn)) + fn.params.forEach { p -> add(buildJvmInvokeArg(p)) } } return args.joinToString(", ") } private fun buildTopLevelInvokeArgs(fn: KneFunction): String { val args = buildList { - fn.params.forEach { p -> add(buildJvmInvokeArg(p.name, p.type)) } + fn.params.forEach { p -> + when { + p.type == KneType.BYTE_ARRAY -> { + add("${p.name}Seg") + add("${p.name}.size") + } + p.type is KneType.LIST -> { + val elemType = (p.type as KneType.LIST).elementType + if (elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY) { + add("${p.name}PtrSeg") + add("${p.name}.size") + } else { + add("${p.name}Seg") + add("${p.name}.size") + } + } + p.type is KneType.SET -> { + val elemType = (p.type as KneType.SET).elementType + if (elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY) { + add("${p.name}PtrSeg") + add("${p.name}.size") + } else { + add("${p.name}Seg") + add("${p.name}.size") + } + } + else -> add(buildJvmInvokeArg(p)) + } + } } return args.joinToString(", ") } // ── Code emission helpers ──────────────────────────────────────────────── + /** + * Emits copy-back code for mutable ByteArray params (&mut [u8]). + * Must be called AFTER the native invoke, while the arena is still alive. + */ + private fun StringBuilder.appendByteArrayCopyBack(indent: String, params: List) { + params.filter { it.type == KneType.BYTE_ARRAY && it.rustType?.startsWith("&mut ") == true }.forEach { p -> + appendLine("${indent}MemorySegment.copy(${p.name}Seg, JAVA_BYTE, 0, ${p.name}, 0, ${p.name}.size)") + } + } + private fun StringBuilder.appendStringInvokeArgsAlloc(indent: String, params: List) { params.filter { it.type == KneType.STRING }.forEach { p -> appendLine("${indent}val ${p.name}Seg = arena.allocateFrom(${p.name})") @@ -3068,6 +4758,27 @@ class FfmProxyGenerator { params.filter { it.type is KneType.NULLABLE && (it.type as KneType.NULLABLE).inner == KneType.STRING }.forEach { p -> appendLine("${indent}val ${p.name}Seg = if (${p.name} != null) arena.allocateFrom(${p.name}) else MemorySegment.NULL") } + // Allocate String/ByteArray fields from tuple params + params.filter { it.type is KneType.TUPLE }.forEach { p -> + val tuple = p.type as KneType.TUPLE + tuple.elementTypes.forEachIndexed { idx, type -> + when (type) { + KneType.STRING -> appendLine("${indent}val ${p.name}_${idx}Seg = arena.allocateFrom(${p.name}._$idx)") + KneType.BYTE_ARRAY -> appendLine("${indent}val ${p.name}_${idx}Seg = arena.allocate(${p.name}._$idx.size.toLong())") + else -> {} + } + } + } + params.filter { it.type is KneType.NULLABLE && (it.type as KneType.NULLABLE).inner is KneType.TUPLE }.forEach { p -> + val tuple = (p.type as KneType.NULLABLE).inner as KneType.TUPLE + tuple.elementTypes.forEachIndexed { idx, type -> + when (type) { + KneType.STRING -> appendLine("${indent}val ${p.name}_${idx}Seg = if (${p.name} != null) arena.allocateFrom(${p.name}._$idx) else MemorySegment.NULL") + KneType.BYTE_ARRAY -> appendLine("${indent}val ${p.name}_${idx}Seg = if (${p.name} != null) arena.allocate(${p.name}._$idx.size.toLong()) else MemorySegment.NULL") + else -> {} + } + } + } // Allocate String fields from data class params (including nullable) params.forEach { p -> val dc = extractDataClass(p.type) ?: return@forEach @@ -3292,11 +5003,15 @@ class FfmProxyGenerator { private fun StringBuilder.appendListParamAlloc(indent: String, name: String, elemType: KneType, srcExpr: String = name) { when (elemType) { KneType.STRING -> { - // Pack strings as null-terminated sequence in a single buffer - appendLine("${indent}val ${name}TotalBytes = $srcExpr.sumOf { it.toByteArray(Charsets.UTF_8).size + 1 }") - appendLine("${indent}val ${name}Seg = arena.allocate(${name}TotalBytes.toLong().coerceAtLeast(1))") - appendLine("${indent}var ${name}Off = 0L") - appendLine("${indent}for (_s in $srcExpr) { ${name}Seg.setString(${name}Off, _s); ${name}Off += _s.toByteArray(Charsets.UTF_8).size + 1 }") + // Allocate array of pointers to null-terminated strings + appendLine("${indent}val ${name}PtrSeg = arena.allocate(ADDRESS, $srcExpr.size.toLong())") + appendLine("${indent}$srcExpr.forEachIndexed { i, _s ->") + appendLine("${indent} val _sBytes = _s.toByteArray(Charsets.UTF_8)") + appendLine("${indent} val _sSeg = arena.allocate(_sBytes.size.toLong() + 1)") + appendLine("${indent} MemorySegment.copy(_sBytes, 0, _sSeg, JAVA_BYTE, 0, _sBytes.size)") + appendLine("${indent} _sSeg.set(JAVA_BYTE, _sBytes.size.toLong(), 0)") + appendLine("${indent} ${name}PtrSeg.set(ADDRESS, (i * 8).toLong(), _sSeg)") + appendLine("${indent}}") } KneType.BOOLEAN -> { appendLine("${indent}val ${name}Seg = arena.allocate(JAVA_INT, $srcExpr.size.toLong())") @@ -3310,6 +5025,11 @@ class FfmProxyGenerator { appendLine("${indent}val ${name}Seg = arena.allocate(JAVA_LONG, $srcExpr.size.toLong())") appendLine("${indent}$srcExpr.forEachIndexed { i, v -> ${name}Seg.setAtIndex(JAVA_LONG, i.toLong(), v.handle) }") } + is KneType.INTERFACE -> { + val wrapper = dynWrapperLookup[elemType.fqName] ?: elemType.simpleName + appendLine("${indent}val ${name}Seg = arena.allocate(JAVA_LONG, $srcExpr.size.toLong())") + appendLine("${indent}$srcExpr.forEachIndexed { i, v -> ${name}Seg.setAtIndex(JAVA_LONG, i.toLong(), (v as $wrapper).handle) }") + } KneType.BYTE_ARRAY -> { // ByteArray elements: wrap each as StableRef handle via wrap bridge appendLine("${indent}val ${name}Seg = arena.allocate(JAVA_LONG, $srcExpr.size.toLong())") @@ -3407,21 +5127,21 @@ class FfmProxyGenerator { } /** Generate the return-proxy for collection types (handles nullable: -1 = null). */ - private fun StringBuilder.appendCollectionReturnProxy(indent: String, fn: KneFunction, handleName: String) { + private fun StringBuilder.appendCollectionReturnProxy(indent: String, fn: KneFunction, handleName: String, baseInvokeArgs: String? = null) { val isNullable = fn.returnType is KneType.NULLABLE val inner = fn.returnType.unwrapCollection() when (inner) { - is KneType.LIST -> appendListReturnProxy(indent, fn, handleName, inner.elementType, "List", isNullable) - is KneType.SET -> appendListReturnProxy(indent, fn, handleName, inner.elementType, "Set", isNullable) - is KneType.MAP -> appendMapReturnProxy(indent, fn, handleName, inner, isNullable) + is KneType.LIST -> appendListReturnProxy(indent, fn, handleName, inner.elementType, "List", isNullable, baseInvokeArgs) + is KneType.SET -> appendListReturnProxy(indent, fn, handleName, inner.elementType, "Set", isNullable, baseInvokeArgs) + is KneType.MAP -> appendMapReturnProxy(indent, fn, handleName, inner, isNullable, baseInvokeArgs) else -> {} } } - private fun StringBuilder.appendListReturnProxy(indent: String, fn: KneFunction, handleName: String, elemType: KneType, collType: String, nullable: Boolean = false) { + private fun StringBuilder.appendListReturnProxy(indent: String, fn: KneFunction, handleName: String, elemType: KneType, collType: String, nullable: Boolean = false, baseInvokeArgs: String? = null) { // List — opaque handle pattern if (elemType is KneType.DATA_CLASS) { - val invokeArgs = buildClassInvokeArgsExpanded(fn) + val invokeArgs = baseInvokeArgs ?: buildClassInvokeArgsExpanded(fn) appendLine("${indent}val _listHandle = $handleName.invoke($invokeArgs) as Long") appendLine("${indent}KneRuntime.checkError()") if (nullable) appendLine("${indent}if (_listHandle == 0L) return null") @@ -3457,13 +5177,22 @@ class FfmProxyGenerator { appendLine("${indent}}") return } + val baseArgs = baseInvokeArgs ?: buildClassInvokeArgsExpanded(fn) when (elemType) { KneType.STRING -> { - appendLine("${indent}val _outBuf = arena.allocate($STRING_BUF_SIZE.toLong())") - val invokeArgs = buildClassInvokeArgsExpanded(fn) + ", _outBuf, $STRING_BUF_SIZE" - appendLine("${indent}val _count = $handleName.invoke($invokeArgs) as Int") + appendLine("${indent}var _bufSize = $STRING_BUF_SIZE") + appendLine("${indent}var _outBuf = arena.allocate(_bufSize.toLong())") + val invokeArgs = if (baseArgs.isEmpty()) "_outBuf, _bufSize" else "$baseArgs, _outBuf, _bufSize" + appendLine("${indent}var _count = $handleName.invoke($invokeArgs) as Int") appendLine("${indent}KneRuntime.checkError()") - if (nullable) appendLine("${indent}if (_count < 0) return null") + if (nullable) appendLine("${indent}if (_count == -1) return null") + appendLine("${indent}if (_count < 0) {") + appendLine("${indent} _bufSize = -_count") + appendLine("${indent} _outBuf = arena.allocate(_bufSize.toLong())") + val retryArgs = if (baseArgs.isEmpty()) "_outBuf, _bufSize" else "$baseArgs, _outBuf, _bufSize" + appendLine("${indent} _count = $handleName.invoke($retryArgs) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent}}") appendLine("${indent}val _list = mutableListOf()") appendLine("${indent}var _off = 0L") appendLine("${indent}repeat(_count) { _list.add(_outBuf.getString(_off)); _off += _list.last().toByteArray(Charsets.UTF_8).size + 1 }") @@ -3472,11 +5201,19 @@ class FfmProxyGenerator { } else -> { val layout = KneType.collectionElementLayout(elemType) - appendLine("${indent}val _outBuf = arena.allocate($layout, $MAX_COLLECTION_SIZE.toLong())") - val invokeArgs = buildClassInvokeArgsExpanded(fn) + ", _outBuf, $MAX_COLLECTION_SIZE" - appendLine("${indent}val _count = $handleName.invoke($invokeArgs) as Int") + appendLine("${indent}var _bufSize = $MAX_COLLECTION_SIZE") + appendLine("${indent}var _outBuf = arena.allocate($layout, _bufSize.toLong())") + val invokeArgs = if (baseArgs.isEmpty()) "_outBuf, _bufSize" else "$baseArgs, _outBuf, _bufSize" + appendLine("${indent}var _count = $handleName.invoke($invokeArgs) as Int") appendLine("${indent}KneRuntime.checkError()") if (nullable) appendLine("${indent}if (_count < 0) return null") + appendLine("${indent}if (_count > _bufSize) {") + appendLine("${indent} _bufSize = _count") + appendLine("${indent} _outBuf = arena.allocate($layout, _bufSize.toLong())") + val invokeArgs2 = if (baseArgs.isEmpty()) "_outBuf, _bufSize" else "$baseArgs, _outBuf, _bufSize" + appendLine("${indent} _count = $handleName.invoke($invokeArgs2) as Int") + appendLine("${indent} KneRuntime.checkError()") + appendLine("${indent}}") appendCollectionElementRead(indent, elemType, "_count", collType) } } @@ -3491,7 +5228,17 @@ class FfmProxyGenerator { appendLine("${indent}val _list = List($countExpr) { ${elemType.simpleName}.entries[_outBuf.getAtIndex(JAVA_INT, it.toLong())] }") } is KneType.OBJECT -> { - appendLine("${indent}val _list = List($countExpr) { ${elemType.simpleName}.fromNativeHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") + // Check if this OBJECT is actually an enum that was mis-resolved during + // cross-crate parsing (e.g. FrameFormat from nokhwa_core resolved as OBJECT in nokhwa) + if (elemType.simpleName in simpleEnumTypeNames) { + appendLine("${indent}val _list = List($countExpr) { ${elemType.simpleName}.entries[_outBuf.getAtIndex(JAVA_INT, it.toLong())] }") + } else { + appendLine("${indent}val _list = List($countExpr) { ${elemType.simpleName}.fromBorrowedHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") + } + } + is KneType.INTERFACE -> { + val wrapperName = dynWrapperLookup[elemType.fqName] ?: elemType.simpleName + appendLine("${indent}val _list = List($countExpr) { $wrapperName.fromBorrowedHandle(_outBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") } KneType.BYTE_ARRAY -> { // ByteArray elements: each is a StableRef handle @@ -3535,7 +5282,7 @@ class FfmProxyGenerator { when (innerElem) { KneType.BOOLEAN -> appendLine("${indent} val _inner = List(_iCount) { _iBuf.getAtIndex(JAVA_INT, it.toLong()) != 0 }") is KneType.ENUM -> appendLine("${indent} val _inner = List(_iCount) { ${innerElem.simpleName}.entries[_iBuf.getAtIndex(JAVA_INT, it.toLong())] }") - is KneType.OBJECT -> appendLine("${indent} val _inner = List(_iCount) { ${innerElem.simpleName}.fromNativeHandle(_iBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") + is KneType.OBJECT -> appendLine("${indent} val _inner = List(_iCount) { ${innerElem.simpleName}.fromBorrowedHandle(_iBuf.getAtIndex(JAVA_LONG, it.toLong()) as Long) }") else -> appendLine("${indent} val _inner = List(_iCount) { _iBuf.getAtIndex($layout, it.toLong()) as ${innerElem.jvmTypeName} }") } } @@ -3575,7 +5322,7 @@ class FfmProxyGenerator { } } - private fun StringBuilder.appendMapReturnProxy(indent: String, fn: KneFunction, handleName: String, type: KneType.MAP, nullable: Boolean = false) { + private fun StringBuilder.appendMapReturnProxy(indent: String, fn: KneFunction, handleName: String, type: KneType.MAP, nullable: Boolean = false, baseInvokeArgs: String? = null) { val kLayout = KneType.collectionElementLayout(type.keyType) val vLayout = KneType.collectionElementLayout(type.valueType) val isKeyString = type.keyType == KneType.STRING @@ -3586,8 +5333,12 @@ class FfmProxyGenerator { else appendLine("${indent}val _valuesBuf = arena.allocate($vLayout, $MAX_COLLECTION_SIZE.toLong())") val invokeArgs = buildList { - add("handle") - fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } + if (baseInvokeArgs != null) { + add(baseInvokeArgs) + } else { + add(buildReceiverInvokeArg(fn)) + fn.params.forEach { p -> addAll(buildExpandedInvokeArgs(p)) } + } add("_keysBuf") if (isKeyString) add("$STRING_BUF_SIZE") add("_valuesBuf") @@ -3624,6 +5375,17 @@ class FfmProxyGenerator { when (elemType) { KneType.BOOLEAN -> appendLine("${indent}val $varName = List($countExpr) { $bufExpr.getAtIndex(JAVA_INT, it.toLong()) != 0 }") is KneType.ENUM -> appendLine("${indent}val $varName = List($countExpr) { ${elemType.simpleName}.entries[$bufExpr.getAtIndex(JAVA_INT, it.toLong())] }") + is KneType.OBJECT -> appendLine("${indent}val $varName = List($countExpr) { ${elemType.simpleName}.fromBorrowedHandle($bufExpr.getAtIndex($layout, it.toLong())) }") + is KneType.INTERFACE -> { + val wrapperName = dynWrapperLookup[elemType.fqName] ?: elemType.simpleName + appendLine("${indent}val $varName = List($countExpr) { $wrapperName.fromBorrowedHandle($bufExpr.getAtIndex($layout, it.toLong())) }") + } + is KneType.SEALED_ENUM -> appendLine("${indent}val $varName = List($countExpr) { ${elemType.simpleName}.fromBorrowedHandle($bufExpr.getAtIndex($layout, it.toLong())) }") + is KneType.LIST, is KneType.SET -> { + val innerElem = if (elemType is KneType.LIST) elemType.elementType else (elemType as KneType.SET).elementType + val readMethod = boxedListReadMethod(innerElem) + appendLine("${indent}val $varName = List($countExpr) { KneRuntime.$readMethod($bufExpr.getAtIndex(JAVA_LONG, it.toLong())) }") + } else -> appendLine("${indent}val $varName = List($countExpr) { $bufExpr.getAtIndex($layout, it.toLong()) as ${elemType.jvmTypeName} }") } } @@ -3637,12 +5399,17 @@ class FfmProxyGenerator { returnType: KneType, handleName: String, invokeArgs: String, + returnsBorrowed: Boolean = false, ) { when (returnType) { KneType.UNIT -> { appendLine("${indent}$handleName.invoke($invokeArgs)") appendLine("${indent}KneRuntime.checkError()") } + KneType.NEVER -> { + appendLine("${indent}$handleName.invoke($invokeArgs)") + appendLine("${indent}throw RuntimeException(KneRuntime.errorMessage ?: \"Rust panic\")") + } KneType.STRING -> { appendStringReadWithRetry(indent, handleName, invokeArgs) appendLine("${indent}return _buf.getString(0)") @@ -3687,21 +5454,53 @@ class FfmProxyGenerator { appendLine("${indent}return _r") } is KneType.OBJECT, is KneType.INTERFACE -> { - val simpleName = when (returnType) { - is KneType.OBJECT -> returnType.simpleName - is KneType.INTERFACE -> returnType.simpleName - else -> error("unreachable") + // Redirect OBJECT types that are actually enums to the enum codegen path + if (returnType is KneType.OBJECT && returnType.simpleName in simpleEnumTypeNames) { + appendLine("${indent}val _r = $handleName.invoke($invokeArgs) as Int") + appendLine("${indent}KneRuntime.checkError()") + appendLine("${indent}return ${returnType.simpleName}.entries[_r]") + } else { + val simpleName = when (returnType) { + is KneType.OBJECT -> returnType.simpleName + is KneType.INTERFACE -> { + // Use dyn wrapper class for factory call (e.g. DynDescribable.fromNativeHandle) + dynWrapperLookup[returnType.fqName] ?: returnType.simpleName + } + else -> error("unreachable") + } + appendLine("${indent}val resultHandle = $handleName.invoke($invokeArgs) as Long") + appendLine("${indent}KneRuntime.checkError()") + val factory = if (returnType is KneType.OBJECT && returnsBorrowed) { + "$simpleName.fromBorrowedHandle" + } else { + "$simpleName.fromNativeHandle" + } + appendLine("${indent}return $factory(resultHandle)") + } + } + is KneType.SEALED_ENUM -> { + // If the sealed enum is actually a simple enum (type mismatch), use ordinal pattern + if (returnType.simpleName in simpleEnumTypeNames) { + appendLine("${indent}val _r = $handleName.invoke($invokeArgs) as Int") + appendLine("${indent}KneRuntime.checkError()") + appendLine("${indent}return ${returnType.simpleName}.entries[_r]") + } else { + appendLine("${indent}val resultHandle = $handleName.invoke($invokeArgs) as Long") + appendLine("${indent}KneRuntime.checkError()") + val factory = if (returnsBorrowed) { + "${returnType.simpleName}.fromBorrowedHandle" + } else { + "${returnType.simpleName}.fromHandle" + } + appendLine("${indent}return $factory(resultHandle)") } - appendLine("${indent}val resultHandle = $handleName.invoke($invokeArgs) as Long") - appendLine("${indent}KneRuntime.checkError()") - appendLine("${indent}return $simpleName.fromNativeHandle(resultHandle)") } is KneType.ENUM -> { appendLine("${indent}val _r = $handleName.invoke($invokeArgs) as Int") appendLine("${indent}KneRuntime.checkError()") appendLine("${indent}return ${returnType.simpleName}.entries[_r]") } - is KneType.NULLABLE -> appendNullableCallAndReturn(indent, returnType, handleName, invokeArgs) + is KneType.NULLABLE -> appendNullableCallAndReturn(indent, returnType, handleName, invokeArgs, returnsBorrowed) is KneType.FUNCTION -> { val fnId = fnInvokeId(returnType) appendLine("${indent}val _fnHandle = $handleName.invoke($invokeArgs) as Long") @@ -3753,6 +5552,11 @@ class FfmProxyGenerator { appendLine("${indent}$handleName.invoke($invokeArgs)") appendLine("${indent}KneRuntime.checkError()") } + is KneType.TUPLE -> { + // TUPLE returns are handled separately in appendTupleReturnProxy + appendLine("${indent}$handleName.invoke($invokeArgs)") + appendLine("${indent}KneRuntime.checkError()") + } is KneType.LIST, is KneType.SET, is KneType.MAP -> { // Collection returns are handled separately in appendCollectionReturnProxy appendLine("${indent}$handleName.invoke($invokeArgs)") @@ -3794,12 +5598,17 @@ class FfmProxyGenerator { type: KneType.NULLABLE, handleName: String, invokeArgs: String, + returnsBorrowed: Boolean = false, ) { when (type.inner) { KneType.STRING -> { appendStringReadWithRetry(indent, handleName, invokeArgs) appendLine("${indent}return if (_len < 0) null else _buf.getString(0)") } + KneType.BYTE_ARRAY -> { + appendStringReadWithRetry(indent, handleName, invokeArgs) + appendLine("${indent}return if (_len < 0) null else _buf.asSlice(0, _len.toLong()).toArray(JAVA_BYTE)") + } KneType.BOOLEAN -> { appendLine("${indent}val raw = $handleName.invoke($invokeArgs) as Int") appendLine("${indent}KneRuntime.checkError()") @@ -3835,10 +5644,37 @@ class FfmProxyGenerator { appendLine("${indent}KneRuntime.checkError()") appendLine("${indent}return if (raw == Long.MIN_VALUE) null else Double.fromBits(raw)") } - is KneType.OBJECT -> { + is KneType.OBJECT, is KneType.INTERFACE -> { + // Redirect OBJECT types that are actually enums (cross-crate type mismatch) + if (type.inner is KneType.OBJECT && (type.inner as KneType.OBJECT).simpleName in simpleEnumTypeNames) { + appendLine("${indent}val raw = $handleName.invoke($invokeArgs) as Int") + appendLine("${indent}KneRuntime.checkError()") + appendLine("${indent}return if (raw < 0) null else ${(type.inner as KneType.OBJECT).simpleName}.entries[raw]") + } else { + val innerName = when (val inner = type.inner) { + is KneType.INTERFACE -> dynWrapperLookup[inner.fqName] ?: inner.simpleName + is KneType.OBJECT -> inner.simpleName + else -> error("Nullable OBJECT/INTERFACE inner must be OBJECT or INTERFACE, got ${type.inner}") + } + appendLine("${indent}val resultHandle = $handleName.invoke($invokeArgs) as Long") + appendLine("${indent}KneRuntime.checkError()") + val factory = if (returnsBorrowed) { + "$innerName.fromBorrowedHandle" + } else { + "$innerName.fromNativeHandle" + } + appendLine("${indent}return if (resultHandle == 0L) null else $factory(resultHandle)") + } + } + is KneType.SEALED_ENUM -> { appendLine("${indent}val resultHandle = $handleName.invoke($invokeArgs) as Long") appendLine("${indent}KneRuntime.checkError()") - appendLine("${indent}return if (resultHandle == 0L) null else ${type.inner.simpleName}.fromNativeHandle(resultHandle)") + val factory = if (returnsBorrowed) { + "${type.inner.simpleName}.fromBorrowedHandle" + } else { + "${type.inner.simpleName}.fromHandle" + } + appendLine("${indent}return if (resultHandle == 0L) null else $factory(resultHandle)") } is KneType.ENUM -> { appendLine("${indent}val raw = $handleName.invoke($invokeArgs) as Int") diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/NativeBridgeGenerator.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/NativeBridgeGenerator.kt index f2a7a59b..f0152c16 100644 --- a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/NativeBridgeGenerator.kt +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/NativeBridgeGenerator.kt @@ -177,7 +177,7 @@ class NativeBridgeGenerator { private fun StringBuilder.appendTryCatchEnd(returnType: KneType) { appendLine(" } catch (e: Throwable) {") appendLine(" _kneLastError.value = e.message ?: e::class.simpleName ?: \"Unknown error\"") - if (returnType != KneType.UNIT) { + if (returnType != KneType.UNIT && returnType != KneType.NEVER) { appendLine(" return ${defaultErrorValue(returnType)}") } appendLine(" }") @@ -193,7 +193,8 @@ class NativeBridgeGenerator { KneType.SHORT -> "0" KneType.STRING -> "0" KneType.UNIT -> "" - is KneType.OBJECT, is KneType.INTERFACE -> "0L" + KneType.NEVER -> "" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "0L" is KneType.ENUM -> "0" is KneType.NULLABLE -> when (type.inner) { KneType.STRING -> "-1" @@ -210,6 +211,7 @@ class NativeBridgeGenerator { is KneType.SET -> "0" is KneType.MAP -> "0" is KneType.FLOW -> "" + is KneType.TUPLE -> "0" } // ── Classes ────────────────────────────────────────────────────────────── @@ -525,14 +527,20 @@ class NativeBridgeGenerator { when (elemType) { KneType.STRING -> { appendLine(" var _offset = 0") + appendLine(" var _overflow = false") + appendLine(" var _totalNeeded = 0") appendLine(" for (_s in $listExpr) {") appendLine(" val _bytes = _s.encodeToByteArray()") - appendLine(" if (_offset + _bytes.size + 1 > outBufLen) break") - appendLine(" _bytes.forEachIndexed { i, b -> outBuf?.set(_offset + i, b) }") - appendLine(" outBuf?.set(_offset + _bytes.size, 0)") - appendLine(" _offset += _bytes.size + 1") + appendLine(" _totalNeeded += _bytes.size + 1") + appendLine(" if (!_overflow && _offset + _bytes.size + 1 <= outBufLen) {") + appendLine(" _bytes.forEachIndexed { i, b -> outBuf?.set(_offset + i, b) }") + appendLine(" outBuf?.set(_offset + _bytes.size, 0)") + appendLine(" _offset += _bytes.size + 1") + appendLine(" } else {") + appendLine(" _overflow = true") + appendLine(" }") appendLine(" }") - appendLine(" return $listExpr.size") + appendLine(" return if (_overflow) -_totalNeeded else $listExpr.size") } KneType.BOOLEAN -> { appendLine(" val _writeLen = minOf($listExpr.size, outLen)") @@ -877,6 +885,7 @@ class NativeBridgeGenerator { type.returnsViaBuffer() -> ": Int" // byte count (or -1 for null) type.returnsViaCollectionBuffer() -> ": Int" // element count type == KneType.UNIT -> "" + type == KneType.NEVER -> "" type is KneType.OBJECT -> ": Long" type is KneType.INTERFACE -> ": Long" type is KneType.ENUM -> ": Int" @@ -1251,7 +1260,7 @@ class NativeBridgeGenerator { } }.joinToString(", ") - if (fnType.returnType == KneType.UNIT) { + if (fnType.returnType == KneType.UNIT || fnType.returnType == KneType.NEVER) { appendLine(" _fnPtr.invoke($invokeArgs)") } else if (fnType.returnType == KneType.BYTE_ARRAY) { // Callback returns ByteArray as packed buffer: [size:Int32][pad:4][data...] @@ -1445,6 +1454,7 @@ class NativeBridgeGenerator { private fun cFunctionReturnType(type: KneType): String = when (type) { KneType.UNIT -> "Unit" + KneType.NEVER -> "Unit" KneType.BOOLEAN -> "Int" // C uses int for bool KneType.STRING -> "CPointer?" KneType.BYTE_ARRAY -> "CPointer?" // packed buffer: [size][pad][data] @@ -1461,6 +1471,7 @@ class NativeBridgeGenerator { KneType.BYTE_ARRAY -> appendByteArrayReturn(expr) KneType.STRING -> appendStringReturn(expr) KneType.UNIT -> appendLine(" $expr") + KneType.NEVER -> appendLine(" $expr") KneType.BOOLEAN -> appendLine(" return if ($expr) 1 else 0") is KneType.OBJECT, is KneType.INTERFACE -> appendLine(" return StableRef.create($expr).asCPointer().toLong()") is KneType.ENUM -> appendLine(" return ($expr).ordinal") @@ -1492,6 +1503,7 @@ class NativeBridgeGenerator { is KneType.OBJECT, is KneType.INTERFACE -> appendLine(" return if (_result != null) StableRef.create(_result).asCPointer().toLong() else 0L") is KneType.ENUM -> appendLine(" return _result?.ordinal ?: -1") KneType.UNIT -> appendLine(" _result") + KneType.NEVER -> appendLine(" _result") else -> appendLine(" return _result") } } @@ -2207,7 +2219,7 @@ class NativeBridgeGenerator { appendLine(" try {") // Call suspend function and encode result - if (fn.returnType == KneType.UNIT) { + if (fn.returnType == KneType.UNIT || fn.returnType == KneType.NEVER) { appendLine(" obj.${fn.name}($callArgs)") appendLine(" val _contFn = _contPtr.toCPointer Unit>>()!!") appendLine(" _contFn.invoke(1, 0L)") @@ -2396,6 +2408,7 @@ class NativeBridgeGenerator { val returnDecl = when (fnType.returnType) { KneType.UNIT -> "" + KneType.NEVER -> "" KneType.BOOLEAN -> ": Int" KneType.STRING -> ": Long" // StableRef KneType.BYTE_ARRAY -> ": Long" // StableRef @@ -2422,6 +2435,7 @@ class NativeBridgeGenerator { when (fnType.returnType) { KneType.UNIT -> appendLine(" _fn.invoke($invokeArgs)") + KneType.NEVER -> appendLine(" _fn.invoke($invokeArgs)") KneType.BOOLEAN -> appendLine(" return if (_fn.invoke($invokeArgs)) 1 else 0") KneType.STRING -> appendLine(" return StableRef.create(_fn.invoke($invokeArgs)).asCPointer().toLong()") KneType.BYTE_ARRAY -> appendLine(" return StableRef.create(_fn.invoke($invokeArgs)).asCPointer().toLong()") diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/RustBridgeGenerator.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/RustBridgeGenerator.kt new file mode 100644 index 00000000..1280a476 --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/RustBridgeGenerator.kt @@ -0,0 +1,3680 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.codegen + +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.* + +/** + * Generates Rust `#[no_mangle] pub extern "C" fn` bridge code from a [KneModule]. + * + * The generated bridges expose the Rust library's public API via the C ABI, + * using the same symbol naming convention that [FfmProxyGenerator] expects. + * This allows the JVM FFM proxies to call into Rust identically to Kotlin/Native. + * + * Object lifecycle uses `Box::into_raw` / `Box::from_raw` (equivalent to Kotlin/Native's StableRef). + * Error propagation uses a thread-local `KNE_LAST_ERROR` (equivalent to `_kneLastError`). + * String I/O uses the output-buffer pattern (equivalent to Kotlin/Native's `CPointer`). + */ +class RustBridgeGenerator { + + companion object { + private val RUST_PRIMITIVE_TYPES = setOf( + "u8", "u16", "u32", "u64", "u128", "usize", + "i8", "i16", "i32", "i64", "i128", "isize", + "f32", "f64", "bool", "String", "str", + ) + } + + /** Returns the Rust call name for a function, using rustMethodName + turbofish if available. */ + private fun rustCallName(fn: KneFunction): String { + val base = fn.rustMethodName ?: fn.name + val turbo = fn.turbofish ?: "" + return "$base$turbo" + } + + /** Tracks emitted bridge symbol names to detect and resolve overload collisions. */ + private val emittedSymbols = mutableMapOf() + + /** Returns a unique symbol name, appending a numeric suffix on collision. */ + private fun uniqueSym(base: String): String { + val count = emittedSymbols.getOrDefault(base, 0) + emittedSymbols[base] = count + 1 + return if (count == 0) base else "${base}_${count}" + } + + private var knownTypeNames: Set = emptySet() + private var genericOrLifetimeClassNames: Set = emptySet() + private var unbridgeableTemplateNames: Set = emptySet() + /** Type simple names that appear in multiple crates (e.g. SampleFormat from both symphonia and cpal). */ + private var ambiguousTypeNames: Set = emptySet() + /** Maps fqName -> Rust path for all types, used to resolve ambiguous names. */ + private var fqNameToRustPath: Map = emptyMap() + /** Maps simpleName -> full Rust type (with generics) for generic classes used in callbacks. */ + private var objectRustTypeNames: Map = emptyMap() + /** Maps enum simpleName -> list of entry names, for ZST enum construction. */ + private var enumEntryNames: Map> = emptyMap() + /** Base Rust type names of monomorphised generic classes (e.g. "AudioPlanesMut" from "AudioPlanesMut"). */ + private var monomorphisedBaseNames: Set = emptySet() + + private fun isKnownType(type: KneType): Boolean = when (type) { + is KneType.OBJECT -> type.simpleName in knownTypeNames + is KneType.INTERFACE -> type.simpleName in knownTypeNames + is KneType.NULLABLE -> isKnownType(type.inner) + is KneType.LIST -> isSupportedCollectionElementForBridge(type.elementType) + is KneType.SET -> isSupportedCollectionElementForBridge(type.elementType) + is KneType.MAP -> isSupportedCollectionElementForBridge(type.keyType) && isSupportedCollectionElementForBridge(type.valueType) + else -> true + } + + private fun isSupportedCollectionElementForBridge(elemType: KneType): Boolean = when (elemType) { + KneType.INT, KneType.LONG, KneType.DOUBLE, KneType.FLOAT, + KneType.SHORT, KneType.BYTE, KneType.BOOLEAN, KneType.STRING, + KneType.BYTE_ARRAY -> true + is KneType.OBJECT -> elemType.simpleName in knownTypeNames + is KneType.INTERFACE -> elemType.simpleName in knownTypeNames + is KneType.SEALED_ENUM -> elemType.simpleName in knownTypeNames + is KneType.ENUM -> true + is KneType.LIST -> isSupportedCollectionElementForBridge(elemType.elementType) + is KneType.SET -> isSupportedCollectionElementForBridge(elemType.elementType) + else -> false + } + + private var enumTypeNames: Set = emptySet() + private var simpleEnumTypeNames: Set = emptySet() + + private fun hasOnlyKnownTypes(fn: KneFunction): Boolean = + isKnownType(fn.returnType) && fn.params.all { isKnownType(it.type) } && + !hasTypeMismatch(fn) && + fn.params.none { hasUnbridgeableParam(it) } && + !hasAmbiguousType(fn) + + /** Returns true if a param has a type that cannot cross the C ABI boundary: + * - `&'static [&str]`, `&'static [&[u8]]` (static slices of non-primitives) + * - Function pointer types with non-primitive params (e.g. `fn(&[u8]) -> u8`) + * - FUNCTION type params where the function's param types include OBJECT/INTERFACE/etc. + */ + private fun hasUnbridgeableParam(p: KneParam): Boolean { + val rt = p.rustType ?: "" + // Static slices of references or slices: &'static [&str], &'static [&[u8]], etc. + if (rt.contains("&'static [") || rt.contains("&[&")) return true + // Function pointer with non-primitive parameters (contains & or complex types) + if (p.type is KneType.FUNCTION) { + val fnType = p.type as KneType.FUNCTION + if (fnType.paramTypes.any { !isCAbiFriendlyType(it) } || + !isCAbiFriendlyType(fnType.returnType)) { + return true + } + // Callback with OBJECT params that are generic templates (e.g. AudioPlanesMut without ) + // — the bridge can't know which concrete monomorphization to use + if (fnType.paramTypes.any { it is KneType.OBJECT && it.simpleName in monomorphisedBaseNames }) return true + } + // fn(...) in rustType with complex arg types + if (rt.startsWith("fn(") && (rt.contains("&[") || rt.contains("&mut ") || rt.contains("dyn "))) return true + // Box fields (fat pointers, cannot be passed as i64 handle) + // Exception: INTERFACE types are handled via registry-based fat pointer reconstruction + // Exception: OBJECT types with Box are monomorphized concrete implementations + if (rt.contains("Box true + is KneType.ENUM -> true + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> true + else -> false + } + + /** Returns true if any param or return type uses a type name that's ambiguous + * across crates and the rustType doesn't carry a qualified Rust path. */ + private fun hasAmbiguousType(fn: KneFunction): Boolean { + if (ambiguousTypeNames.isEmpty()) return false + return fn.params.any { p -> isAmbiguousParam(p) } || isAmbiguousReturnType(fn.returnType, fn.returnRustType) + } + + private fun isAmbiguousParam(p: KneParam): Boolean { + val simpleName = typeSimpleName(p.type) ?: return false + if (simpleName !in ambiguousTypeNames) return false + // If the rustType carries a qualified path (contains ::), the bridge can use it correctly + val rt = unwrapRustWrapperType(p.rustType) ?: return true + return !rt.contains("::") + } + + private fun isAmbiguousReturnType(type: KneType, rustType: String?): Boolean { + val simpleName = typeSimpleName(type) ?: return false + if (simpleName !in ambiguousTypeNames) return false + val rt = unwrapRustWrapperType(rustType) ?: return true + return !rt.contains("::") + } + + private fun typeSimpleName(type: KneType): String? = when (type) { + is KneType.OBJECT -> type.simpleName + is KneType.ENUM -> type.simpleName + is KneType.SEALED_ENUM -> type.simpleName + is KneType.DATA_CLASS -> type.simpleName + is KneType.NULLABLE -> typeSimpleName(type.inner) + else -> null + } + + private fun hasTypeMismatch(fn: KneFunction): Boolean { + val allTypes = fn.params.map { it.type } + fn.returnType + if (allTypes.any { type -> type is KneType.OBJECT && type.simpleName in enumTypeNames }) return true + // Detect params where the resolved KneType is primitive but the rustType suggests + // a complex struct type (e.g. Duration resolved as u64 but actual Rust type is + // std::time::Duration). These cannot be safely bridged. + return fn.params.any { p -> hasPrimitiveTypeAmbiguity(p) } + } + + /** Returns true if a param's KneType was resolved as a primitive but its rustType + * suggests a different complex type that happens to share the same name. */ + private fun hasPrimitiveTypeAmbiguity(p: KneParam): Boolean { + val rt = p.rustType ?: return false + val normalized = normalizeRustType(rt) ?: return false + // If the KneType is a primitive but the rustType is a qualified path to a struct, + // there's a type alias collision (e.g. symphonia Duration = u64 vs std::time::Duration) + if (p.type is KneType.LONG || p.type is KneType.INT || p.type is KneType.DOUBLE || p.type is KneType.FLOAT) { + // Qualified type path suggests a struct, not a primitive alias + if (normalized.contains("::") && !normalized.startsWith("std::") && + !RUST_PRIMITIVE_TYPES.contains(normalized)) { + // The normalized type is a crate-qualified struct — that's fine, it's a type alias. + // But check if the original type has a std:: prefix (meaning it's std::time::Duration etc.) + } + // std::time::Duration or similar standard lib types mistakenly resolved as primitive + if (rt.contains("std::time::") || rt.contains("core::time::")) return true + } + return false + } + + fun generate(module: KneModule): String { + emittedSymbols.clear() + knownTypeNames = buildSet { + module.classes.forEach { add(it.simpleName) } + module.dataClasses.forEach { add(it.simpleName) } + module.enums.forEach { add(it.simpleName) } + module.sealedEnums.forEach { add(it.simpleName) } + module.interfaces.forEach { add(it.simpleName) } + } + enumTypeNames = buildSet { + module.enums.forEach { add(it.simpleName) } + module.sealedEnums.forEach { add(it.simpleName) } + } + simpleEnumTypeNames = module.enums.map { it.simpleName }.toSet() + // Use pre-computed ambiguous type names from module merge (detects names that + // appeared in multiple crates before deduplication, e.g. SampleFormat from both + // symphonia_core and cpal). + ambiguousTypeNames = module.ambiguousTypeNames + // Track class names whose rustTypeName contains generics or lifetimes, + // or whose Rust struct definition has lifetime params (e.g. BufReader<'a>) + // — these classes cannot be safely used as type args in other generic classes. + genericOrLifetimeClassNames = buildSet { + module.classes.forEach { cls -> + if (cls.rustTypeName.contains("<") || cls.rustTypeName.contains("'")) { + add(cls.simpleName) + } + if (cls.hasLifetimeParams || cls.hasUnresolvedGenericTypeParams) { + add(cls.simpleName) + } + } + } + // Detect generic template classes whose concrete type uses the class name with <...>. + // These templates (e.g. Processor with rustTypeName="Processor") are not directly + // bridgeable — only their monomorphisations (Processor_Doubler) are. + // Also detect structs that require type params but weren't monomorphised. + monomorphisedBaseNames = module.classes + .filter { it.rustTypeName.contains("<") } + .map { it.rustTypeName.substringBefore('<') } + .toSet() + unbridgeableTemplateNames = module.classes + .filter { cls -> + !cls.rustTypeName.contains("<") && + cls.simpleName in monomorphisedBaseNames + } + .map { it.simpleName } + .toSet() + + // Map from simpleName to full Rust type name for handle casts and callback type resolution. + // Includes generics (e.g. PhysicalSize) and opaque types with qualified paths + // (e.g. tray_icon::Icon, tray_icon::menu::MenuItem). + objectRustTypeNames = module.classes + .filter { it.rustTypeName != it.simpleName } + .associate { it.simpleName to it.rustTypeName } + // Map from enum simpleName to its entry names for ZST enum handling + enumEntryNames = module.enums.associate { it.simpleName to it.entries } + + val sb = StringBuilder() + val prefix = module.libName + + sb.appendPreamble(module) + sb.appendErrorInfra(prefix) + sb.appendFreeBuf(prefix) + if (moduleHasNestedCollections(module)) { + sb.appendBoxedListHelpers(prefix) + } + + // Check if any class or top-level function uses suspend or flow + val hasSuspend = module.classes.any { c -> c.methods.any { it.isSuspend } } || + module.functions.any { it.isSuspend } + val hasFlow = module.classes.any { c -> c.methods.any { it.returnType is KneType.FLOW } } || + module.functions.any { it.returnType is KneType.FLOW } + if (hasSuspend || hasFlow) { + sb.appendSuspendHelpers(prefix) + } + + for (cls in module.classes) { + sb.appendClass(cls, prefix) + } + + for (enum in module.enums) { + sb.appendEnum(enum, prefix) + } + + for (sealed in module.sealedEnums) { + sb.appendSealedEnum(sealed, prefix) + } + + for (fn in module.functions) { + sb.appendTopLevelFunction(fn, prefix) + } + + return sb.toString() + } + + // --- Preamble --- + + private fun StringBuilder.appendPreamble(module: KneModule) { + appendLine("// Auto-generated by NNA (Nucleus Native Access) — do not edit.") + appendLine() + appendLine("use std::cell::RefCell;") + appendLine("use std::ffi::CStr;") + appendLine("use std::collections::HashMap;") + appendLine("use std::os::raw::c_char;") + appendLine("use std::panic::catch_unwind;") + appendLine("use std::ops::{Add, Sub, Mul, Div, Rem, BitAnd, BitOr, BitXor, Not, Shl, Shr};") + appendLine("use std::ops::{AddAssign, SubAssign, MulAssign, DivAssign, BitAndAssign, BitOrAssign, BitXorAssign};") + appendLine("use std::io::Read;") + appendLine("use std::io::Seek;") + // No `use` imports needed for opaque external types — the bridge uses fully + // qualified paths in dispose and inferred casts (*const _, *mut _) elsewhere. + appendLine() + appendLine("// dyn Trait registry: stores fat pointer components as [usize; 2]") + appendLine("// Box is a fat pointer (data + vtable). We transmute it to [usize; 2] for storage.") + appendLine() + appendLine("thread_local! {") + appendLine(" static KNE_TRAIT_REGISTRY: RefCell> = RefCell::new(HashMap::new());") + appendLine(" static KNE_NEXT_HANDLE: RefCell = RefCell::new(1);") + appendLine("}") + appendLine() + appendLine("// Drop a trait object by handle") + appendLine("// We reconstruct the Box from the stored fat pointer components.") + appendLine("fn kne_drop_trait_object(handle: u64) {") + appendLine(" KNE_TRAIT_REGISTRY.with(|reg| {") + appendLine(" if let Some(words) = reg.borrow_mut().remove(&handle) {") + appendLine(" unsafe {") + appendLine(" // Reconstruct Box from [usize; 2]") + appendLine(" let fat_ptr: *mut dyn std::any::Any = std::mem::transmute(words);") + appendLine(" let boxed: Box> = Box::from_raw(fat_ptr as *mut Box);") + appendLine(" drop(boxed);") + appendLine(" }") + appendLine(" }") + appendLine(" });") + appendLine("}") + appendLine() + appendLine("// Helper to convert a C string pointer to a Rust String") + appendLine("fn cstr_to_string(ptr: *const c_char) -> String {") + appendLine(" if ptr.is_null() {") + appendLine(" String::new()") + appendLine(" } else {") + appendLine(" unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or(\"\").to_string()") + appendLine(" }") + appendLine("}") + appendLine() + } + + // --- Error infrastructure --- + + private fun StringBuilder.appendErrorInfra(prefix: String) { + appendLine("thread_local! {") + appendLine(" static KNE_LAST_ERROR: RefCell> = RefCell::new(None);") + appendLine("}") + appendLine() + appendLine("fn kne_set_error(msg: String) {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = Some(msg));") + appendLine("}") + appendLine() + appendLine("fn kne_set_panic_error(err: Box) {") + appendLine(" let msg = if let Some(s) = err.downcast_ref::<&str>() {") + appendLine(" s.to_string()") + appendLine(" } else if let Some(s) = err.downcast_ref::() {") + appendLine(" s.clone()") + appendLine(" } else {") + appendLine(" \"Unknown panic\".to_string()") + appendLine(" };") + appendLine(" kne_set_error(msg);") + appendLine("}") + appendLine() + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_hasError() -> i32 {") + appendLine(" KNE_LAST_ERROR.with(|e| if e.borrow().is_some() { 1 } else { 0 })") + appendLine("}") + appendLine() + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_getLastError(out_buf: *mut u8, out_buf_len: i32) -> i32 {") + appendLine(" KNE_LAST_ERROR.with(|e| {") + appendLine(" let mut err = e.borrow_mut();") + appendLine(" if let Some(ref msg) = *err {") + appendLine(" let bytes = msg.as_bytes();") + appendLine(" let len = bytes.len() as i32;") + appendLine(" if len < out_buf_len {") + appendLine(" unsafe {") + appendLine(" std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf, bytes.len());") + appendLine(" *out_buf.add(bytes.len()) = 0;") + appendLine(" }") + appendLine(" }") + appendLine(" *err = None;") + appendLine(" len + 1") + appendLine(" } else {") + appendLine(" 0") + appendLine(" }") + appendLine(" })") + appendLine("}") + appendLine() + } + + // --- Tuple buffer free --- + + private fun StringBuilder.appendFreeBuf(prefix: String) { + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_free_buf(ptr: i64, size: i64) {") + appendLine(" if ptr != 0 && size > 0 {") + appendLine(" unsafe {") + appendLine(" let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr as *mut u8, size as usize));") + appendLine(" }") + appendLine(" }") + appendLine("}") + appendLine() + } + + private fun moduleHasNestedCollections(module: KneModule): Boolean { + fun typeHasNestedCollection(type: KneType): Boolean = when (type) { + is KneType.MAP -> type.valueType is KneType.LIST || type.valueType is KneType.SET || type.valueType is KneType.MAP + is KneType.LIST -> type.elementType is KneType.LIST || type.elementType is KneType.SET || type.elementType is KneType.MAP + is KneType.SET -> type.elementType is KneType.LIST || type.elementType is KneType.SET || type.elementType is KneType.MAP + is KneType.TUPLE -> type.elementTypes.any { typeHasNestedCollection(it) } + is KneType.NULLABLE -> typeHasNestedCollection(type.inner) + else -> false + } + fun fnHasNestedCollection(fn: KneFunction) = + typeHasNestedCollection(fn.returnType) || fn.params.any { typeHasNestedCollection(it.type) } + return module.functions.any { fnHasNestedCollection(it) } || + module.classes.any { cls -> + cls.methods.any { fnHasNestedCollection(it) } || + cls.companionMethods.any { fnHasNestedCollection(it) } + } + } + + private fun StringBuilder.appendBoxedListHelpers(prefix: String) { + appendLine("// ── Boxed list reader helpers ─────────────────────────────────────────────") + appendLine() + // i32 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_i32(handle: i64, out_buf: *mut i32, max_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len().min(max_len as usize) as i32;") + appendLine(" unsafe { std::ptr::copy_nonoverlapping(vec.as_ptr(), out_buf, count as usize); }") + appendLine(" count") + appendLine("}") + appendLine() + // i64 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_i64(handle: i64, out_buf: *mut i64, max_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len().min(max_len as usize) as i32;") + appendLine(" unsafe { std::ptr::copy_nonoverlapping(vec.as_ptr(), out_buf, count as usize); }") + appendLine(" count") + appendLine("}") + appendLine() + // f32 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_f32(handle: i64, out_buf: *mut f32, max_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len().min(max_len as usize) as i32;") + appendLine(" unsafe { std::ptr::copy_nonoverlapping(vec.as_ptr(), out_buf, count as usize); }") + appendLine(" count") + appendLine("}") + appendLine() + // f64 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_f64(handle: i64, out_buf: *mut f64, max_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len().min(max_len as usize) as i32;") + appendLine(" unsafe { std::ptr::copy_nonoverlapping(vec.as_ptr(), out_buf, count as usize); }") + appendLine(" count") + appendLine("}") + appendLine() + // bool (stored as i32 in JVM) + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_bool(handle: i64, out_buf: *mut i32, max_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len().min(max_len as usize) as i32;") + appendLine(" for (i, v) in vec.iter().take(count as usize).enumerate() {") + appendLine(" unsafe { *(out_buf.add(i)) = if *v { 1 } else { 0 }; }") + appendLine(" }") + appendLine(" count") + appendLine("}") + appendLine() + // i16 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_i16(handle: i64, out_buf: *mut i16, max_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len().min(max_len as usize) as i32;") + appendLine(" unsafe { std::ptr::copy_nonoverlapping(vec.as_ptr(), out_buf, count as usize); }") + appendLine(" count") + appendLine("}") + appendLine() + // i8 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_i8(handle: i64, out_buf: *mut i8, max_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len().min(max_len as usize) as i32;") + appendLine(" unsafe { std::ptr::copy_nonoverlapping(vec.as_ptr(), out_buf, count as usize); }") + appendLine(" count") + appendLine("}") + appendLine() + // String (packed null-terminated) + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_read_box_list_string(handle: i64, out_buf: *mut u8, buf_len: i32) -> i32 {") + appendLine(" if handle == 0 { return 0; }") + appendLine(" let vec = unsafe { Box::from_raw(handle as *mut Vec) };") + appendLine(" let count = vec.len() as i32;") + appendLine(" let mut offset = 0usize;") + appendLine(" for s in vec.iter() {") + appendLine(" let bytes = s.as_bytes();") + appendLine(" if offset + bytes.len() + 1 > buf_len as usize { break; }") + appendLine(" unsafe {") + appendLine(" std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf.add(offset), bytes.len());") + appendLine(" *out_buf.add(offset + bytes.len()) = 0;") + appendLine(" }") + appendLine(" offset += bytes.len() + 1;") + appendLine(" }") + appendLine(" count") + appendLine("}") + appendLine() + } + + // --- Class bridges --- + + private fun StringBuilder.appendClass(cls: KneClass, prefix: String) { + val className = cls.simpleName + val rustTypeName = cls.rustTypeName + + // Skip classes whose Rust type cannot be correctly bridged: + // 1. Types with lifetime params (e.g. BufReader<'_>) + if (rustTypeName.contains("'")) { + return + } + // 1b. Transitive dependency types (e.g. bytes::bytes::Bytes) — external crate path + // with no bridgeable methods or constructor. Only a dispose would be generated, + // but the type path may not resolve in the wrapper scope. + if (requiresInferredObjectCast(rustTypeName) && + cls.methods.isEmpty() && cls.properties.isEmpty() && + cls.constructor.kind == KneConstructorKind.NONE) { + return + } + // 2. Generic templates that have monomorphised variants — the template itself + // can't be bridged (e.g. Processor with template, vs Processor) + if (cls.genericParams.isNotEmpty() && !rustTypeName.contains("<")) { + return + } + // 3. Templates detected by name analysis (for structs where genericParams wasn't set) + if (className in unbridgeableTemplateNames) { + return + } + // 4. Structs with unresolved generic type params that weren't monomorphised + // (e.g. ReadOnlySource where no concrete R was found) + if (cls.hasUnresolvedGenericTypeParams && !rustTypeName.contains("<")) { + return + } + // 2. Generics: skip multi-param, or single-param with unknown/problematic type arg + if (rustTypeName.contains("<")) { + val genericContent = rustTypeName.substringAfter('<').substringBeforeLast('>') + val argTypes = genericContent.split(",").map { it.trim() } + // Multi-param generics: trait bounds make these unreliable + if (argTypes.size > 1) return + val argType = argTypes.first().removePrefix("&").removePrefix("mut ").trim() + // Unknown type arg + if (argType.isNotEmpty() && argType !in knownTypeNames && argType !in RUST_PRIMITIVE_TYPES) { + return + } + // Type arg is itself a class with generics/lifetimes in its own definition + // (e.g. BufReader which is BufReader<'a> in Rust — the bridge loses the lifetime) + if (argType in genericOrLifetimeClassNames) { + return + } + } + + val baseSym = "${prefix}_${className}" + + if (cls.constructor.kind != KneConstructorKind.NONE && + cls.constructor.params.all { isKnownType(it.type) } && + cls.constructor.params.none { hasUnbridgeableParam(it) } && + cls.constructor.params.none { isAmbiguousParam(it) }) { + appendConstructor(cls, prefix) + } + + // Dispose + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${baseSym}_dispose(handle: i64) {") + appendLine(" if handle != 0 {") + appendLine(" unsafe { drop(Box::from_raw(handle as *mut $rustTypeName)); }") + appendLine(" }") + appendLine("}") + appendLine() + + // Methods (skip those referencing external unknown types) + for (method in cls.methods.filter { hasOnlyKnownTypes(it) }) { + appendMethod(method, cls, prefix) + } + + for (method in cls.companionMethods.filter { hasOnlyKnownTypes(it) }) { + appendCompanionMethod(method, cls, prefix) + } + + // Properties (skip those with unknown types) + for (prop in cls.properties.filter { isKnownType(it.type) }) { + appendPropertyBridges(prop, cls, prefix) + } + } + + private fun StringBuilder.appendConstructor(cls: KneClass, prefix: String) { + val className = cls.simpleName + val rustName = cls.rustTypeName + val sym = "${prefix}_${className}" + appendLine("#[no_mangle]") + append("pub extern \"C\" fn ${sym}_new(") + append(cls.constructor.params.joinToString(", ") { p -> + when (p.type) { + KneType.BYTE_ARRAY, is KneType.LIST, is KneType.SET -> "${p.name}_ptr: ${slicePointerType(p.type)}, ${p.name}_len: i32" + is KneType.MAP -> { + val mapType = p.type as KneType.MAP + "${p.name}_keys_ptr: ${mapSlicePointerType(mapType.keyType)}, ${p.name}_values_ptr: ${mapSlicePointerType(mapType.valueType)}, ${p.name}_size: i32" + } + else -> "${p.name}: ${rustCType(p.type)}" + } + }) + appendLine(") -> i64 {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + for (p in cls.constructor.params) { + appendParamConversion(p) + } + // For generic structs like Processor, constructor path needs turbofish: Processor::::new(...) + val ctorRustName = rustName.replace("<", "::<") + val ctorExpr = when (cls.constructor.kind) { + KneConstructorKind.FUNCTION -> { + val args = cls.constructor.params.joinToString(", ") { p -> convertedCallArg(p) } + "$ctorRustName::new($args)" + } + KneConstructorKind.STRUCT_LITERAL -> { + val args = cls.constructor.params.joinToString(", ") { p -> "${p.name}: ${convertedCallArg(p)}" } + "$rustName { $args }" + } + KneConstructorKind.NONE -> error("unreachable") + } + if (cls.constructor.canFail) { + appendFallibleReturnHandling(ctorExpr, KneType.OBJECT(cls.fqName, className)) + } else { + appendValueReturnHandling(ctorExpr, KneType.OBJECT(cls.fqName, className)) + } + appendLine(" })) {") + appendLine(" Ok(v) => v,") + appendLine(" Err(e) => { kne_set_panic_error(e); 0i64 }") + appendLine(" }") + appendLine("}") + appendLine() + } + + private fun StringBuilder.appendMethod(fn: KneFunction, cls: KneClass, prefix: String) { + if (fn.returnType is KneType.FLOW) { + appendFlowMethod(fn, cls, prefix) + return + } + if (fn.isSuspend) { + appendSuspendMethod(fn, cls, prefix) + return + } + if (!isSupportedReturnType(fn.returnType)) return + // Skip methods with turbofish on generic classes: the turbofish is a + // monomorphisation artifact. The method either doesn't take generics (E0107) + // or the class already provides the type. + val turbo = fn.turbofish + if (turbo != null && cls.rustTypeName.contains("<")) { + return + } + val sym = uniqueSym("${prefix}_${cls.simpleName}_${fn.name}") + val needsBufOutput = needsOutputBuffer(fn.returnType) + + appendLine("#[no_mangle]") + append("pub extern \"C\" fn $sym(handle: i64") + for (p in fn.params) { + val ndc = nullableDataClass(p.type) + if (p.type == KneType.BYTE_ARRAY || p.type is KneType.LIST) { + append(", ${p.name}_ptr: ${slicePointerType(p.type)}, ${p.name}_len: i32") + } else if (p.type is KneType.MAP) { + val mapType = p.type as KneType.MAP + append(", ${p.name}_keys_ptr: ${mapSlicePointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) append(", ${p.name}_keys_len: i32") + append(", ${p.name}_values_ptr: ${mapSlicePointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) append(", ${p.name}_values_len: i32") + append(", ${p.name}_size: i32") + } else if (ndc != null) { + appendNullableDataClassSignatureParams(p.name, ndc) + } else if (p.type is KneType.DATA_CLASS) { + // Expand data class fields as individual C params + val dc = p.type as KneType.DATA_CLASS + for (field in dc.fields) { + append(", ${p.name}_${field.name}: ${rustCType(field.type)}") + } + } else if (p.type is KneType.TUPLE) { + val tuple = p.type as KneType.TUPLE + for ((idx, elemType) in tuple.elementTypes.withIndex()) { + when (elemType) { + is KneType.LIST, is KneType.SET -> { + append(", ${p.name}_${idx}_ptr: ${slicePointerType(elemType)}, ${p.name}_${idx}_len: i32") + } + is KneType.MAP -> { + append(", ${p.name}_${idx}_keys_ptr: ${mapSlicePointerType(elemType.keyType)}, ${p.name}_${idx}_keys_len: i32") + append(", ${p.name}_${idx}_values_ptr: ${mapSlicePointerType(elemType.valueType)}, ${p.name}_${idx}_values_len: i32") + } + else -> append(", ${p.name}_$idx: ${rustCType(elemType)}") + } + } + } else { + append(", ${p.name}: ${rustCType(p.type)}") + } + } + if (needsBufOutput) { + append(", out_buf: *mut u8, out_buf_len: i32") + } + // Data class return (or nullable data class): add per-field out-params + val returnDc = extractReturnDataClass(fn.returnType) + if (returnDc != null) { + for (field in returnDc.fields) { + when (field.type) { + KneType.STRING -> { + append(", out_${field.name}: *mut u8, out_${field.name}_len: i32") + } + else -> { + append(", out_${field.name}: *mut ${rustCType(field.type)}") + } + } + } + } + // MAP return: dual key/value buffers + max count + extractMapReturnType(fn.returnType)?.let { mapType -> + append(", out_keys: ${mapOutPointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) append(", out_keys_len: i32") + append(", out_values: ${mapOutPointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) append(", out_values_len: i32") + append(", out_max_len: i32") + } + // Tuple return: add per-element out-params + if (fn.returnType is KneType.TUPLE) { + val tuple = fn.returnType as KneType.TUPLE + for ((idx, elemType) in tuple.elementTypes.withIndex()) { + when (elemType) { + KneType.STRING -> { + append(", out_t_$idx: *mut u8, out_t_${idx}_len: i32") + } + is KneType.LIST, is KneType.SET -> { + val innerElem = when (elemType) { is KneType.LIST -> elemType.elementType; is KneType.SET -> (elemType as KneType.SET).elementType; else -> KneType.INT } + val ptrType = listOutPointerType(innerElem) + append(", out_t_$idx: $ptrType, out_t_${idx}_cap: i32, out_t_${idx}_len: *mut i32") + } + is KneType.MAP -> { + append(", out_t_${idx}_keys: *mut u8, out_t_${idx}_keys_len: i32") + append(", out_t_${idx}_values: *mut u8, out_t_${idx}_values_len: i32") + append(", out_t_${idx}_len: *mut i32") + } + else -> { + append(", out_t_$idx: *mut ${rustCType(elemType)}") + } + } + } + } + appendLine(") -> ${if (fn.returnType == KneType.NEVER) "()" else rustCReturnType(fn.returnType)} {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + if (fn.returnType == KneType.NEVER) { + appendReceiverBinding(fn, cls.rustTypeName) + for (p in fn.params) { + appendParamConversion(p) + } + val callArgs = fn.params.joinToString(", ") { p -> convertedParamName(p) } + val rawCall = wrapCallForAsync(wrapCallForSafety("obj.${rustCallName(fn)}($callArgs)", fn.isUnsafe), fn.isAsync) + val expr = wrapExprWithMutObjectSliceWriteback(rawCall, fn.params, " ") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + appendLine(" $expr") + appendLine(" })) {") + appendLine(" Ok(_) => unreachable!(),") + appendLine(" Err(e) => { kne_set_panic_error(e); }") + appendLine(" }") + } else { + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + appendReceiverBinding(fn, cls.rustTypeName) + for (p in fn.params) { + appendParamConversion(p) + } + val callArgs = fn.params.joinToString(", ") { p -> convertedParamName(p) } + val rawCall = wrapCallForAsync(wrapCallForSafety("obj.${rustCallName(fn)}($callArgs)", fn.isUnsafe), fn.isAsync) + val expr = wrapExprWithMutObjectSliceWriteback(rawCall, fn.params + ) + if (fn.canFail) { + appendFallibleReturnHandling(expr, fn.returnType, fn.returnRustType, fn.returnsBorrowed, fn.returnConversion) + } else { + appendValueReturnHandling(expr, fn.returnType, fn.returnRustType, fn.returnsBorrowed, fn.returnConversion) + } + appendLine(" })) {") + if (fn.returnType is KneType.DATA_CLASS) { + appendLine(" Ok(_) => {},") + } else { + appendLine(" Ok(v) => v,") + } + appendLine(" Err(e) => { kne_set_panic_error(e); ${defaultReturnValue(fn.returnType)} }") + appendLine(" }") + } + appendLine("}") + appendLine() + } + + private fun StringBuilder.appendCompanionMethod(fn: KneFunction, cls: KneClass, prefix: String) { + if (!isSupportedReturnType(fn.returnType)) return + val sym = uniqueSym("${prefix}_${cls.simpleName}_companion_${fn.name}") + val needsBuf = needsOutputBuffer(fn.returnType) + + appendLine("#[no_mangle]") + append("pub extern \"C\" fn $sym(") + val allParams = mutableListOf() + for (p in fn.params) { + val ndc = nullableDataClass(p.type) + if (p.type == KneType.BYTE_ARRAY || p.type is KneType.LIST) { + allParams.add("${p.name}_ptr: ${slicePointerType(p.type)}") + allParams.add("${p.name}_len: i32") + } else if (p.type is KneType.MAP) { + val mapType = p.type as KneType.MAP + allParams.add("${p.name}_keys_ptr: ${mapSlicePointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) allParams.add("${p.name}_keys_len: i32") + allParams.add("${p.name}_values_ptr: ${mapSlicePointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) allParams.add("${p.name}_values_len: i32") + allParams.add("${p.name}_size: i32") + } else if (ndc != null) { + allParams.addAll(nullableDataClassSignatureParamList(p.name, ndc)) + } else if (p.type is KneType.DATA_CLASS) { + val dc = p.type as KneType.DATA_CLASS + for (field in dc.fields) { + allParams.add("${p.name}_${field.name}: ${rustCType(field.type)}") + } + } else { + allParams.add("${p.name}: ${rustCType(p.type)}") + } + } + if (needsBuf) { + allParams.add("out_buf: *mut u8") + allParams.add("out_buf_len: i32") + } + val companionReturnDc = extractReturnDataClass(fn.returnType) + if (companionReturnDc != null) { + for (field in companionReturnDc.fields) { + when (field.type) { + KneType.STRING -> { + allParams.add("out_${field.name}: *mut u8") + allParams.add("out_${field.name}_len: i32") + } + else -> allParams.add("out_${field.name}: *mut ${rustCType(field.type)}") + } + } + } + // MAP return: dual key/value buffers + max count + extractMapReturnType(fn.returnType)?.let { mapType -> + allParams.add("out_keys: ${mapOutPointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) allParams.add("out_keys_len: i32") + allParams.add("out_values: ${mapOutPointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) allParams.add("out_values_len: i32") + allParams.add("out_max_len: i32") + } + append(allParams.joinToString(", ")) + appendLine(") -> ${rustCReturnType(fn.returnType)} {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + for (p in fn.params) { + appendParamConversion(p) + } + val callArgs = fn.params.joinToString(", ") { p -> convertedParamName(p) } + val expr = wrapExprWithMutObjectSliceWriteback( + wrapCallForSafety("${cls.simpleName}::${rustCallName(fn)}($callArgs)", fn.isUnsafe), fn.params + ) + if (fn.canFail) { + appendFallibleReturnHandling(expr, fn.returnType, fn.returnRustType, fn.returnsBorrowed, fn.returnConversion) + } else { + appendValueReturnHandling(expr, fn.returnType, fn.returnRustType, fn.returnsBorrowed, fn.returnConversion) + } + appendLine(" })) {") + if (fn.returnType is KneType.DATA_CLASS) { + appendLine(" Ok(_) => {},") + } else { + appendLine(" Ok(v) => v,") + } + appendLine(" Err(e) => { kne_set_panic_error(e); ${defaultReturnValue(fn.returnType)} }") + appendLine(" }") + appendLine("}") + appendLine() + } + + private fun StringBuilder.appendPropertyBridges(prop: KneProperty, cls: KneClass, prefix: String) { + if (!isSupportedReturnType(prop.type)) return + val className = cls.simpleName + val sym = "${prefix}_${className}" + + if (prop.type is KneType.MAP) { + appendMapPropertyGetter(prop, cls, sym) + return + } + + val needsBuf = needsOutputBuffer(prop.type) + val returnDc = extractReturnDataClass(prop.type) + + // Getter bridge + appendLine("#[no_mangle]") + append("pub extern \"C\" fn ${sym}_get_${prop.name}(handle: i64") + if (needsBuf) append(", out_buf: *mut u8, out_buf_len: i32") + if (returnDc != null) { + for (field in returnDc.fields) { + when (field.type) { + KneType.STRING -> append(", out_${field.name}: *mut u8, out_${field.name}_len: i32") + else -> append(", out_${field.name}: *mut ${rustCType(field.type)}") + } + } + } + appendLine(") -> ${rustCReturnType(prop.type)} {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + appendLine(" let obj = unsafe { &*(handle as *const ${cls.rustTypeName}) };") + appendValueReturnHandling("obj.get_${prop.name}()", prop.type) + appendLine(" })) {") + if (returnDc != null) { + appendLine(" Ok(_) => {},") + } else { + appendLine(" Ok(v) => v,") + } + appendLine(" Err(e) => { kne_set_panic_error(e); ${defaultReturnValue(prop.type)} }") + appendLine(" }") + appendLine("}") + appendLine() + + // Setter bridge (only if mutable) + if (prop.mutable) { + val param = KneParam("value", prop.type) + appendLine("#[no_mangle]") + append("pub extern \"C\" fn ${sym}_set_${prop.name}(handle: i64") + append(", value: ${rustCType(prop.type)}") + appendLine(") {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + appendLine(" let obj = unsafe { &mut *(handle as *mut ${cls.rustTypeName}) };") + appendParamConversion(param) + val callArg = convertedParamName(param) + appendLine(" obj.set_${prop.name}($callArg);") + appendLine(" })) {") + appendLine(" Ok(_) => {},") + appendLine(" Err(e) => { kne_set_panic_error(e); }") + appendLine(" }") + appendLine("}") + appendLine() + } + } + + private fun StringBuilder.appendMapPropertyGetter(prop: KneProperty, cls: KneClass, sym: String) { + val mapType = prop.type as KneType.MAP + appendLine("#[no_mangle]") + append("pub extern \"C\" fn ${sym}_get_${prop.name}(handle: i64") + append(", out_keys: ${mapOutPointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) append(", out_keys_len: i32") + append(", out_values: ${mapOutPointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) append(", out_values_len: i32") + appendLine(", out_max_len: i32) -> i32 {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + appendLine(" let obj = unsafe { &*(handle as *const ${cls.rustTypeName}) };") + appendLine(" let _map_result = obj.get_${prop.name}();") + appendMapReturnFromBinding("_map_result", mapType, " ") + appendLine(" })) {") + appendLine(" Ok(v) => v,") + appendLine(" Err(e) => { kne_set_panic_error(e); 0 }") + appendLine(" }") + appendLine("}") + appendLine() + } + + /** Extract a DATA_CLASS from a return type (handles both direct and nullable). */ + private fun extractReturnDataClass(type: KneType): KneType.DATA_CLASS? = when (type) { + is KneType.DATA_CLASS -> type + is KneType.NULLABLE -> type.inner as? KneType.DATA_CLASS + else -> null + } + + /** Check if the return type is fully supported by the Rust bridge generator. */ + private fun isSupportedReturnType(type: KneType): Boolean = when (type) { + is KneType.MAP -> isSupportedMapElementType(type.keyType) && isSupportedMapElementType(type.valueType) + is KneType.NULLABLE -> isSupportedReturnType(type.inner) + else -> true + } + + private fun isSupportedMapElementType(type: KneType): Boolean = when (type) { + KneType.INT, KneType.LONG, KneType.DOUBLE, KneType.FLOAT, + KneType.SHORT, KneType.BYTE, KneType.BOOLEAN, KneType.STRING -> true + is KneType.ENUM, is KneType.OBJECT, is KneType.SEALED_ENUM -> true + is KneType.LIST -> isSupportedCollectionElementForBridge((type as KneType.LIST).elementType) + is KneType.SET -> isSupportedCollectionElementForBridge((type as KneType.SET).elementType) + else -> false + } + + /** + * Returns true if the given type is a dyn Trait (fat pointer) that cannot be + * represented as an i64 handle across the FFM boundary. + * Handles: dyn Trait, &dyn Trait, &mut dyn Trait, Box + */ + private fun isDynTraitType(type: KneType, rustType: String?): Boolean { + if (type is KneType.INTERFACE) { + val rt = rustType ?: return false + // Check for direct dyn Trait or dyn Trait in Box/Option/Result + rt.startsWith("dyn ") || rt.startsWith("&dyn ") || rt.startsWith("&mut dyn ") || + rt.startsWith("Box p.type is KneType.INTERFACE }) { + return true // Handle via registry — pass handle, reconstruct &dyn Trait on Rust side + } + + // If return type is dyn Trait (Box, Option<...>, Result<...>), handle via registry + // Exclude "impl dyn" — these are impl Trait returns handled via standard boxing path + val retRt = fn.returnRustType ?: "" + if (retRt.contains("dyn ") && !retRt.startsWith("impl dyn ")) { + return true // Handle via registry + } + + // If return type is INTERFACE (dyn Trait interface), handle via registry + if (isDynTraitType(fn.returnType, fn.returnRustType)) { + return true + } + + return false // Not a dyn Trait function + } + + /** Extract MAP type from a direct MAP or Nullable return. */ + private fun extractMapReturnType(type: KneType): KneType.MAP? = when (type) { + is KneType.MAP -> type + is KneType.NULLABLE -> type.inner as? KneType.MAP + else -> null + } + + /** Returns the Rust pointer type for a MAP out-parameter buffer. */ + private fun mapOutPointerType(elemType: KneType): String = when (elemType) { + KneType.STRING -> "*mut u8" + KneType.INT, KneType.BOOLEAN -> "*mut i32" + KneType.LONG -> "*mut i64" + KneType.DOUBLE -> "*mut f64" + KneType.FLOAT -> "*mut f32" + KneType.SHORT -> "*mut i16" + KneType.BYTE -> "*mut i8" + is KneType.ENUM -> "*mut i32" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "*mut i64" + else -> "*mut i64" + } + + private fun needsOutputBuffer(type: KneType): Boolean = when (type) { + KneType.STRING, KneType.BYTE_ARRAY -> true + is KneType.LIST, is KneType.SET -> true + is KneType.NULLABLE -> when (type.inner) { + KneType.STRING, KneType.BYTE_ARRAY -> true + is KneType.LIST, is KneType.SET -> true + else -> false + } + is KneType.DATA_CLASS -> false // Data class returns use per-field out-params, not a single buffer + is KneType.MAP -> false // MAP uses dual buffers, handled separately + else -> false + } + + private fun StringBuilder.appendReturnHandling(expr: String, returnType: KneType) { + appendValueReturnHandling(expr, returnType) + } + + private fun StringBuilder.appendFallibleReturnHandling( + expr: String, + returnType: KneType, + returnRustType: String? = null, + returnsBorrowed: Boolean = false, + returnConversion: String? = null, + ) { + appendLine(" match $expr {") + appendLine(" Ok(result) => {") + appendValueReturnFromBinding("result", returnType, returnRustType, returnsBorrowed, " ", returnConversion) + appendLine(" }") + appendLine(" Err(e) => {") + appendLine(" kne_set_error(e.to_string());") + appendLine(" ${defaultReturnValue(returnType)}") + appendLine(" }") + appendLine(" }") + } + + private fun StringBuilder.appendValueReturnHandling( + expr: String, + returnType: KneType, + returnRustType: String? = null, + returnsBorrowed: Boolean = false, + returnConversion: String? = null, + ) { + appendLine(" let result = $expr;") + appendValueReturnFromBinding("result", returnType, returnRustType, returnsBorrowed, " ", returnConversion) + } + + private fun StringBuilder.appendValueReturnFromBinding( + binding: String, + returnType: KneType, + returnRustType: String? = null, + returnsBorrowed: Boolean = false, + indent: String = " ", + returnConversion: String? = null, + ) { + if (returnConversion != null) { + appendLine("${indent}let $binding = $binding$returnConversion;") + } + when (returnType) { + KneType.STRING -> { + appendStringOutput(binding, indent, returnRustType) + } + KneType.BYTE_ARRAY -> { + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} unsafe { std::ptr::copy_nonoverlapping($binding.as_ptr(), out_buf, $binding.len()); }") + appendLine("${indent}}") + appendLine("${indent}len") + } + KneType.UNIT -> { + appendLine("${indent}()") + } + KneType.NEVER -> { + appendLine("${indent}()") + } + is KneType.DATA_CLASS -> { + val dc = returnType + for (field in dc.fields) { + when (field.type) { + KneType.STRING -> { + appendLine("${indent}let _f_bytes = $binding.${field.name}.as_bytes();") + appendLine("${indent}if (_f_bytes.len() as i32) < out_${field.name}_len {") + appendLine("${indent} unsafe { std::ptr::copy_nonoverlapping(_f_bytes.as_ptr(), out_${field.name}, _f_bytes.len()); }") + appendLine("${indent} unsafe { *out_${field.name}.add(_f_bytes.len()) = 0; }") + appendLine("${indent}}") + } + else -> { + appendLine("${indent}unsafe { *out_${field.name} = ${rustReturnExpr("$binding.${field.name}", field.type, field.rustType)}; }") + } + } + } + appendLine("${indent}()") + } + is KneType.TUPLE -> { + val tuple = returnType + val tupleCounter = intArrayOf(0) // shared across all nested tuples in this return + for ((idx, elemType) in tuple.elementTypes.withIndex()) { + when (elemType) { + KneType.STRING -> { + appendLine("${indent}let _e_bytes = $binding.$idx.as_bytes();") + appendLine("${indent}if (_e_bytes.len() as i32) < out_t_${idx}_len {") + appendLine("${indent} unsafe { std::ptr::copy_nonoverlapping(_e_bytes.as_ptr(), out_t_$idx, _e_bytes.len()); }") + appendLine("${indent} unsafe { *out_t_$idx.add(_e_bytes.len()) = 0; }") + appendLine("${indent}}") + } + is KneType.LIST -> { + appendLine("${indent}let _list_len_$idx = $binding.$idx.len() as i32;") + appendLine("${indent}unsafe { *out_t_${idx}_len = _list_len_$idx; }") + appendLine("${indent}if _list_len_$idx <= out_t_${idx}_cap {") + appendListTupleElementWrite("$binding.$idx", elemType as KneType.LIST, "out_t_$idx", indent) + appendLine("${indent}}") + } + is KneType.SET -> { + appendLine("${indent}let _set_len_$idx = $binding.$idx.len() as i32;") + appendLine("${indent}unsafe { *out_t_${idx}_len = _set_len_$idx; }") + appendLine("${indent}if _set_len_$idx <= out_t_${idx}_cap {") + appendListTupleElementWrite("$binding.$idx", KneType.LIST(elemType.elementType), "out_t_$idx", indent) + appendLine("${indent}}") + } + is KneType.MAP -> { + appendMapTupleElementWrite("$binding.$idx", elemType as KneType.MAP, idx, indent) + } + is KneType.TUPLE -> { + val bufVar = appendNestedTupleWrite(indent, "$binding.$idx", elemType as KneType.TUPLE, tupleCounter) + appendLine("${indent}unsafe { out_t_$idx.write($bufVar as i64); }") + } + else -> { + appendLine("${indent}unsafe { *out_t_$idx = ${rustReturnExpr("$binding.$idx", elemType, null)}; }") + } + } + } + appendLine("${indent}()") + } + is KneType.OBJECT -> { + if (returnsBorrowed) { + appendLine("${indent}$binding as *const _ as i64") + } else { + appendLine("${indent}Box::into_raw(Box::new($binding)) as i64") + } + } + is KneType.INTERFACE -> { + // impl Trait returns: box the concrete value into Box first + if (returnRustType?.startsWith("impl dyn ") == true) { + val traitName = returnRustType.removePrefix("impl dyn ") + appendLine("${indent}let $binding: Box = Box::new($binding);") + } + if (returnsBorrowed) { + appendLine("${indent}$binding as *const _ as i64") + } else { + appendLine("${indent}Box::into_raw(Box::new($binding)) as i64") + } + } + is KneType.SEALED_ENUM -> { + if (returnsBorrowed) { + appendLine("${indent}$binding as *const _ as i64") + } else { + appendLine("${indent}Box::into_raw(Box::new($binding)) as i64") + } + } + is KneType.NULLABLE -> { + appendNullableReturn(returnType, binding, returnsBorrowed, returnRustType, indent) + } + is KneType.LIST -> { + appendListReturnFromBinding(binding, returnType, indent, returnRustType) + } + is KneType.SET -> { + appendListReturnFromBinding(binding, KneType.LIST(returnType.elementType), indent, returnRustType) + } + is KneType.MAP -> { + appendMapReturnFromBinding(binding, returnType, indent, returnRustType) + } + else -> { appendLine("${indent}${rustReturnExpr(binding, returnType, returnRustType, returnsBorrowed)}") + } + } + } + + // --- Enum bridges --- + + private fun StringBuilder.appendListReturnFromBinding(binding: String, listType: KneType.LIST, indent: String, returnRustType: String? = null) { + val elemType = listType.elementType + when (elemType) { + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> { + // Check if this OBJECT is actually an enum (cross-crate mis-resolution) + if (elemType is KneType.OBJECT && elemType.simpleName in simpleEnumTypeNames) { + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut i32).add(i) = *v as i32; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } else { + // Return borrowed pointers - ownership remains with parent + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut i64).add(i) = v as *const _ as i64; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } + } + KneType.STRING -> { + // Serialize strings as null-terminated, concatenated in buffer. + // Returns count on success, or -(total_bytes_needed) on overflow. + val needsLossy = isPathLikeRustType(returnRustType) + appendLine("${indent}let mut total_needed = 0usize;") + appendLine("${indent}let mut offset = 0usize;") + appendLine("${indent}let mut overflow = false;") + appendLine("${indent}for s in $binding.iter() {") + if (needsLossy) { + appendLine("${indent} let _lossy = s.to_string_lossy();") + appendLine("${indent} let bytes = _lossy.as_bytes();") + } else { + appendLine("${indent} let bytes = s.as_bytes();") + } + appendLine("${indent} total_needed += bytes.len() + 1;") + appendLine("${indent} if !overflow && offset + bytes.len() + 1 <= out_buf_len as usize {") + appendLine("${indent} unsafe {") + appendLine("${indent} std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf.add(offset), bytes.len());") + appendLine("${indent} *out_buf.add(offset + bytes.len()) = 0;") + appendLine("${indent} }") + appendLine("${indent} offset += bytes.len() + 1;") + appendLine("${indent} } else {") + appendLine("${indent} overflow = true;") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}if overflow { -(total_needed as i32) } else { $binding.len() as i32 }") + } + KneType.LONG -> { + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut i64).add(i) = *v as i64; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } + KneType.DOUBLE -> { + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut f64).add(i) = *v as f64; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } + KneType.FLOAT -> { + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut f32).add(i) = *v as f32; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } + KneType.BOOLEAN -> { + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut i32).add(i) = if *v { 1 } else { 0 }; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } + is KneType.ENUM -> { + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut i32).add(i) = v.clone() as i32; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } + else -> { + // Default: treat as i32 primitives + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_buf_len {") + appendLine("${indent} for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *(out_buf as *mut i32).add(i) = *v as i32; }") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len") + } + } + } + + private fun StringBuilder.appendListTupleElementWrite( + binding: String, listType: KneType.LIST, bufName: String, indent: String + ) { + val elemType = listType.elementType + when (elemType) { + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = v as *const _ as i64; }") + appendLine("${indent}}") + } + KneType.STRING -> { + appendLine("${indent}let mut offset = 0usize;") + appendLine("${indent}for s in $binding.iter() {") + appendLine("${indent} let bytes = s.as_bytes();") + appendLine("${indent} unsafe {") + appendLine("${indent} std::ptr::copy_nonoverlapping(bytes.as_ptr(), $bufName.add(offset), bytes.len());") + appendLine("${indent} *$bufName.add(offset + bytes.len()) = 0;") + appendLine("${indent} }") + appendLine("${indent} offset += bytes.len() + 1;") + appendLine("${indent}}") + } + KneType.LONG -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = *v as i64; }") + appendLine("${indent}}") + } + KneType.DOUBLE -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut f64).add(i) = *v as f64; }") + appendLine("${indent}}") + } + KneType.FLOAT -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut f32).add(i) = *v as f32; }") + appendLine("${indent}}") + } + KneType.BOOLEAN -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = if *v { 1 } else { 0 }; }") + appendLine("${indent}}") + } + is KneType.ENUM -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = v.clone() as i32; }") + appendLine("${indent}}") + } + KneType.INT -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = *v as i32; }") + appendLine("${indent}}") + } + KneType.SHORT -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i16).add(i) = *v as i16; }") + appendLine("${indent}}") + } + KneType.BYTE -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i8).add(i) = *v as i8; }") + appendLine("${indent}}") + } + is KneType.LIST, is KneType.SET -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = Box::into_raw(Box::new(v.clone())) as i64; }") + appendLine("${indent}}") + } + is KneType.MAP, is KneType.TUPLE, is KneType.DATA_CLASS, is KneType.NULLABLE, KneType.BYTE_ARRAY -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = Box::into_raw(Box::new(v)) as i64; }") + appendLine("${indent}}") + } + else -> { + appendLine("${indent}for (i, v) in $binding.iter().enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = *v as i32; }") + appendLine("${indent}}") + } + } + } + + private fun StringBuilder.appendMapTupleElementWrite( + binding: String, mapType: KneType.MAP, idx: Int, indent: String + ) { + val keyType = mapType.keyType + val valueType = mapType.valueType + appendLine("${indent}let _map_len = $binding.len() as i32;") + appendLine("${indent}unsafe { *out_t_${idx}_len = _map_len; }") + appendLine("${indent}if _map_len <= out_t_${idx}_keys_len && _map_len <= out_t_${idx}_values_len {") + appendMapElementWrite("$binding.keys()", keyType, "out_t_${idx}_keys", indent + " ", null) + appendMapElementWrite("$binding.values()", valueType, "out_t_${idx}_values", indent + " ", null) + appendLine("${indent}}") + } + + // ── MAP return bridge ──────────────────────────────────────────────────── + + private fun StringBuilder.appendMapReturnFromBinding( + binding: String, mapType: KneType.MAP, indent: String, returnRustType: String? = null + ) { + val keyType = mapType.keyType + val valueType = mapType.valueType + appendLine("${indent}let len = $binding.len() as i32;") + appendLine("${indent}if len <= out_max_len {") + // Write keys + appendMapElementWrite("$binding.keys()", keyType, "out_keys", indent + " ", returnRustType) + // Write values + appendMapElementWrite("$binding.values()", valueType, "out_values", indent + " ", returnRustType) + appendLine("${indent}}") + appendLine("${indent}len") + } + + private fun StringBuilder.appendMapElementWrite( + iterExpr: String, elemType: KneType, bufName: String, + indent: String, returnRustType: String? = null, + ) { + when (elemType) { + KneType.STRING -> { + val needsLossy = isPathLikeRustType(returnRustType) + appendLine("${indent}let mut offset = 0usize;") + appendLine("${indent}for s in $iterExpr {") + if (needsLossy) { + appendLine("${indent} let _lossy = s.to_string_lossy();") + appendLine("${indent} let bytes = _lossy.as_bytes();") + } else { + appendLine("${indent} let bytes = s.as_bytes();") + } + appendLine("${indent} unsafe {") + appendLine("${indent} std::ptr::copy_nonoverlapping(bytes.as_ptr(), $bufName.add(offset), bytes.len());") + appendLine("${indent} *$bufName.add(offset + bytes.len()) = 0;") + appendLine("${indent} }") + appendLine("${indent} offset += bytes.len() + 1;") + appendLine("${indent}}") + } + KneType.LONG -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = *v as i64; }") + appendLine("${indent}}") + } + KneType.INT -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = *v as i32; }") + appendLine("${indent}}") + } + KneType.DOUBLE -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut f64).add(i) = *v as f64; }") + appendLine("${indent}}") + } + KneType.FLOAT -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut f32).add(i) = *v as f32; }") + appendLine("${indent}}") + } + KneType.BOOLEAN -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = if *v { 1 } else { 0 }; }") + appendLine("${indent}}") + } + is KneType.ENUM -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = v.clone() as i32; }") + appendLine("${indent}}") + } + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = v as *const _ as i64; }") + appendLine("${indent}}") + } + is KneType.LIST, is KneType.SET -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = Box::into_raw(Box::new(v.clone())) as i64; }") + appendLine("${indent}}") + } + is KneType.TUPLE, is KneType.DATA_CLASS, is KneType.NULLABLE, KneType.BYTE_ARRAY, is KneType.MAP -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i64).add(i) = Box::into_raw(Box::new(v)) as i64; }") + appendLine("${indent}}") + } + else -> { + appendLine("${indent}for (i, v) in $iterExpr.enumerate() {") + appendLine("${indent} unsafe { *($bufName as *mut i32).add(i) = *v as i32; }") + appendLine("${indent}}") + } + } + } + + // ── Enum bridges ───────────────────────────────────────────────────────── + + private fun StringBuilder.appendEnum(enum: KneEnum, prefix: String) { + val sym = "${prefix}_${enum.simpleName}" + + // name(ordinal) -> string + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${sym}_name(ordinal: i32, out_buf: *mut u8, out_buf_len: i32) -> i32 {") + appendLine(" let name = match ordinal {") + for ((i, entry) in enum.entries.withIndex()) { + appendLine(" $i => \"$entry\",") + } + appendLine(" _ => \"Unknown\",") + appendLine(" };") + appendLine(" let bytes = name.as_bytes();") + appendLine(" let len = bytes.len() as i32;") + appendLine(" if len < out_buf_len {") + appendLine(" unsafe {") + appendLine(" std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf, bytes.len());") + appendLine(" *out_buf.add(bytes.len()) = 0;") + appendLine(" }") + appendLine(" }") + appendLine(" len + 1") + appendLine("}") + appendLine() + + // count() -> i32 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${sym}_count() -> i32 {") + appendLine(" ${enum.entries.size}") + appendLine("}") + appendLine() + } + + // --- Sealed enums (tagged enums with data variants) --- + + private fun StringBuilder.appendSealedEnum(sealed: KneSealedEnum, prefix: String) { + val sym = "${prefix}_${sealed.simpleName}" + val rustName = sealed.simpleName + + // dispose(handle) + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${sym}_dispose(handle: i64) {") + appendLine(" if handle != 0 {") + appendLine(" unsafe { drop(Box::from_raw(handle as *mut $rustName)); }") + appendLine(" }") + appendLine("}") + appendLine() + + // tag(handle) -> i32 + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${sym}_tag(handle: i64) -> i32 {") + appendLine(" let obj = unsafe { &*(handle as *const $rustName) };") + appendLine(" match obj {") + for ((i, variant) in sealed.variants.withIndex()) { + val pattern = when { + variant.fields.isEmpty() -> "$rustName::${variant.name}" + variant.isTuple -> "$rustName::${variant.name}(..)" + else -> "$rustName::${variant.name} { .. }" + } + appendLine(" $pattern => $i,") + } + appendLine(" _ => -1,") + appendLine(" }") + appendLine("}") + appendLine() + + // Per-variant constructors and field getters + for ((i, variant) in sealed.variants.withIndex()) { + appendSealedVariantConstructor(sym, rustName, variant, prefix) + appendSealedVariantFieldGetters(sym, rustName, variant, prefix) + } + } + + /** Whether a collection element type can be passed as a flat C slice. */ + private fun isSupportedCollectionElementForConstructor(elemType: KneType): Boolean = when (elemType) { + KneType.INT, KneType.LONG, KneType.DOUBLE, KneType.FLOAT, + KneType.SHORT, KneType.BYTE, KneType.BOOLEAN, + KneType.STRING -> true + else -> false + } + + private fun StringBuilder.appendSealedVariantConstructor( + sym: String, rustName: String, variant: KneSealedVariant, prefix: String + ) { + // Skip variants with unsupported field types + val hasUnsupportedField = variant.fields.any { f -> + val t = f.type + when (t) { + is KneType.TUPLE -> true // Tuple fields in struct variants not yet supported + is KneType.LIST -> !isSupportedCollectionElementForConstructor(t.elementType) + is KneType.SET -> !isSupportedCollectionElementForConstructor(t.elementType) + is KneType.MAP -> !isSupportedCollectionElementForConstructor(t.keyType) || !isSupportedCollectionElementForConstructor(t.valueType) + else -> hasUnbridgeableParam(f) + } + } + if (hasUnsupportedField) return + val fnName = "${sym}_new_${variant.name}" + appendLine("#[no_mangle]") + val params = variant.fields.joinToString(", ") { f -> + when (f.type) { + KneType.BYTE_ARRAY, is KneType.LIST, is KneType.SET -> "${f.name}_ptr: ${slicePointerType(f.type)}, ${f.name}_len: i32" + is KneType.MAP -> { + val mapType = f.type as KneType.MAP + buildString { + append("${f.name}_keys_ptr: ${mapSlicePointerType(mapType.keyType)}, ") + append("${f.name}_values_ptr: ${mapSlicePointerType(mapType.valueType)}, ") + append("${f.name}_size: i32") + } + } + else -> "${f.name}: ${rustCType(f.type)}" + } + } + appendLine("pub extern \"C\" fn $fnName($params) -> i64 {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + for (f in variant.fields) { + appendParamConversion(f) + } + if (variant.fields.isEmpty()) { + appendLine(" Box::into_raw(Box::new($rustName::${variant.name})) as i64") + } else if (variant.isTuple) { + val args = variant.fields.joinToString(", ") { convertedCallArg(it) } + appendLine(" Box::into_raw(Box::new($rustName::${variant.name}($args))) as i64") + } else { + val fieldArgs = variant.fields.joinToString(", ") { f -> + "${f.name}: ${convertedCallArg(f)}" + } + appendLine(" Box::into_raw(Box::new($rustName::${variant.name} { $fieldArgs })) as i64") + } + appendLine(" })) {") + appendLine(" Ok(v) => v,") + appendLine(" Err(e) => { kne_set_panic_error(e); 0i64 }") + appendLine(" }") + appendLine("}") + appendLine() + } + + private fun StringBuilder.appendSealedFieldConversion(f: KneParam) { + when (f.type) { + KneType.STRING -> { + appendLine(" let ${f.name} = unsafe { CStr::from_ptr(${f.name}) }.to_str().unwrap_or(\"\").to_string();") + } + KneType.BOOLEAN -> { + appendLine(" let ${f.name} = ${f.name} != 0;") + } + else -> {} // Primitives don't need conversion + } + } + + private fun sealedFieldArgExpr(f: KneParam): String = when (f.type) { + KneType.STRING -> f.name // already converted to String above + KneType.BOOLEAN -> f.name // already converted to bool above + KneType.FLOAT -> "${f.name} as f32" // i32 → f32 not needed, but f32 C param is f32 + else -> f.name + } + + private fun StringBuilder.appendSealedVariantFieldGetters( + sym: String, rustName: String, variant: KneSealedVariant, prefix: String + ) { + for (f in variant.fields) { + // Skip tuple-typed fields (not yet supported in sealed variant getters) + if (f.type is KneType.TUPLE) continue + // Skip fields with unbridgeable types (fn pointers with complex params, etc.) + if (hasUnbridgeableParam(f)) continue + // Handle MAP fields specially with dual buffers + if (f.type is KneType.MAP) { + appendSealedMapVariantFieldGetter(sym, rustName, variant, f) + continue + } + + val fnName = "${sym}_${variant.name}_get_${f.name}" + val needsBuf = needsOutputBuffer(f.type) + + appendLine("#[no_mangle]") + if (needsBuf) { + appendLine("pub extern \"C\" fn $fnName(handle: i64, out_buf: *mut u8, out_buf_len: i32) -> i32 {") + } else { + appendLine("pub extern \"C\" fn $fnName(handle: i64) -> ${rustCType(f.type)} {") + } + appendLine(" let obj = unsafe { &*(handle as *const $rustName) };") + + // Build match pattern and value expression based on tuple vs struct + val fieldIndex = variant.fields.indexOf(f) + val (fieldPattern, valExpr) = if (variant.isTuple) { + // Tuple variant: match positionally + if (variant.fields.size == 1) { + "$rustName::${variant.name}(ref _v)" to "_v" + } else { + val wildcards = variant.fields.indices.joinToString(", ") { i -> + if (i == fieldIndex) "ref _v$i" else "_" + } + "$rustName::${variant.name}($wildcards)" to "_v$fieldIndex" + } + } else { + // Struct variant: match by field name + "$rustName::${variant.name} { ref ${f.name}, .. }" to f.name + } + + appendLine(" match obj {") + if (needsBuf || f.type is KneType.NULLABLE) { + // NULLABLE and buffer types need block-style handling via appendValueReturnFromBinding + appendLine(" $fieldPattern => {") + appendValueReturnFromBinding( + sealedGetterBindingExpr(f, valExpr), + f.type, + f.rustType, + returnsBorrowed = sealedGetterReturnsBorrowed(f.type), + indent = " ", + ) + appendLine(" }") + } else { + val bindingExpr = sealedGetterBindingExpr(f, valExpr) + appendLine(" $fieldPattern => ${rustReturnExpr(bindingExpr, f.type, f.rustType, sealedGetterReturnsBorrowed(f.type))},") + } + appendLine(" _ => ${defaultCReturnValue(f.type)}") + appendLine(" }") + appendLine("}") + appendLine() + } + } + + private fun StringBuilder.appendSealedMapVariantFieldGetter( + sym: String, rustName: String, variant: KneSealedVariant, f: KneParam + ) { + val fnName = "${sym}_${variant.name}_get_${f.name}" + val mapType = f.type as KneType.MAP + + appendLine("#[no_mangle]") + append("pub extern \"C\" fn $fnName(handle: i64") + append(", out_keys: ${mapOutPointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) append(", out_keys_len: i32") + append(", out_values: ${mapOutPointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) append(", out_values_len: i32") + appendLine(", out_max_len: i32) -> i32 {") + appendLine(" let obj = unsafe { &*(handle as *const $rustName) };") + + // Build match pattern and value expression based on tuple vs struct + val fieldIndex = variant.fields.indexOf(f) + val (fieldPattern, valExpr) = if (variant.isTuple) { + // Tuple variant: match positionally + if (variant.fields.size == 1) { + "$rustName::${variant.name}(ref _v)" to "_v" + } else { + val wildcards = variant.fields.indices.joinToString(", ") { i -> + if (i == fieldIndex) "ref _v$i" else "_" + } + "$rustName::${variant.name}($wildcards)" to "_v$fieldIndex" + } + } else { + // Struct variant: match by field name + "$rustName::${variant.name} { ref ${f.name}, .. }" to f.name + } + + appendLine(" match obj {") + appendLine(" $fieldPattern => {") + appendMapReturnFromBinding( + sealedGetterBindingExpr(f, valExpr), + mapType, + " ", + f.rustType + ) + appendLine(" }") + appendLine(" _ => 0") + appendLine(" }") + appendLine("}") + appendLine() + } + + private fun defaultCReturnValue(type: KneType): String { + val v = when (type) { + KneType.DOUBLE -> "0.0" + KneType.FLOAT -> "0.0" + KneType.STRING -> "0" + else -> "0" + } + return "$v," + } + + // --- Top-level functions --- + + private fun StringBuilder.appendTopLevelFunction(fn: KneFunction, prefix: String) { + // Handle dyn Trait functions with registry-based approach + if (isDynTraitFunction(fn)) { + appendDynTraitTopLevelFunction(fn, prefix) + return + } + + val sym = uniqueSym("${prefix}_${fn.name}") + val needsBuf = needsOutputBuffer(fn.returnType) + + appendLine("#[no_mangle]") + append("pub extern \"C\" fn $sym(") + val allParams = mutableListOf() + for (p in fn.params) { + val ndc = nullableDataClass(p.type) + if (p.type == KneType.BYTE_ARRAY || p.type is KneType.LIST) { + allParams.add("${p.name}_ptr: ${slicePointerType(p.type)}") + allParams.add("${p.name}_len: i32") + } else if (p.type is KneType.MAP) { + val mapType = p.type as KneType.MAP + allParams.add("${p.name}_keys_ptr: ${mapSlicePointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) allParams.add("${p.name}_keys_len: i32") + allParams.add("${p.name}_values_ptr: ${mapSlicePointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) allParams.add("${p.name}_values_len: i32") + allParams.add("${p.name}_size: i32") + } else if (ndc != null) { + allParams.addAll(nullableDataClassSignatureParamList(p.name, ndc)) + } else if (p.type is KneType.DATA_CLASS) { + val dc = p.type as KneType.DATA_CLASS + for (field in dc.fields) { + allParams.add("${p.name}_${field.name}: ${rustCType(field.type)}") + } + } else if (p.type is KneType.TUPLE) { + val tuple = p.type as KneType.TUPLE + for ((idx, elemType) in tuple.elementTypes.withIndex()) { + allParams.add("${p.name}_$idx: ${rustCType(elemType)}") + } + } else { + allParams.add("${p.name}: ${rustCType(p.type)}") + } + } + if (needsBuf) { + allParams.add("out_buf: *mut u8") + allParams.add("out_buf_len: i32") + } + // Data class return: add per-field out-params + if (fn.returnType is KneType.DATA_CLASS) { + val dc = fn.returnType as KneType.DATA_CLASS + for (field in dc.fields) { + when (field.type) { + KneType.STRING -> { + allParams.add("out_${field.name}: *mut u8") + allParams.add("out_${field.name}_len: i32") + } + else -> { + allParams.add("out_${field.name}: *mut ${rustCType(field.type)}") + } + } + } + } + // Tuple return: add per-element out-params + if (fn.returnType is KneType.TUPLE) { + val tuple = fn.returnType as KneType.TUPLE + for ((idx, elemType) in tuple.elementTypes.withIndex()) { + when (elemType) { + KneType.STRING -> { + allParams.add("out_t_$idx: *mut u8") + allParams.add("out_t_${idx}_len: i32") + } + else -> { + allParams.add("out_t_$idx: *mut ${rustCType(elemType)}") + } + } + } + } + // MAP return: dual key/value buffers + max count + extractMapReturnType(fn.returnType)?.let { mapType -> + allParams.add("out_keys: ${mapOutPointerType(mapType.keyType)}") + if (mapType.keyType == KneType.STRING) allParams.add("out_keys_len: i32") + allParams.add("out_values: ${mapOutPointerType(mapType.valueType)}") + if (mapType.valueType == KneType.STRING) allParams.add("out_values_len: i32") + allParams.add("out_max_len: i32") + } + append(allParams.joinToString(", ")) + appendLine(") -> ${rustCReturnType(fn.returnType)} {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + for (p in fn.params) { + appendParamConversion(p) + } + val callArgs = fn.params.joinToString(", ") { p -> convertedParamName(p) } + val expr = wrapExprWithMutObjectSliceWriteback( + wrapCallForSafety("${rustCallName(fn)}($callArgs)", fn.isUnsafe), fn.params + ) + if (fn.canFail) { + appendFallibleReturnHandling(expr, fn.returnType, fn.returnRustType, fn.returnsBorrowed, fn.returnConversion) + } else { + appendValueReturnHandling(expr, fn.returnType, fn.returnRustType, fn.returnsBorrowed, fn.returnConversion) + } + appendLine(" })) {") + if (fn.returnType is KneType.DATA_CLASS) { + appendLine(" Ok(_) => {},") + } else { + appendLine(" Ok(v) => v,") + } + appendLine(" Err(e) => { kne_set_panic_error(e); ${defaultReturnValue(fn.returnType)} }") + appendLine(" }") + appendLine("}") + appendLine() + } + + // --- Suspend infrastructure --- + + private fun StringBuilder.appendSuspendHelpers(prefix: String) { + appendLine("// ── Suspend helpers ──────────────────────────────────────────────────") + appendLine() + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_cancelJob(job_handle: i64) {") + appendLine(" if job_handle == 0 { return; }") + appendLine(" let flag = unsafe { &*(job_handle as *const std::sync::atomic::AtomicBool) };") + appendLine(" flag.store(true, std::sync::atomic::Ordering::SeqCst);") + appendLine("}") + appendLine() + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_disposeRef(handle: i64) {") + appendLine(" if handle == 0 { return; }") + appendLine(" unsafe { drop(Box::from_raw(handle as *mut String)); }") + appendLine("}") + appendLine() + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn ${prefix}_kne_readStringRef(handle: i64, out_buf: *mut u8, out_buf_len: i32) -> i32 {") + appendLine(" let s = unsafe { &*(handle as *const String) };") + appendLine(" let bytes = s.as_bytes();") + appendLine(" let len = bytes.len() as i32;") + appendLine(" if len < out_buf_len {") + appendLine(" unsafe {") + appendLine(" std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf, bytes.len());") + appendLine(" *out_buf.add(bytes.len()) = 0;") + appendLine(" }") + appendLine(" }") + appendLine(" len + 1") + appendLine("}") + appendLine() + } + + /** + * Generates bridge code for functions that use dyn Trait. + * Uses a registry-based approach: Box objects are stored in a registry + * and referenced by u64 handles across the FFM boundary. + */ + private fun StringBuilder.appendDynTraitTopLevelFunction(fn: KneFunction, prefix: String) { + val sym = uniqueSym("${prefix}_${fn.name}") + val returnType = fn.returnType + val params = fn.params + val rustRetType = fn.returnRustType ?: "" + val needsBuf = needsOutputBuffer(returnType) + + // Determine trait handle params (INTERFACE type = &dyn Trait or &mut dyn Trait) + val traitHandleParams = params.filter { p -> p.type is KneType.INTERFACE } + + // Determine return type category + val isBoxDynTrait = rustRetType.startsWith("Box() + for (p in params) { + when { + p.type is KneType.INTERFACE -> { + allParams.add("${p.name}_handle: i64") + } + p.type == KneType.BYTE_ARRAY || p.type is KneType.LIST -> { + allParams.add("${p.name}_ptr: ${slicePointerType(p.type)}") + allParams.add("${p.name}_len: i32") + } + p.type is KneType.DATA_CLASS -> { + val dc = p.type as KneType.DATA_CLASS + for (field in dc.fields) { + allParams.add("${p.name}_${field.name}: ${rustCType(field.type)}") + } + } + p.type is KneType.TUPLE -> { + val tuple = p.type as KneType.TUPLE + for ((idx, elemType) in tuple.elementTypes.withIndex()) { + allParams.add("${p.name}_$idx: ${rustCType(elemType)}") + } + } + else -> allParams.add("${p.name}: ${rustCType(p.type)}") + } + } + + // Add output buffer for string returns + if (needsBuf) { + allParams.add("out_buf: *mut u8") + allParams.add("out_buf_len: i32") + } + + // Handle different return types - order matters! Result must be before Box), not Result<...>. + // We detect Result returns via fn.canFail flag combined with isBoxDynTrait. + val isResultReturn = fn.canFail && isBoxDynTrait + when { + // Result, E> return - detected via canFail flag + isResultReturn -> { + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn $sym(${allParams.joinToString(", ")}) -> i64 {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + val callArgs = params.filter { (it.rustType ?: "").let { rt -> !rt.startsWith("&dyn ") && !rt.startsWith("&mut dyn ") && !rt.startsWith("Box {") + appendLine(" let handle = KNE_NEXT_HANDLE.with(|counter| {") + appendLine(" let h = *counter.borrow();") + appendLine(" *counter.borrow_mut() += 1;") + appendLine(" h") + appendLine(" });") + appendLine(" let fat_ptr_words: [usize; 2] = unsafe { std::mem::transmute(v) };") + appendLine(" KNE_TRAIT_REGISTRY.with(|reg| { reg.borrow_mut().insert(handle, fat_ptr_words); });") + appendLine(" handle as i64") + appendLine(" }") + appendLine(" Err(e) => {") + appendLine(" kne_set_error(e.to_string());") + appendLine(" 0i64") + appendLine(" }") + appendLine(" }") + appendLine(" })) {") + appendLine(" Ok(v) => v,") + appendLine(" Err(e) => { kne_set_panic_error(e); 0i64 }") + appendLine(" }") + appendLine("}") + appendLine() + } + + // Option> return + isOptionBoxDyn -> { + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn $sym(${allParams.joinToString(", ")}) -> i64 {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + val callArgs = params.filter { (it.rustType ?: "").let { rt -> !rt.startsWith("&dyn ") && !rt.startsWith("&mut dyn ") && !rt.startsWith("Box {") + appendLine(" let handle = KNE_NEXT_HANDLE.with(|counter| {") + appendLine(" let h = *counter.borrow();") + appendLine(" *counter.borrow_mut() += 1;") + appendLine(" h") + appendLine(" });") + appendLine(" let fat_ptr_words: [usize; 2] = unsafe { std::mem::transmute(v) };") + appendLine(" KNE_TRAIT_REGISTRY.with(|reg| { reg.borrow_mut().insert(handle, fat_ptr_words); });") + appendLine(" handle as i64") + appendLine(" }") + appendLine(" None => 0i64") + appendLine(" }") + appendLine(" })) {") + appendLine(" Ok(v) => v,") + appendLine(" Err(e) => { kne_set_panic_error(e); 0i64 }") + appendLine(" }") + appendLine("}") + appendLine() + } + + // Vec> return + isVecBoxDyn -> { + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn $sym(${allParams.joinToString(", ")}, out_handles: *mut i64, out_count: *mut i32) -> i32 {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + for (p in params) { + if (p.type is KneType.LIST || p.type == KneType.BYTE_ARRAY) { + appendLine(" let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + } + } + val callArgs = params.filter { (it.rustType ?: "").let { rt -> !rt.startsWith("&dyn ") && !rt.startsWith("&mut dyn ") && !rt.startsWith("Box v,") + appendLine(" Err(e) => { kne_set_panic_error(e); -1 }") + appendLine(" }") + appendLine("}") + appendLine() + } + + // Box return + isBoxDynTrait -> { + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn $sym(${allParams.joinToString(", ")}) -> i64 {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + val callArgs = params.filter { (it.rustType ?: "").let { rt -> !rt.startsWith("&dyn ") && !rt.startsWith("&mut dyn ") && !rt.startsWith("Box v,") + appendLine(" Err(e) => { kne_set_panic_error(e); 0i64 }") + appendLine(" }") + appendLine("}") + appendLine() + } + + // Functions taking &dyn Trait params — reconstruct reference via transmute from registry + traitHandleParams.isNotEmpty() -> { + appendLine("#[no_mangle]") + appendLine("pub extern \"C\" fn $sym(${allParams.joinToString(", ")}) -> ${rustCReturnType(returnType)} {") + appendLine(" KNE_LAST_ERROR.with(|e| *e.borrow_mut() = None);") + appendLine(" match catch_unwind(std::panic::AssertUnwindSafe(|| {") + // Reconstruct dyn Trait from registry via transmute + for (p in traitHandleParams) { + val traitName = (p.type as KneType.INTERFACE).simpleName + val rt = p.rustType ?: "" + if (rt.startsWith("Box: ownership transfer — remove from registry and transmute to Box + appendLine(" let ${p.name}_words = KNE_TRAIT_REGISTRY.with(|reg| {") + appendLine(" reg.borrow_mut().remove(&(${p.name}_handle as u64)).expect(\"Invalid trait handle\")") + appendLine(" });") + appendLine(" let ${p.name}_owned: Box = unsafe { std::mem::transmute(${p.name}_words) };") + } else { + // &dyn Trait / &mut dyn Trait: borrow from registry + val isMut = rt.contains("&mut ") + val mutKw = if (isMut) "mut " else "" + val refKw = if (isMut) "&mut " else "&" + appendLine(" let ${mutKw}${p.name}_words = KNE_TRAIT_REGISTRY.with(|reg| {") + appendLine(" *reg.borrow().get(&(${p.name}_handle as u64)).expect(\"Invalid trait handle\")") + appendLine(" });") + appendLine(" let ${p.name}_ref: ${refKw}dyn $traitName = unsafe { std::mem::transmute(${p.name}_words) };") + } + } + // Non-trait param conversions (strings, booleans, slices, objects, etc.) + for (p in params) { + if (!traitHandleParams.contains(p)) { + appendParamConversion(p) + } + } + // Build call args + val callArgs = params.joinToString(", ") { p -> + if (traitHandleParams.contains(p)) { + if ((p.rustType ?: "").startsWith("Box {} // nothing to return + KneType.INT -> appendLine(" result as i32") + KneType.LONG -> appendLine(" result as i64") + KneType.BOOLEAN -> appendLine(" if result { 1 } else { 0 }") + else -> appendLine(" result") + } + } + appendLine(" })) {") + appendLine(" Ok(v) => v,") + appendLine(" Err(e) => { kne_set_panic_error(e); ${defaultReturnValue(returnType)} }") + appendLine(" }") + appendLine("}") + appendLine() + } + + else -> { + // Unhandled dyn Trait return type - should not reach here with current cases + } + } + } + + private fun StringBuilder.appendFlowMethod(fn: KneFunction, cls: KneClass, prefix: String) { + val sym = uniqueSym("${prefix}_${cls.simpleName}_${fn.name}") + val className = cls.simpleName + val flowType = fn.returnType as KneType.FLOW + val elemType = flowType.elementType + + appendLine("#[no_mangle]") + append("pub extern \"C\" fn $sym(handle: i64") + for (p in fn.params) { + val ndc = nullableDataClass(p.type) + if (p.type == KneType.BYTE_ARRAY || p.type is KneType.LIST) { + append(", ${p.name}_ptr: ${slicePointerType(p.type)}, ${p.name}_len: i32") + } else if (ndc != null) { + appendNullableDataClassSignatureParams(p.name, ndc) + } else if (p.type is KneType.DATA_CLASS) { + val dc = p.type as KneType.DATA_CLASS + for (field in dc.fields) { + append(", ${p.name}_${field.name}: ${rustCType(field.type)}") + } + } else { + append(", ${p.name}: ${rustCType(p.type)}") + } + } + append(", next_ptr: i64, error_ptr: i64, complete_ptr: i64, cancel_out: *mut i64") + appendLine(") {") + + // Create cancellation flag + appendLine(" let cancel_flag = Box::into_raw(Box::new(std::sync::atomic::AtomicBool::new(false)));") + appendLine(" unsafe { *cancel_out = cancel_flag as i64; }") + appendLine(" let cancel_addr = cancel_flag as usize;") + appendLine() + + // Spawn thread + appendLine(" std::thread::spawn(move || {") + appendLine(" let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {") + if (fn.isMutating) { + appendLine(" let obj = unsafe { &mut *(handle as *mut $className) };") + } else { + appendLine(" let obj = unsafe { &*(handle as *const $className) };") + } + // Param conversions inside the closure + for (p in fn.params) { + appendSuspendParamConversion(p) + } + val callArgs = fn.params.joinToString(", ") { p -> convertedParamName(p) } + appendLine(" obj.${rustCallName(fn)}($callArgs)") + appendLine(" }));") + appendLine() + appendLine(" match result {") + appendLine(" Ok(vec) => {") + appendLine(" let next_fn: extern \"C\" fn(i64) = unsafe { std::mem::transmute(next_ptr) };") + appendLine(" let cancel = unsafe { &*(cancel_addr as *const std::sync::atomic::AtomicBool) };") + appendLine(" for item in vec {") + appendLine(" if cancel.load(std::sync::atomic::Ordering::SeqCst) { break; }") + appendLine(" next_fn(${flowElementEncode(elemType)});") + appendLine(" }") + appendLine(" let complete_fn: extern \"C\" fn() = unsafe { std::mem::transmute(complete_ptr) };") + appendLine(" complete_fn();") + appendLine(" }") + appendLine(" Err(e) => {") + appendLine(" kne_set_panic_error(e);") + appendLine(" let msg = KNE_LAST_ERROR.with(|e| e.borrow_mut().take().unwrap_or_default());") + appendLine(" let msg_handle = Box::into_raw(Box::new(msg)) as i64;") + appendLine(" let error_fn: extern \"C\" fn(i64) = unsafe { std::mem::transmute(error_ptr) };") + appendLine(" error_fn(msg_handle);") + appendLine(" }") + appendLine(" }") + appendLine(" });") + appendLine("}") + appendLine() + } + + /** Encodes a flow element value to i64 for the next_fn callback. */ + private fun flowElementEncode(elemType: KneType): String = when (elemType) { + KneType.INT -> "item as i64" + KneType.LONG -> "item as i64" + KneType.BYTE -> "item as i64" + KneType.SHORT -> "item as i64" + KneType.BOOLEAN -> "if item { 1i64 } else { 0i64 }" + KneType.STRING -> "Box::into_raw(Box::new(item)) as i64" + KneType.DOUBLE -> "i64::from_ne_bytes(item.to_ne_bytes())" + KneType.FLOAT -> "item.to_bits() as i64" + is KneType.ENUM -> "item as i64" + is KneType.OBJECT -> "Box::into_raw(Box::new(item)) as i64" + is KneType.SEALED_ENUM -> "Box::into_raw(Box::new(item)) as i64" + is KneType.INTERFACE -> "{ let w: [usize; 2] = unsafe { std::mem::transmute(item) }; let h = KNE_NEXT_HANDLE.with(|h| { let v = *h.borrow(); *h.borrow_mut() = v + 1; v }); KNE_TRAIT_REGISTRY.with(|reg| { reg.borrow_mut().insert(h, w); }); h as i64 }" + else -> "item as i64" + } + + private fun StringBuilder.appendSuspendMethod(fn: KneFunction, cls: KneClass, prefix: String) { + val sym = uniqueSym("${prefix}_${cls.simpleName}_${fn.name}") + val className = cls.simpleName + + appendLine("#[no_mangle]") + append("pub extern \"C\" fn $sym(handle: i64") + for (p in fn.params) { + val ndc = nullableDataClass(p.type) + if (p.type == KneType.BYTE_ARRAY || p.type is KneType.LIST) { + append(", ${p.name}_ptr: ${slicePointerType(p.type)}, ${p.name}_len: i32") + } else if (ndc != null) { + appendNullableDataClassSignatureParams(p.name, ndc) + } else if (p.type is KneType.DATA_CLASS) { + val dc = p.type as KneType.DATA_CLASS + for (field in dc.fields) { + append(", ${p.name}_${field.name}: ${rustCType(field.type)}") + } + } else { + append(", ${p.name}: ${rustCType(p.type)}") + } + } + append(", cont_ptr: i64, exc_ptr: i64, cancel_out: *mut i64") + appendLine(") {") + + // Create cancellation flag + appendLine(" let cancel_flag = Box::into_raw(Box::new(std::sync::atomic::AtomicBool::new(false)));") + appendLine(" unsafe { *cancel_out = cancel_flag as i64; }") + appendLine() + + // Spawn thread + appendLine(" std::thread::spawn(move || {") + appendLine(" let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {") + if (fn.isMutating) { + appendLine(" let obj = unsafe { &mut *(handle as *mut $className) };") + } else { + appendLine(" let obj = unsafe { &*(handle as *const $className) };") + } + // Param conversions inside the closure + for (p in fn.params) { + appendSuspendParamConversion(p) + } + val callArgs = fn.params.joinToString(", ") { p -> convertedParamName(p) } + appendLine(" obj.${rustCallName(fn)}($callArgs)") + appendLine(" }));") + appendLine() + appendLine(" match result {") + appendLine(" Ok(value) => {") + appendSuspendContinuationCall(fn.returnType) + appendLine(" }") + appendLine(" Err(e) => {") + appendLine(" kne_set_panic_error(e);") + appendLine(" let msg = KNE_LAST_ERROR.with(|e| e.borrow_mut().take().unwrap_or_default());") + appendLine(" let msg_handle = Box::into_raw(Box::new(msg)) as i64;") + appendLine(" let exc_fn: extern \"C\" fn(i64) = unsafe { std::mem::transmute(exc_ptr) };") + appendLine(" exc_fn(msg_handle);") + appendLine(" }") + appendLine(" }") + appendLine(" });") + appendLine("}") + appendLine() + } + + /** Generates the continuation callback invocation based on the return type. */ + private fun StringBuilder.appendSuspendContinuationCall(returnType: KneType) { + appendLine(" let cont_fn: extern \"C\" fn(i32, i64) = unsafe { std::mem::transmute(cont_ptr) };") + when (returnType) { + KneType.INT -> appendLine(" cont_fn(1, value as i64);") + KneType.LONG -> appendLine(" cont_fn(1, value);") + KneType.DOUBLE -> appendLine(" cont_fn(1, f64::to_bits(value) as i64);") + KneType.FLOAT -> appendLine(" cont_fn(1, f32::to_bits(value) as i64);") + KneType.BOOLEAN -> appendLine(" cont_fn(1, (value as i32) as i64);") + KneType.BYTE -> appendLine(" cont_fn(1, value as i64);") + KneType.SHORT -> appendLine(" cont_fn(1, value as i64);") + KneType.STRING -> { + appendLine(" let str_handle = Box::into_raw(Box::new(value)) as i64;") + appendLine(" cont_fn(1, str_handle);") + } + KneType.UNIT -> appendLine(" cont_fn(1, 0i64);") + KneType.NEVER -> appendLine(" cont_fn(1, 0i64);") + is KneType.OBJECT -> { + appendLine(" let obj_handle = Box::into_raw(Box::new(value)) as i64;") + appendLine(" cont_fn(1, obj_handle);") + } + is KneType.SEALED_ENUM -> { + appendLine(" let obj_handle = Box::into_raw(Box::new(value)) as i64;") + appendLine(" cont_fn(1, obj_handle);") + } + is KneType.INTERFACE -> { + appendLine(" let fat_ptr_words: [usize; 2] = unsafe { std::mem::transmute(value) };") + appendLine(" let handle = KNE_NEXT_HANDLE.with(|h| { let v = *h.borrow(); *h.borrow_mut() = v + 1; v });") + appendLine(" KNE_TRAIT_REGISTRY.with(|reg| { reg.borrow_mut().insert(handle, fat_ptr_words); });") + appendLine(" cont_fn(1, handle as i64);") + } + is KneType.ENUM -> appendLine(" cont_fn(1, (value as i32) as i64);") + else -> appendLine(" cont_fn(1, value as i64);") + } + } + + /** Param conversion for suspend methods (indented one extra level for the thread closure). */ + private fun StringBuilder.appendSuspendParamConversion(p: KneParam) { + appendParamConversion(p, " ") + } + + // --- Helpers --- + + private fun StringBuilder.appendReceiverBinding(fn: KneFunction, rustTypeName: String) { + when (fn.receiverKind) { + KneReceiverKind.BORROWED_SHARED -> { + appendLine(" let obj = unsafe { &*(handle as *const $rustTypeName) };") + } + KneReceiverKind.BORROWED_MUT -> { + appendLine(" let obj = unsafe { &mut *(handle as *mut $rustTypeName) };") + } + KneReceiverKind.OWNED -> { + // Use ptr::read instead of Box::from_raw to avoid UB on borrowed handles + // (e.g. objects returned from MAP). Safe for Copy types; for non-Copy types + // the caller must ensure the handle is owned. + appendLine(" let obj = unsafe { std::ptr::read(handle as *const $rustTypeName) };") + } + KneReceiverKind.NONE -> { + if (fn.isMutating) { + appendLine(" let obj = unsafe { &mut *(handle as *mut $rustTypeName) };") + } else { + appendLine(" let obj = unsafe { &*(handle as *const $rustTypeName) };") + } + } + } + } + + private fun StringBuilder.appendParamConversion(p: KneParam, indent: String = " ") { + when (p.type) { + KneType.STRING -> { + appendLine("${indent}let ${p.name}_conv = unsafe { CStr::from_ptr(${p.name}) }.to_str().unwrap_or(\"\");") + when { + requiresStaticStr(p.rustType) -> { + appendLine("${indent}let ${p.name}_static_str: &'static str = Box::leak(${p.name}_conv.to_string().into_boxed_str());") + } + isOsStrLikeRustType(p.rustType) && (p.isBorrowed || isBorrowedRustType(p.rustType)) -> { + appendLine("${indent}let ${p.name}_path = std::ffi::OsStr::new(${p.name}_conv);") + } + isOsStrLikeRustType(p.rustType) -> { + appendLine("${indent}let ${p.name}_path = std::ffi::OsString::from(${p.name}_conv.to_string());") + } + isPathLikeRustType(p.rustType) && (p.isBorrowed || isBorrowedRustType(p.rustType)) -> { + appendLine("${indent}let ${p.name}_path = std::path::Path::new(${p.name}_conv);") + } + isPathLikeRustType(p.rustType) -> { + appendLine("${indent}let ${p.name}_path = std::path::PathBuf::from(${p.name}_conv);") + } + !p.isBorrowed -> { + appendLine("${indent}let ${p.name}_str: String = ${p.name}_conv.to_string();") + } + } + } + KneType.BOOLEAN -> { + appendLine("${indent}let ${p.name}_conv = ${p.name} != 0;") + } + is KneType.ENUM -> { + // Use the qualified rustType if available to avoid ambiguity when + // multiple crates export the same enum name (e.g. cpal::SampleFormat + // vs symphonia_core::SampleFormat) + val enumName = unwrapRustWrapperType(p.rustType) ?: (p.type as KneType.ENUM).simpleName + val simpleName = (p.type as KneType.ENUM).simpleName + val entries = enumEntryNames[simpleName] + if (entries != null && entries.size == 1) { + // Single-variant enum (possibly ZST) — construct directly to avoid transmute size mismatch + appendLine("${indent}let ${p.name}_conv = $enumName::${entries[0]};") + } else { + appendLine("${indent}let ${p.name}_conv: $enumName = unsafe { std::mem::transmute(${p.name} as u8) };") + } + } + is KneType.OBJECT -> { + val rt = p.rustType ?: "" + if (rt.startsWith("Box with concrete implementor: reconstruct Box without dereference + // Rust unsize coercion will upcast Box to Box at the call site + val concreteType = rustHandleTypeName(p.type, null) + appendLine("${indent}let ${p.name}_owned = unsafe { Box::from_raw(${p.name} as *mut $concreteType) };") + } else { + appendObjectHandleConversion(p.name, rustHandleTypeName(p.type, p.rustType), p.isBorrowed, indent, p.rustType) + } + } + is KneType.INTERFACE -> { + val traitName = (p.type as KneType.INTERFACE).simpleName + val rt = p.rustType ?: "" + if (rt.startsWith("Box: ownership transfer — remove from registry and transmute to Box + appendLine("${indent}let ${p.name}_words = KNE_TRAIT_REGISTRY.with(|reg| {") + appendLine("${indent} reg.borrow_mut().remove(&(${p.name} as u64)).expect(\"Invalid trait handle\")") + appendLine("${indent}});") + appendLine("${indent}let ${p.name}_owned: Box = unsafe { std::mem::transmute(${p.name}_words) };") + } else { + // &dyn Trait / &mut dyn Trait: borrow from registry + val isMut = rt.contains("&mut ") + val mutKw = if (isMut) "mut " else "" + val refKw = if (isMut) "&mut " else "&" + appendLine("${indent}let ${mutKw}${p.name}_words = KNE_TRAIT_REGISTRY.with(|reg| {") + appendLine("${indent} *reg.borrow().get(&(${p.name} as u64)).expect(\"Invalid trait handle\")") + appendLine("${indent}});") + appendLine("${indent}let ${p.name}_ref: ${refKw}dyn $traitName = unsafe { std::mem::transmute(${p.name}_words) };") + } + } + is KneType.SEALED_ENUM -> { + appendObjectHandleConversion(p.name, rustHandleTypeName(p.type, p.rustType), p.isBorrowed, indent, p.rustType) + } + is KneType.DATA_CLASS -> { + appendDataClassParamConversion(p, p.type as KneType.DATA_CLASS, indent) + } + is KneType.TUPLE -> { + val tuple = p.type as KneType.TUPLE + val convertedFields = tuple.elementTypes.mapIndexed { idx, elemType -> + when (elemType) { + KneType.STRING -> "${p.name}_${idx}_str" + KneType.BOOLEAN -> "${p.name}_${idx}_conv" + is KneType.ENUM -> "${p.name}_${idx}_conv" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "${p.name}_${idx}_owned" + is KneType.LIST, KneType.BYTE_ARRAY -> "${p.name}_${idx}_slice" + else -> "${p.name}_$idx" + } + } + appendLine("${indent}let ${p.name}_tuple = (${convertedFields.joinToString(", ")});") + } + is KneType.NULLABLE -> { + appendNullableParamConversion(p, indent) + } + is KneType.FUNCTION -> { + appendFunctionParamConversion(p, indent) + } + KneType.BYTE_ARRAY -> { + val isMutSlice = p.rustType?.startsWith("&mut ") == true + if (isMutSlice) { + appendLine("${indent}let ${p.name}_slice = unsafe { std::slice::from_raw_parts_mut(${p.name}_ptr as *mut u8, ${p.name}_len as usize) };") + } else { + appendLine("${indent}let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + } + if (expectsOwnedVecLike(p.rustType, p.isBorrowed)) { + appendLine("${indent}let ${p.name}_vec = ${p.name}_slice.to_vec();") + } + } + is KneType.LIST -> { + val elemType = (p.type as KneType.LIST).elementType + val isStringLike = elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY + if (isStringLike) { + appendLine("${indent}let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + if (elemType == KneType.STRING) { + appendLine("${indent}let ${p.name}_vec: Vec = ${p.name}_slice.iter().map(|&p| cstr_to_string(p)).collect();") + } else { + appendLine("${indent}let ${p.name}_vec = ${p.name}_slice.to_vec();") + } + } else if (elemType is KneType.ENUM) { + // List of enum ordinals: each i64 is an ordinal, transmute to the enum type + val enumName = unwrapRustWrapperType(extractSliceElementRustType(p.rustType)) + ?: (elemType as KneType.ENUM).simpleName + val isBorrowedSlice = p.rustType?.trimStart()?.startsWith("&") == true + appendLine("${indent}let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + val mutPrefix = if (isBorrowedSlice && p.rustType?.contains("&mut ") == true) "mut " else "" + appendLine("${indent}let ${mutPrefix}${p.name}_vec: Vec<$enumName> = ${p.name}_slice.iter().map(|&v| unsafe { std::mem::transmute(v as u8) }).collect();") + } else if (elemType is KneType.OBJECT || elemType is KneType.INTERFACE || elemType is KneType.SEALED_ENUM) { + val isBorrowedSlice = p.rustType?.trimStart()?.startsWith("&") == true + if (isBorrowedSlice) { + // Borrowed slice param (&[T] or &mut [T]): copy values from handles without consuming ownership + val rustElemType = rustHandleTypeName(elemType, extractSliceElementRustType(p.rustType)) + val isMutSlice = p.rustType?.contains("&mut ") == true + appendLine("${indent}let ${p.name}_handles = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + val mutPrefix = if (isMutSlice) "mut " else "" + appendLine("${indent}let ${mutPrefix}${p.name}_vec: Vec<$rustElemType> = ${p.name}_handles.iter().map(|&h| unsafe { std::ptr::read(h as *const $rustElemType) }).collect();") + } else { + // Owned Vec field: consume boxes to transfer ownership + val rustElemType = rustHandleTypeName(elemType, null) + appendLine("${indent}let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + appendLine("${indent}let ${p.name}_vec: Vec<$rustElemType> = ${p.name}_slice.iter().map(|&h| unsafe { *Box::from_raw(h as *mut $rustElemType) }).collect();") + } + } else { + appendLine("${indent}let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + if (expectsOwnedVecLike(p.rustType, p.isBorrowed)) { + appendLine("${indent}let ${p.name}_vec = ${p.name}_slice.to_vec();") + } + } + } + is KneType.SET -> { + val elemType = (p.type as KneType.SET).elementType + val isStringLike = elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY + appendLine("${indent}let ${p.name}_slice = unsafe { std::slice::from_raw_parts(${p.name}_ptr, ${p.name}_len as usize) };") + if (isStringLike) { + if (elemType == KneType.STRING) { + appendLine("${indent}let ${p.name}_set: std::collections::HashSet = ${p.name}_slice.iter().map(|&p| cstr_to_string(p)).collect();") + } else { + appendLine("${indent}let ${p.name}_set = ${p.name}_slice.iter().cloned().collect::>();") + } + } else { + appendLine("${indent}let ${p.name}_set = ${p.name}_slice.iter().cloned().collect::>();") + } + } + is KneType.MAP -> { + val mapType = p.type as KneType.MAP + val keyType = mapType.keyType + val valueType = mapType.valueType + appendLine("${indent}let ${p.name}_keys_slice = unsafe { std::slice::from_raw_parts(${p.name}_keys_ptr, ${p.name}_size as usize) };") + appendLine("${indent}let ${p.name}_values_slice = unsafe { std::slice::from_raw_parts(${p.name}_values_ptr, ${p.name}_size as usize) };") + val keyConv = mapSliceElemReadExpr("${p.name}_keys_slice", "i", keyType) + val valueConv = mapSliceElemReadExpr("${p.name}_values_slice", "i", valueType) + appendLine("${indent}let mut ${p.name}_map: std::collections::HashMap<${mapValueRustType(keyType)}, ${mapValueRustType(valueType)}> = std::collections::HashMap::new();") + appendLine("${indent}for i in 0..${p.name}_size as usize {") + appendLine("${indent} ${p.name}_map.insert($keyConv, $valueConv);") + appendLine("${indent}}") + } + else -> { + primitiveCastType(p.type, p.rustType)?.let { castType -> + appendLine("${indent}let ${p.name}_conv = ${p.name} as $castType;") + } + } + } + } + + private fun StringBuilder.appendObjectHandleConversion( + name: String, + rustTypeName: String, + borrowed: Boolean, + indent: String, + originalRustType: String? = null, + ) { + val useInferredCast = requiresInferredObjectCast(rustTypeName) + if (borrowed) { + // Check mutability from the ORIGINAL rustType (before unwrapping stripped &mut) + val isMutRef = originalRustType?.contains("&mut") == true || + rustTypeName.startsWith("&mut ") || rustTypeName.contains("&mut") + if (isMutRef) { + if (useInferredCast) { + appendLine("${indent}let ${name}_borrowed = unsafe { &mut *(${name} as *mut _) };") + } else { + val cleanType = rustTypeName.removePrefix("&mut ").trim() + appendLine("${indent}let ${name}_borrowed = unsafe { &mut *(${name} as *mut $cleanType) };") + } + } else if (useInferredCast) { + appendLine("${indent}let ${name}_borrowed = unsafe { &*(${name} as *const _) };") + } else { + appendLine("${indent}let ${name}_borrowed = unsafe { &*(${name} as *const $rustTypeName) };") + } + } else { + if (useInferredCast) { + appendLine("${indent}let ${name}_owned = unsafe { *Box::from_raw(${name} as *mut _) };") + } else { + appendLine("${indent}let ${name}_owned = unsafe { *Box::from_raw(${name} as *mut $rustTypeName) };") + } + } + } + + private fun StringBuilder.appendDataClassParamConversion( + p: KneParam, + dc: KneType.DATA_CLASS, + indent: String, + ) { + for (field in dc.fields) { + if (field.type == KneType.STRING) { + appendLine("${indent}let ${p.name}_${field.name}_conv = unsafe { CStr::from_ptr(${p.name}_${field.name}) }.to_str().unwrap_or(\"\");") + } + } + val fieldAssignments = dc.fields.joinToString(", ") { field -> + val sourceName = "${p.name}_${field.name}" + when (field.type) { + KneType.STRING -> when { + requiresStaticStr(field.rustType) -> "${field.name}: Box::leak(${sourceName}_conv.to_string().into_boxed_str())" + isPathLikeRustType(field.rustType) -> "${field.name}: std::path::PathBuf::from(${sourceName}_conv)" + field.isBorrowed -> "${field.name}: ${sourceName}_conv" + else -> "${field.name}: ${sourceName}_conv.to_string()" + } + KneType.BOOLEAN -> "${field.name}: ${sourceName} != 0" + is KneType.ENUM -> "${field.name}: unsafe { std::mem::transmute(${sourceName} as u8) }" + else -> { + primitiveCastType(field.type, field.rustType)?.let { castType -> + "${field.name}: ${sourceName} as $castType" + } ?: "${field.name}: $sourceName" + } + } + } + appendLine("${indent}let ${p.name}_dc = ${dc.simpleName} { $fieldAssignments };") + } + + private fun StringBuilder.appendNullableParamConversion(p: KneParam, indent: String) { + val inner = (p.type as KneType.NULLABLE).inner + val optionInnerRustType = optionInnerRustType(p.rustType) + when (inner) { + KneType.INT -> { + val cast = primitiveCastType(inner, optionInnerRustType) + val rustInner = cast ?: "i32" + val someExpr = cast?.let { "${p.name} as $it" } ?: "${p.name} as i32" + appendLine("${indent}let ${p.name}_opt: Option<$rustInner> = if ${p.name} == i64::MIN { None } else { Some($someExpr) };") + } + KneType.LONG -> { + val cast = primitiveCastType(inner, optionInnerRustType) + val rustInner = cast ?: "i64" + val someExpr = cast?.let { "${p.name} as $it" } ?: p.name + appendLine("${indent}let ${p.name}_opt: Option<$rustInner> = if ${p.name} == i64::MIN { None } else { Some($someExpr) };") + } + KneType.STRING -> { + appendLine("${indent}let ${p.name}_opt = if ${p.name}.is_null() { None } else {") + appendLine("${indent} let ${p.name}_value = unsafe { CStr::from_ptr(${p.name}) }.to_str().unwrap_or(\"\");") + when { + requiresStaticStr(optionInnerRustType) -> { + appendLine("${indent} Some(Box::leak(${p.name}_value.to_string().into_boxed_str()) as &'static str)") + } + isPathLikeRustType(optionInnerRustType) && isBorrowedRustType(optionInnerRustType) -> { + appendLine("${indent} Some(std::path::Path::new(${p.name}_value))") + } + isPathLikeRustType(optionInnerRustType) -> { + appendLine("${indent} Some(std::path::PathBuf::from(${p.name}_value))") + } + isBorrowedRustType(optionInnerRustType) -> { + appendLine("${indent} Some(${p.name}_value)") + } + else -> { + appendLine("${indent} Some(${p.name}_value.to_string())") + } + } + appendLine("${indent}};") + } + KneType.BOOLEAN -> { + appendLine("${indent}let ${p.name}_opt = if ${p.name} < 0 { None } else { Some(${p.name} != 0) };") + } + KneType.DOUBLE -> { + appendLine("${indent}let ${p.name}_opt = if ${p.name} == i64::MIN { None } else { Some(f64::from_ne_bytes(${p.name}.to_ne_bytes())) };") + } + KneType.FLOAT -> { + appendLine("${indent}let ${p.name}_opt = if ${p.name} == i64::MIN { None } else { Some(f32::from_bits(${p.name} as u32)) };") + } + KneType.BYTE -> { + val cast = primitiveCastType(inner, optionInnerRustType) ?: "i8" + appendLine("${indent}let ${p.name}_opt = if ${p.name} == i32::MIN { None } else { Some(${p.name} as $cast) };") + } + KneType.SHORT -> { + val cast = primitiveCastType(inner, optionInnerRustType) ?: "i16" + appendLine("${indent}let ${p.name}_opt = if ${p.name} == i32::MIN { None } else { Some(${p.name} as $cast) };") + } + is KneType.ENUM -> { + val enumName = inner.simpleName + appendLine("${indent}let ${p.name}_opt: Option<$enumName> = if ${p.name} < 0 { None } else { Some(unsafe { std::mem::transmute(${p.name} as u8) }) };") + } + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> { + val rustTypeName = rustHandleTypeName(inner, optionInnerRustType) + val borrowed = isBorrowedRustType(optionInnerRustType) + if (borrowed) { + if (requiresInferredObjectCast(rustTypeName)) { + appendLine("${indent}let ${p.name}_opt = if ${p.name} == 0 { None } else { Some(unsafe { &*(${p.name} as *const _) }) };") + } else { + appendLine("${indent}let ${p.name}_opt = if ${p.name} == 0 { None } else { Some(unsafe { &*(${p.name} as *const $rustTypeName) }) };") + } + } else { + if (requiresInferredObjectCast(rustTypeName)) { + appendLine("${indent}let ${p.name}_opt = if ${p.name} == 0 { None } else { Some(unsafe { *Box::from_raw(${p.name} as *mut _) }) };") + } else { + appendLine("${indent}let ${p.name}_opt = if ${p.name} == 0 { None } else { Some(unsafe { *Box::from_raw(${p.name} as *mut $rustTypeName) }) };") + } + } + } + is KneType.FUNCTION -> { + // Nullable function: 0 means None, otherwise transmute to fn and wrap in Some + appendLine("${indent}let ${p.name}_opt = if ${p.name} == 0 { None } else {") + // Reuse non-nullable function conversion by creating a synthetic param + val innerParam = KneParam(name = p.name, type = inner, isBorrowed = p.isBorrowed, rustType = p.rustType?.let { optionInnerRustType(it) }) + appendFunctionParamConversion(innerParam, "$indent ") + appendLine("$indent Some(${p.name}_fn)") + appendLine("$indent};") + } + is KneType.DATA_CLASS -> { + val dc = inner + val borrowed = isBorrowedRustType(optionInnerRustType) + // Reconstruct the data class from expanded fields + for (field in dc.fields) { + if (field.type == KneType.STRING) { + appendLine("${indent}let ${p.name}_${field.name}_conv = unsafe { CStr::from_ptr(${p.name}_${field.name}) }.to_str().unwrap_or(\"\");") + } + } + val fieldAssignments = dc.fields.joinToString(", ") { field -> + val sourceName = "${p.name}_${field.name}" + when (field.type) { + KneType.STRING -> when { + requiresStaticStr(field.rustType) -> "${field.name}: Box::leak(${sourceName}_conv.to_string().into_boxed_str())" + isPathLikeRustType(field.rustType) -> "${field.name}: std::path::PathBuf::from(${sourceName}_conv)" + field.isBorrowed -> "${field.name}: ${sourceName}_conv" + else -> "${field.name}: ${sourceName}_conv.to_string()" + } + KneType.BOOLEAN -> "${field.name}: ${sourceName} != 0" + is KneType.ENUM -> "${field.name}: unsafe { std::mem::transmute(${sourceName} as u8) }" + else -> { + primitiveCastType(field.type, field.rustType)?.let { castType -> + "${field.name}: ${sourceName} as $castType" + } ?: "${field.name}: $sourceName" + } + } + } + val dcVal = "${dc.simpleName} { $fieldAssignments }" + if (borrowed) { + // Declare the value outside the Option so the borrow lives long enough + appendLine("${indent}let ${p.name}_dc_val = $dcVal;") + appendLine("${indent}let ${p.name}_opt = if ${p.name}_has != 0 { None } else { Some(&${p.name}_dc_val) };") + } else { + appendLine("${indent}let ${p.name}_opt = if ${p.name}_has != 0 { None } else { Some($dcVal) };") + } + } + else -> { + appendLine("${indent}let ${p.name}_opt = if ${p.name} == i64::MIN { None } else { Some(${p.name}) };") + } + } + } + + /** + * Generates conversion for a FUNCTION param. + * For simple types (all primitives): transmute directly to Rust fn() pointer. + * For types needing conversion (bool, String): transmute to extern "C" fn + closure wrapper. + */ + private fun StringBuilder.appendFunctionParamConversion(p: KneParam, indent: String) { + val fnType = p.type as KneType.FUNCTION + val hasHandleType = fnType.paramTypes.any { it.isHandleBackedType() } || fnType.returnType.isHandleBackedType() + val needsConversion = fnType.paramTypes.any { it == KneType.BOOLEAN || it == KneType.STRING } || + (fnType.returnType == KneType.BOOLEAN || fnType.returnType == KneType.STRING) || + hasHandleType + + if (!needsConversion) { + // Direct transmute to Rust fn pointer (safe for primitives on x86-64/aarch64) + val nativeParams = fnType.paramTypes.joinToString(", ") { rustNativeType(it) } + val nativeRet = if (fnType.returnType == KneType.UNIT || fnType.returnType == KneType.NEVER) "()" else rustNativeType(fnType.returnType) + appendLine("${indent}let ${p.name}_fn: fn($nativeParams) -> $nativeRet = unsafe { std::mem::transmute(${p.name}) };") + } else if (!hasHandleType) { + // Transmute to extern "C" fn, then wrap in closure for type conversion + val cParamTypes = fnType.paramTypes.mapIndexed { i, t -> + "_p$i: ${rustCType(t)}" + }.joinToString(", ") + val cRetType = if (fnType.returnType == KneType.UNIT || fnType.returnType == KneType.NEVER) "()" else rustCType(fnType.returnType) + appendLine("${indent}let ${p.name}_c: extern \"C\" fn($cParamTypes) -> $cRetType = unsafe { std::mem::transmute(${p.name}) };") + + val closureParams = fnType.paramTypes.mapIndexed { i, t -> + "_cp$i: ${rustNativeType(t)}" + }.joinToString(", ") + val closureRetType = if (fnType.returnType == KneType.UNIT || fnType.returnType == KneType.NEVER) "" else " -> ${rustNativeType(fnType.returnType)}" + val callArgs = fnType.paramTypes.mapIndexed { i, t -> + rustToCCallArgConvert("_cp$i", t) + }.joinToString(", ") + val callExpr = "${p.name}_c($callArgs)" + val returnExpr = rustFromCRetConvert(callExpr, fnType.returnType) + appendLine("${indent}let ${p.name}_fn = move |$closureParams|$closureRetType { $returnExpr };") + } else { + // Multi-line closure for handle-backed types (OBJECT, INTERFACE, SEALED_ENUM) + val cParamTypes = fnType.paramTypes.mapIndexed { i, t -> + "_p$i: ${rustCType(t)}" + }.joinToString(", ") + val cRetType = if (fnType.returnType == KneType.UNIT || fnType.returnType == KneType.NEVER) "()" else rustCType(fnType.returnType) + appendLine("${indent}let ${p.name}_c: extern \"C\" fn($cParamTypes) -> $cRetType = unsafe { std::mem::transmute(${p.name}) };") + + val closureParams = fnType.paramTypes.mapIndexed { i, t -> + "_cp$i: ${callbackNativeParamType(t)}" + }.joinToString(", ") + val closureRetType = if (fnType.returnType == KneType.UNIT || fnType.returnType == KneType.NEVER) "" else " -> ${callbackNativeReturnType(fnType.returnType)}" + appendLine("${indent}let ${p.name}_fn = move |$closureParams|$closureRetType {") + + // Convert each param to C ABI handle + for ((i, t) in fnType.paramTypes.withIndex()) { + appendCallbackParamToHandle(i, t, "$indent ") + } + + // Build call args + val callArgs = fnType.paramTypes.mapIndexed { i, t -> + callbackHandleArgExpr(i, t) + }.joinToString(", ") + + val retType = fnType.returnType + if (retType == KneType.UNIT || retType == KneType.NEVER) { + appendLine("$indent ${p.name}_c($callArgs);") + } else { + appendLine("$indent let _cb_result = ${p.name}_c($callArgs);") + } + + // Cleanup temporary handles for borrowed params + for ((i, t) in fnType.paramTypes.withIndex()) { + appendCallbackParamCleanup(i, t, "$indent ") + } + + // Convert return value from C ABI + if (retType != KneType.UNIT && retType != KneType.NEVER) { + appendCallbackReturnFromHandle(retType, "$indent ") + } + + appendLine("$indent};") + } + } + + /** Returns true if the type uses opaque i64 handles across the C ABI boundary. */ + private fun KneType.isHandleBackedType(): Boolean = this is KneType.OBJECT || this is KneType.INTERFACE || this is KneType.SEALED_ENUM + + /** Native Rust type for callback closure parameters. */ + private fun callbackNativeParamType(type: KneType): String = when (type) { + is KneType.OBJECT -> objectRustTypeNames[type.simpleName] ?: type.simpleName + is KneType.SEALED_ENUM -> type.simpleName + is KneType.INTERFACE -> "Box" + else -> rustNativeType(type) + } + + /** Native Rust type for callback closure return values. */ + private fun callbackNativeReturnType(type: KneType): String = when (type) { + is KneType.OBJECT -> objectRustTypeNames[type.simpleName] ?: type.simpleName + is KneType.SEALED_ENUM -> type.simpleName + is KneType.INTERFACE -> "Box" + else -> rustNativeType(type) + } + + /** Emit code to convert a callback param to a C ABI handle. */ + private fun StringBuilder.appendCallbackParamToHandle(index: Int, type: KneType, indent: String) { + when (type) { + is KneType.OBJECT, is KneType.SEALED_ENUM -> { + appendLine("${indent}let _h$index = Box::into_raw(Box::new(_cp$index)) as i64;") + } + is KneType.INTERFACE -> { + appendLine("${indent}let _h${index}_words: [usize; 2] = unsafe { std::mem::transmute(_cp$index) };") + appendLine("${indent}let _h$index = KNE_NEXT_HANDLE.with(|h| { let v = *h.borrow(); *h.borrow_mut() = v + 1; v });") + appendLine("${indent}KNE_TRAIT_REGISTRY.with(|reg| { reg.borrow_mut().insert(_h$index, _h${index}_words); });") + } + KneType.BOOLEAN -> appendLine("${indent}let _h$index = if _cp$index { 1i32 } else { 0i32 };") + KneType.STRING -> appendLine("${indent}let _h$index = std::ffi::CString::new(_cp$index).unwrap().into_raw();") + else -> {} // Primitives don't need conversion + } + } + + /** The C ABI argument expression for a callback param. */ + private fun callbackHandleArgExpr(index: Int, type: KneType): String = when (type) { + is KneType.OBJECT, is KneType.SEALED_ENUM -> "_h$index" + is KneType.INTERFACE -> "_h$index as i64" + KneType.BOOLEAN, KneType.STRING -> "_h$index" + else -> "_cp$index" + } + + /** Emit cleanup code after callback returns (reclaim temporary handles). */ + private fun StringBuilder.appendCallbackParamCleanup(index: Int, type: KneType, indent: String) { + when (type) { + is KneType.OBJECT -> { + val rustName = objectRustTypeNames[type.simpleName] ?: type.simpleName + appendLine("${indent}unsafe { let _ = Box::from_raw(_h$index as *mut $rustName); }") + } + is KneType.SEALED_ENUM -> { + appendLine("${indent}unsafe { let _ = Box::from_raw(_h$index as *mut ${type.simpleName}); }") + } + is KneType.INTERFACE -> { + appendLine("${indent}KNE_TRAIT_REGISTRY.with(|reg| { reg.borrow_mut().remove(&_h$index); });") + } + KneType.STRING -> { + appendLine("${indent}unsafe { let _ = std::ffi::CString::from_raw(_h$index); }") + } + else -> {} // No cleanup needed + } + } + + /** Emit code to convert a C ABI return value back to native Rust type. */ + private fun StringBuilder.appendCallbackReturnFromHandle(type: KneType, indent: String) { + when (type) { + is KneType.OBJECT -> { + appendLine("${indent}unsafe { *Box::from_raw(_cb_result as *mut ${type.simpleName}) }") + } + is KneType.SEALED_ENUM -> { + appendLine("${indent}unsafe { *Box::from_raw(_cb_result as *mut ${type.simpleName}) }") + } + is KneType.INTERFACE -> { + appendLine("${indent}let _ret_words = KNE_TRAIT_REGISTRY.with(|reg| {") + appendLine("$indent reg.borrow_mut().remove(&(_cb_result as u64)).expect(\"Invalid trait handle\")") + appendLine("${indent}});") + appendLine("${indent}unsafe { std::mem::transmute::<[usize; 2], Box>(_ret_words) }") + } + KneType.BOOLEAN -> appendLine("${indent}_cb_result != 0") + KneType.STRING -> appendLine("${indent}unsafe { std::ffi::CString::from_raw(_cb_result as *mut c_char) }.into_string().unwrap_or_default()") + KneType.UNIT, KneType.NEVER -> {} + else -> appendLine("${indent}_cb_result") + } + } + + /** Rust native type (not C ABI) for closure params. */ + private fun rustNativeType(type: KneType): String = when (type) { + KneType.INT -> "i32" + KneType.LONG -> "i64" + KneType.DOUBLE -> "f64" + KneType.FLOAT -> "f32" + KneType.BOOLEAN -> "bool" + KneType.BYTE -> "i8" + KneType.SHORT -> "i16" + KneType.STRING -> "String" + else -> rustCType(type) // fallback + } + + /** Convert a Rust native value to C ABI for calling into a C callback. */ + private fun rustToCCallArgConvert(expr: String, type: KneType): String = when (type) { + KneType.BOOLEAN -> "if $expr { 1 } else { 0 }" + KneType.STRING -> "std::ffi::CString::new($expr).unwrap().into_raw()" + else -> expr + } + + /** Convert a C ABI return value back to Rust native type. */ + private fun rustFromCRetConvert(expr: String, type: KneType): String = when (type) { + KneType.UNIT -> expr + KneType.NEVER -> expr + KneType.BOOLEAN -> "$expr != 0" + KneType.STRING -> "unsafe { std::ffi::CString::from_raw($expr as *mut c_char) }.into_string().unwrap_or_default()" + else -> expr + } + + /** Param name for method calls — uses isBorrowed to decide &str vs String, &T vs T. */ + private fun convertedParamName(p: KneParam): String = when (p.type) { + KneType.STRING -> when { + requiresStaticStr(p.rustType) -> "${p.name}_static_str" + isPathLikeRustType(p.rustType) -> "${p.name}_path" + p.isBorrowed || isBorrowedRustType(p.rustType) -> "${p.name}_conv" + else -> "${p.name}_str" + } + KneType.BOOLEAN -> "${p.name}_conv" + is KneType.FUNCTION -> "${p.name}_fn" + is KneType.ENUM -> if (p.isBorrowed) "&${p.name}_conv" else "${p.name}_conv" + is KneType.INTERFACE -> if (p.rustType?.startsWith("Box + if (p.isBorrowed) "${p.name}_borrowed" else "${p.name}_owned" + is KneType.DATA_CLASS -> if (p.isBorrowed) "&${p.name}_dc" else "${p.name}_dc" + is KneType.TUPLE -> "${p.name}_tuple" + is KneType.NULLABLE -> "${p.name}_opt" + KneType.BYTE_ARRAY -> if (expectsOwnedVecLike(p.rustType, p.isBorrowed)) "${p.name}_vec" else "${p.name}_slice" + is KneType.LIST -> { + val elemType = (p.type as KneType.LIST).elementType + val isStringLike = elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY + val isObjectLike = elemType is KneType.OBJECT || elemType is KneType.INTERFACE || elemType is KneType.SEALED_ENUM + val isEnumLike = elemType is KneType.ENUM + when { + isStringLike -> { + val isBorrowedSlice = p.rustType?.trimStart()?.startsWith("&") == true + when { + p.rustType?.contains("&mut ") == true -> "&mut ${p.name}_vec" + isBorrowedSlice -> "&${p.name}_vec" + else -> "${p.name}_ptr" + } + } + isEnumLike -> { + val isBorrowedSlice = p.rustType?.trimStart()?.startsWith("&") == true + when { + p.rustType?.contains("&mut ") == true -> "&mut ${p.name}_vec" + isBorrowedSlice -> "&${p.name}_vec" + else -> "${p.name}_vec" + } + } + isObjectLike -> { + val isBorrowedSlice = p.rustType?.trimStart()?.startsWith("&") == true + when { + p.rustType?.contains("&mut ") == true -> "&mut ${p.name}_vec" + isBorrowedSlice -> "&${p.name}_vec" + else -> "${p.name}_vec" // owned Vec + } + } + expectsOwnedVecLike(p.rustType, p.isBorrowed) -> "${p.name}_vec" + else -> "${p.name}_slice" + } + } + is KneType.SET -> { + val elemType = (p.type as KneType.SET).elementType + if (elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY) "${p.name}_ptr" else "${p.name}_set" + } + is KneType.MAP -> "${p.name}_map" + else -> if (primitiveCastType(p.type, p.rustType) != null) "${p.name}_conv" else p.name + } + + /** Param name for sealed enum constructor calls — uses the Vec name for LIST/SET with String. */ + private fun convertedCallArg(p: KneParam): String = when (p.type) { + is KneType.LIST -> { + val elemType = (p.type as KneType.LIST).elementType + if (elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY) "${p.name}_vec" else convertedParamName(p) + } + is KneType.SET -> { + val elemType = (p.type as KneType.SET).elementType + if (elemType == KneType.STRING || elemType == KneType.BYTE_ARRAY) "${p.name}_set" else convertedParamName(p) + } + else -> convertedParamName(p) + } + + private fun StringBuilder.appendNullableReturn( + nullableType: KneType.NULLABLE, + binding: String, + returnsBorrowed: Boolean = false, + rustType: String? = null, + indent: String = " ", + ) { + when (nullableType.inner) { + KneType.INT -> { + appendLine("${indent}match $binding { Some(v) => v as i64, None => i64::MIN }") + } + KneType.LONG -> { + appendLine("${indent}match $binding { Some(v) => v as i64, None => i64::MIN }") + } + KneType.DOUBLE -> { + appendLine("${indent}match $binding { Some(v) => i64::from_ne_bytes(v.to_ne_bytes()), None => i64::MIN }") + } + KneType.FLOAT -> { + appendLine("${indent}match $binding { Some(v) => v.to_bits() as i64, None => i64::MIN }") + } + KneType.BOOLEAN -> { + appendLine("${indent}match $binding { Some(true) => 1, Some(false) => 0, None => -1 }") + } + KneType.BYTE -> { + appendLine("${indent}match $binding { Some(v) => v as i32, None => i32::MIN }") + } + KneType.SHORT -> { + appendLine("${indent}match $binding { Some(v) => v as i32, None => i32::MIN }") + } + KneType.STRING -> { + appendLine("${indent}match $binding {") + appendLine("${indent} Some(ref s) => {") + appendStringOutput("s", "$indent ", rustType) + appendLine("${indent} }") + appendLine("${indent} None => -1") + appendLine("${indent}}") + } + is KneType.ENUM -> { + appendLine("${indent}match $binding { Some(v) => v as i32, None => -1 }") + } + is KneType.DATA_CLASS -> { + val dc = nullableType.inner + appendLine("${indent}match $binding {") + appendLine("${indent} Some(v) => {") + for (field in dc.fields) { + when (field.type) { + KneType.STRING -> { + appendLine("${indent} let _f_bytes = v.${field.name}.as_bytes();") + appendLine("${indent} if (_f_bytes.len() as i32) < out_${field.name}_len {") + appendLine("${indent} unsafe { std::ptr::copy_nonoverlapping(_f_bytes.as_ptr(), out_${field.name}, _f_bytes.len()); }") + appendLine("${indent} unsafe { *out_${field.name}.add(_f_bytes.len()) = 0; }") + appendLine("${indent} }") + } + else -> { + appendLine("${indent} unsafe { *out_${field.name} = ${rustReturnExpr("v.${field.name}", field.type, field.rustType)}; }") + } + } + } + appendLine("${indent} 1i32") + appendLine("${indent} }") + appendLine("${indent} None => 0i32") + appendLine("${indent}}") + } + KneType.BYTE_ARRAY -> { + appendLine("${indent}match $binding {") + appendLine("${indent} Some(v) => {") + appendLine("${indent} let len = v.len() as i32;") + appendLine("${indent} if len <= out_buf_len {") + appendLine("${indent} unsafe { std::ptr::copy_nonoverlapping(v.as_ptr(), out_buf, v.len()); }") + appendLine("${indent} }") + appendLine("${indent} len") + appendLine("${indent} }") + appendLine("${indent} None => -1") + appendLine("${indent}}") + } + is KneType.LIST -> { + appendLine("${indent}match $binding {") + appendLine("${indent} Some(v) => {") + appendListReturnFromBinding("v", nullableType.inner as KneType.LIST, "${indent} ", rustType) + appendLine("${indent} }") + appendLine("${indent} None => -1") + appendLine("${indent}}") + } + is KneType.SET -> { + appendLine("${indent}match $binding {") + appendLine("${indent} Some(v) => {") + appendListReturnFromBinding("v", KneType.LIST((nullableType.inner as KneType.SET).elementType), "${indent} ", rustType) + appendLine("${indent} }") + appendLine("${indent} None => -1") + appendLine("${indent}}") + } + is KneType.MAP -> { + appendLine("${indent}match $binding {") + appendLine("${indent} Some(v) => {") + appendMapReturnFromBinding("v", nullableType.inner as KneType.MAP, "${indent} ", rustType) + appendLine("${indent} }") + appendLine("${indent} None => -1") + appendLine("${indent}}") + } + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> { + appendLine("${indent}match $binding {") + if (returnsBorrowed) { + appendLine("${indent} Some(v) => v as *const _ as i64,") + } else { + appendLine("${indent} Some(v) => Box::into_raw(Box::new(v)) as i64,") + } + appendLine("${indent} None => 0i64") + appendLine("${indent}}") + } + else -> { + appendLine("${indent}match $binding { Some(v) => v as i64, None => i64::MIN }") + } + } + } + + private fun rustReturnExpr( + binding: String, + returnType: KneType, + returnRustType: String? = null, + returnsBorrowed: Boolean = false, + ): String = when (returnType) { + KneType.INT -> "$binding as i32" + KneType.LONG -> "$binding as i64" + KneType.DOUBLE -> "$binding as f64" + KneType.FLOAT -> "$binding as f32" + KneType.BYTE -> "$binding as i8" + KneType.SHORT -> "$binding as i16" + KneType.BOOLEAN -> "if $binding { 1 } else { 0 }" + is KneType.ENUM -> if (returnsBorrowed) "(unsafe { *($binding as *const _ as *const u8) }) as i32" else "$binding as i32" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> + if (returnsBorrowed) "$binding as *const _ as i64" else "Box::into_raw(Box::new($binding)) as i64" + else -> binding + } + + private fun StringBuilder.appendStringOutput(expr: String, indent: String = " ", rustType: String? = null) { + if (isPathLikeRustType(rustType)) { + appendLine("${indent}let _s = $expr.to_string_lossy();") + appendLine("${indent}let bytes = _s.as_bytes();") + } else { + appendLine("${indent}let bytes = $expr.as_bytes();") + } + appendLine("${indent}let len = bytes.len() as i32;") + appendLine("${indent}if len < out_buf_len {") + appendLine("${indent} unsafe {") + appendLine("${indent} std::ptr::copy_nonoverlapping(bytes.as_ptr(), out_buf, bytes.len());") + appendLine("${indent} *out_buf.add(bytes.len()) = 0;") + appendLine("${indent} }") + appendLine("${indent}}") + appendLine("${indent}len + 1") + } + + private fun expectsOwnedVecLike(rustType: String?, isBorrowed: Boolean): Boolean { + if (isBorrowed) return false + val normalized = normalizeRustType(rustType) ?: return false + return normalized.startsWith("Vec<") || normalized.startsWith("Into "i32" + KneType.LONG -> "i64" + KneType.DOUBLE -> "f64" + KneType.FLOAT -> "f32" + KneType.BYTE -> "i8" + KneType.SHORT -> "i16" + else -> null + } ?: return null + return when (normalized) { + defaultRustType -> null + "u32", "u64", "usize", "isize", "u16", "u8" -> normalized + else -> null + } + } + + private fun rustHandleTypeName(type: KneType, rustType: String?): String { + // For OBJECT types, prefer the class's full rustTypeName (includes qualified paths + // like tray_icon::Icon and generics like PhysicalSize). + // This must come before unwrapRustWrapperType, which would strip the qualifier + // from a simple "&Icon" to just "Icon" without the full path. + if (type is KneType.OBJECT) { + objectRustTypeNames[type.simpleName]?.let { return it } + } + val normalized = unwrapRustWrapperType(rustType) + if (normalized != null) return normalized + return when (type) { + is KneType.OBJECT -> type.simpleName + is KneType.INTERFACE -> type.simpleName + is KneType.SEALED_ENUM -> type.simpleName + else -> error("Expected handle-backed type, got $type") + } + } + + private fun requiresInferredObjectCast(rustTypeName: String): Boolean = + rustTypeName.contains("::") && + !rustTypeName.startsWith("dpi::") && + !rustTypeName.startsWith("std::") + + private fun unwrapRustWrapperType(rustType: String?): String? { + var current = rustType?.trim() ?: return null + var changed = true + while (changed) { + changed = false + normalizeRustType(current)?.let { normalized -> + if (normalized != current) { + current = normalized + changed = true + } + } + for (wrapper in listOf("Option", "Into", "From")) { + extractGenericInner(current, wrapper)?.let { inner -> + current = inner + changed = true + } + } + } + return current.ifBlank { null } + } + + private fun extractGenericInner(rustType: String, wrapper: String): String? { + val prefix = "$wrapper<" + if (!rustType.startsWith(prefix) || !rustType.endsWith(">")) return null + return rustType.substring(prefix.length, rustType.length - 1).trim() + } + + private fun optionInnerRustType(rustType: String?): String? = + rustType?.trim()?.let { extractGenericInner(it, "Option") } + + private fun normalizeRustType(rustType: String?): String? { + var result = rustType?.trim() ?: return null + var changed = true + while (changed) { + changed = false + if (result.startsWith("&")) { + result = result.removePrefix("&").trim() + changed = true + } + if (result.startsWith("mut ")) { + result = result.removePrefix("mut ").trim() + changed = true + } + if (result.startsWith("'")) { + val afterLifetime = result.substringAfter(' ', result).trim() + if (afterLifetime != result) { + result = afterLifetime + changed = true + } + } + } + return result.ifBlank { null } + } + + private fun isBorrowedRustType(rustType: String?): Boolean = rustType?.trim()?.startsWith("&") == true + + private fun isPathLikeRustType(rustType: String?): Boolean { + val normalized = normalizeRustType(rustType) ?: return false + return normalized.contains("Path") || normalized.contains("OsStr") || normalized.contains("OsString") + } + + private fun isOsStrLikeRustType(rustType: String?): Boolean { + val normalized = normalizeRustType(rustType) ?: return false + return normalized.contains("OsStr") || normalized.contains("OsString") + } + + private fun requiresStaticStr(rustType: String?): Boolean = + rustType?.contains("'static str") == true + + /** Extract the element type from a Rust slice type, e.g. "&[Complex]" → "Complex", "&mut [Complex]" → "Complex" */ + private fun extractSliceElementRustType(rustType: String?): String? { + val rt = rustType?.trim() ?: return null + val bracketStart = rt.indexOf('[') + val bracketEnd = rt.lastIndexOf(']') + if (bracketStart < 0 || bracketEnd <= bracketStart) return null + return rt.substring(bracketStart + 1, bracketEnd).trim().ifBlank { null } + } + + /** Wrap a call expression with writeback code for &mut [Object] slice parameters. + * After the call, modified values in the local Vec are written back to the original Box handles. */ + private fun wrapExprWithMutObjectSliceWriteback(expr: String, params: List, indent: String = " "): String { + val mutObjectSliceParams = params.filter { p -> + val elemType = (p.type as? KneType.LIST)?.elementType + (elemType is KneType.OBJECT || elemType is KneType.INTERFACE || elemType is KneType.SEALED_ENUM) && + p.rustType?.contains("&mut ") == true + } + if (mutObjectSliceParams.isEmpty()) return expr + + val sb = StringBuilder() + sb.appendLine("{") + sb.appendLine("${indent} let _kne_call_result = $expr;") + for (p in mutObjectSliceParams) { + val elemType = (p.type as KneType.LIST).elementType + val rustElemType = rustHandleTypeName(elemType, extractSliceElementRustType(p.rustType)) + sb.appendLine("${indent} for (_kne_i, &_kne_h) in ${p.name}_handles.iter().enumerate() {") + sb.appendLine("${indent} unsafe { std::ptr::write(_kne_h as *mut $rustElemType, ${p.name}_vec[_kne_i]); }") + sb.appendLine("${indent} }") + } + sb.append("${indent} _kne_call_result\n${indent}}") + return sb.toString() + } + + private fun sealedGetterReturnsBorrowed(type: KneType): Boolean = when (type) { + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM, is KneType.ENUM -> true + is KneType.NULLABLE -> sealedGetterReturnsBorrowed(type.inner) + else -> false + } + + private fun sealedGetterBindingExpr(field: KneParam, valueExpr: String): String = when (field.type) { + KneType.STRING, KneType.BYTE_ARRAY, + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM, + is KneType.ENUM, + is KneType.LIST, is KneType.SET, is KneType.MAP -> valueExpr + // NULLABLE: the ref binding gives &Option, we need to clone to get an owned Option + is KneType.NULLABLE -> "$valueExpr.clone()" + else -> "*$valueExpr" + } + + /** Mutable pointer type for a LIST element out-buffer in tuple returns. */ + private fun listOutPointerType(elemType: KneType): String = when (elemType) { + KneType.INT, KneType.BOOLEAN -> "*mut i32" + KneType.LONG -> "*mut i64" + KneType.DOUBLE -> "*mut f64" + KneType.FLOAT -> "*mut f32" + KneType.SHORT -> "*mut i16" + KneType.BYTE -> "*mut i8" + KneType.STRING -> "*mut u8" + is KneType.ENUM -> "*mut i32" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "*mut i64" + is KneType.LIST, is KneType.SET, is KneType.MAP, is KneType.TUPLE, is KneType.DATA_CLASS, is KneType.NULLABLE, KneType.BYTE_ARRAY -> "*mut i64" + else -> "*mut i32" + } + + /** C ABI pointer type for a slice parameter. */ + private fun slicePointerType(type: KneType): String = when (type) { + KneType.BYTE_ARRAY -> "*const u8" + is KneType.LIST -> when (type.elementType) { + KneType.INT -> "*const i32" + KneType.LONG -> "*const i64" + KneType.DOUBLE -> "*const f64" + KneType.FLOAT -> "*const f32" + KneType.STRING -> "*const *const c_char" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM, is KneType.ENUM -> "*const i64" + else -> "*const i64" + } + is KneType.SET -> when ((type as KneType.SET).elementType) { + KneType.INT -> "*const i32" + KneType.LONG -> "*const i64" + KneType.DOUBLE -> "*const f64" + KneType.FLOAT -> "*const f32" + KneType.STRING -> "*const *const c_char" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM, is KneType.ENUM -> "*const i64" + else -> "*const i64" + } + else -> "*const u8" + } + + private fun mapSlicePointerType(elemType: KneType): String = when (elemType) { + KneType.STRING, is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "*const *const c_char" + KneType.INT, KneType.BOOLEAN -> "*const i32" + KneType.LONG -> "*const i64" + KneType.DOUBLE -> "*const f64" + KneType.FLOAT -> "*const f32" + KneType.SHORT -> "*const i16" + KneType.BYTE -> "*const i8" + is KneType.ENUM -> "*const i32" + else -> "*const i64" + } + + /** Extract DATA_CLASS from a nullable param type, if applicable. */ + private fun nullableDataClass(type: KneType): KneType.DATA_CLASS? = + (type as? KneType.NULLABLE)?.inner as? KneType.DATA_CLASS + + /** Append C ABI params for a nullable DATA_CLASS: a sentinel flag + expanded fields. */ + private fun StringBuilder.appendNullableDataClassSignatureParams(paramName: String, dc: KneType.DATA_CLASS, separator: String = ", ") { + append("${separator}${paramName}_has: i32") + for (field in dc.fields) { + append(", ${paramName}_${field.name}: ${rustCType(field.type)}") + } + } + + /** Same as [appendNullableDataClassSignatureParams] but returns list entries. */ + private fun nullableDataClassSignatureParamList(paramName: String, dc: KneType.DATA_CLASS): List { + val params = mutableListOf("${paramName}_has: i32") + for (field in dc.fields) { + params.add("${paramName}_${field.name}: ${rustCType(field.type)}") + } + return params + } + + /** Generate Rust expression to read one element at index i from a map slice. */ + private fun mapSliceElemReadExpr(sliceName: String, indexExpr: String, elemType: KneType): String = when (elemType) { + KneType.BOOLEAN -> "$sliceName[$indexExpr] != 0" + KneType.INT -> "$sliceName[$indexExpr]" + KneType.LONG -> "$sliceName[$indexExpr]" + KneType.DOUBLE -> "$sliceName[$indexExpr]" + KneType.FLOAT -> "$sliceName[$indexExpr]" + KneType.SHORT -> "$sliceName[$indexExpr]" + KneType.BYTE -> "$sliceName[$indexExpr]" + KneType.STRING -> "cstr_to_string($sliceName[$indexExpr])" + is KneType.ENUM -> { + val enumName = elemType.simpleName + "unsafe { std::mem::transmute($sliceName[$indexExpr] as u8) }" + } + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> { + "unsafe { *Box::from_raw($sliceName[$indexExpr] as *mut _) }" + } + else -> "$sliceName[$indexExpr]" + } + + /** Render a Rust owned type for HashMap key/value (not C ABI pointer types). */ + private fun mapValueRustType(type: KneType): String = when (type) { + KneType.BOOLEAN -> "bool" + KneType.INT -> "i32" + KneType.LONG -> "i64" + KneType.DOUBLE -> "f64" + KneType.FLOAT -> "f32" + KneType.SHORT -> "i16" + KneType.BYTE -> "i8" + KneType.STRING -> "String" + is KneType.ENUM -> type.simpleName + is KneType.OBJECT -> type.simpleName + is KneType.INTERFACE -> type.simpleName + is KneType.SEALED_ENUM -> type.simpleName + else -> "i64" + } + + /** C ABI type for a parameter. */ + private fun rustCType(type: KneType): String = when (type) { + KneType.INT -> "i32" + KneType.LONG -> "i64" + KneType.DOUBLE -> "f64" + KneType.FLOAT -> "f32" + KneType.BOOLEAN -> "i32" // 0/1 + KneType.BYTE -> "i8" + KneType.SHORT -> "i16" + KneType.STRING -> "*const c_char" + KneType.UNIT -> "()" + KneType.NEVER -> "!" + is KneType.OBJECT -> "i64" // opaque handle + is KneType.INTERFACE -> "i64" + is KneType.SEALED_ENUM -> "i64" // opaque handle + is KneType.ENUM -> "i32" // ordinal + is KneType.NULLABLE -> when ((type).inner) { + KneType.STRING -> "*const c_char" // null pointer = None + KneType.BOOLEAN -> "i32" // -1 = null + KneType.BYTE, KneType.SHORT -> "i32" + is KneType.ENUM -> "i32" + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "i64" + else -> "i64" // widened or sentinel + } + is KneType.LIST -> "i64" // pointer handle + is KneType.SET -> "i64" + is KneType.MAP -> "i64" + KneType.BYTE_ARRAY -> "i64" + is KneType.DATA_CLASS -> "i64" + is KneType.TUPLE -> "i64" + is KneType.FUNCTION -> "i64" + is KneType.FLOW -> "()" + } + + /** C ABI return type. Buffer-pattern types return i32 (byte/element count). */ + private fun rustCReturnType(type: KneType): String = when (type) { + KneType.STRING -> "i32" + KneType.BYTE_ARRAY -> "i32" + KneType.UNIT -> "()" + KneType.NEVER -> "!" + is KneType.LIST -> "i32" + is KneType.MAP -> "i32" // element count + is KneType.DATA_CLASS -> "()" // Data class returns use per-field out-params + is KneType.TUPLE -> "()" // Tuple returns use per-field out-params + is KneType.NULLABLE -> when ((type).inner) { + KneType.INT, KneType.LONG, KneType.DOUBLE, KneType.FLOAT -> "i64" + KneType.BOOLEAN, KneType.BYTE, KneType.SHORT, KneType.STRING, KneType.BYTE_ARRAY -> "i32" + is KneType.ENUM -> "i32" + is KneType.DATA_CLASS -> "i32" // 0=None, 1=Some (fields in out-params) + is KneType.LIST, is KneType.SET, is KneType.MAP -> "i32" // count or -1 for None + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "i64" + else -> "i64" + } + else -> rustCType(type) + } + + /** + * Recursively generates Rust code to write a tuple into a heap-allocated buffer + * using uniform 8-byte slots. Returns the variable name holding the buffer pointer. + * + * Layout: element i is at byte offset i * 8. Buffer size = elements.size * 8. + * [counter] is a shared mutable int to guarantee unique variable names across sibling tuples. + */ + private fun StringBuilder.appendNestedTupleWrite( + indent: String, + binding: String, + tupleType: KneType.TUPLE, + counter: IntArray, + ): String { + val id = counter[0]++ + val bufSize = tupleType.elementTypes.size * 8 + val bufVar = "_tbuf_$id" + appendLine("${indent}let $bufVar = Box::into_raw(vec![0u8; $bufSize].into_boxed_slice()) as *mut u8;") + + for ((idx, elemType) in tupleType.elementTypes.withIndex()) { + val offset = idx * 8 + val elem = "$binding.$idx" + when (elemType) { + KneType.STRING -> { + appendLine("${indent}unsafe {") + appendLine("${indent} let _sb_${id}_$idx = $elem.as_bytes();") + appendLine("${indent} let _sl = _sb_${id}_$idx.len();") + appendLine("${indent} if _sl > 0 {") + appendLine("${indent} let _sc = Box::into_raw(vec![0u8; _sl + 1].into_boxed_slice()) as *mut u8;") + appendLine("${indent} std::ptr::copy_nonoverlapping(_sb_${id}_$idx.as_ptr(), _sc, _sl);") + appendLine("${indent} *($bufVar.add($offset) as *mut usize) = _sc as usize;") + appendLine("${indent} } else {") + appendLine("${indent} *($bufVar.add($offset) as *mut usize) = 0usize;") + appendLine("${indent} }") + appendLine("${indent}}") + } + KneType.BOOLEAN -> { + appendLine("${indent}unsafe { $bufVar.add($offset).write(if $elem { 1u8 } else { 0u8 }); }") + } + KneType.BYTE -> { + appendLine("${indent}unsafe { $bufVar.add($offset).write($elem as u8); }") + } + KneType.SHORT -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut i16) = $elem; }") + } + KneType.INT -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut i32) = $elem; }") + } + KneType.LONG -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut i64) = $elem; }") + } + KneType.FLOAT -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut f32) = $elem; }") + } + KneType.DOUBLE -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut f64) = $elem; }") + } + is KneType.ENUM -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut i32) = $elem as i32; }") + } + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut i64) = Box::into_raw(Box::new($elem)) as i64; }") + } + is KneType.TUPLE -> { + val innerBufVar = appendNestedTupleWrite(indent, elem, elemType, counter) + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut usize) = $innerBufVar as usize; }") + } + is KneType.LIST, is KneType.SET, is KneType.MAP, is KneType.DATA_CLASS, is KneType.NULLABLE, KneType.BYTE_ARRAY -> { + appendLine("${indent}unsafe { *($bufVar.add($offset) as *mut i64) = Box::into_raw(Box::new($elem)) as i64; }") + } + else -> { + appendLine("${indent}// TODO: handle ${elemType::class.simpleName} in nested tuple") + } + } + } + + return bufVar + } + + /** Conversion suffix for return expressions (e.g., " as i32" for Boolean). */ + private fun rustReturnConversion(type: KneType): String = when (type) { + KneType.BOOLEAN -> " as i32" + is KneType.ENUM -> " as i32" + else -> "" + } + + /** Default value returned on panic/error. */ + private fun defaultReturnValue(type: KneType): String = when (type) { + KneType.INT, KneType.BYTE, KneType.SHORT -> "0" + KneType.LONG -> "0i64" + KneType.DOUBLE -> "0.0f64" + KneType.FLOAT -> "0.0f32" + KneType.BOOLEAN -> "0" + KneType.STRING -> "0" // byte count = 0 + KneType.UNIT -> "()" + KneType.NEVER -> "()" + is KneType.MAP -> "0" // element count = 0 + is KneType.DATA_CLASS -> "()" // out-params pattern, void return + is KneType.TUPLE -> "()" // out-params pattern, void return + is KneType.OBJECT, is KneType.INTERFACE, is KneType.SEALED_ENUM -> "0i64" + is KneType.ENUM -> "0" + is KneType.NULLABLE -> when ((type).inner) { + KneType.BOOLEAN, KneType.BYTE, KneType.SHORT, KneType.STRING, KneType.BYTE_ARRAY -> "0i32" + is KneType.ENUM -> "0i32" + is KneType.DATA_CLASS -> "0i32" // 0 = None + is KneType.LIST, is KneType.SET, is KneType.MAP -> "0" // count = 0 on error + else -> "0i64" + } + else -> "0" + } +} diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/ir/KneIR.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/ir/KneIR.kt index 755fbc75..3fdc0a15 100644 --- a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/ir/KneIR.kt +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/ir/KneIR.kt @@ -10,6 +10,14 @@ data class KneModule( val enums: List, val functions: List, val interfaces: List = emptyList(), + val sealedEnums: List = emptyList(), + /** Map from trait FQ name → list of concrete types that implement the trait. + * Built by scanning all `impl Trait for ConcreteType` blocks in rustdoc JSON. + * Enables auto-monomorphisation of generic functions by finding concrete implementors. */ + val traitImpls: Map> = emptyMap(), + /** Type simple names that appear in multiple crates (detected during module merge). + * Methods using these types without qualified paths should be skipped. */ + val ambiguousTypeNames: Set = emptySet(), ) : Serializable data class KneDataClass( @@ -43,6 +51,19 @@ data class KneClass( val interfaces: List = emptyList(), val sealedSubclasses: List = emptyList(), val isCommon: Boolean = false, + val isOpaque: Boolean = false, + val rustTypeName: String = simpleName, + /** True for synthetic classes wrapping `Box` trait objects. */ + val isDynTrait: Boolean = false, + /** Generic type parameters for this struct (e.g., `T` in `RequestedFormat`). + * When non-empty, the class is a generic template requiring monomorphisation. */ + val genericParams: List = emptyList(), + /** True if the Rust struct has lifetime parameters (e.g. `BufReader<'a>`). + * Such types cannot be safely used as type args in other generic classes. */ + val hasLifetimeParams: Boolean = false, + /** True if the Rust struct has generic type parameters that were NOT monomorphised. + * (e.g. `ReadOnlySource` where no concrete `R` was found.) */ + val hasUnresolvedGenericTypeParams: Boolean = false, ) : Serializable data class KneEnum( @@ -51,8 +72,63 @@ data class KneEnum( val entries: List, ) : Serializable +/** + * Rust enum with data variants → Kotlin sealed class. + * Each variant may carry fields (tuple or struct variant) or be a unit variant (no fields). + */ +data class KneSealedEnum( + val simpleName: String, + val fqName: String, + val variants: List, +) : Serializable + +data class KneSealedVariant( + val name: String, + val fields: List, // empty for unit variants + val isTuple: Boolean = false, // true for tuple variants like Value(i32) +) : Serializable + +enum class KneConstructorKind : Serializable { + FUNCTION, + STRUCT_LITERAL, + NONE, +} + data class KneConstructor( val params: List, + val kind: KneConstructorKind = KneConstructorKind.FUNCTION, + val canFail: Boolean = false, +) : Serializable + +enum class KneReceiverKind : Serializable { + NONE, + BORROWED_SHARED, + BORROWED_MUT, + OWNED, +} + +/** + * Represents a single trait bound on a generic parameter. + * For `T: FormatDecoder`, this would be `GenericBound(traitFqName = "nokhwa_types.FormatDecoder")`. + * For `T: Trait1 + Trait2`, this would be two GenericBound entries. + */ +data class GenericBound( + /** Fully-qualified trait name (e.g., "nokhwa_types.FormatDecoder") */ + val traitFqName: String, + /** Simple trait name for naming (e.g., "FormatDecoder") */ + val traitSimpleName: String, +) : Serializable + +/** + * Information about a generic type parameter used in monomorphisation. + * Captures the parameter name and all trait bounds that must be satisfied. + */ +data class GenericParamInfo( + val paramName: String, + val bounds: List, + /** Concrete types that implement ALL bounds — used for monomorphisation. + * Computed from traitImpls registry. */ + val concreteTypes: List = emptyList(), ) : Serializable data class KneFunction( @@ -63,6 +139,25 @@ data class KneFunction( val isExtension: Boolean = false, val receiverType: KneType? = null, val isOverride: Boolean = false, + /** True if the method takes `&mut self` (Rust). Used by Rust bridge generator. */ + val isMutating: Boolean = false, + val receiverKind: KneReceiverKind = KneReceiverKind.NONE, + val canFail: Boolean = false, + val returnsBorrowed: Boolean = false, + val returnRustType: String? = null, + val isUnsafe: Boolean = false, + /** True if the Rust method is `async fn`. The bridge must wrap the call with `pollster::block_on()`. */ + val isAsync: Boolean = false, + /** Rust expression suffix for `impl Trait` return conversion (e.g. `.collect::>()`). */ + val returnConversion: String? = null, + /** Generic type parameters that require monomorphisation. + * When non-empty, the method needs concrete type substitutions. */ + val genericParams: List = emptyList(), + /** Original Rust method name before monomorphisation suffix was added. + * When set, the bridge must call this name (with turbofish) instead of [name]. */ + val rustMethodName: String? = null, + /** Turbofish type args to append to the Rust method call (e.g. "::"). */ + val turbofish: String? = null, ) : Serializable data class KneProperty( @@ -76,6 +171,10 @@ data class KneParam( val name: String, val type: KneType, val hasDefault: Boolean = false, + /** True if this param is borrowed (`&str`, `&T`) in Rust. Affects bridge codegen. */ + val isBorrowed: Boolean = false, + /** Best-effort original Rust type hint used by bridge generation for casts and ownership. */ + val rustType: String? = null, ) : Serializable sealed class KneType : Serializable { @@ -88,9 +187,11 @@ sealed class KneType : Serializable { object SHORT : KneType() object STRING : KneType() object UNIT : KneType() + object NEVER : KneType() data class OBJECT(val fqName: String, val simpleName: String) : KneType() data class INTERFACE(val fqName: String, val simpleName: String) : KneType() data class ENUM(val fqName: String, val simpleName: String) : KneType() + data class SEALED_ENUM(val fqName: String, val simpleName: String) : KneType() data class NULLABLE(val inner: KneType) : KneType() data class FUNCTION(val paramTypes: List, val returnType: KneType) : KneType() data class DATA_CLASS(val fqName: String, val simpleName: String, val fields: List) : KneType() @@ -99,6 +200,32 @@ sealed class KneType : Serializable { data class SET(val elementType: KneType) : KneType() data class MAP(val keyType: KneType, val valueType: KneType) : KneType() data class FLOW(val elementType: KneType) : KneType() + data class TUPLE(val elementTypes: List) : KneType() { + val typeId: String get() { + fun go(sb: StringBuilder, t: KneType) { + when (t) { + is TUPLE -> { sb.append("T"); t.elementTypes.forEach { go(sb, it) } } + UNIT -> sb.append("U"); BOOLEAN -> sb.append("Z"); BYTE -> sb.append("B") + SHORT -> sb.append("S"); INT -> sb.append("I"); LONG -> sb.append("J") + FLOAT -> sb.append("F"); DOUBLE -> sb.append("D"); STRING -> sb.append("R") + BYTE_ARRAY -> sb.append("Y") + is ENUM -> sb.append("E${t.simpleName}") + is OBJECT -> sb.append("O${t.simpleName}") + is DATA_CLASS -> sb.append("D${t.simpleName}") + is LIST -> { sb.append("L"); go(sb, t.elementType) } + is SET -> { sb.append("S"); go(sb, t.elementType) } + is MAP -> { sb.append("M"); go(sb, t.keyType); go(sb, t.valueType) } + is FLOW -> { sb.append("W"); go(sb, t.elementType) } + is NULLABLE -> { sb.append("N"); go(sb, t.inner) } + is FUNCTION -> { sb.append("F"); t.paramTypes.forEach { go(sb, it) }; sb.append("X"); go(sb, t.returnType) } + is INTERFACE -> sb.append("I${t.simpleName}") + is SEALED_ENUM -> sb.append("K${t.simpleName}") + NEVER -> sb.append("V") + } + } + return buildString { go(this, this@TUPLE) } + } + } /** The FFM ValueLayout constant name for this type. */ val ffmLayout: String @@ -112,15 +239,17 @@ sealed class KneType : Serializable { SHORT -> "JAVA_SHORT" STRING -> "ADDRESS" // char* (input) or output buffer pattern (return) UNIT -> "" // void — used with FunctionDescriptor.ofVoid(...) + NEVER -> "" // void — diverging type never returns is OBJECT -> "JAVA_LONG" // opaque handle is INTERFACE -> "JAVA_LONG" // opaque handle (same as OBJECT) + is SEALED_ENUM -> "JAVA_LONG" // opaque handle is ENUM -> "JAVA_INT" // ordinal is NULLABLE -> when (inner) { STRING -> "ADDRESS" BOOLEAN, is ENUM -> "JAVA_INT" SHORT, BYTE -> "JAVA_INT" // widened for sentinel INT, LONG, FLOAT, DOUBLE -> "JAVA_LONG" // widened or raw bits - is OBJECT, is INTERFACE -> "JAVA_LONG" + is OBJECT, is INTERFACE, is SEALED_ENUM -> "JAVA_LONG" else -> inner.ffmLayout } is FUNCTION -> "JAVA_LONG" // function pointer address @@ -130,6 +259,7 @@ sealed class KneType : Serializable { is SET -> "ADDRESS" // same encoding as LIST is MAP -> "ADDRESS" // keys pointer (+ values pointer + size handled in expansion) is FLOW -> "" // void — result delivered via callbacks (like suspend) + is TUPLE -> "ADDRESS" // pointer to tuple data on heap } /** Kotlin/JVM type name as it appears in generated JVM code. */ @@ -144,8 +274,10 @@ sealed class KneType : Serializable { SHORT -> "Short" STRING -> "String" UNIT -> "Unit" + NEVER -> "Nothing" is OBJECT -> simpleName is INTERFACE -> simpleName + is SEALED_ENUM -> simpleName is ENUM -> simpleName is NULLABLE -> if (inner is FUNCTION) "(${inner.jvmTypeName})?" else "${inner.jvmTypeName}?" is FUNCTION -> "(${paramTypes.joinToString(", ") { it.jvmTypeName }}) -> ${returnType.jvmTypeName}" @@ -155,6 +287,7 @@ sealed class KneType : Serializable { is SET -> "Set<${elementType.jvmTypeName}>" is MAP -> "Map<${keyType.jvmTypeName}, ${valueType.jvmTypeName}>" is FLOW -> "kotlinx.coroutines.flow.Flow<${elementType.jvmTypeName}>" + is TUPLE -> "KneTuple${elementTypes.size}_${typeId}" } /** Kotlin/Native type used in the @CName bridge function signature. */ @@ -169,8 +302,10 @@ sealed class KneType : Serializable { SHORT -> "Short" STRING -> "CPointer?" // null-terminated char* UNIT -> "Unit" + NEVER -> "Nothing" // diverging type - never returns is OBJECT -> "Long" // opaque handle is INTERFACE -> "Long" // opaque handle (same as OBJECT) + is SEALED_ENUM -> "Long" // opaque handle is ENUM -> "Int" // ordinal is NULLABLE -> when (inner) { STRING -> "CPointer?" @@ -178,7 +313,7 @@ sealed class KneType : Serializable { SHORT, BYTE -> "Int" // widened, Int.MIN_VALUE = null INT, LONG -> "Long" // widened, Long.MIN_VALUE = null FLOAT, DOUBLE -> "Long" // raw bits, Long.MIN_VALUE = null - is OBJECT, is INTERFACE -> "Long" // 0L = null + is OBJECT, is INTERFACE, is SEALED_ENUM -> "Long" // 0L = null else -> inner.nativeBridgeType } is FUNCTION -> "Long" // function pointer address @@ -188,6 +323,7 @@ sealed class KneType : Serializable { is SET -> collectionPointerType(elementType) is MAP -> collectionPointerType(keyType) // keys pointer type; values handled in expansion is FLOW -> "Unit" // void — callbacks deliver values + is TUPLE -> "Long" // pointer to tuple on heap } /** The native pointer type for out-param usage (e.g. IntVar for Int). */ @@ -201,7 +337,7 @@ sealed class KneType : Serializable { BYTE -> "ByteVar" SHORT -> "ShortVar" is ENUM -> "IntVar" // ordinal - is OBJECT, is INTERFACE -> "LongVar" // StableRef handle + is OBJECT, is INTERFACE, is SEALED_ENUM -> "LongVar" // StableRef handle else -> "ByteVar" } @@ -217,7 +353,7 @@ sealed class KneType : Serializable { STRING -> "CPointer?" // packed null-terminated BYTE_ARRAY -> "CPointer?" // StableRef handles is ENUM -> "CPointer?" // ordinals - is OBJECT, is INTERFACE -> "CPointer?" // handles + is OBJECT, is INTERFACE, is SEALED_ENUM -> "CPointer?" // handles is LIST, is SET, is MAP -> "CPointer?" // nested collection handles else -> "CPointer?" } @@ -231,7 +367,7 @@ sealed class KneType : Serializable { SHORT -> "ShortVar" BYTE, STRING -> "ByteVar" is ENUM -> "IntVar" - is OBJECT, is INTERFACE -> "LongVar" + is OBJECT, is INTERFACE, is SEALED_ENUM -> "LongVar" else -> "ByteVar" } @@ -245,7 +381,7 @@ sealed class KneType : Serializable { BYTE -> "JAVA_BYTE" STRING -> "JAVA_BYTE" // packed buffer uses byte layout is ENUM -> "JAVA_INT" - is OBJECT, is INTERFACE -> "JAVA_LONG" + is OBJECT, is INTERFACE, is SEALED_ENUM -> "JAVA_LONG" BYTE_ARRAY -> "JAVA_LONG" // StableRef handles is LIST, is SET, is MAP -> "JAVA_LONG" // nested collection handles else -> "JAVA_BYTE" diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/tasks/CargoBuildTask.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/tasks/CargoBuildTask.kt new file mode 100644 index 00000000..a3fcdb84 --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/tasks/CargoBuildTask.kt @@ -0,0 +1,75 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault +import io.github.kdroidfilter.nucleusnativeaccess.plugin.findCargo + +@DisableCachingByDefault(because = "Cargo build has its own caching") +abstract class CargoBuildTask : DefaultTask() { + + @get:Input abstract val buildType: Property + @get:InputDirectory @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val cargoProjectDir: DirectoryProperty + @get:OutputDirectory abstract val nativeLibOutputDir: DirectoryProperty + + @TaskAction + fun build() { + val projectDir = cargoProjectDir.get().asFile + val buildType = buildType.get().lowercase() + val cargo = findCargo() + + val args = mutableListOf(cargo, "build") + if (buildType == "release") { + args.add("--release") + } + + logger.lifecycle("kne-rust: Running ${args.joinToString(" ")} in ${projectDir.absolutePath}") + + val process = ProcessBuilder(args) + .directory(projectDir) + .redirectErrorStream(true) + .start() + + val output = process.inputStream.bufferedReader().readText() + val exitCode = process.waitFor() + + if (exitCode != 0) { + throw org.gradle.api.GradleException("cargo build failed (exit $exitCode):\n$output") + } + + // Copy the built shared library to the output dir + val targetSubdir = if (buildType == "release") "release" else "debug" + val targetDir = projectDir.resolve("target/$targetSubdir") + val platform = detectPlatform() + val destDir = nativeLibOutputDir.get().asFile.resolve("kne/native/$platform") + destDir.mkdirs() + + val libFiles = targetDir.listFiles()?.filter { f -> + f.extension in listOf("so", "dylib", "dll") + } ?: emptyList() + + for (libFile in libFiles) { + libFile.copyTo(destDir.resolve(libFile.name), overwrite = true) + logger.lifecycle("kne-rust: Bundled ${libFile.name} → kne/native/$platform/") + } + } + + private fun detectPlatform(): String { + val os = System.getProperty("os.name").lowercase() + val arch = System.getProperty("os.arch").lowercase() + return when { + os.contains("linux") -> if (arch.contains("aarch") || arch.contains("arm")) "linux-aarch64" else "linux-x64" + os.contains("mac") || os.contains("darwin") -> if (arch.contains("aarch") || arch.contains("arm")) "darwin-aarch64" else "darwin-x64" + os.contains("win") -> if (arch.contains("aarch") || arch.contains("arm")) "win32-arm64" else "win32-x64" + else -> "unknown" + } + } +} diff --git a/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/tasks/GenerateRustBindingsTask.kt b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/tasks/GenerateRustBindingsTask.kt new file mode 100644 index 00000000..6f78a566 --- /dev/null +++ b/plugin-build/plugin/src/main/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/tasks/GenerateRustBindingsTask.kt @@ -0,0 +1,40 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.tasks + +import io.github.kdroidfilter.nucleusnativeaccess.plugin.CrateDependency +import io.github.kdroidfilter.nucleusnativeaccess.plugin.analysis.RustWorkAction +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault + +@DisableCachingByDefault(because = "Rust binding generation depends on cargo rustdoc output") +abstract class GenerateRustBindingsTask : DefaultTask() { + + @get:Input abstract val libName: Property + @get:Input abstract val jvmPackage: Property + @get:Input abstract val crates: ListProperty + @get:OutputDirectory abstract val rustProjectDir: DirectoryProperty + @get:OutputDirectory abstract val rustBridgesDir: DirectoryProperty + @get:OutputDirectory abstract val jvmProxiesDir: DirectoryProperty + @get:OutputDirectory abstract val jvmResourcesDir: DirectoryProperty + + @TaskAction + fun generate() { + jvmProxiesDir.get().asFile.apply { deleteRecursively(); mkdirs() } + + RustWorkAction.execute( + crates = crates.get(), + libName = libName.get(), + jvmPackage = jvmPackage.get(), + rustProjectDir = rustProjectDir.get().asFile, + rustBridgesDir = rustBridgesDir.get().asFile, + jvmProxiesDir = jvmProxiesDir.get().asFile, + jvmResourcesDir = jvmResourcesDir.get().asFile, + logger = logger, + ) + } +} diff --git a/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/CrossCrateReexportTest.kt b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/CrossCrateReexportTest.kt new file mode 100644 index 00000000..52609936 --- /dev/null +++ b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/CrossCrateReexportTest.kt @@ -0,0 +1,171 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.analysis + +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.* +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +/** + * Tests lazy cross-crate type resolution: when a method references a type from + * a sub-crate (e.g., `nokhwa_core::Resolution`), the parser should discover it + * from the rustdoc JSON index and resolve it as a proper data class, enum, or + * sealed enum — not as an opaque class. + */ +class CrossCrateReexportTest { + + private lateinit var module: KneModule + private val unsupportedMessages = mutableListOf() + + @Before + fun setUp() { + val json = javaClass.classLoader + .getResourceAsStream("rustdoc-fixtures/cross-crate-reexport.json")!! + .bufferedReader() + .readText() + module = RustdocJsonParser().parse(json, "mylib") { unsupportedMessages.add(it) } + } + + @Test + fun `Camera class is parsed`() { + val camera = module.classes.find { it.simpleName == "Camera" } + assertNotNull("Camera class should exist", camera) + } + + @Test + fun `Camera has get_resolution property`() { + val camera = module.classes.first { it.simpleName == "Camera" } + // get_resolution is a no-arg getter, promoted to a property + val prop = camera.properties.find { it.name == "resolution" } + assertNotNull("get_resolution property should exist, properties: ${camera.properties.map { it.name }}", prop) + } + + @Test + fun `Resolution from sub-crate is resolved as data class`() { + val camera = module.classes.first { it.simpleName == "Camera" } + val prop = camera.properties.find { it.name == "resolution" }!! + val returnType = prop.type + assertTrue( + "Resolution should be DATA_CLASS, got $returnType", + returnType is KneType.DATA_CLASS + ) + val dc = returnType as KneType.DATA_CLASS + assertEquals("Resolution", dc.simpleName) + assertEquals(2, dc.fields.size) + assertEquals("width", dc.fields[0].name) + assertEquals(KneType.INT, dc.fields[0].type) + assertEquals("height", dc.fields[1].name) + assertEquals(KneType.INT, dc.fields[1].type) + } + + @Test + fun `Resolution appears in module dataClasses`() { + val dc = module.dataClasses.find { it.simpleName == "Resolution" } + assertNotNull("Resolution data class should be in module", dc) + assertEquals(2, dc!!.fields.size) + } + + @Test + fun `FrameFormat from sub-crate is resolved as simple enum`() { + val camera = module.classes.first { it.simpleName == "Camera" } + // get_format() returns ENUM and is extracted as a property by extractProperties + val prop = camera.properties.find { it.name == "format" } + assertNotNull("Camera should have 'format' property, has: ${camera.properties.map { it.name }}", prop) + val type = prop!!.type + assertTrue("format type should be ENUM, got $type", type is KneType.ENUM) + assertEquals("FrameFormat", (type as KneType.ENUM).simpleName) + } + + @Test + fun `FrameFormat appears in module enums`() { + val enum = module.enums.find { it.simpleName == "FrameFormat" } + assertNotNull("FrameFormat enum should be in module", enum) + assertEquals(3, enum!!.entries.size) + assertTrue(enum.entries.contains("Rgb")) + assertTrue(enum.entries.contains("Yuv")) + assertTrue(enum.entries.contains("Gray")) + } + + @Test + fun `CaptureError from sub-crate is resolved as sealed enum`() { + val camera = module.classes.first { it.simpleName == "Camera" } + val method = camera.methods.find { it.name == "capture" }!! + // Result — canFail=true, return type should be INT + assertTrue("capture should be canFail", method.canFail) + } + + @Test + fun `cross-crate types are not opaque`() { + val opaqueNames = module.classes.filter { it.isOpaque }.map { it.simpleName } + assertFalse("Resolution should not be opaque", opaqueNames.contains("Resolution")) + assertFalse("FrameFormat should not be opaque", opaqueNames.contains("FrameFormat")) + assertFalse("CameraFormat should not be opaque", opaqueNames.contains("CameraFormat")) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Nested cross-crate data class: CameraFormat { resolution, format, frame_rate } + // ═══════════════════════════════════════════════════════════════════════════ + + @Test + fun `CameraFormat from sub-crate is resolved as data class`() { + val camera = module.classes.first { it.simpleName == "Camera" } + val prop = camera.properties.find { it.name == "camera_format" } + assertNotNull("get_camera_format property should exist", prop) + val returnType = prop!!.type + assertTrue( + "CameraFormat should be DATA_CLASS, got $returnType", + returnType is KneType.DATA_CLASS + ) + } + + @Test + fun `CameraFormat has resolution field as nested data class`() { + val camera = module.classes.first { it.simpleName == "Camera" } + val prop = camera.properties.first { it.name == "camera_format" } + val dc = prop.type as KneType.DATA_CLASS + assertEquals("CameraFormat", dc.simpleName) + assertEquals(3, dc.fields.size) + + val resolutionField = dc.fields.find { it.name == "resolution" } + assertNotNull("resolution field should exist", resolutionField) + assertTrue( + "resolution should be DATA_CLASS, got ${resolutionField!!.type}", + resolutionField.type is KneType.DATA_CLASS + ) + val nestedDc = resolutionField.type as KneType.DATA_CLASS + assertEquals("Resolution", nestedDc.simpleName) + assertEquals(2, nestedDc.fields.size) + } + + @Test + fun `CameraFormat has format field as enum`() { + val camera = module.classes.first { it.simpleName == "Camera" } + val prop = camera.properties.first { it.name == "camera_format" } + val dc = prop.type as KneType.DATA_CLASS + + val formatField = dc.fields.find { it.name == "format" } + assertNotNull("format field should exist", formatField) + assertTrue( + "format should be ENUM, got ${formatField!!.type}", + formatField.type is KneType.ENUM + ) + assertEquals("FrameFormat", (formatField.type as KneType.ENUM).simpleName) + } + + @Test + fun `CameraFormat has frame_rate field as primitive`() { + val camera = module.classes.first { it.simpleName == "Camera" } + val prop = camera.properties.first { it.name == "camera_format" } + val dc = prop.type as KneType.DATA_CLASS + + val frameRateField = dc.fields.find { it.name == "frame_rate" } + assertNotNull("frame_rate field should exist", frameRateField) + assertEquals(KneType.INT, frameRateField!!.type) + } + + @Test + fun `CameraFormat appears in module dataClasses`() { + val dc = module.dataClasses.find { it.simpleName == "CameraFormat" } + assertNotNull("CameraFormat data class should be in module", dc) + assertEquals(3, dc!!.fields.size) + } +} diff --git a/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustCalculatorParserTest.kt b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustCalculatorParserTest.kt new file mode 100644 index 00000000..eb1c00f5 --- /dev/null +++ b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustCalculatorParserTest.kt @@ -0,0 +1,624 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.analysis + +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.* +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +/** + * Tests parsing the Rust Calculator example — which mirrors the Kotlin/Native Calculator. + * Validates that the parser produces a KneModule equivalent to what PsiSourceParser + * would produce for the Kotlin version. + */ +class RustCalculatorParserTest { + + private lateinit var module: KneModule + + @Before + fun setUp() { + val json = javaClass.classLoader + .getResourceAsStream("rustdoc-fixtures/rust-calculator.json")!! + .bufferedReader() + .readText() + module = RustdocJsonParser().parse(json, "calculator") + } + + // --- Calculator class --- + + @Test + fun `parses Calculator struct`() { + val calc = module.classes.find { it.simpleName == "Calculator" } + assertNotNull("Calculator class should exist", calc) + } + + @Test + fun `Calculator constructor takes i32`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + assertEquals(1, calc.constructor.params.size) + assertEquals("initial", calc.constructor.params[0].name) + assertEquals(KneType.INT, calc.constructor.params[0].type) + } + + @Test + fun `Calculator has all arithmetic methods`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val methodNames = calc.methods.map { it.name } + assertTrue("add", "add" in methodNames) + assertTrue("subtract", "subtract" in methodNames) + assertTrue("multiply", "multiply" in methodNames) + assertTrue("divide", "divide" in methodNames) + assertTrue("reset", "reset" in methodNames) + } + + @Test + fun `Calculator add method signature`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val add = calc.methods.first { it.name == "add" } + assertEquals(1, add.params.size) + assertEquals("value", add.params[0].name) + assertEquals(KneType.INT, add.params[0].type) + assertEquals(KneType.INT, add.returnType) + } + + @Test + fun `Calculator has all primitive type methods`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val methods = calc.methods.associateBy { it.name } + + // add_long: (i64) -> i64 + assertEquals(KneType.LONG, methods["add_long"]?.params?.get(0)?.type) + assertEquals(KneType.LONG, methods["add_long"]?.returnType) + + // add_double: (f64) -> f64 + assertEquals(KneType.DOUBLE, methods["add_double"]?.params?.get(0)?.type) + assertEquals(KneType.DOUBLE, methods["add_double"]?.returnType) + + // add_float: (f32) -> f32 + assertEquals(KneType.FLOAT, methods["add_float"]?.params?.get(0)?.type) + assertEquals(KneType.FLOAT, methods["add_float"]?.returnType) + + // add_short: (i16) -> i16 + assertEquals(KneType.SHORT, methods["add_short"]?.params?.get(0)?.type) + assertEquals(KneType.SHORT, methods["add_short"]?.returnType) + + // add_byte: (i8) -> i8 + assertEquals(KneType.BYTE, methods["add_byte"]?.params?.get(0)?.type) + assertEquals(KneType.BYTE, methods["add_byte"]?.returnType) + + // is_positive: () -> bool + assertEquals(KneType.BOOLEAN, methods["is_positive"]?.returnType) + + // check_flag: (bool) -> bool + assertEquals(KneType.BOOLEAN, methods["check_flag"]?.params?.get(0)?.type) + assertEquals(KneType.BOOLEAN, methods["check_flag"]?.returnType) + } + + @Test + fun `Calculator has string methods`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val methods = calc.methods.associateBy { it.name } + + // describe: () -> String + assertEquals(KneType.STRING, methods["describe"]?.returnType) + + // echo: (&str) -> String + assertEquals(KneType.STRING, methods["echo"]?.params?.get(0)?.type) + assertEquals(KneType.STRING, methods["echo"]?.returnType) + + // concat: (&str, &str) -> String + assertEquals(2, methods["concat"]?.params?.size) + assertEquals(KneType.STRING, methods["concat"]?.returnType) + } + + @Test + fun `Calculator has properties extracted from get_ set_ accessors`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val props = calc.properties.associateBy { it.name } + + // label: get_label + set_label → mutable String property + assertNotNull(props["label"]) + assertEquals(KneType.STRING, props["label"]!!.type) + assertTrue("label should be mutable", props["label"]!!.mutable) + + // scale: get_scale + set_scale → mutable Double property + assertNotNull(props["scale"]) + assertEquals(KneType.DOUBLE, props["scale"]!!.type) + assertTrue("scale should be mutable", props["scale"]!!.mutable) + + // enabled: get_enabled + set_enabled → mutable Boolean property + assertNotNull(props["enabled"]) + assertEquals(KneType.BOOLEAN, props["enabled"]!!.type) + assertTrue("enabled should be mutable", props["enabled"]!!.mutable) + + // get_/set_ methods should be removed from methods list + val methodNames = calc.methods.map { it.name } + assertFalse("get_label should not be in methods", "get_label" in methodNames) + assertFalse("set_label should not be in methods", "set_label" in methodNames) + } + + @Test + fun `Calculator has nullable return methods`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val methods = calc.methods.associateBy { it.name } + + // divide_or_null: (i32) -> Option + val divOrNull = methods["divide_or_null"] + assertNotNull(divOrNull) + assertTrue(divOrNull!!.returnType is KneType.NULLABLE) + assertEquals(KneType.INT, (divOrNull.returnType as KneType.NULLABLE).inner) + + // describe_or_null: () -> Option + val descOrNull = methods["describe_or_null"] + assertNotNull(descOrNull) + assertTrue(descOrNull!!.returnType is KneType.NULLABLE) + assertEquals(KneType.STRING, (descOrNull.returnType as KneType.NULLABLE).inner) + } + + @Test + fun `Calculator has collection methods`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val methods = calc.methods.associateBy { it.name } + + // get_recent_scores: () -> Vec = LIST(INT) + val scores = methods["get_recent_scores"] + assertNotNull(scores) + assertTrue(scores!!.returnType is KneType.LIST) + assertEquals(KneType.INT, (scores.returnType as KneType.LIST).elementType) + } + + @Test + fun `Calculator has byte array methods`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val methods = calc.methods.associateBy { it.name } + + // to_bytes: () -> Vec = BYTE_ARRAY + assertEquals(KneType.BYTE_ARRAY, methods["to_bytes"]?.returnType) + + // reverse_bytes: (&[u8]) -> Vec = BYTE_ARRAY + assertEquals(KneType.BYTE_ARRAY, methods["reverse_bytes"]?.returnType) + } + + // --- Point data class --- + + @Test + fun `Point is parsed as KneDataClass`() { + val point = module.dataClasses.find { it.simpleName == "Point" } + assertNotNull("Point should be a data class", point) + assertEquals(2, point!!.fields.size) + assertEquals("x", point.fields[0].name) + assertEquals(KneType.INT, point.fields[0].type) + assertEquals("y", point.fields[1].name) + assertEquals(KneType.INT, point.fields[1].type) + } + + @Test + fun `NamedValue is parsed as KneDataClass`() { + val nv = module.dataClasses.find { it.simpleName == "NamedValue" } + assertNotNull("NamedValue should be a data class", nv) + assertEquals(2, nv!!.fields.size) + assertEquals("name", nv.fields[0].name) + assertEquals(KneType.STRING, nv.fields[0].type) + assertEquals("value", nv.fields[1].name) + assertEquals(KneType.INT, nv.fields[1].type) + } + + @Test + fun `Point is not in classes list`() { + assertNull(module.classes.find { it.simpleName == "Point" }) + } + + @Test + fun `NamedValue is not in classes list`() { + assertNull(module.classes.find { it.simpleName == "NamedValue" }) + } + + @Test + fun `two data classes`() { + assertEquals(2, module.dataClasses.size) + } + + @Test + fun `get_point returns DATA_CLASS type`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + // get_point is a no-arg getter, promoted to a property (prefix "get_" stripped) + val prop = calc.properties.find { it.name == "point" || it.name == "get_point" } + assertNotNull("point property not found, properties: ${calc.properties.map { it.name }}", prop) + assertTrue(prop!!.type is KneType.DATA_CLASS) + val dc = prop.type as KneType.DATA_CLASS + assertEquals("Point", dc.simpleName) + assertEquals(2, dc.fields.size) + } + + @Test + fun `add_point takes DATA_CLASS param`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val addPoint = calc.methods.find { it.name == "add_point" } + assertNotNull(addPoint) + assertEquals(1, addPoint!!.params.size) + assertTrue(addPoint.params[0].type is KneType.DATA_CLASS) + assertEquals("Point", (addPoint.params[0].type as KneType.DATA_CLASS).simpleName) + } + + @Test + fun `get_named_value returns DATA_CLASS with String field`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + // get_named_value is a no-arg getter, promoted to a property (prefix "get_" stripped) + val prop = calc.properties.find { it.name == "named_value" || it.name == "get_named_value" } + assertNotNull("named_value property not found, properties: ${calc.properties.map { it.name }}", prop) + assertTrue(prop!!.type is KneType.DATA_CLASS) + val dc = prop.type as KneType.DATA_CLASS + assertEquals("NamedValue", dc.simpleName) + assertEquals(KneType.STRING, dc.fields[0].type) + } + + @Test + fun `set_from_named takes DATA_CLASS param`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val setNamed = calc.methods.find { it.name == "set_from_named" } + assertNotNull(setNamed) + assertTrue(setNamed!!.params[0].type is KneType.DATA_CLASS) + assertEquals("NamedValue", (setNamed.params[0].type as KneType.DATA_CLASS).simpleName) + } + + // --- Nullable params --- + + @Test + fun `add_optional takes NULLABLE INT param`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "add_optional" } + assertNotNull(method) + assertEquals(1, method!!.params.size) + assertTrue("param should be NULLABLE", method.params[0].type is KneType.NULLABLE) + assertEquals(KneType.INT, (method.params[0].type as KneType.NULLABLE).inner) + } + + @Test + fun `set_nickname takes NULLABLE STRING param`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "set_nickname" } + assertNotNull(method) + assertTrue(method!!.params[0].type is KneType.NULLABLE) + assertEquals(KneType.STRING, (method.params[0].type as KneType.NULLABLE).inner) + } + + @Test + fun `get_nickname returns NULLABLE STRING`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "get_nickname" } + assertNotNull(method) + assertTrue(method!!.returnType is KneType.NULLABLE) + assertEquals(KneType.STRING, (method.returnType as KneType.NULLABLE).inner) + } + + // --- Operation enum --- + + @Test + fun `parses Operation enum with 3 variants`() { + val op = module.enums.find { it.simpleName == "Operation" } + assertNotNull("Operation enum should exist", op) + assertEquals(3, op!!.entries.size) + assertTrue(op.entries.containsAll(listOf("Add", "Subtract", "Multiply"))) + } + + // --- Top-level functions --- + + @Test + fun `parses compute function`() { + val compute = module.functions.find { it.name == "compute" } + assertNotNull(compute) + assertEquals(3, compute!!.params.size) + assertEquals(KneType.INT, compute.returnType) + } + + @Test + fun `parses greet function`() { + val greet = module.functions.find { it.name == "greet" } + assertNotNull(greet) + assertEquals(KneType.STRING, greet!!.params[0].type) + assertEquals(KneType.STRING, greet.returnType) + } + + @Test + fun `parses find_max with Option return`() { + val findMax = module.functions.find { it.name == "find_max" } + assertNotNull(findMax) + assertTrue(findMax!!.returnType is KneType.NULLABLE) + assertEquals(KneType.INT, (findMax.returnType as KneType.NULLABLE).inner) + } + + // --- Slice param tests --- + + @Test + fun `Calculator has sum_bytes method with BYTE_ARRAY param`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val sumBytes = calc.methods.find { it.name == "sum_bytes" } + assertNotNull(sumBytes) + assertEquals(1, sumBytes!!.params.size) + assertEquals(KneType.BYTE_ARRAY, sumBytes.params[0].type) + assertEquals(KneType.INT, sumBytes.returnType) + } + + @Test + fun `Calculator has reverse_bytes method`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val revBytes = calc.methods.find { it.name == "reverse_bytes" } + assertNotNull(revBytes) + assertEquals(KneType.BYTE_ARRAY, revBytes!!.params[0].type) + assertEquals(KneType.BYTE_ARRAY, revBytes.returnType) + } + + @Test + fun `parses sum_all with slice param`() { + val sumAll = module.functions.find { it.name == "sum_all" } + assertNotNull(sumAll) + assertEquals(1, sumAll!!.params.size) + assertTrue(sumAll.params[0].type is KneType.LIST) + assertEquals(KneType.INT, (sumAll.params[0].type as KneType.LIST).elementType) + } + + @Test + fun `parses find_max with slice param and Option return`() { + val findMax = module.functions.find { it.name == "find_max" } + assertNotNull(findMax) + assertTrue(findMax!!.params[0].type is KneType.LIST) + assertTrue(findMax.returnType is KneType.NULLABLE) + } + + // --- Parity checks --- + + @Test + fun `same number of classes as expected`() { + // Only Calculator (Point and NamedValue are data classes) + assertEquals(1, module.classes.size) + } + + @Test + fun `same number of enums as expected`() { + // Operation = 1 + assertEquals(1, module.enums.size) + } + + // --- Suspend detection --- + + @Test + fun `delayed_add is marked as suspend`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "delayed_add" } + assertNotNull(method) + assertTrue(method!!.isSuspend) + } + + @Test + fun `delayed_describe is marked as suspend`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "delayed_describe" } + assertNotNull(method) + assertTrue(method!!.isSuspend) + assertEquals(KneType.STRING, method.returnType) + } + + @Test + fun `non-suspend methods are not marked as suspend`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val add = calc.methods.find { it.name == "add" } + assertFalse(add!!.isSuspend) + } + + @Test + fun `delayed_noop is suspend with Unit return`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "delayed_noop" } + assertNotNull(method) + assertTrue(method!!.isSuspend) + assertEquals(KneType.UNIT, method.returnType) + } + + @Test + fun `delayed_is_positive is suspend with Boolean return`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "delayed_is_positive" } + assertNotNull(method) + assertTrue(method!!.isSuspend) + assertEquals(KneType.BOOLEAN, method.returnType) + } + + @Test + fun `fail_after_delay is marked as suspend`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "fail_after_delay" } + assertNotNull(method) + assertTrue(method!!.isSuspend) + } + + @Test + fun `top-level functions are not marked as suspend`() { + val compute = module.functions.find { it.name == "compute" } + assertFalse(compute!!.isSuspend) + } + + // --- Flow detection --- + + @Test + fun `count_up has Flow Int return type`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "count_up" } + assertNotNull(method) + assertTrue(method!!.returnType is KneType.FLOW) + assertEquals(KneType.INT, (method.returnType as KneType.FLOW).elementType) + } + + @Test + fun `score_labels has Flow String return type`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "score_labels" } + assertNotNull(method) + assertTrue(method!!.returnType is KneType.FLOW) + assertEquals(KneType.STRING, (method.returnType as KneType.FLOW).elementType) + } + + @Test + fun `flow methods are not marked as suspend`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val countUp = calc.methods.find { it.name == "count_up" } + assertFalse(countUp!!.isSuspend) + } + + // --- Traits → Interfaces --- + + @Test + fun `parses Describable trait as KneInterface`() { + val iface = module.interfaces.find { it.simpleName == "Describable" } + assertNotNull("Describable interface should exist", iface) + assertEquals(1, iface!!.methods.size) + assertEquals("describe_self", iface.methods[0].name) + assertEquals(KneType.STRING, iface.methods[0].returnType) + } + + @Test + fun `parses Measurable trait with 2 methods`() { + val iface = module.interfaces.find { it.simpleName == "Measurable" } + assertNotNull("Measurable interface should exist", iface) + assertEquals(2, iface!!.methods.size) + assertTrue(iface.methods.any { it.name == "measure" && it.returnType == KneType.DOUBLE }) + assertTrue(iface.methods.any { it.name == "unit" && it.returnType == KneType.STRING }) + } + + @Test + fun `parses Resettable trait`() { + val iface = module.interfaces.find { it.simpleName == "Resettable" } + assertNotNull("Resettable interface should exist", iface) + assertEquals(1, iface!!.methods.size) + assertEquals("reset_to_default", iface.methods[0].name) + } + + @Test + fun `Calculator implements all 3 traits`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + assertTrue("Expected >= 3 interfaces, got: ${calc.interfaces}", calc.interfaces.size >= 3) + assertTrue(calc.interfaces.any { "Describable" in it }) + assertTrue(calc.interfaces.any { "Resettable" in it }) + assertTrue(calc.interfaces.any { "Measurable" in it }) + } + + @Test + fun `Calculator has trait methods with isOverride`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val describeSelf = calc.methods.find { it.name == "describe_self" } + assertNotNull("describe_self should exist", describeSelf) + assertTrue(describeSelf!!.isOverride) + } + + // --- Sealed enums (tagged enums with data) --- + + @Test + fun `CalcResult is parsed as sealed enum not simple enum`() { + // CalcResult has data variants, so it should be in sealedEnums, not enums + assertEquals(1, module.enums.size) // Only Operation + assertEquals("Operation", module.enums[0].simpleName) + val sealed = module.sealedEnums.find { it.simpleName == "CalcResult" } + assertNotNull("CalcResult should be a sealed enum", sealed) + } + + @Test + fun `CalcResult has 4 variants`() { + val sealed = module.sealedEnums.first { it.simpleName == "CalcResult" } + assertEquals(4, sealed.variants.size) + val names = sealed.variants.map { it.name } + assertTrue("Value" in names) + assertTrue("Error" in names) + assertTrue("Partial" in names) + assertTrue("Nothing" in names) + } + + @Test + fun `CalcResult Value variant has single i32 field`() { + val sealed = module.sealedEnums.first { it.simpleName == "CalcResult" } + val value = sealed.variants.first { it.name == "Value" } + assertEquals(1, value.fields.size) + assertEquals("value", value.fields[0].name) + assertEquals(KneType.INT, value.fields[0].type) + } + + @Test + fun `CalcResult Error variant has single String field`() { + val sealed = module.sealedEnums.first { it.simpleName == "CalcResult" } + val error = sealed.variants.first { it.name == "Error" } + assertEquals(1, error.fields.size) + assertEquals("value", error.fields[0].name) + assertEquals(KneType.STRING, error.fields[0].type) + } + + @Test + fun `CalcResult Partial variant has named struct fields`() { + val sealed = module.sealedEnums.first { it.simpleName == "CalcResult" } + val partial = sealed.variants.first { it.name == "Partial" } + assertEquals(2, partial.fields.size) + assertEquals("value", partial.fields[0].name) + assertEquals(KneType.INT, partial.fields[0].type) + assertEquals("confidence", partial.fields[1].name) + assertEquals(KneType.DOUBLE, partial.fields[1].type) + } + + @Test + fun `CalcResult Nothing variant has no fields`() { + val sealed = module.sealedEnums.first { it.simpleName == "CalcResult" } + val nothing = sealed.variants.first { it.name == "Nothing" } + assertTrue(nothing.fields.isEmpty()) + } + + @Test + fun `try_divide returns SEALED_ENUM type`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "try_divide" } + assertNotNull("try_divide should exist", method) + assertTrue(method!!.returnType is KneType.SEALED_ENUM) + assertEquals("CalcResult", (method.returnType as KneType.SEALED_ENUM).simpleName) + } + + @Test + fun `last_result returns SEALED_ENUM type`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "last_result" } + assertNotNull("last_result should exist", method) + assertTrue(method!!.returnType is KneType.SEALED_ENUM) + } + + // --- Callback / function pointer params --- + + @Test + fun `transform_and_sum has fn(i32) to i32 callback param`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "transform_and_sum" } + assertNotNull("transform_and_sum should exist", method) + val cbParam = method!!.params.find { it.type is KneType.FUNCTION } + assertNotNull("Should have a FUNCTION param", cbParam) + val fnType = cbParam!!.type as KneType.FUNCTION + assertEquals(listOf(KneType.INT), fnType.paramTypes) + assertEquals(KneType.INT, fnType.returnType) + } + + @Test + fun `for_each_score has fn(i32) callback param`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "for_each_score" } + assertNotNull("for_each_score should exist", method) + val cbParam = method!!.params.find { it.type is KneType.FUNCTION } + assertNotNull("Should have a FUNCTION param", cbParam) + val fnType = cbParam!!.type as KneType.FUNCTION + assertEquals(listOf(KneType.INT), fnType.paramTypes) + assertEquals(KneType.UNIT, fnType.returnType) + } + + @Test + fun `run_tick_loop is suspend with fn(i32) callback`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val method = calc.methods.find { it.name == "run_tick_loop" } + assertNotNull("run_tick_loop should exist", method) + assertTrue(method!!.isSuspend) + val cbParam = method.params.find { it.type is KneType.FUNCTION } + assertNotNull("Should have a FUNCTION param", cbParam) + val fnType = cbParam!!.type as KneType.FUNCTION + assertEquals(listOf(KneType.INT), fnType.paramTypes) + assertEquals(KneType.UNIT, fnType.returnType) + } +} diff --git a/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustWorkActionTest.kt b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustWorkActionTest.kt new file mode 100644 index 00000000..479cc354 --- /dev/null +++ b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustWorkActionTest.kt @@ -0,0 +1,275 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.analysis + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import java.io.File +import kotlin.io.path.createTempDirectory + +class RustWorkActionTest { + + @Test + fun `select rustdoc json prefers current lib target over stale files`() { + val tempDir = createTempDirectory("kne-rustdoc-select").toFile() + try { + File(tempDir, "Cargo.toml").writeText( + """ + [package] + name = "kne-test-wrapper" + + [lib] + name = "test" + """.trimIndent() + ) + + val docDir = File(tempDir, "target/doc").apply { mkdirs() } + File(docDir, "rustsysinfo.json").writeText("{}") + val expected = File(docDir, "test.json").apply { writeText("{}") } + + val selected = RustWorkAction.selectRustdocJson(docDir, tempDir, "test") + + assertEquals(expected.absolutePath, selected?.absolutePath) + } finally { + tempDir.deleteRecursively() + } + } + + @Test + fun `resolve rustdoc target name falls back to package name when lib section is absent`() { + val tempDir = createTempDirectory("kne-rustdoc-target").toFile() + try { + File(tempDir, "Cargo.toml").writeText( + """ + [package] + name = "rust-sysinfo" + """.trimIndent() + ) + + assertEquals("rust_sysinfo", RustWorkAction.resolveRustdocTargetName(tempDir)) + } finally { + tempDir.deleteRecursively() + } + } + + @Test + fun `resolve rustdoc target name returns null when cargo toml is missing`() { + val tempDir = createTempDirectory("kne-rustdoc-missing").toFile() + try { + assertNull(RustWorkAction.resolveRustdocTargetName(tempDir)) + } finally { + tempDir.deleteRecursively() + } + } + + @Test + fun `find package manifest dir resolves registry dependency from cargo metadata`() { + val metadataJson = """ + { + "packages": [ + { + "name": "kne-test-wrapper", + "version": "0.1.0", + "manifest_path": "/tmp/wrapper/Cargo.toml" + }, + { + "name": "tray", + "version": "0.1.2", + "manifest_path": "/home/user/.cargo/registry/src/index.crates.io-xxxx/tray-0.1.2/Cargo.toml" + } + ] + } + """.trimIndent() + + val manifest = RustWorkAction.findPackageManifestDir(metadataJson, io.github.kdroidfilter.nucleusnativeaccess.plugin.CrateDependency(name = "tray", version = "0.1.2")) + + assertNotNull(manifest) + assertEquals("/home/user/.cargo/registry/src/index.crates.io-xxxx/tray-0.1.2/Cargo.toml", manifest!!.path) + } + + @Test + fun `parse with unsupported signatures collects warnings through callback`() { + val json = """ + { + "root": 0, + "crate_version": "0.1.0", + "index": { + "0": { + "id": 0, + "crate_id": 0, + "name": "sample", + "visibility": "public", + "inner": { + "module": { + "items": [1, 2, 3, 4], + "is_crate": true, + "is_stripped": false + } + } + }, + "1": { + "id": 1, + "crate_id": 0, + "name": "MyStruct", + "visibility": "public", + "span": {"filename": "src/lib.rs", "begin": [1, 1], "end": [1, 10]}, + "inner": {"struct": {"kind": {"plain": {"fields": []}}, "generics": {"params": [], "where_predicates": []}, "impls": []}} + }, + "2": { + "id": 2, + "crate_id": 0, + "name": "supported_fn", + "visibility": "public", + "span": {"filename": "src/lib.rs", "begin": [5, 1], "end": [5, 20]}, + "inner": { + "function": { + "sig": { + "inputs": [["value", {"primitive": "i32"}]], + "output": {"primitive": "i32"}, + "is_c_variadic": false + }, + "generics": {"params": [], "where_predicates": []}, + "header": {"is_const": false, "is_unsafe": false, "is_async": false, "abi": "Rust"}, + "has_body": true + } + } + }, + "3": { + "id": 3, + "crate_id": 0, + "name": "unit_return_fn", + "visibility": "public", + "span": {"filename": "src/lib.rs", "begin": [10, 1], "end": [10, 30]}, + "inner": { + "function": { + "sig": { + "inputs": [["self", {"borrowed_ref": {"lifetime": null, "is_mutable": true, "type": {"generic": "Self"}}}]], + "output": null, + "is_c_variadic": false + }, + "generics": {"params": [], "where_predicates": []}, + "header": {"is_const": false, "is_unsafe": false, "is_async": false, "abi": "Rust"}, + "has_body": true + } + } + }, + "4": { + "id": 4, + "crate_id": 0, + "name": "unsupported_generic_param", + "visibility": "public", + "span": {"filename": "src/lib.rs", "begin": [15, 1], "end": [15, 40]}, + "inner": { + "function": { + "sig": { + "inputs": [["value", {"generic": "T"}]], + "output": {"primitive": "i32"}, + "is_c_variadic": false + }, + "generics": {"params": [{"name": "T", "kind": {"type": {"bounds": [], "default": null}}}], "where_predicates": []}, + "header": {"is_const": false, "is_unsafe": false, "is_async": false, "abi": "Rust"}, + "has_body": true + } + } + } + } + } + """.trimIndent() + + val unsupported = mutableListOf() + val module = RustdocJsonParser().parse(json, "sample") { unsupported.add(it) } + + // supported_fn should be present (i32 -> i32) + assertNotNull(module.functions.find { it.name == "supported_fn" }) + // unsupported_generic_param should NOT be present (unbounded generic T is unsupported) + assertNull(module.functions.find { it.name == "unsupported_generic_param" }) + // At least one unsupported warning must have been reported + assertTrue("Expected at least 1 unsupported warning, got: $unsupported", unsupported.size >= 1) + assertTrue("Expected unsupported warning for unsupported_generic_param, got: $unsupported", unsupported.any { it.contains("unsupported_generic_param") }) + } + + @Test + fun `parse with unit-returning method does not report unsupported`() { + val json = """ + { + "root": 0, + "crate_version": "0.1.0", + "index": { + "0": { + "id": 0, + "crate_id": 0, + "name": "sample", + "visibility": "public", + "inner": { + "module": { + "items": [1, 2], + "is_crate": true, + "is_stripped": false + } + } + }, + "1": { + "id": 1, + "crate_id": 0, + "name": "Counter", + "visibility": "public", + "span": {"filename": "src/lib.rs", "begin": [1, 1], "end": [1, 10]}, + "inner": {"struct": {"kind": {"plain": {"fields": []}}, "generics": {"params": [], "where_predicates": []}, "impls": [2]}} + }, + "2": { + "id": 2, + "crate_id": 0, + "name": null, + "visibility": "default", + "inner": { + "impl": { + "is_unsafe": false, + "generics": {"params": [], "where_predicates": []}, + "provided_trait_methods": [], + "trait": null, + "for": {"resolved_path": {"path": "Counter", "id": 1, "args": null}}, + "items": [3], + "is_negative": false, + "is_synthetic": false, + "blanket_impl": null + } + } + }, + "3": { + "id": 3, + "crate_id": 0, + "name": "reset", + "visibility": "public", + "span": {"filename": "src/lib.rs", "begin": [10, 1], "end": [10, 20]}, + "inner": { + "function": { + "sig": { + "inputs": [["self", {"borrowed_ref": {"lifetime": null, "is_mutable": true, "type": {"generic": "Self"}}}]], + "output": null, + "is_c_variadic": false + }, + "generics": {"params": [], "where_predicates": []}, + "header": {"is_const": false, "is_unsafe": false, "is_async": false, "abi": "Rust"}, + "has_body": true + } + } + } + } + } + """.trimIndent() + + val unsupported = mutableListOf() + val module = RustdocJsonParser().parse(json, "sample") { unsupported.add(it) } + + val counterClass = module.classes.find { it.simpleName == "Counter" } + assertNotNull("Counter class should be present", counterClass) + // reset() with output=null should NOT be reported as unsupported + assertTrue(unsupported.none { it.contains("reset") }) + // Counter should have exactly one method: reset + assertEquals(1, counterClass!!.methods.size) + assertEquals("reset", counterClass.methods[0].name) + assertEquals(io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.KneType.UNIT, counterClass.methods[0].returnType) + } +} diff --git a/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustdocJsonParserTest.kt b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustdocJsonParserTest.kt new file mode 100644 index 00000000..cc383578 --- /dev/null +++ b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/analysis/RustdocJsonParserTest.kt @@ -0,0 +1,749 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.analysis + +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.* +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class RustdocJsonParserTest { + + private lateinit var module: KneModule + + @Before + fun setUp() { + val json = javaClass.classLoader + .getResourceAsStream("rustdoc-fixtures/mini-calculator.json")!! + .bufferedReader() + .readText() + module = RustdocJsonParser().parse(json, "calculator") + } + + // --- Module-level --- + + @Test + fun `module has correct lib name`() { + assertEquals("calculator", module.libName) + } + + @Test + fun `module has a package`() { + assertTrue(module.packages.isNotEmpty()) + } + + // --- Structs → KneClass --- + + @Test + fun `parses Calculator struct as KneClass`() { + val calc = module.classes.find { it.simpleName == "Calculator" } + assertNotNull("Calculator class should exist", calc) + } + + @Test + fun `Calculator has constructor with correct params`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + // new(initial_value: i32, name: String) -> Self + assertEquals(2, calc.constructor.params.size) + assertEquals("initial_value", calc.constructor.params[0].name) + assertEquals(KneType.INT, calc.constructor.params[0].type) + assertEquals("name", calc.constructor.params[1].name) + assertEquals(KneType.STRING, calc.constructor.params[1].type) + } + + @Test + fun `Calculator has add method`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val add = calc.methods.find { it.name == "add" } + assertNotNull("add method should exist", add) + assertEquals(1, add!!.params.size) + assertEquals("n", add.params[0].name) + assertEquals(KneType.INT, add.params[0].type) + assertEquals(KneType.INT, add.returnType) + } + + @Test + fun `Calculator has subtract method`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val sub = calc.methods.find { it.name == "subtract" } + assertNotNull("subtract method should exist", sub) + } + + @Test + fun `Calculator has multiply method`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val mul = calc.methods.find { it.name == "multiply" } + assertNotNull("multiply method should exist", mul) + } + + @Test + fun `Calculator has value property extracted from get_value`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val valueProp = calc.properties.find { it.name == "value" } + assertNotNull("value property should exist (extracted from get_value)", valueProp) + assertEquals(KneType.INT, valueProp!!.type) + } + + @Test + fun `Calculator has name property extracted from get_name`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val nameProp = calc.properties.find { it.name == "name" } + assertNotNull("name property should exist (extracted from get_name)", nameProp) + assertEquals(KneType.STRING, nameProp!!.type) + } + + @Test + fun `Calculator has reset method with mut self`() { + val calc = module.classes.first { it.simpleName == "Calculator" } + val reset = calc.methods.find { it.name == "reset" } + assertNotNull("reset method should exist", reset) + assertEquals(1, reset!!.params.size) + assertEquals("new_value", reset.params[0].name) + assertEquals(KneType.INT, reset.returnType) + } + + // --- Point struct --- + + @Test + fun `parses Point struct as KneClass`() { + val point = module.classes.find { it.simpleName == "Point" } + assertNotNull("Point class should exist", point) + } + + @Test + fun `Point has constructor with x and y as Double`() { + val point = module.classes.first { it.simpleName == "Point" } + assertEquals(2, point.constructor.params.size) + assertEquals("x", point.constructor.params[0].name) + assertEquals(KneType.DOUBLE, point.constructor.params[0].type) + assertEquals("y", point.constructor.params[1].name) + assertEquals(KneType.DOUBLE, point.constructor.params[1].type) + } + + @Test + fun `Point has distance_to method taking another Point`() { + val point = module.classes.first { it.simpleName == "Point" } + val dist = point.methods.find { it.name == "distance_to" } + assertNotNull("distance_to method should exist", dist) + assertEquals(1, dist!!.params.size) + assertTrue("param should be OBJECT(Point)", dist.params[0].type is KneType.OBJECT) + assertEquals("Point", (dist.params[0].type as KneType.OBJECT).simpleName) + assertEquals(KneType.DOUBLE, dist.returnType) + } + + @Test + fun `Point has to_string_repr method returning String`() { + val point = module.classes.first { it.simpleName == "Point" } + val toStr = point.methods.find { it.name == "to_string_repr" } + assertNotNull("to_string_repr method should exist", toStr) + assertEquals(KneType.STRING, toStr!!.returnType) + } + + // --- Enum --- + + @Test + fun `parses Operation enum`() { + val op = module.enums.find { it.simpleName == "Operation" } + assertNotNull("Operation enum should exist", op) + } + + @Test + fun `Operation enum has 4 variants`() { + val op = module.enums.first { it.simpleName == "Operation" } + assertEquals(4, op.entries.size) + assertTrue(op.entries.containsAll(listOf("Add", "Subtract", "Multiply", "Divide"))) + } + + // --- Top-level functions --- + + @Test + fun `parses compute as top-level function`() { + val compute = module.functions.find { it.name == "compute" } + assertNotNull("compute function should exist", compute) + } + + @Test + fun `compute has correct params`() { + val compute = module.functions.first { it.name == "compute" } + assertEquals(3, compute.params.size) + assertEquals("a", compute.params[0].name) + assertEquals(KneType.INT, compute.params[0].type) + assertEquals("b", compute.params[1].name) + assertEquals(KneType.INT, compute.params[1].type) + assertEquals("op", compute.params[2].name) + assertTrue("op should be ENUM type", compute.params[2].type is KneType.ENUM) + assertEquals("Operation", (compute.params[2].type as KneType.ENUM).simpleName) + } + + @Test + fun `parses sum_all with Vec param mapped to LIST`() { + val sumAll = module.functions.find { it.name == "sum_all" } + assertNotNull("sum_all function should exist", sumAll) + assertEquals(1, sumAll!!.params.size) + val paramType = sumAll.params[0].type + assertTrue("param should be LIST", paramType is KneType.LIST) + assertEquals(KneType.INT, (paramType as KneType.LIST).elementType) + assertEquals(KneType.INT, sumAll.returnType) + } + + @Test + fun `parses greet with str param mapped to STRING`() { + val greet = module.functions.find { it.name == "greet" } + assertNotNull("greet function should exist", greet) + assertEquals(1, greet!!.params.size) + assertEquals(KneType.STRING, greet.params[0].type) + assertEquals(KneType.STRING, greet.returnType) + } + + @Test + fun `parses find_max with Option return mapped to NULLABLE`() { + val findMax = module.functions.find { it.name == "find_max" } + assertNotNull("find_max function should exist", findMax) + val retType = findMax!!.returnType + assertTrue("return should be NULLABLE", retType is KneType.NULLABLE) + assertEquals(KneType.INT, (retType as KneType.NULLABLE).inner) + } + + // --- Filtering --- + + @Test + fun `does not include private or default visibility items as classes`() { + // Standard library trait impls (From, Into, etc.) should not be classes + val classNames = module.classes.map { it.simpleName } + assertFalse("Should not contain standard lib types", classNames.any { + it in listOf("From", "Into", "Borrow", "BorrowMut", "Any") + }) + } + + @Test + fun `no suspend functions since Rust has no suspend`() { + val allMethods = module.classes.flatMap { it.methods } + module.functions + assertTrue(allMethods.none { it.isSuspend }) + } + + @Test + fun `ignores generated bridge functions from kne bridges file`() { + val json = """ + { + "root": 0, + "index": { + "0": { + "id": 0, + "crate_id": 0, + "name": "sample", + "visibility": "public", + "inner": { + "module": { + "is_crate": true, + "items": [1, 2], + "is_stripped": false + } + } + }, + "1": { + "id": 1, + "crate_id": 0, + "name": "real_api", + "visibility": "public", + "span": { + "filename": "src/lib.rs" + }, + "inner": { + "function": { + "sig": { + "inputs": [], + "output": null, + "is_c_variadic": false + }, + "generics": { + "params": [], + "where_predicates": [] + } + } + } + }, + "2": { + "id": 2, + "crate_id": 0, + "name": "sample_kne_hasError", + "visibility": "public", + "span": { + "filename": "/tmp/out/kne_bridges.rs" + }, + "inner": { + "function": { + "sig": { + "inputs": [], + "output": { + "primitive": "i32" + }, + "is_c_variadic": false + }, + "generics": { + "params": [], + "where_predicates": [] + } + } + } + } + } + } + """.trimIndent() + + val parsed = RustdocJsonParser().parse(json, "ffi_name") + + assertEquals(listOf("real_api"), parsed.functions.map { it.name }) + } + + @Test + fun `preserves dyn trait generics in rust type hints and marks unsafe methods`() { + val json = """ + { + "root": 0, + "index": { + "0": { + "id": 0, + "crate_id": 0, + "name": "sample", + "visibility": "public", + "inner": { + "module": { + "is_crate": true, + "items": [1, 2], + "is_stripped": false + } + } + }, + "1": { + "id": 1, + "crate_id": 0, + "name": "Builder", + "visibility": "public", + "inner": { + "struct": { + "kind": { "unit": true }, + "generics": { + "params": [], + "where_predicates": [] + }, + "impls": [2] + } + } + }, + "2": { + "id": 2, + "crate_id": 0, + "name": null, + "visibility": "default", + "inner": { + "impl": { + "is_unsafe": false, + "generics": { + "params": [], + "where_predicates": [] + }, + "provided_trait_methods": [], + "trait": null, + "for": { + "resolved_path": { + "path": "Builder", + "id": 1, + "args": null + } + }, + "items": [3, 4], + "is_negative": false, + "is_synthetic": false, + "blanket_impl": null + } + } + }, + "3": { + "id": 3, + "crate_id": 0, + "name": "with_menu", + "visibility": "public", + "inner": { + "function": { + "sig": { + "inputs": [ + ["self", { "generic": "Self" }], + ["menu", { + "resolved_path": { + "path": "Box", + "id": 9, + "args": { + "angle_bracketed": { + "args": [ + { + "type": { + "dyn_trait": { + "traits": [ + { + "trait": { + "path": "menu::ContextMenu", + "id": 10, + "args": null + }, + "generic_params": [] + } + ], + "lifetime": null + } + } + } + ], + "constraints": [] + } + } + } + }] + ], + "output": { "generic": "Self" }, + "is_c_variadic": false + }, + "generics": { + "params": [], + "where_predicates": [] + }, + "header": { + "is_const": false, + "is_unsafe": false, + "is_async": false, + "abi": "Rust" + }, + "has_body": true + } + } + }, + "4": { + "id": 4, + "crate_id": 0, + "name": "app_indicator", + "visibility": "public", + "inner": { + "function": { + "sig": { + "inputs": [ + [ + "self", + { + "borrowed_ref": { + "lifetime": null, + "is_mutable": false, + "type": { "generic": "Self" } + } + } + ] + ], + "output": null, + "is_c_variadic": false + }, + "generics": { + "params": [], + "where_predicates": [] + }, + "header": { + "is_const": false, + "is_unsafe": true, + "is_async": false, + "abi": "Rust" + }, + "has_body": true + } + } + } + } + } + """.trimIndent() + + val parsed = RustdocJsonParser().parse(json, "sample") + val builder = parsed.classes.first { it.simpleName == "Builder" } + + val withMenu = builder.methods.first { it.name == "with_menu" } + assertTrue(withMenu.params[0].type is KneType.OBJECT) + assertEquals("Box", (withMenu.params[0].type as KneType.OBJECT).simpleName) + assertEquals("Box", withMenu.params[0].rustType) + + val appIndicator = builder.methods.first { it.name == "app_indicator" } + assertTrue(appIndicator.isUnsafe) + } + + @Test + fun `impl Trait return resolved as INTERFACE for known crate trait`() { + // Fixture: a crate with a trait "Describable" and a function "get_describable() -> impl Describable" + val json = """ + { + "root": 0, + "index": { + "0": { + "id": 0, + "crate_id": 0, + "name": "mylib", + "visibility": "public", + "inner": { + "module": { + "items": [1, 2], + "is_crate": true, + "is_stripped": false + } + } + }, + "1": { + "id": 1, + "crate_id": 0, + "name": "Describable", + "visibility": "public", + "inner": { + "trait": { + "items": [], + "generics": { "params": [], "where_predicates": [] }, + "bounds": [], + "is_auto": false, + "is_unsafe": false + } + } + }, + "2": { + "id": 2, + "crate_id": 0, + "name": "get_describable", + "visibility": "public", + "span": { "filename": "src/lib.rs" }, + "inner": { + "function": { + "sig": { + "inputs": [], + "output": { + "impl_trait": [ + { + "trait_bound": { + "trait": { + "id": 1, + "path": "mylib::Describable", + "args": { "angle_bracketed": { "args": [], "bindings": [] } } + } + } + } + ] + }, + "is_c_variadic": false + }, + "generics": { "params": [], "where_predicates": [] } + } + } + } + } + } + """.trimIndent() + + val parsed = RustdocJsonParser().parse(json, "mylib") + + // The function should be bridged (not skipped) + assertEquals(1, parsed.functions.size) + val fn = parsed.functions[0] + assertEquals("get_describable", fn.name) + assertTrue("Return type should be INTERFACE", fn.returnType is KneType.INTERFACE) + assertEquals("Describable", (fn.returnType as KneType.INTERFACE).simpleName) + assertEquals("impl dyn Describable", fn.returnRustType) + + // A DynDescribable wrapper class should be generated + assertTrue( + "DynDescribable wrapper class should be generated", + parsed.classes.any { it.simpleName == "DynDescribable" && it.isDynTrait }, + ) + } + + @Test + fun `reports unsupported signatures through callback`() { + // Use an unbounded generic T which is truly unsupported + val json = """ + { + "root": 0, + "index": { + "0": { + "id": 0, + "crate_id": 0, + "name": "sample", + "visibility": "public", + "inner": { + "module": { + "items": [1], + "is_crate": true, + "is_stripped": false + } + } + }, + "1": { + "id": 1, + "crate_id": 0, + "name": "bad_fn", + "visibility": "public", + "span": { + "filename": "src/lib.rs" + }, + "inner": { + "function": { + "sig": { + "inputs": [ + ["value", {"generic": "T"}] + ], + "output": {"primitive": "i32"}, + "is_c_variadic": false + }, + "generics": { + "params": [{"name": "T", "kind": {"type": {"bounds": [], "default": null}}}], + "where_predicates": [] + } + } + } + } + } + } + """.trimIndent() + + val unsupported = mutableListOf() + val parsed = RustdocJsonParser().parse(json, "sample") { unsupported.add(it) } + + assertTrue("Function with unsupported generic should be skipped", parsed.functions.isEmpty()) + assertTrue("At least one unsupported warning expected", unsupported.size >= 1) + assertTrue("Warning should mention bad_fn", unsupported.any { it.contains("bad_fn") }) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // impl Future return types + // ═══════════════════════════════════════════════════════════════════════════ + + @Test + fun `async fn method sets isAsync flag`() { + val json = """ + { + "root": 0, + "index": { + "0": { + "id": 0, "crate_id": 0, "name": "sample", "visibility": "public", + "inner": {"module": {"items": [1], "is_crate": true, "is_stripped": false}} + }, + "1": { + "id": 1, "crate_id": 0, "name": "MyStruct", "visibility": "public", + "span": {"filename": "src/lib.rs"}, + "inner": {"struct": {"kind": {"plain": {"fields": []}}, "generics": {"params": [], "where_predicates": []}, "impls": [2]}} + }, + "2": { + "id": 2, "crate_id": 0, "name": null, "visibility": "public", + "inner": {"impl": {"is_synthetic": false, "is_negative": false, "trait": null, + "for": {"resolved_path": {"path": "MyStruct", "id": 1, "args": null}}, + "items": [3] + }} + }, + "3": { + "id": 3, "crate_id": 0, "name": "fetch_data", "visibility": "public", + "span": {"filename": "src/lib.rs"}, + "inner": {"function": { + "sig": { + "inputs": [["self", {"borrowed_ref": {"lifetime": null, "is_mutable": false, "type": {"generic": "Self"}}}]], + "output": {"resolved_path": {"path": "Vec", "id": 99, "args": {"angle_bracketed": {"args": [{"type": {"primitive": "u8"}}], "constraints": []}}}}, + "is_c_variadic": false + }, + "generics": {"params": [], "where_predicates": []}, + "header": {"is_const": false, "is_unsafe": false, "is_async": true, "abi": "Rust"}, + "has_body": true + }} + } + }, + "paths": {"1": {"crate_id": 0, "path": ["sample", "MyStruct"], "kind": "struct"}} + } + """.trimIndent() + + val module = RustdocJsonParser().parse(json, "sample") + val cls = module.classes.first { it.simpleName == "MyStruct" } + val method = cls.methods.find { it.name == "fetch_data" } + assertNotNull("fetch_data should exist", method) + assertTrue("fetch_data should be async", method!!.isAsync) + assertEquals(KneType.BYTE_ARRAY, method.returnType) + } + + @Test + fun `impl Future return type sets isAsync flag`() { + val json = """ + { + "root": 0, + "index": { + "0": { + "id": 0, "crate_id": 0, "name": "sample", "visibility": "public", + "inner": {"module": {"items": [1], "is_crate": true, "is_stripped": false}} + }, + "1": { + "id": 1, "crate_id": 0, "name": "MyStruct", "visibility": "public", + "span": {"filename": "src/lib.rs"}, + "inner": {"struct": {"kind": {"plain": {"fields": []}}, "generics": {"params": [], "where_predicates": []}, "impls": [2]}} + }, + "2": { + "id": 2, "crate_id": 0, "name": null, "visibility": "public", + "inner": {"impl": {"is_synthetic": false, "is_negative": false, "trait": null, + "for": {"resolved_path": {"path": "MyStruct", "id": 1, "args": null}}, + "items": [3] + }} + }, + "3": { + "id": 3, "crate_id": 0, "name": "do_work", "visibility": "public", + "span": {"filename": "src/lib.rs"}, + "inner": {"function": { + "sig": { + "inputs": [["self", {"generic": "Self"}]], + "output": {"impl_trait": [{"trait_bound": {"trait": {"path": "Future", "id": 50, "args": {"angle_bracketed": {"args": [], "constraints": [{"name": "Output", "args": null, "binding": {"equality": {"type": {"primitive": "i32"}}}}]}}}, "generic_params": [], "modifier": "none"}}]}, + "is_c_variadic": false + }, + "generics": {"params": [], "where_predicates": []}, + "header": {"is_const": false, "is_unsafe": false, "is_async": false, "abi": "Rust"}, + "has_body": true + }} + } + }, + "paths": {"1": {"crate_id": 0, "path": ["sample", "MyStruct"], "kind": "struct"}} + } + """.trimIndent() + + val module = RustdocJsonParser().parse(json, "sample") + val cls = module.classes.first { it.simpleName == "MyStruct" } + val method = cls.methods.find { it.name == "do_work" } + assertNotNull("do_work should exist", method) + assertTrue("do_work should be async (impl Future)", method!!.isAsync) + assertEquals(KneType.INT, method.returnType) + } + + // ═══════════════════════════════════════════════════════════════════════════ + // Synthetic impl Trait generic params (is_synthetic: true) + // ═══════════════════════════════════════════════════════════════════════════ + + @Test + fun `synthetic impl Trait params do not block method parsing`() { + val json = """ + { + "root": 0, + "index": { + "0": { + "id": 0, "crate_id": 0, "name": "sample", "visibility": "public", + "inner": {"module": {"items": [1], "is_crate": true, "is_stripped": false}} + }, + "1": { + "id": 1, "crate_id": 0, "name": "greet", "visibility": "public", + "span": {"filename": "src/lib.rs"}, + "inner": {"function": { + "sig": { + "inputs": [["name", {"impl_trait": [{"trait_bound": {"trait": {"path": "Into", "id": 55, "args": {"angle_bracketed": {"args": [{"type": {"resolved_path": {"path": "String", "id": 21, "args": null}}}], "constraints": []}}}, "generic_params": [], "modifier": "none"}}]}]], + "output": {"resolved_path": {"path": "String", "id": 21, "args": null}}, + "is_c_variadic": false + }, + "generics": {"params": [{"name": "impl Into", "kind": {"type": {"bounds": [{"trait_bound": {"trait": {"path": "Into", "id": 55, "args": {"angle_bracketed": {"args": [{"type": {"resolved_path": {"path": "String", "id": 21, "args": null}}}], "constraints": []}}}, "generic_params": [], "modifier": "none"}}], "default": null, "is_synthetic": true}}}], "where_predicates": []} + }} + } + } + } + """.trimIndent() + + val parsed = RustdocJsonParser().parse(json, "sample") + assertEquals("greet should be parsed", 1, parsed.functions.size) + assertEquals("greet", parsed.functions[0].name) + assertEquals(KneType.STRING, parsed.functions[0].params[0].type) + assertEquals(KneType.STRING, parsed.functions[0].returnType) + } +} diff --git a/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/RustBridgeGeneratorTest.kt b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/RustBridgeGeneratorTest.kt new file mode 100644 index 00000000..fc733810 --- /dev/null +++ b/plugin-build/plugin/src/test/kotlin/io/github/kdroidfilter/nucleusnativeaccess/plugin/codegen/RustBridgeGeneratorTest.kt @@ -0,0 +1,1166 @@ +package io.github.kdroidfilter.nucleusnativeaccess.plugin.codegen + +import io.github.kdroidfilter.nucleusnativeaccess.plugin.ir.* +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class RustBridgeGeneratorTest { + + private val simpleModule = KneModule( + libName = "calculator", + packages = setOf("calculator"), + classes = listOf( + KneClass( + simpleName = "Calculator", + fqName = "calculator.Calculator", + constructor = KneConstructor( + params = listOf( + KneParam("initial_value", KneType.INT), + KneParam("name", KneType.STRING), + ) + ), + methods = listOf( + KneFunction("add", listOf(KneParam("n", KneType.INT)), KneType.INT), + KneFunction("get_value", emptyList(), KneType.INT), + KneFunction("get_name", emptyList(), KneType.STRING), + KneFunction("reset", listOf(KneParam("new_value", KneType.INT)), KneType.INT), + ), + properties = emptyList(), + ), + ), + dataClasses = emptyList(), + enums = listOf( + KneEnum( + simpleName = "Operation", + fqName = "calculator.Operation", + entries = listOf("Add", "Subtract", "Multiply", "Divide"), + ) + ), + functions = listOf( + KneFunction( + name = "compute", + params = listOf( + KneParam("a", KneType.INT), + KneParam("b", KneType.INT), + KneParam("op", KneType.ENUM("calculator.Operation", "Operation")), + ), + returnType = KneType.INT, + ), + KneFunction( + name = "greet", + params = listOf(KneParam("name", KneType.STRING)), + returnType = KneType.STRING, + ), + ), + ) + + private lateinit var code: String + + @Before + fun setUp() { + code = RustBridgeGenerator().generate(simpleModule) + } + + // --- Error infrastructure --- + + @Test + fun `generates thread_local error storage`() { + assertContains("KNE_LAST_ERROR") + assertContains("RefCell>") + } + + @Test + fun `generates hasError function`() { + assertContains("fn calculator_kne_hasError") + assertContains("#[no_mangle]") + assertContains("pub extern \"C\"") + } + + @Test + fun `generates getLastError function`() { + assertContains("fn calculator_kne_getLastError") + } + + // --- Constructor --- + + @Test + fun `generates constructor for Calculator`() { + assertContains("fn calculator_Calculator_new") + assertContains("initial_value: i32") + assertContains("-> i64") + assertContains("Box::into_raw") + } + + @Test + fun `constructor has String param as pointer`() { + // String params should be *const c_char in the extern "C" fn + assertContains("name: *const c_char") + } + + // --- Dispose --- + + @Test + fun `generates dispose for Calculator`() { + assertContains("fn calculator_Calculator_dispose") + assertContains("handle: i64") + assertContains("Box::from_raw") + } + + // --- Methods --- + + @Test + fun `generates add method`() { + assertContains("fn calculator_Calculator_add") + assertContains("handle: i64") + assertContains("n: i32") + assertContains("-> i32") + } + + @Test + fun `generates get_value method`() { + assertContains("fn calculator_Calculator_get_value") + assertContains("handle: i64") + assertContains("-> i32") + } + + @Test + fun `generates get_name method with buffer pattern`() { + assertContains("fn calculator_Calculator_get_name") + assertContains("out_buf: *mut u8") + assertContains("out_buf_len: i32") + assertContains("-> i32") // returns byte count + } + + @Test + fun `generates reset method`() { + assertContains("fn calculator_Calculator_reset") + assertContains("new_value: i32") + } + + // --- Enum --- + + @Test + fun `generates enum name bridge`() { + assertContains("fn calculator_Operation_name") + } + + @Test + fun `generates enum count bridge`() { + assertContains("fn calculator_Operation_count") + assertContains("-> i32") + } + + // --- Top-level functions --- + + @Test + fun `generates compute function`() { + assertContains("fn calculator_compute") + assertContains("a: i32") + assertContains("b: i32") + assertContains("op: i32") // enum passed as ordinal + } + + @Test + fun `generates greet function with string output`() { + assertContains("fn calculator_greet") + assertContains("name: *const c_char") + assertContains("out_buf: *mut u8") + } + + // --- Panic catching --- + + @Test + fun `wraps bodies in catch_unwind`() { + assertContains("catch_unwind") + } + + // --- Code validity --- + + @Test + fun `generates valid use statements`() { + assertContains("use std::cell::RefCell") + assertContains("use std::ffi::CStr") + } + + @Test + fun `all extern C functions are no_mangle`() { + // Count #[no_mangle] and extern "C" — they should match + val noMangle = code.split("#[no_mangle]").size - 1 + val externC = code.split("pub extern \"C\"").size - 1 + assertEquals("Each extern C fn should have #[no_mangle]", noMangle, externC) + } + + @Test + fun `generates slice param as pointer plus length`() { + val moduleWithSlice = simpleModule.copy( + functions = simpleModule.functions + KneFunction( + name = "process_bytes", + params = listOf(KneParam("data", KneType.BYTE_ARRAY, isBorrowed = true)), + returnType = KneType.INT, + ) + ) + val sliceCode = RustBridgeGenerator().generate(moduleWithSlice) + assertTrue(sliceCode.contains("data_ptr: *const u8")) + assertTrue(sliceCode.contains("data_len: i32")) + assertTrue(sliceCode.contains("std::slice::from_raw_parts")) + } + + @Test + fun `generates nullable param conversion for Option INT`() { + val moduleWithNullable = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "add_optional", + params = listOf(KneParam("value", KneType.NULLABLE(KneType.INT))), + returnType = KneType.INT, + isMutating = true, + ) + )) + ) + val nullableCode = RustBridgeGenerator().generate(moduleWithNullable) + assertTrue(nullableCode.contains("value_opt")) + assertTrue(nullableCode.contains("Option")) + assertTrue(nullableCode.contains("i64::MIN")) + } + + @Test + fun `generates nullable String param as pointer`() { + val moduleWithNullable = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "set_nickname", + params = listOf(KneParam("name", KneType.NULLABLE(KneType.STRING))), + returnType = KneType.UNIT, + isMutating = true, + ) + )) + ) + val nullableCode = RustBridgeGenerator().generate(moduleWithNullable) + assertTrue(nullableCode.contains("name: *const c_char")) + assertTrue(nullableCode.contains("name_opt")) + assertTrue(nullableCode.contains("Option")) + assertTrue(nullableCode.contains("is_null()")) + } + + // --- Suspend --- + + @Test + fun `generates suspend helpers when module has suspend functions`() { + val moduleWithSuspend = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "delayed_add", + params = listOf(KneParam("value", KneType.INT), KneParam("delay_ms", KneType.INT)), + returnType = KneType.INT, + isSuspend = true, + isMutating = true, + ) + )) + ) + val suspendCode = RustBridgeGenerator().generate(moduleWithSuspend) + assertTrue(suspendCode.contains("fn calculator_kne_cancelJob")) + assertTrue(suspendCode.contains("fn calculator_kne_disposeRef")) + assertTrue(suspendCode.contains("fn calculator_kne_readStringRef")) + } + + @Test + fun `does not generate suspend helpers when no suspend functions`() { + assertFalse(code.contains("kne_cancelJob")) + assertFalse(code.contains("kne_disposeRef")) + assertFalse(code.contains("kne_readStringRef")) + } + + @Test + fun `generates suspend method bridge with cont_ptr and exc_ptr`() { + val moduleWithSuspend = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "delayed_add", + params = listOf(KneParam("value", KneType.INT), KneParam("delay_ms", KneType.INT)), + returnType = KneType.INT, + isSuspend = true, + isMutating = true, + ) + )) + ) + val suspendCode = RustBridgeGenerator().generate(moduleWithSuspend) + assertTrue(suspendCode.contains("fn calculator_Calculator_delayed_add")) + assertTrue(suspendCode.contains("cont_ptr: i64")) + assertTrue(suspendCode.contains("exc_ptr: i64")) + assertTrue(suspendCode.contains("cancel_out: *mut i64")) + assertTrue(suspendCode.contains("std::thread::spawn")) + assertTrue(suspendCode.contains("AtomicBool")) + } + + @Test + fun `suspend method with String return uses Box String handle`() { + val moduleWithSuspend = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "delayed_describe", + params = listOf(KneParam("delay_ms", KneType.INT)), + returnType = KneType.STRING, + isSuspend = true, + ) + )) + ) + val suspendCode = RustBridgeGenerator().generate(moduleWithSuspend) + assertTrue(suspendCode.contains("fn calculator_Calculator_delayed_describe")) + assertTrue(suspendCode.contains("Box::into_raw(Box::new(value))")) + } + + @Test + fun `suspend method with Unit return calls cont_fn with zero`() { + val moduleWithSuspend = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "delayed_noop", + params = listOf(KneParam("delay_ms", KneType.INT)), + returnType = KneType.UNIT, + isSuspend = true, + ) + )) + ) + val suspendCode = RustBridgeGenerator().generate(moduleWithSuspend) + assertTrue(suspendCode.contains("cont_fn(1, 0i64)")) + } + + // --- Flow --- + + @Test + fun `generates flow method bridge with next_ptr error_ptr complete_ptr`() { + val moduleWithFlow = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "count_up", + params = listOf(KneParam("max", KneType.INT), KneParam("interval_ms", KneType.INT)), + returnType = KneType.FLOW(KneType.INT), + ) + )) + ) + val flowCode = RustBridgeGenerator().generate(moduleWithFlow) + assertTrue(flowCode.contains("fn calculator_Calculator_count_up")) + assertTrue(flowCode.contains("next_ptr: i64")) + assertTrue(flowCode.contains("error_ptr: i64")) + assertTrue(flowCode.contains("complete_ptr: i64")) + assertTrue(flowCode.contains("cancel_out: *mut i64")) + assertTrue(flowCode.contains("std::thread::spawn")) + assertTrue(flowCode.contains("AtomicBool")) + assertTrue(flowCode.contains("next_fn(item as i64)")) + assertTrue(flowCode.contains("complete_fn()")) + } + + @Test + fun `generates flow with String element using Box`() { + val moduleWithFlow = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "score_labels", + params = listOf(KneParam("count", KneType.INT)), + returnType = KneType.FLOW(KneType.STRING), + ) + )) + ) + val flowCode = RustBridgeGenerator().generate(moduleWithFlow) + assertTrue(flowCode.contains("fn calculator_Calculator_score_labels")) + assertTrue(flowCode.contains("Box::into_raw(Box::new(item)) as i64")) + } + + @Test + fun `flow generates suspend helpers for cancelJob and disposeRef`() { + val moduleWithFlow = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "count_up", + params = listOf(KneParam("max", KneType.INT)), + returnType = KneType.FLOW(KneType.INT), + ) + )) + ) + val flowCode = RustBridgeGenerator().generate(moduleWithFlow) + assertTrue(flowCode.contains("fn calculator_kne_cancelJob")) + assertTrue(flowCode.contains("fn calculator_kne_disposeRef")) + assertTrue(flowCode.contains("fn calculator_kne_readStringRef")) + } + + // --- MAP return --- + + @Test + fun `generates MAP return with String keys and Int values for instance method`() { + val moduleWithMap = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "get_scores", + params = emptyList(), + returnType = KneType.MAP(KneType.STRING, KneType.INT), + ) + )) + ) + val mapCode = RustBridgeGenerator().generate(moduleWithMap) + // Signature includes dual buffers and max count + assertTrue("Should have out_keys param", mapCode.contains("out_keys: *mut u8")) + assertTrue("Should have out_keys_len param", mapCode.contains("out_keys_len: i32")) + assertTrue("Should have out_values param", mapCode.contains("out_values: *mut i32")) + assertTrue("Should have out_max_len param", mapCode.contains("out_max_len: i32")) + assertTrue("Should return i32 count", mapCode.contains(") -> i32")) + // Body serializes keys as null-terminated and values as i32 + assertTrue("Should iterate keys", mapCode.contains(".keys()")) + assertTrue("Should iterate values", mapCode.contains(".values()")) + assertTrue("Should write null terminator", mapCode.contains("= 0;")) + } + + @Test + fun `generates MAP return with Int keys and Long values for companion method`() { + val moduleWithMap = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + companionMethods = listOf(KneFunction( + name = "lookup_table", + params = emptyList(), + returnType = KneType.MAP(KneType.INT, KneType.LONG), + )) + )) + ) + val mapCode = RustBridgeGenerator().generate(moduleWithMap) + assertTrue("Should have companion fn", mapCode.contains("companion_lookup_table")) + assertTrue("Should have out_keys param", mapCode.contains("out_keys: *mut i32")) + assertTrue("Should have out_values param", mapCode.contains("out_values: *mut i64")) + assertTrue("Should have out_max_len param", mapCode.contains("out_max_len: i32")) + } + + @Test + fun `generates MAP property getter with dual buffers`() { + val moduleWithMapProp = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + properties = listOf(KneProperty( + name = "scores", + type = KneType.MAP(KneType.STRING, KneType.INT), + mutable = false, + )) + )) + ) + val mapCode = RustBridgeGenerator().generate(moduleWithMapProp) + assertTrue("Should generate getter for MAP property", mapCode.contains("get_scores")) + assertTrue("Should have out_keys param", mapCode.contains("out_keys: *mut u8")) + assertTrue("Should have out_values param", mapCode.contains("out_values: *mut i32")) + assertTrue("Should have out_max_len param", mapCode.contains("out_max_len: i32")) + } + + // --- SET return --- + + @Test + fun `generates SET return as list with out_buf`() { + val moduleWithSet = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "get_tags", + params = emptyList(), + returnType = KneType.SET(KneType.STRING), + ) + )) + ) + val setCode = RustBridgeGenerator().generate(moduleWithSet) + assertTrue("Should generate SET method", setCode.contains("get_tags")) + assertTrue("Should have out_buf param", setCode.contains("out_buf: *mut u8")) + assertTrue("Should return i32 count", setCode.contains("-> i32")) + } + + // --- Option return --- + + @Test + fun `generates nullable DataClass return with out-params and i32 presence flag`() { + val dc = KneType.DATA_CLASS( + fqName = "calculator.Point", + simpleName = "Point", + fields = listOf( + KneParam("x", KneType.DOUBLE), + KneParam("y", KneType.DOUBLE), + ) + ) + val moduleWithNullDc = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "find_point", + params = emptyList(), + returnType = KneType.NULLABLE(dc), + ) + )) + ) + val nullDcCode = RustBridgeGenerator().generate(moduleWithNullDc) + assertTrue("Should have find_point fn", nullDcCode.contains("find_point")) + assertTrue("Should have out_x param", nullDcCode.contains("out_x: *mut f64")) + assertTrue("Should have out_y param", nullDcCode.contains("out_y: *mut f64")) + assertTrue("Should return i32", nullDcCode.contains("-> i32")) + assertTrue("Should match Some", nullDcCode.contains("Some(v)")) + assertTrue("Should return 1 on Some", nullDcCode.contains("1i32")) + assertTrue("Should return 0 on None", nullDcCode.contains("0i32")) + } + + @Test + fun `generates nullable DataClass with String field return`() { + val dc = KneType.DATA_CLASS( + fqName = "calculator.NamedValue", + simpleName = "NamedValue", + fields = listOf( + KneParam("name", KneType.STRING), + KneParam("value", KneType.INT), + ) + ) + val moduleWithNullDcStr = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + companionMethods = listOf(KneFunction( + name = "find_named", + params = emptyList(), + returnType = KneType.NULLABLE(dc), + )) + )) + ) + val code = RustBridgeGenerator().generate(moduleWithNullDcStr) + assertTrue("Should have companion fn", code.contains("companion_find_named")) + assertTrue("Should have out_name string param", code.contains("out_name: *mut u8")) + assertTrue("Should have out_name_len param", code.contains("out_name_len: i32")) + assertTrue("Should have out_value param", code.contains("out_value: *mut i32")) + } + + // ── Object / Interface / SealedEnum returns ──────────────────────────── + + @Test + fun `generates method returning Object reference`() { + val m = moduleWith(KneFunction("get_child", emptyList(), KneType.OBJECT("calculator.Calculator", "Calculator"))) + assertIn(m, "fn calculator_Calculator_get_child") + assertIn(m, "-> i64") + assertIn(m, "Box::into_raw") + } + + @Test + fun `generates method returning borrowed Object`() { + val m = moduleWith(KneFunction("peek_child", emptyList(), KneType.OBJECT("calculator.Calculator", "Calculator"), returnsBorrowed = true)) + assertIn(m, "as *const _ as i64") + } + + @Test + fun `generates method with Object param`() { + val m = moduleWith(KneFunction("set_child", listOf(KneParam("child", KneType.OBJECT("calculator.Calculator", "Calculator"))), KneType.UNIT)) + assertIn(m, "child: i64") + } + + @Test + fun `generates method returning Interface`() { + val mod = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction("get_measurable", emptyList(), KneType.INTERFACE("calculator.Measurable", "Measurable")) + )) + ) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("-> i64")) + assertTrue(c.contains("Box::into_raw")) + } + + // ── Sealed enum bridges ───────────────────────────────────────────────── + + @Test + fun `generates sealed enum dispose bridge`() { + val mod = moduleWithSealed() + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_AppResult_dispose")) + assertTrue(c.contains("Box::from_raw(handle as *mut AppResult)")) + } + + @Test + fun `generates sealed enum tag bridge`() { + val c = RustBridgeGenerator().generate(moduleWithSealed()) + assertTrue(c.contains("fn calculator_AppResult_tag")) + assertTrue(c.contains("-> i32")) + assertTrue(c.contains("AppResult::Ok")) + assertTrue(c.contains("AppResult::Error")) + } + + @Test + fun `generates sealed enum variant field getter`() { + val c = RustBridgeGenerator().generate(moduleWithSealed()) + // Ok variant has a value:i32 field + assertTrue(c.contains("fn calculator_AppResult_Ok_get_value")) + } + + @Test + fun `generates sealed enum string variant field getter with buffer`() { + val c = RustBridgeGenerator().generate(moduleWithSealed()) + // Error variant has a message:String field + assertTrue(c.contains("fn calculator_AppResult_Error_get_message")) + assertTrue(c.contains("out_buf: *mut u8")) + } + + @Test + fun `generates method returning sealed enum`() { + val mod = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction("try_op", emptyList(), KneType.SEALED_ENUM("calculator.AppResult", "AppResult")) + )), + sealedEnums = listOf(appResultSealed()) + ) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Calculator_try_op")) + assertTrue(c.contains("-> i64")) + } + + // ── Properties ────────────────────────────────────────────────────────── + + @Test + fun `generates property getter bridge`() { + val mod = simpleModule.copy(classes = listOf(simpleModule.classes.first().copy( + properties = listOf(KneProperty("scale", KneType.DOUBLE, mutable = false)) + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Calculator_get_scale")) + assertTrue(c.contains("handle: i64")) + assertTrue(c.contains("-> f64")) + } + + @Test + fun `generates property setter bridge for mutable property`() { + val mod = simpleModule.copy(classes = listOf(simpleModule.classes.first().copy( + properties = listOf(KneProperty("scale", KneType.DOUBLE, mutable = true)) + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Calculator_get_scale")) + assertTrue(c.contains("fn calculator_Calculator_set_scale")) + assertTrue(c.contains("value: f64")) + } + + @Test + fun `generates String property getter with buffer`() { + val mod = simpleModule.copy(classes = listOf(simpleModule.classes.first().copy( + properties = listOf(KneProperty("label", KneType.STRING, mutable = false)) + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Calculator_get_label")) + assertTrue(c.contains("out_buf: *mut u8")) + assertTrue(c.contains("out_buf_len: i32")) + } + + @Test + fun `generates Boolean property getter`() { + val mod = simpleModule.copy(classes = listOf(simpleModule.classes.first().copy( + properties = listOf(KneProperty("enabled", KneType.BOOLEAN, mutable = true)) + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Calculator_get_enabled")) + assertTrue(c.contains("-> i32")) + assertTrue(c.contains("fn calculator_Calculator_set_enabled")) + } + + // ── Companion methods ─────────────────────────────────────────────────── + + @Test + fun `generates companion method with params`() { + val mod = simpleModule.copy(classes = listOf(simpleModule.classes.first().copy( + companionMethods = listOf(KneFunction("from_value", listOf(KneParam("v", KneType.INT)), KneType.OBJECT("calculator.Calculator", "Calculator"))) + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Calculator_companion_from_value")) + assertTrue(c.contains("v: i32")) + assertTrue(c.contains("-> i64")) + assertTrue(c.contains("Calculator::from_value")) + } + + @Test + fun `generates companion method returning String`() { + val mod = simpleModule.copy(classes = listOf(simpleModule.classes.first().copy( + companionMethods = listOf(KneFunction("version", emptyList(), KneType.STRING)) + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Calculator_companion_version")) + assertTrue(c.contains("out_buf: *mut u8")) + } + + // ── canFail / Result returns ──────────────────────────────────────────── + + @Test + fun `generates canFail method with match Ok Err pattern`() { + val m = moduleWith(KneFunction("divide", listOf(KneParam("b", KneType.INT)), KneType.INT, canFail = true)) + assertIn(m, "match") + assertIn(m, "Ok(result)") + assertIn(m, "Err(e)") + assertIn(m, "kne_set_error") + } + + @Test + fun `generates canFail method returning Object`() { + val m = moduleWith(KneFunction("try_create", emptyList(), KneType.OBJECT("calculator.Calculator", "Calculator"), canFail = true)) + assertIn(m, "Ok(result)") + assertIn(m, "Box::into_raw") + assertIn(m, "Err(e)") + } + + // ── unsafe methods ────────────────────────────────────────────────────── + + @Test + fun `generates unsafe wrapper for unsafe methods`() { + val m = moduleWith(KneFunction("raw_op", emptyList(), KneType.INT, isUnsafe = true)) + assertIn(m, "unsafe {") + } + + // ── OWNED vs BORROWED receiver ────────────────────────────────────────── + + @Test + fun `generates ptr_read for OWNED receiver`() { + val m = moduleWith(KneFunction("consume", emptyList(), KneType.INT, receiverKind = KneReceiverKind.OWNED)) + assertIn(m, "std::ptr::read(handle as *const Calculator)") + } + + @Test + fun `generates shared ref for BORROWED_SHARED receiver`() { + val m = moduleWith(KneFunction("peek", emptyList(), KneType.INT, receiverKind = KneReceiverKind.BORROWED_SHARED)) + assertIn(m, "&*(handle as *const Calculator)") + } + + @Test + fun `generates mut ref for BORROWED_MUT receiver`() { + val m = moduleWith(KneFunction("mutate", emptyList(), KneType.UNIT, receiverKind = KneReceiverKind.BORROWED_MUT)) + assertIn(m, "&mut *(handle as *mut Calculator)") + } + + // ── BYTE_ARRAY return ─────────────────────────────────────────────────── + + @Test + fun `generates BYTE_ARRAY return with buffer pattern`() { + val m = moduleWith(KneFunction("to_bytes", emptyList(), KneType.BYTE_ARRAY)) + assertIn(m, "out_buf: *mut u8") + assertIn(m, "out_buf_len: i32") + assertIn(m, "-> i32") + assertIn(m, "copy_nonoverlapping") + } + + // ── LIST returns for various element types ────────────────────────────── + + @Test + fun `generates LIST Long return`() { + val m = moduleWith(KneFunction("get_ids", emptyList(), KneType.LIST(KneType.LONG))) + assertIn(m, "out_buf: *mut u8") + assertIn(m, "*(out_buf as *mut i64)") + } + + @Test + fun `generates LIST Double return`() { + val m = moduleWith(KneFunction("get_values", emptyList(), KneType.LIST(KneType.DOUBLE))) + assertIn(m, "*(out_buf as *mut f64)") + } + + @Test + fun `generates LIST String return with null-terminated serialization`() { + val m = moduleWith(KneFunction("get_names", emptyList(), KneType.LIST(KneType.STRING))) + assertIn(m, "out_buf: *mut u8") + assertIn(m, "as_bytes()") + assertIn(m, "= 0;") + } + + @Test + fun `generates LIST Object return with i64 handles`() { + val m = moduleWith(KneFunction("get_children", emptyList(), KneType.LIST(KneType.OBJECT("calculator.Calculator", "Calculator")))) + assertIn(m, "*(out_buf as *mut i64)") + assertIn(m, "as *const _ as i64") + } + + @Test + fun `generates LIST Boolean return`() { + val m = moduleWith(KneFunction("get_flags", emptyList(), KneType.LIST(KneType.BOOLEAN))) + assertIn(m, "*(out_buf as *mut i32)") + assertIn(m, "if *v { 1 } else { 0 }") + } + + @Test + fun `generates LIST Enum return`() { + val m = moduleWith(KneFunction("get_ops", emptyList(), KneType.LIST(KneType.ENUM("calculator.Operation", "Operation")))) + assertIn(m, "*(out_buf as *mut i32)") + assertIn(m, "v.clone() as i32") + } + + @Test + fun `generates SET Long return mapped to list`() { + val m = moduleWith(KneFunction("get_unique_ids", emptyList(), KneType.SET(KneType.LONG))) + assertIn(m, "*(out_buf as *mut i64)") + } + + // ── MAP key/value type diversity ──────────────────────────────────────── + + @Test + fun `generates MAP Long to Double return`() { + val m = moduleWith(KneFunction("get_measurements", emptyList(), KneType.MAP(KneType.LONG, KneType.DOUBLE))) + assertIn(m, "out_keys: *mut i64") + assertIn(m, "out_values: *mut f64") + } + + @Test + fun `generates MAP String to String return`() { + val m = moduleWith(KneFunction("get_env", emptyList(), KneType.MAP(KneType.STRING, KneType.STRING))) + assertIn(m, "out_keys: *mut u8") + assertIn(m, "out_keys_len: i32") + assertIn(m, "out_values: *mut u8") + assertIn(m, "out_values_len: i32") + } + + @Test + fun `generates MAP Int to Object return`() { + val m = moduleWith(KneFunction("get_index", emptyList(), KneType.MAP(KneType.INT, KneType.OBJECT("calculator.Calculator", "Calculator")))) + assertIn(m, "out_keys: *mut i32") + assertIn(m, "out_values: *mut i64") + } + + // ── Nullable returns for all types ─────────────────────────────────────── + + @Test + fun `generates nullable Long return with i64 MIN sentinel`() { + val m = moduleWith(KneFunction("get_count", emptyList(), KneType.NULLABLE(KneType.LONG))) + assertIn(m, "-> i64") + assertIn(m, "i64::MIN") + } + + @Test + fun `generates nullable Double return with bits encoding`() { + val m = moduleWith(KneFunction("get_ratio", emptyList(), KneType.NULLABLE(KneType.DOUBLE))) + assertIn(m, "to_ne_bytes") + } + + @Test + fun `generates nullable Float return`() { + val m = moduleWith(KneFunction("get_scale", emptyList(), KneType.NULLABLE(KneType.FLOAT))) + assertIn(m, "to_bits") + } + + @Test + fun `generates nullable Boolean return with minus one sentinel`() { + val m = moduleWith(KneFunction("is_ready", emptyList(), KneType.NULLABLE(KneType.BOOLEAN))) + assertIn(m, "Some(true) => 1") + assertIn(m, "Some(false) => 0") + assertIn(m, "None => -1") + } + + @Test + fun `generates nullable Enum return`() { + val m = moduleWith(KneFunction("get_op", emptyList(), KneType.NULLABLE(KneType.ENUM("calculator.Operation", "Operation")))) + assertIn(m, "Some(v) => v as i32") + assertIn(m, "None => -1") + } + + @Test + fun `generates nullable Object return with zero sentinel`() { + val m = moduleWith(KneFunction("find_child", emptyList(), KneType.NULLABLE(KneType.OBJECT("calculator.Calculator", "Calculator")))) + assertIn(m, "Some(v) => Box::into_raw") + assertIn(m, "None => 0i64") + } + + @Test + fun `generates nullable String return with minus one sentinel`() { + val m = moduleWith(KneFunction("get_label", emptyList(), KneType.NULLABLE(KneType.STRING))) + assertIn(m, "Some(ref s)") + assertIn(m, "None => -1") + } + + @Test + fun `generates nullable Byte return`() { + val m = moduleWith(KneFunction("get_flag", emptyList(), KneType.NULLABLE(KneType.BYTE))) + assertIn(m, "i32::MIN") + } + + @Test + fun `generates nullable Short return`() { + val m = moduleWith(KneFunction("get_tag", emptyList(), KneType.NULLABLE(KneType.SHORT))) + assertIn(m, "i32::MIN") + } + + // ── Data class return field types ──────────────────────────────────────── + + @Test + fun `generates DataClass return with Long and Boolean fields`() { + val dc = KneType.DATA_CLASS("calculator.Stats", "Stats", listOf( + KneParam("count", KneType.LONG), + KneParam("active", KneType.BOOLEAN), + )) + val m = moduleWith(KneFunction("get_stats", emptyList(), dc)) + assertIn(m, "out_count: *mut i64") + assertIn(m, "out_active: *mut i32") + assertIn(m, "-> ()") + } + + @Test + fun `generates DataClass return with Enum field`() { + val dc = KneType.DATA_CLASS("calculator.Entry", "Entry", listOf( + KneParam("op", KneType.ENUM("calculator.Operation", "Operation")), + KneParam("value", KneType.INT), + )) + val m = moduleWith(KneFunction("last_entry", emptyList(), dc)) + assertIn(m, "out_op: *mut i32") + assertIn(m, "out_value: *mut i32") + } + + @Test + fun `generates DataClass return with Float field`() { + val dc = KneType.DATA_CLASS("calculator.Coord", "Coord", listOf( + KneParam("lat", KneType.FLOAT), + KneParam("lng", KneType.FLOAT), + )) + val m = moduleWith(KneFunction("get_coord", emptyList(), dc)) + assertIn(m, "out_lat: *mut f32") + assertIn(m, "out_lng: *mut f32") + } + + // ── Struct literal constructor ─────────────────────────────────────────── + + @Test + fun `generates struct literal constructor`() { + val mod = simpleModule.copy(classes = listOf(KneClass( + simpleName = "Point", + fqName = "calculator.Point", + constructor = KneConstructor( + params = listOf(KneParam("x", KneType.DOUBLE), KneParam("y", KneType.DOUBLE)), + kind = KneConstructorKind.STRUCT_LITERAL, + ), + methods = emptyList(), + properties = emptyList(), + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Point_new")) + assertTrue(c.contains("Point { x:")) + } + + @Test + fun `generates function constructor`() { + assertContains("Calculator::new(") + } + + @Test + fun `does not generate constructor for NONE kind`() { + val mod = simpleModule.copy(classes = listOf(KneClass( + simpleName = "Singleton", + fqName = "calculator.Singleton", + constructor = KneConstructor(params = emptyList(), kind = KneConstructorKind.NONE), + methods = listOf(KneFunction("value", emptyList(), KneType.INT)), + properties = emptyList(), + ))) + val c = RustBridgeGenerator().generate(mod) + assertFalse(c.contains("fn calculator_Singleton_new")) + assertTrue(c.contains("fn calculator_Singleton_value")) + } + + // ── canFail constructor ───────────────────────────────────────────────── + + @Test + fun `generates canFail constructor with Result pattern`() { + val mod = simpleModule.copy(classes = listOf(KneClass( + simpleName = "Parser", + fqName = "calculator.Parser", + constructor = KneConstructor( + params = listOf(KneParam("input", KneType.STRING)), + kind = KneConstructorKind.FUNCTION, + canFail = true, + ), + methods = emptyList(), + properties = emptyList(), + ))) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Parser_new")) + assertTrue(c.contains("Ok(result)")) + assertTrue(c.contains("Err(e)")) + assertTrue(c.contains("kne_set_error")) + } + + // ── no_mangle count matches across all additions ──────────────────────── + + @Test + fun `no_mangle and extern C counts match for module with sealed enum`() { + val c = RustBridgeGenerator().generate(moduleWithSealed()) + val noMangle = c.split("#[no_mangle]").size - 1 + val externC = c.split("pub extern \"C\"").size - 1 + assertEquals("Each extern C fn should have #[no_mangle]", noMangle, externC) + } + + // ── Helpers ────────────────────────────────────────────────────────────── + + /** Create a module with one extra method added to the Calculator class. */ + private fun moduleWith(fn: KneFunction): String { + val mod = simpleModule.copy( + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + fn + )) + ) + return RustBridgeGenerator().generate(mod) + } + + private fun assertIn(code: String, substring: String) { + assertTrue("Generated code should contain '$substring'", code.contains(substring)) + } + + private fun appResultSealed() = KneSealedEnum( + simpleName = "AppResult", + fqName = "calculator.AppResult", + variants = listOf( + KneSealedVariant("Ok", listOf(KneParam("value", KneType.INT)), isTuple = true), + KneSealedVariant("Error", listOf(KneParam("message", KneType.STRING)), isTuple = true), + KneSealedVariant("Nothing", emptyList()), + ) + ) + + private fun moduleWithSealed() = simpleModule.copy( + sealedEnums = listOf(appResultSealed()) + ) + + @Test + fun `generates sealed variant LIST field getter with buffer`() { + val sealed = KneSealedEnum( + simpleName = "Result", + fqName = "test.Result", + variants = listOf( + KneSealedVariant("Success", listOf(KneParam("values", KneType.LIST(KneType.INT))), isTuple = true), + ) + ) + val mod = simpleModule.copy(sealedEnums = listOf(sealed)) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Result_Success_get_values")) + assertTrue(c.contains("out_buf: *mut u8, out_buf_len: i32") || c.contains("out_buf: *mut i32, out_buf_len: i32")) + assertTrue(c.contains("-> i32")) + } + + @Test + fun `generates sealed variant SET field getter with buffer`() { + val sealed = KneSealedEnum( + simpleName = "Result", + fqName = "test.Result", + variants = listOf( + KneSealedVariant("Success", listOf(KneParam("tags", KneType.SET(KneType.STRING))), isTuple = true), + ) + ) + val mod = simpleModule.copy(sealedEnums = listOf(sealed)) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Result_Success_get_tags")) + assertTrue(c.contains("out_buf: *mut u8, out_buf_len: i32")) + assertTrue(c.contains("-> i32")) + } + + @Test + fun `generates sealed variant MAP field getter with dual buffers`() { + val sealed = KneSealedEnum( + simpleName = "Result", + fqName = "test.Result", + variants = listOf( + KneSealedVariant("Data", listOf(KneParam("mapping", KneType.MAP(KneType.STRING, KneType.INT))), isTuple = true), + ) + ) + val mod = simpleModule.copy(sealedEnums = listOf(sealed)) + val c = RustBridgeGenerator().generate(mod) + assertTrue(c.contains("fn calculator_Result_Data_get_mapping")) + assertTrue(c.contains("out_keys: *mut u8, out_keys_len: i32")) + assertTrue(c.contains("out_values: *mut i32")) + assertTrue(c.contains("out_max_len: i32")) + assertTrue(c.contains("-> i32")) + } + + // ── Nullable collection returns ───────────────────────────────────────── + + @Test + fun `generates method returning nullable LIST`() { + val c = moduleWith(KneFunction("get_items", emptyList(), KneType.NULLABLE(KneType.LIST(KneType.INT)))) + assertTrue("Should generate function", c.contains("fn calculator_Calculator_get_items")) + assertTrue("Should have out_buf param", c.contains("out_buf: *mut u8")) + assertTrue("Should return i32", c.contains("-> i32")) + assertTrue("Should handle None", c.contains("None => -1")) + } + + @Test + fun `generates method returning nullable SET`() { + val c = moduleWith(KneFunction("get_tags", emptyList(), KneType.NULLABLE(KneType.SET(KneType.STRING)))) + assertTrue("Should generate function", c.contains("fn calculator_Calculator_get_tags")) + assertTrue("Should have out_buf param", c.contains("out_buf: *mut u8")) + assertTrue("Should return i32", c.contains("-> i32")) + assertTrue("Should handle None", c.contains("None => -1")) + } + + @Test + fun `generates method returning nullable MAP`() { + val c = moduleWith(KneFunction("get_mapping", emptyList(), KneType.NULLABLE(KneType.MAP(KneType.STRING, KneType.INT)))) + assertTrue("Should generate function", c.contains("fn calculator_Calculator_get_mapping")) + assertTrue("Should have out_keys param", c.contains("out_keys: *mut u8")) + assertTrue("Should have out_values param", c.contains("out_values: *mut i32")) + assertTrue("Should have out_max_len param", c.contains("out_max_len: i32")) + assertTrue("Should return i32", c.contains("-> i32")) + assertTrue("Should handle None", c.contains("None => -1")) + } + + @Test + fun `nullable LIST Some branch writes to buffer`() { + val c = moduleWith(KneFunction("get_items", emptyList(), KneType.NULLABLE(KneType.LIST(KneType.INT)))) + assertTrue("Should have Some branch", c.contains("Some(v)")) + assertTrue("Should write len", c.contains("v.len() as i32")) + } + + // --- impl Trait return --- + + @Test + fun `generates boxing for impl Trait return in top-level function`() { + val moduleWithImplTrait = simpleModule.copy( + interfaces = listOf( + KneInterface( + simpleName = "Describable", + fqName = "calculator.Describable", + methods = emptyList(), + properties = emptyList(), + ) + ), + classes = simpleModule.classes + KneClass( + simpleName = "DynDescribable", + fqName = "calculator.DynDescribable", + constructor = KneConstructor(emptyList(), KneConstructorKind.NONE), + methods = emptyList(), + properties = emptyList(), + interfaces = listOf("calculator.Describable"), + isDynTrait = true, + rustTypeName = "Box", + ), + functions = simpleModule.functions + KneFunction( + name = "get_describable", + params = emptyList(), + returnType = KneType.INTERFACE("calculator.Describable", "Describable"), + returnRustType = "impl dyn Describable", + ), + ) + val implCode = RustBridgeGenerator().generate(moduleWithImplTrait) + // Should generate bridge function + assertTrue("Should have bridge fn", implCode.contains("fn calculator_get_describable")) + // Should box the result into Box + assertTrue("Should box into dyn Trait", implCode.contains("Box")) + assertTrue("Should box result", implCode.contains("Box::new(result)")) + // Should double-box for handle + assertTrue("Should produce handle", implCode.contains("Box::into_raw")) + } + + @Test + fun `generates boxing for impl Trait return in struct method`() { + val moduleWithImplTrait = simpleModule.copy( + interfaces = listOf( + KneInterface( + simpleName = "Describable", + fqName = "calculator.Describable", + methods = emptyList(), + properties = emptyList(), + ) + ), + classes = listOf(simpleModule.classes.first().copy( + methods = simpleModule.classes.first().methods + KneFunction( + name = "describe", + params = emptyList(), + returnType = KneType.INTERFACE("calculator.Describable", "Describable"), + returnRustType = "impl dyn Describable", + ) + )), + ) + val implCode = RustBridgeGenerator().generate(moduleWithImplTrait) + assertTrue("Should have method bridge", implCode.contains("fn calculator_Calculator_describe")) + assertTrue("Should box into dyn Trait", implCode.contains("Box")) + } + + private fun assertContains(substring: String) { + assertTrue( + "Generated code should contain '$substring'.\nGenerated code:\n${code.take(3000)}", + code.contains(substring) + ) + } +} diff --git a/plugin-build/plugin/src/test/resources/rustdoc-fixtures/cross-crate-reexport.json b/plugin-build/plugin/src/test/resources/rustdoc-fixtures/cross-crate-reexport.json new file mode 100644 index 00000000..d5c9783c --- /dev/null +++ b/plugin-build/plugin/src/test/resources/rustdoc-fixtures/cross-crate-reexport.json @@ -0,0 +1,163 @@ +{ + "root": 100, + "crate_id": 0, + "format_version": 56, + "paths": { + "100": {"crate_id": 0, "path": ["mylib"], "kind": "module"}, + "1": {"crate_id": 0, "path": ["mylib", "Camera"], "kind": "struct"}, + "50": {"crate_id": 1, "path": ["mylib_core", "Resolution"], "kind": "struct"}, + "60": {"crate_id": 1, "path": ["mylib_core", "FrameFormat"], "kind": "enum"}, + "70": {"crate_id": 1, "path": ["mylib_core", "CaptureError"], "kind": "enum"}, + "80": {"crate_id": 1, "path": ["mylib_core", "CameraFormat"], "kind": "struct"} + }, + "external_crates": { + "1": {"name": "mylib_core", "html_root_url": null} + }, + "index": { + "100": { + "id": 100, "crate_id": 0, "name": "mylib", "visibility": "public", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"module": {"is_crate": true, "items": [1], "is_stripped": false}} + }, + "1": { + "id": 1, "crate_id": 0, "name": "Camera", "visibility": "public", + "docs": "A camera device.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct": {"kind": {"plain": {"fields": [], "has_stripped_fields": true}}, "generics": {"params": [], "where_predicates": []}}} + }, + "2": { + "id": 2, "crate_id": 0, "name": "new", "visibility": "public", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"function": {"sig": { + "inputs": [["index", {"primitive": "i32"}]], + "output": {"resolved_path": {"path": "Camera", "id": 1, "args": null}}, + "is_c_variadic": false + }, "generics": {"params": [], "where_predicates": []}}} + }, + "3": { + "id": 3, "crate_id": 0, "name": "get_resolution", "visibility": "public", + "docs": "Returns the current resolution.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"function": {"sig": { + "inputs": [["self", {"borrowed_ref": {"lifetime": null, "is_mutable": false, "type": {"generic": "Self"}}}]], + "output": {"resolved_path": {"path": "Resolution", "id": 50, "args": null}}, + "is_c_variadic": false + }, "generics": {"params": [], "where_predicates": []}}} + }, + "4": { + "id": 4, "crate_id": 0, "name": "get_format", "visibility": "public", + "docs": "Returns the current frame format.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"function": {"sig": { + "inputs": [["self", {"borrowed_ref": {"lifetime": null, "is_mutable": false, "type": {"generic": "Self"}}}]], + "output": {"resolved_path": {"path": "FrameFormat", "id": 60, "args": null}}, + "is_c_variadic": false + }, "generics": {"params": [], "where_predicates": []}}} + }, + "5": { + "id": 5, "crate_id": 0, "name": "capture", "visibility": "public", + "docs": "Capture a frame.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"function": {"sig": { + "inputs": [["self", {"borrowed_ref": {"lifetime": null, "is_mutable": true, "type": {"generic": "Self"}}}]], + "output": {"resolved_path": {"path": "Result", "id": 999, "args": {"angle_bracketed": {"args": [ + {"type": {"primitive": "i32"}}, + {"type": {"resolved_path": {"path": "CaptureError", "id": 70, "args": null}}} + ], "constraints": []}}}}, + "is_c_variadic": false + }, "generics": {"params": [], "where_predicates": []}}} + }, + "10": { + "id": 10, "crate_id": 0, "name": null, "visibility": "default", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"impl": { + "is_unsafe": false, "generics": {"params": [], "where_predicates": []}, + "provided_trait_impls": [], "trait": null, "blanket_impl": null, + "for": {"resolved_path": {"path": "Camera", "id": 1, "args": null}}, + "items": [2, 3, 4, 5, 6], + "is_synthetic": false, "is_negative": false + }} + }, + "50": { + "id": 50, "crate_id": 1, "name": "Resolution", "visibility": "public", + "docs": "Display resolution.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct": {"kind": {"plain": {"fields": [51, 52], "has_stripped_fields": false}}, "generics": {"params": [], "where_predicates": []}}} + }, + "51": { + "id": 51, "crate_id": 1, "name": "width", "visibility": "public", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct_field": {"primitive": "u32"}} + }, + "52": { + "id": 52, "crate_id": 1, "name": "height", "visibility": "public", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct_field": {"primitive": "u32"}} + }, + "60": { + "id": 60, "crate_id": 1, "name": "FrameFormat", "visibility": "public", + "docs": "Pixel format for frames.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"enum": {"generics": {"params": [], "where_predicates": []}, "variants": [61, 62, 63]}} + }, + "61": { + "id": 61, "crate_id": 1, "name": "Rgb", "visibility": "default", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"variant": {"kind": "plain", "discriminant": null}} + }, + "62": { + "id": 62, "crate_id": 1, "name": "Yuv", "visibility": "default", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"variant": {"kind": "plain", "discriminant": null}} + }, + "63": { + "id": 63, "crate_id": 1, "name": "Gray", "visibility": "default", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"variant": {"kind": "plain", "discriminant": null}} + }, + "70": { + "id": 70, "crate_id": 1, "name": "CaptureError", "visibility": "public", + "docs": "Error during capture.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"enum": {"generics": {"params": [], "where_predicates": []}, "variants": [71, 72]}} + }, + "71": { + "id": 71, "crate_id": 1, "name": "DeviceLost", "visibility": "default", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"variant": {"kind": {"tuple": [73]}, "discriminant": null}} + }, + "72": { + "id": 72, "crate_id": 1, "name": "Timeout", "visibility": "default", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"variant": {"kind": "plain", "discriminant": null}} + }, + "73": { + "id": 73, "crate_id": 1, "name": null, "visibility": "default", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct_field": {"resolved_path": {"path": "String", "id": 999, "args": null}}} + }, + "6": { + "id": 6, "crate_id": 0, "name": "get_camera_format", "visibility": "public", + "docs": "Returns the full camera format.", "links": {}, "attrs": [], "deprecation": null, + "inner": {"function": {"sig": { + "inputs": [["self", {"borrowed_ref": {"lifetime": null, "is_mutable": false, "type": {"generic": "Self"}}}]], + "output": {"resolved_path": {"path": "CameraFormat", "id": 80, "args": null}}, + "is_c_variadic": false + }, "generics": {"params": [], "where_predicates": []}}} + }, + "80": { + "id": 80, "crate_id": 1, "name": "CameraFormat", "visibility": "public", + "docs": "Full camera format with resolution, pixel format, and frame rate.", + "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct": {"kind": {"plain": {"fields": [81, 82, 83], "has_stripped_fields": false}}, "generics": {"params": [], "where_predicates": []}}} + }, + "81": { + "id": 81, "crate_id": 1, "name": "resolution", "visibility": "public", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct_field": {"resolved_path": {"path": "Resolution", "id": 50, "args": null}}} + }, + "82": { + "id": 82, "crate_id": 1, "name": "format", "visibility": "public", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct_field": {"resolved_path": {"path": "FrameFormat", "id": 60, "args": null}}} + }, + "83": { + "id": 83, "crate_id": 1, "name": "frame_rate", "visibility": "public", + "docs": null, "links": {}, "attrs": [], "deprecation": null, + "inner": {"struct_field": {"primitive": "u32"}} + } + } +} diff --git a/plugin-build/plugin/src/test/resources/rustdoc-fixtures/mini-calculator.json b/plugin-build/plugin/src/test/resources/rustdoc-fixtures/mini-calculator.json new file mode 100644 index 00000000..11ea4d81 --- /dev/null +++ b/plugin-build/plugin/src/test/resources/rustdoc-fixtures/mini-calculator.json @@ -0,0 +1 @@ +{"root":95,"crate_version":"0.1.0","includes_private":false,"index":{"0":{"id":0,"crate_id":0,"name":"value","span":{"filename":"src/lib.rs","begin":[3,5],"end":[3,19]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"i32"}}},"88":{"id":88,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":50,"args":null},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[47],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"25":{"id":25,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":27,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"69":{"id":69,"crate_id":0,"name":"x","span":{"filename":"src/lib.rs","begin":[59,5],"end":[59,15]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"f64"}}},"6":{"id":6,"crate_id":0,"name":"subtract","span":{"filename":"src/lib.rs","begin":[22,5],"end":[24,6]},"visibility":"public","docs":"Subtracts n from the current value and returns the result.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["n",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"75":{"id":75,"crate_id":0,"name":null,"span":{"filename":"src/lib.rs","begin":[63,1],"end":[78,2]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":null,"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[72,73,74],"is_negative":false,"is_synthetic":false,"blanket_impl":null}}},"12":{"id":12,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":13,"args":null},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"56":{"id":56,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":13,"args":null},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"37":{"id":37,"crate_id":2,"name":"Error","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"assoc_type":{"generics":{"params":[],"where_predicates":[]},"bounds":[],"type":{"qualified_path":{"name":"Error","args":null,"self_type":{"generic":"U"},"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}}}}}}},"81":{"id":81,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":23,"args":null},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"18":{"id":18,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":19,"args":null},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"62":{"id":62,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":27,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"43":{"id":43,"crate_id":2,"name":"Error","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"assoc_type":{"generics":{"params":[],"where_predicates":[]},"bounds":[],"type":{"resolved_path":{"path":"Infallible","id":44,"args":null}}}}},"87":{"id":87,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[43,45],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"24":{"id":24,"crate_id":2,"name":"borrow","span":null,"visibility":"default","docs":null,"links":{},"attrs":[{"other":"#[rustc_diagnostic_item = \"noop_method_borrow\"]"}],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"T"}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"68":{"id":68,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":50,"args":null},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[47],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"5":{"id":5,"crate_id":0,"name":"add","span":{"filename":"src/lib.rs","begin":[17,5],"end":[19,6]},"visibility":"public","docs":"Adds n to the current value and returns the result.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["n",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"49":{"id":49,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":50,"args":null},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[47],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"93":{"id":93,"crate_id":0,"name":"find_max","span":{"filename":"src/lib.rs","begin":[107,1],"end":[109,2]},"visibility":"public","docs":"Finds the maximum value in a list, or None if empty.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["numbers",{"resolved_path":{"path":"Vec","id":91,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}}]],"output":{"resolved_path":{"path":"Option","id":94,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"74":{"id":74,"crate_id":0,"name":"to_string_repr","span":{"filename":"src/lib.rs","begin":[75,5],"end":[77,6]},"visibility":"public","docs":"Returns a string representation.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":2,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"11":{"id":11,"crate_id":0,"name":null,"span":{"filename":"src/lib.rs","begin":[7,1],"end":[47,2]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":null,"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[4,5,6,7,8,9,10],"is_negative":false,"is_synthetic":false,"blanket_impl":null}}},"55":{"id":55,"crate_id":0,"name":"Operation","span":{"filename":"src/lib.rs","begin":[50,1],"end":[55,2]},"visibility":"public","docs":"Supported arithmetic operations.","links":{},"attrs":[],"deprecation":null,"inner":{"enum":{"generics":{"params":[],"where_predicates":[]},"has_stripped_variants":false,"variants":[51,52,53,54],"impls":[56,57,58,59,60,61,62,63,64,65,66,67,68]}}},"36":{"id":36,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":31,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[35],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"80":{"id":80,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":21,"args":null},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"61":{"id":61,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":23,"args":null},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"86":{"id":86,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":42,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[37,39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"67":{"id":67,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[43,45],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"4":{"id":4,"crate_id":0,"name":"new","span":{"filename":"src/lib.rs","begin":[9,5],"end":[14,6]},"visibility":"public","docs":"Creates a new Calculator with an initial value.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["initial_value",{"primitive":"i32"}],["name",{"resolved_path":{"path":"String","id":2,"args":null}}]],"output":{"generic":"Self"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"92":{"id":92,"crate_id":0,"name":"greet","span":{"filename":"src/lib.rs","begin":[102,1],"end":[104,2]},"visibility":"public","docs":"Returns a greeting message.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["name",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"primitive":"str"}}}]],"output":{"resolved_path":{"path":"String","id":2,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"29":{"id":29,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[28],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"73":{"id":73,"crate_id":0,"name":"distance_to","span":{"filename":"src/lib.rs","begin":[70,5],"end":[72,6]},"visibility":"public","docs":"Computes the distance to another point.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["other",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"resolved_path":{"path":"Point","id":71,"args":null}}}}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"10":{"id":10,"crate_id":0,"name":"reset","span":{"filename":"src/lib.rs","begin":[42,5],"end":[46,6]},"visibility":"public","docs":"Resets the calculator to a new value and returns the old value.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["new_value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"54":{"id":54,"crate_id":0,"name":"Divide","span":{"filename":"src/lib.rs","begin":[54,5],"end":[54,11]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"35":{"id":35,"crate_id":2,"name":"from","span":null,"visibility":"default","docs":"Returns the argument unchanged.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["t",{"generic":"T"}]],"output":{"generic":"T"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"79":{"id":79,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":19,"args":null},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"16":{"id":16,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":17,"args":null},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"60":{"id":60,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":21,"args":null},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"41":{"id":41,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":42,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[37,39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"85":{"id":85,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":31,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[35],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"22":{"id":22,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":23,"args":null},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"66":{"id":66,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":42,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[37,39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"3":{"id":3,"crate_id":0,"name":"Calculator","span":{"filename":"src/lib.rs","begin":[2,1],"end":[5,2]},"visibility":"public","docs":"A simple calculator that accumulates a value.","links":{},"attrs":[],"deprecation":null,"inner":{"struct":{"kind":{"plain":{"fields":[0,1],"has_stripped_fields":false}},"generics":{"params":[],"where_predicates":[]},"impls":[11,12,14,16,18,20,22,25,29,33,36,41,46,49]}}},"47":{"id":47,"crate_id":2,"name":"type_id","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"TypeId","id":48,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"28":{"id":28,"crate_id":2,"name":"borrow_mut","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}]],"output":{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"T"}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"72":{"id":72,"crate_id":0,"name":"new","span":{"filename":"src/lib.rs","begin":[65,5],"end":[67,6]},"visibility":"public","docs":"Creates a new Point.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["x",{"primitive":"f64"}],["y",{"primitive":"f64"}]],"output":{"generic":"Self"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"9":{"id":9,"crate_id":0,"name":"get_name","span":{"filename":"src/lib.rs","begin":[37,5],"end":[39,6]},"visibility":"public","docs":"Returns the name of this calculator.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":2,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"53":{"id":53,"crate_id":0,"name":"Multiply","span":{"filename":"src/lib.rs","begin":[53,5],"end":[53,13]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"78":{"id":78,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":17,"args":null},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"59":{"id":59,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":19,"args":null},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"84":{"id":84,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":31,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[32],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"65":{"id":65,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":31,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[35],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"46":{"id":46,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[43,45],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"90":{"id":90,"crate_id":0,"name":"sum_all","span":{"filename":"src/lib.rs","begin":[97,1],"end":[99,2]},"visibility":"public","docs":"Adds all numbers in a list.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["numbers",{"resolved_path":{"path":"Vec","id":91,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"71":{"id":71,"crate_id":0,"name":"Point","span":{"filename":"src/lib.rs","begin":[58,1],"end":[61,2]},"visibility":"public","docs":"A simple point in 2D space.","links":{},"attrs":[],"deprecation":null,"inner":{"struct":{"kind":{"plain":{"fields":[69,70],"has_stripped_fields":false}},"generics":{"params":[],"where_predicates":[]},"impls":[75,76,77,78,79,80,81,82,83,84,85,86,87,88]}}},"8":{"id":8,"crate_id":0,"name":"get_value","span":{"filename":"src/lib.rs","begin":[32,5],"end":[34,6]},"visibility":"public","docs":"Returns the current value.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"52":{"id":52,"crate_id":0,"name":"Subtract","span":{"filename":"src/lib.rs","begin":[52,5],"end":[52,13]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"33":{"id":33,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":31,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[32],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"77":{"id":77,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":15,"args":null},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"14":{"id":14,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":15,"args":null},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"58":{"id":58,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":17,"args":null},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"39":{"id":39,"crate_id":2,"name":"try_into","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"generic":"Self"}]],"output":{"resolved_path":{"path":"Result","id":40,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}},{"type":{"qualified_path":{"name":"Error","args":null,"self_type":{"generic":"U"},"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}}}}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"83":{"id":83,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[28],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"20":{"id":20,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":21,"args":null},"for":{"resolved_path":{"path":"Calculator","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"64":{"id":64,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":31,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[32],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"1":{"id":1,"crate_id":0,"name":"name","span":{"filename":"src/lib.rs","begin":[4,5],"end":[4,21]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"resolved_path":{"path":"String","id":2,"args":null}}}},"45":{"id":45,"crate_id":2,"name":"try_from","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["value",{"generic":"U"}]],"output":{"resolved_path":{"path":"Result","id":40,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}},{"type":{"qualified_path":{"name":"Error","args":null,"self_type":{"generic":"T"},"trait":{"path":"TryFrom","id":38,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}}}}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"89":{"id":89,"crate_id":0,"name":"compute","span":{"filename":"src/lib.rs","begin":[81,1],"end":[94,2]},"visibility":"public","docs":"Computes a binary operation on two integers.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["a",{"primitive":"i32"}],["b",{"primitive":"i32"}],["op",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"resolved_path":{"path":"Operation","id":55,"args":null}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"70":{"id":70,"crate_id":0,"name":"y","span":{"filename":"src/lib.rs","begin":[60,5],"end":[60,15]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"f64"}}},"7":{"id":7,"crate_id":0,"name":"multiply","span":{"filename":"src/lib.rs","begin":[27,5],"end":[29,6]},"visibility":"public","docs":"Multiplies the current value by n and returns the result.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["n",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"51":{"id":51,"crate_id":0,"name":"Add","span":{"filename":"src/lib.rs","begin":[51,5],"end":[51,8]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"95":{"id":95,"crate_id":0,"name":"kne_test_calculator","span":{"filename":"src/lib.rs","begin":[1,1],"end":[109,2]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"module":{"is_crate":true,"items":[3,55,71,89,90,92,93],"is_stripped":false}}},"32":{"id":32,"crate_id":2,"name":"into","span":null,"visibility":"default","docs":"Calls `U::from(self)`.\n\nThat is, this conversion is whatever the implementation of\n[From]<T> for U chooses to do.","links":{"From":31},"attrs":[{"other":"#[attr = TrackCaller]"}],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"generic":"Self"}]],"output":{"generic":"U"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"76":{"id":76,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":13,"args":null},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"57":{"id":57,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":15,"args":null},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"82":{"id":82,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":27,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":71,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"63":{"id":63,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":26,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":55,"args":null}},"items":[28],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}}},"paths":{"1762":{"crate_id":16,"path":["gimli","read","cfi","RegisterRule"],"kind":"enum"},"2089":{"crate_id":17,"path":["object","read","Error"],"kind":"struct"},"327":{"crate_id":1,"path":["std","sys","os_str","bytes","Slice"],"kind":"struct"},"1208":{"crate_id":3,"path":["alloc","string","IntoChars"],"kind":"struct"},"1535":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","msqid_ds"],"kind":"struct"},"654":{"crate_id":2,"path":["core","cell","RefCell"],"kind":"struct"},"2416":{"crate_id":2,"path":["core","intrinsics","unchecked_funnel_shr"],"kind":"function"},"100":{"crate_id":2,"path":["core","fmt","Formatter"],"kind":"struct"},"981":{"crate_id":2,"path":["core","sync","atomic","AtomicI16"],"kind":"struct"},"1862":{"crate_id":16,"path":["gimli","read","unit","DebugInfoUnitHeadersIter"],"kind":"struct"},"2189":{"crate_id":17,"path":["object","macho","UuidCommand"],"kind":"struct"},"427":{"crate_id":2,"path":["core","iter","adapters","zip","Zip"],"kind":"struct"},"1308":{"crate_id":5,"path":["libc","unix","linux_like","sock_filter"],"kind":"struct"},"754":{"crate_id":2,"path":["core","core_simd","masks","MaskElement"],"kind":"trait"},"1635":{"crate_id":10,"path":["hashbrown","set","OccupiedEntry"],"kind":"struct"},"200":{"crate_id":1,"path":["std","io","Lines"],"kind":"struct"},"1081":{"crate_id":2,"path":["core","cell","CloneFromCell"],"kind":"trait"},"1962":{"crate_id":17,"path":["object","read","coff","import","ImportType"],"kind":"enum"},"1408":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_nxtinfo"],"kind":"struct"},"527":{"crate_id":2,"path":["core","ops","control_flow","ControlFlow"],"kind":"enum"},"2289":{"crate_id":17,"path":["object","xcoff","FileHeader32"],"kind":"struct"},"854":{"crate_id":2,"path":["core","wtf8","EncodeWide"],"kind":"struct"},"1735":{"crate_id":16,"path":["gimli","read","util","ArrayVec"],"kind":"struct"},"300":{"crate_id":1,"path":["std","sync","poison","PoisonError"],"kind":"struct"},"1181":{"crate_id":3,"path":["alloc","collections","btree","map","Range"],"kind":"struct"},"2062":{"crate_id":17,"path":["object","read","pe","import","DelayLoadDescriptorIterator"],"kind":"struct"},"1508":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","nl_mmap_hdr"],"kind":"struct"},"627":{"crate_id":2,"path":["core","intrinsics","AtomicOrdering"],"kind":"enum"},"2389":{"crate_id":2,"path":["core","mem","replace"],"kind":"function"},"954":{"crate_id":2,"path":["core","core_arch","x86","bf16"],"kind":"struct"},"1835":{"crate_id":16,"path":["gimli","read","op","Piece"],"kind":"struct"},"2162":{"crate_id":17,"path":["object","macho","LcStr"],"kind":"struct"},"400":{"crate_id":2,"path":["core","error","Error"],"kind":"trait"},"1281":{"crate_id":5,"path":["libc","unix","itimerval"],"kind":"struct"},"1608":{"crate_id":10,"path":["hashbrown","raw","RawIntoIter"],"kind":"struct"},"727":{"crate_id":2,"path":["core","core_arch","simd","i8x32"],"kind":"struct"},"1054":{"crate_id":2,"path":["core","fmt","Pointer"],"kind":"trait"},"173":{"crate_id":1,"path":["std","io","buffered","linewriter","LineWriter"],"kind":"struct"},"1935":{"crate_id":17,"path":["object","read","archive","Members"],"kind":"enum"},"2262":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation32V2"],"kind":"struct"},"500":{"crate_id":2,"path":["core","slice","iter","RChunksExactMut"],"kind":"struct"},"1381":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_response"],"kind":"struct"},"1708":{"crate_id":16,"path":["gimli","constants","DwLle"],"kind":"struct"},"827":{"crate_id":2,"path":["core","str","iter","SplitWhitespace"],"kind":"struct"},"273":{"crate_id":1,"path":["std","sync","once","Once"],"kind":"struct"},"1154":{"crate_id":3,"path":["alloc","string","Drain"],"kind":"struct"},"2035":{"crate_id":17,"path":["object","read","macho","segment","MachOSegmentInternal"],"kind":"struct"},"2362":{"crate_id":18,"path":["memchr","memmem","searcher","Prefilter"],"kind":"struct"},"600":{"crate_id":2,"path":["core","num","error","TryFromIntError"],"kind":"struct"},"1481":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_bd_header_u"],"kind":"union"},"927":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1808":{"crate_id":16,"path":["gimli","read","line","IncompleteLineProgram"],"kind":"struct"},"373":{"crate_id":1,"path":["std","sync","mpmc","list","Channel"],"kind":"struct"},"1254":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","big","i256"],"kind":"struct"},"2135":{"crate_id":17,"path":["object","elf","Versym"],"kind":"struct"},"2462":{"crate_id":1,"path":["std","u32"],"kind":"primitive"},"700":{"crate_id":2,"path":["core","core_arch","simd","u64x1"],"kind":"struct"},"1581":{"crate_id":10,"path":["hashbrown","control","group","sse2","Group"],"kind":"struct"},"1027":{"crate_id":2,"path":["core","ops","bit","ShlAssign"],"kind":"trait"},"146":{"crate_id":1,"path":["std","env","VarsOs"],"kind":"struct"},"1908":{"crate_id":17,"path":["object","read","util","DebugByte"],"kind":"struct"},"473":{"crate_id":2,"path":["core","ops","bit","BitAnd"],"kind":"trait"},"1354":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_condition_effect"],"kind":"struct"},"2235":{"crate_id":17,"path":["object","pe","ImageAuxSymbolFunctionBeginEnd"],"kind":"struct"},"1681":{"crate_id":16,"path":["gimli","common","DebugStrOffsetsBase"],"kind":"struct"},"800":{"crate_id":2,"path":["core","hash","sip","SipHasher"],"kind":"struct"},"1127":{"crate_id":3,"path":["alloc","collections","binary_heap","PeekMut"],"kind":"struct"},"246":{"crate_id":1,"path":["std","random","DefaultRandomSource"],"kind":"struct"},"2008":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheImageIterator"],"kind":"struct"},"2335":{"crate_id":18,"path":["memchr","arch","generic","memchr","Iter"],"kind":"struct"},"573":{"crate_id":1,"path":["std","os","unix","thread","JoinHandleExt"],"kind":"trait"},"1454":{"crate_id":5,"path":["libc","unix","linux_like","linux","xsk_tx_metadata_completion"],"kind":"struct"},"1781":{"crate_id":16,"path":["gimli","read","relocate","RelocateReader"],"kind":"struct"},"19":{"crate_id":2,"path":["core","marker","Unpin"],"kind":"trait"},"900":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1227":{"crate_id":3,"path":["alloc","collections","btree","set","entry","Entry"],"kind":"enum"},"346":{"crate_id":1,"path":["std","thread","spawnhook","ChildSpawnHooks"],"kind":"struct"},"2108":{"crate_id":17,"path":["object","read","CompressedFileRange"],"kind":"struct"},"2435":{"crate_id":13,"path":["rustc_demangle"],"kind":"module"},"673":{"crate_id":2,"path":["core","sync","exclusive","Exclusive"],"kind":"struct"},"1554":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","mcontext_t"],"kind":"struct"},"1881":{"crate_id":17,"path":["object","common","SubArchitecture"],"kind":"enum"},"119":{"crate_id":1,"path":["std","collections","hash","map","Entry"],"kind":"enum"},"1000":{"crate_id":2,"path":["core","str","lossy","Debug"],"kind":"struct"},"446":{"crate_id":2,"path":["core","iter","traits","collect","FromIterator"],"kind":"trait"},"1327":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_pkt"],"kind":"struct"},"2208":{"crate_id":17,"path":["object","macho","Nlist64"],"kind":"struct"},"773":{"crate_id":2,"path":["core","iter","adapters","flatten","FlattenCompat"],"kind":"struct"},"1654":{"crate_id":15,"path":["addr2line","unit","LocationRangeIter"],"kind":"struct"},"1100":{"crate_id":2,"path":["core","core_simd","swizzle","interleave","Hi"],"kind":"struct"},"219":{"crate_id":1,"path":["std","panic","BacktraceStyle"],"kind":"enum"},"1981":{"crate_id":17,"path":["object","read","elf","relocation","CrelIterator"],"kind":"struct"},"546":{"crate_id":1,"path":["std","net","socket_addr","ToSocketAddrs"],"kind":"trait"},"1427":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_scan_req"],"kind":"struct"},"2308":{"crate_id":17,"path":["object","xcoff","DwarfAux32"],"kind":"struct"},"873":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1754":{"crate_id":16,"path":["gimli","read","cfi","PartialFrameDescriptionEntry"],"kind":"struct"},"1200":{"crate_id":3,"path":["alloc","collections","linked_list","Cursor"],"kind":"struct"},"319":{"crate_id":1,"path":["std","sys","fs","unix","OpenOptions"],"kind":"struct"},"2081":{"crate_id":17,"path":["object","read","xcoff","symbol","XcoffSymbol"],"kind":"struct"},"646":{"crate_id":2,"path":["core","ops","range","RangeFrom"],"kind":"struct"},"1527":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fpos64_t"],"kind":"struct"},"2408":{"crate_id":1,"path":["std","env"],"kind":"module"},"1854":{"crate_id":16,"path":["gimli","read","rnglists","RngListIter"],"kind":"struct"},"92":{"crate_id":0,"path":["kne_test_calculator","greet"],"kind":"function"},"973":{"crate_id":2,"path":["core","panic","panic_info","PanicMessage"],"kind":"struct"},"1300":{"crate_id":5,"path":["libc","unix","linux_like","in_pktinfo"],"kind":"struct"},"419":{"crate_id":2,"path":["core","iter","traits","collect","IntoIterator"],"kind":"trait"},"2181":{"crate_id":17,"path":["object","macho","DysymtabCommand"],"kind":"struct"},"746":{"crate_id":2,"path":["core","core_arch","simd","u64x8"],"kind":"struct"},"1627":{"crate_id":10,"path":["hashbrown","map","VacantEntry"],"kind":"struct"},"1954":{"crate_id":17,"path":["object","read","coff","symbol","CoffSymbolIterator"],"kind":"struct"},"192":{"crate_id":1,"path":["std","io","util","Sink"],"kind":"struct"},"1073":{"crate_id":2,"path":["core","iter","traits","marker","TrustedLen"],"kind":"trait"},"1400":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_extts_request"],"kind":"struct"},"519":{"crate_id":2,"path":["core","ops","range","OneSidedRange"],"kind":"trait"},"2281":{"crate_id":17,"path":["object","pe","ImageDebugMisc"],"kind":"struct"},"846":{"crate_id":2,"path":["core","str","CharEscapeUnicode"],"kind":"struct"},"1727":{"crate_id":16,"path":["gimli","constants","DwMacro"],"kind":"struct"},"2054":{"crate_id":17,"path":["object","read","pe","export","Export"],"kind":"struct"},"292":{"crate_id":1,"path":["std","sync","poison","mutex","Mutex"],"kind":"struct"},"1173":{"crate_id":3,"path":["alloc","bstr","ByteString"],"kind":"struct"},"619":{"crate_id":2,"path":["core","num","niche_types","NonZeroUsizeInner"],"kind":"struct"},"1500":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","glob64_t"],"kind":"struct"},"2381":{"crate_id":1,"path":["std","io","error","Result"],"kind":"type_alias"},"946":{"crate_id":2,"path":["core","core_arch","x86","__m512bh"],"kind":"struct"},"1827":{"crate_id":16,"path":["gimli","read","macros","MacroUnitHeader"],"kind":"struct"},"2154":{"crate_id":17,"path":["object","macho","DyldSubCacheEntryV1"],"kind":"struct"},"392":{"crate_id":2,"path":["core","cmp","PartialEq"],"kind":"trait"},"1273":{"crate_id":5,"path":["libc","unix","rusage"],"kind":"struct"},"719":{"crate_id":2,"path":["core","core_arch","simd","m8x16"],"kind":"struct"},"1600":{"crate_id":10,"path":["hashbrown","set","Difference"],"kind":"struct"},"165":{"crate_id":1,"path":["std","fs","FileType"],"kind":"struct"},"1046":{"crate_id":2,"path":["core","ffi","va_list","VaArgSafe"],"kind":"trait"},"1927":{"crate_id":17,"path":["object","read","any","SymbolIterator"],"kind":"struct"},"1373":{"crate_id":5,"path":["libc","unix","linux_like","linux","ucred"],"kind":"struct"},"492":{"crate_id":2,"path":["core","slice","iter","Chunks"],"kind":"struct"},"2254":{"crate_id":17,"path":["object","pe","ImageResourceDirectoryEntry"],"kind":"struct"},"819":{"crate_id":2,"path":["core","str","iter","MatchIndicesInternal"],"kind":"struct"},"1700":{"crate_id":16,"path":["gimli","constants","DwSectV2"],"kind":"struct"},"2027":{"crate_id":17,"path":["object","read","macho","file","MachOComdatIterator"],"kind":"struct"},"265":{"crate_id":1,"path":["std","sync","mpsc","RecvError"],"kind":"struct"},"1146":{"crate_id":3,"path":["alloc","collections","vec_deque","truncate","Dropper"],"kind":"struct"},"1473":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ifr_ifru"],"kind":"union"},"592":{"crate_id":2,"path":["core","num","dec2flt","decimal","Decimal"],"kind":"struct"},"2354":{"crate_id":18,"path":["memchr","memchr","Memchr2"],"kind":"struct"},"919":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"38":{"crate_id":2,"path":["core","convert","TryFrom"],"kind":"trait"},"1800":{"crate_id":16,"path":["gimli","read","line","DebugLine"],"kind":"struct"},"2127":{"crate_id":17,"path":["object","elf","Rel64"],"kind":"struct"},"365":{"crate_id":1,"path":["std","backtrace_rs","backtrace","libunwind","Frame"],"kind":"enum"},"1246":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Owned"],"kind":"enum"},"1573":{"crate_id":8,"path":["miniz_oxide","inflate","TINFLStatus"],"kind":"enum"},"692":{"crate_id":2,"path":["core","core_arch","simd","i8x2"],"kind":"struct"},"2454":{"crate_id":1,"path":["std","f128"],"kind":"primitive"},"138":{"crate_id":2,"path":["core","hash","BuildHasher"],"kind":"trait"},"1019":{"crate_id":2,"path":["core","ops","arith","RemAssign"],"kind":"trait"},"1900":{"crate_id":17,"path":["object","endian","U64Bytes"],"kind":"struct"},"2227":{"crate_id":17,"path":["object","pe","AnonObjectHeaderBigobj"],"kind":"struct"},"465":{"crate_id":1,"path":["std","os","unix","net","ancillary","AncillaryDataIter"],"kind":"struct"},"1346":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_absinfo"],"kind":"struct"},"792":{"crate_id":2,"path":["core","result","Iter"],"kind":"struct"},"1673":{"crate_id":16,"path":["gimli","common","DebugLocListsIndex"],"kind":"struct"},"238":{"crate_id":1,"path":["std","process","Command"],"kind":"struct"},"1119":{"crate_id":2,"path":["core","core_simd","simd","ptr","sealed","Sealed"],"kind":"trait"},"2000":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsection"],"kind":"struct"},"2327":{"crate_id":18,"path":["memchr","arch","all","twoway","TwoWay"],"kind":"struct"},"565":{"crate_id":3,"path":["alloc","rc","UniqueRc"],"kind":"struct"},"1446":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_ring_offset_v1"],"kind":"struct"},"892":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1773":{"crate_id":16,"path":["gimli","read","dwarf","UnitRef"],"kind":"struct"},"338":{"crate_id":1,"path":["std","alloc","System"],"kind":"struct"},"1219":{"crate_id":3,"path":["alloc","collections","binary_heap","Drain"],"kind":"struct"},"2100":{"crate_id":17,"path":["object","read","Import"],"kind":"struct"},"1546":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","statfs64"],"kind":"struct"},"665":{"crate_id":2,"path":["core","net","parser","AddrParseError"],"kind":"struct"},"2427":{"crate_id":5,"path":["libc"],"kind":"module"},"992":{"crate_id":2,"path":["core","ops","function","Fn"],"kind":"trait"},"111":{"crate_id":1,"path":["std","backtrace","Backtrace"],"kind":"struct"},"1873":{"crate_id":16,"path":["gimli","read","unit","EntriesTreeIter"],"kind":"struct"},"2200":{"crate_id":17,"path":["object","macho","SymsegCommand"],"kind":"struct"},"438":{"crate_id":2,"path":["core","iter","adapters","skip","Skip"],"kind":"struct"},"1319":{"crate_id":5,"path":["libc","unix","linux_like","linux","passwd"],"kind":"struct"},"1646":{"crate_id":13,"path":["rustc_demangle","v0","Ident"],"kind":"struct"},"765":{"crate_id":2,"path":["core","char","EscapeUnicode"],"kind":"struct"},"1092":{"crate_id":2,"path":["core","str","pattern","MultiCharEqPattern"],"kind":"struct"},"211":{"crate_id":1,"path":["std","os","unix","net","listener","UnixListener"],"kind":"struct"},"1973":{"crate_id":17,"path":["object","read","elf","symbol","ElfSymbol"],"kind":"struct"},"2300":{"crate_id":17,"path":["object","xcoff","CsectAux32"],"kind":"struct"},"538":{"crate_id":1,"path":["std","io","stdio","StderrRaw"],"kind":"struct"},"1419":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aria_gcm_128"],"kind":"struct"},"1746":{"crate_id":16,"path":["gimli","read","cfi","CieOffsetEncoding"],"kind":"enum"},"865":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"311":{"crate_id":1,"path":["std","sys","args","common","Args"],"kind":"struct"},"1192":{"crate_id":3,"path":["alloc","collections","btree","set","Difference"],"kind":"struct"},"2073":{"crate_id":17,"path":["object","read","xcoff","file","XcoffFile"],"kind":"struct"},"2400":{"crate_id":2,"path":["core","iter","sources","once","once"],"kind":"function"},"638":{"crate_id":2,"path":["core","marker","variance","PhantomInvariantLifetime"],"kind":"struct"},"1519":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ptrace_syscall_info"],"kind":"struct"},"1846":{"crate_id":16,"path":["gimli","read","pubtypes","DebugPubTypes"],"kind":"struct"},"965":{"crate_id":2,"path":["core","cell","BorrowError"],"kind":"struct"},"411":{"crate_id":3,"path":["alloc","sync","Arc"],"kind":"struct"},"1292":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_in6"],"kind":"struct"},"2173":{"crate_id":17,"path":["object","macho","SubUmbrellaCommand"],"kind":"struct"},"738":{"crate_id":2,"path":["core","core_arch","simd","u8x64"],"kind":"struct"},"1619":{"crate_id":10,"path":["hashbrown","set","ExtractIf"],"kind":"struct"},"1065":{"crate_id":2,"path":["core","ops","async_function","AsyncFn"],"kind":"trait"},"184":{"crate_id":1,"path":["std","io","stdio","Stdin"],"kind":"struct"},"1946":{"crate_id":17,"path":["object","read","coff","section","SectionTable"],"kind":"struct"},"511":{"crate_id":2,"path":["core","slice","iter","RSplitN"],"kind":"struct"},"1392":{"crate_id":5,"path":["libc","unix","linux_like","linux","nlmsghdr"],"kind":"struct"},"2273":{"crate_id":17,"path":["object","pe","ImageAlpha64RuntimeFunctionEntry"],"kind":"struct"},"1719":{"crate_id":16,"path":["gimli","constants","DwOrd"],"kind":"struct"},"838":{"crate_id":2,"path":["core","str","pattern","CharSliceSearcher"],"kind":"struct"},"1165":{"crate_id":3,"path":["alloc","vec","in_place_drop","InPlaceDrop"],"kind":"struct"},"284":{"crate_id":1,"path":["std","sync","nonpoison","mutex","MutexGuard"],"kind":"struct"},"2046":{"crate_id":17,"path":["object","read","pe","file","PeComdat"],"kind":"struct"},"2373":{"crate_id":1,"path":["std","ffi"],"kind":"module"},"611":{"crate_id":2,"path":["core","num","niche_types","NonZeroU128Inner"],"kind":"struct"},"1492":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ptp_perout_request_1"],"kind":"union"},"1819":{"crate_id":16,"path":["gimli","read","loclists","LocListIter"],"kind":"struct"},"938":{"crate_id":2,"path":["core","core_arch","x86","__m512"],"kind":"struct"},"1265":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","__c_anonymous_sockaddr_can_can_addr"],"kind":"union"},"384":{"crate_id":1,"path":["std","sys","sync","once","futex","CompletionGuard"],"kind":"struct"},"2146":{"crate_id":17,"path":["object","macho","DyldCacheMappingInfo"],"kind":"struct"},"711":{"crate_id":2,"path":["core","core_arch","simd","i8x16"],"kind":"struct"},"1592":{"crate_id":10,"path":["hashbrown","raw","RawIterHash"],"kind":"struct"},"1919":{"crate_id":17,"path":["object","read","any","Section"],"kind":"struct"},"157":{"crate_id":1,"path":["std","fs","ReadDir"],"kind":"struct"},"1038":{"crate_id":2,"path":["core","slice","sort","stable","merge","MergeState"],"kind":"struct"},"484":{"crate_id":2,"path":["core","mem","maybe_uninit","MaybeUninit"],"kind":"union"},"1365":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Sym"],"kind":"struct"},"2246":{"crate_id":17,"path":["object","pe","ImageThunkData32"],"kind":"struct"},"811":{"crate_id":2,"path":["core","str","iter","Split"],"kind":"struct"},"1692":{"crate_id":16,"path":["gimli","arch","AArch64"],"kind":"struct"},"2019":{"crate_id":17,"path":["object","read","macho","dyld_cache","RelocationStateV3"],"kind":"enum"},"257":{"crate_id":1,"path":["std","sync","mpmc","Sender"],"kind":"struct"},"1138":{"crate_id":3,"path":["alloc","collections","linked_list","drop","DropGuard"],"kind":"struct"},"584":{"crate_id":2,"path":["core","alloc","layout","Layout"],"kind":"struct"},"1465":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf64_rela"],"kind":"struct"},"2346":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","Two"],"kind":"struct"},"30":{"crate_id":2,"path":["core","borrow","BorrowMut"],"kind":"trait"},"911":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1792":{"crate_id":16,"path":["gimli","read","aranges","ArangeEntryIter"],"kind":"struct"},"1238":{"crate_id":3,"path":["alloc","vec","peek_mut","PeekMut"],"kind":"struct"},"357":{"crate_id":1,"path":["std","sys","pal","unix","time","Timespec"],"kind":"struct"},"2119":{"crate_id":17,"path":["object","elf","CompressionHeader32"],"kind":"struct"},"684":{"crate_id":2,"path":["core","time","TryFromFloatSecsErrorKind"],"kind":"enum"},"1565":{"crate_id":5,"path":["libc","unix","DIR"],"kind":"enum"},"2446":{"crate_id":1,"path":["std","pointer"],"kind":"primitive"},"1892":{"crate_id":17,"path":["object","common","SectionFlags"],"kind":"enum"},"130":{"crate_id":1,"path":["std","collections","hash","set","HashSet"],"kind":"struct"},"1011":{"crate_id":2,"path":["core","fmt","LowerHex"],"kind":"trait"},"1338":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_bd_ts"],"kind":"struct"},"457":{"crate_id":2,"path":["core","iter","adapters","cycle","Cycle"],"kind":"struct"},"2219":{"crate_id":17,"path":["object","pe","ImageRomOptionalHeader"],"kind":"struct"},"784":{"crate_id":2,"path":["core","iter","sources","repeat_with","RepeatWith"],"kind":"struct"},"1665":{"crate_id":16,"path":["gimli","common","DebugAddrBase"],"kind":"struct"},"1992":{"crate_id":17,"path":["object","read","elf","version","Version"],"kind":"struct"},"230":{"crate_id":1,"path":["std","path","StripPrefixError"],"kind":"struct"},"1111":{"crate_id":2,"path":["core","core_simd","simd","num","uint","SimdUint"],"kind":"trait"},"1438":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_barrierattr_t"],"kind":"struct"},"557":{"crate_id":1,"path":["std","os","unix","fs","OpenOptionsExt"],"kind":"trait"},"2319":{"crate_id":18,"path":["memchr","arch","all","memchr","ThreeIter"],"kind":"struct"},"3":{"crate_id":0,"path":["kne_test_calculator","Calculator"],"kind":"struct"},"884":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1765":{"crate_id":16,"path":["gimli","read","cfi","UnwindExpression"],"kind":"struct"},"2092":{"crate_id":17,"path":["object","read","SectionIndex"],"kind":"struct"},"330":{"crate_id":1,"path":["std","sys","process","unix","common","ProgramKind"],"kind":"enum"},"1211":{"crate_id":3,"path":["alloc","collections","btree","map","RangeMut"],"kind":"struct"},"1538":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","sigaction"],"kind":"struct"},"657":{"crate_id":2,"path":["core","char","convert","CharTryFromError"],"kind":"struct"},"2419":{"crate_id":2,"path":["core","ptr","without_provenance"],"kind":"function"},"103":{"crate_id":1,"path":["std","thread","local","LocalKey"],"kind":"struct"},"984":{"crate_id":2,"path":["core","sync","atomic","AtomicU32"],"kind":"struct"},"1865":{"crate_id":16,"path":["gimli","read","unit","DebuggingInformationEntry"],"kind":"struct"},"2192":{"crate_id":17,"path":["object","macho","FilesetEntryCommand"],"kind":"struct"},"430":{"crate_id":2,"path":["core","iter","adapters","map","Map"],"kind":"struct"},"1311":{"crate_id":5,"path":["libc","unix","linux_like","statx_timestamp"],"kind":"struct"},"757":{"crate_id":2,"path":["core","ptr","unique","Unique"],"kind":"struct"},"1638":{"crate_id":10,"path":["hashbrown","table","OccupiedEntry"],"kind":"struct"},"203":{"crate_id":1,"path":["std","net","tcp","TcpStream"],"kind":"struct"},"1084":{"crate_id":2,"path":["core","fmt","builders","PadAdapter"],"kind":"struct"},"1965":{"crate_id":17,"path":["object","read","elf","segment","ElfSegmentIterator"],"kind":"struct"},"1411":{"crate_id":5,"path":["libc","unix","linux_like","linux","rlimit64"],"kind":"struct"},"530":{"crate_id":3,"path":["alloc","borrow","ToOwned"],"kind":"trait"},"2292":{"crate_id":17,"path":["object","xcoff","AuxHeader64"],"kind":"struct"},"857":{"crate_id":2,"path":["core","future","ResumeTy"],"kind":"struct"},"1738":{"crate_id":16,"path":["gimli","read","addr","AddrHeader"],"kind":"struct"},"303":{"crate_id":1,"path":["std","time","SystemTimeError"],"kind":"struct"},"1184":{"crate_id":3,"path":["alloc","collections","btree","merge_iter","Peeked"],"kind":"enum"},"2065":{"crate_id":17,"path":["object","read","pe","relocation","Relocation"],"kind":"struct"},"1511":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","regex_t"],"kind":"struct"},"630":{"crate_id":2,"path":["core","mem","Discriminant"],"kind":"struct"},"2392":{"crate_id":2,"path":["core","str","converts","from_utf8_unchecked"],"kind":"function"},"957":{"crate_id":2,"path":["core","error","tags","Value"],"kind":"struct"},"1838":{"crate_id":16,"path":["gimli","read","op","EvaluationResult"],"kind":"enum"},"2165":{"crate_id":17,"path":["object","macho","Section32"],"kind":"struct"},"403":{"crate_id":1,"path":["std","panicking","panic_handler","StaticStrPayload"],"kind":"struct"},"1284":{"crate_id":5,"path":["libc","unix","protoent"],"kind":"struct"},"1611":{"crate_id":10,"path":["hashbrown","map","IntoValues"],"kind":"struct"},"730":{"crate_id":2,"path":["core","core_arch","simd","i64x4"],"kind":"struct"},"176":{"crate_id":1,"path":["std","io","cursor","Cursor"],"kind":"struct"},"1057":{"crate_id":2,"path":["core","iter","adapters","GenericShunt"],"kind":"struct"},"1938":{"crate_id":17,"path":["object","read","archive","MemberHeader"],"kind":"enum"},"2265":{"crate_id":17,"path":["object","pe","ImageEpilogueDynamicRelocationHeader"],"kind":"struct"},"503":{"crate_id":2,"path":["core","slice","iter","Split"],"kind":"struct"},"1384":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_vm"],"kind":"struct"},"1711":{"crate_id":16,"path":["gimli","constants","DwAccess"],"kind":"struct"},"830":{"crate_id":2,"path":["core","str","iter","EncodeUtf16"],"kind":"struct"},"276":{"crate_id":1,"path":["std","sync","barrier","BarrierWaitResult"],"kind":"struct"},"1157":{"crate_id":3,"path":["alloc","sync","UniqueArcUninit"],"kind":"struct"},"2038":{"crate_id":17,"path":["object","read","macho","section","MachOSectionInternal"],"kind":"struct"},"2365":{"crate_id":18,"path":["memchr","memmem","FindIter"],"kind":"struct"},"603":{"crate_id":2,"path":["core","num","nonzero","ZeroablePrimitive"],"kind":"trait"},"1484":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_mutex_t"],"kind":"struct"},"930":{"crate_id":2,"path":["core","core_arch","x86","__m256i"],"kind":"struct"},"1811":{"crate_id":16,"path":["gimli","read","line","FileEntryFormat"],"kind":"struct"},"376":{"crate_id":1,"path":["std","sync","lazy_lock","force_mut","really_init_mut","PoisonOnPanic"],"kind":"struct"},"1257":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","feature_detect","Flags"],"kind":"struct"},"2138":{"crate_id":17,"path":["object","elf","Verneed"],"kind":"struct"},"1584":{"crate_id":10,"path":["hashbrown","raw","TableLayout"],"kind":"struct"},"703":{"crate_id":2,"path":["core","core_arch","simd","i32x2"],"kind":"struct"},"2465":{"crate_id":1,"path":["std","isize"],"kind":"primitive"},"1030":{"crate_id":2,"path":["core","hint","select_unpredictable","DropOnPanic"],"kind":"struct"},"149":{"crate_id":1,"path":["std","env","JoinPathsError"],"kind":"struct"},"1911":{"crate_id":17,"path":["object","read","util","StringTable"],"kind":"struct"},"476":{"crate_id":2,"path":["core","time","Duration"],"kind":"struct"},"1357":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_effect"],"kind":"struct"},"2238":{"crate_id":17,"path":["object","pe","ImageAuxSymbolCrc"],"kind":"struct"},"1684":{"crate_id":16,"path":["gimli","common","DebugTypeSignature"],"kind":"struct"},"803":{"crate_id":2,"path":["core","hash","sip","Sip13Rounds"],"kind":"struct"},"1130":{"crate_id":3,"path":["alloc","collections","binary_heap","DrainSorted"],"kind":"struct"},"249":{"crate_id":1,"path":["std","sync","mpmc","context","Inner"],"kind":"struct"},"2011":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingIterator"],"kind":"struct"},"2338":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","OneIter"],"kind":"struct"},"576":{"crate_id":1,"path":["std","os","net","linux_ext","socket","UnixSocketExt"],"kind":"trait"},"1457":{"crate_id":5,"path":["libc","unix","linux_like","linux","mnt_ns_info"],"kind":"struct"},"1784":{"crate_id":16,"path":["gimli","read","abbrev","AbbreviationsCache"],"kind":"struct"},"903":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1230":{"crate_id":3,"path":["alloc","collections","btree","set","DifferenceInner"],"kind":"enum"},"349":{"crate_id":1,"path":["std","sync","mpmc","utils","CachePadded"],"kind":"struct"},"2111":{"crate_id":17,"path":["object","archive","AixHeader"],"kind":"struct"},"2438":{"crate_id":16,"path":["gimli"],"kind":"module"},"676":{"crate_id":2,"path":["core","fmt","DebugAsHex"],"kind":"enum"},"1557":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","ptrace_rseq_configuration"],"kind":"struct"},"1884":{"crate_id":17,"path":["object","common","SectionKind"],"kind":"enum"},"122":{"crate_id":1,"path":["std","collections","hash","map","OccupiedError"],"kind":"struct"},"1003":{"crate_id":2,"path":["core","task","wake","ExtData"],"kind":"enum"},"449":{"crate_id":2,"path":["core","iter","traits","collect","Extend"],"kind":"trait"},"1330":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_hdr_variant1"],"kind":"struct"},"2211":{"crate_id":17,"path":["object","macho","ScatteredRelocationInfo"],"kind":"struct"},"776":{"crate_id":2,"path":["core","iter","sources","empty","Empty"],"kind":"struct"},"1657":{"crate_id":15,"path":["addr2line","DebugFile"],"kind":"enum"},"1103":{"crate_id":2,"path":["core","core_simd","swizzle","resize","Resize"],"kind":"struct"},"222":{"crate_id":1,"path":["std","path","PrefixComponent"],"kind":"struct"},"1984":{"crate_id":17,"path":["object","read","elf","comdat","ElfComdatSectionIterator"],"kind":"struct"},"549":{"crate_id":2,"path":["core","net","ip_addr","IpAddr"],"kind":"enum"},"1430":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_pmkid_cand"],"kind":"struct"},"2311":{"crate_id":17,"path":["object","xcoff","Rel64"],"kind":"struct"},"876":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1757":{"crate_id":16,"path":["gimli","read","cfi","UnwindTable"],"kind":"struct"},"1203":{"crate_id":3,"path":["alloc","collections","TryReserveErrorKind"],"kind":"enum"},"322":{"crate_id":1,"path":["std","sys","fs","unix","Mode"],"kind":"struct"},"2084":{"crate_id":17,"path":["object","read","xcoff","comdat","XcoffComdat"],"kind":"struct"},"649":{"crate_id":2,"path":["core","ops","range","RangeToInclusive"],"kind":"struct"},"1530":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","sifields_sigchld"],"kind":"struct"},"2411":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt","st_atime"],"kind":"function"},"1857":{"crate_id":16,"path":["gimli","read","str","DebugStr"],"kind":"struct"},"95":{"crate_id":0,"path":["kne_test_calculator"],"kind":"module"},"976":{"crate_id":2,"path":["core","panicking","assert_matches_failed","Pattern"],"kind":"struct"},"1303":{"crate_id":5,"path":["libc","unix","linux_like","arpreq"],"kind":"struct"},"422":{"crate_id":2,"path":["core","ops","function","FnOnce"],"kind":"trait"},"2184":{"crate_id":17,"path":["object","macho","DylibModule64"],"kind":"struct"},"749":{"crate_id":2,"path":["core","core_arch","simd","i32x32"],"kind":"struct"},"1630":{"crate_id":10,"path":["hashbrown","map","OccupiedError"],"kind":"struct"},"1957":{"crate_id":17,"path":["object","read","coff","comdat","CoffComdatIterator"],"kind":"struct"},"195":{"crate_id":1,"path":["std","io","SeekFrom"],"kind":"enum"},"1076":{"crate_id":2,"path":["core","ops","range","IntoBounds"],"kind":"trait"},"1403":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_extts_event"],"kind":"struct"},"522":{"crate_id":2,"path":["core","str","lossy","Utf8Chunks"],"kind":"struct"},"2284":{"crate_id":17,"path":["object","pe","ImageSeparateDebugHeader"],"kind":"struct"},"849":{"crate_id":2,"path":["core","str","IsAsciiWhitespace"],"kind":"struct"},"1730":{"crate_id":16,"path":["gimli","constants","DwEhPe"],"kind":"struct"},"2057":{"crate_id":17,"path":["object","read","pe","import","ImportTable"],"kind":"struct"},"295":{"crate_id":1,"path":["std","sync","poison","rwlock","RwLock"],"kind":"struct"},"1176":{"crate_id":3,"path":["alloc","collections","binary_heap","IntoIter"],"kind":"struct"},"622":{"crate_id":2,"path":["core","num","niche_types","I32NotAllOnes"],"kind":"struct"},"1503":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","termios"],"kind":"struct"},"2384":{"crate_id":1,"path":["std","fs","exists"],"kind":"function"},"949":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1830":{"crate_id":16,"path":["gimli","read","macros","MacroIter"],"kind":"struct"},"1276":{"crate_id":5,"path":["libc","unix","iovec"],"kind":"struct"},"395":{"crate_id":1,"path":["std","sys","pal","unix","kernel_copy","SpliceMode"],"kind":"enum"},"2157":{"crate_id":17,"path":["object","macho","FatArch32"],"kind":"struct"},"722":{"crate_id":2,"path":["core","core_arch","simd","m64x2"],"kind":"struct"},"1603":{"crate_id":10,"path":["hashbrown","table","HashTable"],"kind":"struct"},"168":{"crate_id":1,"path":["std","hash","random","RandomState"],"kind":"struct"},"1049":{"crate_id":2,"path":["core","ops","deref","DerefPure"],"kind":"trait"},"1930":{"crate_id":17,"path":["object","read","any","DynamicRelocationIterator"],"kind":"struct"},"1376":{"crate_id":5,"path":["libc","unix","linux_like","linux","posix_spawnattr_t"],"kind":"struct"},"495":{"crate_id":2,"path":["core","slice","iter","ChunksExactMut"],"kind":"struct"},"2257":{"crate_id":17,"path":["object","pe","ImageResourceDataEntry"],"kind":"struct"},"822":{"crate_id":2,"path":["core","str","iter","MatchesInternal"],"kind":"struct"},"1703":{"crate_id":16,"path":["gimli","constants","DwChildren"],"kind":"struct"},"2030":{"crate_id":17,"path":["object","read","macho","load_command","LoadCommandIterator"],"kind":"struct"},"268":{"crate_id":1,"path":["std","sync","mpsc","Sender"],"kind":"struct"},"1149":{"crate_id":3,"path":["alloc","rc","from_iter_exact","Guard"],"kind":"struct"},"1476":{"crate_id":5,"path":["libc","unix","linux_like","linux","ifconf"],"kind":"struct"},"595":{"crate_id":2,"path":["core","num","dec2flt","FloatErrorKind"],"kind":"enum"},"2357":{"crate_id":18,"path":["memchr","memmem","searcher","SearcherKind"],"kind":"union"},"922":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1803":{"crate_id":16,"path":["gimli","read","line","LineInstructions"],"kind":"struct"},"2130":{"crate_id":17,"path":["object","elf","Relr64"],"kind":"struct"},"368":{"crate_id":1,"path":["std","thread","PanicGuard"],"kind":"struct"},"1249":{"crate_id":3,"path":["alloc","string","ToString"],"kind":"trait"},"1576":{"crate_id":8,"path":["miniz_oxide","MZError"],"kind":"enum"},"695":{"crate_id":2,"path":["core","core_arch","simd","i8x4"],"kind":"struct"},"2457":{"crate_id":1,"path":["std","i32"],"kind":"primitive"},"141":{"crate_id":1,"path":["std","collections","hash","set","Union"],"kind":"struct"},"1022":{"crate_id":2,"path":["core","ops","arith","MulAssign"],"kind":"trait"},"1903":{"crate_id":17,"path":["object","endian","I64Bytes"],"kind":"struct"},"2230":{"crate_id":17,"path":["object","pe","ImageSymbolBytes"],"kind":"struct"},"468":{"crate_id":1,"path":["std","os","unix","net","ancillary","Messages"],"kind":"struct"},"1349":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_replay"],"kind":"struct"},"795":{"crate_id":2,"path":["core","fmt","rt","Count"],"kind":"enum"},"1676":{"crate_id":16,"path":["gimli","common","RawRangeListsOffset"],"kind":"struct"},"241":{"crate_id":1,"path":["std","process","Output"],"kind":"struct"},"1122":{"crate_id":2,"path":["core","core_simd","simd","cmp","ord","SimdPartialOrd"],"kind":"trait"},"2003":{"crate_id":17,"path":["object","read","elf","attributes","AttributeIndexIterator"],"kind":"struct"},"2330":{"crate_id":18,"path":["memchr","arch","all","twoway","SuffixOrdering"],"kind":"enum"},"568":{"crate_id":1,"path":["std","os","fd","raw","IntoRawFd"],"kind":"trait"},"1449":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_umem_reg_v1"],"kind":"struct"},"895":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1776":{"crate_id":16,"path":["gimli","read","endian_slice","EndianSlice"],"kind":"struct"},"341":{"crate_id":1,"path":["std","backtrace_rs","symbolize","Symbol"],"kind":"struct"},"1222":{"crate_id":3,"path":["alloc","collections","btree","map","entry","OccupiedEntry"],"kind":"struct"},"2103":{"crate_id":17,"path":["object","read","RelocationTarget"],"kind":"enum"},"1549":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","_libc_fpxreg"],"kind":"struct"},"668":{"crate_id":2,"path":["core","range","Range"],"kind":"struct"},"2430":{"crate_id":8,"path":["miniz_oxide"],"kind":"module"},"995":{"crate_id":2,"path":["core","cell","Ref"],"kind":"struct"},"114":{"crate_id":1,"path":["std","backtrace","BytesOrWide"],"kind":"enum"},"1876":{"crate_id":16,"path":["gimli","read","value","ValueType"],"kind":"enum"},"2203":{"crate_id":17,"path":["object","macho","EntryPointCommand"],"kind":"struct"},"441":{"crate_id":2,"path":["core","iter","adapters","flatten","FlatMap"],"kind":"struct"},"1322":{"crate_id":5,"path":["libc","unix","linux_like","linux","signalfd_siginfo"],"kind":"struct"},"1649":{"crate_id":13,"path":["rustc_demangle","v0","ParseError"],"kind":"enum"},"768":{"crate_id":2,"path":["core","char","ToLowercase"],"kind":"struct"},"1095":{"crate_id":2,"path":["core","core_simd","swizzle","rotate_elements_left","Rotate"],"kind":"struct"},"214":{"crate_id":1,"path":["std","os","unix","net","ucred","UCred"],"kind":"struct"},"1976":{"crate_id":17,"path":["object","read","elf","relocation","ElfSectionRelocationIterator"],"kind":"struct"},"2303":{"crate_id":17,"path":["object","xcoff","FunAux64"],"kind":"struct"},"541":{"crate_id":1,"path":["std","io","Seek"],"kind":"trait"},"1422":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_point"],"kind":"struct"},"1749":{"crate_id":16,"path":["gimli","read","cfi","CfiEntriesIter"],"kind":"struct"},"868":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"314":{"crate_id":1,"path":["std","sys","fd","unix","FileDesc"],"kind":"struct"},"1195":{"crate_id":3,"path":["alloc","collections","btree","set","Union"],"kind":"struct"},"2076":{"crate_id":17,"path":["object","read","xcoff","section","SectionTable"],"kind":"struct"},"2403":{"crate_id":2,"path":["core","ptr","metadata","metadata"],"kind":"function"},"641":{"crate_id":2,"path":["core","marker","variance","PhantomInvariant"],"kind":"struct"},"1522":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","tcp_info"],"kind":"struct"},"968":{"crate_id":2,"path":["core","ffi","c_void"],"kind":"enum"},"1849":{"crate_id":16,"path":["gimli","read","rnglists","DebugRngLists"],"kind":"struct"},"414":{"crate_id":3,"path":["alloc","collections","TryReserveError"],"kind":"struct"},"1295":{"crate_id":5,"path":["libc","unix","linux_like","fd_set"],"kind":"struct"},"2176":{"crate_id":17,"path":["object","macho","DylinkerCommand"],"kind":"struct"},"741":{"crate_id":2,"path":["core","core_arch","simd","i32x16"],"kind":"struct"},"1622":{"crate_id":10,"path":["hashbrown","table","IntoIter"],"kind":"struct"},"1068":{"crate_id":2,"path":["core","ops","try_trait","FromResidual"],"kind":"trait"},"187":{"crate_id":1,"path":["std","io","stdio","StdoutLock"],"kind":"struct"},"1949":{"crate_id":17,"path":["object","read","coff","section","CoffSectionIterator"],"kind":"struct"},"514":{"crate_id":2,"path":["core","core_simd","vector","Simd"],"kind":"struct"},"1395":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ifru_map"],"kind":"struct"},"2276":{"crate_id":17,"path":["object","pe","ImageEnclaveConfig32"],"kind":"struct"},"1722":{"crate_id":16,"path":["gimli","constants","DwDefaulted"],"kind":"struct"},"841":{"crate_id":2,"path":["core","str","pattern","StrSearcherImpl"],"kind":"enum"},"1168":{"crate_id":3,"path":["alloc","vec","dedup_by","FillGapOnDrop"],"kind":"struct"},"287":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","RwLockReadGuard"],"kind":"struct"},"2049":{"crate_id":17,"path":["object","read","pe","section","PeSegment"],"kind":"struct"},"2376":{"crate_id":1,"path":["std","fs","metadata"],"kind":"function"},"614":{"crate_id":2,"path":["core","num","niche_types","NonZeroI32Inner"],"kind":"struct"},"1495":{"crate_id":5,"path":["libc","unix","linux_like","linux","xsk_tx_metadata"],"kind":"struct"},"1822":{"crate_id":16,"path":["gimli","read","lookup","LookupEntryIter"],"kind":"struct"},"941":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1268":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","can_filter"],"kind":"struct"},"387":{"crate_id":1,"path":["std","sys","thread_local","abort_on_dtor_unwind","DtorUnwindGuard"],"kind":"struct"},"2149":{"crate_id":17,"path":["object","macho","DyldCacheSlideInfo2"],"kind":"struct"},"714":{"crate_id":2,"path":["core","core_arch","simd","i64x2"],"kind":"struct"},"1595":{"crate_id":10,"path":["hashbrown","map","Keys"],"kind":"struct"},"1922":{"crate_id":17,"path":["object","read","any","Comdat"],"kind":"struct"},"160":{"crate_id":1,"path":["std","fs","Permissions"],"kind":"struct"},"1041":{"crate_id":2,"path":["core","slice","sort","shared","smallsort","CopyOnDrop"],"kind":"struct"},"487":{"crate_id":2,"path":["core","slice","index","SliceIndex"],"kind":"trait"},"1368":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Shdr"],"kind":"struct"},"2249":{"crate_id":17,"path":["object","pe","ImageImportDescriptor"],"kind":"struct"},"814":{"crate_id":2,"path":["core","str","iter","SplitTerminator"],"kind":"struct"},"1695":{"crate_id":16,"path":["gimli","arch","RiscV"],"kind":"struct"},"2022":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorV5"],"kind":"struct"},"260":{"crate_id":1,"path":["std","sync","mpmc","IntoIter"],"kind":"struct"},"1141":{"crate_id":3,"path":["alloc","collections","vec_deque","into_iter","try_fold","Guard"],"kind":"struct"},"587":{"crate_id":2,"path":["core","alloc","AllocError"],"kind":"struct"},"1468":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_alg"],"kind":"struct"},"2349":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","ThreeIter"],"kind":"struct"},"914":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1795":{"crate_id":16,"path":["gimli","read","index","DebugTuIndex"],"kind":"struct"},"1241":{"crate_id":3,"path":["alloc","collections","btree","append","MergeIter"],"kind":"struct"},"360":{"crate_id":1,"path":["std","sys","fs","unix","StatxExtraFields"],"kind":"struct"},"2122":{"crate_id":17,"path":["object","elf","Sym64"],"kind":"struct"},"687":{"crate_id":2,"path":["core","task","poll","Poll"],"kind":"enum"},"1568":{"crate_id":7,"path":["unwind","libunwind","_Unwind_Reason_Code"],"kind":"enum"},"2449":{"crate_id":1,"path":["std","str"],"kind":"primitive"},"1895":{"crate_id":17,"path":["object","endian","Endianness"],"kind":"enum"},"133":{"crate_id":1,"path":["std","collections","hash","set","Drain"],"kind":"struct"},"1014":{"crate_id":2,"path":["core","fmt","UpperExp"],"kind":"trait"},"1341":{"crate_id":5,"path":["libc","unix","linux_like","linux","if_nameindex"],"kind":"struct"},"460":{"crate_id":2,"path":["core","iter","traits","accum","Product"],"kind":"trait"},"2222":{"crate_id":17,"path":["object","pe","ImageNtHeaders32"],"kind":"struct"},"787":{"crate_id":2,"path":["core","option","Item"],"kind":"struct"},"1668":{"crate_id":16,"path":["gimli","common","DebugInfoOffset"],"kind":"struct"},"1995":{"crate_id":17,"path":["object","read","elf","version","VerdauxIterator"],"kind":"struct"},"233":{"crate_id":1,"path":["std","path","Display"],"kind":"struct"},"1114":{"crate_id":2,"path":["core","ptr","metadata","Pointee"],"kind":"trait"},"1441":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_pin_desc"],"kind":"struct"},"560":{"crate_id":1,"path":["std","os","unix","fs","DirEntryExt"],"kind":"trait"},"2322":{"crate_id":18,"path":["memchr","arch","all","rabinkarp","Finder"],"kind":"struct"},"887":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1768":{"crate_id":16,"path":["gimli","read","dwarf","DwarfSections"],"kind":"struct"},"2095":{"crate_id":17,"path":["object","read","SymbolMap"],"kind":"struct"},"333":{"crate_id":1,"path":["std","sys","process","unix","common","CommandArgs"],"kind":"struct"},"1214":{"crate_id":3,"path":["alloc","collections","btree","map","IntoValues"],"kind":"struct"},"660":{"crate_id":2,"path":["core","ffi","c_str","CStr"],"kind":"struct"},"1541":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","flock64"],"kind":"struct"},"2422":{"crate_id":2,"path":["core","ptr","with_exposed_provenance_mut"],"kind":"function"},"106":{"crate_id":1,"path":["std","thread","ThreadId"],"kind":"struct"},"987":{"crate_id":2,"path":["core","sync","atomic","AtomicIsize"],"kind":"struct"},"1868":{"crate_id":16,"path":["gimli","read","unit","AttrsIter"],"kind":"struct"},"2195":{"crate_id":17,"path":["object","macho","VersionMinCommand"],"kind":"struct"},"433":{"crate_id":2,"path":["core","iter","adapters","enumerate","Enumerate"],"kind":"struct"},"1314":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_storage"],"kind":"struct"},"760":{"crate_id":2,"path":["core","array","TryFromSliceError"],"kind":"struct"},"1641":{"crate_id":10,"path":["hashbrown","scopeguard","ScopeGuard"],"kind":"struct"},"206":{"crate_id":1,"path":["std","net","Shutdown"],"kind":"enum"},"1087":{"crate_id":2,"path":["core","random","Distribution"],"kind":"trait"},"1968":{"crate_id":17,"path":["object","read","elf","section","ElfSectionIterator"],"kind":"struct"},"1414":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aes_gcm_256"],"kind":"struct"},"533":{"crate_id":3,"path":["alloc","collections","vec_deque","VecDeque"],"kind":"struct"},"2295":{"crate_id":17,"path":["object","xcoff","SymbolBytes"],"kind":"struct"},"860":{"crate_id":2,"path":["core","escape","MaybeEscapedCharacter"],"kind":"union"},"1741":{"crate_id":16,"path":["gimli","read","cfi","EhFrameHdr"],"kind":"struct"},"2068":{"crate_id":17,"path":["object","read","pe","resource","ResourceDirectoryEntryData"],"kind":"enum"},"306":{"crate_id":1,"path":["std","sys","pal","unix","linux","pidfd","PidFd"],"kind":"struct"},"1187":{"crate_id":3,"path":["alloc","collections","btree","navigate","LazyLeafHandle"],"kind":"enum"},"1514":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","seminfo"],"kind":"struct"},"633":{"crate_id":2,"path":["core","ptr","metadata","DynMetadata"],"kind":"struct"},"2395":{"crate_id":2,"path":["core","fmt","Result"],"kind":"type_alias"},"960":{"crate_id":2,"path":["core","ops","try_trait","Yeet"],"kind":"struct"},"1841":{"crate_id":16,"path":["gimli","read","op","Evaluation"],"kind":"struct"},"2168":{"crate_id":17,"path":["object","macho","FvmlibCommand"],"kind":"struct"},"406":{"crate_id":2,"path":["core","error","private","Internal"],"kind":"struct"},"1287":{"crate_id":5,"path":["libc","unix","linux_like","ip_mreq"],"kind":"struct"},"1614":{"crate_id":10,"path":["hashbrown","map","IntoIter"],"kind":"struct"},"733":{"crate_id":2,"path":["core","core_arch","simd","f64x4"],"kind":"struct"},"179":{"crate_id":1,"path":["std","io","error","Custom"],"kind":"struct"},"1060":{"crate_id":2,"path":["core","str","pattern","ReverseSearcher"],"kind":"trait"},"1941":{"crate_id":17,"path":["object","read","archive","ArchiveSymbolIterator"],"kind":"struct"},"2268":{"crate_id":17,"path":["object","pe","ImageHotPatchInfo"],"kind":"struct"},"506":{"crate_id":2,"path":["core","slice","iter","SplitInclusiveMut"],"kind":"struct"},"1387":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_data"],"kind":"struct"},"1714":{"crate_id":16,"path":["gimli","constants","DwLang"],"kind":"struct"},"833":{"crate_id":2,"path":["core","str","iter","EscapeUnicode"],"kind":"struct"},"279":{"crate_id":1,"path":["std","sync","reentrant_lock","ReentrantLock"],"kind":"struct"},"1160":{"crate_id":3,"path":["alloc","vec","splice","Splice"],"kind":"struct"},"2041":{"crate_id":17,"path":["object","read","macho","symbol","MachOSymbolIterator"],"kind":"struct"},"2368":{"crate_id":18,"path":["memchr","memmem","FinderRev"],"kind":"struct"},"606":{"crate_id":2,"path":["core","num","niche_types","Nanoseconds"],"kind":"struct"},"1487":{"crate_id":5,"path":["libc","unix","linux_like","linux","sock_txtime"],"kind":"struct"},"933":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"52":{"crate_id":0,"path":["kne_test_calculator","Operation","Subtract"],"kind":"variant"},"1814":{"crate_id":16,"path":["gimli","read","loclists","DebugLocLists"],"kind":"struct"},"379":{"crate_id":1,"path":["std","sys","fs","unix","Dir"],"kind":"struct"},"1260":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","j1939","j1939_filter"],"kind":"struct"},"2141":{"crate_id":17,"path":["object","elf","NoteHeader64"],"kind":"struct"},"1587":{"crate_id":10,"path":["hashbrown","raw","Bucket"],"kind":"struct"},"706":{"crate_id":2,"path":["core","core_arch","simd","f64x1"],"kind":"struct"},"2468":{"crate_id":1,"path":["std","fn"],"kind":"primitive"},"1033":{"crate_id":2,"path":["core","array","drain","Drain"],"kind":"struct"},"152":{"crate_id":1,"path":["std","error","Report"],"kind":"struct"},"1914":{"crate_id":17,"path":["object","read","any","SegmentIteratorInternal"],"kind":"enum"},"479":{"crate_id":2,"path":["core","fmt","Arguments"],"kind":"struct"},"1360":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_abs_setup"],"kind":"struct"},"2241":{"crate_id":17,"path":["object","pe","ImageBaseRelocation"],"kind":"struct"},"1687":{"crate_id":16,"path":["gimli","common","UnitSectionOffset"],"kind":"enum"},"806":{"crate_id":2,"path":["core","slice","ascii","EscapeByte"],"kind":"struct"},"1133":{"crate_id":3,"path":["alloc","collections","btree","map","IntoIter"],"kind":"struct"},"252":{"crate_id":1,"path":["std","sync","mpmc","list","ListToken"],"kind":"struct"},"2014":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheSlideInfo"],"kind":"enum"},"2341":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","Three"],"kind":"struct"},"579":{"crate_id":2,"path":["core","random","RandomSource"],"kind":"trait"},"1460":{"crate_id":5,"path":["libc","unix","linux_like","linux","dmabuf_token"],"kind":"struct"},"1787":{"crate_id":16,"path":["gimli","read","abbrev","Attributes"],"kind":"enum"},"906":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"352":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","Cache"],"kind":"struct"},"1233":{"crate_id":3,"path":["alloc","collections","btree","set","CursorMut"],"kind":"struct"},"2114":{"crate_id":17,"path":["object","elf","FileHeader32"],"kind":"struct"},"2441":{"crate_id":19,"path":["panic_unwind"],"kind":"module"},"679":{"crate_id":2,"path":["core","str","error","ParseBoolError"],"kind":"struct"},"1560":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","ucontext_t"],"kind":"struct"},"1887":{"crate_id":17,"path":["object","common","SymbolScope"],"kind":"enum"},"125":{"crate_id":1,"path":["std","collections","hash","map","ValuesMut"],"kind":"struct"},"1006":{"crate_id":2,"path":["core","ptr","metadata","Thin"],"kind":"trait_alias"},"452":{"crate_id":2,"path":["core","cmp","Ord"],"kind":"trait"},"1333":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_req3"],"kind":"struct"},"2214":{"crate_id":17,"path":["object","pe","ImageVxdHeader"],"kind":"struct"},"779":{"crate_id":2,"path":["core","iter","sources","once","Once"],"kind":"struct"},"1660":{"crate_id":16,"path":["gimli","common","Encoding"],"kind":"struct"},"1106":{"crate_id":2,"path":["core","core_simd","cast","SimdCast"],"kind":"trait"},"225":{"crate_id":1,"path":["std","path","fmt","DebugHelper"],"kind":"struct"},"1987":{"crate_id":17,"path":["object","read","elf","note","GnuPropertyIterator"],"kind":"struct"},"552":{"crate_id":3,"path":["alloc","vec","into_iter","IntoIter"],"kind":"struct"},"1433":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_priv_args"],"kind":"struct"},"2314":{"crate_id":18,"path":["memchr","arch","all","memchr","One"],"kind":"struct"},"1760":{"crate_id":16,"path":["gimli","read","cfi","UnwindTableRow"],"kind":"struct"},"879":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1206":{"crate_id":3,"path":["alloc","ffi","c_str","IntoStringError"],"kind":"struct"},"325":{"crate_id":1,"path":["std","sys","net","connection","socket","UdpSocket"],"kind":"struct"},"2087":{"crate_id":17,"path":["object","read","xcoff","segment","XcoffSegment"],"kind":"struct"},"652":{"crate_id":2,"path":["core","cell","once","OnceCell"],"kind":"struct"},"1533":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","sigset_t"],"kind":"struct"},"2414":{"crate_id":2,"path":["core","intrinsics","disjoint_bitor"],"kind":"function"},"1860":{"crate_id":16,"path":["gimli","read","UnitOffset"],"kind":"struct"},"98":{"crate_id":1,"path":["std","thread","scoped","Scope"],"kind":"struct"},"979":{"crate_id":2,"path":["core","sync","atomic","AtomicI8"],"kind":"struct"},"1306":{"crate_id":5,"path":["libc","unix","linux_like","mmsghdr"],"kind":"struct"},"425":{"crate_id":2,"path":["core","iter","adapters","step_by","StepBy"],"kind":"struct"},"2187":{"crate_id":17,"path":["object","macho","TwolevelHint"],"kind":"struct"},"752":{"crate_id":2,"path":["core","core_simd","masks","mask_impl","Mask"],"kind":"struct"},"1633":{"crate_id":10,"path":["hashbrown","rustc_entry","RustcVacantEntry"],"kind":"struct"},"1960":{"crate_id":17,"path":["object","read","coff","import","ImportFile"],"kind":"struct"},"198":{"crate_id":1,"path":["std","io","Bytes"],"kind":"struct"},"1079":{"crate_id":2,"path":["core","async_iter","async_iter","IntoAsyncIterator"],"kind":"trait"},"1406":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_sndinfo"],"kind":"struct"},"525":{"crate_id":3,"path":["alloc","slice","Join"],"kind":"trait"},"2287":{"crate_id":17,"path":["object","pe","ImportObjectHeader"],"kind":"struct"},"852":{"crate_id":2,"path":["core","str","UnsafeBytesToStr"],"kind":"struct"},"1733":{"crate_id":16,"path":["gimli","endianity","BigEndian"],"kind":"struct"},"2060":{"crate_id":17,"path":["object","read","pe","import","Import"],"kind":"enum"},"298":{"crate_id":1,"path":["std","sync","poison","rwlock","MappedRwLockReadGuard"],"kind":"struct"},"1179":{"crate_id":3,"path":["alloc","collections","btree","map","Keys"],"kind":"struct"},"625":{"crate_id":2,"path":["core","num","niche_types","CodePointInner"],"kind":"struct"},"1506":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","nl_pktinfo"],"kind":"struct"},"2387":{"crate_id":2,"path":["core","mem","forget"],"kind":"function"},"71":{"crate_id":0,"path":["kne_test_calculator","Point"],"kind":"struct"},"952":{"crate_id":2,"path":["core","core_arch","x86","__m512h"],"kind":"struct"},"1833":{"crate_id":16,"path":["gimli","read","op","OperationEvaluationResult"],"kind":"enum"},"1279":{"crate_id":5,"path":["libc","unix","linger"],"kind":"struct"},"398":{"crate_id":1,"path":["std","sync","mpmc","counter","Sender"],"kind":"struct"},"2160":{"crate_id":17,"path":["object","macho","MachHeader64"],"kind":"struct"},"725":{"crate_id":2,"path":["core","core_arch","simd","u32x8"],"kind":"struct"},"1606":{"crate_id":10,"path":["hashbrown","TryReserveError"],"kind":"enum"},"171":{"crate_id":1,"path":["std","io","buffered","bufwriter","BufWriter"],"kind":"struct"},"1052":{"crate_id":2,"path":["core","pin","PinCoerceUnsized"],"kind":"trait"},"1933":{"crate_id":17,"path":["object","read","any","SectionRelocationIteratorInternal"],"kind":"enum"},"1379":{"crate_id":5,"path":["libc","unix","linux_like","linux","arpd_request"],"kind":"struct"},"498":{"crate_id":2,"path":["core","slice","iter","RChunksMut"],"kind":"struct"},"2260":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation32"],"kind":"struct"},"825":{"crate_id":2,"path":["core","str","iter","Lines"],"kind":"struct"},"1706":{"crate_id":16,"path":["gimli","constants","DwForm"],"kind":"struct"},"2033":{"crate_id":17,"path":["object","read","macho","segment","MachOSegmentIterator"],"kind":"struct"},"271":{"crate_id":1,"path":["std","sync","mpsc","SendError"],"kind":"struct"},"1152":{"crate_id":3,"path":["alloc","slice","to_vec_in","to_vec","DropGuard"],"kind":"struct"},"1479":{"crate_id":5,"path":["libc","unix","linux_like","linux","sched_attr"],"kind":"struct"},"598":{"crate_id":2,"path":["core","num","flt2dec","Sign"],"kind":"enum"},"2360":{"crate_id":18,"path":["memchr","memmem","searcher","SearcherRevKind"],"kind":"enum"},"44":{"crate_id":2,"path":["core","convert","Infallible"],"kind":"enum"},"925":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1806":{"crate_id":16,"path":["gimli","read","line","LineSequence"],"kind":"struct"},"2133":{"crate_id":17,"path":["object","elf","Dyn32"],"kind":"struct"},"371":{"crate_id":2,"path":["core","ops","drop","Drop"],"kind":"trait"},"1252":{"crate_id":4,"path":["compiler_builtins","int","big","i256"],"kind":"struct"},"1579":{"crate_id":9,"path":["adler2","Adler32"],"kind":"struct"},"698":{"crate_id":2,"path":["core","core_arch","simd","u16x4"],"kind":"struct"},"2460":{"crate_id":1,"path":["std","u8"],"kind":"primitive"},"144":{"crate_id":1,"path":["std","collections","hash","set","VacantEntry"],"kind":"struct"},"1025":{"crate_id":2,"path":["core","ops","bit","BitAndAssign"],"kind":"trait"},"1906":{"crate_id":17,"path":["object","read","read_cache","ReadCacheRange"],"kind":"struct"},"2233":{"crate_id":17,"path":["object","pe","ImageAuxSymbolTokenDef"],"kind":"struct"},"471":{"crate_id":2,"path":["core","iter","traits","marker","FusedIterator"],"kind":"trait"},"1352":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_constant_effect"],"kind":"struct"},"798":{"crate_id":2,"path":["core","hash","sip","SipHasher13"],"kind":"struct"},"1679":{"crate_id":16,"path":["gimli","common","DebugRngListsIndex"],"kind":"struct"},"244":{"crate_id":1,"path":["std","process","ExitStatusError"],"kind":"struct"},"1125":{"crate_id":3,"path":["alloc","boxed","thin","ThinBox"],"kind":"struct"},"2006":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldSubCacheSlice"],"kind":"enum"},"1452":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_options"],"kind":"struct"},"571":{"crate_id":1,"path":["std","os","unix","process","ExitStatusExt"],"kind":"trait"},"2333":{"crate_id":18,"path":["memchr","arch","generic","memchr","Two"],"kind":"struct"},"898":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"17":{"crate_id":2,"path":["core","marker","Freeze"],"kind":"trait"},"1779":{"crate_id":16,"path":["gimli","read","endian_slice","DebugLen"],"kind":"struct"},"344":{"crate_id":1,"path":["std","backtrace_rs","types","BytesOrWideString"],"kind":"enum"},"1225":{"crate_id":3,"path":["alloc","collections","btree","map","CursorMut"],"kind":"struct"},"2106":{"crate_id":17,"path":["object","read","RelocationMapEntry"],"kind":"struct"},"1552":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","user_regs_struct"],"kind":"struct"},"671":{"crate_id":2,"path":["core","range","RangeToInclusive"],"kind":"struct"},"2433":{"crate_id":11,"path":["rustc_std_workspace_alloc"],"kind":"module"},"998":{"crate_id":2,"path":["core","cell","SyncUnsafeCell"],"kind":"struct"},"117":{"crate_id":1,"path":["std","collections","hash","map","Keys"],"kind":"struct"},"1879":{"crate_id":16,"path":["gimli","read","Error"],"kind":"enum"},"2206":{"crate_id":17,"path":["object","macho","NoteCommand"],"kind":"struct"},"444":{"crate_id":2,"path":["core","iter","adapters","fuse","Fuse"],"kind":"struct"},"1325":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanout_args"],"kind":"struct"},"1652":{"crate_id":13,"path":["rustc_demangle","SizeLimitedFmtAdapter"],"kind":"struct"},"771":{"crate_id":2,"path":["core","ffi","c_str","Bytes"],"kind":"struct"},"1098":{"crate_id":2,"path":["core","core_simd","swizzle","shift_elements_right","Shift"],"kind":"struct"},"217":{"crate_id":1,"path":["std","os","fd","owned","OwnedFd"],"kind":"struct"},"1979":{"crate_id":17,"path":["object","read","elf","relocation","CrelIteratorHeader"],"kind":"struct"},"2306":{"crate_id":17,"path":["object","xcoff","BlockAux64"],"kind":"struct"},"544":{"crate_id":2,"path":["core","net","socket_addr","SocketAddr"],"kind":"enum"},"1425":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_discarded"],"kind":"struct"},"1752":{"crate_id":16,"path":["gimli","read","cfi","AugmentationData"],"kind":"struct"},"871":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"317":{"crate_id":1,"path":["std","sys","fs","unix","FilePermissions"],"kind":"struct"},"1198":{"crate_id":3,"path":["alloc","collections","linked_list","Iter"],"kind":"struct"},"2079":{"crate_id":17,"path":["object","read","xcoff","symbol","XcoffSymbolTable"],"kind":"struct"},"2406":{"crate_id":2,"path":["core","format_args"],"kind":"macro"},"644":{"crate_id":2,"path":["core","ops","coroutine","CoroutineState"],"kind":"enum"},"1525":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","sem_t"],"kind":"struct"},"971":{"crate_id":2,"path":["core","option","IterMut"],"kind":"struct"},"90":{"crate_id":0,"path":["kne_test_calculator","sum_all"],"kind":"function"},"1852":{"crate_id":16,"path":["gimli","read","rnglists","RawRngListIter"],"kind":"struct"},"417":{"crate_id":2,"path":["core","ops","index","Index"],"kind":"trait"},"1298":{"crate_id":5,"path":["libc","unix","linux_like","Dl_info"],"kind":"struct"},"2179":{"crate_id":17,"path":["object","macho","RoutinesCommand64"],"kind":"struct"},"744":{"crate_id":2,"path":["core","core_arch","simd","f32x16"],"kind":"struct"},"1625":{"crate_id":10,"path":["hashbrown","map","Entry"],"kind":"enum"},"1071":{"crate_id":2,"path":["core","ops","deref","Receiver"],"kind":"trait"},"190":{"crate_id":1,"path":["std","io","util","Empty"],"kind":"struct"},"1952":{"crate_id":17,"path":["object","read","coff","symbol","SymbolIterator"],"kind":"struct"},"517":{"crate_id":2,"path":["core","core_simd","lane_count","SupportedLaneCount"],"kind":"trait"},"1398":{"crate_id":5,"path":["libc","unix","linux_like","linux","open_how"],"kind":"struct"},"2279":{"crate_id":17,"path":["object","pe","ImageDebugDirectory"],"kind":"struct"},"1725":{"crate_id":16,"path":["gimli","constants","DwLnct"],"kind":"struct"},"844":{"crate_id":2,"path":["core","str","LinesMap"],"kind":"struct"},"1171":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Immut"],"kind":"struct"},"290":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","MappedRwLockWriteGuard"],"kind":"struct"},"2052":{"crate_id":17,"path":["object","read","pe","section","PeRelocationIterator"],"kind":"struct"},"2379":{"crate_id":1,"path":["std","path","absolute"],"kind":"function"},"617":{"crate_id":2,"path":["core","num","niche_types","NonZeroCharInner"],"kind":"struct"},"1498":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__exit_status"],"kind":"struct"},"1825":{"crate_id":16,"path":["gimli","read","macros","DebugMacinfo"],"kind":"struct"},"944":{"crate_id":2,"path":["core","core_arch","x86","__m256bh"],"kind":"struct"},"1271":{"crate_id":5,"path":["libc","unix","timeval"],"kind":"struct"},"390":{"crate_id":1,"path":["std","backtrace_rs","print","BacktraceFrameFmt"],"kind":"struct"},"2152":{"crate_id":17,"path":["object","macho","DyldCacheSlideInfo5"],"kind":"struct"},"717":{"crate_id":2,"path":["core","core_arch","simd","f32x4"],"kind":"struct"},"1598":{"crate_id":10,"path":["hashbrown","set","Iter"],"kind":"struct"},"1925":{"crate_id":17,"path":["object","read","any","SymbolTable"],"kind":"struct"},"163":{"crate_id":1,"path":["std","fs","File"],"kind":"struct"},"1044":{"crate_id":2,"path":["core","intrinsics","fallback","FunnelShift"],"kind":"trait"},"490":{"crate_id":2,"path":["core","slice","iter","IterMut"],"kind":"struct"},"1371":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf64_rel"],"kind":"struct"},"2252":{"crate_id":17,"path":["object","pe","ImageDelayloadDescriptor"],"kind":"struct"},"817":{"crate_id":2,"path":["core","str","iter","SplitN"],"kind":"struct"},"1698":{"crate_id":16,"path":["gimli","arch","PowerPc64"],"kind":"struct"},"1144":{"crate_id":3,"path":["alloc","collections","vec_deque","write_iter_wrapping","Guard"],"kind":"struct"},"263":{"crate_id":1,"path":["std","sync","mpsc","TryIter"],"kind":"struct"},"2025":{"crate_id":17,"path":["object","read","macho","fat","MachOFatFile"],"kind":"struct"},"590":{"crate_id":2,"path":["core","num","bignum","tests","Big8x3"],"kind":"struct"},"1471":{"crate_id":5,"path":["libc","unix","linux_like","linux","af_alg_iv"],"kind":"struct"},"2352":{"crate_id":18,"path":["memchr","cow","Imp"],"kind":"struct"},"917":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1798":{"crate_id":16,"path":["gimli","read","index","UnitIndexSection"],"kind":"struct"},"1244":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Mut"],"kind":"struct"},"363":{"crate_id":1,"path":["std","sys","thread_local","native","eager","State"],"kind":"enum"},"2125":{"crate_id":17,"path":["object","elf","Rel32"],"kind":"struct"},"690":{"crate_id":2,"path":["core","alloc","layout","LayoutError"],"kind":"struct"},"1571":{"crate_id":8,"path":["miniz_oxide","inflate","core","LocalVars"],"kind":"struct"},"2452":{"crate_id":1,"path":["std","f32"],"kind":"primitive"},"1898":{"crate_id":17,"path":["object","endian","U16Bytes"],"kind":"struct"},"136":{"crate_id":2,"path":["core","cmp","Eq"],"kind":"trait"},"1017":{"crate_id":2,"path":["core","ops","arith","DivAssign"],"kind":"trait"},"1344":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_event"],"kind":"struct"},"463":{"crate_id":1,"path":["std","io","Read"],"kind":"trait"},"2225":{"crate_id":17,"path":["object","pe","AnonObjectHeader"],"kind":"struct"},"790":{"crate_id":2,"path":["core","range","iter","IterRangeInclusive"],"kind":"struct"},"1671":{"crate_id":16,"path":["gimli","common","LocationListsOffset"],"kind":"struct"},"1998":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSection"],"kind":"struct"},"236":{"crate_id":1,"path":["std","process","ChildStdout"],"kind":"struct"},"1117":{"crate_id":2,"path":["core","core_simd","simd","num","int","SimdInt"],"kind":"trait"},"1444":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_ring_offset"],"kind":"struct"},"563":{"crate_id":1,"path":["std","os","net","linux_ext","addr","SocketAddrExt"],"kind":"trait"},"2325":{"crate_id":18,"path":["memchr","arch","all","twoway","Finder"],"kind":"struct"},"890":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1771":{"crate_id":16,"path":["gimli","read","dwarf","DwarfPackage"],"kind":"struct"},"2098":{"crate_id":17,"path":["object","read","ObjectMapEntry"],"kind":"struct"},"336":{"crate_id":1,"path":["std","sys","process","env","CommandEnv"],"kind":"struct"},"1217":{"crate_id":3,"path":["alloc","collections","vec_deque","iter_mut","IterMut"],"kind":"struct"},"663":{"crate_id":2,"path":["core","net","ip_addr","Ipv6MulticastScope"],"kind":"enum"},"1544":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","stat"],"kind":"struct"},"2425":{"crate_id":3,"path":["alloc"],"kind":"module"},"109":{"crate_id":1,"path":["std","backtrace","BacktraceStatus"],"kind":"enum"},"990":{"crate_id":2,"path":["core","sync","atomic","AtomicPtr"],"kind":"struct"},"1871":{"crate_id":16,"path":["gimli","read","unit","EntriesTree"],"kind":"struct"},"2198":{"crate_id":17,"path":["object","macho","DyldInfoCommand"],"kind":"struct"},"436":{"crate_id":2,"path":["core","iter","adapters","take_while","TakeWhile"],"kind":"struct"},"1317":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_versions"],"kind":"enum"},"763":{"crate_id":2,"path":["core","cell","BorrowRef"],"kind":"struct"},"1644":{"crate_id":13,"path":["rustc_demangle","legacy","Demangle"],"kind":"struct"},"209":{"crate_id":1,"path":["std","os","unix","net","ancillary","SocketAncillary"],"kind":"struct"},"1090":{"crate_id":2,"path":["core","slice","private_get_disjoint_mut_index","Sealed"],"kind":"trait"},"1971":{"crate_id":17,"path":["object","read","elf","symbol","ElfSymbolTable"],"kind":"struct"},"1417":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_sm4_gcm"],"kind":"struct"},"536":{"crate_id":1,"path":["std","sys","stdio","unix","Stdin"],"kind":"struct"},"2298":{"crate_id":17,"path":["object","xcoff","FileAux32"],"kind":"struct"},"863":{"crate_id":2,"path":["core","escape","EscapeIterInner"],"kind":"struct"},"1744":{"crate_id":16,"path":["gimli","read","cfi","EhHdrTable"],"kind":"struct"},"2071":{"crate_id":17,"path":["object","read","pe","rich","RichHeaderInfo"],"kind":"struct"},"309":{"crate_id":1,"path":["std","sys","pal","unix","time","SystemTime"],"kind":"struct"},"1190":{"crate_id":3,"path":["alloc","collections","btree","set","Iter"],"kind":"struct"},"1517":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_exit"],"kind":"struct"},"636":{"crate_id":2,"path":["core","marker","variance","PhantomCovariantLifetime"],"kind":"struct"},"2398":{"crate_id":2,"path":["core","ptr","drop_in_place"],"kind":"function"},"963":{"crate_id":2,"path":["core","asserting","TryCaptureWithDebug"],"kind":"struct"},"1844":{"crate_id":16,"path":["gimli","read","pubnames","PubNamesEntryIter"],"kind":"struct"},"2171":{"crate_id":17,"path":["object","macho","SubFrameworkCommand"],"kind":"struct"},"409":{"crate_id":1,"path":["std","thread","thread_name_string","ThreadNameString"],"kind":"struct"},"1290":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr"],"kind":"struct"},"1617":{"crate_id":10,"path":["hashbrown","set","IntoIter"],"kind":"struct"},"736":{"crate_id":2,"path":["core","core_arch","simd","m32x8"],"kind":"struct"},"182":{"crate_id":1,"path":["std","io","pipe","PipeReader"],"kind":"struct"},"1063":{"crate_id":2,"path":["core","ops","async_function","AsyncFnMut"],"kind":"trait"},"1944":{"crate_id":17,"path":["object","read","coff","file","CoffCommon"],"kind":"struct"},"2271":{"crate_id":17,"path":["object","pe","ImageArmRuntimeFunctionEntry"],"kind":"struct"},"509":{"crate_id":2,"path":["core","slice","iter","SplitN"],"kind":"struct"},"1390":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif_resp"],"kind":"struct"},"836":{"crate_id":2,"path":["core","str","pattern","CharArraySearcher"],"kind":"struct"},"1717":{"crate_id":16,"path":["gimli","constants","DwCc"],"kind":"struct"},"282":{"crate_id":1,"path":["std","sync","nonpoison","condvar","Condvar"],"kind":"struct"},"1163":{"crate_id":3,"path":["alloc","vec","into_iter","drop","DropGuard"],"kind":"struct"},"2044":{"crate_id":17,"path":["object","read","pe","file","PeFile"],"kind":"struct"},"2371":{"crate_id":18,"path":["memchr","arch","all","twoway","Suffix"],"kind":"struct"},"609":{"crate_id":2,"path":["core","num","niche_types","NonZeroU32Inner"],"kind":"struct"},"1490":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_iwreq"],"kind":"union"},"936":{"crate_id":2,"path":["core","core_arch","x86","__m512i"],"kind":"struct"},"55":{"crate_id":0,"path":["kne_test_calculator","Operation"],"kind":"enum"},"1817":{"crate_id":16,"path":["gimli","read","loclists","RawLocListIter"],"kind":"struct"},"382":{"crate_id":1,"path":["std","sys","process","unix","unix","posix_spawn","PosixSpawnFileActions"],"kind":"struct"},"1263":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","canxl_frame"],"kind":"struct"},"2144":{"crate_id":17,"path":["object","macho","PtrauthKey"],"kind":"enum"},"1590":{"crate_id":10,"path":["hashbrown","raw","RawIter"],"kind":"struct"},"709":{"crate_id":2,"path":["core","core_arch","simd","u32x4"],"kind":"struct"},"1036":{"crate_id":2,"path":["core","cell","BorrowRefMut"],"kind":"struct"},"155":{"crate_id":1,"path":["std","ffi","os_str","OsStr"],"kind":"struct"},"1917":{"crate_id":17,"path":["object","read","any","SectionIterator"],"kind":"struct"},"2244":{"crate_id":17,"path":["object","pe","ImageImportByName"],"kind":"struct"},"482":{"crate_id":2,"path":["core","ops","deref","Deref"],"kind":"trait"},"1363":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Ehdr"],"kind":"struct"},"1690":{"crate_id":16,"path":["gimli","common","DwarfFileType"],"kind":"enum"},"809":{"crate_id":2,"path":["core","str","iter","Bytes"],"kind":"struct"},"1136":{"crate_id":3,"path":["alloc","collections","btree","node","drop_key_val","Dropper"],"kind":"struct"},"255":{"crate_id":1,"path":["std","sync","mpmc","select","Selected"],"kind":"enum"},"2017":{"crate_id":17,"path":["object","read","macho","dyld_cache","RelocationStateV2"],"kind":"enum"},"2344":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","One"],"kind":"struct"},"582":{"crate_id":2,"path":["core","ops","arith","SubAssign"],"kind":"trait"},"1463":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_michaelmicfailure"],"kind":"struct"},"1790":{"crate_id":16,"path":["gimli","read","aranges","ArangeHeaderIter"],"kind":"struct"},"909":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"355":{"crate_id":1,"path":["std","os","linux","raw","arch","stat"],"kind":"struct"},"1236":{"crate_id":3,"path":["alloc","collections","linked_list","ExtractIf"],"kind":"struct"},"2117":{"crate_id":17,"path":["object","elf","SectionHeader32"],"kind":"struct"},"2444":{"crate_id":1,"path":["std","char"],"kind":"primitive"},"682":{"crate_id":2,"path":["core","str","lossy","Utf8Chunk"],"kind":"struct"},"1563":{"crate_id":5,"path":["libc","unix","linux_like","linux","arch","generic","termios2"],"kind":"struct"},"1890":{"crate_id":17,"path":["object","common","FileFlags"],"kind":"enum"},"128":{"crate_id":1,"path":["std","collections","hash","map","Drain"],"kind":"struct"},"1009":{"crate_id":2,"path":["core","fmt","Binary"],"kind":"trait"},"455":{"crate_id":2,"path":["core","iter","adapters","copied","Copied"],"kind":"struct"},"1336":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_stats_v3"],"kind":"struct"},"2217":{"crate_id":17,"path":["object","pe","ImageDataDirectory"],"kind":"struct"},"782":{"crate_id":2,"path":["core","iter","sources","repeat_n","RepeatNInner"],"kind":"struct"},"1663":{"crate_id":16,"path":["gimli","common","DebugAbbrevOffset"],"kind":"struct"},"1109":{"crate_id":2,"path":["core","core_simd","masks","sealed","Sealed"],"kind":"trait"},"228":{"crate_id":1,"path":["std","path","Ancestors"],"kind":"struct"},"1990":{"crate_id":17,"path":["object","read","elf","hash","GnuHashTable"],"kind":"struct"},"555":{"crate_id":1,"path":["std","os","unix","fs","FileExt"],"kind":"trait"},"1436":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_rwlockattr_t"],"kind":"struct"},"2317":{"crate_id":18,"path":["memchr","arch","all","memchr","TwoIter"],"kind":"struct"},"1763":{"crate_id":16,"path":["gimli","read","cfi","CallFrameInstruction"],"kind":"enum"},"882":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1209":{"crate_id":3,"path":["alloc","wtf8","Wtf8Buf"],"kind":"struct"},"328":{"crate_id":1,"path":["std","sys","process","unix","common","cstring_array","CStringArray"],"kind":"struct"},"2090":{"crate_id":17,"path":["object","read","FileKind"],"kind":"enum"},"2417":{"crate_id":2,"path":["core","core_simd","simd","ptr","const_ptr","SimdConstPtr","with_addr"],"kind":"function"},"655":{"crate_id":2,"path":["core","char","convert","ParseCharError"],"kind":"struct"},"1536":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","semid_ds"],"kind":"struct"},"1863":{"crate_id":16,"path":["gimli","read","unit","UnitType"],"kind":"enum"},"101":{"crate_id":2,"path":["core","fmt","Debug"],"kind":"trait"},"982":{"crate_id":2,"path":["core","sync","atomic","AtomicU16"],"kind":"struct"},"1309":{"crate_id":5,"path":["libc","unix","linux_like","sock_fprog"],"kind":"struct"},"428":{"crate_id":2,"path":["core","iter","adapters","intersperse","Intersperse"],"kind":"struct"},"2190":{"crate_id":17,"path":["object","macho","RpathCommand"],"kind":"struct"},"755":{"crate_id":2,"path":["core","num","diy_float","Fp"],"kind":"struct"},"1636":{"crate_id":10,"path":["hashbrown","set","VacantEntry"],"kind":"struct"},"1963":{"crate_id":17,"path":["object","read","coff","import","ImportObjectData"],"kind":"struct"},"201":{"crate_id":1,"path":["std","net","tcp","Incoming"],"kind":"struct"},"1082":{"crate_id":2,"path":["core","ffi","va_list","sealed","Sealed"],"kind":"trait"},"528":{"crate_id":2,"path":["core","clone","CloneToUninit"],"kind":"trait"},"1409":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_prinfo"],"kind":"struct"},"2290":{"crate_id":17,"path":["object","xcoff","FileHeader64"],"kind":"struct"},"855":{"crate_id":2,"path":["core","future","pending","Pending"],"kind":"struct"},"1736":{"crate_id":16,"path":["gimli","read","addr","DebugAddr"],"kind":"struct"},"2063":{"crate_id":17,"path":["object","read","pe","relocation","RelocationBlockIterator"],"kind":"struct"},"301":{"crate_id":1,"path":["std","sync","poison","TryLockError"],"kind":"enum"},"1182":{"crate_id":3,"path":["alloc","collections","btree","map","Cursor"],"kind":"struct"},"628":{"crate_id":2,"path":["core","mem","manually_drop","ManuallyDrop"],"kind":"struct"},"1509":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","rtentry"],"kind":"struct"},"2390":{"crate_id":2,"path":["core","ptr"],"kind":"module"},"955":{"crate_id":2,"path":["core","clone","UseCloned"],"kind":"trait"},"1836":{"crate_id":16,"path":["gimli","read","op","EvaluationState"],"kind":"enum"},"1282":{"crate_id":5,"path":["libc","unix","tms"],"kind":"struct"},"401":{"crate_id":1,"path":["std","sys","backtrace","print","DisplayBacktrace"],"kind":"struct"},"2163":{"crate_id":17,"path":["object","macho","SegmentCommand32"],"kind":"struct"},"728":{"crate_id":2,"path":["core","core_arch","simd","i16x16"],"kind":"struct"},"1609":{"crate_id":10,"path":["hashbrown","raw","RawDrain"],"kind":"struct"},"1936":{"crate_id":17,"path":["object","read","archive","ArchiveFile"],"kind":"struct"},"174":{"crate_id":1,"path":["std","io","buffered","linewritershim","LineWriterShim"],"kind":"struct"},"1055":{"crate_id":2,"path":["core","convert","num","FloatToInt"],"kind":"trait"},"1382":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_event_info_header"],"kind":"struct"},"501":{"crate_id":2,"path":["core","slice","iter","ChunkBy"],"kind":"struct"},"2263":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation64V2"],"kind":"struct"},"828":{"crate_id":2,"path":["core","str","iter","SplitAsciiWhitespace"],"kind":"struct"},"1709":{"crate_id":16,"path":["gimli","constants","DwDs"],"kind":"struct"},"2036":{"crate_id":17,"path":["object","read","macho","section","MachOSectionIterator"],"kind":"struct"},"274":{"crate_id":1,"path":["std","sync","once","OnceState"],"kind":"struct"},"1155":{"crate_id":3,"path":["alloc","sync","from_iter_exact","Guard"],"kind":"struct"},"1482":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_block_desc"],"kind":"struct"},"601":{"crate_id":2,"path":["core","num","error","ParseIntError"],"kind":"struct"},"2363":{"crate_id":18,"path":["memchr","memmem","searcher","PrefilterKind"],"kind":"union"},"928":{"crate_id":2,"path":["core","core_arch","x86","__m128d"],"kind":"struct"},"1809":{"crate_id":16,"path":["gimli","read","line","CompleteLineProgram"],"kind":"struct"},"2136":{"crate_id":17,"path":["object","elf","Verdef"],"kind":"struct"},"374":{"crate_id":1,"path":["std","sync","mpmc","waker","Waker"],"kind":"struct"},"1255":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","env","Round"],"kind":"enum"},"1582":{"crate_id":10,"path":["hashbrown","control","tag","Tag"],"kind":"struct"},"701":{"crate_id":2,"path":["core","core_arch","simd","i8x8"],"kind":"struct"},"2463":{"crate_id":1,"path":["std","u64"],"kind":"primitive"},"147":{"crate_id":1,"path":["std","env","VarError"],"kind":"enum"},"1028":{"crate_id":2,"path":["core","ops","bit","Shr"],"kind":"trait"},"1909":{"crate_id":17,"path":["object","read","util","DebugLen"],"kind":"struct"},"2236":{"crate_id":17,"path":["object","pe","ImageAuxSymbolWeak"],"kind":"struct"},"474":{"crate_id":2,"path":["core","ops","bit","BitXor"],"kind":"trait"},"1355":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_periodic_effect"],"kind":"struct"},"801":{"crate_id":2,"path":["core","hash","sip","State"],"kind":"struct"},"1682":{"crate_id":16,"path":["gimli","common","DebugStrOffsetsIndex"],"kind":"struct"},"247":{"crate_id":1,"path":["std","sync","mpmc","array","ArrayToken"],"kind":"struct"},"1128":{"crate_id":3,"path":["alloc","collections","binary_heap","RebuildOnDrop"],"kind":"struct"},"2009":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheImage"],"kind":"struct"},"1455":{"crate_id":5,"path":["libc","unix","linux_like","linux","xsk_tx_metadata_request"],"kind":"struct"},"574":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt"],"kind":"trait"},"2336":{"crate_id":18,"path":["memchr","arch","generic","packedpair","Finder"],"kind":"struct"},"901":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1782":{"crate_id":16,"path":["gimli","read","abbrev","DebugAbbrev"],"kind":"struct"},"347":{"crate_id":2,"path":["core","default","Default"],"kind":"trait"},"1228":{"crate_id":3,"path":["alloc","collections","btree","set","entry","OccupiedEntry"],"kind":"struct"},"2109":{"crate_id":17,"path":["object","read","CompressedData"],"kind":"struct"},"1555":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","ipc_perm"],"kind":"struct"},"674":{"crate_id":2,"path":["core","fmt","Alignment"],"kind":"enum"},"2436":{"crate_id":14,"path":["cfg_if"],"kind":"module"},"1001":{"crate_id":2,"path":["core","wtf8","fmt","CodeUnit"],"kind":"struct"},"120":{"crate_id":1,"path":["std","collections","hash","map","OccupiedEntry"],"kind":"struct"},"1882":{"crate_id":17,"path":["object","common","AddressSize"],"kind":"enum"},"2209":{"crate_id":17,"path":["object","macho","Relocation"],"kind":"struct"},"447":{"crate_id":2,"path":["core","ops","try_trait","Try"],"kind":"trait"},"1328":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_auxdata"],"kind":"struct"},"1655":{"crate_id":15,"path":["addr2line","unit","SupUnits"],"kind":"struct"},"774":{"crate_id":2,"path":["core","iter","adapters","map_windows","Buffer"],"kind":"struct"},"220":{"crate_id":1,"path":["std","path","Prefix"],"kind":"enum"},"1101":{"crate_id":2,"path":["core","core_simd","swizzle","deinterleave","Even"],"kind":"struct"},"1982":{"crate_id":17,"path":["object","read","elf","comdat","ElfComdatIterator"],"kind":"struct"},"2309":{"crate_id":17,"path":["object","xcoff","DwarfAux64"],"kind":"struct"},"547":{"crate_id":2,"path":["core","net","socket_addr","SocketAddrV4"],"kind":"struct"},"1428":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_encode_ext"],"kind":"struct"},"1755":{"crate_id":16,"path":["gimli","read","cfi","FrameDescriptionEntry"],"kind":"struct"},"874":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"320":{"crate_id":1,"path":["std","sys","fs","unix","DirBuilder"],"kind":"struct"},"1201":{"crate_id":3,"path":["alloc","collections","vec_deque","into_iter","IntoIter"],"kind":"struct"},"2082":{"crate_id":17,"path":["object","read","xcoff","relocation","XcoffRelocationIterator"],"kind":"struct"},"2409":{"crate_id":1,"path":["std","panic","always_abort"],"kind":"function"},"647":{"crate_id":2,"path":["core","ops","range","RangeTo"],"kind":"struct"},"1528":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fpos_t"],"kind":"struct"},"974":{"crate_id":2,"path":["core","panic","unwind_safe","AssertUnwindSafe"],"kind":"struct"},"93":{"crate_id":0,"path":["kne_test_calculator","find_max"],"kind":"function"},"1855":{"crate_id":16,"path":["gimli","read","rnglists","RawRange"],"kind":"struct"},"420":{"crate_id":2,"path":["core","iter","traits","iterator","Iterator"],"kind":"trait"},"1301":{"crate_id":5,"path":["libc","unix","linux_like","ifaddrs"],"kind":"struct"},"2182":{"crate_id":17,"path":["object","macho","DylibTableOfContents"],"kind":"struct"},"1628":{"crate_id":10,"path":["hashbrown","map","EntryRef"],"kind":"enum"},"747":{"crate_id":2,"path":["core","core_arch","simd","f64x8"],"kind":"struct"},"1074":{"crate_id":2,"path":["core","iter","adapters","zip","TrustedRandomAccess"],"kind":"trait"},"193":{"crate_id":1,"path":["std","io","IoSliceMut"],"kind":"struct"},"1955":{"crate_id":17,"path":["object","read","coff","symbol","CoffSymbol"],"kind":"struct"},"520":{"crate_id":2,"path":["core","slice","GetDisjointMutIndex"],"kind":"trait"},"1401":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_sys_offset_extended"],"kind":"struct"},"2282":{"crate_id":17,"path":["object","pe","ImageFunctionEntry"],"kind":"struct"},"1728":{"crate_id":16,"path":["gimli","constants","DwRle"],"kind":"struct"},"847":{"crate_id":2,"path":["core","str","CharEscapeDefault"],"kind":"struct"},"1174":{"crate_id":3,"path":["alloc","collections","binary_heap","BinaryHeap"],"kind":"struct"},"293":{"crate_id":1,"path":["std","sync","poison","mutex","MutexGuard"],"kind":"struct"},"2055":{"crate_id":17,"path":["object","read","pe","export","ExportTarget"],"kind":"enum"},"2382":{"crate_id":1,"path":["std","fs","read_dir"],"kind":"function"},"620":{"crate_id":2,"path":["core","num","niche_types","NonZeroIsizeInner"],"kind":"struct"},"1501":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","msghdr"],"kind":"struct"},"1828":{"crate_id":16,"path":["gimli","read","macros","MacroString"],"kind":"enum"},"947":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1274":{"crate_id":5,"path":["libc","unix","ipv6_mreq"],"kind":"struct"},"393":{"crate_id":2,"path":["core","marker","StructuralPartialEq"],"kind":"trait"},"2155":{"crate_id":17,"path":["object","macho","DyldSubCacheEntryV2"],"kind":"struct"},"720":{"crate_id":2,"path":["core","core_arch","simd","m16x8"],"kind":"struct"},"1601":{"crate_id":10,"path":["hashbrown","set","SymmetricDifference"],"kind":"struct"},"1928":{"crate_id":17,"path":["object","read","any","SymbolIteratorInternal"],"kind":"enum"},"166":{"crate_id":1,"path":["std","fs","DirEntry"],"kind":"struct"},"1047":{"crate_id":2,"path":["core","pin","helper","PinDerefMutHelper"],"kind":"trait"},"493":{"crate_id":2,"path":["core","slice","iter","ChunksMut"],"kind":"struct"},"1374":{"crate_id":5,"path":["libc","unix","linux_like","linux","mntent"],"kind":"struct"},"2255":{"crate_id":17,"path":["object","pe","ImageResourceDirectoryString"],"kind":"struct"},"820":{"crate_id":2,"path":["core","str","iter","MatchIndices"],"kind":"struct"},"1701":{"crate_id":16,"path":["gimli","constants","DwUt"],"kind":"struct"},"1147":{"crate_id":3,"path":["alloc","collections","vec_deque","truncate_front","Dropper"],"kind":"struct"},"266":{"crate_id":1,"path":["std","sync","mpsc","TryRecvError"],"kind":"enum"},"2028":{"crate_id":17,"path":["object","read","macho","file","MachOComdat"],"kind":"struct"},"593":{"crate_id":2,"path":["core","num","dec2flt","decimal_seq","DecimalSeq"],"kind":"struct"},"1474":{"crate_id":5,"path":["libc","unix","linux_like","linux","ifreq"],"kind":"struct"},"2355":{"crate_id":18,"path":["memchr","memchr","Memchr3"],"kind":"struct"},"920":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1801":{"crate_id":16,"path":["gimli","read","line","LineRows"],"kind":"struct"},"1247":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Dying"],"kind":"enum"},"366":{"crate_id":1,"path":["std","backtrace_rs","print","PrintFmt"],"kind":"enum"},"2128":{"crate_id":17,"path":["object","elf","Rela64"],"kind":"struct"},"693":{"crate_id":2,"path":["core","core_arch","simd","u8x4"],"kind":"struct"},"1574":{"crate_id":8,"path":["miniz_oxide","MZFlush"],"kind":"enum"},"2455":{"crate_id":1,"path":["std","i8"],"kind":"primitive"},"1901":{"crate_id":17,"path":["object","endian","I16Bytes"],"kind":"struct"},"139":{"crate_id":1,"path":["std","collections","hash","set","Difference"],"kind":"struct"},"1020":{"crate_id":2,"path":["core","ops","arith","Neg"],"kind":"trait"},"1347":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_keymap_entry"],"kind":"struct"},"466":{"crate_id":1,"path":["std","os","unix","net","ancillary","ScmRights"],"kind":"struct"},"2228":{"crate_id":17,"path":["object","pe","ImageSectionHeader"],"kind":"struct"},"793":{"crate_id":2,"path":["core","result","IntoIter"],"kind":"struct"},"1674":{"crate_id":16,"path":["gimli","common","DebugMacinfoOffset"],"kind":"struct"},"2001":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsubsectionIterator"],"kind":"struct"},"239":{"crate_id":1,"path":["std","process","CommandArgs"],"kind":"struct"},"1120":{"crate_id":2,"path":["core","core_simd","simd","ptr","mut_ptr","SimdMutPtr"],"kind":"trait"},"1447":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_mmap_offsets_v1"],"kind":"struct"},"566":{"crate_id":1,"path":["std","sys","net","connection","socket","unix","Socket"],"kind":"struct"},"2328":{"crate_id":18,"path":["memchr","arch","all","twoway","Shift"],"kind":"enum"},"893":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1774":{"crate_id":16,"path":["gimli","read","dwarf","RangeIter"],"kind":"struct"},"2101":{"crate_id":17,"path":["object","read","Export"],"kind":"struct"},"339":{"crate_id":1,"path":["std","panicking","panic_count","MustAbort"],"kind":"enum"},"1220":{"crate_id":3,"path":["alloc","collections","btree","map","entry","Entry"],"kind":"enum"},"666":{"crate_id":2,"path":["core","panic","location","Location"],"kind":"struct"},"1547":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","statvfs64"],"kind":"struct"},"2428":{"crate_id":6,"path":["rustc_std_workspace_core"],"kind":"module"},"112":{"crate_id":1,"path":["std","backtrace","BacktraceFrame"],"kind":"struct"},"993":{"crate_id":2,"path":["core","fmt","num_buffer","NumBuffer"],"kind":"struct"},"1874":{"crate_id":16,"path":["gimli","read","unit","DebugTypes"],"kind":"struct"},"1320":{"crate_id":5,"path":["libc","unix","linux_like","linux","spwd"],"kind":"struct"},"439":{"crate_id":2,"path":["core","iter","adapters","take","Take"],"kind":"struct"},"2201":{"crate_id":17,"path":["object","macho","IdentCommand"],"kind":"struct"},"766":{"crate_id":2,"path":["core","char","EscapeDefault"],"kind":"struct"},"1647":{"crate_id":13,"path":["rustc_demangle","DemangleStyle"],"kind":"enum"},"212":{"crate_id":1,"path":["std","os","unix","net","listener","Incoming"],"kind":"struct"},"1093":{"crate_id":2,"path":["core","future","into_future","IntoFuture"],"kind":"trait"},"1974":{"crate_id":17,"path":["object","read","elf","relocation","RelocationSections"],"kind":"struct"},"1420":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aria_gcm_256"],"kind":"struct"},"539":{"crate_id":1,"path":["std","sys","stdio","unix","Stdout"],"kind":"struct"},"2301":{"crate_id":17,"path":["object","xcoff","CsectAux64"],"kind":"struct"},"866":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1747":{"crate_id":16,"path":["gimli","read","cfi","BaseAddresses"],"kind":"struct"},"2074":{"crate_id":17,"path":["object","read","xcoff","section","XcoffSectionIterator"],"kind":"struct"},"312":{"crate_id":1,"path":["std","sys","env","common","EnvStrDebug"],"kind":"struct"},"1193":{"crate_id":3,"path":["alloc","collections","btree","set","SymmetricDifference"],"kind":"struct"},"1520":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ptrace_sud_config"],"kind":"struct"},"639":{"crate_id":2,"path":["core","marker","variance","PhantomCovariant"],"kind":"struct"},"2401":{"crate_id":2,"path":["core","iter","adapters","zip","zip"],"kind":"function"},"966":{"crate_id":2,"path":["core","cell","BorrowMutError"],"kind":"struct"},"1847":{"crate_id":16,"path":["gimli","read","pubtypes","PubTypesEntryIter"],"kind":"struct"},"2174":{"crate_id":17,"path":["object","macho","SubLibraryCommand"],"kind":"struct"},"412":{"crate_id":3,"path":["alloc","rc","Rc"],"kind":"struct"},"1293":{"crate_id":5,"path":["libc","unix","linux_like","addrinfo"],"kind":"struct"},"1620":{"crate_id":10,"path":["hashbrown","table","IterMut"],"kind":"struct"},"739":{"crate_id":2,"path":["core","core_arch","simd","i16x32"],"kind":"struct"},"185":{"crate_id":1,"path":["std","io","stdio","StdinLock"],"kind":"struct"},"1066":{"crate_id":2,"path":["core","future","future","Future"],"kind":"trait"},"1947":{"crate_id":17,"path":["object","read","coff","section","CoffSegmentIterator"],"kind":"struct"},"2274":{"crate_id":17,"path":["object","pe","ImageAlphaRuntimeFunctionEntry"],"kind":"struct"},"512":{"crate_id":2,"path":["core","slice","iter","RSplitNMut"],"kind":"struct"},"1393":{"crate_id":5,"path":["libc","unix","linux_like","linux","nlmsgerr"],"kind":"struct"},"839":{"crate_id":2,"path":["core","str","pattern","CharPredicateSearcher"],"kind":"struct"},"1720":{"crate_id":16,"path":["gimli","constants","DwDsc"],"kind":"struct"},"285":{"crate_id":1,"path":["std","sync","nonpoison","mutex","MappedMutexGuard"],"kind":"struct"},"1166":{"crate_id":3,"path":["alloc","vec","in_place_drop","InPlaceDstDataSrcBufDrop"],"kind":"struct"},"2047":{"crate_id":17,"path":["object","read","pe","file","PeComdatSectionIterator"],"kind":"struct"},"2374":{"crate_id":2,"path":["core","char","REPLACEMENT_CHARACTER"],"kind":"constant"},"612":{"crate_id":2,"path":["core","num","niche_types","NonZeroI8Inner"],"kind":"struct"},"1493":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ptp_perout_request_2"],"kind":"union"},"939":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1820":{"crate_id":16,"path":["gimli","read","loclists","LocationListEntry"],"kind":"struct"},"385":{"crate_id":1,"path":["std","sys","sync","once_box","OnceBox"],"kind":"struct"},"1266":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","__c_anonymous_sockaddr_can_tp"],"kind":"struct"},"2147":{"crate_id":17,"path":["object","macho","DyldCacheMappingAndSlideInfo"],"kind":"struct"},"1593":{"crate_id":10,"path":["hashbrown","map","HashMap"],"kind":"struct"},"712":{"crate_id":2,"path":["core","core_arch","simd","i16x8"],"kind":"struct"},"1039":{"crate_id":2,"path":["core","slice","sort","unstable","quicksort","GapGuard"],"kind":"struct"},"158":{"crate_id":1,"path":["std","fs","OpenOptions"],"kind":"struct"},"1920":{"crate_id":17,"path":["object","read","any","ComdatIterator"],"kind":"struct"},"2247":{"crate_id":17,"path":["object","pe","ImageTlsDirectory64"],"kind":"struct"},"485":{"crate_id":2,"path":["core","ascii","ascii_char","AsciiChar"],"kind":"enum"},"1366":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Phdr"],"kind":"struct"},"1693":{"crate_id":16,"path":["gimli","arch","LoongArch"],"kind":"struct"},"812":{"crate_id":2,"path":["core","str","pattern","Pattern"],"kind":"trait"},"1139":{"crate_id":3,"path":["alloc","collections","vec_deque","drain","Drain"],"kind":"struct"},"258":{"crate_id":1,"path":["std","sync","mpmc","Iter"],"kind":"struct"},"2020":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorV3"],"kind":"struct"},"2347":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","TwoIter"],"kind":"struct"},"585":{"crate_id":2,"path":["core","alloc","global","GlobalAlloc"],"kind":"trait"},"1466":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_nl"],"kind":"struct"},"1793":{"crate_id":16,"path":["gimli","read","aranges","ArangeEntry"],"kind":"struct"},"31":{"crate_id":2,"path":["core","convert","From"],"kind":"trait"},"912":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"358":{"crate_id":1,"path":["std","sys","personality","dwarf","eh","EHContext"],"kind":"struct"},"1239":{"crate_id":3,"path":["alloc","task","Wake"],"kind":"trait"},"2120":{"crate_id":17,"path":["object","elf","CompressionHeader64"],"kind":"struct"},"2447":{"crate_id":1,"path":["std","array"],"kind":"primitive"},"685":{"crate_id":2,"path":["core","wtf8","CodePoint"],"kind":"struct"},"1566":{"crate_id":5,"path":["libc","unix","FILE"],"kind":"enum"},"1012":{"crate_id":2,"path":["core","fmt","UpperHex"],"kind":"trait"},"131":{"crate_id":1,"path":["std","collections","hash","set","Iter"],"kind":"struct"},"1893":{"crate_id":17,"path":["object","common","SymbolFlags"],"kind":"enum"},"458":{"crate_id":2,"path":["core","iter","adapters","array_chunks","ArrayChunks"],"kind":"struct"},"1339":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_hdr_v1"],"kind":"struct"},"2220":{"crate_id":17,"path":["object","pe","ImageOptionalHeader64"],"kind":"struct"},"785":{"crate_id":2,"path":["core","iter","sources","successors","Successors"],"kind":"struct"},"1666":{"crate_id":16,"path":["gimli","common","DebugAddrIndex"],"kind":"struct"},"1112":{"crate_id":2,"path":["core","core_simd","to_bytes","sealed","Sealed"],"kind":"trait"},"231":{"crate_id":1,"path":["std","path","NormalizeError"],"kind":"struct"},"1993":{"crate_id":17,"path":["object","read","elf","version","VersionTable"],"kind":"struct"},"558":{"crate_id":1,"path":["std","os","unix","fs","MetadataExt"],"kind":"trait"},"1439":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_event_metadata"],"kind":"struct"},"2320":{"crate_id":18,"path":["memchr","arch","all","packedpair","Finder"],"kind":"struct"},"1766":{"crate_id":16,"path":["gimli","read","cfi","Pointer"],"kind":"enum"},"885":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1212":{"crate_id":3,"path":["alloc","collections","btree","map","ValuesMut"],"kind":"struct"},"331":{"crate_id":1,"path":["std","sys","process","unix","common","Command"],"kind":"struct"},"2093":{"crate_id":17,"path":["object","read","SymbolIndex"],"kind":"struct"},"2420":{"crate_id":2,"path":["core","ptr","with_exposed_provenance"],"kind":"function"},"658":{"crate_id":2,"path":["core","char","decode","DecodeUtf16Error"],"kind":"struct"},"1539":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","statfs"],"kind":"struct"},"1866":{"crate_id":16,"path":["gimli","read","unit","AttributeValue"],"kind":"enum"},"104":{"crate_id":1,"path":["std","thread","local","AccessError"],"kind":"struct"},"985":{"crate_id":2,"path":["core","sync","atomic","AtomicI64"],"kind":"struct"},"1312":{"crate_id":5,"path":["libc","unix","linux_like","epoll_event"],"kind":"struct"},"431":{"crate_id":2,"path":["core","iter","adapters","filter","Filter"],"kind":"struct"},"2193":{"crate_id":17,"path":["object","macho","EncryptionInfoCommand32"],"kind":"struct"},"758":{"crate_id":2,"path":["core","error","Source"],"kind":"struct"},"1639":{"crate_id":10,"path":["hashbrown","table","VacantEntry"],"kind":"struct"},"1966":{"crate_id":17,"path":["object","read","elf","segment","ElfSegment"],"kind":"struct"},"204":{"crate_id":1,"path":["std","net","tcp","TcpListener"],"kind":"struct"},"1085":{"crate_id":2,"path":["core","future","join","MaybeDone"],"kind":"enum"},"531":{"crate_id":2,"path":["core","str","traits","FromStr"],"kind":"trait"},"1412":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls_crypto_info"],"kind":"struct"},"2293":{"crate_id":17,"path":["object","xcoff","SectionHeader32"],"kind":"struct"},"858":{"crate_id":2,"path":["core","task","wake","Waker"],"kind":"struct"},"1739":{"crate_id":16,"path":["gimli","read","addr","AddrEntryIter"],"kind":"struct"},"2066":{"crate_id":17,"path":["object","read","pe","resource","ResourceDirectory"],"kind":"struct"},"304":{"crate_id":1,"path":["std","time","Instant"],"kind":"struct"},"1185":{"crate_id":3,"path":["alloc","collections","btree","merge_iter","MergeIterInner"],"kind":"struct"},"631":{"crate_id":2,"path":["core","ptr","alignment","Alignment"],"kind":"struct"},"1512":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","Elf64_Chdr"],"kind":"struct"},"2393":{"crate_id":2,"path":["core","str","converts","from_utf8"],"kind":"function"},"958":{"crate_id":2,"path":["core","error","tags","MaybeSizedValue"],"kind":"struct"},"1839":{"crate_id":16,"path":["gimli","read","op","Expression"],"kind":"struct"},"1285":{"crate_id":5,"path":["libc","unix","in6_addr"],"kind":"struct"},"404":{"crate_id":1,"path":["std","panicking","begin_panic","Payload"],"kind":"struct"},"2166":{"crate_id":17,"path":["object","macho","Section64"],"kind":"struct"},"731":{"crate_id":2,"path":["core","core_arch","simd","f16x16"],"kind":"struct"},"1612":{"crate_id":10,"path":["hashbrown","map","ExtractIf"],"kind":"struct"},"1939":{"crate_id":17,"path":["object","read","archive","ArchiveMember"],"kind":"struct"},"177":{"crate_id":1,"path":["std","io","error","Error"],"kind":"struct"},"1058":{"crate_id":2,"path":["core","iter","range","Step"],"kind":"trait"},"1385":{"crate_id":5,"path":["libc","unix","linux_like","linux","regmatch_t"],"kind":"struct"},"504":{"crate_id":2,"path":["core","slice","iter","SplitMut"],"kind":"struct"},"2266":{"crate_id":17,"path":["object","pe","ImageLoadConfigDirectory32"],"kind":"struct"},"831":{"crate_id":2,"path":["core","str","iter","EscapeDebug"],"kind":"struct"},"1712":{"crate_id":16,"path":["gimli","constants","DwVis"],"kind":"struct"},"2039":{"crate_id":17,"path":["object","read","macho","symbol","SymbolTable"],"kind":"struct"},"277":{"crate_id":1,"path":["std","sync","lazy_lock","LazyLock"],"kind":"struct"},"1158":{"crate_id":3,"path":["alloc","sync","UniqueArc"],"kind":"struct"},"1485":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_rwlock_t"],"kind":"struct"},"604":{"crate_id":2,"path":["core","num","saturating","Saturating"],"kind":"struct"},"2366":{"crate_id":18,"path":["memchr","memmem","FindRevIter"],"kind":"struct"},"50":{"crate_id":2,"path":["core","any","Any"],"kind":"trait"},"931":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1812":{"crate_id":16,"path":["gimli","read","lists","ListsHeader"],"kind":"struct"},"2139":{"crate_id":17,"path":["object","elf","Vernaux"],"kind":"struct"},"377":{"crate_id":1,"path":["std","sys","pal","unix","stack_overflow","Handler"],"kind":"struct"},"1258":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","hex_float","HexFloatParseError"],"kind":"struct"},"704":{"crate_id":2,"path":["core","core_arch","simd","i64x1"],"kind":"struct"},"1585":{"crate_id":10,"path":["hashbrown","control","bitmask","BitMaskIter"],"kind":"struct"},"2466":{"crate_id":1,"path":["std","usize"],"kind":"primitive"},"150":{"crate_id":1,"path":["std","env","Args"],"kind":"struct"},"1031":{"crate_id":2,"path":["core","mem","maybe_uninit","Guard"],"kind":"struct"},"1912":{"crate_id":17,"path":["object","read","any","File"],"kind":"enum"},"2239":{"crate_id":17,"path":["object","pe","ImageRelocation"],"kind":"struct"},"477":{"crate_id":1,"path":["std","error","Indented"],"kind":"struct"},"1358":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_ff_upload"],"kind":"struct"},"804":{"crate_id":2,"path":["core","hash","sip","Sip24Rounds"],"kind":"struct"},"1685":{"crate_id":16,"path":["gimli","common","DebugFrameOffset"],"kind":"struct"},"250":{"crate_id":1,"path":["std","sync","mpmc","error","SendTimeoutError"],"kind":"enum"},"1131":{"crate_id":3,"path":["alloc","collections","binary_heap","drop","DropGuard"],"kind":"struct"},"2012":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingVersionIterator"],"kind":"enum"},"1458":{"crate_id":5,"path":["libc","unix","linux_like","linux","pidfd_info"],"kind":"struct"},"577":{"crate_id":1,"path":["std","os","net","linux_ext","tcp","TcpStreamExt"],"kind":"trait"},"2339":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","Two"],"kind":"struct"},"904":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"23":{"crate_id":2,"path":["core","panic","unwind_safe","RefUnwindSafe"],"kind":"trait"},"1785":{"crate_id":16,"path":["gimli","read","abbrev","Abbreviations"],"kind":"struct"},"2112":{"crate_id":17,"path":["object","archive","AixFileHeader"],"kind":"struct"},"350":{"crate_id":1,"path":["std","panicking","Hook"],"kind":"enum"},"1231":{"crate_id":3,"path":["alloc","collections","btree","set","IntersectionInner"],"kind":"enum"},"1558":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","clone_args"],"kind":"struct"},"677":{"crate_id":2,"path":["core","fmt","FormattingOptions"],"kind":"struct"},"2439":{"crate_id":17,"path":["object"],"kind":"module"},"1004":{"crate_id":2,"path":["core","task","wake","Context"],"kind":"struct"},"123":{"crate_id":1,"path":["std","collections","hash","map","IterMut"],"kind":"struct"},"1885":{"crate_id":17,"path":["object","common","ComdatKind"],"kind":"enum"},"2212":{"crate_id":17,"path":["object","pe","ImageDosHeader"],"kind":"struct"},"450":{"crate_id":2,"path":["core","iter","traits","double_ended","DoubleEndedIterator"],"kind":"trait"},"1331":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket2_hdr"],"kind":"struct"},"1658":{"crate_id":16,"path":["gimli","common","Format"],"kind":"enum"},"777":{"crate_id":2,"path":["core","iter","sources","from_coroutine","FromCoroutine"],"kind":"struct"},"223":{"crate_id":1,"path":["std","path","Component"],"kind":"enum"},"1104":{"crate_id":2,"path":["core","core_simd","swizzle","extract","Extract"],"kind":"struct"},"1985":{"crate_id":17,"path":["object","read","elf","note","NoteIterator"],"kind":"struct"},"2312":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingVersion"],"kind":"enum"},"550":{"crate_id":2,"path":["core","net","ip_addr","Ipv4Addr"],"kind":"struct"},"1431":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_statistics"],"kind":"struct"},"1758":{"crate_id":16,"path":["gimli","read","cfi","RegisterRuleMap"],"kind":"struct"},"877":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"323":{"crate_id":1,"path":["std","sys","net","connection","socket","TcpStream"],"kind":"struct"},"1204":{"crate_id":3,"path":["alloc","ffi","c_str","FromBytesWithNulErrorKind"],"kind":"enum"},"2085":{"crate_id":17,"path":["object","read","xcoff","comdat","XcoffComdatSectionIterator"],"kind":"struct"},"2412":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt","st_mtime"],"kind":"function"},"650":{"crate_id":2,"path":["core","ops","range","Bound"],"kind":"enum"},"1531":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_data"],"kind":"union"},"977":{"crate_id":2,"path":["core","pin","unsafe_pinned","UnsafePinned"],"kind":"struct"},"96":{"crate_id":2,"path":["core","marker","MetaSized"],"kind":"trait"},"1858":{"crate_id":16,"path":["gimli","read","str","DebugStrOffsets"],"kind":"struct"},"423":{"crate_id":2,"path":["core","array","iter","IntoIter"],"kind":"struct"},"1304":{"crate_id":5,"path":["libc","unix","linux_like","arpreq_old"],"kind":"struct"},"2185":{"crate_id":17,"path":["object","macho","DylibReference"],"kind":"struct"},"1631":{"crate_id":10,"path":["hashbrown","rustc_entry","RustcEntry"],"kind":"enum"},"750":{"crate_id":2,"path":["core","core_arch","simd","u32x32"],"kind":"struct"},"1077":{"crate_id":2,"path":["core","ops","range","OneSidedRangeBound"],"kind":"enum"},"196":{"crate_id":1,"path":["std","io","Chain"],"kind":"struct"},"1958":{"crate_id":17,"path":["object","read","coff","comdat","CoffComdat"],"kind":"struct"},"523":{"crate_id":2,"path":["core","alloc","Allocator"],"kind":"trait"},"1404":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_initmsg"],"kind":"struct"},"2285":{"crate_id":17,"path":["object","pe","NonPagedDebugInfo"],"kind":"struct"},"1731":{"crate_id":16,"path":["gimli","endianity","RunTimeEndian"],"kind":"enum"},"850":{"crate_id":2,"path":["core","str","IsNotEmpty"],"kind":"struct"},"1177":{"crate_id":3,"path":["alloc","collections","binary_heap","IntoIterSorted"],"kind":"struct"},"296":{"crate_id":1,"path":["std","sync","poison","rwlock","RwLockReadGuard"],"kind":"struct"},"2058":{"crate_id":17,"path":["object","read","pe","import","ImportDescriptorIterator"],"kind":"struct"},"2385":{"crate_id":3,"path":["alloc","alloc","alloc"],"kind":"function"},"623":{"crate_id":2,"path":["core","num","niche_types","U64NotAllOnes"],"kind":"struct"},"1504":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","mallinfo"],"kind":"struct"},"1831":{"crate_id":16,"path":["gimli","read","op","DieReference"],"kind":"enum"},"950":{"crate_id":2,"path":["core","core_arch","x86","__m256h"],"kind":"struct"},"396":{"crate_id":1,"path":["std","sys","thread","unix","cgroups","Cgroup"],"kind":"enum"},"1277":{"crate_id":5,"path":["libc","unix","pollfd"],"kind":"struct"},"2158":{"crate_id":17,"path":["object","macho","FatArch64"],"kind":"struct"},"723":{"crate_id":2,"path":["core","core_arch","simd","u8x32"],"kind":"struct"},"1604":{"crate_id":10,"path":["hashbrown","table","Iter"],"kind":"struct"},"1931":{"crate_id":17,"path":["object","read","any","DynamicRelocationIteratorInternal"],"kind":"enum"},"169":{"crate_id":1,"path":["std","io","buffered","bufreader","BufReader"],"kind":"struct"},"1050":{"crate_id":2,"path":["core","marker","Unsize"],"kind":"trait"},"496":{"crate_id":2,"path":["core","slice","iter","ArrayWindows"],"kind":"struct"},"1377":{"crate_id":5,"path":["libc","unix","linux_like","linux","genlmsghdr"],"kind":"struct"},"2258":{"crate_id":17,"path":["object","pe","ImageLoadConfigCodeIntegrity"],"kind":"struct"},"823":{"crate_id":2,"path":["core","str","iter","Matches"],"kind":"struct"},"1704":{"crate_id":16,"path":["gimli","constants","DwTag"],"kind":"struct"},"1150":{"crate_id":3,"path":["alloc","rc","Weak"],"kind":"struct"},"269":{"crate_id":1,"path":["std","sync","mpsc","SyncSender"],"kind":"struct"},"2031":{"crate_id":17,"path":["object","read","macho","load_command","LoadCommandData"],"kind":"struct"},"596":{"crate_id":2,"path":["core","num","flt2dec","decoder","Decoded"],"kind":"struct"},"1477":{"crate_id":5,"path":["libc","unix","linux_like","linux","hwtstamp_config"],"kind":"struct"},"2358":{"crate_id":18,"path":["memchr","memmem","searcher","TwoWayWithPrefilter"],"kind":"struct"},"1804":{"crate_id":16,"path":["gimli","read","line","LineRow"],"kind":"struct"},"42":{"crate_id":2,"path":["core","convert","TryInto"],"kind":"trait"},"923":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1250":{"crate_id":4,"path":["compiler_builtins","float","cmp","Result"],"kind":"enum"},"369":{"crate_id":1,"path":["std","thread","Packet"],"kind":"struct"},"2131":{"crate_id":17,"path":["object","elf","ProgramHeader32"],"kind":"struct"},"696":{"crate_id":2,"path":["core","core_arch","simd","i16x2"],"kind":"struct"},"1577":{"crate_id":8,"path":["miniz_oxide","DataFormat"],"kind":"enum"},"2458":{"crate_id":1,"path":["std","i64"],"kind":"primitive"},"1904":{"crate_id":17,"path":["object","read","read_cache","ReadCache"],"kind":"struct"},"142":{"crate_id":1,"path":["std","collections","hash","set","Entry"],"kind":"enum"},"1023":{"crate_id":2,"path":["core","ops","bit","Not"],"kind":"trait"},"1350":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_trigger"],"kind":"struct"},"469":{"crate_id":1,"path":["std","os","unix","net","ancillary","AncillaryData"],"kind":"enum"},"2231":{"crate_id":17,"path":["object","pe","ImageSymbolEx"],"kind":"struct"},"796":{"crate_id":2,"path":["core","fmt","rt","ArgumentType"],"kind":"enum"},"1677":{"crate_id":16,"path":["gimli","common","RangeListsOffset"],"kind":"struct"},"2004":{"crate_id":17,"path":["object","read","elf","attributes","AttributeReader"],"kind":"struct"},"242":{"crate_id":1,"path":["std","process","Stdio"],"kind":"struct"},"1123":{"crate_id":2,"path":["core","core_simd","simd","cmp","ord","SimdOrd"],"kind":"trait"},"1450":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_statistics"],"kind":"struct"},"569":{"crate_id":1,"path":["std","os","fd","owned","AsFd"],"kind":"trait"},"2331":{"crate_id":18,"path":["memchr","arch","all","twoway","ApproximateByteSet"],"kind":"struct"},"15":{"crate_id":2,"path":["core","marker","Sync"],"kind":"trait"},"896":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1777":{"crate_id":16,"path":["gimli","read","endian_slice","DebugBytes"],"kind":"struct"},"2104":{"crate_id":17,"path":["object","read","Relocation"],"kind":"struct"},"342":{"crate_id":1,"path":["std","backtrace_rs","symbolize","SymbolName"],"kind":"struct"},"1223":{"crate_id":3,"path":["alloc","collections","btree","map","entry","OccupiedError"],"kind":"struct"},"669":{"crate_id":2,"path":["core","range","RangeInclusive"],"kind":"struct"},"1550":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","_libc_xmmreg"],"kind":"struct"},"2431":{"crate_id":9,"path":["adler2"],"kind":"module"},"115":{"crate_id":1,"path":["std","collections","hash","map","HashMap"],"kind":"struct"},"996":{"crate_id":2,"path":["core","cell","RefMut"],"kind":"struct"},"1877":{"crate_id":16,"path":["gimli","read","value","Value"],"kind":"enum"},"1323":{"crate_id":5,"path":["libc","unix","linux_like","linux","itimerspec"],"kind":"struct"},"442":{"crate_id":2,"path":["core","iter","adapters","flatten","Flatten"],"kind":"struct"},"2204":{"crate_id":17,"path":["object","macho","SourceVersionCommand"],"kind":"struct"},"769":{"crate_id":2,"path":["core","char","ToUppercase"],"kind":"struct"},"1650":{"crate_id":13,"path":["rustc_demangle","TryDemangleError"],"kind":"struct"},"215":{"crate_id":1,"path":["std","os","linux","process","PidFd"],"kind":"struct"},"1096":{"crate_id":2,"path":["core","core_simd","swizzle","rotate_elements_right","Rotate"],"kind":"struct"},"1977":{"crate_id":17,"path":["object","read","elf","relocation","RelrIterator"],"kind":"struct"},"1423":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_freq"],"kind":"struct"},"542":{"crate_id":1,"path":["std","io","stdio","IsTerminal"],"kind":"trait"},"2304":{"crate_id":17,"path":["object","xcoff","ExpAux"],"kind":"struct"},"869":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1750":{"crate_id":16,"path":["gimli","read","cfi","CieOrFde"],"kind":"enum"},"2077":{"crate_id":17,"path":["object","read","xcoff","symbol","SymbolTable"],"kind":"struct"},"315":{"crate_id":1,"path":["std","sys","fs","unix","FileTimes"],"kind":"struct"},"1196":{"crate_id":3,"path":["alloc","collections","btree","set","Cursor"],"kind":"struct"},"1523":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fanotify_event_info_pidfd"],"kind":"struct"},"642":{"crate_id":2,"path":["core","marker","PhantomData"],"kind":"struct"},"2404":{"crate_id":1,"path":["std","fs","read"],"kind":"function"},"969":{"crate_id":2,"path":["core","io","borrowed_buf","BorrowedBuf"],"kind":"struct"},"1850":{"crate_id":16,"path":["gimli","read","rnglists","RangeLists"],"kind":"struct"},"2177":{"crate_id":17,"path":["object","macho","ThreadCommand"],"kind":"struct"},"415":{"crate_id":1,"path":["std","sys","pal","unix","weak","dlsym","DlsymWeak"],"kind":"struct"},"1296":{"crate_id":5,"path":["libc","unix","linux_like","tm"],"kind":"struct"},"1623":{"crate_id":10,"path":["hashbrown","table","Drain"],"kind":"struct"},"742":{"crate_id":2,"path":["core","core_arch","simd","u32x16"],"kind":"struct"},"188":{"crate_id":1,"path":["std","io","stdio","Stderr"],"kind":"struct"},"1069":{"crate_id":2,"path":["core","ops","try_trait","NeverShortCircuit"],"kind":"struct"},"1950":{"crate_id":17,"path":["object","read","coff","section","CoffSection"],"kind":"struct"},"2277":{"crate_id":17,"path":["object","pe","ImageEnclaveConfig64"],"kind":"struct"},"515":{"crate_id":2,"path":["core","core_simd","vector","SimdElement"],"kind":"trait"},"1396":{"crate_id":5,"path":["libc","unix","linux_like","linux","in6_ifreq"],"kind":"struct"},"842":{"crate_id":2,"path":["core","str","pattern","EmptyNeedle"],"kind":"struct"},"1723":{"crate_id":16,"path":["gimli","constants","DwLns"],"kind":"struct"},"288":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","RwLockWriteGuard"],"kind":"struct"},"1169":{"crate_id":3,"path":["alloc","alloc","Global"],"kind":"struct"},"2050":{"crate_id":17,"path":["object","read","pe","section","PeSectionIterator"],"kind":"struct"},"1496":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_xsk_tx_metadata_union"],"kind":"union"},"615":{"crate_id":2,"path":["core","num","niche_types","NonZeroI64Inner"],"kind":"struct"},"2377":{"crate_id":1,"path":["std","fs","symlink_metadata"],"kind":"function"},"942":{"crate_id":2,"path":["core","core_arch","x86","__m128bh"],"kind":"struct"},"1823":{"crate_id":16,"path":["gimli","read","lookup","PubStuffHeader"],"kind":"struct"},"388":{"crate_id":1,"path":["std","backtrace_rs","backtrace","libunwind","Bomb"],"kind":"struct"},"1269":{"crate_id":5,"path":["libc","unix","group"],"kind":"struct"},"2150":{"crate_id":17,"path":["object","macho","DyldCacheSlideInfo3"],"kind":"struct"},"1596":{"crate_id":10,"path":["hashbrown","map","Values"],"kind":"struct"},"715":{"crate_id":2,"path":["core","core_arch","simd","f16x4"],"kind":"struct"},"1042":{"crate_id":2,"path":["core","intrinsics","fallback","CarryingMulAdd"],"kind":"trait"},"161":{"crate_id":1,"path":["std","fs","DirBuilder"],"kind":"struct"},"1923":{"crate_id":17,"path":["object","read","any","ComdatSectionIterator"],"kind":"struct"},"2250":{"crate_id":17,"path":["object","pe","ImageBoundImportDescriptor"],"kind":"struct"},"488":{"crate_id":2,"path":["core","ops","range","Range"],"kind":"struct"},"1369":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Shdr"],"kind":"struct"},"1696":{"crate_id":16,"path":["gimli","arch","X86"],"kind":"struct"},"815":{"crate_id":2,"path":["core","str","iter","RSplitTerminator"],"kind":"struct"},"1142":{"crate_id":3,"path":["alloc","collections","vec_deque","into_iter","try_rfold","Guard"],"kind":"struct"},"261":{"crate_id":1,"path":["std","sync","mpmc","Receiver"],"kind":"struct"},"2023":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldRelocation"],"kind":"struct"},"2350":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","packedpair","Finder"],"kind":"struct"},"588":{"crate_id":2,"path":["core","ptr","non_null","NonNull"],"kind":"struct"},"1469":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_setup"],"kind":"struct"},"1796":{"crate_id":16,"path":["gimli","read","index","UnitIndex"],"kind":"struct"},"34":{"crate_id":2,"path":["core","convert","Into"],"kind":"trait"},"915":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"361":{"crate_id":1,"path":["std","sys","io","io_slice","iovec","IoSlice"],"kind":"struct"},"1242":{"crate_id":3,"path":["alloc","collections","btree","dedup_sorted_iter","DedupSortedIter"],"kind":"struct"},"2123":{"crate_id":17,"path":["object","elf","Syminfo32"],"kind":"struct"},"2450":{"crate_id":1,"path":["std","tuple"],"kind":"primitive"},"688":{"crate_id":2,"path":["core","task","wake","RawWaker"],"kind":"struct"},"1569":{"crate_id":8,"path":["miniz_oxide","inflate","core","DecompressorOxide"],"kind":"struct"},"1015":{"crate_id":2,"path":["core","ops","bit","BitOrAssign"],"kind":"trait"},"134":{"crate_id":1,"path":["std","collections","hash","set","ExtractIf"],"kind":"struct"},"1896":{"crate_id":17,"path":["object","endian","LittleEndian"],"kind":"struct"},"461":{"crate_id":2,"path":["core","cmp","PartialOrd"],"kind":"trait"},"1342":{"crate_id":5,"path":["libc","unix","linux_like","linux","msginfo"],"kind":"struct"},"2223":{"crate_id":17,"path":["object","pe","ImageRomHeaders"],"kind":"struct"},"788":{"crate_id":2,"path":["core","option","Iter"],"kind":"struct"},"1669":{"crate_id":16,"path":["gimli","common","DebugLineOffset"],"kind":"struct"},"1115":{"crate_id":2,"path":["core","core_simd","simd","num","float","SimdFloat"],"kind":"trait"},"234":{"crate_id":1,"path":["std","process","Child"],"kind":"struct"},"1996":{"crate_id":17,"path":["object","read","elf","version","VerneedIterator"],"kind":"struct"},"561":{"crate_id":1,"path":["std","os","unix","fs","DirEntryExt2"],"kind":"trait"},"1442":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_clock_caps"],"kind":"struct"},"2323":{"crate_id":18,"path":["memchr","arch","all","rabinkarp","FinderRev"],"kind":"struct"},"1769":{"crate_id":16,"path":["gimli","read","dwarf","Dwarf"],"kind":"struct"},"888":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1215":{"crate_id":3,"path":["alloc","collections","btree","set","IntoIter"],"kind":"struct"},"334":{"crate_id":1,"path":["std","sys","process","unix","unix","ExitStatus"],"kind":"struct"},"2096":{"crate_id":17,"path":["object","read","SymbolMapName"],"kind":"struct"},"2423":{"crate_id":1,"path":["std"],"kind":"module"},"661":{"crate_id":2,"path":["core","ffi","c_str","FromBytesWithNulError"],"kind":"enum"},"1542":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","siginfo_t"],"kind":"struct"},"1869":{"crate_id":16,"path":["gimli","read","unit","EntriesRaw"],"kind":"struct"},"107":{"crate_id":1,"path":["std","thread","Thread"],"kind":"struct"},"988":{"crate_id":2,"path":["core","sync","atomic","AtomicUsize"],"kind":"struct"},"1315":{"crate_id":5,"path":["libc","unix","linux_like","utsname"],"kind":"struct"},"434":{"crate_id":2,"path":["core","iter","adapters","peekable","Peekable"],"kind":"struct"},"2196":{"crate_id":17,"path":["object","macho","BuildVersionCommand"],"kind":"struct"},"761":{"crate_id":2,"path":["core","ascii","EscapeDefault"],"kind":"struct"},"1642":{"crate_id":12,"path":["std_detect","detect","arch","x86","Feature"],"kind":"enum"},"1969":{"crate_id":17,"path":["object","read","elf","section","ElfSection"],"kind":"struct"},"207":{"crate_id":1,"path":["std","os","unix","net","addr","SocketAddr"],"kind":"struct"},"1088":{"crate_id":2,"path":["core","sync","atomic","AtomicPrimitive"],"kind":"trait"},"534":{"crate_id":1,"path":["std","io","stdio","StdinRaw"],"kind":"struct"},"1415":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aes_ccm_128"],"kind":"struct"},"2296":{"crate_id":17,"path":["object","xcoff","Symbol32"],"kind":"struct"},"861":{"crate_id":2,"path":["core","escape","AlwaysEscaped"],"kind":"struct"},"1742":{"crate_id":16,"path":["gimli","read","cfi","ParsedEhFrameHdr"],"kind":"struct"},"1188":{"crate_id":3,"path":["alloc","collections","btree","navigate","LazyLeafRange"],"kind":"struct"},"307":{"crate_id":1,"path":["std","sys","pal","unix","os","JoinPathsError"],"kind":"struct"},"2069":{"crate_id":17,"path":["object","read","pe","resource","ResourceName"],"kind":"struct"},"634":{"crate_id":2,"path":["core","marker","FnPtr"],"kind":"trait"},"1515":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ptrace_peeksiginfo_args"],"kind":"struct"},"2396":{"crate_id":2,"path":["core","mem","drop"],"kind":"function"},"961":{"crate_id":2,"path":["core","asserting","Capture"],"kind":"struct"},"1842":{"crate_id":16,"path":["gimli","read","pubnames","PubNamesEntry"],"kind":"struct"},"1288":{"crate_id":5,"path":["libc","unix","linux_like","ip_mreqn"],"kind":"struct"},"407":{"crate_id":2,"path":["core","error","Request"],"kind":"struct"},"2169":{"crate_id":17,"path":["object","macho","Dylib"],"kind":"struct"},"734":{"crate_id":2,"path":["core","core_arch","simd","m8x32"],"kind":"struct"},"1615":{"crate_id":10,"path":["hashbrown","map","ValuesMut"],"kind":"struct"},"1942":{"crate_id":17,"path":["object","read","archive","SymbolIteratorInternal"],"kind":"enum"},"180":{"crate_id":1,"path":["std","io","error","ErrorKind"],"kind":"enum"},"1061":{"crate_id":2,"path":["core","marker","variance","Variance"],"kind":"trait"},"1388":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif_sizes"],"kind":"struct"},"507":{"crate_id":2,"path":["core","slice","iter","RSplit"],"kind":"struct"},"2269":{"crate_id":17,"path":["object","pe","ImageHotPatchBase"],"kind":"struct"},"834":{"crate_id":2,"path":["core","str","pattern","CharSearcher"],"kind":"struct"},"1715":{"crate_id":16,"path":["gimli","constants","DwAddr"],"kind":"struct"},"2042":{"crate_id":17,"path":["object","read","macho","symbol","MachOSymbol"],"kind":"struct"},"280":{"crate_id":1,"path":["std","sync","reentrant_lock","ReentrantLockGuard"],"kind":"struct"},"1161":{"crate_id":3,"path":["alloc","vec","drain","Drain"],"kind":"struct"},"1488":{"crate_id":5,"path":["libc","unix","linux_like","linux","iwreq_data"],"kind":"union"},"607":{"crate_id":2,"path":["core","num","niche_types","NonZeroU8Inner"],"kind":"struct"},"2369":{"crate_id":18,"path":["memchr","memmem","FinderBuilder"],"kind":"struct"},"53":{"crate_id":0,"path":["kne_test_calculator","Operation","Multiply"],"kind":"variant"},"934":{"crate_id":2,"path":["core","core_arch","x86","__m256d"],"kind":"struct"},"1815":{"crate_id":16,"path":["gimli","read","loclists","LocationLists"],"kind":"struct"},"2142":{"crate_id":17,"path":["object","elf","HashHeader"],"kind":"struct"},"380":{"crate_id":1,"path":["std","sys","net","connection","socket","LookupHost"],"kind":"struct"},"1261":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","can_frame"],"kind":"struct"},"707":{"crate_id":2,"path":["core","core_arch","simd","u8x16"],"kind":"struct"},"1588":{"crate_id":10,"path":["hashbrown","raw","RawTable"],"kind":"struct"},"153":{"crate_id":2,"path":["core","fmt","Display"],"kind":"trait"},"1034":{"crate_id":2,"path":["core","array","Guard"],"kind":"struct"},"1915":{"crate_id":17,"path":["object","read","any","SegmentInternal"],"kind":"enum"},"2242":{"crate_id":17,"path":["object","pe","ImageArchiveMemberHeader"],"kind":"struct"},"480":{"crate_id":1,"path":["std","io","default_write_fmt","Adapter"],"kind":"struct"},"1361":{"crate_id":5,"path":["libc","unix","linux_like","linux","dl_phdr_info"],"kind":"struct"},"807":{"crate_id":2,"path":["core","str","iter","Chars"],"kind":"struct"},"1688":{"crate_id":16,"path":["gimli","common","SectionId"],"kind":"enum"},"253":{"crate_id":1,"path":["std","sync","mpmc","select","Token"],"kind":"struct"},"1134":{"crate_id":3,"path":["alloc","collections","btree","map","drop","DropGuard"],"kind":"struct"},"2015":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIterator"],"kind":"struct"},"1461":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_thrspy"],"kind":"struct"},"580":{"crate_id":2,"path":["core","ops","arith","Add"],"kind":"trait"},"2342":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","ThreeIter"],"kind":"struct"},"907":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"26":{"crate_id":2,"path":["core","marker","Sized"],"kind":"trait"},"1788":{"crate_id":16,"path":["gimli","read","abbrev","AttributeSpecification"],"kind":"struct"},"2115":{"crate_id":17,"path":["object","elf","FileHeader64"],"kind":"struct"},"353":{"crate_id":2,"path":["core","clone","Clone"],"kind":"trait"},"1234":{"crate_id":3,"path":["alloc","collections","btree","set","CursorMutKey"],"kind":"struct"},"1561":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","max_align_t"],"kind":"struct"},"680":{"crate_id":2,"path":["core","str","pattern","Utf8Pattern"],"kind":"enum"},"2442":{"crate_id":1,"path":["std","bool"],"kind":"primitive"},"1007":{"crate_id":2,"path":["core","fmt","builders","PadAdapterState"],"kind":"struct"},"126":{"crate_id":1,"path":["std","collections","hash","map","IntoKeys"],"kind":"struct"},"1888":{"crate_id":17,"path":["object","common","RelocationKind"],"kind":"enum"},"2215":{"crate_id":17,"path":["object","pe","MaskedRichHeaderEntry"],"kind":"struct"},"453":{"crate_id":2,"path":["core","cmp","Ordering"],"kind":"enum"},"1334":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_rollover_stats"],"kind":"struct"},"1661":{"crate_id":16,"path":["gimli","common","LineEncoding"],"kind":"struct"},"780":{"crate_id":2,"path":["core","iter","sources","once_with","OnceWith"],"kind":"struct"},"226":{"crate_id":1,"path":["std","path","Iter"],"kind":"struct"},"1107":{"crate_id":2,"path":["core","core_simd","cast","sealed","Sealed"],"kind":"trait"},"1988":{"crate_id":17,"path":["object","read","elf","note","GnuProperty"],"kind":"struct"},"2315":{"crate_id":18,"path":["memchr","arch","all","memchr","OneIter"],"kind":"struct"},"553":{"crate_id":1,"path":["std","os","unix","ffi","os_str","OsStringExt"],"kind":"trait"},"1434":{"crate_id":5,"path":["libc","unix","linux_like","linux","epoll_params"],"kind":"struct"},"880":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1761":{"crate_id":16,"path":["gimli","read","cfi","CfaRule"],"kind":"enum"},"326":{"crate_id":1,"path":["std","sys","os_str","bytes","Buf"],"kind":"struct"},"1207":{"crate_id":3,"path":["alloc","string","FromUtf8Error"],"kind":"struct"},"2088":{"crate_id":17,"path":["object","read","traits","NoDynamicRelocationIterator"],"kind":"struct"},"2415":{"crate_id":2,"path":["core","intrinsics","unchecked_funnel_shl"],"kind":"function"},"653":{"crate_id":2,"path":["core","cell","Cell"],"kind":"struct"},"1534":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","sysinfo"],"kind":"struct"},"980":{"crate_id":2,"path":["core","sync","atomic","AtomicU8"],"kind":"struct"},"99":{"crate_id":2,"path":["core","fmt","Error"],"kind":"struct"},"1861":{"crate_id":16,"path":["gimli","read","unit","DebugInfo"],"kind":"struct"},"426":{"crate_id":2,"path":["core","iter","adapters","chain","Chain"],"kind":"struct"},"1307":{"crate_id":5,"path":["libc","unix","linux_like","file_clone_range"],"kind":"struct"},"2188":{"crate_id":17,"path":["object","macho","PrebindCksumCommand"],"kind":"struct"},"1634":{"crate_id":10,"path":["hashbrown","set","Entry"],"kind":"enum"},"753":{"crate_id":2,"path":["core","core_simd","masks","Mask"],"kind":"struct"},"1080":{"crate_id":2,"path":["core","slice","index","private_slice_index","Sealed"],"kind":"trait"},"199":{"crate_id":1,"path":["std","io","Split"],"kind":"struct"},"1961":{"crate_id":17,"path":["object","read","coff","import","ImportName"],"kind":"enum"},"2288":{"crate_id":17,"path":["object","pe","ImageCor20Header"],"kind":"struct"},"526":{"crate_id":2,"path":["core","ops","deref","DerefMut"],"kind":"trait"},"1407":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_rcvinfo"],"kind":"struct"},"1734":{"crate_id":16,"path":["gimli","read","util","sealed","CapacityFull"],"kind":"struct"},"853":{"crate_id":2,"path":["core","wtf8","Wtf8CodePoints"],"kind":"struct"},"1180":{"crate_id":3,"path":["alloc","collections","btree","map","Values"],"kind":"struct"},"299":{"crate_id":1,"path":["std","sync","poison","rwlock","MappedRwLockWriteGuard"],"kind":"struct"},"2061":{"crate_id":17,"path":["object","read","pe","import","DelayLoadImportTable"],"kind":"struct"},"2388":{"crate_id":2,"path":["core","mem","take"],"kind":"function"},"626":{"crate_id":2,"path":["core","num","FpCategory"],"kind":"enum"},"1507":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","nl_mmap_req"],"kind":"struct"},"1834":{"crate_id":16,"path":["gimli","read","op","Location"],"kind":"enum"},"953":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"399":{"crate_id":1,"path":["std","sync","mpmc","counter","Receiver"],"kind":"struct"},"1280":{"crate_id":5,"path":["libc","unix","sigval"],"kind":"struct"},"2161":{"crate_id":17,"path":["object","macho","LoadCommand"],"kind":"struct"},"726":{"crate_id":2,"path":["core","core_arch","simd","u64x4"],"kind":"struct"},"1607":{"crate_id":10,"path":["hashbrown","raw","FullBucketsIndices"],"kind":"struct"},"1934":{"crate_id":17,"path":["object","read","archive","ArchiveKind"],"kind":"enum"},"172":{"crate_id":1,"path":["std","io","Write"],"kind":"trait"},"1053":{"crate_id":2,"path":["core","ops","unsize","DispatchFromDyn"],"kind":"trait"},"499":{"crate_id":2,"path":["core","slice","iter","RChunksExact"],"kind":"struct"},"1380":{"crate_id":5,"path":["libc","unix","linux_like","linux","inotify_event"],"kind":"struct"},"2261":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation64"],"kind":"struct"},"826":{"crate_id":2,"path":["core","str","iter","LinesAny"],"kind":"struct"},"1707":{"crate_id":16,"path":["gimli","constants","DwAte"],"kind":"struct"},"1153":{"crate_id":3,"path":["alloc","string","retain","SetLenOnDrop"],"kind":"struct"},"272":{"crate_id":1,"path":["std","sync","mpsc","TrySendError"],"kind":"enum"},"2034":{"crate_id":17,"path":["object","read","macho","segment","MachOSegment"],"kind":"struct"},"599":{"crate_id":2,"path":["core","num","fmt","Part"],"kind":"enum"},"1480":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_req_u"],"kind":"union"},"2361":{"crate_id":18,"path":["memchr","memmem","searcher","PrefilterConfig"],"kind":"enum"},"1807":{"crate_id":16,"path":["gimli","read","line","LineProgramHeader"],"kind":"struct"},"926":{"crate_id":2,"path":["core","core_arch","x86","__m128"],"kind":"struct"},"1253":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","big","u256"],"kind":"struct"},"372":{"crate_id":1,"path":["std","io","Guard"],"kind":"struct"},"2134":{"crate_id":17,"path":["object","elf","Dyn64"],"kind":"struct"},"2461":{"crate_id":1,"path":["std","u16"],"kind":"primitive"},"699":{"crate_id":2,"path":["core","core_arch","simd","u32x2"],"kind":"struct"},"1580":{"crate_id":10,"path":["hashbrown","control","bitmask","BitMask"],"kind":"struct"},"1907":{"crate_id":17,"path":["object","read","util","Bytes"],"kind":"struct"},"145":{"crate_id":1,"path":["std","env","Vars"],"kind":"struct"},"1026":{"crate_id":2,"path":["core","ops","bit","Shl"],"kind":"trait"},"1353":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_ramp_effect"],"kind":"struct"},"472":{"crate_id":2,"path":["core","ops","bit","BitOr"],"kind":"trait"},"2234":{"crate_id":17,"path":["object","pe","ImageAuxSymbolFunction"],"kind":"struct"},"799":{"crate_id":2,"path":["core","hash","sip","SipHasher24"],"kind":"struct"},"1680":{"crate_id":16,"path":["gimli","common","DebugStrOffset"],"kind":"struct"},"2007":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldFile"],"kind":"struct"},"245":{"crate_id":1,"path":["std","process","ExitCode"],"kind":"struct"},"1126":{"crate_id":3,"path":["alloc","boxed","thin","drop","DropGuard"],"kind":"struct"},"572":{"crate_id":1,"path":["std","os","unix","process","ChildExt"],"kind":"trait"},"1453":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_desc"],"kind":"struct"},"2334":{"crate_id":18,"path":["memchr","arch","generic","memchr","Three"],"kind":"struct"},"899":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1780":{"crate_id":16,"path":["gimli","read","reader","ReaderOffsetId"],"kind":"struct"},"2107":{"crate_id":17,"path":["object","read","CompressionFormat"],"kind":"enum"},"345":{"crate_id":1,"path":["std","thread","spawnhook","SpawnHooks"],"kind":"struct"},"1226":{"crate_id":3,"path":["alloc","collections","btree","map","CursorMutKey"],"kind":"struct"},"672":{"crate_id":2,"path":["core","sync","atomic","Ordering"],"kind":"enum"},"1553":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","user"],"kind":"struct"},"2434":{"crate_id":12,"path":["std_detect"],"kind":"module"},"118":{"crate_id":1,"path":["std","collections","hash","map","Values"],"kind":"struct"},"999":{"crate_id":2,"path":["core","slice","iter","GenericSplitN"],"kind":"struct"},"1880":{"crate_id":17,"path":["object","common","Architecture"],"kind":"enum"},"1326":{"crate_id":5,"path":["libc","unix","linux_like","linux","packet_mreq"],"kind":"struct"},"445":{"crate_id":2,"path":["core","iter","adapters","inspect","Inspect"],"kind":"struct"},"2207":{"crate_id":17,"path":["object","macho","Nlist32"],"kind":"struct"},"772":{"crate_id":2,"path":["core","ffi","va_list","VaListImpl"],"kind":"struct"},"1653":{"crate_id":15,"path":["addr2line","line","LineLocationRangeIter"],"kind":"struct"},"1980":{"crate_id":17,"path":["object","read","elf","relocation","CrelIteratorState"],"kind":"struct"},"218":{"crate_id":1,"path":["std","panic","PanicHookInfo"],"kind":"struct"},"1099":{"crate_id":2,"path":["core","core_simd","swizzle","interleave","Lo"],"kind":"struct"},"1426":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_missed"],"kind":"struct"},"545":{"crate_id":2,"path":["core","option","IntoIter"],"kind":"struct"},"2307":{"crate_id":17,"path":["object","xcoff","StatAux"],"kind":"struct"},"872":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1753":{"crate_id":16,"path":["gimli","read","cfi","CommonInformationEntry"],"kind":"struct"},"2080":{"crate_id":17,"path":["object","read","xcoff","symbol","XcoffSymbolIterator"],"kind":"struct"},"318":{"crate_id":1,"path":["std","sys","fs","unix","ReadDir"],"kind":"struct"},"1199":{"crate_id":3,"path":["alloc","collections","linked_list","IntoIter"],"kind":"struct"},"1526":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","mbstate_t"],"kind":"struct"},"645":{"crate_id":2,"path":["core","ops","index_range","IndexRange"],"kind":"struct"},"2407":{"crate_id":1,"path":["std","io"],"kind":"module"},"91":{"crate_id":3,"path":["alloc","vec","Vec"],"kind":"struct"},"972":{"crate_id":2,"path":["core","panic","panic_info","PanicInfo"],"kind":"struct"},"1853":{"crate_id":16,"path":["gimli","read","rnglists","RawRngListEntry"],"kind":"enum"},"2180":{"crate_id":17,"path":["object","macho","SymtabCommand"],"kind":"struct"},"418":{"crate_id":2,"path":["core","ops","range","RangeFull"],"kind":"struct"},"1299":{"crate_id":5,"path":["libc","unix","linux_like","lconv"],"kind":"struct"},"1626":{"crate_id":10,"path":["hashbrown","map","OccupiedEntry"],"kind":"struct"},"745":{"crate_id":2,"path":["core","core_arch","simd","i64x8"],"kind":"struct"},"191":{"crate_id":1,"path":["std","io","util","Repeat"],"kind":"struct"},"1072":{"crate_id":2,"path":["core","str","pattern","DoubleEndedSearcher"],"kind":"trait"},"1953":{"crate_id":17,"path":["object","read","coff","symbol","CoffSymbolTable"],"kind":"struct"},"2280":{"crate_id":17,"path":["object","pe","ImageCoffSymbolsHeader"],"kind":"struct"},"518":{"crate_id":2,"path":["core","convert","AsMut"],"kind":"trait"},"1399":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_clock_time"],"kind":"struct"},"845":{"crate_id":2,"path":["core","str","CharEscapeDebugContinue"],"kind":"struct"},"1726":{"crate_id":16,"path":["gimli","constants","DwMacinfo"],"kind":"struct"},"291":{"crate_id":1,"path":["std","sync","poison","condvar","Condvar"],"kind":"struct"},"1172":{"crate_id":3,"path":["alloc","collections","btree","node","Handle"],"kind":"struct"},"2053":{"crate_id":17,"path":["object","read","pe","data_directory","DataDirectories"],"kind":"struct"},"1499":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__timeval"],"kind":"struct"},"618":{"crate_id":2,"path":["core","num","niche_types","UsizeNoHighBit"],"kind":"struct"},"2380":{"crate_id":1,"path":["std","fs","read_link"],"kind":"function"},"945":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1826":{"crate_id":16,"path":["gimli","read","macros","DebugMacro"],"kind":"struct"},"391":{"crate_id":2,"path":["core","marker","Copy"],"kind":"trait"},"1272":{"crate_id":5,"path":["libc","unix","rlimit"],"kind":"struct"},"2153":{"crate_id":17,"path":["object","macho","DyldCacheSlidePointer5"],"kind":"struct"},"1599":{"crate_id":10,"path":["hashbrown","set","Intersection"],"kind":"struct"},"718":{"crate_id":2,"path":["core","core_arch","simd","f64x2"],"kind":"struct"},"1045":{"crate_id":2,"path":["core","marker","ConstParamTy_"],"kind":"trait"},"164":{"crate_id":1,"path":["std","fs","Metadata"],"kind":"struct"},"1926":{"crate_id":17,"path":["object","read","any","SymbolTableInternal"],"kind":"enum"},"2253":{"crate_id":17,"path":["object","pe","ImageResourceDirectory"],"kind":"struct"},"491":{"crate_id":2,"path":["core","slice","iter","Windows"],"kind":"struct"},"1372":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous__kernel_fsid_t"],"kind":"struct"},"1699":{"crate_id":16,"path":["gimli","constants","DwSect"],"kind":"struct"},"818":{"crate_id":2,"path":["core","str","iter","RSplitN"],"kind":"struct"},"264":{"crate_id":1,"path":["std","sync","mpsc","IntoIter"],"kind":"struct"},"1145":{"crate_id":3,"path":["alloc","collections","vec_deque","shrink_to","Guard"],"kind":"struct"},"2026":{"crate_id":17,"path":["object","read","macho","file","MachOFile"],"kind":"struct"},"2353":{"crate_id":18,"path":["memchr","memchr","Memchr"],"kind":"struct"},"591":{"crate_id":2,"path":["core","num","dec2flt","common","BiasedFp"],"kind":"struct"},"1472":{"crate_id":5,"path":["libc","unix","linux_like","linux","mq_attr"],"kind":"struct"},"1799":{"crate_id":16,"path":["gimli","read","index","IndexSectionId"],"kind":"enum"},"918":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"364":{"crate_id":1,"path":["std","sys","thread_local","native","lazy","State"],"kind":"enum"},"1245":{"crate_id":3,"path":["alloc","collections","btree","node","marker","ValMut"],"kind":"struct"},"2126":{"crate_id":17,"path":["object","elf","Rela32"],"kind":"struct"},"2453":{"crate_id":1,"path":["std","f64"],"kind":"primitive"},"691":{"crate_id":2,"path":["core","core_arch","simd","u8x2"],"kind":"struct"},"1572":{"crate_id":8,"path":["miniz_oxide","inflate","output_buffer","InputWrapper"],"kind":"struct"},"1018":{"crate_id":2,"path":["core","ops","arith","Rem"],"kind":"trait"},"137":{"crate_id":2,"path":["core","hash","Hash"],"kind":"trait"},"1899":{"crate_id":17,"path":["object","endian","U32Bytes"],"kind":"struct"},"464":{"crate_id":1,"path":["std","io","BufRead"],"kind":"trait"},"1345":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_id"],"kind":"struct"},"2226":{"crate_id":17,"path":["object","pe","AnonObjectHeaderV2"],"kind":"struct"},"1672":{"crate_id":16,"path":["gimli","common","DebugLocListsBase"],"kind":"struct"},"791":{"crate_id":2,"path":["core","range","iter","IterRangeFrom"],"kind":"struct"},"1118":{"crate_id":2,"path":["core","core_simd","simd","ptr","const_ptr","SimdConstPtr"],"kind":"trait"},"237":{"crate_id":1,"path":["std","process","ChildStderr"],"kind":"struct"},"1999":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsectionIterator"],"kind":"struct"},"564":{"crate_id":1,"path":["std","os","fd","raw","AsRawFd"],"kind":"trait"},"1445":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_mmap_offsets"],"kind":"struct"},"2326":{"crate_id":18,"path":["memchr","arch","all","twoway","FinderRev"],"kind":"struct"},"1772":{"crate_id":16,"path":["gimli","read","dwarf","Unit"],"kind":"struct"},"891":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1218":{"crate_id":3,"path":["alloc","boxed","convert","from","StringError"],"kind":"struct"},"337":{"crate_id":1,"path":["std","sys","process","env","CommandEnvs"],"kind":"struct"},"2099":{"crate_id":17,"path":["object","read","ObjectMapFile"],"kind":"struct"},"2426":{"crate_id":4,"path":["compiler_builtins"],"kind":"module"},"664":{"crate_id":2,"path":["core","net","parser","AddrKind"],"kind":"enum"},"1545":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","stat64"],"kind":"struct"},"1872":{"crate_id":16,"path":["gimli","read","unit","EntriesTreeNode"],"kind":"struct"},"110":{"crate_id":1,"path":["std","backtrace","RawFrame"],"kind":"enum"},"991":{"crate_id":2,"path":["core","fmt","builders","FromFn"],"kind":"struct"},"1318":{"crate_id":5,"path":["libc","unix","linux_like","linux","glob_t"],"kind":"struct"},"437":{"crate_id":2,"path":["core","iter","adapters","map_while","MapWhile"],"kind":"struct"},"2199":{"crate_id":17,"path":["object","macho","LinkerOptionCommand"],"kind":"struct"},"764":{"crate_id":2,"path":["core","char","decode","DecodeUtf16"],"kind":"struct"},"1645":{"crate_id":13,"path":["rustc_demangle","v0","Demangle"],"kind":"struct"},"1972":{"crate_id":17,"path":["object","read","elf","symbol","ElfSymbolIterator"],"kind":"struct"},"210":{"crate_id":1,"path":["std","os","unix","net","datagram","UnixDatagram"],"kind":"struct"},"1091":{"crate_id":2,"path":["core","str","pattern","Searcher"],"kind":"trait"},"537":{"crate_id":1,"path":["std","io","stdio","StdoutRaw"],"kind":"struct"},"1418":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_sm4_ccm"],"kind":"struct"},"2299":{"crate_id":17,"path":["object","xcoff","FileAux64"],"kind":"struct"},"864":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1745":{"crate_id":16,"path":["gimli","read","cfi","EhFrame"],"kind":"struct"},"1191":{"crate_id":3,"path":["alloc","collections","btree","set","Range"],"kind":"struct"},"310":{"crate_id":1,"path":["std","sys","pal","unix","time","Instant"],"kind":"struct"},"2072":{"crate_id":17,"path":["object","read","pe","rich","RichHeaderEntry"],"kind":"struct"},"637":{"crate_id":2,"path":["core","marker","variance","PhantomContravariantLifetime"],"kind":"struct"},"1518":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_seccomp"],"kind":"struct"},"2399":{"crate_id":2,"path":["core","iter"],"kind":"module"},"964":{"crate_id":2,"path":["core","cell","lazy","LazyCell"],"kind":"struct"},"1845":{"crate_id":16,"path":["gimli","read","pubtypes","PubTypesEntry"],"kind":"struct"},"1291":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_in"],"kind":"struct"},"410":{"crate_id":2,"path":["core","convert","AsRef"],"kind":"trait"},"2172":{"crate_id":17,"path":["object","macho","SubClientCommand"],"kind":"struct"},"737":{"crate_id":2,"path":["core","core_arch","simd","i8x64"],"kind":"struct"},"1618":{"crate_id":10,"path":["hashbrown","set","Drain"],"kind":"struct"},"1945":{"crate_id":17,"path":["object","read","coff","file","CoffFile"],"kind":"struct"},"183":{"crate_id":1,"path":["std","io","pipe","PipeWriter"],"kind":"struct"},"1064":{"crate_id":2,"path":["core","marker","Tuple"],"kind":"trait"},"1391":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif_addfd"],"kind":"struct"},"510":{"crate_id":2,"path":["core","slice","iter","SplitNMut"],"kind":"struct"},"2272":{"crate_id":17,"path":["object","pe","ImageArm64RuntimeFunctionEntry"],"kind":"struct"},"837":{"crate_id":2,"path":["core","str","pattern","CharArrayRefSearcher"],"kind":"struct"},"1718":{"crate_id":16,"path":["gimli","constants","DwInl"],"kind":"struct"},"2045":{"crate_id":17,"path":["object","read","pe","file","PeComdatIterator"],"kind":"struct"},"283":{"crate_id":1,"path":["std","sync","nonpoison","mutex","Mutex"],"kind":"struct"},"1164":{"crate_id":3,"path":["alloc","vec","set_len_on_drop","SetLenOnDrop"],"kind":"struct"},"1491":{"crate_id":5,"path":["libc","unix","linux_like","linux","iwreq"],"kind":"struct"},"610":{"crate_id":2,"path":["core","num","niche_types","NonZeroU64Inner"],"kind":"struct"},"2372":{"crate_id":18,"path":["memchr","memmem","searcher","Pre"],"kind":"struct"},"937":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1818":{"crate_id":16,"path":["gimli","read","loclists","RawLocListEntry"],"kind":"enum"},"2145":{"crate_id":17,"path":["object","macho","DyldCacheHeader"],"kind":"struct"},"383":{"crate_id":1,"path":["std","sys","process","unix","unix","posix_spawn","PosixSpawnattr"],"kind":"struct"},"1264":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","sockaddr_can"],"kind":"struct"},"710":{"crate_id":2,"path":["core","core_arch","simd","u64x2"],"kind":"struct"},"1591":{"crate_id":10,"path":["hashbrown","raw","RawIterHashInner"],"kind":"struct"},"156":{"crate_id":1,"path":["std","ffi","os_str","Display"],"kind":"struct"},"1037":{"crate_id":2,"path":["core","iter","adapters","filter_map","next_chunk","Guard"],"kind":"struct"},"1918":{"crate_id":17,"path":["object","read","any","SectionIteratorInternal"],"kind":"enum"},"1364":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Sym"],"kind":"struct"},"483":{"crate_id":2,"path":["core","ops","range","RangeBounds"],"kind":"trait"},"2245":{"crate_id":17,"path":["object","pe","ImageThunkData64"],"kind":"struct"},"810":{"crate_id":2,"path":["core","str","iter","SplitInternal"],"kind":"struct"},"1691":{"crate_id":16,"path":["gimli","arch","Arm"],"kind":"struct"},"256":{"crate_id":1,"path":["std","sync","mpmc","zero","ZeroToken"],"kind":"struct"},"1137":{"crate_id":3,"path":["alloc","collections","linked_list","LinkedList"],"kind":"struct"},"2018":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorV2"],"kind":"struct"},"1464":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf32_rela"],"kind":"struct"},"583":{"crate_id":1,"path":["std","std_float","StdFloat"],"kind":"trait"},"2345":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","OneIter"],"kind":"struct"},"910":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1791":{"crate_id":16,"path":["gimli","read","aranges","ArangeHeader"],"kind":"struct"},"2118":{"crate_id":17,"path":["object","elf","SectionHeader64"],"kind":"struct"},"356":{"crate_id":1,"path":["std","sync","poison","Guard"],"kind":"struct"},"1237":{"crate_id":3,"path":["alloc","string","FromUtf16Error"],"kind":"struct"},"1564":{"crate_id":5,"path":["libc","types","Padding"],"kind":"struct"},"683":{"crate_id":2,"path":["core","time","TryFromFloatSecsError"],"kind":"struct"},"2445":{"crate_id":1,"path":["std","unit"],"kind":"primitive"},"1010":{"crate_id":2,"path":["core","fmt","Octal"],"kind":"trait"},"129":{"crate_id":1,"path":["std","collections","hash","map","ExtractIf"],"kind":"struct"},"1891":{"crate_id":17,"path":["object","common","SegmentFlags"],"kind":"enum"},"2218":{"crate_id":17,"path":["object","pe","ImageOptionalHeader32"],"kind":"struct"},"456":{"crate_id":2,"path":["core","iter","adapters","cloned","Cloned"],"kind":"struct"},"1337":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket3_hdr"],"kind":"struct"},"1664":{"crate_id":16,"path":["gimli","common","DebugAddrOffset"],"kind":"struct"},"783":{"crate_id":2,"path":["core","iter","sources","repeat_n","RepeatN"],"kind":"struct"},"229":{"crate_id":1,"path":["std","path","PathBuf"],"kind":"struct"},"1110":{"crate_id":2,"path":["core","core_simd","to_bytes","ToBytes"],"kind":"trait"},"1991":{"crate_id":17,"path":["object","read","elf","version","VersionIndex"],"kind":"struct"},"2318":{"crate_id":18,"path":["memchr","arch","all","memchr","Three"],"kind":"struct"},"556":{"crate_id":1,"path":["std","os","unix","fs","PermissionsExt"],"kind":"trait"},"1437":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_condattr_t"],"kind":"struct"},"883":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"2":{"crate_id":3,"path":["alloc","string","String"],"kind":"struct"},"1764":{"crate_id":16,"path":["gimli","read","cfi","CallFrameInstructionIter"],"kind":"struct"},"329":{"crate_id":1,"path":["std","sys","process","unix","common","Stdio"],"kind":"enum"},"1210":{"crate_id":3,"path":["alloc","collections","btree","map","IterMut"],"kind":"struct"},"2091":{"crate_id":17,"path":["object","read","ObjectKind"],"kind":"enum"},"2418":{"crate_id":2,"path":["core","core_simd","simd","ptr","const_ptr","SimdConstPtr","with_exposed_provenance"],"kind":"function"},"656":{"crate_id":2,"path":["core","char","convert","CharErrorKind"],"kind":"enum"},"1537":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","timex"],"kind":"struct"},"983":{"crate_id":2,"path":["core","sync","atomic","AtomicI32"],"kind":"struct"},"102":{"crate_id":1,"path":["std","thread","scoped","ScopedJoinHandle"],"kind":"struct"},"1864":{"crate_id":16,"path":["gimli","read","unit","UnitHeader"],"kind":"struct"},"429":{"crate_id":2,"path":["core","iter","adapters","intersperse","IntersperseWith"],"kind":"struct"},"1310":{"crate_id":5,"path":["libc","unix","linux_like","statx"],"kind":"struct"},"2191":{"crate_id":17,"path":["object","macho","LinkeditDataCommand"],"kind":"struct"},"1637":{"crate_id":10,"path":["hashbrown","table","Entry"],"kind":"enum"},"756":{"crate_id":2,"path":["core","num","fmt","Formatted"],"kind":"struct"},"1083":{"crate_id":2,"path":["core","net","display_buffer","DisplayBuffer"],"kind":"struct"},"202":{"crate_id":1,"path":["std","net","tcp","IntoIncoming"],"kind":"struct"},"1964":{"crate_id":17,"path":["object","read","elf","file","ElfFile"],"kind":"struct"},"2291":{"crate_id":17,"path":["object","xcoff","AuxHeader32"],"kind":"struct"},"529":{"crate_id":2,"path":["core","str","error","Utf8Error"],"kind":"struct"},"1410":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_authinfo"],"kind":"struct"},"1737":{"crate_id":16,"path":["gimli","read","addr","AddrHeaderIter"],"kind":"struct"},"856":{"crate_id":2,"path":["core","future","ready","Ready"],"kind":"struct"},"1183":{"crate_id":3,"path":["alloc","collections","btree","map","UnorderedKeyError"],"kind":"struct"},"302":{"crate_id":1,"path":["std","sync","WaitTimeoutResult"],"kind":"struct"},"2064":{"crate_id":17,"path":["object","read","pe","relocation","RelocationIterator"],"kind":"struct"},"2391":{"crate_id":2,"path":["core","ffi","primitives","c_char"],"kind":"type_alias"},"629":{"crate_id":2,"path":["core","mem","transmutability","Assume"],"kind":"struct"},"1510":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ntptimeval"],"kind":"struct"},"1837":{"crate_id":16,"path":["gimli","read","op","EvaluationWaiting"],"kind":"enum"},"956":{"crate_id":2,"path":["core","mem","drop_guard","DropGuard"],"kind":"struct"},"402":{"crate_id":1,"path":["std","panicking","panic_handler","FormatStringPayload"],"kind":"struct"},"1283":{"crate_id":5,"path":["libc","unix","servent"],"kind":"struct"},"2164":{"crate_id":17,"path":["object","macho","SegmentCommand64"],"kind":"struct"},"729":{"crate_id":2,"path":["core","core_arch","simd","i32x8"],"kind":"struct"},"1610":{"crate_id":10,"path":["hashbrown","map","IntoKeys"],"kind":"struct"},"1056":{"crate_id":2,"path":["core","convert","num","private","Sealed"],"kind":"trait"},"175":{"crate_id":1,"path":["std","io","buffered","IntoInnerError"],"kind":"struct"},"1937":{"crate_id":17,"path":["object","read","archive","ArchiveMemberIterator"],"kind":"struct"},"502":{"crate_id":2,"path":["core","slice","iter","ChunkByMut"],"kind":"struct"},"1383":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_event_info_fid"],"kind":"struct"},"2264":{"crate_id":17,"path":["object","pe","ImagePrologueDynamicRelocationHeader"],"kind":"struct"},"829":{"crate_id":2,"path":["core","str","iter","SplitInclusive"],"kind":"struct"},"1710":{"crate_id":16,"path":["gimli","constants","DwEnd"],"kind":"struct"},"1156":{"crate_id":3,"path":["alloc","sync","Weak"],"kind":"struct"},"275":{"crate_id":1,"path":["std","sync","barrier","Barrier"],"kind":"struct"},"2037":{"crate_id":17,"path":["object","read","macho","section","MachOSection"],"kind":"struct"},"602":{"crate_id":2,"path":["core","num","error","IntErrorKind"],"kind":"enum"},"1483":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_cond_t"],"kind":"struct"},"2364":{"crate_id":18,"path":["memchr","memmem","searcher","PrefilterState"],"kind":"struct"},"1810":{"crate_id":16,"path":["gimli","read","line","FileEntry"],"kind":"struct"},"48":{"crate_id":2,"path":["core","any","TypeId"],"kind":"struct"},"929":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1256":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","env","Status"],"kind":"struct"},"375":{"crate_id":1,"path":["std","sync","mpmc","waker","SyncWaker"],"kind":"struct"},"2137":{"crate_id":17,"path":["object","elf","Verdaux"],"kind":"struct"},"2464":{"crate_id":1,"path":["std","u128"],"kind":"primitive"},"702":{"crate_id":2,"path":["core","core_arch","simd","i16x4"],"kind":"struct"},"1583":{"crate_id":10,"path":["hashbrown","raw","Fallibility"],"kind":"enum"},"1910":{"crate_id":17,"path":["object","read","util","ByteString"],"kind":"struct"},"148":{"crate_id":1,"path":["std","env","SplitPaths"],"kind":"struct"},"1029":{"crate_id":2,"path":["core","ops","bit","ShrAssign"],"kind":"trait"},"1356":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_rumble_effect"],"kind":"struct"},"475":{"crate_id":2,"path":["core","ops","arith","Sub"],"kind":"trait"},"2237":{"crate_id":17,"path":["object","pe","ImageAuxSymbolSection"],"kind":"struct"},"802":{"crate_id":2,"path":["core","hash","sip","Hasher"],"kind":"struct"},"1683":{"crate_id":16,"path":["gimli","common","DebugTypesOffset"],"kind":"struct"},"2010":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingSlice"],"kind":"enum"},"248":{"crate_id":1,"path":["std","sync","mpmc","context","Context"],"kind":"struct"},"1129":{"crate_id":3,"path":["alloc","collections","binary_heap","Hole"],"kind":"struct"},"575":{"crate_id":1,"path":["std","os","linux","process","CommandExt"],"kind":"trait"},"1456":{"crate_id":5,"path":["libc","unix","linux_like","linux","mount_attr"],"kind":"struct"},"2337":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","One"],"kind":"struct"},"21":{"crate_id":2,"path":["core","panic","unwind_safe","UnwindSafe"],"kind":"trait"},"902":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1783":{"crate_id":16,"path":["gimli","read","abbrev","AbbreviationsCacheStrategy"],"kind":"enum"},"2110":{"crate_id":17,"path":["object","archive","Header"],"kind":"struct"},"348":{"crate_id":3,"path":["alloc","boxed","Box"],"kind":"struct"},"1229":{"crate_id":3,"path":["alloc","collections","btree","set","entry","VacantEntry"],"kind":"struct"},"675":{"crate_id":2,"path":["core","fmt","Sign"],"kind":"enum"},"1556":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","shmid_ds"],"kind":"struct"},"2437":{"crate_id":15,"path":["addr2line"],"kind":"module"},"121":{"crate_id":1,"path":["std","collections","hash","map","VacantEntry"],"kind":"struct"},"1002":{"crate_id":2,"path":["core","future","poll_fn","PollFn"],"kind":"struct"},"1883":{"crate_id":17,"path":["object","common","BinaryFormat"],"kind":"enum"},"1329":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_hdr"],"kind":"struct"},"448":{"crate_id":2,"path":["core","ops","try_trait","Residual"],"kind":"trait"},"2210":{"crate_id":17,"path":["object","macho","RelocationInfo"],"kind":"struct"},"775":{"crate_id":2,"path":["core","iter","adapters","map_windows","MapWindowsInner"],"kind":"struct"},"1656":{"crate_id":15,"path":["addr2line","RangeAttributes"],"kind":"struct"},"1983":{"crate_id":17,"path":["object","read","elf","comdat","ElfComdat"],"kind":"struct"},"221":{"crate_id":1,"path":["std","path","State"],"kind":"enum"},"1102":{"crate_id":2,"path":["core","core_simd","swizzle","deinterleave","Odd"],"kind":"struct"},"1429":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_pmksa"],"kind":"struct"},"548":{"crate_id":2,"path":["core","net","socket_addr","SocketAddrV6"],"kind":"struct"},"2310":{"crate_id":17,"path":["object","xcoff","Rel32"],"kind":"struct"},"875":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1756":{"crate_id":16,"path":["gimli","read","cfi","UnwindContext"],"kind":"struct"},"2083":{"crate_id":17,"path":["object","read","xcoff","comdat","XcoffComdatIterator"],"kind":"struct"},"321":{"crate_id":1,"path":["std","sys","fs","unix","File"],"kind":"struct"},"1202":{"crate_id":3,"path":["alloc","collections","vec_deque","iter","Iter"],"kind":"struct"},"1529":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","timespec"],"kind":"struct"},"648":{"crate_id":2,"path":["core","ops","range","RangeInclusive"],"kind":"struct"},"2410":{"crate_id":1,"path":["std","process","exit"],"kind":"function"},"94":{"crate_id":2,"path":["core","option","Option"],"kind":"enum"},"975":{"crate_id":2,"path":["core","panicking","AssertKind"],"kind":"enum"},"1856":{"crate_id":16,"path":["gimli","read","rnglists","Range"],"kind":"struct"},"2183":{"crate_id":17,"path":["object","macho","DylibModule32"],"kind":"struct"},"421":{"crate_id":2,"path":["core","ops","function","FnMut"],"kind":"trait"},"1302":{"crate_id":5,"path":["libc","unix","linux_like","in6_rtmsg"],"kind":"struct"},"748":{"crate_id":2,"path":["core","core_arch","simd","u16x64"],"kind":"struct"},"1629":{"crate_id":10,"path":["hashbrown","map","VacantEntryRef"],"kind":"struct"},"194":{"crate_id":1,"path":["std","io","IoSlice"],"kind":"struct"},"1075":{"crate_id":2,"path":["core","iter","traits","marker","TrustedStep"],"kind":"trait"},"1956":{"crate_id":17,"path":["object","read","coff","relocation","CoffRelocationIterator"],"kind":"struct"},"2283":{"crate_id":17,"path":["object","pe","ImageFunctionEntry64"],"kind":"struct"},"521":{"crate_id":2,"path":["core","slice","GetDisjointMutError"],"kind":"enum"},"1402":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_sys_offset_precise"],"kind":"struct"},"848":{"crate_id":2,"path":["core","str","IsWhitespace"],"kind":"struct"},"1729":{"crate_id":16,"path":["gimli","constants","DwOp"],"kind":"struct"},"294":{"crate_id":1,"path":["std","sync","poison","mutex","MappedMutexGuard"],"kind":"struct"},"1175":{"crate_id":3,"path":["alloc","collections","binary_heap","Iter"],"kind":"struct"},"2056":{"crate_id":17,"path":["object","read","pe","export","ExportTable"],"kind":"struct"},"1502":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","cmsghdr"],"kind":"struct"},"621":{"crate_id":2,"path":["core","num","niche_types","U32NotAllOnes"],"kind":"struct"},"2383":{"crate_id":1,"path":["std","fs"],"kind":"module"},"948":{"crate_id":2,"path":["core","core_arch","x86","__m128h"],"kind":"struct"},"1829":{"crate_id":16,"path":["gimli","read","macros","MacroEntry"],"kind":"enum"},"2156":{"crate_id":17,"path":["object","macho","FatHeader"],"kind":"struct"},"394":{"crate_id":1,"path":["std","sys","pal","unix","kernel_copy","FdHandle"],"kind":"enum"},"1275":{"crate_id":5,"path":["libc","unix","hostent"],"kind":"struct"},"1602":{"crate_id":10,"path":["hashbrown","set","Union"],"kind":"struct"},"721":{"crate_id":2,"path":["core","core_arch","simd","m32x4"],"kind":"struct"},"1048":{"crate_id":2,"path":["core","pin","helper","PinHelper"],"kind":"struct"},"167":{"crate_id":1,"path":["std","hash","random","DefaultHasher"],"kind":"struct"},"1929":{"crate_id":17,"path":["object","read","any","Symbol"],"kind":"struct"},"2256":{"crate_id":17,"path":["object","pe","ImageResourceDirStringU"],"kind":"struct"},"494":{"crate_id":2,"path":["core","slice","iter","ChunksExact"],"kind":"struct"},"1375":{"crate_id":5,"path":["libc","unix","linux_like","linux","posix_spawn_file_actions_t"],"kind":"struct"},"1702":{"crate_id":16,"path":["gimli","constants","DwCfa"],"kind":"struct"},"821":{"crate_id":2,"path":["core","str","iter","RMatchIndices"],"kind":"struct"},"267":{"crate_id":1,"path":["std","sync","mpsc","RecvTimeoutError"],"kind":"enum"},"1148":{"crate_id":3,"path":["alloc","ffi","c_str","CString"],"kind":"struct"},"2029":{"crate_id":17,"path":["object","read","macho","file","MachOComdatSectionIterator"],"kind":"struct"},"2356":{"crate_id":18,"path":["memchr","memmem","searcher","Searcher"],"kind":"struct"},"594":{"crate_id":2,"path":["core","num","dec2flt","ParseFloatError"],"kind":"struct"},"1475":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ifc_ifcu"],"kind":"union"},"1802":{"crate_id":16,"path":["gimli","read","line","LineInstruction"],"kind":"enum"},"40":{"crate_id":2,"path":["core","result","Result"],"kind":"enum"},"921":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"367":{"crate_id":1,"path":["std","thread","spawn_unchecked_","MaybeDangling"],"kind":"struct"},"1248":{"crate_id":3,"path":["alloc","sync","ArcInner"],"kind":"struct"},"2129":{"crate_id":17,"path":["object","elf","Relr32"],"kind":"struct"},"2456":{"crate_id":1,"path":["std","i16"],"kind":"primitive"},"694":{"crate_id":2,"path":["core","core_arch","simd","u16x2"],"kind":"struct"},"1575":{"crate_id":8,"path":["miniz_oxide","MZStatus"],"kind":"enum"},"1021":{"crate_id":2,"path":["core","ops","arith","Mul"],"kind":"trait"},"140":{"crate_id":1,"path":["std","collections","hash","set","SymmetricDifference"],"kind":"struct"},"1902":{"crate_id":17,"path":["object","endian","I32Bytes"],"kind":"struct"},"467":{"crate_id":1,"path":["std","os","unix","net","ancillary","ScmCredentials"],"kind":"struct"},"1348":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_mask"],"kind":"struct"},"2229":{"crate_id":17,"path":["object","pe","ImageSymbol"],"kind":"struct"},"1675":{"crate_id":16,"path":["gimli","common","DebugMacroOffset"],"kind":"struct"},"794":{"crate_id":2,"path":["core","fmt","rt","Placeholder"],"kind":"struct"},"1121":{"crate_id":2,"path":["core","core_simd","simd","cmp","eq","SimdPartialEq"],"kind":"trait"},"240":{"crate_id":1,"path":["std","process","CommandEnvs"],"kind":"struct"},"2002":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsubsection"],"kind":"struct"},"2329":{"crate_id":18,"path":["memchr","arch","all","twoway","SuffixKind"],"kind":"enum"},"567":{"crate_id":1,"path":["std","os","fd","raw","FromRawFd"],"kind":"trait"},"1448":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_umem_reg"],"kind":"struct"},"1775":{"crate_id":16,"path":["gimli","read","dwarf","RangeIterInner"],"kind":"enum"},"13":{"crate_id":2,"path":["core","marker","Send"],"kind":"trait"},"894":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1221":{"crate_id":3,"path":["alloc","collections","btree","map","entry","VacantEntry"],"kind":"struct"},"340":{"crate_id":1,"path":["std","backtrace_rs","backtrace","Frame"],"kind":"struct"},"2102":{"crate_id":17,"path":["object","read","CodeView"],"kind":"struct"},"2429":{"crate_id":7,"path":["unwind"],"kind":"module"},"667":{"crate_id":2,"path":["core","pin","Pin"],"kind":"struct"},"1548":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","pthread_attr_t"],"kind":"struct"},"1875":{"crate_id":16,"path":["gimli","read","unit","DebugTypesUnitHeadersIter"],"kind":"struct"},"113":{"crate_id":1,"path":["std","backtrace","BacktraceSymbol"],"kind":"struct"},"994":{"crate_id":2,"path":["core","fmt","num_buffer","NumBufferTrait"],"kind":"trait"},"440":{"crate_id":2,"path":["core","iter","adapters","scan","Scan"],"kind":"struct"},"1321":{"crate_id":5,"path":["libc","unix","linux_like","linux","dqblk"],"kind":"struct"},"2202":{"crate_id":17,"path":["object","macho","FvmfileCommand"],"kind":"struct"},"767":{"crate_id":2,"path":["core","char","EscapeDebug"],"kind":"struct"},"1648":{"crate_id":13,"path":["rustc_demangle","Demangle"],"kind":"struct"},"1975":{"crate_id":17,"path":["object","read","elf","relocation","ElfDynamicRelocationIterator"],"kind":"struct"},"213":{"crate_id":1,"path":["std","os","unix","net","stream","UnixStream"],"kind":"struct"},"1094":{"crate_id":2,"path":["core","core_simd","swizzle","reverse","Reverse"],"kind":"struct"},"540":{"crate_id":1,"path":["std","sys","stdio","unix","Stderr"],"kind":"struct"},"1421":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_param"],"kind":"struct"},"2302":{"crate_id":17,"path":["object","xcoff","FunAux32"],"kind":"struct"},"867":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1748":{"crate_id":16,"path":["gimli","read","cfi","SectionBaseAddresses"],"kind":"struct"},"1194":{"crate_id":3,"path":["alloc","collections","btree","set","Intersection"],"kind":"struct"},"313":{"crate_id":1,"path":["std","sys","env","common","Env"],"kind":"struct"},"2075":{"crate_id":17,"path":["object","read","xcoff","section","XcoffSection"],"kind":"struct"},"640":{"crate_id":2,"path":["core","marker","variance","PhantomContravariant"],"kind":"struct"},"1521":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","iocb"],"kind":"struct"},"2402":{"crate_id":2,"path":["core","write"],"kind":"macro"},"1848":{"crate_id":16,"path":["gimli","read","rnglists","DebugRanges"],"kind":"struct"},"967":{"crate_id":2,"path":["core","ffi","va_list","VaList"],"kind":"struct"},"1294":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_ll"],"kind":"struct"},"413":{"crate_id":3,"path":["alloc","ffi","c_str","NulError"],"kind":"struct"},"2175":{"crate_id":17,"path":["object","macho","PreboundDylibCommand"],"kind":"struct"},"740":{"crate_id":2,"path":["core","core_arch","simd","u16x32"],"kind":"struct"},"1621":{"crate_id":10,"path":["hashbrown","table","IterHashMut"],"kind":"struct"},"1948":{"crate_id":17,"path":["object","read","coff","section","CoffSegment"],"kind":"struct"},"186":{"crate_id":1,"path":["std","io","stdio","Stdout"],"kind":"struct"},"1067":{"crate_id":2,"path":["core","ops","async_function","AsyncFnOnce"],"kind":"trait"},"1394":{"crate_id":5,"path":["libc","unix","linux_like","linux","nlattr"],"kind":"struct"},"513":{"crate_id":2,"path":["core","slice","SlicePattern"],"kind":"trait"},"2275":{"crate_id":17,"path":["object","pe","ImageRuntimeFunctionEntry"],"kind":"struct"},"840":{"crate_id":2,"path":["core","str","pattern","StrSearcher"],"kind":"struct"},"1721":{"crate_id":16,"path":["gimli","constants","DwIdx"],"kind":"struct"},"2048":{"crate_id":17,"path":["object","read","pe","section","PeSegmentIterator"],"kind":"struct"},"286":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","RwLock"],"kind":"struct"},"1167":{"crate_id":3,"path":["alloc","vec","retain_mut","BackshiftOnDrop"],"kind":"struct"},"1494":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_perout_request"],"kind":"struct"},"613":{"crate_id":2,"path":["core","num","niche_types","NonZeroI16Inner"],"kind":"struct"},"2375":{"crate_id":1,"path":["std","path","MAIN_SEPARATOR"],"kind":"constant"},"940":{"crate_id":2,"path":["core","core_arch","x86","__m512d"],"kind":"struct"},"1821":{"crate_id":16,"path":["gimli","read","lookup","DebugLookup"],"kind":"struct"},"2148":{"crate_id":17,"path":["object","macho","DyldCacheImageInfo"],"kind":"struct"},"386":{"crate_id":1,"path":["std","sys","thread","unix","Thread"],"kind":"struct"},"1267":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","__c_anonymous_sockaddr_can_j1939"],"kind":"struct"},"713":{"crate_id":2,"path":["core","core_arch","simd","i32x4"],"kind":"struct"},"1594":{"crate_id":10,"path":["hashbrown","map","Iter"],"kind":"struct"},"159":{"crate_id":1,"path":["std","fs","FileTimes"],"kind":"struct"},"1040":{"crate_id":2,"path":["core","slice","sort","unstable","quicksort","GapGuardRaw"],"kind":"struct"},"1921":{"crate_id":17,"path":["object","read","any","ComdatIteratorInternal"],"kind":"enum"},"1367":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Phdr"],"kind":"struct"},"486":{"crate_id":2,"path":["core","slice","ascii","EscapeAscii"],"kind":"struct"},"2248":{"crate_id":17,"path":["object","pe","ImageTlsDirectory32"],"kind":"struct"},"813":{"crate_id":2,"path":["core","str","iter","RSplit"],"kind":"struct"},"1694":{"crate_id":16,"path":["gimli","arch","MIPS"],"kind":"struct"},"259":{"crate_id":1,"path":["std","sync","mpmc","TryIter"],"kind":"struct"},"1140":{"crate_id":3,"path":["alloc","collections","vec_deque","drain","drop","DropGuard"],"kind":"struct"},"2021":{"crate_id":17,"path":["object","read","macho","dyld_cache","RelocationStateV5"],"kind":"enum"},"1467":{"crate_id":5,"path":["libc","unix","linux_like","linux","dirent"],"kind":"struct"},"586":{"crate_id":1,"path":["std","os","linux","process","ChildExt"],"kind":"trait"},"2348":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","Three"],"kind":"struct"},"913":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1794":{"crate_id":16,"path":["gimli","read","index","DebugCuIndex"],"kind":"struct"},"2121":{"crate_id":17,"path":["object","elf","Sym32"],"kind":"struct"},"359":{"crate_id":1,"path":["std","sys","fs","unix","FileAttr"],"kind":"struct"},"1240":{"crate_id":3,"path":["alloc","task","LocalWake"],"kind":"trait"},"1567":{"crate_id":5,"path":["libc","unix","linux_like","timezone"],"kind":"enum"},"686":{"crate_id":2,"path":["core","wtf8","Wtf8"],"kind":"struct"},"2448":{"crate_id":1,"path":["std","slice"],"kind":"primitive"},"132":{"crate_id":1,"path":["std","collections","hash","set","IntoIter"],"kind":"struct"},"1013":{"crate_id":2,"path":["core","fmt","LowerExp"],"kind":"trait"},"1894":{"crate_id":17,"path":["object","common","RelocationFlags"],"kind":"enum"},"2221":{"crate_id":17,"path":["object","pe","ImageNtHeaders64"],"kind":"struct"},"459":{"crate_id":2,"path":["core","iter","traits","accum","Sum"],"kind":"trait"},"1340":{"crate_id":5,"path":["libc","unix","linux_like","linux","cpu_set_t"],"kind":"struct"},"1667":{"crate_id":16,"path":["gimli","common","DebugArangesOffset"],"kind":"struct"},"786":{"crate_id":2,"path":["core","net","ip_addr","fmt","Span"],"kind":"struct"},"232":{"crate_id":1,"path":["std","path","Path"],"kind":"struct"},"1113":{"crate_id":2,"path":["core","core_simd","vector","sealed","Sealed"],"kind":"trait"},"1994":{"crate_id":17,"path":["object","read","elf","version","VerdefIterator"],"kind":"struct"},"2321":{"crate_id":18,"path":["memchr","arch","all","packedpair","Pair"],"kind":"struct"},"559":{"crate_id":1,"path":["std","os","unix","fs","FileTypeExt"],"kind":"trait"},"1440":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_sys_offset"],"kind":"struct"},"886":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1767":{"crate_id":16,"path":["gimli","read","cfi","PointerEncodingParameters"],"kind":"struct"},"332":{"crate_id":1,"path":["std","sys","process","unix","common","ExitCode"],"kind":"struct"},"1213":{"crate_id":3,"path":["alloc","collections","btree","map","IntoKeys"],"kind":"struct"},"2094":{"crate_id":17,"path":["object","read","SymbolSection"],"kind":"enum"},"1540":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","flock"],"kind":"struct"},"659":{"crate_id":2,"path":["core","char","TryFromCharError"],"kind":"struct"},"2421":{"crate_id":2,"path":["core","core_simd","simd","ptr","mut_ptr","SimdMutPtr","with_exposed_provenance"],"kind":"function"},"986":{"crate_id":2,"path":["core","sync","atomic","AtomicU64"],"kind":"struct"},"105":{"crate_id":1,"path":["std","thread","Builder"],"kind":"struct"},"1867":{"crate_id":16,"path":["gimli","read","unit","Attribute"],"kind":"struct"},"432":{"crate_id":2,"path":["core","iter","adapters","filter_map","FilterMap"],"kind":"struct"},"1313":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_un"],"kind":"struct"},"2194":{"crate_id":17,"path":["object","macho","EncryptionInfoCommand64"],"kind":"struct"},"1640":{"crate_id":10,"path":["hashbrown","table","AbsentEntry"],"kind":"struct"},"759":{"crate_id":2,"path":["core","array","iter","iter_inner","PolymorphicIter"],"kind":"struct"},"1086":{"crate_id":2,"path":["core","pat","RangePattern"],"kind":"trait"},"205":{"crate_id":1,"path":["std","net","udp","UdpSocket"],"kind":"struct"},"1967":{"crate_id":17,"path":["object","read","elf","section","SectionTable"],"kind":"struct"},"2294":{"crate_id":17,"path":["object","xcoff","SectionHeader64"],"kind":"struct"},"532":{"crate_id":2,"path":["core","io","borrowed_buf","BorrowedCursor"],"kind":"struct"},"1413":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aes_gcm_128"],"kind":"struct"},"1740":{"crate_id":16,"path":["gimli","read","cfi","DebugFrame"],"kind":"struct"},"859":{"crate_id":2,"path":["core","task","wake","LocalWaker"],"kind":"struct"},"1186":{"crate_id":3,"path":["alloc","collections","btree","navigate","LeafRange"],"kind":"struct"},"305":{"crate_id":1,"path":["std","time","SystemTime"],"kind":"struct"},"2067":{"crate_id":17,"path":["object","read","pe","resource","ResourceDirectoryTable"],"kind":"struct"},"2394":{"crate_id":2,"path":["core","str","pattern"],"kind":"module"},"632":{"crate_id":2,"path":["core","ptr","alignment","AlignmentEnum"],"kind":"enum"},"1513":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","Elf32_Chdr"],"kind":"struct"},"1840":{"crate_id":16,"path":["gimli","read","op","OperationIter"],"kind":"struct"},"959":{"crate_id":2,"path":["core","error","tags","Ref"],"kind":"struct"},"405":{"crate_id":1,"path":["std","panicking","resume_unwind","RewrapBox"],"kind":"struct"},"1286":{"crate_id":5,"path":["libc","unix","linux_like","in_addr"],"kind":"struct"},"2167":{"crate_id":17,"path":["object","macho","Fvmlib"],"kind":"struct"},"732":{"crate_id":2,"path":["core","core_arch","simd","f32x8"],"kind":"struct"},"1613":{"crate_id":10,"path":["hashbrown","map","IterMut"],"kind":"struct"},"1059":{"crate_id":2,"path":["core","ops","coroutine","Coroutine"],"kind":"trait"},"178":{"crate_id":1,"path":["std","io","error","SimpleMessage"],"kind":"struct"},"1940":{"crate_id":17,"path":["object","read","archive","ArchiveOffset"],"kind":"struct"},"505":{"crate_id":2,"path":["core","slice","iter","SplitInclusive"],"kind":"struct"},"1386":{"crate_id":5,"path":["libc","unix","linux_like","linux","sock_extended_err"],"kind":"struct"},"2267":{"crate_id":17,"path":["object","pe","ImageLoadConfigDirectory64"],"kind":"struct"},"832":{"crate_id":2,"path":["core","str","iter","EscapeDefault"],"kind":"struct"},"1713":{"crate_id":16,"path":["gimli","constants","DwVirtuality"],"kind":"struct"},"1159":{"crate_id":3,"path":["alloc","vec","extract_if","ExtractIf"],"kind":"struct"},"278":{"crate_id":1,"path":["std","sync","once_lock","OnceLock"],"kind":"struct"},"2040":{"crate_id":17,"path":["object","read","macho","symbol","MachOSymbolTable"],"kind":"struct"},"605":{"crate_id":2,"path":["core","num","wrapping","Wrapping"],"kind":"struct"},"1486":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_barrier_t"],"kind":"struct"},"2367":{"crate_id":18,"path":["memchr","memmem","Finder"],"kind":"struct"},"1813":{"crate_id":16,"path":["gimli","read","loclists","DebugLoc"],"kind":"struct"},"51":{"crate_id":0,"path":["kne_test_calculator","Operation","Add"],"kind":"variant"},"932":{"crate_id":2,"path":["core","core_arch","x86","__m256"],"kind":"struct"},"1259":{"crate_id":4,"path":["compiler_builtins","math","libm_math","generic","fma","Norm"],"kind":"struct"},"378":{"crate_id":1,"path":["std","sys","pal","unix","stack_overflow","thread_info","UnlockOnDrop"],"kind":"struct"},"2140":{"crate_id":17,"path":["object","elf","NoteHeader32"],"kind":"struct"},"2467":{"crate_id":1,"path":["std","reference"],"kind":"primitive"},"705":{"crate_id":2,"path":["core","core_arch","simd","f32x2"],"kind":"struct"},"1586":{"crate_id":10,"path":["hashbrown","raw","ProbeSeq"],"kind":"struct"},"1913":{"crate_id":17,"path":["object","read","any","SegmentIterator"],"kind":"struct"},"151":{"crate_id":1,"path":["std","env","ArgsOs"],"kind":"struct"},"1032":{"crate_id":2,"path":["core","clone","uninit","InitializingSlice"],"kind":"struct"},"1359":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_ff_erase"],"kind":"struct"},"478":{"crate_id":2,"path":["core","fmt","Write"],"kind":"trait"},"2240":{"crate_id":17,"path":["object","pe","ImageLinenumber"],"kind":"struct"},"805":{"crate_id":2,"path":["core","slice","sort","stable","drift","DriftsortRun"],"kind":"struct"},"1686":{"crate_id":16,"path":["gimli","common","EhFrameOffset"],"kind":"struct"},"2013":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMapping"],"kind":"struct"},"251":{"crate_id":1,"path":["std","sync","mpmc","list","Position"],"kind":"struct"},"1132":{"crate_id":3,"path":["alloc","collections","btree","map","BTreeMap"],"kind":"struct"},"578":{"crate_id":1,"path":["std","process","Termination"],"kind":"trait"},"1459":{"crate_id":5,"path":["libc","unix","linux_like","linux","dmabuf_cmsg"],"kind":"struct"},"2340":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","TwoIter"],"kind":"struct"},"905":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1786":{"crate_id":16,"path":["gimli","read","abbrev","Abbreviation"],"kind":"struct"},"1232":{"crate_id":3,"path":["alloc","collections","btree","set","ExtractIf"],"kind":"struct"},"351":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","lru","Lru"],"kind":"struct"},"2113":{"crate_id":17,"path":["object","archive","AixMemberOffset"],"kind":"struct"},"678":{"crate_id":2,"path":["core","hash","BuildHasherDefault"],"kind":"struct"},"1559":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","user_fpregs_struct"],"kind":"struct"},"2440":{"crate_id":18,"path":["memchr"],"kind":"module"},"124":{"crate_id":1,"path":["std","collections","hash","map","IntoIter"],"kind":"struct"},"1005":{"crate_id":2,"path":["core","task","wake","ContextBuilder"],"kind":"struct"},"1886":{"crate_id":17,"path":["object","common","SymbolKind"],"kind":"enum"},"1332":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_req"],"kind":"struct"},"451":{"crate_id":2,"path":["core","iter","traits","exact_size","ExactSizeIterator"],"kind":"trait"},"2213":{"crate_id":17,"path":["object","pe","ImageOs2Header"],"kind":"struct"},"778":{"crate_id":2,"path":["core","iter","sources","from_fn","FromFn"],"kind":"struct"},"1659":{"crate_id":16,"path":["gimli","common","Vendor"],"kind":"enum"},"1986":{"crate_id":17,"path":["object","read","elf","note","Note"],"kind":"struct"},"224":{"crate_id":1,"path":["std","path","Components"],"kind":"struct"},"1105":{"crate_id":2,"path":["core","core_simd","vector","splat","splat_rt","Splat"],"kind":"struct"},"1432":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_range"],"kind":"struct"},"551":{"crate_id":2,"path":["core","net","ip_addr","Ipv6Addr"],"kind":"struct"},"2313":{"crate_id":17,"path":["object","read","elf","relocation","ElfRelocationIterator"],"kind":"enum"},"878":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1759":{"crate_id":16,"path":["gimli","read","cfi","RegisterRuleIter"],"kind":"struct"},"2086":{"crate_id":17,"path":["object","read","xcoff","segment","XcoffSegmentIterator"],"kind":"struct"},"324":{"crate_id":1,"path":["std","sys","net","connection","socket","TcpListener"],"kind":"struct"},"1205":{"crate_id":3,"path":["alloc","ffi","c_str","FromVecWithNulError"],"kind":"struct"},"1532":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","utmpx"],"kind":"struct"},"651":{"crate_id":2,"path":["core","bstr","ByteStr"],"kind":"struct"},"2413":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt","st_ctime"],"kind":"function"},"97":{"crate_id":2,"path":["core","marker","UnsafeUnpin"],"kind":"trait"},"978":{"crate_id":2,"path":["core","result","IterMut"],"kind":"struct"},"1859":{"crate_id":16,"path":["gimli","read","str","DebugLineStr"],"kind":"struct"},"2186":{"crate_id":17,"path":["object","macho","TwolevelHintsCommand"],"kind":"struct"},"424":{"crate_id":2,"path":["core","num","nonzero","NonZero"],"kind":"struct"},"1305":{"crate_id":5,"path":["libc","unix","linux_like","arphdr"],"kind":"struct"},"751":{"crate_id":2,"path":["core","core_arch","x86","cpuid","CpuidResult"],"kind":"struct"},"1632":{"crate_id":10,"path":["hashbrown","rustc_entry","RustcOccupiedEntry"],"kind":"struct"},"197":{"crate_id":1,"path":["std","io","Take"],"kind":"struct"},"1078":{"crate_id":2,"path":["core","async_iter","async_iter","AsyncIterator"],"kind":"trait"},"1959":{"crate_id":17,"path":["object","read","coff","comdat","CoffComdatSectionIterator"],"kind":"struct"},"2286":{"crate_id":17,"path":["object","pe","ImageArchitectureEntry"],"kind":"struct"},"524":{"crate_id":3,"path":["alloc","slice","Concat"],"kind":"trait"},"1405":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_sndrcvinfo"],"kind":"struct"},"851":{"crate_id":2,"path":["core","str","BytesIsNotEmpty"],"kind":"struct"},"1732":{"crate_id":16,"path":["gimli","endianity","LittleEndian"],"kind":"struct"},"297":{"crate_id":1,"path":["std","sync","poison","rwlock","RwLockWriteGuard"],"kind":"struct"},"1178":{"crate_id":3,"path":["alloc","collections","btree","map","Iter"],"kind":"struct"},"2059":{"crate_id":17,"path":["object","read","pe","import","ImportThunkList"],"kind":"struct"},"1505":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","mallinfo2"],"kind":"struct"},"624":{"crate_id":2,"path":["core","num","niche_types","I64NotAllOnes"],"kind":"struct"},"2386":{"crate_id":2,"path":["core","slice","raw","from_raw_parts"],"kind":"function"},"951":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1832":{"crate_id":16,"path":["gimli","read","op","Operation"],"kind":"enum"},"2159":{"crate_id":17,"path":["object","macho","MachHeader32"],"kind":"struct"},"397":{"crate_id":3,"path":["alloc","borrow","Cow"],"kind":"enum"},"1278":{"crate_id":5,"path":["libc","unix","winsize"],"kind":"struct"},"1605":{"crate_id":10,"path":["hashbrown","table","IterHash"],"kind":"struct"},"724":{"crate_id":2,"path":["core","core_arch","simd","u16x16"],"kind":"struct"},"1051":{"crate_id":2,"path":["core","ops","unsize","CoerceUnsized"],"kind":"trait"},"170":{"crate_id":1,"path":["std","io","buffered","bufwriter","WriterPanicked"],"kind":"struct"},"1932":{"crate_id":17,"path":["object","read","any","SectionRelocationIterator"],"kind":"struct"},"2259":{"crate_id":17,"path":["object","pe","ImageDynamicRelocationTable"],"kind":"struct"},"497":{"crate_id":2,"path":["core","slice","iter","RChunks"],"kind":"struct"},"1378":{"crate_id":5,"path":["libc","unix","linux_like","linux","in6_pktinfo"],"kind":"struct"},"1705":{"crate_id":16,"path":["gimli","constants","DwAt"],"kind":"struct"},"824":{"crate_id":2,"path":["core","str","iter","RMatches"],"kind":"struct"},"270":{"crate_id":1,"path":["std","sync","mpsc","Receiver"],"kind":"struct"},"1151":{"crate_id":3,"path":["alloc","rc","UniqueRcUninit"],"kind":"struct"},"2032":{"crate_id":17,"path":["object","read","macho","load_command","LoadCommandVariant"],"kind":"enum"},"2359":{"crate_id":18,"path":["memchr","memmem","searcher","SearcherRev"],"kind":"struct"},"597":{"crate_id":2,"path":["core","num","flt2dec","decoder","FullDecoded"],"kind":"enum"},"1478":{"crate_id":5,"path":["libc","unix","linux_like","linux","dirent64"],"kind":"struct"},"924":{"crate_id":2,"path":["core","core_arch","x86","__m128i"],"kind":"struct"},"1805":{"crate_id":16,"path":["gimli","read","line","ColumnType"],"kind":"enum"},"370":{"crate_id":1,"path":["std","io","buffered","bufwriter","flush_buf","BufGuard"],"kind":"struct"},"1251":{"crate_id":4,"path":["compiler_builtins","int","big","u256"],"kind":"struct"},"2132":{"crate_id":17,"path":["object","elf","ProgramHeader64"],"kind":"struct"},"2459":{"crate_id":1,"path":["std","i128"],"kind":"primitive"},"697":{"crate_id":2,"path":["core","core_arch","simd","u8x8"],"kind":"struct"},"1578":{"crate_id":9,"path":["adler2","algo","U32X4"],"kind":"struct"},"1024":{"crate_id":2,"path":["core","ops","bit","BitXorAssign"],"kind":"trait"},"143":{"crate_id":1,"path":["std","collections","hash","set","OccupiedEntry"],"kind":"struct"},"1905":{"crate_id":17,"path":["object","read","read_cache","ReadCacheInternal"],"kind":"struct"},"470":{"crate_id":1,"path":["std","sys_common","wstr","WStrUnits"],"kind":"struct"},"1351":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_envelope"],"kind":"struct"},"2232":{"crate_id":17,"path":["object","pe","ImageSymbolExBytes"],"kind":"struct"},"1678":{"crate_id":16,"path":["gimli","common","DebugRngListsBase"],"kind":"struct"},"797":{"crate_id":2,"path":["core","fmt","rt","Argument"],"kind":"struct"},"1124":{"crate_id":3,"path":["alloc","raw_vec","RawVec"],"kind":"struct"},"243":{"crate_id":1,"path":["std","process","ExitStatus"],"kind":"struct"},"2005":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCache"],"kind":"struct"},"2332":{"crate_id":18,"path":["memchr","arch","generic","memchr","One"],"kind":"struct"},"570":{"crate_id":1,"path":["std","os","unix","process","CommandExt"],"kind":"trait"},"1451":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_statistics_v1"],"kind":"struct"},"1778":{"crate_id":16,"path":["gimli","read","endian_slice","DebugByte"],"kind":"struct"},"897":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1224":{"crate_id":3,"path":["alloc","collections","btree","map","ExtractIf"],"kind":"struct"},"343":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","parse_running_mmaps","MapsEntry"],"kind":"struct"},"2105":{"crate_id":17,"path":["object","read","RelocationMap"],"kind":"struct"},"2432":{"crate_id":10,"path":["hashbrown"],"kind":"module"},"670":{"crate_id":2,"path":["core","range","RangeFrom"],"kind":"struct"},"1551":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","_libc_fpstate"],"kind":"struct"},"1878":{"crate_id":16,"path":["gimli","read","StoreOnHeap"],"kind":"struct"},"116":{"crate_id":1,"path":["std","collections","hash","map","Iter"],"kind":"struct"},"997":{"crate_id":2,"path":["core","cell","UnsafeCell"],"kind":"struct"},"443":{"crate_id":2,"path":["core","iter","adapters","map_windows","MapWindows"],"kind":"struct"},"1324":{"crate_id":5,"path":["libc","unix","linux_like","linux","fsid_t"],"kind":"struct"},"2205":{"crate_id":17,"path":["object","macho","DataInCodeEntry"],"kind":"struct"},"770":{"crate_id":2,"path":["core","char","CaseMappingIter"],"kind":"struct"},"1651":{"crate_id":13,"path":["rustc_demangle","SizeLimitExhausted"],"kind":"struct"},"1978":{"crate_id":17,"path":["object","read","elf","relocation","Crel"],"kind":"struct"},"216":{"crate_id":1,"path":["std","os","fd","owned","BorrowedFd"],"kind":"struct"},"1097":{"crate_id":2,"path":["core","core_simd","swizzle","shift_elements_left","Shift"],"kind":"struct"},"543":{"crate_id":1,"path":["std","sealed","Sealed"],"kind":"trait"},"1424":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_quality"],"kind":"struct"},"2305":{"crate_id":17,"path":["object","xcoff","BlockAux32"],"kind":"struct"},"870":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1751":{"crate_id":16,"path":["gimli","read","cfi","Augmentation"],"kind":"struct"},"1197":{"crate_id":3,"path":["alloc","collections","btree","set_val","SetValZST"],"kind":"struct"},"316":{"crate_id":1,"path":["std","sys","fs","unix","FileType"],"kind":"struct"},"2078":{"crate_id":17,"path":["object","read","xcoff","symbol","SymbolIterator"],"kind":"struct"},"643":{"crate_id":2,"path":["core","marker","PhantomPinned"],"kind":"struct"},"1524":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fanotify_event_info_error"],"kind":"struct"},"2405":{"crate_id":1,"path":["std","fs","read_to_string"],"kind":"function"},"1851":{"crate_id":16,"path":["gimli","read","rnglists","RangeListsFormat"],"kind":"enum"},"89":{"crate_id":0,"path":["kne_test_calculator","compute"],"kind":"function"},"970":{"crate_id":2,"path":["core","iter","adapters","by_ref_sized","ByRefSized"],"kind":"struct"},"1297":{"crate_id":5,"path":["libc","unix","linux_like","sched_param"],"kind":"struct"},"416":{"crate_id":1,"path":["std","ascii","AsciiExt"],"kind":"trait"},"2178":{"crate_id":17,"path":["object","macho","RoutinesCommand32"],"kind":"struct"},"743":{"crate_id":2,"path":["core","core_arch","simd","f16x32"],"kind":"struct"},"1624":{"crate_id":10,"path":["hashbrown","table","ExtractIf"],"kind":"struct"},"1951":{"crate_id":17,"path":["object","read","coff","symbol","SymbolTable"],"kind":"struct"},"189":{"crate_id":1,"path":["std","io","stdio","StderrLock"],"kind":"struct"},"1070":{"crate_id":2,"path":["core","ops","try_trait","NeverShortCircuitResidual"],"kind":"enum"},"1397":{"crate_id":5,"path":["libc","unix","linux_like","linux","option"],"kind":"struct"},"516":{"crate_id":2,"path":["core","core_simd","lane_count","LaneCount"],"kind":"struct"},"2278":{"crate_id":17,"path":["object","pe","ImageEnclaveImport"],"kind":"struct"},"843":{"crate_id":2,"path":["core","str","pattern","TwoWaySearcher"],"kind":"struct"},"1724":{"crate_id":16,"path":["gimli","constants","DwLne"],"kind":"struct"},"2051":{"crate_id":17,"path":["object","read","pe","section","PeSection"],"kind":"struct"},"289":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","MappedRwLockReadGuard"],"kind":"struct"},"1170":{"crate_id":3,"path":["alloc","collections","btree","node","NodeRef"],"kind":"struct"},"616":{"crate_id":2,"path":["core","num","niche_types","NonZeroI128Inner"],"kind":"struct"},"1497":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","aiocb"],"kind":"struct"},"2378":{"crate_id":1,"path":["std","fs","canonicalize"],"kind":"function"},"943":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1824":{"crate_id":16,"path":["gimli","read","lookup","PubStuffParser"],"kind":"struct"},"2151":{"crate_id":17,"path":["object","macho","DyldCacheSlidePointer3"],"kind":"struct"},"389":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","mmap","Mmap"],"kind":"struct"},"1270":{"crate_id":5,"path":["libc","unix","utimbuf"],"kind":"struct"},"716":{"crate_id":2,"path":["core","core_arch","simd","f16x8"],"kind":"struct"},"1597":{"crate_id":10,"path":["hashbrown","set","HashSet"],"kind":"struct"},"162":{"crate_id":1,"path":["std","fs","TryLockError"],"kind":"enum"},"1043":{"crate_id":2,"path":["core","intrinsics","fallback","DisjointBitOr"],"kind":"trait"},"1924":{"crate_id":17,"path":["object","read","any","ComdatSectionIteratorInternal"],"kind":"enum"},"1370":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf32_rel"],"kind":"struct"},"489":{"crate_id":2,"path":["core","slice","iter","Iter"],"kind":"struct"},"2251":{"crate_id":17,"path":["object","pe","ImageBoundForwarderRef"],"kind":"struct"},"816":{"crate_id":2,"path":["core","str","iter","SplitNInternal"],"kind":"struct"},"1697":{"crate_id":16,"path":["gimli","arch","X86_64"],"kind":"struct"},"2024":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldRelocationAuth"],"kind":"struct"},"262":{"crate_id":1,"path":["std","sync","mpsc","Iter"],"kind":"struct"},"1143":{"crate_id":3,"path":["alloc","collections","vec_deque","drop","Dropper"],"kind":"struct"},"1470":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_user_dev"],"kind":"struct"},"589":{"crate_id":2,"path":["core","num","bignum","Big32x40"],"kind":"struct"},"2351":{"crate_id":18,"path":["memchr","cow","CowBytes"],"kind":"struct"},"916":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1797":{"crate_id":16,"path":["gimli","read","index","UnitIndexSectionIterator"],"kind":"struct"},"2124":{"crate_id":17,"path":["object","elf","Syminfo64"],"kind":"struct"},"362":{"crate_id":1,"path":["std","sys","process","unix","common","cstring_array","CStringIter"],"kind":"struct"},"1243":{"crate_id":3,"path":["alloc","collections","btree","borrow","DormantMutRef"],"kind":"struct"},"1570":{"crate_id":8,"path":["miniz_oxide","inflate","core","State"],"kind":"enum"},"689":{"crate_id":2,"path":["core","task","wake","RawWakerVTable"],"kind":"struct"},"2451":{"crate_id":1,"path":["std","f16"],"kind":"primitive"},"135":{"crate_id":1,"path":["std","collections","hash","set","Intersection"],"kind":"struct"},"1016":{"crate_id":2,"path":["core","ops","arith","Div"],"kind":"trait"},"1897":{"crate_id":17,"path":["object","endian","BigEndian"],"kind":"struct"},"2224":{"crate_id":17,"path":["object","pe","Guid"],"kind":"struct"},"462":{"crate_id":2,"path":["core","iter","adapters","zip","TrustedRandomAccessNoCoerce"],"kind":"trait"},"1343":{"crate_id":5,"path":["libc","unix","linux_like","linux","sembuf"],"kind":"struct"},"1670":{"crate_id":16,"path":["gimli","common","DebugLineStrOffset"],"kind":"struct"},"789":{"crate_id":2,"path":["core","range","iter","IterRange"],"kind":"struct"},"235":{"crate_id":1,"path":["std","process","ChildStdin"],"kind":"struct"},"1116":{"crate_id":2,"path":["core","core_simd","simd","num","sealed","Sealed"],"kind":"trait"},"1997":{"crate_id":17,"path":["object","read","elf","version","VernauxIterator"],"kind":"struct"},"2324":{"crate_id":18,"path":["memchr","arch","all","rabinkarp","Hash"],"kind":"struct"},"562":{"crate_id":1,"path":["std","os","unix","fs","DirBuilderExt"],"kind":"trait"},"1443":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_xdp"],"kind":"struct"},"889":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1770":{"crate_id":16,"path":["gimli","read","dwarf","DwarfPackageSections"],"kind":"struct"},"335":{"crate_id":1,"path":["std","sys","process","unix","unix","ExitStatusError"],"kind":"struct"},"1216":{"crate_id":3,"path":["alloc","collections","linked_list","IterMut"],"kind":"struct"},"2097":{"crate_id":17,"path":["object","read","ObjectMap"],"kind":"struct"},"1543":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","stack_t"],"kind":"struct"},"662":{"crate_id":2,"path":["core","ffi","c_str","FromBytesUntilNulError"],"kind":"struct"},"2424":{"crate_id":2,"path":["core"],"kind":"module"},"989":{"crate_id":2,"path":["core","sync","atomic","AtomicBool"],"kind":"struct"},"108":{"crate_id":1,"path":["std","thread","JoinHandle"],"kind":"struct"},"1870":{"crate_id":16,"path":["gimli","read","unit","EntriesCursor"],"kind":"struct"},"435":{"crate_id":2,"path":["core","iter","adapters","skip_while","SkipWhile"],"kind":"struct"},"1316":{"crate_id":5,"path":["libc","unix","linux_like","sigevent"],"kind":"struct"},"2197":{"crate_id":17,"path":["object","macho","BuildToolVersion"],"kind":"struct"},"1643":{"crate_id":12,"path":["std_detect","detect","cache","Initializer"],"kind":"struct"},"762":{"crate_id":2,"path":["core","async_iter","from_iter","FromIter"],"kind":"struct"},"1089":{"crate_id":2,"path":["core","sync","atomic","Sealed"],"kind":"trait"},"208":{"crate_id":1,"path":["std","os","unix","net","ancillary","AncillaryError"],"kind":"enum"},"1970":{"crate_id":17,"path":["object","read","elf","symbol","SymbolTable"],"kind":"struct"},"2297":{"crate_id":17,"path":["object","xcoff","Symbol64"],"kind":"struct"},"535":{"crate_id":1,"path":["std","sys","fs","unix","cfm","CachedFileMetadata"],"kind":"struct"},"1416":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_chacha20_poly1305"],"kind":"struct"},"1743":{"crate_id":16,"path":["gimli","read","cfi","EhHdrTableIter"],"kind":"struct"},"862":{"crate_id":2,"path":["core","escape","MaybeEscaped"],"kind":"struct"},"308":{"crate_id":1,"path":["std","sys","pal","unix","pipe","AnonPipe"],"kind":"struct"},"1189":{"crate_id":3,"path":["alloc","collections","btree","set","BTreeSet"],"kind":"struct"},"2070":{"crate_id":17,"path":["object","read","pe","resource","ResourceNameOrId"],"kind":"enum"},"2397":{"crate_id":2,"path":["core","panic"],"kind":"macro"},"635":{"crate_id":2,"path":["core","cmp","Reverse"],"kind":"struct"},"1516":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_entry"],"kind":"struct"},"1843":{"crate_id":16,"path":["gimli","read","pubnames","DebugPubNames"],"kind":"struct"},"962":{"crate_id":2,"path":["core","asserting","TryCaptureWithoutDebug"],"kind":"struct"},"408":{"crate_id":2,"path":["core","hash","Hasher"],"kind":"trait"},"1289":{"crate_id":5,"path":["libc","unix","linux_like","ip_mreq_source"],"kind":"struct"},"2170":{"crate_id":17,"path":["object","macho","DylibCommand"],"kind":"struct"},"735":{"crate_id":2,"path":["core","core_arch","simd","m16x16"],"kind":"struct"},"1616":{"crate_id":10,"path":["hashbrown","map","Drain"],"kind":"struct"},"1062":{"crate_id":2,"path":["core","marker","variance","sealed","Sealed"],"kind":"trait"},"181":{"crate_id":1,"path":["std","io","error","repr_bitpacked","Repr"],"kind":"struct"},"1943":{"crate_id":17,"path":["object","read","archive","ArchiveSymbol"],"kind":"struct"},"508":{"crate_id":2,"path":["core","slice","iter","RSplitMut"],"kind":"struct"},"1389":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif"],"kind":"struct"},"2270":{"crate_id":17,"path":["object","pe","ImageHotPatchHashes"],"kind":"struct"},"1716":{"crate_id":16,"path":["gimli","constants","DwId"],"kind":"struct"},"835":{"crate_id":2,"path":["core","str","pattern","MultiCharEqSearcher"],"kind":"struct"},"1162":{"crate_id":3,"path":["alloc","vec","drain","drop","DropGuard"],"kind":"struct"},"281":{"crate_id":1,"path":["std","sync","nonpoison","WouldBlock"],"kind":"struct"},"2043":{"crate_id":17,"path":["object","read","macho","relocation","MachORelocationIterator"],"kind":"struct"},"608":{"crate_id":2,"path":["core","num","niche_types","NonZeroU16Inner"],"kind":"struct"},"1489":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_event"],"kind":"struct"},"2370":{"crate_id":18,"path":["memchr","vector","SensibleMoveMask"],"kind":"struct"},"1816":{"crate_id":16,"path":["gimli","read","loclists","LocListsFormat"],"kind":"enum"},"54":{"crate_id":0,"path":["kne_test_calculator","Operation","Divide"],"kind":"variant"},"935":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1262":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","canfd_frame"],"kind":"struct"},"381":{"crate_id":1,"path":["std","sys","process","unix","unix","do_exec","Reset"],"kind":"struct"},"2143":{"crate_id":17,"path":["object","elf","GnuHashHeader"],"kind":"struct"},"708":{"crate_id":2,"path":["core","core_arch","simd","u16x8"],"kind":"struct"},"1589":{"crate_id":10,"path":["hashbrown","raw","RawIterRange"],"kind":"struct"},"1916":{"crate_id":17,"path":["object","read","any","Segment"],"kind":"struct"},"154":{"crate_id":1,"path":["std","ffi","os_str","OsString"],"kind":"struct"},"1035":{"crate_id":2,"path":["core","cell","lazy","force_mut","really_init_mut","PoisonOnPanic"],"kind":"struct"},"1362":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Ehdr"],"kind":"struct"},"481":{"crate_id":2,"path":["core","ops","index","IndexMut"],"kind":"trait"},"2243":{"crate_id":17,"path":["object","pe","ImageExportDirectory"],"kind":"struct"},"808":{"crate_id":2,"path":["core","str","iter","CharIndices"],"kind":"struct"},"1689":{"crate_id":16,"path":["gimli","common","DwoId"],"kind":"struct"},"2016":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorVersion"],"kind":"enum"},"254":{"crate_id":1,"path":["std","sync","mpmc","select","Operation"],"kind":"struct"},"1135":{"crate_id":3,"path":["alloc","collections","btree","mem","replace","PanicGuard"],"kind":"struct"},"581":{"crate_id":2,"path":["core","ops","arith","AddAssign"],"kind":"trait"},"1462":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_mlme"],"kind":"struct"},"2343":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","packedpair","Finder"],"kind":"struct"},"27":{"crate_id":2,"path":["core","borrow","Borrow"],"kind":"trait"},"908":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1789":{"crate_id":16,"path":["gimli","read","aranges","DebugAranges"],"kind":"struct"},"1235":{"crate_id":3,"path":["alloc","collections","linked_list","CursorMut"],"kind":"struct"},"354":{"crate_id":1,"path":["std","os","unix","net","ancillary","SocketCred"],"kind":"struct"},"2116":{"crate_id":17,"path":["object","elf","Ident"],"kind":"struct"},"681":{"crate_id":2,"path":["core","str","pattern","SearchStep"],"kind":"enum"},"1562":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","not_x32","statvfs"],"kind":"struct"},"2443":{"crate_id":1,"path":["std","never"],"kind":"primitive"},"127":{"crate_id":1,"path":["std","collections","hash","map","IntoValues"],"kind":"struct"},"1008":{"crate_id":2,"path":["core","num","nonzero","private","Sealed"],"kind":"trait"},"1889":{"crate_id":17,"path":["object","common","RelocationEncoding"],"kind":"enum"},"1335":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_stats"],"kind":"struct"},"454":{"crate_id":2,"path":["core","iter","adapters","rev","Rev"],"kind":"struct"},"2216":{"crate_id":17,"path":["object","pe","ImageFileHeader"],"kind":"struct"},"781":{"crate_id":2,"path":["core","iter","sources","repeat","Repeat"],"kind":"struct"},"1662":{"crate_id":16,"path":["gimli","common","Register"],"kind":"struct"},"1989":{"crate_id":17,"path":["object","read","elf","hash","HashTable"],"kind":"struct"},"227":{"crate_id":1,"path":["std","path","fmt","DebugHelper"],"kind":"struct"},"1108":{"crate_id":2,"path":["core","core_simd","lane_count","sealed","Sealed"],"kind":"trait"},"1435":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_mutexattr_t"],"kind":"struct"},"554":{"crate_id":1,"path":["std","os","unix","ffi","os_str","OsStrExt"],"kind":"trait"},"2316":{"crate_id":18,"path":["memchr","arch","all","memchr","Two"],"kind":"struct"},"881":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"}},"external_crates":{"11":{"name":"rustc_std_workspace_alloc","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"3":{"name":"alloc","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"14":{"name":"cfg_if","html_root_url":"https://docs.rs/cfg-if/"},"6":{"name":"rustc_std_workspace_core","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"17":{"name":"object","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"9":{"name":"adler2","html_root_url":"https://docs.rs/adler2/2.0.0/"},"1":{"name":"std","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"12":{"name":"std_detect","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"4":{"name":"compiler_builtins","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"15":{"name":"addr2line","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"7":{"name":"unwind","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"18":{"name":"memchr","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"10":{"name":"hashbrown","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"2":{"name":"core","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"13":{"name":"rustc_demangle","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"5":{"name":"libc","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"16":{"name":"gimli","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"8":{"name":"miniz_oxide","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"19":{"name":"panic_unwind","html_root_url":"https://doc.rust-lang.org/1.92.0/"}},"target":{"triple":"x86_64-unknown-linux-gnu","target_features":[{"name":"adx","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"aes","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"amx-avx512","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-bf16","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-complex","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-fp8","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-fp16","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-int8","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-movrs","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-tf32","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-tile","implies_features":[],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-transpose","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"apxf","implies_features":[],"unstable_feature_gate":"apx_target_feature","globally_enabled":false},{"name":"avx","implies_features":["sse4.2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx2","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx10.1","implies_features":["avx512bf16","avx512bitalg","avx512bw","avx512cd","avx512dq","avx512f","avx512fp16","avx512ifma","avx512vbmi","avx512vbmi2","avx512vl","avx512vnni","avx512vpopcntdq"],"unstable_feature_gate":"avx10_target_feature","globally_enabled":false},{"name":"avx10.2","implies_features":["avx10.1"],"unstable_feature_gate":"avx10_target_feature","globally_enabled":false},{"name":"avx512bf16","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512bitalg","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512bw","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512cd","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512dq","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512f","implies_features":["avx2","fma","f16c"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512fp16","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512ifma","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vbmi","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vbmi2","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vl","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vnni","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vp2intersect","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vpopcntdq","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxifma","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxneconvert","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxvnni","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxvnniint8","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxvnniint16","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"bmi1","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"bmi2","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"cmpxchg16b","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"ermsb","implies_features":[],"unstable_feature_gate":"ermsb_target_feature","globally_enabled":false},{"name":"f16c","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"fma","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"fxsr","implies_features":[],"unstable_feature_gate":null,"globally_enabled":true},{"name":"gfni","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"kl","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"lahfsahf","implies_features":[],"unstable_feature_gate":"lahfsahf_target_feature","globally_enabled":false},{"name":"lzcnt","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"movbe","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"movrs","implies_features":[],"unstable_feature_gate":"movrs_target_feature","globally_enabled":false},{"name":"pclmulqdq","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"popcnt","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"prfchw","implies_features":[],"unstable_feature_gate":"prfchw_target_feature","globally_enabled":false},{"name":"rdrand","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"rdseed","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"rtm","implies_features":[],"unstable_feature_gate":"rtm_target_feature","globally_enabled":false},{"name":"sha","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sha512","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sm3","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sm4","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"soft-float","implies_features":[],"unstable_feature_gate":"x87_target_feature","globally_enabled":false},{"name":"sse","implies_features":[],"unstable_feature_gate":null,"globally_enabled":true},{"name":"sse2","implies_features":["sse"],"unstable_feature_gate":null,"globally_enabled":true},{"name":"sse3","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sse4.1","implies_features":["ssse3"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sse4.2","implies_features":["sse4.1"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sse4a","implies_features":["sse3"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"ssse3","implies_features":["sse3"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"tbm","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"vaes","implies_features":["avx2","aes"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"vpclmulqdq","implies_features":["avx","pclmulqdq"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"widekl","implies_features":["kl"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"x87","implies_features":[],"unstable_feature_gate":"x87_target_feature","globally_enabled":true},{"name":"xop","implies_features":["avx","sse4a"],"unstable_feature_gate":"xop_target_feature","globally_enabled":false},{"name":"xsave","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"xsavec","implies_features":["xsave"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"xsaveopt","implies_features":["xsave"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"xsaves","implies_features":["xsave"],"unstable_feature_gate":null,"globally_enabled":false}]},"format_version":56} \ No newline at end of file diff --git a/plugin-build/plugin/src/test/resources/rustdoc-fixtures/rust-calculator.json b/plugin-build/plugin/src/test/resources/rustdoc-fixtures/rust-calculator.json new file mode 100644 index 00000000..ed7495b3 --- /dev/null +++ b/plugin-build/plugin/src/test/resources/rustdoc-fixtures/rust-calculator.json @@ -0,0 +1 @@ +{"root":271,"crate_version":"0.1.0","includes_private":false,"index":{"0":{"id":0,"crate_id":0,"name":"Add","span":{"filename":"src/lib.rs","begin":[6,5],"end":[6,8]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"151":{"id":151,"crate_id":0,"name":"set_nickname","span":{"filename":"src/lib.rs","begin":[253,5],"end":[255,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["name",{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"resolved_path":{"path":"String","id":46,"args":null}}}],"constraints":[]}}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"25":{"id":25,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"176":{"id":176,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":5,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"50":{"id":50,"crate_id":0,"name":"Partial","span":{"filename":"src/lib.rs","begin":[18,5],"end":[18,44]},"visibility":"default","docs":"A partial result with value and confidence.","links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":{"struct":{"fields":[48,49],"has_stripped_fields":false}},"discriminant":null}}},"201":{"id":201,"crate_id":0,"name":"rustcalc_Calculator_subtract","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[114,1],"end":[123,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"75":{"id":75,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":19,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[16],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"226":{"id":226,"crate_id":0,"name":"rustcalc_Calculator_add_point_or_null","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[482,1],"end":[492,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["p",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"100":{"id":100,"crate_id":0,"name":"describe_self","span":{"filename":"src/lib.rs","begin":[422,5],"end":[424,6]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"251":{"id":251,"crate_id":0,"name":"rustcalc_Calculator_get_scale","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[965,1],"end":[974,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"125":{"id":125,"crate_id":0,"name":"fail_always","span":{"filename":"src/lib.rs","begin":[114,5],"end":[116,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"150":{"id":150,"crate_id":0,"name":"add_optional","span":{"filename":"src/lib.rs","begin":[246,5],"end":[251,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["value",{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"24":{"id":24,"crate_id":2,"name":"into","span":null,"visibility":"default","docs":"Calls `U::from(self)`.\n\nThat is, this conversion is whatever the implementation of\n[From]<T> for U chooses to do.","links":{"From":23},"attrs":[{"other":"#[attr = TrackCaller]"}],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"generic":"Self"}]],"output":{"generic":"U"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"175":{"id":175,"crate_id":0,"name":null,"span":{"filename":"src/lib.rs","begin":[71,1],"end":[417,2]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":null,"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,165,166,167,168,169,170,171,172,173,174],"is_negative":false,"is_synthetic":false,"blanket_impl":null}}},"49":{"id":49,"crate_id":0,"name":"confidence","span":{"filename":"src/lib.rs","begin":[18,27],"end":[18,42]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"f64"}}},"200":{"id":200,"crate_id":0,"name":"rustcalc_Calculator_add","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[102,1],"end":[111,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"74":{"id":74,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":15,"args":null},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"225":{"id":225,"crate_id":0,"name":"rustcalc_Calculator_get_nickname","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[456,1],"end":[479,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"99":{"id":99,"crate_id":0,"name":"Describable","span":{"filename":"src/lib.rs","begin":[38,1],"end":[40,2]},"visibility":"public","docs":"Something that can describe itself.","links":{},"attrs":[],"deprecation":null,"inner":{"trait":{"is_auto":false,"is_unsafe":false,"is_dyn_compatible":true,"items":[98],"generics":{"params":[],"where_predicates":[]},"bounds":[],"implementations":[101]}}},"250":{"id":250,"crate_id":0,"name":"rustcalc_Calculator_set_label","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[951,1],"end":[962,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"124":{"id":124,"crate_id":0,"name":"divide","span":{"filename":"src/lib.rs","begin":[106,5],"end":[112,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["divisor",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"149":{"id":149,"crate_id":0,"name":"to_double_or_null","span":{"filename":"src/lib.rs","begin":[236,5],"end":[242,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"f64"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"174":{"id":174,"crate_id":0,"name":"score_labels","span":{"filename":"src/lib.rs","begin":[414,5],"end":[416,6]},"visibility":"public","docs":"Emits score labels as strings.\n@kne:flow(String)","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["count",{"primitive":"i32"}]],"output":{"resolved_path":{"path":"Vec","id":164,"args":{"angle_bracketed":{"args":[{"type":{"resolved_path":{"path":"String","id":46,"args":null}}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"48":{"id":48,"crate_id":0,"name":"value","span":{"filename":"src/lib.rs","begin":[18,15],"end":[18,25]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"i32"}}},"199":{"id":199,"crate_id":0,"name":"rustcalc_Calculator_dispose","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[95,1],"end":[99,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"73":{"id":73,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":13,"args":null},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"224":{"id":224,"crate_id":0,"name":"rustcalc_Calculator_set_nickname","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[441,1],"end":[453,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["name",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"98":{"id":98,"crate_id":0,"name":"describe_self","span":{"filename":"src/lib.rs","begin":[39,5],"end":[39,39]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":false}}},"249":{"id":249,"crate_id":0,"name":"rustcalc_Calculator_get_label","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[930,1],"end":[948,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"123":{"id":123,"crate_id":0,"name":"reset","span":{"filename":"src/lib.rs","begin":[102,5],"end":[104,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"148":{"id":148,"crate_id":0,"name":"to_long_or_null","span":{"filename":"src/lib.rs","begin":[228,5],"end":[234,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i64"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"173":{"id":173,"crate_id":0,"name":"count_up","span":{"filename":"src/lib.rs","begin":[403,5],"end":[410,6]},"visibility":"public","docs":"Emits integers from 1 to max with interval_ms delay between each.\n@kne:flow(Int)","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["max",{"primitive":"i32"}],["interval_ms",{"primitive":"i32"}]],"output":{"resolved_path":{"path":"Vec","id":164,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"47":{"id":47,"crate_id":0,"name":"Error","span":{"filename":"src/lib.rs","begin":[16,5],"end":[16,18]},"visibility":"default","docs":"An error with a message.","links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":{"tuple":[45]},"discriminant":null}}},"198":{"id":198,"crate_id":0,"name":"rustcalc_Calculator_new","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[83,1],"end":[92,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["initial",{"primitive":"i32"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"72":{"id":72,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":11,"args":null},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"223":{"id":223,"crate_id":0,"name":"rustcalc_Calculator_add_optional","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[428,1],"end":[438,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"97":{"id":97,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":42,"args":null},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"248":{"id":248,"crate_id":0,"name":"rustcalc_Calculator_get_current","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[918,1],"end":[927,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"122":{"id":122,"crate_id":0,"name":"multiply","span":{"filename":"src/lib.rs","begin":[97,5],"end":[100,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"147":{"id":147,"crate_id":0,"name":"is_positive_or_null","span":{"filename":"src/lib.rs","begin":[220,5],"end":[226,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"bool"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"21":{"id":21,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":22,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[20],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"172":{"id":172,"crate_id":0,"name":"delayed_is_positive","span":{"filename":"src/lib.rs","begin":[392,5],"end":[395,6]},"visibility":"public","docs":"Returns whether accumulator is positive, after a delay.\n@kne:suspend","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["delay_ms",{"primitive":"i32"}]],"output":{"primitive":"bool"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"197":{"id":197,"crate_id":0,"name":"rustcalc_kne_readStringRef","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[69,1],"end":[80,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"71":{"id":71,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":9,"args":null},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"222":{"id":222,"crate_id":0,"name":"rustcalc_Calculator_to_double_or_null","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[415,1],"end":[425,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"96":{"id":96,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[35,37],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"247":{"id":247,"crate_id":0,"name":"rustcalc_Calculator_describe_self","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[897,1],"end":[915,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"121":{"id":121,"crate_id":0,"name":"subtract","span":{"filename":"src/lib.rs","begin":[92,5],"end":[95,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"146":{"id":146,"crate_id":0,"name":"describe_or_null","span":{"filename":"src/lib.rs","begin":[212,5],"end":[218,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"resolved_path":{"path":"String","id":46,"args":null}}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"20":{"id":20,"crate_id":2,"name":"borrow_mut","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}]],"output":{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"T"}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"171":{"id":171,"crate_id":0,"name":"delayed_noop","span":{"filename":"src/lib.rs","begin":[386,5],"end":[388,6]},"visibility":"public","docs":"Does nothing after a delay (suspend returning Unit).\n@kne:suspend","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["delay_ms",{"primitive":"i32"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"45":{"id":45,"crate_id":0,"name":"0","span":{"filename":"src/lib.rs","begin":[16,11],"end":[16,17]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"resolved_path":{"path":"String","id":46,"args":null}}}},"196":{"id":196,"crate_id":0,"name":"rustcalc_kne_disposeRef","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[63,1],"end":[66,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"70":{"id":70,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":7,"args":null},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"221":{"id":221,"crate_id":0,"name":"rustcalc_Calculator_to_long_or_null","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[402,1],"end":[412,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"95":{"id":95,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[29,31],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"246":{"id":246,"crate_id":0,"name":"rustcalc_Calculator_reset_to_default","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[885,1],"end":[894,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"120":{"id":120,"crate_id":0,"name":"add","span":{"filename":"src/lib.rs","begin":[87,5],"end":[90,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"271":{"id":271,"crate_id":0,"name":"rustcalc","span":{"filename":"src/lib.rs","begin":[4,1],"end":[555,55]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"module":{"is_crate":true,"items":[3,52,68,84,99,104,109,102,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270],"is_stripped":false}}},"170":{"id":170,"crate_id":0,"name":"fail_after_delay","span":{"filename":"src/lib.rs","begin":[379,5],"end":[382,6]},"visibility":"public","docs":"Panics after a delay (tests suspend error propagation).\n@kne:suspend","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["delay_ms",{"primitive":"i32"}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"44":{"id":44,"crate_id":0,"name":"Value","span":{"filename":"src/lib.rs","begin":[14,5],"end":[14,15]},"visibility":"default","docs":"A successful integer result.","links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":{"tuple":[43]},"discriminant":null}}},"195":{"id":195,"crate_id":0,"name":"rustcalc_kne_cancelJob","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[56,1],"end":[60,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["job_handle",{"primitive":"i64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"69":{"id":69,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":5,"args":null},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"220":{"id":220,"crate_id":0,"name":"rustcalc_Calculator_is_positive_or_null","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[389,1],"end":[399,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"94":{"id":94,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[27],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"245":{"id":245,"crate_id":0,"name":"rustcalc_Calculator_unit","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[864,1],"end":[882,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"119":{"id":119,"crate_id":0,"name":"new","span":{"filename":"src/lib.rs","begin":[74,5],"end":[83,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["initial",{"primitive":"i32"}]],"output":{"generic":"Self"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"270":{"id":270,"crate_id":0,"name":"rustcalc_find_max","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1167,1],"end":[1177,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["numbers_ptr",{"raw_pointer":{"is_mutable":false,"type":{"primitive":"i32"}}}],["numbers_len",{"primitive":"i32"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"144":{"id":144,"crate_id":0,"name":"divide_or_null","span":{"filename":"src/lib.rs","begin":[204,5],"end":[210,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["divisor",{"primitive":"i32"}]],"output":{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"169":{"id":169,"crate_id":0,"name":"delayed_describe","span":{"filename":"src/lib.rs","begin":[372,5],"end":[375,6]},"visibility":"public","docs":"Returns a description string after a delay.\n@kne:suspend","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["delay_ms",{"primitive":"i32"}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"43":{"id":43,"crate_id":0,"name":"0","span":{"filename":"src/lib.rs","begin":[14,11],"end":[14,14]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"i32"}}},"194":{"id":194,"crate_id":0,"name":"rustcalc_kne_getLastError","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[33,1],"end":[51,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"68":{"id":68,"crate_id":0,"name":"Point","span":{"filename":"src/lib.rs","begin":[24,1],"end":[27,2]},"visibility":"public","docs":"Simple 2D point (data class -- all public fields, no complex methods).","links":{},"attrs":[],"deprecation":null,"inner":{"struct":{"kind":{"plain":{"fields":[66,67],"has_stripped_fields":false}},"generics":{"params":[],"where_predicates":[]},"impls":[69,70,71,72,73,74,75,76,77,78,79,80,81]}}},"219":{"id":219,"crate_id":0,"name":"rustcalc_Calculator_describe_or_null","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[363,1],"end":[386,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"93":{"id":93,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"244":{"id":244,"crate_id":0,"name":"rustcalc_Calculator_measure","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[852,1],"end":[861,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"269":{"id":269,"crate_id":0,"name":"rustcalc_sum_all","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1155,1],"end":[1164,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["numbers_ptr",{"raw_pointer":{"is_mutable":false,"type":{"primitive":"i32"}}}],["numbers_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"143":{"id":143,"crate_id":0,"name":"apply_op","span":{"filename":"src/lib.rs","begin":[194,5],"end":[200,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["op",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"resolved_path":{"path":"Operation","id":3,"args":null}}}}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"17":{"id":17,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":19,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[16],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"168":{"id":168,"crate_id":0,"name":"delayed_add","span":{"filename":"src/lib.rs","begin":[364,5],"end":[368,6]},"visibility":"public","docs":"Adds value after a delay and returns the new accumulator.\n@kne:suspend","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["value",{"primitive":"i32"}],["delay_ms",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"193":{"id":193,"crate_id":0,"name":"rustcalc_kne_hasError","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[28,1],"end":[30,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"67":{"id":67,"crate_id":0,"name":"y","span":{"filename":"src/lib.rs","begin":[26,5],"end":[26,15]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"i32"}}},"218":{"id":218,"crate_id":0,"name":"rustcalc_Calculator_divide_or_null","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[350,1],"end":[360,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["divisor",{"primitive":"i32"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"92":{"id":92,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":22,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[20],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"243":{"id":243,"crate_id":0,"name":"rustcalc_Calculator_score_labels","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[818,1],"end":[849,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["count",{"primitive":"i32"}],["next_ptr",{"primitive":"i64"}],["error_ptr",{"primitive":"i64"}],["complete_ptr",{"primitive":"i64"}],["cancel_out",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i64"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"268":{"id":268,"crate_id":0,"name":"rustcalc_greet","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1133,1],"end":[1152,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["name",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"142":{"id":142,"crate_id":0,"name":"set_enabled","span":{"filename":"src/lib.rs","begin":[188,5],"end":[190,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["enabled",{"primitive":"bool"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"16":{"id":16,"crate_id":2,"name":"borrow","span":null,"visibility":"default","docs":null,"links":{},"attrs":[{"other":"#[rustc_diagnostic_item = \"noop_method_borrow\"]"}],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"T"}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"167":{"id":167,"crate_id":0,"name":"get_recent_scores","span":{"filename":"src/lib.rs","begin":[353,5],"end":[355,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Vec","id":164,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"41":{"id":41,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":42,"args":null},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"192":{"id":192,"crate_id":0,"name":"find_max","span":{"filename":"src/lib.rs","begin":[468,1],"end":[470,2]},"visibility":"public","docs":"Finds the maximum value in a slice, or None if empty.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["numbers",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"slice":{"primitive":"i32"}}}}]],"output":{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"i32"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"66":{"id":66,"crate_id":0,"name":"x","span":{"filename":"src/lib.rs","begin":[25,5],"end":[25,15]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"i32"}}},"217":{"id":217,"crate_id":0,"name":"rustcalc_Calculator_apply_op","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[337,1],"end":[347,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["op",{"primitive":"i32"}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"91":{"id":91,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":19,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[16],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"242":{"id":242,"crate_id":0,"name":"rustcalc_Calculator_count_up","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[784,1],"end":[815,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["max",{"primitive":"i32"}],["interval_ms",{"primitive":"i32"}],["next_ptr",{"primitive":"i64"}],["error_ptr",{"primitive":"i64"}],["complete_ptr",{"primitive":"i64"}],["cancel_out",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i64"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"267":{"id":267,"crate_id":0,"name":"rustcalc_compute","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1121,1],"end":[1130,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["a",{"primitive":"i32"}],["b",{"primitive":"i32"}],["op",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"141":{"id":141,"crate_id":0,"name":"get_enabled","span":{"filename":"src/lib.rs","begin":[184,5],"end":[186,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"primitive":"bool"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"166":{"id":166,"crate_id":0,"name":"reverse_bytes","span":{"filename":"src/lib.rs","begin":[347,5],"end":[349,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["data",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"slice":{"primitive":"u8"}}}}]],"output":{"resolved_path":{"path":"Vec","id":164,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"u8"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"191":{"id":191,"crate_id":0,"name":"sum_all","span":{"filename":"src/lib.rs","begin":[463,1],"end":[465,2]},"visibility":"public","docs":"Adds all numbers in a slice.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["numbers",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"slice":{"primitive":"i32"}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"65":{"id":65,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":42,"args":null},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"216":{"id":216,"crate_id":0,"name":"rustcalc_Calculator_concat","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[312,1],"end":[334,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["a",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}],["b",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"90":{"id":90,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":15,"args":null},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"241":{"id":241,"crate_id":0,"name":"rustcalc_Calculator_delayed_is_positive","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[757,1],"end":[781,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["delay_ms",{"primitive":"i32"}],["cont_ptr",{"primitive":"i64"}],["exc_ptr",{"primitive":"i64"}],["cancel_out",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i64"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"266":{"id":266,"crate_id":0,"name":"rustcalc_CalcResult_new_Nothing","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1116,1],"end":[1118,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"140":{"id":140,"crate_id":0,"name":"set_scale","span":{"filename":"src/lib.rs","begin":[180,5],"end":[182,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["scale",{"primitive":"f64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"14":{"id":14,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":15,"args":null},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"165":{"id":165,"crate_id":0,"name":"sum_bytes","span":{"filename":"src/lib.rs","begin":[342,5],"end":[345,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["data",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"slice":{"primitive":"u8"}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"39":{"id":39,"crate_id":2,"name":"type_id","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"TypeId","id":40,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"190":{"id":190,"crate_id":0,"name":"greet","span":{"filename":"src/lib.rs","begin":[458,1],"end":[460,2]},"visibility":"public","docs":"Returns a greeting message.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["name",{"resolved_path":{"path":"String","id":46,"args":null}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"64":{"id":64,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[35,37],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"89":{"id":89,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":13,"args":null},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"240":{"id":240,"crate_id":0,"name":"rustcalc_Calculator_delayed_noop","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[730,1],"end":[754,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["delay_ms",{"primitive":"i32"}],["cont_ptr",{"primitive":"i64"}],["exc_ptr",{"primitive":"i64"}],["cancel_out",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i64"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"265":{"id":265,"crate_id":0,"name":"rustcalc_CalcResult_Partial_get_confidence","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1107,1],"end":[1113,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"139":{"id":139,"crate_id":0,"name":"get_scale","span":{"filename":"src/lib.rs","begin":[176,5],"end":[178,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"38":{"id":38,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[35,37],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"189":{"id":189,"crate_id":0,"name":"compute","span":{"filename":"src/lib.rs","begin":[449,1],"end":[455,2]},"visibility":"public","docs":"Computes a binary operation on two integers.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["a",{"primitive":"i32"}],["b",{"primitive":"i32"}],["op",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"resolved_path":{"path":"Operation","id":3,"args":null}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"63":{"id":63,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[29,31],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"214":{"id":214,"crate_id":0,"name":"rustcalc_Calculator_echo","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[289,1],"end":[309,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["text",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"88":{"id":88,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":11,"args":null},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"239":{"id":239,"crate_id":0,"name":"rustcalc_Calculator_fail_after_delay","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[702,1],"end":[727,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["delay_ms",{"primitive":"i32"}],["cont_ptr",{"primitive":"i64"}],["exc_ptr",{"primitive":"i64"}],["cancel_out",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i64"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"264":{"id":264,"crate_id":0,"name":"rustcalc_CalcResult_Partial_get_value","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1098,1],"end":[1104,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"138":{"id":138,"crate_id":0,"name":"set_label","span":{"filename":"src/lib.rs","begin":[172,5],"end":[174,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["label",{"resolved_path":{"path":"String","id":46,"args":null}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"12":{"id":12,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":13,"args":null},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"163":{"id":163,"crate_id":0,"name":"to_bytes","span":{"filename":"src/lib.rs","begin":[338,5],"end":[340,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Vec","id":164,"args":{"angle_bracketed":{"args":[{"type":{"primitive":"u8"}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"37":{"id":37,"crate_id":2,"name":"try_from","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["value",{"generic":"U"}]],"output":{"resolved_path":{"path":"Result","id":32,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}},{"type":{"qualified_path":{"name":"Error","args":null,"self_type":{"generic":"T"},"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}}}}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"188":{"id":188,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":42,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"62":{"id":62,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[27],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"213":{"id":213,"crate_id":0,"name":"rustcalc_Calculator_describe","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[268,1],"end":[286,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"87":{"id":87,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":9,"args":null},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"238":{"id":238,"crate_id":0,"name":"rustcalc_Calculator_delayed_describe","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[674,1],"end":[699,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["delay_ms",{"primitive":"i32"}],["cont_ptr",{"primitive":"i64"}],["exc_ptr",{"primitive":"i64"}],["cancel_out",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i64"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"112":{"id":112,"crate_id":0,"name":null,"span":{"filename":"src/lib.rs","begin":[436,1],"end":[444,2]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Measurable","id":109,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[110,111],"is_negative":false,"is_synthetic":false,"blanket_impl":null}}},"263":{"id":263,"crate_id":0,"name":"rustcalc_CalcResult_new_Partial","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1093,1],"end":[1095,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["value",{"primitive":"i32"}],["confidence",{"primitive":"f64"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"137":{"id":137,"crate_id":0,"name":"get_label","span":{"filename":"src/lib.rs","begin":[168,5],"end":[170,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"162":{"id":162,"crate_id":0,"name":"set_from_named","span":{"filename":"src/lib.rs","begin":[331,5],"end":[334,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["nv",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"resolved_path":{"path":"NamedValue","id":84,"args":null}}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"187":{"id":187,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[35,37],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"61":{"id":61,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"212":{"id":212,"crate_id":0,"name":"rustcalc_Calculator_check_flag","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[255,1],"end":[265,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["flag",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"86":{"id":86,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":7,"args":null},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"237":{"id":237,"crate_id":0,"name":"rustcalc_Calculator_delayed_add","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[647,1],"end":[671,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i32"}],["delay_ms",{"primitive":"i32"}],["cont_ptr",{"primitive":"i64"}],["exc_ptr",{"primitive":"i64"}],["cancel_out",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i64"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"111":{"id":111,"crate_id":0,"name":"unit","span":{"filename":"src/lib.rs","begin":[441,5],"end":[443,6]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"262":{"id":262,"crate_id":0,"name":"rustcalc_CalcResult_Error_get_value","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1074,1],"end":[1090,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"136":{"id":136,"crate_id":0,"name":"concat","span":{"filename":"src/lib.rs","begin":[162,5],"end":[164,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["a",{"resolved_path":{"path":"String","id":46,"args":null}}],["b",{"resolved_path":{"path":"String","id":46,"args":null}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"10":{"id":10,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":11,"args":null},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"161":{"id":161,"crate_id":0,"name":"get_named_value","span":{"filename":"src/lib.rs","begin":[326,5],"end":[329,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"35":{"id":35,"crate_id":2,"name":"Error","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"assoc_type":{"generics":{"params":[],"where_predicates":[]},"bounds":[],"type":{"resolved_path":{"path":"Infallible","id":36,"args":null}}}}},"186":{"id":186,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[29,31],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"60":{"id":60,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":22,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[20],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"211":{"id":211,"crate_id":0,"name":"rustcalc_Calculator_is_positive","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[243,1],"end":[252,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"85":{"id":85,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":5,"args":null},"for":{"resolved_path":{"path":"NamedValue","id":84,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"236":{"id":236,"crate_id":0,"name":"rustcalc_Calculator_get_recent_scores","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[628,1],"end":[644,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"110":{"id":110,"crate_id":0,"name":"measure","span":{"filename":"src/lib.rs","begin":[437,5],"end":[439,6]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"261":{"id":261,"crate_id":0,"name":"rustcalc_CalcResult_new_Error","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1068,1],"end":[1071,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["value",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"135":{"id":135,"crate_id":0,"name":"echo","span":{"filename":"src/lib.rs","begin":[158,5],"end":[160,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["text",{"resolved_path":{"path":"String","id":46,"args":null}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"160":{"id":160,"crate_id":0,"name":"add_point","span":{"filename":"src/lib.rs","begin":[321,5],"end":[324,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["p",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"resolved_path":{"path":"Point","id":68,"args":null}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"185":{"id":185,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[27],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"59":{"id":59,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":19,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[16],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"210":{"id":210,"crate_id":0,"name":"rustcalc_Calculator_add_byte","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[231,1],"end":[240,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i8"}]],"output":{"primitive":"i8"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"84":{"id":84,"crate_id":0,"name":"NamedValue","span":{"filename":"src/lib.rs","begin":[30,1],"end":[33,2]},"visibility":"public","docs":"A named value (data class -- mirrors Kotlin NamedValue).","links":{},"attrs":[],"deprecation":null,"inner":{"struct":{"kind":{"plain":{"fields":[82,83],"has_stripped_fields":false}},"generics":{"params":[],"where_predicates":[]},"impls":[85,86,87,88,89,90,91,92,93,94,95,96,97]}}},"235":{"id":235,"crate_id":0,"name":"rustcalc_Calculator_reverse_bytes","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[610,1],"end":[625,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["data_ptr",{"raw_pointer":{"is_mutable":false,"type":{"primitive":"u8"}}}],["data_len",{"primitive":"i32"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"109":{"id":109,"crate_id":0,"name":"Measurable","span":{"filename":"src/lib.rs","begin":[48,1],"end":[51,2]},"visibility":"public","docs":"Something that can measure a numeric value.","links":{},"attrs":[],"deprecation":null,"inner":{"trait":{"is_auto":false,"is_unsafe":false,"is_dyn_compatible":true,"items":[107,108],"generics":{"params":[],"where_predicates":[]},"bounds":[],"implementations":[112]}}},"260":{"id":260,"crate_id":0,"name":"rustcalc_CalcResult_Value_get_value","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1059,1],"end":[1065,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"134":{"id":134,"crate_id":0,"name":"describe","span":{"filename":"src/lib.rs","begin":[154,5],"end":[156,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"8":{"id":8,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":9,"args":null},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"159":{"id":159,"crate_id":0,"name":"get_point","span":{"filename":"src/lib.rs","begin":[317,5],"end":[319,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Point","id":68,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"33":{"id":33,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[29,31],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"184":{"id":184,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"58":{"id":58,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":15,"args":null},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"209":{"id":209,"crate_id":0,"name":"rustcalc_Calculator_add_short","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[219,1],"end":[228,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i16"}]],"output":{"primitive":"i16"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"83":{"id":83,"crate_id":0,"name":"value","span":{"filename":"src/lib.rs","begin":[32,5],"end":[32,19]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"primitive":"i32"}}},"234":{"id":234,"crate_id":0,"name":"rustcalc_Calculator_sum_bytes","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[597,1],"end":[607,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["data_ptr",{"raw_pointer":{"is_mutable":false,"type":{"primitive":"u8"}}}],["data_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"108":{"id":108,"crate_id":0,"name":"unit","span":{"filename":"src/lib.rs","begin":[50,5],"end":[50,30]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"String","id":46,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":false}}},"259":{"id":259,"crate_id":0,"name":"rustcalc_CalcResult_new_Value","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1054,1],"end":[1056,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["value",{"primitive":"i32"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"133":{"id":133,"crate_id":0,"name":"check_flag","span":{"filename":"src/lib.rs","begin":[148,5],"end":[150,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["flag",{"primitive":"bool"}]],"output":{"primitive":"bool"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"158":{"id":158,"crate_id":0,"name":"run_tick_loop","span":{"filename":"src/lib.rs","begin":[308,5],"end":[313,6]},"visibility":"public","docs":"Runs a tick loop, calling on_tick for each iteration.\n@kne:suspend","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["count",{"primitive":"i32"}],["interval_ms",{"primitive":"i32"}],["on_tick",{"function_pointer":{"sig":{"inputs":[["_",{"primitive":"i32"}]],"output":null,"is_c_variadic":false},"generic_params":[],"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"183":{"id":183,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":22,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[20],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"57":{"id":57,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":13,"args":null},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"208":{"id":208,"crate_id":0,"name":"rustcalc_Calculator_add_float","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[207,1],"end":[216,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"f32"}]],"output":{"primitive":"f32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"82":{"id":82,"crate_id":0,"name":"name","span":{"filename":"src/lib.rs","begin":[31,5],"end":[31,21]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"struct_field":{"resolved_path":{"path":"String","id":46,"args":null}}}},"233":{"id":233,"crate_id":0,"name":"rustcalc_Calculator_to_bytes","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[580,1],"end":[594,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"107":{"id":107,"crate_id":0,"name":"measure","span":{"filename":"src/lib.rs","begin":[49,5],"end":[49,30]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":false}}},"258":{"id":258,"crate_id":0,"name":"rustcalc_CalcResult_tag","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1043,1],"end":[1051,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"132":{"id":132,"crate_id":0,"name":"is_positive","span":{"filename":"src/lib.rs","begin":[144,5],"end":[146,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"primitive":"bool"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"6":{"id":6,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":7,"args":null},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"157":{"id":157,"crate_id":0,"name":"for_each_score","span":{"filename":"src/lib.rs","begin":[300,5],"end":[304,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["count",{"primitive":"i32"}],["callback",{"function_pointer":{"sig":{"inputs":[["_",{"primitive":"i32"}]],"output":null,"is_c_variadic":false},"generic_params":[],"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"31":{"id":31,"crate_id":2,"name":"try_into","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"generic":"Self"}]],"output":{"resolved_path":{"path":"Result","id":32,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}},{"type":{"qualified_path":{"name":"Error","args":null,"self_type":{"generic":"U"},"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}}}}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"182":{"id":182,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Borrow","id":19,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[16],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"56":{"id":56,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":11,"args":null},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"207":{"id":207,"crate_id":0,"name":"rustcalc_Calculator_add_double","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[195,1],"end":[204,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"f64"}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"81":{"id":81,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"outlives":"'static"},{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Any","id":42,"args":null},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[39],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"232":{"id":232,"crate_id":0,"name":"rustcalc_Calculator_set_from_named","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[566,1],"end":[577,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["nv_name",{"raw_pointer":{"is_mutable":false,"type":{"resolved_path":{"path":"std::os::raw::c_char","id":215,"args":null}}}}],["nv_value",{"primitive":"i32"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"106":{"id":106,"crate_id":0,"name":null,"span":{"filename":"src/lib.rs","begin":[427,1],"end":[434,2]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Resettable","id":104,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[105],"is_negative":false,"is_synthetic":false,"blanket_impl":null}}},"257":{"id":257,"crate_id":0,"name":"rustcalc_CalcResult_dispose","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1038,1],"end":[1040,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"131":{"id":131,"crate_id":0,"name":"add_byte","span":{"filename":"src/lib.rs","begin":[140,5],"end":[142,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["value",{"primitive":"i8"}]],"output":{"primitive":"i8"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"156":{"id":156,"crate_id":0,"name":"transform_and_sum","span":{"filename":"src/lib.rs","begin":[296,5],"end":[298,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["values",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"slice":{"primitive":"i32"}}}}],["transform",{"function_pointer":{"sig":{"inputs":[["_",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generic_params":[],"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"181":{"id":181,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"RefUnwindSafe","id":15,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"55":{"id":55,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":9,"args":null},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"206":{"id":206,"crate_id":0,"name":"rustcalc_Calculator_add_long","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[183,1],"end":[192,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i64"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"80":{"id":80,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[35,37],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"231":{"id":231,"crate_id":0,"name":"rustcalc_Calculator_get_named_value","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[548,1],"end":[563,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_name",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_name_len",{"primitive":"i32"}],["out_value",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i32"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"105":{"id":105,"crate_id":0,"name":"reset_to_default","span":{"filename":"src/lib.rs","begin":[428,5],"end":[433,6]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"256":{"id":256,"crate_id":0,"name":"rustcalc_Operation_count","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1033,1],"end":[1035,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"130":{"id":130,"crate_id":0,"name":"add_short","span":{"filename":"src/lib.rs","begin":[136,5],"end":[138,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["value",{"primitive":"i16"}]],"output":{"primitive":"i16"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"4":{"id":4,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":5,"args":null},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"155":{"id":155,"crate_id":0,"name":"last_result","span":{"filename":"src/lib.rs","begin":[284,5],"end":[292,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"29":{"id":29,"crate_id":2,"name":"Error","span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"assoc_type":{"generics":{"params":[],"where_predicates":[]},"bounds":[],"type":{"qualified_path":{"name":"Error","args":null,"self_type":{"generic":"U"},"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}}}}}}},"180":{"id":180,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"UnwindSafe","id":13,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"54":{"id":54,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":7,"args":null},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"205":{"id":205,"crate_id":0,"name":"rustcalc_Calculator_fail_always","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[162,1],"end":[180,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"79":{"id":79,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"TryFrom","id":30,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"TryInto","id":34,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[29,31],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"230":{"id":230,"crate_id":0,"name":"rustcalc_Calculator_add_point","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[535,1],"end":[545,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["p_x",{"primitive":"i32"}],["p_y",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"104":{"id":104,"crate_id":0,"name":"Resettable","span":{"filename":"src/lib.rs","begin":[43,1],"end":[45,2]},"visibility":"public","docs":"Something that can be reset to its initial state.","links":{},"attrs":[],"deprecation":null,"inner":{"trait":{"is_auto":false,"is_unsafe":false,"is_dyn_compatible":true,"items":[103],"generics":{"params":[],"where_predicates":[]},"bounds":[],"implementations":[106]}}},"255":{"id":255,"crate_id":0,"name":"rustcalc_Operation_name","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1014,1],"end":[1030,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["ordinal",{"primitive":"i32"}],["out_buf",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"u8"}}}],["out_buf_len",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"129":{"id":129,"crate_id":0,"name":"add_float","span":{"filename":"src/lib.rs","begin":[132,5],"end":[134,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["value",{"primitive":"f32"}]],"output":{"primitive":"f32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"3":{"id":3,"crate_id":0,"name":"Operation","span":{"filename":"src/lib.rs","begin":[5,1],"end":[9,2]},"visibility":"public","docs":"Arithmetic operations supported by the calculator.","links":{},"attrs":[],"deprecation":null,"inner":{"enum":{"generics":{"params":[],"where_predicates":[]},"has_stripped_variants":false,"variants":[0,1,2],"impls":[4,6,8,10,12,14,17,21,25,28,33,38,41]}}},"154":{"id":154,"crate_id":0,"name":"try_divide","span":{"filename":"src/lib.rs","begin":[270,5],"end":[282,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["divisor",{"primitive":"i32"}]],"output":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"28":{"id":28,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Operation","id":3,"args":null}},"items":[27],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"179":{"id":179,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Unpin","id":11,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"53":{"id":53,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Send","id":5,"args":null},"for":{"resolved_path":{"path":"CalcResult","id":52,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"204":{"id":204,"crate_id":0,"name":"rustcalc_Calculator_divide","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[150,1],"end":[159,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["divisor",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"78":{"id":78,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[27],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"229":{"id":229,"crate_id":0,"name":"rustcalc_Calculator_get_point","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[521,1],"end":[532,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["out_x",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i32"}}}],["out_y",{"raw_pointer":{"is_mutable":true,"type":{"primitive":"i32"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"103":{"id":103,"crate_id":0,"name":"reset_to_default","span":{"filename":"src/lib.rs","begin":[44,5],"end":[44,36]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":false}}},"254":{"id":254,"crate_id":0,"name":"rustcalc_Calculator_set_enabled","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[1001,1],"end":[1011,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i32"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"128":{"id":128,"crate_id":0,"name":"add_double","span":{"filename":"src/lib.rs","begin":[128,5],"end":[130,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["value",{"primitive":"f64"}]],"output":{"primitive":"f64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"2":{"id":2,"crate_id":0,"name":"Multiply","span":{"filename":"src/lib.rs","begin":[8,5],"end":[8,13]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"153":{"id":153,"crate_id":0,"name":"add_point_or_null","span":{"filename":"src/lib.rs","begin":[261,5],"end":[266,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":true,"type":{"generic":"Self"}}}],["p",{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"resolved_path":{"path":"Point","id":68,"args":null}}}}}],"constraints":[]}}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"27":{"id":27,"crate_id":2,"name":"from","span":null,"visibility":"default","docs":"Returns the argument unchanged.","links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["t",{"generic":"T"}]],"output":{"generic":"T"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"178":{"id":178,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Freeze","id":9,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"52":{"id":52,"crate_id":0,"name":"CalcResult","span":{"filename":"src/lib.rs","begin":[12,1],"end":[21,2]},"visibility":"public","docs":"Result of a calculator operation — demonstrates tagged enum (sealed class).","links":{},"attrs":[],"deprecation":null,"inner":{"enum":{"generics":{"params":[],"where_predicates":[]},"has_stripped_variants":false,"variants":[44,47,50,51],"impls":[53,54,55,56,57,58,59,60,61,62,63,64,65]}}},"203":{"id":203,"crate_id":0,"name":"rustcalc_Calculator_reset","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[138,1],"end":[147,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"77":{"id":77,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}},{"name":"U","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"U"},"bounds":[{"trait_bound":{"trait":{"path":"From","id":23,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"generic_params":[],"modifier":"none"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"Into","id":26,"args":{"angle_bracketed":{"args":[{"type":{"generic":"U"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[24],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"228":{"id":228,"crate_id":0,"name":"rustcalc_Calculator_last_result","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[508,1],"end":[518,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"102":{"id":102,"crate_id":0,"name":"Calculator","span":{"filename":"src/lib.rs","begin":[62,1],"end":[69,2]},"visibility":"public","docs":"A stateful calculator that accumulates a value.\n\nMirrors the Kotlin/Native Calculator class:\n- Mutable accumulator with arithmetic operations\n- All primitive type conversions\n- String operations\n- Enum support\n- Nullable returns (via Option)\n- Error propagation (via panic)","links":{},"attrs":[],"deprecation":null,"inner":{"struct":{"kind":{"plain":{"fields":[],"has_stripped_fields":true}},"generics":{"params":[],"where_predicates":[]},"impls":[175,176,177,178,179,180,181,182,183,184,185,186,187,188,101,106,112]}}},"253":{"id":253,"crate_id":0,"name":"rustcalc_Calculator_get_enabled","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[989,1],"end":[998,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"127":{"id":127,"crate_id":0,"name":"add_long","span":{"filename":"src/lib.rs","begin":[124,5],"end":[126,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}],["value",{"primitive":"i64"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"1":{"id":1,"crate_id":0,"name":"Subtract","span":{"filename":"src/lib.rs","begin":[7,5],"end":[7,13]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"152":{"id":152,"crate_id":0,"name":"get_nickname","span":{"filename":"src/lib.rs","begin":[257,5],"end":[259,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"resolved_path":{"path":"Option","id":145,"args":{"angle_bracketed":{"args":[{"type":{"resolved_path":{"path":"String","id":46,"args":null}}}],"constraints":[]}}}},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}},"177":{"id":177,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Sync","id":7,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[],"is_negative":false,"is_synthetic":true,"blanket_impl":null}}},"51":{"id":51,"crate_id":0,"name":"Nothing","span":{"filename":"src/lib.rs","begin":[20,5],"end":[20,12]},"visibility":"default","docs":"No result available.","links":{},"attrs":[],"deprecation":null,"inner":{"variant":{"kind":"plain","discriminant":null}}},"202":{"id":202,"crate_id":0,"name":"rustcalc_Calculator_multiply","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[126,1],"end":[135,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"i32"}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"76":{"id":76,"crate_id":0,"name":null,"span":null,"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[{"name":"T","kind":{"type":{"bounds":[],"default":null,"is_synthetic":false}}}],"where_predicates":[{"bound_predicate":{"type":{"generic":"T"},"bounds":[{"trait_bound":{"trait":{"path":"Sized","id":18,"args":null},"generic_params":[],"modifier":"maybe"}}],"generic_params":[]}}]},"provided_trait_methods":[],"trait":{"path":"BorrowMut","id":22,"args":{"angle_bracketed":{"args":[{"type":{"generic":"T"}}],"constraints":[]}}},"for":{"resolved_path":{"path":"Point","id":68,"args":null}},"items":[20],"is_negative":false,"is_synthetic":false,"blanket_impl":{"generic":"T"}}}},"227":{"id":227,"crate_id":0,"name":"rustcalc_Calculator_try_divide","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[495,1],"end":[505,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["divisor",{"primitive":"i32"}]],"output":{"primitive":"i64"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"101":{"id":101,"crate_id":0,"name":null,"span":{"filename":"src/lib.rs","begin":[421,1],"end":[425,2]},"visibility":"default","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"impl":{"is_unsafe":false,"generics":{"params":[],"where_predicates":[]},"provided_trait_methods":[],"trait":{"path":"Describable","id":99,"args":null},"for":{"resolved_path":{"path":"Calculator","id":102,"args":null}},"items":[100],"is_negative":false,"is_synthetic":false,"blanket_impl":null}}},"252":{"id":252,"crate_id":0,"name":"rustcalc_Calculator_set_scale","span":{"filename":"/home/elie-gambache/IdeaProjects/KotlinNativeExport/examples/rust-calculator/rust/target/debug/build/calculator-bf4d85c1cc6c9ad0/out/kne_bridges.rs","begin":[977,1],"end":[986,2]},"visibility":"public","docs":null,"links":{},"attrs":["no_mangle"],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["handle",{"primitive":"i64"}],["value",{"primitive":"f64"}]],"output":null,"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":{"C":{"unwind":false}}},"has_body":true}}},"126":{"id":126,"crate_id":0,"name":"get_current","span":{"filename":"src/lib.rs","begin":[118,5],"end":[120,6]},"visibility":"public","docs":null,"links":{},"attrs":[],"deprecation":null,"inner":{"function":{"sig":{"inputs":[["self",{"borrowed_ref":{"lifetime":null,"is_mutable":false,"type":{"generic":"Self"}}}]],"output":{"primitive":"i32"},"is_c_variadic":false},"generics":{"params":[],"where_predicates":[]},"header":{"is_const":false,"is_unsafe":false,"is_async":false,"abi":"Rust"},"has_body":true}}}},"paths":{"0":{"crate_id":0,"path":["rustcalc","Operation","Add"],"kind":"variant"},"1762":{"crate_id":10,"path":["hashbrown","control","bitmask","BitMaskIter"],"kind":"struct"},"2089":{"crate_id":17,"path":["object","read","any","File"],"kind":"enum"},"327":{"crate_id":1,"path":["std","env","VarError"],"kind":"enum"},"1208":{"crate_id":2,"path":["core","mem","maybe_uninit","Guard"],"kind":"struct"},"1535":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_ff_upload"],"kind":"struct"},"654":{"crate_id":2,"path":["core","ops","arith","Sub"],"kind":"trait"},"2416":{"crate_id":17,"path":["object","pe","ImageRelocation"],"kind":"struct"},"981":{"crate_id":2,"path":["core","hash","sip","Sip24Rounds"],"kind":"struct"},"1862":{"crate_id":16,"path":["gimli","common","DebugFrameOffset"],"kind":"struct"},"2189":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingVersionIterator"],"kind":"enum"},"427":{"crate_id":1,"path":["std","sync","mpmc","array","ArrayToken"],"kind":"struct"},"1308":{"crate_id":3,"path":["alloc","collections","binary_heap","drop","DropGuard"],"kind":"struct"},"754":{"crate_id":1,"path":["std","os","linux","process","CommandExt"],"kind":"trait"},"1635":{"crate_id":5,"path":["libc","unix","linux_like","linux","pidfd_info"],"kind":"struct"},"2516":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","Two"],"kind":"struct"},"200":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add"],"kind":"function"},"1081":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1962":{"crate_id":16,"path":["gimli","read","abbrev","Abbreviations"],"kind":"struct"},"1408":{"crate_id":3,"path":["alloc","collections","btree","set","IntersectionInner"],"kind":"enum"},"527":{"crate_id":2,"path":["core","default","Default"],"kind":"trait"},"2289":{"crate_id":17,"path":["object","archive","AixFileHeader"],"kind":"struct"},"854":{"crate_id":2,"path":["core","fmt","FormattingOptions"],"kind":"struct"},"1735":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","clone_args"],"kind":"struct"},"2616":{"crate_id":17,"path":["object"],"kind":"module"},"300":{"crate_id":1,"path":["std","collections","hash","map","OccupiedEntry"],"kind":"struct"},"1181":{"crate_id":2,"path":["core","task","wake","Context"],"kind":"struct"},"2062":{"crate_id":17,"path":["object","common","ComdatKind"],"kind":"enum"},"1508":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket2_hdr"],"kind":"struct"},"627":{"crate_id":2,"path":["core","ops","try_trait","Residual"],"kind":"trait"},"2389":{"crate_id":17,"path":["object","pe","ImageDosHeader"],"kind":"struct"},"954":{"crate_id":2,"path":["core","iter","sources","from_coroutine","FromCoroutine"],"kind":"struct"},"1835":{"crate_id":16,"path":["gimli","common","Format"],"kind":"enum"},"2162":{"crate_id":17,"path":["object","read","elf","note","NoteIterator"],"kind":"struct"},"400":{"crate_id":1,"path":["std","path","Prefix"],"kind":"enum"},"1281":{"crate_id":2,"path":["core","core_simd","swizzle","extract","Extract"],"kind":"struct"},"1608":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_statistics"],"kind":"struct"},"727":{"crate_id":2,"path":["core","net","socket_addr","SocketAddrV6"],"kind":"struct"},"2489":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingVersion"],"kind":"enum"},"1054":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1935":{"crate_id":16,"path":["gimli","read","cfi","RegisterRuleMap"],"kind":"struct"},"2262":{"crate_id":17,"path":["object","read","xcoff","comdat","XcoffComdatSectionIterator"],"kind":"struct"},"500":{"crate_id":1,"path":["std","sys","fs","unix","DirBuilder"],"kind":"struct"},"1381":{"crate_id":3,"path":["alloc","ffi","c_str","FromBytesWithNulErrorKind"],"kind":"enum"},"1708":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_data"],"kind":"union"},"827":{"crate_id":2,"path":["core","ops","range","RangeInclusive"],"kind":"struct"},"2589":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt","st_mtime"],"kind":"function"},"273":{"crate_id":2,"path":["core","cell","RefCell"],"kind":"struct"},"1154":{"crate_id":2,"path":["core","pin","unsafe_pinned","UnsafePinned"],"kind":"struct"},"2035":{"crate_id":16,"path":["gimli","read","str","DebugStrOffsets"],"kind":"struct"},"2362":{"crate_id":17,"path":["object","macho","DylibReference"],"kind":"struct"},"600":{"crate_id":2,"path":["core","ops","function","FnMut"],"kind":"trait"},"1481":{"crate_id":5,"path":["libc","unix","linux_like","arpreq_old"],"kind":"struct"},"927":{"crate_id":2,"path":["core","core_arch","simd","u32x32"],"kind":"struct"},"46":{"crate_id":3,"path":["alloc","string","String"],"kind":"struct"},"1808":{"crate_id":10,"path":["hashbrown","rustc_entry","RustcEntry"],"kind":"enum"},"373":{"crate_id":1,"path":["std","io","IoSliceMut"],"kind":"struct"},"1254":{"crate_id":2,"path":["core","ops","range","OneSidedRangeBound"],"kind":"enum"},"2135":{"crate_id":17,"path":["object","read","coff","comdat","CoffComdat"],"kind":"struct"},"2462":{"crate_id":17,"path":["object","pe","NonPagedDebugInfo"],"kind":"struct"},"700":{"crate_id":2,"path":["core","slice","GetDisjointMutError"],"kind":"enum"},"1581":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_initmsg"],"kind":"struct"},"1027":{"crate_id":2,"path":["core","str","IsNotEmpty"],"kind":"struct"},"1908":{"crate_id":16,"path":["gimli","endianity","RunTimeEndian"],"kind":"enum"},"473":{"crate_id":1,"path":["std","sync","poison","mutex","MutexGuard"],"kind":"struct"},"1354":{"crate_id":3,"path":["alloc","collections","binary_heap","IntoIterSorted"],"kind":"struct"},"2235":{"crate_id":17,"path":["object","read","pe","import","ImportDescriptorIterator"],"kind":"struct"},"1681":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","mallinfo"],"kind":"struct"},"800":{"crate_id":2,"path":["core","num","niche_types","U32NotAllOnes"],"kind":"struct"},"2562":{"crate_id":3,"path":["alloc","alloc","alloc"],"kind":"function"},"1127":{"crate_id":2,"path":["core","core_arch","x86","__m256h"],"kind":"struct"},"246":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_reset_to_default"],"kind":"function"},"2008":{"crate_id":16,"path":["gimli","read","op","DieReference"],"kind":"enum"},"2335":{"crate_id":17,"path":["object","macho","FatArch64"],"kind":"struct"},"573":{"crate_id":1,"path":["std","sys","pal","unix","kernel_copy","FdHandle"],"kind":"enum"},"1454":{"crate_id":5,"path":["libc","unix","pollfd"],"kind":"struct"},"1781":{"crate_id":10,"path":["hashbrown","table","Iter"],"kind":"struct"},"19":{"crate_id":2,"path":["core","borrow","Borrow"],"kind":"trait"},"900":{"crate_id":2,"path":["core","core_arch","simd","u8x32"],"kind":"struct"},"1227":{"crate_id":2,"path":["core","marker","Unsize"],"kind":"trait"},"346":{"crate_id":1,"path":["std","fs","DirEntry"],"kind":"struct"},"2108":{"crate_id":17,"path":["object","read","any","DynamicRelocationIteratorInternal"],"kind":"enum"},"2435":{"crate_id":17,"path":["object","pe","ImageLoadConfigCodeIntegrity"],"kind":"struct"},"673":{"crate_id":2,"path":["core","slice","iter","ChunksExact"],"kind":"struct"},"1554":{"crate_id":5,"path":["libc","unix","linux_like","linux","genlmsghdr"],"kind":"struct"},"1881":{"crate_id":16,"path":["gimli","constants","DwTag"],"kind":"struct"},"1000":{"crate_id":2,"path":["core","str","iter","Matches"],"kind":"struct"},"446":{"crate_id":1,"path":["std","sync","mpsc","TryRecvError"],"kind":"enum"},"1327":{"crate_id":3,"path":["alloc","rc","Weak"],"kind":"struct"},"2208":{"crate_id":17,"path":["object","read","macho","load_command","LoadCommandData"],"kind":"struct"},"2535":{"crate_id":18,"path":["memchr","memmem","searcher","TwoWayWithPrefilter"],"kind":"struct"},"773":{"crate_id":2,"path":["core","num","dec2flt","ParseFloatError"],"kind":"struct"},"1654":{"crate_id":5,"path":["libc","unix","linux_like","linux","hwtstamp_config"],"kind":"struct"},"1100":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"219":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_describe_or_null"],"kind":"function"},"1981":{"crate_id":16,"path":["gimli","read","line","LineRow"],"kind":"struct"},"546":{"crate_id":1,"path":["std","thread","spawn_unchecked_","MaybeDangling"],"kind":"struct"},"1427":{"crate_id":4,"path":["compiler_builtins","float","cmp","Result"],"kind":"enum"},"2308":{"crate_id":17,"path":["object","elf","ProgramHeader32"],"kind":"struct"},"2635":{"crate_id":1,"path":["std","i64"],"kind":"primitive"},"873":{"crate_id":2,"path":["core","core_arch","simd","i16x2"],"kind":"struct"},"1754":{"crate_id":8,"path":["miniz_oxide","DataFormat"],"kind":"enum"},"1200":{"crate_id":2,"path":["core","ops","bit","Not"],"kind":"trait"},"319":{"crate_id":1,"path":["std","collections","hash","set","Difference"],"kind":"struct"},"2081":{"crate_id":17,"path":["object","read","read_cache","ReadCache"],"kind":"struct"},"646":{"crate_id":1,"path":["std","os","unix","net","ancillary","ScmCredentials"],"kind":"struct"},"1527":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_trigger"],"kind":"struct"},"2408":{"crate_id":17,"path":["object","pe","ImageSymbolEx"],"kind":"struct"},"1854":{"crate_id":16,"path":["gimli","common","RangeListsOffset"],"kind":"struct"},"973":{"crate_id":2,"path":["core","fmt","rt","ArgumentType"],"kind":"enum"},"1300":{"crate_id":2,"path":["core","core_simd","simd","cmp","ord","SimdOrd"],"kind":"trait"},"419":{"crate_id":1,"path":["std","process","CommandArgs"],"kind":"struct"},"2181":{"crate_id":17,"path":["object","read","elf","attributes","AttributeReader"],"kind":"struct"},"2508":{"crate_id":18,"path":["memchr","arch","all","twoway","ApproximateByteSet"],"kind":"struct"},"746":{"crate_id":1,"path":["std","os","fd","raw","FromRawFd"],"kind":"trait"},"1627":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_statistics"],"kind":"struct"},"1954":{"crate_id":16,"path":["gimli","read","endian_slice","DebugBytes"],"kind":"struct"},"192":{"crate_id":0,"path":["rustcalc","find_max"],"kind":"function"},"1073":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1400":{"crate_id":3,"path":["alloc","collections","btree","map","entry","OccupiedError"],"kind":"struct"},"519":{"crate_id":1,"path":["std","panicking","panic_count","MustAbort"],"kind":"enum"},"2281":{"crate_id":17,"path":["object","read","Relocation"],"kind":"struct"},"2608":{"crate_id":9,"path":["adler2"],"kind":"module"},"846":{"crate_id":2,"path":["core","range","RangeInclusive"],"kind":"struct"},"1727":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","_libc_xmmreg"],"kind":"struct"},"2054":{"crate_id":16,"path":["gimli","read","value","Value"],"kind":"enum"},"292":{"crate_id":1,"path":["std","backtrace","BacktraceFrame"],"kind":"struct"},"1173":{"crate_id":2,"path":["core","cell","RefMut"],"kind":"struct"},"619":{"crate_id":2,"path":["core","iter","adapters","scan","Scan"],"kind":"struct"},"1500":{"crate_id":5,"path":["libc","unix","linux_like","linux","itimerspec"],"kind":"struct"},"2381":{"crate_id":17,"path":["object","macho","SourceVersionCommand"],"kind":"struct"},"946":{"crate_id":2,"path":["core","char","ToUppercase"],"kind":"struct"},"1827":{"crate_id":13,"path":["rustc_demangle","TryDemangleError"],"kind":"struct"},"2154":{"crate_id":17,"path":["object","read","elf","relocation","RelrIterator"],"kind":"struct"},"392":{"crate_id":1,"path":["std","os","unix","net","listener","Incoming"],"kind":"struct"},"1273":{"crate_id":2,"path":["core","core_simd","swizzle","rotate_elements_right","Rotate"],"kind":"struct"},"719":{"crate_id":1,"path":["std","sys","stdio","unix","Stderr"],"kind":"struct"},"1600":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_freq"],"kind":"struct"},"2481":{"crate_id":17,"path":["object","xcoff","ExpAux"],"kind":"struct"},"1046":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1927":{"crate_id":16,"path":["gimli","read","cfi","CieOrFde"],"kind":"enum"},"1373":{"crate_id":3,"path":["alloc","collections","btree","set","Cursor"],"kind":"struct"},"492":{"crate_id":1,"path":["std","sys","env","common","EnvStrDebug"],"kind":"struct"},"2254":{"crate_id":17,"path":["object","read","xcoff","symbol","SymbolTable"],"kind":"struct"},"819":{"crate_id":2,"path":["core","marker","variance","PhantomContravariant"],"kind":"struct"},"1700":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fanotify_event_info_pidfd"],"kind":"struct"},"2581":{"crate_id":1,"path":["std","fs","read"],"kind":"function"},"2027":{"crate_id":16,"path":["gimli","read","rnglists","RangeLists"],"kind":"struct"},"265":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_Partial_get_confidence"],"kind":"function"},"1146":{"crate_id":2,"path":["core","io","borrowed_buf","BorrowedBuf"],"kind":"struct"},"1473":{"crate_id":5,"path":["libc","unix","linux_like","tm"],"kind":"struct"},"592":{"crate_id":3,"path":["alloc","ffi","c_str","NulError"],"kind":"struct"},"2354":{"crate_id":17,"path":["object","macho","ThreadCommand"],"kind":"struct"},"919":{"crate_id":2,"path":["core","core_arch","simd","u32x16"],"kind":"struct"},"1800":{"crate_id":10,"path":["hashbrown","table","Drain"],"kind":"struct"},"2127":{"crate_id":17,"path":["object","read","coff","section","CoffSection"],"kind":"struct"},"365":{"crate_id":1,"path":["std","io","stdio","StdinLock"],"kind":"struct"},"1246":{"crate_id":2,"path":["core","ops","try_trait","NeverShortCircuit"],"kind":"struct"},"1573":{"crate_id":5,"path":["libc","unix","linux_like","linux","in6_ifreq"],"kind":"struct"},"692":{"crate_id":2,"path":["core","slice","SlicePattern"],"kind":"trait"},"2454":{"crate_id":17,"path":["object","pe","ImageEnclaveConfig64"],"kind":"struct"},"1019":{"crate_id":2,"path":["core","str","pattern","EmptyNeedle"],"kind":"struct"},"1900":{"crate_id":16,"path":["gimli","constants","DwLns"],"kind":"struct"},"2227":{"crate_id":17,"path":["object","read","pe","section","PeSectionIterator"],"kind":"struct"},"465":{"crate_id":1,"path":["std","sync","nonpoison","mutex","MappedMutexGuard"],"kind":"struct"},"1346":{"crate_id":3,"path":["alloc","alloc","Global"],"kind":"struct"},"792":{"crate_id":2,"path":["core","num","niche_types","NonZeroI16Inner"],"kind":"struct"},"1673":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_xsk_tx_metadata_union"],"kind":"union"},"2554":{"crate_id":1,"path":["std","fs","symlink_metadata"],"kind":"function"},"238":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_delayed_describe"],"kind":"function"},"1119":{"crate_id":2,"path":["core","core_arch","x86","__m128bh"],"kind":"struct"},"2000":{"crate_id":16,"path":["gimli","read","lookup","PubStuffHeader"],"kind":"struct"},"2327":{"crate_id":17,"path":["object","macho","DyldCacheSlideInfo3"],"kind":"struct"},"565":{"crate_id":1,"path":["std","sys","thread","unix","Thread"],"kind":"struct"},"1446":{"crate_id":5,"path":["libc","unix","group"],"kind":"struct"},"892":{"crate_id":2,"path":["core","core_arch","simd","f16x4"],"kind":"struct"},"11":{"crate_id":2,"path":["core","marker","Unpin"],"kind":"trait"},"1773":{"crate_id":10,"path":["hashbrown","map","Values"],"kind":"struct"},"338":{"crate_id":1,"path":["std","fs","OpenOptions"],"kind":"struct"},"1219":{"crate_id":2,"path":["core","intrinsics","fallback","CarryingMulAdd"],"kind":"trait"},"2100":{"crate_id":17,"path":["object","read","any","ComdatSectionIterator"],"kind":"struct"},"1546":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Shdr"],"kind":"struct"},"665":{"crate_id":2,"path":["core","slice","ascii","EscapeAscii"],"kind":"struct"},"2427":{"crate_id":17,"path":["object","pe","ImageBoundImportDescriptor"],"kind":"struct"},"992":{"crate_id":2,"path":["core","str","iter","RSplitTerminator"],"kind":"struct"},"1873":{"crate_id":16,"path":["gimli","arch","X86"],"kind":"struct"},"2200":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldRelocation"],"kind":"struct"},"438":{"crate_id":1,"path":["std","sync","mpmc","Iter"],"kind":"struct"},"1319":{"crate_id":3,"path":["alloc","collections","vec_deque","into_iter","try_rfold","Guard"],"kind":"struct"},"1646":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_setup"],"kind":"struct"},"765":{"crate_id":1,"path":["std","os","linux","process","ChildExt"],"kind":"trait"},"2527":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","packedpair","Finder"],"kind":"struct"},"1092":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"211":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_is_positive"],"kind":"function"},"1973":{"crate_id":16,"path":["gimli","read","index","UnitIndex"],"kind":"struct"},"2300":{"crate_id":17,"path":["object","elf","Syminfo32"],"kind":"struct"},"538":{"crate_id":1,"path":["std","sys","fs","unix","FileAttr"],"kind":"struct"},"1419":{"crate_id":3,"path":["alloc","collections","btree","dedup_sorted_iter","DedupSortedIter"],"kind":"struct"},"1746":{"crate_id":8,"path":["miniz_oxide","inflate","core","DecompressorOxide"],"kind":"struct"},"865":{"crate_id":2,"path":["core","task","wake","RawWaker"],"kind":"struct"},"2627":{"crate_id":1,"path":["std","tuple"],"kind":"primitive"},"311":{"crate_id":1,"path":["std","collections","hash","set","Iter"],"kind":"struct"},"1192":{"crate_id":2,"path":["core","ops","bit","BitOrAssign"],"kind":"trait"},"2073":{"crate_id":17,"path":["object","endian","LittleEndian"],"kind":"struct"},"2400":{"crate_id":17,"path":["object","pe","ImageRomHeaders"],"kind":"struct"},"638":{"crate_id":2,"path":["core","iter","traits","accum","Sum"],"kind":"trait"},"1519":{"crate_id":5,"path":["libc","unix","linux_like","linux","msginfo"],"kind":"struct"},"1846":{"crate_id":16,"path":["gimli","common","DebugLineOffset"],"kind":"struct"},"84":{"crate_id":0,"path":["rustcalc","NamedValue"],"kind":"struct"},"965":{"crate_id":2,"path":["core","option","Iter"],"kind":"struct"},"411":{"crate_id":1,"path":["std","path","NormalizeError"],"kind":"struct"},"1292":{"crate_id":2,"path":["core","core_simd","simd","num","float","SimdFloat"],"kind":"trait"},"2173":{"crate_id":17,"path":["object","read","elf","version","VerneedIterator"],"kind":"struct"},"2500":{"crate_id":18,"path":["memchr","arch","all","rabinkarp","FinderRev"],"kind":"struct"},"738":{"crate_id":1,"path":["std","os","unix","fs","FileTypeExt"],"kind":"trait"},"1619":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_clock_caps"],"kind":"struct"},"1065":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1946":{"crate_id":16,"path":["gimli","read","dwarf","Dwarf"],"kind":"struct"},"511":{"crate_id":1,"path":["std","sys","process","unix","common","Command"],"kind":"struct"},"1392":{"crate_id":3,"path":["alloc","collections","btree","set","IntoIter"],"kind":"struct"},"2273":{"crate_id":17,"path":["object","read","SymbolMapName"],"kind":"struct"},"1719":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","siginfo_t"],"kind":"struct"},"838":{"crate_id":2,"path":["core","ffi","c_str","FromBytesWithNulError"],"kind":"enum"},"2600":{"crate_id":1,"path":["std"],"kind":"module"},"1165":{"crate_id":2,"path":["core","sync","atomic","AtomicUsize"],"kind":"struct"},"284":{"crate_id":1,"path":["std","thread","local","AccessError"],"kind":"struct"},"2046":{"crate_id":16,"path":["gimli","read","unit","EntriesRaw"],"kind":"struct"},"2373":{"crate_id":17,"path":["object","macho","BuildVersionCommand"],"kind":"struct"},"611":{"crate_id":2,"path":["core","iter","adapters","filter_map","FilterMap"],"kind":"struct"},"1492":{"crate_id":5,"path":["libc","unix","linux_like","utsname"],"kind":"struct"},"1819":{"crate_id":12,"path":["std_detect","detect","arch","x86","Feature"],"kind":"enum"},"938":{"crate_id":2,"path":["core","ascii","EscapeDefault"],"kind":"struct"},"1265":{"crate_id":2,"path":["core","sync","atomic","AtomicPrimitive"],"kind":"trait"},"384":{"crate_id":1,"path":["std","net","tcp","TcpListener"],"kind":"struct"},"2146":{"crate_id":17,"path":["object","read","elf","section","ElfSection"],"kind":"struct"},"2473":{"crate_id":17,"path":["object","xcoff","Symbol32"],"kind":"struct"},"711":{"crate_id":2,"path":["core","io","borrowed_buf","BorrowedCursor"],"kind":"struct"},"1592":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aes_ccm_128"],"kind":"struct"},"1919":{"crate_id":16,"path":["gimli","read","cfi","ParsedEhFrameHdr"],"kind":"struct"},"1038":{"crate_id":2,"path":["core","escape","AlwaysEscaped"],"kind":"struct"},"484":{"crate_id":1,"path":["std","time","Instant"],"kind":"struct"},"1365":{"crate_id":3,"path":["alloc","collections","btree","navigate","LazyLeafRange"],"kind":"struct"},"2246":{"crate_id":17,"path":["object","read","pe","resource","ResourceName"],"kind":"struct"},"2573":{"crate_id":2,"path":["core","mem","drop"],"kind":"function"},"811":{"crate_id":2,"path":["core","ptr","alignment","AlignmentEnum"],"kind":"enum"},"1692":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ptrace_peeksiginfo_args"],"kind":"struct"},"2019":{"crate_id":16,"path":["gimli","read","pubnames","PubNamesEntry"],"kind":"struct"},"257":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_dispose"],"kind":"function"},"1138":{"crate_id":2,"path":["core","asserting","Capture"],"kind":"struct"},"584":{"crate_id":1,"path":["std","panicking","resume_unwind","RewrapBox"],"kind":"struct"},"1465":{"crate_id":5,"path":["libc","unix","linux_like","ip_mreqn"],"kind":"struct"},"2346":{"crate_id":17,"path":["object","macho","Dylib"],"kind":"struct"},"30":{"crate_id":2,"path":["core","convert","TryFrom"],"kind":"trait"},"911":{"crate_id":2,"path":["core","core_arch","simd","m8x32"],"kind":"struct"},"1792":{"crate_id":10,"path":["hashbrown","map","ValuesMut"],"kind":"struct"},"1238":{"crate_id":2,"path":["core","marker","variance","Variance"],"kind":"trait"},"357":{"crate_id":1,"path":["std","io","error","Error"],"kind":"struct"},"2119":{"crate_id":17,"path":["object","read","archive","SymbolIteratorInternal"],"kind":"enum"},"684":{"crate_id":2,"path":["core","slice","iter","SplitInclusive"],"kind":"struct"},"1565":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif_sizes"],"kind":"struct"},"2446":{"crate_id":17,"path":["object","pe","ImageHotPatchBase"],"kind":"struct"},"1892":{"crate_id":16,"path":["gimli","constants","DwAddr"],"kind":"struct"},"1011":{"crate_id":2,"path":["core","str","pattern","CharSearcher"],"kind":"struct"},"1338":{"crate_id":3,"path":["alloc","vec","drain","Drain"],"kind":"struct"},"457":{"crate_id":1,"path":["std","sync","lazy_lock","LazyLock"],"kind":"struct"},"2219":{"crate_id":17,"path":["object","read","macho","symbol","MachOSymbol"],"kind":"struct"},"784":{"crate_id":2,"path":["core","num","wrapping","Wrapping"],"kind":"struct"},"1665":{"crate_id":5,"path":["libc","unix","linux_like","linux","iwreq_data"],"kind":"union"},"2546":{"crate_id":18,"path":["memchr","memmem","FinderBuilder"],"kind":"struct"},"1992":{"crate_id":16,"path":["gimli","read","loclists","LocationLists"],"kind":"struct"},"230":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_point"],"kind":"function"},"1111":{"crate_id":2,"path":["core","core_arch","x86","__m256d"],"kind":"struct"},"1438":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","can_frame"],"kind":"struct"},"557":{"crate_id":1,"path":["std","sys","pal","unix","stack_overflow","thread_info","UnlockOnDrop"],"kind":"struct"},"2319":{"crate_id":17,"path":["object","elf","HashHeader"],"kind":"struct"},"3":{"crate_id":0,"path":["rustcalc","Operation"],"kind":"enum"},"884":{"crate_id":2,"path":["core","core_arch","simd","u8x16"],"kind":"struct"},"1765":{"crate_id":10,"path":["hashbrown","raw","RawTable"],"kind":"struct"},"2092":{"crate_id":17,"path":["object","read","any","SegmentInternal"],"kind":"enum"},"330":{"crate_id":1,"path":["std","env","Args"],"kind":"struct"},"1211":{"crate_id":2,"path":["core","array","Guard"],"kind":"struct"},"1538":{"crate_id":5,"path":["libc","unix","linux_like","linux","dl_phdr_info"],"kind":"struct"},"657":{"crate_id":2,"path":["core","fmt","Write"],"kind":"trait"},"2419":{"crate_id":17,"path":["object","pe","ImageArchiveMemberHeader"],"kind":"struct"},"984":{"crate_id":2,"path":["core","str","iter","Chars"],"kind":"struct"},"1865":{"crate_id":16,"path":["gimli","common","SectionId"],"kind":"enum"},"2192":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIterator"],"kind":"struct"},"430":{"crate_id":1,"path":["std","sync","mpmc","error","SendTimeoutError"],"kind":"enum"},"1311":{"crate_id":3,"path":["alloc","collections","btree","map","drop","DropGuard"],"kind":"struct"},"757":{"crate_id":1,"path":["std","process","Termination"],"kind":"trait"},"1638":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_thrspy"],"kind":"struct"},"2519":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","ThreeIter"],"kind":"struct"},"203":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_reset"],"kind":"function"},"1084":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1965":{"crate_id":16,"path":["gimli","read","abbrev","AttributeSpecification"],"kind":"struct"},"1411":{"crate_id":3,"path":["alloc","collections","btree","set","CursorMutKey"],"kind":"struct"},"530":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","lru","Lru"],"kind":"struct"},"2292":{"crate_id":17,"path":["object","elf","FileHeader64"],"kind":"struct"},"857":{"crate_id":2,"path":["core","str","pattern","Utf8Pattern"],"kind":"enum"},"1738":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","max_align_t"],"kind":"struct"},"2619":{"crate_id":1,"path":["std","bool"],"kind":"primitive"},"303":{"crate_id":1,"path":["std","collections","hash","map","IterMut"],"kind":"struct"},"1184":{"crate_id":2,"path":["core","fmt","builders","PadAdapterState"],"kind":"struct"},"2065":{"crate_id":17,"path":["object","common","RelocationKind"],"kind":"enum"},"1511":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_rollover_stats"],"kind":"struct"},"630":{"crate_id":2,"path":["core","iter","traits","exact_size","ExactSizeIterator"],"kind":"trait"},"2392":{"crate_id":17,"path":["object","pe","MaskedRichHeaderEntry"],"kind":"struct"},"957":{"crate_id":2,"path":["core","iter","sources","once_with","OnceWith"],"kind":"struct"},"1838":{"crate_id":16,"path":["gimli","common","LineEncoding"],"kind":"struct"},"2165":{"crate_id":17,"path":["object","read","elf","note","GnuProperty"],"kind":"struct"},"403":{"crate_id":1,"path":["std","path","Component"],"kind":"enum"},"1284":{"crate_id":2,"path":["core","core_simd","cast","sealed","Sealed"],"kind":"trait"},"1611":{"crate_id":5,"path":["libc","unix","linux_like","linux","epoll_params"],"kind":"struct"},"730":{"crate_id":2,"path":["core","net","ip_addr","Ipv6Addr"],"kind":"struct"},"2492":{"crate_id":18,"path":["memchr","arch","all","memchr","OneIter"],"kind":"struct"},"1057":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1938":{"crate_id":16,"path":["gimli","read","cfi","CfaRule"],"kind":"enum"},"2265":{"crate_id":17,"path":["object","read","traits","NoDynamicRelocationIterator"],"kind":"struct"},"503":{"crate_id":1,"path":["std","sys","net","connection","socket","TcpStream"],"kind":"struct"},"1384":{"crate_id":3,"path":["alloc","string","FromUtf8Error"],"kind":"struct"},"1711":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","sysinfo"],"kind":"struct"},"830":{"crate_id":2,"path":["core","bstr","ByteStr"],"kind":"struct"},"2592":{"crate_id":2,"path":["core","intrinsics","unchecked_funnel_shl"],"kind":"function"},"276":{"crate_id":1,"path":["std","thread","local","LocalKey"],"kind":"struct"},"1157":{"crate_id":2,"path":["core","sync","atomic","AtomicU8"],"kind":"struct"},"2038":{"crate_id":16,"path":["gimli","read","unit","DebugInfo"],"kind":"struct"},"2365":{"crate_id":17,"path":["object","macho","PrebindCksumCommand"],"kind":"struct"},"603":{"crate_id":2,"path":["core","num","nonzero","NonZero"],"kind":"struct"},"1484":{"crate_id":5,"path":["libc","unix","linux_like","file_clone_range"],"kind":"struct"},"930":{"crate_id":2,"path":["core","core_simd","masks","Mask"],"kind":"struct"},"1811":{"crate_id":10,"path":["hashbrown","set","Entry"],"kind":"enum"},"376":{"crate_id":1,"path":["std","io","Chain"],"kind":"struct"},"1257":{"crate_id":2,"path":["core","slice","index","private_slice_index","Sealed"],"kind":"trait"},"2138":{"crate_id":17,"path":["object","read","coff","import","ImportName"],"kind":"enum"},"1584":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_rcvinfo"],"kind":"struct"},"703":{"crate_id":3,"path":["alloc","slice","Concat"],"kind":"trait"},"2465":{"crate_id":17,"path":["object","pe","ImageCor20Header"],"kind":"struct"},"1030":{"crate_id":2,"path":["core","wtf8","Wtf8CodePoints"],"kind":"struct"},"1911":{"crate_id":16,"path":["gimli","read","util","sealed","CapacityFull"],"kind":"struct"},"476":{"crate_id":1,"path":["std","sync","poison","rwlock","RwLockReadGuard"],"kind":"struct"},"1357":{"crate_id":3,"path":["alloc","collections","btree","map","Values"],"kind":"struct"},"2238":{"crate_id":17,"path":["object","read","pe","import","DelayLoadImportTable"],"kind":"struct"},"1684":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","nl_mmap_req"],"kind":"struct"},"803":{"crate_id":2,"path":["core","num","niche_types","I64NotAllOnes"],"kind":"struct"},"2565":{"crate_id":2,"path":["core","mem","take"],"kind":"function"},"1130":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"249":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_label"],"kind":"function"},"2011":{"crate_id":16,"path":["gimli","read","op","Location"],"kind":"enum"},"2338":{"crate_id":17,"path":["object","macho","LoadCommand"],"kind":"struct"},"576":{"crate_id":3,"path":["alloc","borrow","Cow"],"kind":"enum"},"1457":{"crate_id":5,"path":["libc","unix","sigval"],"kind":"struct"},"1784":{"crate_id":10,"path":["hashbrown","raw","FullBucketsIndices"],"kind":"struct"},"22":{"crate_id":2,"path":["core","borrow","BorrowMut"],"kind":"trait"},"903":{"crate_id":2,"path":["core","core_arch","simd","u64x4"],"kind":"struct"},"1230":{"crate_id":2,"path":["core","ops","unsize","DispatchFromDyn"],"kind":"trait"},"349":{"crate_id":1,"path":["std","io","buffered","bufreader","BufReader"],"kind":"struct"},"2111":{"crate_id":17,"path":["object","read","archive","ArchiveKind"],"kind":"enum"},"2438":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation64"],"kind":"struct"},"676":{"crate_id":2,"path":["core","slice","iter","RChunks"],"kind":"struct"},"1557":{"crate_id":5,"path":["libc","unix","linux_like","linux","inotify_event"],"kind":"struct"},"1884":{"crate_id":16,"path":["gimli","constants","DwAte"],"kind":"struct"},"1003":{"crate_id":2,"path":["core","str","iter","LinesAny"],"kind":"struct"},"449":{"crate_id":1,"path":["std","sync","mpsc","SyncSender"],"kind":"struct"},"1330":{"crate_id":3,"path":["alloc","string","retain","SetLenOnDrop"],"kind":"struct"},"2211":{"crate_id":17,"path":["object","read","macho","segment","MachOSegment"],"kind":"struct"},"2538":{"crate_id":18,"path":["memchr","memmem","searcher","PrefilterConfig"],"kind":"enum"},"776":{"crate_id":2,"path":["core","num","flt2dec","decoder","FullDecoded"],"kind":"enum"},"1657":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_req_u"],"kind":"union"},"1103":{"crate_id":2,"path":["core","core_arch","x86","__m128"],"kind":"struct"},"222":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_to_double_or_null"],"kind":"function"},"1984":{"crate_id":16,"path":["gimli","read","line","LineProgramHeader"],"kind":"struct"},"549":{"crate_id":1,"path":["std","io","buffered","bufwriter","flush_buf","BufGuard"],"kind":"struct"},"1430":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","big","u256"],"kind":"struct"},"2311":{"crate_id":17,"path":["object","elf","Dyn64"],"kind":"struct"},"2638":{"crate_id":1,"path":["std","u16"],"kind":"primitive"},"876":{"crate_id":2,"path":["core","core_arch","simd","u32x2"],"kind":"struct"},"1757":{"crate_id":10,"path":["hashbrown","control","bitmask","BitMask"],"kind":"struct"},"1203":{"crate_id":2,"path":["core","ops","bit","Shl"],"kind":"trait"},"322":{"crate_id":1,"path":["std","collections","hash","set","Entry"],"kind":"enum"},"2084":{"crate_id":17,"path":["object","read","util","Bytes"],"kind":"struct"},"649":{"crate_id":1,"path":["std","sys_common","wstr","WStrUnits"],"kind":"struct"},"1530":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_ramp_effect"],"kind":"struct"},"2411":{"crate_id":17,"path":["object","pe","ImageAuxSymbolFunction"],"kind":"struct"},"1857":{"crate_id":16,"path":["gimli","common","DebugStrOffset"],"kind":"struct"},"976":{"crate_id":2,"path":["core","hash","sip","SipHasher24"],"kind":"struct"},"1303":{"crate_id":3,"path":["alloc","boxed","thin","drop","DropGuard"],"kind":"struct"},"422":{"crate_id":1,"path":["std","process","Stdio"],"kind":"struct"},"2184":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldFile"],"kind":"struct"},"2511":{"crate_id":18,"path":["memchr","arch","generic","memchr","Three"],"kind":"struct"},"749":{"crate_id":1,"path":["std","os","unix","process","CommandExt"],"kind":"trait"},"1630":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_desc"],"kind":"struct"},"1957":{"crate_id":16,"path":["gimli","read","reader","ReaderOffsetId"],"kind":"struct"},"195":{"crate_id":0,"path":["rustcalc","rustcalc_kne_cancelJob"],"kind":"function"},"1076":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1403":{"crate_id":3,"path":["alloc","collections","btree","map","CursorMutKey"],"kind":"struct"},"522":{"crate_id":1,"path":["std","backtrace_rs","symbolize","SymbolName"],"kind":"struct"},"2284":{"crate_id":17,"path":["object","read","CompressionFormat"],"kind":"enum"},"2611":{"crate_id":12,"path":["std_detect"],"kind":"module"},"849":{"crate_id":2,"path":["core","sync","atomic","Ordering"],"kind":"enum"},"1730":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","user"],"kind":"struct"},"2057":{"crate_id":17,"path":["object","common","Architecture"],"kind":"enum"},"295":{"crate_id":1,"path":["std","collections","hash","map","HashMap"],"kind":"struct"},"1176":{"crate_id":2,"path":["core","slice","iter","GenericSplitN"],"kind":"struct"},"622":{"crate_id":2,"path":["core","iter","adapters","map_windows","MapWindows"],"kind":"struct"},"1503":{"crate_id":5,"path":["libc","unix","linux_like","linux","packet_mreq"],"kind":"struct"},"2384":{"crate_id":17,"path":["object","macho","Nlist32"],"kind":"struct"},"68":{"crate_id":0,"path":["rustcalc","Point"],"kind":"struct"},"949":{"crate_id":2,"path":["core","ffi","va_list","VaListImpl"],"kind":"struct"},"1830":{"crate_id":15,"path":["addr2line","line","LineLocationRangeIter"],"kind":"struct"},"1276":{"crate_id":2,"path":["core","core_simd","swizzle","interleave","Lo"],"kind":"struct"},"395":{"crate_id":1,"path":["std","os","linux","process","PidFd"],"kind":"struct"},"2157":{"crate_id":17,"path":["object","read","elf","relocation","CrelIteratorState"],"kind":"struct"},"722":{"crate_id":1,"path":["std","sealed","Sealed"],"kind":"trait"},"1603":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_missed"],"kind":"struct"},"2484":{"crate_id":17,"path":["object","xcoff","StatAux"],"kind":"struct"},"1049":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1930":{"crate_id":16,"path":["gimli","read","cfi","CommonInformationEntry"],"kind":"struct"},"1376":{"crate_id":3,"path":["alloc","collections","linked_list","IntoIter"],"kind":"struct"},"495":{"crate_id":1,"path":["std","sys","fs","unix","FileTimes"],"kind":"struct"},"2257":{"crate_id":17,"path":["object","read","xcoff","symbol","XcoffSymbolIterator"],"kind":"struct"},"822":{"crate_id":2,"path":["core","marker","PhantomPinned"],"kind":"struct"},"1703":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","mbstate_t"],"kind":"struct"},"2584":{"crate_id":1,"path":["std","io"],"kind":"module"},"2030":{"crate_id":16,"path":["gimli","read","rnglists","RawRngListEntry"],"kind":"enum"},"268":{"crate_id":0,"path":["rustcalc","rustcalc_greet"],"kind":"function"},"1149":{"crate_id":2,"path":["core","panic","panic_info","PanicInfo"],"kind":"struct"},"1476":{"crate_id":5,"path":["libc","unix","linux_like","lconv"],"kind":"struct"},"595":{"crate_id":1,"path":["std","ascii","AsciiExt"],"kind":"trait"},"2357":{"crate_id":17,"path":["object","macho","SymtabCommand"],"kind":"struct"},"922":{"crate_id":2,"path":["core","core_arch","simd","i64x8"],"kind":"struct"},"1803":{"crate_id":10,"path":["hashbrown","map","OccupiedEntry"],"kind":"struct"},"2130":{"crate_id":17,"path":["object","read","coff","symbol","CoffSymbolTable"],"kind":"struct"},"368":{"crate_id":1,"path":["std","io","stdio","Stderr"],"kind":"struct"},"1249":{"crate_id":2,"path":["core","str","pattern","DoubleEndedSearcher"],"kind":"trait"},"1576":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_clock_time"],"kind":"struct"},"695":{"crate_id":2,"path":["core","core_simd","lane_count","LaneCount"],"kind":"struct"},"2457":{"crate_id":17,"path":["object","pe","ImageCoffSymbolsHeader"],"kind":"struct"},"1022":{"crate_id":2,"path":["core","str","CharEscapeDebugContinue"],"kind":"struct"},"1903":{"crate_id":16,"path":["gimli","constants","DwMacinfo"],"kind":"struct"},"2230":{"crate_id":17,"path":["object","read","pe","data_directory","DataDirectories"],"kind":"struct"},"468":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","RwLockWriteGuard"],"kind":"struct"},"1349":{"crate_id":3,"path":["alloc","collections","btree","node","Handle"],"kind":"struct"},"795":{"crate_id":2,"path":["core","num","niche_types","NonZeroI128Inner"],"kind":"struct"},"1676":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__timeval"],"kind":"struct"},"2557":{"crate_id":1,"path":["std","fs","read_link"],"kind":"function"},"241":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_delayed_is_positive"],"kind":"function"},"1122":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"2003":{"crate_id":16,"path":["gimli","read","macros","DebugMacro"],"kind":"struct"},"2330":{"crate_id":17,"path":["object","macho","DyldCacheSlidePointer5"],"kind":"struct"},"568":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","mmap","Mmap"],"kind":"struct"},"1449":{"crate_id":5,"path":["libc","unix","rlimit"],"kind":"struct"},"895":{"crate_id":2,"path":["core","core_arch","simd","f64x2"],"kind":"struct"},"1776":{"crate_id":10,"path":["hashbrown","set","Intersection"],"kind":"struct"},"341":{"crate_id":1,"path":["std","fs","DirBuilder"],"kind":"struct"},"1222":{"crate_id":2,"path":["core","marker","ConstParamTy_"],"kind":"trait"},"2103":{"crate_id":17,"path":["object","read","any","SymbolTableInternal"],"kind":"enum"},"1549":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous__kernel_fsid_t"],"kind":"struct"},"668":{"crate_id":2,"path":["core","slice","iter","Iter"],"kind":"struct"},"2430":{"crate_id":17,"path":["object","pe","ImageResourceDirectory"],"kind":"struct"},"995":{"crate_id":2,"path":["core","str","iter","RSplitN"],"kind":"struct"},"1876":{"crate_id":16,"path":["gimli","constants","DwSect"],"kind":"struct"},"2203":{"crate_id":17,"path":["object","read","macho","file","MachOFile"],"kind":"struct"},"441":{"crate_id":1,"path":["std","sync","mpmc","Receiver"],"kind":"struct"},"1322":{"crate_id":3,"path":["alloc","collections","vec_deque","shrink_to","Guard"],"kind":"struct"},"1649":{"crate_id":5,"path":["libc","unix","linux_like","linux","mq_attr"],"kind":"struct"},"768":{"crate_id":2,"path":["core","num","bignum","Big32x40"],"kind":"struct"},"2530":{"crate_id":18,"path":["memchr","memchr","Memchr"],"kind":"struct"},"1095":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"214":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_echo"],"kind":"function"},"1976":{"crate_id":16,"path":["gimli","read","index","IndexSectionId"],"kind":"enum"},"2303":{"crate_id":17,"path":["object","elf","Rela32"],"kind":"struct"},"541":{"crate_id":1,"path":["std","sys","process","unix","common","cstring_array","CStringIter"],"kind":"struct"},"1422":{"crate_id":3,"path":["alloc","collections","btree","node","marker","ValMut"],"kind":"struct"},"1749":{"crate_id":8,"path":["miniz_oxide","inflate","output_buffer","InputWrapper"],"kind":"struct"},"868":{"crate_id":2,"path":["core","core_arch","simd","u8x2"],"kind":"struct"},"2630":{"crate_id":1,"path":["std","f64"],"kind":"primitive"},"314":{"crate_id":1,"path":["std","collections","hash","set","ExtractIf"],"kind":"struct"},"1195":{"crate_id":2,"path":["core","ops","arith","Rem"],"kind":"trait"},"2076":{"crate_id":17,"path":["object","endian","U32Bytes"],"kind":"struct"},"2403":{"crate_id":17,"path":["object","pe","AnonObjectHeaderV2"],"kind":"struct"},"641":{"crate_id":2,"path":["core","iter","adapters","zip","TrustedRandomAccessNoCoerce"],"kind":"trait"},"1522":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_id"],"kind":"struct"},"968":{"crate_id":2,"path":["core","range","iter","IterRangeFrom"],"kind":"struct"},"1849":{"crate_id":16,"path":["gimli","common","DebugLocListsBase"],"kind":"struct"},"414":{"crate_id":1,"path":["std","process","Child"],"kind":"struct"},"1295":{"crate_id":2,"path":["core","core_simd","simd","ptr","const_ptr","SimdConstPtr"],"kind":"trait"},"2176":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsectionIterator"],"kind":"struct"},"2503":{"crate_id":18,"path":["memchr","arch","all","twoway","FinderRev"],"kind":"struct"},"741":{"crate_id":1,"path":["std","os","unix","fs","DirBuilderExt"],"kind":"trait"},"1622":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_mmap_offsets"],"kind":"struct"},"1068":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1949":{"crate_id":16,"path":["gimli","read","dwarf","Unit"],"kind":"struct"},"514":{"crate_id":1,"path":["std","sys","process","unix","unix","ExitStatus"],"kind":"struct"},"1395":{"crate_id":3,"path":["alloc","boxed","convert","from","StringError"],"kind":"struct"},"2276":{"crate_id":17,"path":["object","read","ObjectMapFile"],"kind":"struct"},"1722":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","stat64"],"kind":"struct"},"841":{"crate_id":2,"path":["core","net","parser","AddrKind"],"kind":"enum"},"2603":{"crate_id":4,"path":["compiler_builtins"],"kind":"module"},"1168":{"crate_id":2,"path":["core","fmt","builders","FromFn"],"kind":"struct"},"287":{"crate_id":1,"path":["std","thread","Thread"],"kind":"struct"},"2049":{"crate_id":16,"path":["gimli","read","unit","EntriesTreeNode"],"kind":"struct"},"2376":{"crate_id":17,"path":["object","macho","LinkerOptionCommand"],"kind":"struct"},"614":{"crate_id":2,"path":["core","iter","adapters","skip_while","SkipWhile"],"kind":"struct"},"1495":{"crate_id":5,"path":["libc","unix","linux_like","linux","glob_t"],"kind":"struct"},"1822":{"crate_id":13,"path":["rustc_demangle","v0","Demangle"],"kind":"struct"},"941":{"crate_id":2,"path":["core","char","decode","DecodeUtf16"],"kind":"struct"},"1268":{"crate_id":2,"path":["core","str","pattern","Searcher"],"kind":"trait"},"387":{"crate_id":1,"path":["std","os","unix","net","addr","SocketAddr"],"kind":"struct"},"2149":{"crate_id":17,"path":["object","read","elf","symbol","ElfSymbolIterator"],"kind":"struct"},"2476":{"crate_id":17,"path":["object","xcoff","FileAux64"],"kind":"struct"},"714":{"crate_id":1,"path":["std","sys","fs","unix","cfm","CachedFileMetadata"],"kind":"struct"},"1595":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_sm4_ccm"],"kind":"struct"},"1922":{"crate_id":16,"path":["gimli","read","cfi","EhFrame"],"kind":"struct"},"1041":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"487":{"crate_id":1,"path":["std","sys","pal","unix","os","JoinPathsError"],"kind":"struct"},"1368":{"crate_id":3,"path":["alloc","collections","btree","set","Range"],"kind":"struct"},"2249":{"crate_id":17,"path":["object","read","pe","rich","RichHeaderEntry"],"kind":"struct"},"2576":{"crate_id":2,"path":["core","iter"],"kind":"module"},"814":{"crate_id":2,"path":["core","cmp","Reverse"],"kind":"struct"},"1695":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_seccomp"],"kind":"struct"},"2022":{"crate_id":16,"path":["gimli","read","pubtypes","PubTypesEntry"],"kind":"struct"},"260":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_Value_get_value"],"kind":"function"},"1141":{"crate_id":2,"path":["core","cell","lazy","LazyCell"],"kind":"struct"},"587":{"crate_id":2,"path":["core","hash","Hasher"],"kind":"trait"},"1468":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_in"],"kind":"struct"},"2349":{"crate_id":17,"path":["object","macho","SubClientCommand"],"kind":"struct"},"914":{"crate_id":2,"path":["core","core_arch","simd","i8x64"],"kind":"struct"},"1795":{"crate_id":10,"path":["hashbrown","set","Drain"],"kind":"struct"},"1241":{"crate_id":2,"path":["core","marker","Tuple"],"kind":"trait"},"360":{"crate_id":1,"path":["std","io","error","ErrorKind"],"kind":"enum"},"2122":{"crate_id":17,"path":["object","read","coff","file","CoffFile"],"kind":"struct"},"687":{"crate_id":2,"path":["core","slice","iter","RSplitMut"],"kind":"struct"},"1568":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif_addfd"],"kind":"struct"},"2449":{"crate_id":17,"path":["object","pe","ImageArm64RuntimeFunctionEntry"],"kind":"struct"},"1895":{"crate_id":16,"path":["gimli","constants","DwInl"],"kind":"struct"},"1014":{"crate_id":2,"path":["core","str","pattern","CharArrayRefSearcher"],"kind":"struct"},"1341":{"crate_id":3,"path":["alloc","vec","set_len_on_drop","SetLenOnDrop"],"kind":"struct"},"460":{"crate_id":1,"path":["std","sync","reentrant_lock","ReentrantLockGuard"],"kind":"struct"},"2222":{"crate_id":17,"path":["object","read","pe","file","PeComdatIterator"],"kind":"struct"},"2549":{"crate_id":18,"path":["memchr","memmem","searcher","Pre"],"kind":"struct"},"787":{"crate_id":2,"path":["core","num","niche_types","NonZeroU16Inner"],"kind":"struct"},"1668":{"crate_id":5,"path":["libc","unix","linux_like","linux","iwreq"],"kind":"struct"},"1995":{"crate_id":16,"path":["gimli","read","loclists","RawLocListEntry"],"kind":"enum"},"233":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_to_bytes"],"kind":"function"},"1114":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1441":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","sockaddr_can"],"kind":"struct"},"560":{"crate_id":1,"path":["std","sys","process","unix","unix","do_exec","Reset"],"kind":"struct"},"2322":{"crate_id":17,"path":["object","macho","DyldCacheHeader"],"kind":"struct"},"887":{"crate_id":2,"path":["core","core_arch","simd","u64x2"],"kind":"struct"},"1768":{"crate_id":10,"path":["hashbrown","raw","RawIterHashInner"],"kind":"struct"},"2095":{"crate_id":17,"path":["object","read","any","SectionIteratorInternal"],"kind":"enum"},"333":{"crate_id":2,"path":["core","fmt","Display"],"kind":"trait"},"1214":{"crate_id":2,"path":["core","iter","adapters","filter_map","next_chunk","Guard"],"kind":"struct"},"660":{"crate_id":2,"path":["core","ops","index","IndexMut"],"kind":"trait"},"1541":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Sym"],"kind":"struct"},"2422":{"crate_id":17,"path":["object","pe","ImageThunkData64"],"kind":"struct"},"987":{"crate_id":2,"path":["core","str","iter","SplitInternal"],"kind":"struct"},"1868":{"crate_id":16,"path":["gimli","arch","Arm"],"kind":"struct"},"2195":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorV2"],"kind":"struct"},"433":{"crate_id":1,"path":["std","sync","mpmc","select","Token"],"kind":"struct"},"1314":{"crate_id":3,"path":["alloc","collections","linked_list","LinkedList"],"kind":"struct"},"760":{"crate_id":2,"path":["core","ops","arith","AddAssign"],"kind":"trait"},"1641":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf32_rela"],"kind":"struct"},"2522":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","OneIter"],"kind":"struct"},"206":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_long"],"kind":"function"},"1087":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1968":{"crate_id":16,"path":["gimli","read","aranges","ArangeHeader"],"kind":"struct"},"1414":{"crate_id":3,"path":["alloc","string","FromUtf16Error"],"kind":"struct"},"533":{"crate_id":1,"path":["std","os","unix","net","ancillary","SocketCred"],"kind":"struct"},"2295":{"crate_id":17,"path":["object","elf","SectionHeader64"],"kind":"struct"},"860":{"crate_id":2,"path":["core","time","TryFromFloatSecsError"],"kind":"struct"},"1741":{"crate_id":5,"path":["libc","types","Padding"],"kind":"struct"},"2622":{"crate_id":1,"path":["std","unit"],"kind":"primitive"},"2068":{"crate_id":17,"path":["object","common","SegmentFlags"],"kind":"enum"},"306":{"crate_id":1,"path":["std","collections","hash","map","IntoKeys"],"kind":"struct"},"1187":{"crate_id":2,"path":["core","fmt","Octal"],"kind":"trait"},"1514":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket3_hdr"],"kind":"struct"},"633":{"crate_id":2,"path":["core","iter","adapters","rev","Rev"],"kind":"struct"},"2395":{"crate_id":17,"path":["object","pe","ImageOptionalHeader32"],"kind":"struct"},"960":{"crate_id":2,"path":["core","iter","sources","repeat_n","RepeatN"],"kind":"struct"},"1841":{"crate_id":16,"path":["gimli","common","DebugAddrOffset"],"kind":"struct"},"2168":{"crate_id":17,"path":["object","read","elf","version","VersionIndex"],"kind":"struct"},"406":{"crate_id":1,"path":["std","path","Iter"],"kind":"struct"},"1287":{"crate_id":2,"path":["core","core_simd","to_bytes","ToBytes"],"kind":"trait"},"1614":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_condattr_t"],"kind":"struct"},"733":{"crate_id":1,"path":["std","os","unix","ffi","os_str","OsStrExt"],"kind":"trait"},"2495":{"crate_id":18,"path":["memchr","arch","all","memchr","Three"],"kind":"struct"},"1060":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1941":{"crate_id":16,"path":["gimli","read","cfi","CallFrameInstructionIter"],"kind":"struct"},"2268":{"crate_id":17,"path":["object","read","ObjectKind"],"kind":"enum"},"506":{"crate_id":1,"path":["std","sys","os_str","bytes","Buf"],"kind":"struct"},"1387":{"crate_id":3,"path":["alloc","collections","btree","map","IterMut"],"kind":"struct"},"1714":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","timex"],"kind":"struct"},"833":{"crate_id":2,"path":["core","char","convert","ParseCharError"],"kind":"struct"},"2595":{"crate_id":2,"path":["core","core_simd","simd","ptr","const_ptr","SimdConstPtr","with_exposed_provenance"],"kind":"function"},"279":{"crate_id":1,"path":["std","thread","scoped","Scope"],"kind":"struct"},"1160":{"crate_id":2,"path":["core","sync","atomic","AtomicI32"],"kind":"struct"},"2041":{"crate_id":16,"path":["gimli","read","unit","UnitHeader"],"kind":"struct"},"2368":{"crate_id":17,"path":["object","macho","LinkeditDataCommand"],"kind":"struct"},"606":{"crate_id":2,"path":["core","iter","adapters","zip","Zip"],"kind":"struct"},"1487":{"crate_id":5,"path":["libc","unix","linux_like","statx"],"kind":"struct"},"933":{"crate_id":2,"path":["core","num","fmt","Formatted"],"kind":"struct"},"52":{"crate_id":0,"path":["rustcalc","CalcResult"],"kind":"enum"},"1814":{"crate_id":10,"path":["hashbrown","table","Entry"],"kind":"enum"},"379":{"crate_id":1,"path":["std","io","Split"],"kind":"struct"},"1260":{"crate_id":2,"path":["core","net","display_buffer","DisplayBuffer"],"kind":"struct"},"2141":{"crate_id":17,"path":["object","read","elf","file","ElfFile"],"kind":"struct"},"1587":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_authinfo"],"kind":"struct"},"706":{"crate_id":2,"path":["core","ops","control_flow","ControlFlow"],"kind":"enum"},"2468":{"crate_id":17,"path":["object","xcoff","AuxHeader32"],"kind":"struct"},"1033":{"crate_id":2,"path":["core","future","ready","Ready"],"kind":"struct"},"1914":{"crate_id":16,"path":["gimli","read","addr","AddrHeaderIter"],"kind":"struct"},"479":{"crate_id":1,"path":["std","sync","poison","rwlock","MappedRwLockWriteGuard"],"kind":"struct"},"1360":{"crate_id":3,"path":["alloc","collections","btree","map","UnorderedKeyError"],"kind":"struct"},"2241":{"crate_id":17,"path":["object","read","pe","relocation","RelocationIterator"],"kind":"struct"},"1687":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ntptimeval"],"kind":"struct"},"806":{"crate_id":2,"path":["core","intrinsics","AtomicOrdering"],"kind":"enum"},"2568":{"crate_id":2,"path":["core","ffi","primitives","c_char"],"kind":"type_alias"},"1133":{"crate_id":2,"path":["core","mem","drop_guard","DropGuard"],"kind":"struct"},"252":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_set_scale"],"kind":"function"},"2014":{"crate_id":16,"path":["gimli","read","op","EvaluationWaiting"],"kind":"enum"},"2341":{"crate_id":17,"path":["object","macho","SegmentCommand64"],"kind":"struct"},"579":{"crate_id":2,"path":["core","error","Error"],"kind":"trait"},"1460":{"crate_id":5,"path":["libc","unix","servent"],"kind":"struct"},"1787":{"crate_id":10,"path":["hashbrown","map","IntoKeys"],"kind":"struct"},"906":{"crate_id":2,"path":["core","core_arch","simd","i32x8"],"kind":"struct"},"352":{"crate_id":1,"path":["std","io","Write"],"kind":"trait"},"1233":{"crate_id":2,"path":["core","convert","num","private","Sealed"],"kind":"trait"},"2114":{"crate_id":17,"path":["object","read","archive","ArchiveMemberIterator"],"kind":"struct"},"2441":{"crate_id":17,"path":["object","pe","ImagePrologueDynamicRelocationHeader"],"kind":"struct"},"679":{"crate_id":2,"path":["core","slice","iter","RChunksExactMut"],"kind":"struct"},"1560":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_event_info_fid"],"kind":"struct"},"1887":{"crate_id":16,"path":["gimli","constants","DwEnd"],"kind":"struct"},"1006":{"crate_id":2,"path":["core","str","iter","SplitInclusive"],"kind":"struct"},"452":{"crate_id":1,"path":["std","sync","mpsc","TrySendError"],"kind":"enum"},"1333":{"crate_id":3,"path":["alloc","sync","Weak"],"kind":"struct"},"2214":{"crate_id":17,"path":["object","read","macho","section","MachOSection"],"kind":"struct"},"2541":{"crate_id":18,"path":["memchr","memmem","searcher","PrefilterState"],"kind":"struct"},"779":{"crate_id":2,"path":["core","num","error","TryFromIntError"],"kind":"struct"},"1660":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_cond_t"],"kind":"struct"},"1106":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"225":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_nickname"],"kind":"function"},"1987":{"crate_id":16,"path":["gimli","read","line","FileEntry"],"kind":"struct"},"552":{"crate_id":1,"path":["std","sync","mpmc","list","Channel"],"kind":"struct"},"1433":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","env","Status"],"kind":"struct"},"2314":{"crate_id":17,"path":["object","elf","Verdaux"],"kind":"struct"},"1760":{"crate_id":10,"path":["hashbrown","raw","Fallibility"],"kind":"enum"},"879":{"crate_id":2,"path":["core","core_arch","simd","i16x4"],"kind":"struct"},"2641":{"crate_id":1,"path":["std","u128"],"kind":"primitive"},"1206":{"crate_id":2,"path":["core","ops","bit","ShrAssign"],"kind":"trait"},"325":{"crate_id":1,"path":["std","env","Vars"],"kind":"struct"},"2087":{"crate_id":17,"path":["object","read","util","ByteString"],"kind":"struct"},"652":{"crate_id":2,"path":["core","ops","bit","BitAnd"],"kind":"trait"},"1533":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_rumble_effect"],"kind":"struct"},"2414":{"crate_id":17,"path":["object","pe","ImageAuxSymbolSection"],"kind":"struct"},"1860":{"crate_id":16,"path":["gimli","common","DebugTypesOffset"],"kind":"struct"},"979":{"crate_id":2,"path":["core","hash","sip","Hasher"],"kind":"struct"},"1306":{"crate_id":3,"path":["alloc","collections","binary_heap","Hole"],"kind":"struct"},"425":{"crate_id":1,"path":["std","process","ExitCode"],"kind":"struct"},"2187":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingSlice"],"kind":"enum"},"2514":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","One"],"kind":"struct"},"752":{"crate_id":1,"path":["std","os","unix","thread","JoinHandleExt"],"kind":"trait"},"1633":{"crate_id":5,"path":["libc","unix","linux_like","linux","mount_attr"],"kind":"struct"},"1960":{"crate_id":16,"path":["gimli","read","abbrev","AbbreviationsCacheStrategy"],"kind":"enum"},"198":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_new"],"kind":"function"},"1079":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1406":{"crate_id":3,"path":["alloc","collections","btree","set","entry","VacantEntry"],"kind":"struct"},"525":{"crate_id":1,"path":["std","thread","spawnhook","SpawnHooks"],"kind":"struct"},"2287":{"crate_id":17,"path":["object","archive","Header"],"kind":"struct"},"2614":{"crate_id":15,"path":["addr2line"],"kind":"module"},"852":{"crate_id":2,"path":["core","fmt","Sign"],"kind":"enum"},"1733":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","shmid_ds"],"kind":"struct"},"2060":{"crate_id":17,"path":["object","common","BinaryFormat"],"kind":"enum"},"298":{"crate_id":1,"path":["std","collections","hash","map","Values"],"kind":"struct"},"1179":{"crate_id":2,"path":["core","future","poll_fn","PollFn"],"kind":"struct"},"625":{"crate_id":2,"path":["core","iter","traits","collect","FromIterator"],"kind":"trait"},"1506":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_hdr"],"kind":"struct"},"2387":{"crate_id":17,"path":["object","macho","RelocationInfo"],"kind":"struct"},"952":{"crate_id":2,"path":["core","iter","adapters","map_windows","MapWindowsInner"],"kind":"struct"},"1833":{"crate_id":15,"path":["addr2line","RangeAttributes"],"kind":"struct"},"1279":{"crate_id":2,"path":["core","core_simd","swizzle","deinterleave","Odd"],"kind":"struct"},"398":{"crate_id":1,"path":["std","panic","PanicHookInfo"],"kind":"struct"},"2160":{"crate_id":17,"path":["object","read","elf","comdat","ElfComdat"],"kind":"struct"},"725":{"crate_id":1,"path":["std","net","socket_addr","ToSocketAddrs"],"kind":"trait"},"1606":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_pmksa"],"kind":"struct"},"2487":{"crate_id":17,"path":["object","xcoff","Rel32"],"kind":"struct"},"1052":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1933":{"crate_id":16,"path":["gimli","read","cfi","UnwindContext"],"kind":"struct"},"1379":{"crate_id":3,"path":["alloc","collections","vec_deque","iter","Iter"],"kind":"struct"},"498":{"crate_id":1,"path":["std","sys","fs","unix","ReadDir"],"kind":"struct"},"2260":{"crate_id":17,"path":["object","read","xcoff","comdat","XcoffComdatIterator"],"kind":"struct"},"825":{"crate_id":2,"path":["core","ops","range","RangeFrom"],"kind":"struct"},"1706":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","timespec"],"kind":"struct"},"2587":{"crate_id":1,"path":["std","process","exit"],"kind":"function"},"2033":{"crate_id":16,"path":["gimli","read","rnglists","Range"],"kind":"struct"},"271":{"crate_id":0,"path":["rustcalc"],"kind":"module"},"1152":{"crate_id":2,"path":["core","panicking","AssertKind"],"kind":"enum"},"1479":{"crate_id":5,"path":["libc","unix","linux_like","in6_rtmsg"],"kind":"struct"},"598":{"crate_id":2,"path":["core","iter","traits","collect","IntoIterator"],"kind":"trait"},"2360":{"crate_id":17,"path":["object","macho","DylibModule32"],"kind":"struct"},"44":{"crate_id":0,"path":["rustcalc","CalcResult","Value"],"kind":"variant"},"925":{"crate_id":2,"path":["core","core_arch","simd","u16x64"],"kind":"struct"},"1806":{"crate_id":10,"path":["hashbrown","map","VacantEntryRef"],"kind":"struct"},"2133":{"crate_id":17,"path":["object","read","coff","relocation","CoffRelocationIterator"],"kind":"struct"},"371":{"crate_id":1,"path":["std","io","util","Repeat"],"kind":"struct"},"1252":{"crate_id":2,"path":["core","iter","traits","marker","TrustedStep"],"kind":"trait"},"1579":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_sys_offset_precise"],"kind":"struct"},"698":{"crate_id":2,"path":["core","ops","range","OneSidedRange"],"kind":"trait"},"2460":{"crate_id":17,"path":["object","pe","ImageFunctionEntry64"],"kind":"struct"},"1025":{"crate_id":2,"path":["core","str","IsWhitespace"],"kind":"struct"},"1906":{"crate_id":16,"path":["gimli","constants","DwOp"],"kind":"struct"},"2233":{"crate_id":17,"path":["object","read","pe","export","ExportTable"],"kind":"struct"},"471":{"crate_id":1,"path":["std","sync","poison","condvar","Condvar"],"kind":"struct"},"1352":{"crate_id":3,"path":["alloc","collections","binary_heap","Iter"],"kind":"struct"},"798":{"crate_id":2,"path":["core","num","niche_types","NonZeroUsizeInner"],"kind":"struct"},"1679":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","cmsghdr"],"kind":"struct"},"2560":{"crate_id":1,"path":["std","fs"],"kind":"module"},"244":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_measure"],"kind":"function"},"1125":{"crate_id":2,"path":["core","core_arch","x86","__m128h"],"kind":"struct"},"2006":{"crate_id":16,"path":["gimli","read","macros","MacroEntry"],"kind":"enum"},"1452":{"crate_id":5,"path":["libc","unix","hostent"],"kind":"struct"},"571":{"crate_id":2,"path":["core","cmp","PartialEq"],"kind":"trait"},"2333":{"crate_id":17,"path":["object","macho","FatHeader"],"kind":"struct"},"898":{"crate_id":2,"path":["core","core_arch","simd","m32x4"],"kind":"struct"},"1779":{"crate_id":10,"path":["hashbrown","set","Union"],"kind":"struct"},"344":{"crate_id":1,"path":["std","fs","Metadata"],"kind":"struct"},"1225":{"crate_id":2,"path":["core","pin","helper","PinHelper"],"kind":"struct"},"2106":{"crate_id":17,"path":["object","read","any","Symbol"],"kind":"struct"},"1552":{"crate_id":5,"path":["libc","unix","linux_like","linux","posix_spawn_file_actions_t"],"kind":"struct"},"671":{"crate_id":2,"path":["core","slice","iter","Chunks"],"kind":"struct"},"2433":{"crate_id":17,"path":["object","pe","ImageResourceDirStringU"],"kind":"struct"},"998":{"crate_id":2,"path":["core","str","iter","RMatchIndices"],"kind":"struct"},"1879":{"crate_id":16,"path":["gimli","constants","DwCfa"],"kind":"struct"},"2206":{"crate_id":17,"path":["object","read","macho","file","MachOComdatSectionIterator"],"kind":"struct"},"444":{"crate_id":1,"path":["std","sync","mpsc","IntoIter"],"kind":"struct"},"1325":{"crate_id":3,"path":["alloc","ffi","c_str","CString"],"kind":"struct"},"1652":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ifc_ifcu"],"kind":"union"},"771":{"crate_id":2,"path":["core","num","dec2flt","decimal","Decimal"],"kind":"struct"},"2533":{"crate_id":18,"path":["memchr","memmem","searcher","Searcher"],"kind":"struct"},"1098":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"217":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_apply_op"],"kind":"function"},"1979":{"crate_id":16,"path":["gimli","read","line","LineInstruction"],"kind":"enum"},"2306":{"crate_id":17,"path":["object","elf","Relr32"],"kind":"struct"},"544":{"crate_id":1,"path":["std","backtrace_rs","backtrace","libunwind","Frame"],"kind":"enum"},"1425":{"crate_id":3,"path":["alloc","sync","ArcInner"],"kind":"struct"},"1752":{"crate_id":8,"path":["miniz_oxide","MZStatus"],"kind":"enum"},"871":{"crate_id":2,"path":["core","core_arch","simd","u16x2"],"kind":"struct"},"2633":{"crate_id":1,"path":["std","i16"],"kind":"primitive"},"317":{"crate_id":2,"path":["core","hash","Hash"],"kind":"trait"},"1198":{"crate_id":2,"path":["core","ops","arith","Mul"],"kind":"trait"},"2079":{"crate_id":17,"path":["object","endian","I32Bytes"],"kind":"struct"},"2406":{"crate_id":17,"path":["object","pe","ImageSymbol"],"kind":"struct"},"644":{"crate_id":1,"path":["std","os","unix","net","ancillary","AncillaryDataIter"],"kind":"struct"},"1525":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_mask"],"kind":"struct"},"971":{"crate_id":2,"path":["core","fmt","rt","Placeholder"],"kind":"struct"},"1852":{"crate_id":16,"path":["gimli","common","DebugMacroOffset"],"kind":"struct"},"417":{"crate_id":1,"path":["std","process","ChildStderr"],"kind":"struct"},"1298":{"crate_id":2,"path":["core","core_simd","simd","cmp","eq","SimdPartialEq"],"kind":"trait"},"2179":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsubsection"],"kind":"struct"},"2506":{"crate_id":18,"path":["memchr","arch","all","twoway","SuffixKind"],"kind":"enum"},"744":{"crate_id":3,"path":["alloc","rc","UniqueRc"],"kind":"struct"},"1625":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_umem_reg"],"kind":"struct"},"1071":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"190":{"crate_id":0,"path":["rustcalc","greet"],"kind":"function"},"1952":{"crate_id":16,"path":["gimli","read","dwarf","RangeIterInner"],"kind":"enum"},"517":{"crate_id":1,"path":["std","sys","process","env","CommandEnvs"],"kind":"struct"},"1398":{"crate_id":3,"path":["alloc","collections","btree","map","entry","VacantEntry"],"kind":"struct"},"2279":{"crate_id":17,"path":["object","read","CodeView"],"kind":"struct"},"1725":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","pthread_attr_t"],"kind":"struct"},"844":{"crate_id":2,"path":["core","pin","Pin"],"kind":"struct"},"2606":{"crate_id":7,"path":["unwind"],"kind":"module"},"1171":{"crate_id":2,"path":["core","fmt","num_buffer","NumBufferTrait"],"kind":"trait"},"290":{"crate_id":1,"path":["std","backtrace","RawFrame"],"kind":"enum"},"2052":{"crate_id":16,"path":["gimli","read","unit","DebugTypesUnitHeadersIter"],"kind":"struct"},"2379":{"crate_id":17,"path":["object","macho","FvmfileCommand"],"kind":"struct"},"617":{"crate_id":2,"path":["core","iter","adapters","skip","Skip"],"kind":"struct"},"1498":{"crate_id":5,"path":["libc","unix","linux_like","linux","dqblk"],"kind":"struct"},"1825":{"crate_id":13,"path":["rustc_demangle","Demangle"],"kind":"struct"},"944":{"crate_id":2,"path":["core","char","EscapeDebug"],"kind":"struct"},"1271":{"crate_id":2,"path":["core","core_simd","swizzle","reverse","Reverse"],"kind":"struct"},"390":{"crate_id":1,"path":["std","os","unix","net","datagram","UnixDatagram"],"kind":"struct"},"2152":{"crate_id":17,"path":["object","read","elf","relocation","ElfDynamicRelocationIterator"],"kind":"struct"},"2479":{"crate_id":17,"path":["object","xcoff","FunAux32"],"kind":"struct"},"717":{"crate_id":1,"path":["std","io","stdio","StderrRaw"],"kind":"struct"},"1598":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_param"],"kind":"struct"},"1925":{"crate_id":16,"path":["gimli","read","cfi","SectionBaseAddresses"],"kind":"struct"},"1044":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"490":{"crate_id":1,"path":["std","sys","pal","unix","time","Instant"],"kind":"struct"},"1371":{"crate_id":3,"path":["alloc","collections","btree","set","Intersection"],"kind":"struct"},"2252":{"crate_id":17,"path":["object","read","xcoff","section","XcoffSection"],"kind":"struct"},"2579":{"crate_id":2,"path":["core","write"],"kind":"macro"},"817":{"crate_id":2,"path":["core","marker","variance","PhantomInvariantLifetime"],"kind":"struct"},"1698":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","iocb"],"kind":"struct"},"1144":{"crate_id":2,"path":["core","ffi","va_list","VaList"],"kind":"struct"},"263":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_new_Partial"],"kind":"function"},"2025":{"crate_id":16,"path":["gimli","read","rnglists","DebugRanges"],"kind":"struct"},"590":{"crate_id":3,"path":["alloc","sync","Arc"],"kind":"struct"},"1471":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_ll"],"kind":"struct"},"2352":{"crate_id":17,"path":["object","macho","PreboundDylibCommand"],"kind":"struct"},"36":{"crate_id":2,"path":["core","convert","Infallible"],"kind":"enum"},"917":{"crate_id":2,"path":["core","core_arch","simd","u16x32"],"kind":"struct"},"1798":{"crate_id":10,"path":["hashbrown","table","IterHashMut"],"kind":"struct"},"1244":{"crate_id":2,"path":["core","ops","async_function","AsyncFnOnce"],"kind":"trait"},"363":{"crate_id":1,"path":["std","io","pipe","PipeWriter"],"kind":"struct"},"2125":{"crate_id":17,"path":["object","read","coff","section","CoffSegment"],"kind":"struct"},"690":{"crate_id":2,"path":["core","slice","iter","RSplitN"],"kind":"struct"},"1571":{"crate_id":5,"path":["libc","unix","linux_like","linux","nlattr"],"kind":"struct"},"2452":{"crate_id":17,"path":["object","pe","ImageRuntimeFunctionEntry"],"kind":"struct"},"1898":{"crate_id":16,"path":["gimli","constants","DwIdx"],"kind":"struct"},"1017":{"crate_id":2,"path":["core","str","pattern","StrSearcher"],"kind":"struct"},"1344":{"crate_id":3,"path":["alloc","vec","retain_mut","BackshiftOnDrop"],"kind":"struct"},"463":{"crate_id":1,"path":["std","sync","nonpoison","mutex","Mutex"],"kind":"struct"},"2225":{"crate_id":17,"path":["object","read","pe","section","PeSegmentIterator"],"kind":"struct"},"2552":{"crate_id":1,"path":["std","path","MAIN_SEPARATOR"],"kind":"constant"},"790":{"crate_id":2,"path":["core","num","niche_types","NonZeroU128Inner"],"kind":"struct"},"1671":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_perout_request"],"kind":"struct"},"1998":{"crate_id":16,"path":["gimli","read","lookup","DebugLookup"],"kind":"struct"},"236":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_recent_scores"],"kind":"function"},"1117":{"crate_id":2,"path":["core","core_arch","x86","__m512d"],"kind":"struct"},"1444":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","__c_anonymous_sockaddr_can_j1939"],"kind":"struct"},"563":{"crate_id":1,"path":["std","sys","sync","once","futex","CompletionGuard"],"kind":"struct"},"2325":{"crate_id":17,"path":["object","macho","DyldCacheImageInfo"],"kind":"struct"},"9":{"crate_id":2,"path":["core","marker","Freeze"],"kind":"trait"},"890":{"crate_id":2,"path":["core","core_arch","simd","i32x4"],"kind":"struct"},"1771":{"crate_id":10,"path":["hashbrown","map","Iter"],"kind":"struct"},"2098":{"crate_id":17,"path":["object","read","any","ComdatIteratorInternal"],"kind":"enum"},"336":{"crate_id":1,"path":["std","ffi","os_str","Display"],"kind":"struct"},"1217":{"crate_id":2,"path":["core","slice","sort","unstable","quicksort","GapGuardRaw"],"kind":"struct"},"663":{"crate_id":2,"path":["core","mem","maybe_uninit","MaybeUninit"],"kind":"union"},"1544":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Phdr"],"kind":"struct"},"2425":{"crate_id":17,"path":["object","pe","ImageTlsDirectory32"],"kind":"struct"},"109":{"crate_id":0,"path":["rustcalc","Measurable"],"kind":"trait"},"990":{"crate_id":2,"path":["core","str","iter","RSplit"],"kind":"struct"},"1871":{"crate_id":16,"path":["gimli","arch","MIPS"],"kind":"struct"},"2198":{"crate_id":17,"path":["object","read","macho","dyld_cache","RelocationStateV5"],"kind":"enum"},"436":{"crate_id":1,"path":["std","sync","mpmc","zero","ZeroToken"],"kind":"struct"},"1317":{"crate_id":3,"path":["alloc","collections","vec_deque","drain","drop","DropGuard"],"kind":"struct"},"763":{"crate_id":2,"path":["core","alloc","layout","Layout"],"kind":"struct"},"1644":{"crate_id":5,"path":["libc","unix","linux_like","linux","dirent"],"kind":"struct"},"2525":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","Three"],"kind":"struct"},"209":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_short"],"kind":"function"},"1090":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1971":{"crate_id":16,"path":["gimli","read","index","DebugCuIndex"],"kind":"struct"},"1417":{"crate_id":3,"path":["alloc","task","LocalWake"],"kind":"trait"},"536":{"crate_id":1,"path":["std","sys","pal","unix","time","Timespec"],"kind":"struct"},"2298":{"crate_id":17,"path":["object","elf","Sym32"],"kind":"struct"},"863":{"crate_id":2,"path":["core","wtf8","Wtf8"],"kind":"struct"},"1744":{"crate_id":5,"path":["libc","unix","linux_like","timezone"],"kind":"enum"},"2625":{"crate_id":1,"path":["std","slice"],"kind":"primitive"},"2071":{"crate_id":17,"path":["object","common","RelocationFlags"],"kind":"enum"},"309":{"crate_id":1,"path":["std","collections","hash","map","ExtractIf"],"kind":"struct"},"1190":{"crate_id":2,"path":["core","fmt","LowerExp"],"kind":"trait"},"1517":{"crate_id":5,"path":["libc","unix","linux_like","linux","cpu_set_t"],"kind":"struct"},"636":{"crate_id":2,"path":["core","iter","adapters","cycle","Cycle"],"kind":"struct"},"2398":{"crate_id":17,"path":["object","pe","ImageNtHeaders64"],"kind":"struct"},"963":{"crate_id":2,"path":["core","net","ip_addr","fmt","Span"],"kind":"struct"},"1844":{"crate_id":16,"path":["gimli","common","DebugArangesOffset"],"kind":"struct"},"2171":{"crate_id":17,"path":["object","read","elf","version","VerdefIterator"],"kind":"struct"},"409":{"crate_id":1,"path":["std","path","PathBuf"],"kind":"struct"},"1290":{"crate_id":2,"path":["core","core_simd","vector","sealed","Sealed"],"kind":"trait"},"1617":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_sys_offset"],"kind":"struct"},"736":{"crate_id":1,"path":["std","os","unix","fs","OpenOptionsExt"],"kind":"trait"},"2498":{"crate_id":18,"path":["memchr","arch","all","packedpair","Pair"],"kind":"struct"},"1063":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1944":{"crate_id":16,"path":["gimli","read","cfi","PointerEncodingParameters"],"kind":"struct"},"2271":{"crate_id":17,"path":["object","read","SymbolSection"],"kind":"enum"},"509":{"crate_id":1,"path":["std","sys","process","unix","common","Stdio"],"kind":"enum"},"1390":{"crate_id":3,"path":["alloc","collections","btree","map","IntoKeys"],"kind":"struct"},"836":{"crate_id":2,"path":["core","char","decode","DecodeUtf16Error"],"kind":"struct"},"1717":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","flock"],"kind":"struct"},"2598":{"crate_id":2,"path":["core","core_simd","simd","ptr","mut_ptr","SimdMutPtr","with_exposed_provenance"],"kind":"function"},"282":{"crate_id":2,"path":["core","fmt","Debug"],"kind":"trait"},"1163":{"crate_id":2,"path":["core","sync","atomic","AtomicU64"],"kind":"struct"},"2044":{"crate_id":16,"path":["gimli","read","unit","Attribute"],"kind":"struct"},"2371":{"crate_id":17,"path":["object","macho","EncryptionInfoCommand64"],"kind":"struct"},"609":{"crate_id":2,"path":["core","iter","adapters","map","Map"],"kind":"struct"},"1490":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_un"],"kind":"struct"},"936":{"crate_id":2,"path":["core","array","iter","iter_inner","PolymorphicIter"],"kind":"struct"},"1817":{"crate_id":10,"path":["hashbrown","table","AbsentEntry"],"kind":"struct"},"382":{"crate_id":1,"path":["std","net","tcp","IntoIncoming"],"kind":"struct"},"1263":{"crate_id":2,"path":["core","pat","RangePattern"],"kind":"trait"},"2144":{"crate_id":17,"path":["object","read","elf","section","SectionTable"],"kind":"struct"},"1590":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aes_gcm_128"],"kind":"struct"},"709":{"crate_id":3,"path":["alloc","borrow","ToOwned"],"kind":"trait"},"2471":{"crate_id":17,"path":["object","xcoff","SectionHeader64"],"kind":"struct"},"1036":{"crate_id":2,"path":["core","task","wake","LocalWaker"],"kind":"struct"},"1917":{"crate_id":16,"path":["gimli","read","cfi","DebugFrame"],"kind":"struct"},"2244":{"crate_id":17,"path":["object","read","pe","resource","ResourceDirectoryTable"],"kind":"struct"},"482":{"crate_id":1,"path":["std","sync","WaitTimeoutResult"],"kind":"struct"},"1363":{"crate_id":3,"path":["alloc","collections","btree","navigate","LeafRange"],"kind":"struct"},"1690":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","Elf32_Chdr"],"kind":"struct"},"809":{"crate_id":2,"path":["core","mem","Discriminant"],"kind":"struct"},"2571":{"crate_id":2,"path":["core","str","pattern"],"kind":"module"},"1136":{"crate_id":2,"path":["core","error","tags","Ref"],"kind":"struct"},"255":{"crate_id":0,"path":["rustcalc","rustcalc_Operation_name"],"kind":"function"},"2017":{"crate_id":16,"path":["gimli","read","op","OperationIter"],"kind":"struct"},"2344":{"crate_id":17,"path":["object","macho","Fvmlib"],"kind":"struct"},"582":{"crate_id":1,"path":["std","panicking","panic_handler","StaticStrPayload"],"kind":"struct"},"1463":{"crate_id":5,"path":["libc","unix","linux_like","in_addr"],"kind":"struct"},"1790":{"crate_id":10,"path":["hashbrown","map","IterMut"],"kind":"struct"},"909":{"crate_id":2,"path":["core","core_arch","simd","f32x8"],"kind":"struct"},"355":{"crate_id":1,"path":["std","io","buffered","IntoInnerError"],"kind":"struct"},"1236":{"crate_id":2,"path":["core","ops","coroutine","Coroutine"],"kind":"trait"},"2117":{"crate_id":17,"path":["object","read","archive","ArchiveOffset"],"kind":"struct"},"2444":{"crate_id":17,"path":["object","pe","ImageLoadConfigDirectory64"],"kind":"struct"},"682":{"crate_id":2,"path":["core","slice","iter","Split"],"kind":"struct"},"1563":{"crate_id":5,"path":["libc","unix","linux_like","linux","sock_extended_err"],"kind":"struct"},"1890":{"crate_id":16,"path":["gimli","constants","DwVirtuality"],"kind":"struct"},"1009":{"crate_id":2,"path":["core","str","iter","EscapeDefault"],"kind":"struct"},"455":{"crate_id":1,"path":["std","sync","barrier","Barrier"],"kind":"struct"},"1336":{"crate_id":3,"path":["alloc","vec","extract_if","ExtractIf"],"kind":"struct"},"2217":{"crate_id":17,"path":["object","read","macho","symbol","MachOSymbolTable"],"kind":"struct"},"2544":{"crate_id":18,"path":["memchr","memmem","Finder"],"kind":"struct"},"782":{"crate_id":2,"path":["core","num","nonzero","ZeroablePrimitive"],"kind":"trait"},"1663":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_barrier_t"],"kind":"struct"},"1109":{"crate_id":2,"path":["core","core_arch","x86","__m256"],"kind":"struct"},"228":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_last_result"],"kind":"function"},"1990":{"crate_id":16,"path":["gimli","read","loclists","DebugLoc"],"kind":"struct"},"555":{"crate_id":1,"path":["std","sync","lazy_lock","force_mut","really_init_mut","PoisonOnPanic"],"kind":"struct"},"1436":{"crate_id":4,"path":["compiler_builtins","math","libm_math","generic","fma","Norm"],"kind":"struct"},"2317":{"crate_id":17,"path":["object","elf","NoteHeader32"],"kind":"struct"},"1763":{"crate_id":10,"path":["hashbrown","raw","ProbeSeq"],"kind":"struct"},"1":{"crate_id":0,"path":["rustcalc","Operation","Subtract"],"kind":"variant"},"882":{"crate_id":2,"path":["core","core_arch","simd","f32x2"],"kind":"struct"},"2644":{"crate_id":1,"path":["std","reference"],"kind":"primitive"},"1209":{"crate_id":2,"path":["core","clone","uninit","InitializingSlice"],"kind":"struct"},"328":{"crate_id":1,"path":["std","env","SplitPaths"],"kind":"struct"},"2090":{"crate_id":17,"path":["object","read","any","SegmentIterator"],"kind":"struct"},"2417":{"crate_id":17,"path":["object","pe","ImageLinenumber"],"kind":"struct"},"655":{"crate_id":2,"path":["core","time","Duration"],"kind":"struct"},"1536":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_ff_erase"],"kind":"struct"},"1863":{"crate_id":16,"path":["gimli","common","EhFrameOffset"],"kind":"struct"},"982":{"crate_id":2,"path":["core","slice","sort","stable","drift","DriftsortRun"],"kind":"struct"},"1309":{"crate_id":3,"path":["alloc","collections","btree","map","BTreeMap"],"kind":"struct"},"428":{"crate_id":1,"path":["std","sync","mpmc","context","Context"],"kind":"struct"},"2190":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMapping"],"kind":"struct"},"2517":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","TwoIter"],"kind":"struct"},"755":{"crate_id":1,"path":["std","os","net","linux_ext","socket","UnixSocketExt"],"kind":"trait"},"1636":{"crate_id":5,"path":["libc","unix","linux_like","linux","dmabuf_cmsg"],"kind":"struct"},"1963":{"crate_id":16,"path":["gimli","read","abbrev","Abbreviation"],"kind":"struct"},"201":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_subtract"],"kind":"function"},"1082":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"528":{"crate_id":1,"path":["std","sync","mpmc","utils","CachePadded"],"kind":"struct"},"1409":{"crate_id":3,"path":["alloc","collections","btree","set","ExtractIf"],"kind":"struct"},"2290":{"crate_id":17,"path":["object","archive","AixMemberOffset"],"kind":"struct"},"2617":{"crate_id":18,"path":["memchr"],"kind":"module"},"855":{"crate_id":2,"path":["core","hash","BuildHasherDefault"],"kind":"struct"},"1736":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","user_fpregs_struct"],"kind":"struct"},"2063":{"crate_id":17,"path":["object","common","SymbolKind"],"kind":"enum"},"301":{"crate_id":1,"path":["std","collections","hash","map","VacantEntry"],"kind":"struct"},"1182":{"crate_id":2,"path":["core","task","wake","ContextBuilder"],"kind":"struct"},"628":{"crate_id":2,"path":["core","iter","traits","collect","Extend"],"kind":"trait"},"1509":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_req"],"kind":"struct"},"2390":{"crate_id":17,"path":["object","pe","ImageOs2Header"],"kind":"struct"},"955":{"crate_id":2,"path":["core","iter","sources","from_fn","FromFn"],"kind":"struct"},"1836":{"crate_id":16,"path":["gimli","common","Vendor"],"kind":"enum"},"1282":{"crate_id":2,"path":["core","core_simd","vector","splat","splat_rt","Splat"],"kind":"struct"},"401":{"crate_id":1,"path":["std","path","State"],"kind":"enum"},"2163":{"crate_id":17,"path":["object","read","elf","note","Note"],"kind":"struct"},"728":{"crate_id":2,"path":["core","net","ip_addr","IpAddr"],"kind":"enum"},"1609":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_range"],"kind":"struct"},"2490":{"crate_id":17,"path":["object","read","elf","relocation","ElfRelocationIterator"],"kind":"enum"},"1936":{"crate_id":16,"path":["gimli","read","cfi","RegisterRuleIter"],"kind":"struct"},"1055":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1382":{"crate_id":3,"path":["alloc","ffi","c_str","FromVecWithNulError"],"kind":"struct"},"501":{"crate_id":1,"path":["std","sys","fs","unix","File"],"kind":"struct"},"2263":{"crate_id":17,"path":["object","read","xcoff","segment","XcoffSegmentIterator"],"kind":"struct"},"828":{"crate_id":2,"path":["core","ops","range","RangeToInclusive"],"kind":"struct"},"1709":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","utmpx"],"kind":"struct"},"2590":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt","st_ctime"],"kind":"function"},"2036":{"crate_id":16,"path":["gimli","read","str","DebugLineStr"],"kind":"struct"},"274":{"crate_id":2,"path":["core","ffi","c_str","CStr"],"kind":"struct"},"1155":{"crate_id":2,"path":["core","result","IterMut"],"kind":"struct"},"1482":{"crate_id":5,"path":["libc","unix","linux_like","arphdr"],"kind":"struct"},"601":{"crate_id":2,"path":["core","ops","function","FnOnce"],"kind":"trait"},"2363":{"crate_id":17,"path":["object","macho","TwolevelHintsCommand"],"kind":"struct"},"47":{"crate_id":0,"path":["rustcalc","CalcResult","Error"],"kind":"variant"},"928":{"crate_id":2,"path":["core","core_arch","x86","cpuid","CpuidResult"],"kind":"struct"},"1809":{"crate_id":10,"path":["hashbrown","rustc_entry","RustcOccupiedEntry"],"kind":"struct"},"2136":{"crate_id":17,"path":["object","read","coff","comdat","CoffComdatSectionIterator"],"kind":"struct"},"374":{"crate_id":1,"path":["std","io","IoSlice"],"kind":"struct"},"1255":{"crate_id":2,"path":["core","async_iter","async_iter","AsyncIterator"],"kind":"trait"},"1582":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_sndrcvinfo"],"kind":"struct"},"701":{"crate_id":2,"path":["core","str","lossy","Utf8Chunks"],"kind":"struct"},"2463":{"crate_id":17,"path":["object","pe","ImageArchitectureEntry"],"kind":"struct"},"1028":{"crate_id":2,"path":["core","str","BytesIsNotEmpty"],"kind":"struct"},"1909":{"crate_id":16,"path":["gimli","endianity","LittleEndian"],"kind":"struct"},"2236":{"crate_id":17,"path":["object","read","pe","import","ImportThunkList"],"kind":"struct"},"474":{"crate_id":1,"path":["std","sync","poison","mutex","MappedMutexGuard"],"kind":"struct"},"1355":{"crate_id":3,"path":["alloc","collections","btree","map","Iter"],"kind":"struct"},"801":{"crate_id":2,"path":["core","num","niche_types","I32NotAllOnes"],"kind":"struct"},"1682":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","mallinfo2"],"kind":"struct"},"2563":{"crate_id":2,"path":["core","slice","raw","from_raw_parts"],"kind":"function"},"247":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_describe_self"],"kind":"function"},"1128":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"2009":{"crate_id":16,"path":["gimli","read","op","Operation"],"kind":"enum"},"1455":{"crate_id":5,"path":["libc","unix","winsize"],"kind":"struct"},"574":{"crate_id":1,"path":["std","sys","pal","unix","kernel_copy","SpliceMode"],"kind":"enum"},"2336":{"crate_id":17,"path":["object","macho","MachHeader32"],"kind":"struct"},"901":{"crate_id":2,"path":["core","core_arch","simd","u16x16"],"kind":"struct"},"1782":{"crate_id":10,"path":["hashbrown","table","IterHash"],"kind":"struct"},"347":{"crate_id":1,"path":["std","hash","random","DefaultHasher"],"kind":"struct"},"1228":{"crate_id":2,"path":["core","ops","unsize","CoerceUnsized"],"kind":"trait"},"2109":{"crate_id":17,"path":["object","read","any","SectionRelocationIterator"],"kind":"struct"},"1555":{"crate_id":5,"path":["libc","unix","linux_like","linux","in6_pktinfo"],"kind":"struct"},"674":{"crate_id":2,"path":["core","slice","iter","ChunksExactMut"],"kind":"struct"},"2436":{"crate_id":17,"path":["object","pe","ImageDynamicRelocationTable"],"kind":"struct"},"1001":{"crate_id":2,"path":["core","str","iter","RMatches"],"kind":"struct"},"1882":{"crate_id":16,"path":["gimli","constants","DwAt"],"kind":"struct"},"2209":{"crate_id":17,"path":["object","read","macho","load_command","LoadCommandVariant"],"kind":"enum"},"447":{"crate_id":1,"path":["std","sync","mpsc","RecvTimeoutError"],"kind":"enum"},"1328":{"crate_id":3,"path":["alloc","rc","UniqueRcUninit"],"kind":"struct"},"1655":{"crate_id":5,"path":["libc","unix","linux_like","linux","dirent64"],"kind":"struct"},"774":{"crate_id":2,"path":["core","num","dec2flt","FloatErrorKind"],"kind":"enum"},"2536":{"crate_id":18,"path":["memchr","memmem","searcher","SearcherRev"],"kind":"struct"},"220":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_is_positive_or_null"],"kind":"function"},"1101":{"crate_id":2,"path":["core","core_arch","x86","__m128i"],"kind":"struct"},"1982":{"crate_id":16,"path":["gimli","read","line","ColumnType"],"kind":"enum"},"2309":{"crate_id":17,"path":["object","elf","ProgramHeader64"],"kind":"struct"},"547":{"crate_id":1,"path":["std","thread","PanicGuard"],"kind":"struct"},"1428":{"crate_id":4,"path":["compiler_builtins","int","big","u256"],"kind":"struct"},"1755":{"crate_id":9,"path":["adler2","algo","U32X4"],"kind":"struct"},"874":{"crate_id":2,"path":["core","core_arch","simd","u8x8"],"kind":"struct"},"2636":{"crate_id":1,"path":["std","i128"],"kind":"primitive"},"320":{"crate_id":1,"path":["std","collections","hash","set","SymmetricDifference"],"kind":"struct"},"1201":{"crate_id":2,"path":["core","ops","bit","BitXorAssign"],"kind":"trait"},"2082":{"crate_id":17,"path":["object","read","read_cache","ReadCacheInternal"],"kind":"struct"},"2409":{"crate_id":17,"path":["object","pe","ImageSymbolExBytes"],"kind":"struct"},"647":{"crate_id":1,"path":["std","os","unix","net","ancillary","Messages"],"kind":"struct"},"1528":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_envelope"],"kind":"struct"},"974":{"crate_id":2,"path":["core","fmt","rt","Argument"],"kind":"struct"},"1855":{"crate_id":16,"path":["gimli","common","DebugRngListsBase"],"kind":"struct"},"420":{"crate_id":1,"path":["std","process","CommandEnvs"],"kind":"struct"},"1301":{"crate_id":3,"path":["alloc","raw_vec","RawVec"],"kind":"struct"},"2182":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCache"],"kind":"struct"},"1628":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_statistics_v1"],"kind":"struct"},"747":{"crate_id":1,"path":["std","os","fd","raw","IntoRawFd"],"kind":"trait"},"2509":{"crate_id":18,"path":["memchr","arch","generic","memchr","One"],"kind":"struct"},"1074":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"193":{"crate_id":0,"path":["rustcalc","rustcalc_kne_hasError"],"kind":"function"},"1955":{"crate_id":16,"path":["gimli","read","endian_slice","DebugByte"],"kind":"struct"},"520":{"crate_id":1,"path":["std","backtrace_rs","backtrace","Frame"],"kind":"struct"},"1401":{"crate_id":3,"path":["alloc","collections","btree","map","ExtractIf"],"kind":"struct"},"2282":{"crate_id":17,"path":["object","read","RelocationMap"],"kind":"struct"},"1728":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","_libc_fpstate"],"kind":"struct"},"847":{"crate_id":2,"path":["core","range","RangeFrom"],"kind":"struct"},"2609":{"crate_id":10,"path":["hashbrown"],"kind":"module"},"1174":{"crate_id":2,"path":["core","cell","UnsafeCell"],"kind":"struct"},"293":{"crate_id":1,"path":["std","backtrace","BacktraceSymbol"],"kind":"struct"},"2055":{"crate_id":16,"path":["gimli","read","StoreOnHeap"],"kind":"struct"},"2382":{"crate_id":17,"path":["object","macho","DataInCodeEntry"],"kind":"struct"},"620":{"crate_id":2,"path":["core","iter","adapters","flatten","FlatMap"],"kind":"struct"},"1501":{"crate_id":5,"path":["libc","unix","linux_like","linux","fsid_t"],"kind":"struct"},"1828":{"crate_id":13,"path":["rustc_demangle","SizeLimitExhausted"],"kind":"struct"},"947":{"crate_id":2,"path":["core","char","CaseMappingIter"],"kind":"struct"},"1274":{"crate_id":2,"path":["core","core_simd","swizzle","shift_elements_left","Shift"],"kind":"struct"},"393":{"crate_id":1,"path":["std","os","unix","net","stream","UnixStream"],"kind":"struct"},"2155":{"crate_id":17,"path":["object","read","elf","relocation","Crel"],"kind":"struct"},"2482":{"crate_id":17,"path":["object","xcoff","BlockAux32"],"kind":"struct"},"720":{"crate_id":1,"path":["std","io","Seek"],"kind":"trait"},"1601":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_quality"],"kind":"struct"},"1928":{"crate_id":16,"path":["gimli","read","cfi","Augmentation"],"kind":"struct"},"1047":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"493":{"crate_id":1,"path":["std","sys","env","common","Env"],"kind":"struct"},"1374":{"crate_id":3,"path":["alloc","collections","btree","set_val","SetValZST"],"kind":"struct"},"2255":{"crate_id":17,"path":["object","read","xcoff","symbol","SymbolIterator"],"kind":"struct"},"2582":{"crate_id":1,"path":["std","fs","read_to_string"],"kind":"function"},"820":{"crate_id":2,"path":["core","marker","variance","PhantomInvariant"],"kind":"struct"},"1701":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fanotify_event_info_error"],"kind":"struct"},"1147":{"crate_id":2,"path":["core","iter","adapters","by_ref_sized","ByRefSized"],"kind":"struct"},"266":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_new_Nothing"],"kind":"function"},"2028":{"crate_id":16,"path":["gimli","read","rnglists","RangeListsFormat"],"kind":"enum"},"593":{"crate_id":3,"path":["alloc","collections","TryReserveError"],"kind":"struct"},"1474":{"crate_id":5,"path":["libc","unix","linux_like","sched_param"],"kind":"struct"},"2355":{"crate_id":17,"path":["object","macho","RoutinesCommand32"],"kind":"struct"},"920":{"crate_id":2,"path":["core","core_arch","simd","f16x32"],"kind":"struct"},"1801":{"crate_id":10,"path":["hashbrown","table","ExtractIf"],"kind":"struct"},"1247":{"crate_id":2,"path":["core","ops","try_trait","NeverShortCircuitResidual"],"kind":"enum"},"366":{"crate_id":1,"path":["std","io","stdio","Stdout"],"kind":"struct"},"2128":{"crate_id":17,"path":["object","read","coff","symbol","SymbolTable"],"kind":"struct"},"693":{"crate_id":2,"path":["core","core_simd","vector","Simd"],"kind":"struct"},"1574":{"crate_id":5,"path":["libc","unix","linux_like","linux","option"],"kind":"struct"},"2455":{"crate_id":17,"path":["object","pe","ImageEnclaveImport"],"kind":"struct"},"1901":{"crate_id":16,"path":["gimli","constants","DwLne"],"kind":"struct"},"1020":{"crate_id":2,"path":["core","str","pattern","TwoWaySearcher"],"kind":"struct"},"1347":{"crate_id":3,"path":["alloc","collections","btree","node","NodeRef"],"kind":"struct"},"466":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","RwLock"],"kind":"struct"},"2228":{"crate_id":17,"path":["object","read","pe","section","PeSection"],"kind":"struct"},"2555":{"crate_id":1,"path":["std","fs","canonicalize"],"kind":"function"},"793":{"crate_id":2,"path":["core","num","niche_types","NonZeroI32Inner"],"kind":"struct"},"1674":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","aiocb"],"kind":"struct"},"2001":{"crate_id":16,"path":["gimli","read","lookup","PubStuffParser"],"kind":"struct"},"239":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_fail_after_delay"],"kind":"function"},"1120":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1447":{"crate_id":5,"path":["libc","unix","utimbuf"],"kind":"struct"},"566":{"crate_id":1,"path":["std","sys","thread_local","abort_on_dtor_unwind","DtorUnwindGuard"],"kind":"struct"},"2328":{"crate_id":17,"path":["object","macho","DyldCacheSlidePointer3"],"kind":"struct"},"893":{"crate_id":2,"path":["core","core_arch","simd","f16x8"],"kind":"struct"},"1774":{"crate_id":10,"path":["hashbrown","set","HashSet"],"kind":"struct"},"2101":{"crate_id":17,"path":["object","read","any","ComdatSectionIteratorInternal"],"kind":"enum"},"339":{"crate_id":1,"path":["std","fs","FileTimes"],"kind":"struct"},"1220":{"crate_id":2,"path":["core","intrinsics","fallback","DisjointBitOr"],"kind":"trait"},"666":{"crate_id":2,"path":["core","slice","index","SliceIndex"],"kind":"trait"},"1547":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf32_rel"],"kind":"struct"},"2428":{"crate_id":17,"path":["object","pe","ImageBoundForwarderRef"],"kind":"struct"},"993":{"crate_id":2,"path":["core","str","iter","SplitNInternal"],"kind":"struct"},"1874":{"crate_id":16,"path":["gimli","arch","X86_64"],"kind":"struct"},"1320":{"crate_id":3,"path":["alloc","collections","vec_deque","drop","Dropper"],"kind":"struct"},"439":{"crate_id":1,"path":["std","sync","mpmc","TryIter"],"kind":"struct"},"2201":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldRelocationAuth"],"kind":"struct"},"766":{"crate_id":2,"path":["core","alloc","AllocError"],"kind":"struct"},"1647":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_user_dev"],"kind":"struct"},"2528":{"crate_id":18,"path":["memchr","cow","CowBytes"],"kind":"struct"},"212":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_check_flag"],"kind":"function"},"1093":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1974":{"crate_id":16,"path":["gimli","read","index","UnitIndexSectionIterator"],"kind":"struct"},"1420":{"crate_id":3,"path":["alloc","collections","btree","borrow","DormantMutRef"],"kind":"struct"},"539":{"crate_id":1,"path":["std","sys","fs","unix","StatxExtraFields"],"kind":"struct"},"2301":{"crate_id":17,"path":["object","elf","Syminfo64"],"kind":"struct"},"866":{"crate_id":2,"path":["core","task","wake","RawWakerVTable"],"kind":"struct"},"1747":{"crate_id":8,"path":["miniz_oxide","inflate","core","State"],"kind":"enum"},"2628":{"crate_id":1,"path":["std","f16"],"kind":"primitive"},"2074":{"crate_id":17,"path":["object","endian","BigEndian"],"kind":"struct"},"312":{"crate_id":1,"path":["std","collections","hash","set","IntoIter"],"kind":"struct"},"1193":{"crate_id":2,"path":["core","ops","arith","Div"],"kind":"trait"},"1520":{"crate_id":5,"path":["libc","unix","linux_like","linux","sembuf"],"kind":"struct"},"639":{"crate_id":2,"path":["core","iter","traits","accum","Product"],"kind":"trait"},"2401":{"crate_id":17,"path":["object","pe","Guid"],"kind":"struct"},"966":{"crate_id":2,"path":["core","range","iter","IterRange"],"kind":"struct"},"1847":{"crate_id":16,"path":["gimli","common","DebugLineStrOffset"],"kind":"struct"},"2174":{"crate_id":17,"path":["object","read","elf","version","VernauxIterator"],"kind":"struct"},"412":{"crate_id":1,"path":["std","path","Path"],"kind":"struct"},"1293":{"crate_id":2,"path":["core","core_simd","simd","num","sealed","Sealed"],"kind":"trait"},"1620":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_xdp"],"kind":"struct"},"739":{"crate_id":1,"path":["std","os","unix","fs","DirEntryExt"],"kind":"trait"},"2501":{"crate_id":18,"path":["memchr","arch","all","rabinkarp","Hash"],"kind":"struct"},"1066":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1947":{"crate_id":16,"path":["gimli","read","dwarf","DwarfPackageSections"],"kind":"struct"},"2274":{"crate_id":17,"path":["object","read","ObjectMap"],"kind":"struct"},"512":{"crate_id":1,"path":["std","sys","process","unix","common","ExitCode"],"kind":"struct"},"1393":{"crate_id":3,"path":["alloc","collections","linked_list","IterMut"],"kind":"struct"},"839":{"crate_id":2,"path":["core","ffi","c_str","FromBytesUntilNulError"],"kind":"struct"},"1720":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","stack_t"],"kind":"struct"},"2601":{"crate_id":2,"path":["core"],"kind":"module"},"285":{"crate_id":1,"path":["std","thread","Builder"],"kind":"struct"},"1166":{"crate_id":2,"path":["core","sync","atomic","AtomicBool"],"kind":"struct"},"2047":{"crate_id":16,"path":["gimli","read","unit","EntriesCursor"],"kind":"struct"},"2374":{"crate_id":17,"path":["object","macho","BuildToolVersion"],"kind":"struct"},"612":{"crate_id":2,"path":["core","iter","adapters","enumerate","Enumerate"],"kind":"struct"},"1493":{"crate_id":5,"path":["libc","unix","linux_like","sigevent"],"kind":"struct"},"939":{"crate_id":2,"path":["core","async_iter","from_iter","FromIter"],"kind":"struct"},"1820":{"crate_id":12,"path":["std_detect","detect","cache","Initializer"],"kind":"struct"},"385":{"crate_id":1,"path":["std","net","udp","UdpSocket"],"kind":"struct"},"1266":{"crate_id":2,"path":["core","sync","atomic","Sealed"],"kind":"trait"},"2147":{"crate_id":17,"path":["object","read","elf","symbol","SymbolTable"],"kind":"struct"},"1593":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_chacha20_poly1305"],"kind":"struct"},"712":{"crate_id":3,"path":["alloc","collections","vec_deque","VecDeque"],"kind":"struct"},"2474":{"crate_id":17,"path":["object","xcoff","Symbol64"],"kind":"struct"},"1039":{"crate_id":2,"path":["core","escape","MaybeEscaped"],"kind":"struct"},"1920":{"crate_id":16,"path":["gimli","read","cfi","EhHdrTableIter"],"kind":"struct"},"2247":{"crate_id":17,"path":["object","read","pe","resource","ResourceNameOrId"],"kind":"enum"},"485":{"crate_id":1,"path":["std","time","SystemTime"],"kind":"struct"},"1366":{"crate_id":3,"path":["alloc","collections","btree","set","BTreeSet"],"kind":"struct"},"1693":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_entry"],"kind":"struct"},"812":{"crate_id":2,"path":["core","ptr","metadata","DynMetadata"],"kind":"struct"},"2574":{"crate_id":2,"path":["core","panic"],"kind":"macro"},"1139":{"crate_id":2,"path":["core","asserting","TryCaptureWithoutDebug"],"kind":"struct"},"258":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_tag"],"kind":"function"},"2020":{"crate_id":16,"path":["gimli","read","pubnames","DebugPubNames"],"kind":"struct"},"2347":{"crate_id":17,"path":["object","macho","DylibCommand"],"kind":"struct"},"585":{"crate_id":2,"path":["core","error","private","Internal"],"kind":"struct"},"1466":{"crate_id":5,"path":["libc","unix","linux_like","ip_mreq_source"],"kind":"struct"},"1793":{"crate_id":10,"path":["hashbrown","map","Drain"],"kind":"struct"},"912":{"crate_id":2,"path":["core","core_arch","simd","m16x16"],"kind":"struct"},"358":{"crate_id":1,"path":["std","io","error","SimpleMessage"],"kind":"struct"},"1239":{"crate_id":2,"path":["core","marker","variance","sealed","Sealed"],"kind":"trait"},"2120":{"crate_id":17,"path":["object","read","archive","ArchiveSymbol"],"kind":"struct"},"2447":{"crate_id":17,"path":["object","pe","ImageHotPatchHashes"],"kind":"struct"},"685":{"crate_id":2,"path":["core","slice","iter","SplitInclusiveMut"],"kind":"struct"},"1566":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif"],"kind":"struct"},"1012":{"crate_id":2,"path":["core","str","pattern","MultiCharEqSearcher"],"kind":"struct"},"1893":{"crate_id":16,"path":["gimli","constants","DwId"],"kind":"struct"},"458":{"crate_id":1,"path":["std","sync","once_lock","OnceLock"],"kind":"struct"},"1339":{"crate_id":3,"path":["alloc","vec","drain","drop","DropGuard"],"kind":"struct"},"2220":{"crate_id":17,"path":["object","read","macho","relocation","MachORelocationIterator"],"kind":"struct"},"2547":{"crate_id":18,"path":["memchr","vector","SensibleMoveMask"],"kind":"struct"},"785":{"crate_id":2,"path":["core","num","niche_types","Nanoseconds"],"kind":"struct"},"1666":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_event"],"kind":"struct"},"1112":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"231":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_named_value"],"kind":"function"},"1993":{"crate_id":16,"path":["gimli","read","loclists","LocListsFormat"],"kind":"enum"},"558":{"crate_id":1,"path":["std","sys","fs","unix","Dir"],"kind":"struct"},"1439":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","canfd_frame"],"kind":"struct"},"2320":{"crate_id":17,"path":["object","elf","GnuHashHeader"],"kind":"struct"},"1766":{"crate_id":10,"path":["hashbrown","raw","RawIterRange"],"kind":"struct"},"885":{"crate_id":2,"path":["core","core_arch","simd","u16x8"],"kind":"struct"},"1212":{"crate_id":2,"path":["core","cell","lazy","force_mut","really_init_mut","PoisonOnPanic"],"kind":"struct"},"331":{"crate_id":1,"path":["std","env","ArgsOs"],"kind":"struct"},"2093":{"crate_id":17,"path":["object","read","any","Segment"],"kind":"struct"},"2420":{"crate_id":17,"path":["object","pe","ImageExportDirectory"],"kind":"struct"},"658":{"crate_id":2,"path":["core","fmt","Arguments"],"kind":"struct"},"1539":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Ehdr"],"kind":"struct"},"1866":{"crate_id":16,"path":["gimli","common","DwoId"],"kind":"struct"},"104":{"crate_id":0,"path":["rustcalc","Resettable"],"kind":"trait"},"985":{"crate_id":2,"path":["core","str","iter","CharIndices"],"kind":"struct"},"1312":{"crate_id":3,"path":["alloc","collections","btree","mem","replace","PanicGuard"],"kind":"struct"},"431":{"crate_id":1,"path":["std","sync","mpmc","list","Position"],"kind":"struct"},"2193":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorVersion"],"kind":"enum"},"2520":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","packedpair","Finder"],"kind":"struct"},"758":{"crate_id":2,"path":["core","random","RandomSource"],"kind":"trait"},"1639":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_mlme"],"kind":"struct"},"1966":{"crate_id":16,"path":["gimli","read","aranges","DebugAranges"],"kind":"struct"},"204":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_divide"],"kind":"function"},"1085":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"531":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","Cache"],"kind":"struct"},"1412":{"crate_id":3,"path":["alloc","collections","linked_list","CursorMut"],"kind":"struct"},"2293":{"crate_id":17,"path":["object","elf","Ident"],"kind":"struct"},"2620":{"crate_id":1,"path":["std","never"],"kind":"primitive"},"858":{"crate_id":2,"path":["core","str","pattern","SearchStep"],"kind":"enum"},"1739":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","not_x32","statvfs"],"kind":"struct"},"2066":{"crate_id":17,"path":["object","common","RelocationEncoding"],"kind":"enum"},"304":{"crate_id":1,"path":["std","collections","hash","map","IntoIter"],"kind":"struct"},"1185":{"crate_id":2,"path":["core","num","nonzero","private","Sealed"],"kind":"trait"},"631":{"crate_id":2,"path":["core","cmp","Ord"],"kind":"trait"},"1512":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_stats"],"kind":"struct"},"2393":{"crate_id":17,"path":["object","pe","ImageFileHeader"],"kind":"struct"},"958":{"crate_id":2,"path":["core","iter","sources","repeat","Repeat"],"kind":"struct"},"1839":{"crate_id":16,"path":["gimli","common","Register"],"kind":"struct"},"1285":{"crate_id":2,"path":["core","core_simd","lane_count","sealed","Sealed"],"kind":"trait"},"404":{"crate_id":1,"path":["std","path","Components"],"kind":"struct"},"2166":{"crate_id":17,"path":["object","read","elf","hash","HashTable"],"kind":"struct"},"731":{"crate_id":3,"path":["alloc","vec","into_iter","IntoIter"],"kind":"struct"},"1612":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_mutexattr_t"],"kind":"struct"},"2493":{"crate_id":18,"path":["memchr","arch","all","memchr","Two"],"kind":"struct"},"1939":{"crate_id":16,"path":["gimli","read","cfi","RegisterRule"],"kind":"enum"},"1058":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1385":{"crate_id":3,"path":["alloc","string","IntoChars"],"kind":"struct"},"504":{"crate_id":1,"path":["std","sys","net","connection","socket","TcpListener"],"kind":"struct"},"2266":{"crate_id":17,"path":["object","read","Error"],"kind":"struct"},"2593":{"crate_id":2,"path":["core","intrinsics","unchecked_funnel_shr"],"kind":"function"},"831":{"crate_id":2,"path":["core","cell","once","OnceCell"],"kind":"struct"},"1712":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","msqid_ds"],"kind":"struct"},"2039":{"crate_id":16,"path":["gimli","read","unit","DebugInfoUnitHeadersIter"],"kind":"struct"},"277":{"crate_id":3,"path":["alloc","boxed","Box"],"kind":"struct"},"1158":{"crate_id":2,"path":["core","sync","atomic","AtomicI16"],"kind":"struct"},"1485":{"crate_id":5,"path":["libc","unix","linux_like","sock_filter"],"kind":"struct"},"604":{"crate_id":2,"path":["core","iter","adapters","step_by","StepBy"],"kind":"struct"},"2366":{"crate_id":17,"path":["object","macho","UuidCommand"],"kind":"struct"},"50":{"crate_id":0,"path":["rustcalc","CalcResult","Partial"],"kind":"variant"},"931":{"crate_id":2,"path":["core","core_simd","masks","MaskElement"],"kind":"trait"},"1812":{"crate_id":10,"path":["hashbrown","set","OccupiedEntry"],"kind":"struct"},"2139":{"crate_id":17,"path":["object","read","coff","import","ImportType"],"kind":"enum"},"377":{"crate_id":1,"path":["std","io","Take"],"kind":"struct"},"1258":{"crate_id":2,"path":["core","cell","CloneFromCell"],"kind":"trait"},"704":{"crate_id":3,"path":["alloc","slice","Join"],"kind":"trait"},"1585":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_nxtinfo"],"kind":"struct"},"2466":{"crate_id":17,"path":["object","xcoff","FileHeader32"],"kind":"struct"},"1031":{"crate_id":2,"path":["core","wtf8","EncodeWide"],"kind":"struct"},"1912":{"crate_id":16,"path":["gimli","read","util","ArrayVec"],"kind":"struct"},"2239":{"crate_id":17,"path":["object","read","pe","import","DelayLoadDescriptorIterator"],"kind":"struct"},"477":{"crate_id":1,"path":["std","sync","poison","rwlock","RwLockWriteGuard"],"kind":"struct"},"1358":{"crate_id":3,"path":["alloc","collections","btree","map","Range"],"kind":"struct"},"804":{"crate_id":2,"path":["core","num","niche_types","CodePointInner"],"kind":"struct"},"1685":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","nl_mmap_hdr"],"kind":"struct"},"2566":{"crate_id":2,"path":["core","mem","replace"],"kind":"function"},"250":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_set_label"],"kind":"function"},"1131":{"crate_id":2,"path":["core","core_arch","x86","bf16"],"kind":"struct"},"2012":{"crate_id":16,"path":["gimli","read","op","Piece"],"kind":"struct"},"1458":{"crate_id":5,"path":["libc","unix","itimerval"],"kind":"struct"},"577":{"crate_id":1,"path":["std","sync","mpmc","counter","Sender"],"kind":"struct"},"2339":{"crate_id":17,"path":["object","macho","LcStr"],"kind":"struct"},"904":{"crate_id":2,"path":["core","core_arch","simd","i8x32"],"kind":"struct"},"23":{"crate_id":2,"path":["core","convert","From"],"kind":"trait"},"1785":{"crate_id":10,"path":["hashbrown","raw","RawIntoIter"],"kind":"struct"},"2112":{"crate_id":17,"path":["object","read","archive","Members"],"kind":"enum"},"350":{"crate_id":1,"path":["std","io","buffered","bufwriter","WriterPanicked"],"kind":"struct"},"1231":{"crate_id":2,"path":["core","fmt","Pointer"],"kind":"trait"},"1558":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_response"],"kind":"struct"},"677":{"crate_id":2,"path":["core","slice","iter","RChunksMut"],"kind":"struct"},"2439":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation32V2"],"kind":"struct"},"1004":{"crate_id":2,"path":["core","str","iter","SplitWhitespace"],"kind":"struct"},"1885":{"crate_id":16,"path":["gimli","constants","DwLle"],"kind":"struct"},"2212":{"crate_id":17,"path":["object","read","macho","segment","MachOSegmentInternal"],"kind":"struct"},"450":{"crate_id":1,"path":["std","sync","mpsc","Receiver"],"kind":"struct"},"1331":{"crate_id":3,"path":["alloc","string","Drain"],"kind":"struct"},"1658":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_bd_header_u"],"kind":"union"},"777":{"crate_id":2,"path":["core","num","flt2dec","Sign"],"kind":"enum"},"2539":{"crate_id":18,"path":["memchr","memmem","searcher","Prefilter"],"kind":"struct"},"223":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_optional"],"kind":"function"},"1104":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1985":{"crate_id":16,"path":["gimli","read","line","IncompleteLineProgram"],"kind":"struct"},"2312":{"crate_id":17,"path":["object","elf","Versym"],"kind":"struct"},"550":{"crate_id":2,"path":["core","ops","drop","Drop"],"kind":"trait"},"1431":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","big","i256"],"kind":"struct"},"1758":{"crate_id":10,"path":["hashbrown","control","group","sse2","Group"],"kind":"struct"},"877":{"crate_id":2,"path":["core","core_arch","simd","u64x1"],"kind":"struct"},"2639":{"crate_id":1,"path":["std","u32"],"kind":"primitive"},"323":{"crate_id":1,"path":["std","collections","hash","set","OccupiedEntry"],"kind":"struct"},"1204":{"crate_id":2,"path":["core","ops","bit","ShlAssign"],"kind":"trait"},"2085":{"crate_id":17,"path":["object","read","util","DebugByte"],"kind":"struct"},"2412":{"crate_id":17,"path":["object","pe","ImageAuxSymbolFunctionBeginEnd"],"kind":"struct"},"650":{"crate_id":2,"path":["core","iter","traits","marker","FusedIterator"],"kind":"trait"},"1531":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_condition_effect"],"kind":"struct"},"977":{"crate_id":2,"path":["core","hash","sip","SipHasher"],"kind":"struct"},"1858":{"crate_id":16,"path":["gimli","common","DebugStrOffsetsBase"],"kind":"struct"},"423":{"crate_id":1,"path":["std","process","ExitStatus"],"kind":"struct"},"1304":{"crate_id":3,"path":["alloc","collections","binary_heap","PeekMut"],"kind":"struct"},"2185":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheImageIterator"],"kind":"struct"},"1631":{"crate_id":5,"path":["libc","unix","linux_like","linux","xsk_tx_metadata_completion"],"kind":"struct"},"750":{"crate_id":1,"path":["std","os","unix","process","ExitStatusExt"],"kind":"trait"},"2512":{"crate_id":18,"path":["memchr","arch","generic","memchr","Iter"],"kind":"struct"},"1077":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"196":{"crate_id":0,"path":["rustcalc","rustcalc_kne_disposeRef"],"kind":"function"},"1958":{"crate_id":16,"path":["gimli","read","relocate","RelocateReader"],"kind":"struct"},"523":{"crate_id":1,"path":["std","backtrace_rs","symbolize","gimli","parse_running_mmaps","MapsEntry"],"kind":"struct"},"1404":{"crate_id":3,"path":["alloc","collections","btree","set","entry","Entry"],"kind":"enum"},"2285":{"crate_id":17,"path":["object","read","CompressedFileRange"],"kind":"struct"},"1731":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","mcontext_t"],"kind":"struct"},"850":{"crate_id":2,"path":["core","sync","exclusive","Exclusive"],"kind":"struct"},"2612":{"crate_id":13,"path":["rustc_demangle"],"kind":"module"},"1177":{"crate_id":2,"path":["core","str","lossy","Debug"],"kind":"struct"},"296":{"crate_id":1,"path":["std","collections","hash","map","Iter"],"kind":"struct"},"2058":{"crate_id":17,"path":["object","common","SubArchitecture"],"kind":"enum"},"2385":{"crate_id":17,"path":["object","macho","Nlist64"],"kind":"struct"},"623":{"crate_id":2,"path":["core","iter","adapters","fuse","Fuse"],"kind":"struct"},"1504":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_pkt"],"kind":"struct"},"1831":{"crate_id":15,"path":["addr2line","unit","LocationRangeIter"],"kind":"struct"},"950":{"crate_id":2,"path":["core","iter","adapters","flatten","FlattenCompat"],"kind":"struct"},"396":{"crate_id":1,"path":["std","os","fd","owned","BorrowedFd"],"kind":"struct"},"1277":{"crate_id":2,"path":["core","core_simd","swizzle","interleave","Hi"],"kind":"struct"},"2158":{"crate_id":17,"path":["object","read","elf","relocation","CrelIterator"],"kind":"struct"},"2485":{"crate_id":17,"path":["object","xcoff","DwarfAux32"],"kind":"struct"},"723":{"crate_id":2,"path":["core","net","socket_addr","SocketAddr"],"kind":"enum"},"1604":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_scan_req"],"kind":"struct"},"1931":{"crate_id":16,"path":["gimli","read","cfi","PartialFrameDescriptionEntry"],"kind":"struct"},"1050":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"496":{"crate_id":1,"path":["std","sys","fs","unix","FileType"],"kind":"struct"},"1377":{"crate_id":3,"path":["alloc","collections","linked_list","Cursor"],"kind":"struct"},"2258":{"crate_id":17,"path":["object","read","xcoff","symbol","XcoffSymbol"],"kind":"struct"},"2585":{"crate_id":1,"path":["std","env"],"kind":"module"},"823":{"crate_id":2,"path":["core","ops","coroutine","CoroutineState"],"kind":"enum"},"1704":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fpos64_t"],"kind":"struct"},"1150":{"crate_id":2,"path":["core","panic","panic_info","PanicMessage"],"kind":"struct"},"269":{"crate_id":0,"path":["rustcalc","rustcalc_sum_all"],"kind":"function"},"2031":{"crate_id":16,"path":["gimli","read","rnglists","RngListIter"],"kind":"struct"},"596":{"crate_id":2,"path":["core","ops","index","Index"],"kind":"trait"},"1477":{"crate_id":5,"path":["libc","unix","linux_like","in_pktinfo"],"kind":"struct"},"2358":{"crate_id":17,"path":["object","macho","DysymtabCommand"],"kind":"struct"},"1804":{"crate_id":10,"path":["hashbrown","map","VacantEntry"],"kind":"struct"},"42":{"crate_id":2,"path":["core","any","Any"],"kind":"trait"},"923":{"crate_id":2,"path":["core","core_arch","simd","u64x8"],"kind":"struct"},"1250":{"crate_id":2,"path":["core","iter","traits","marker","TrustedLen"],"kind":"trait"},"369":{"crate_id":1,"path":["std","io","stdio","StderrLock"],"kind":"struct"},"2131":{"crate_id":17,"path":["object","read","coff","symbol","CoffSymbolIterator"],"kind":"struct"},"696":{"crate_id":2,"path":["core","core_simd","lane_count","SupportedLaneCount"],"kind":"trait"},"1577":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_extts_request"],"kind":"struct"},"2458":{"crate_id":17,"path":["object","pe","ImageDebugMisc"],"kind":"struct"},"1904":{"crate_id":16,"path":["gimli","constants","DwMacro"],"kind":"struct"},"1023":{"crate_id":2,"path":["core","str","CharEscapeUnicode"],"kind":"struct"},"1350":{"crate_id":3,"path":["alloc","bstr","ByteString"],"kind":"struct"},"469":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","MappedRwLockReadGuard"],"kind":"struct"},"2231":{"crate_id":17,"path":["object","read","pe","export","Export"],"kind":"struct"},"2558":{"crate_id":1,"path":["std","io","error","Result"],"kind":"type_alias"},"796":{"crate_id":2,"path":["core","num","niche_types","NonZeroCharInner"],"kind":"struct"},"1677":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","glob64_t"],"kind":"struct"},"2004":{"crate_id":16,"path":["gimli","read","macros","MacroUnitHeader"],"kind":"struct"},"242":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_count_up"],"kind":"function"},"1123":{"crate_id":2,"path":["core","core_arch","x86","__m512bh"],"kind":"struct"},"1450":{"crate_id":5,"path":["libc","unix","rusage"],"kind":"struct"},"569":{"crate_id":1,"path":["std","backtrace_rs","print","BacktraceFrameFmt"],"kind":"struct"},"2331":{"crate_id":17,"path":["object","macho","DyldSubCacheEntryV1"],"kind":"struct"},"15":{"crate_id":2,"path":["core","panic","unwind_safe","RefUnwindSafe"],"kind":"trait"},"896":{"crate_id":2,"path":["core","core_arch","simd","m8x16"],"kind":"struct"},"1777":{"crate_id":10,"path":["hashbrown","set","Difference"],"kind":"struct"},"2104":{"crate_id":17,"path":["object","read","any","SymbolIterator"],"kind":"struct"},"342":{"crate_id":1,"path":["std","fs","TryLockError"],"kind":"enum"},"1223":{"crate_id":2,"path":["core","ffi","va_list","VaArgSafe"],"kind":"trait"},"669":{"crate_id":2,"path":["core","slice","iter","IterMut"],"kind":"struct"},"1550":{"crate_id":5,"path":["libc","unix","linux_like","linux","ucred"],"kind":"struct"},"2431":{"crate_id":17,"path":["object","pe","ImageResourceDirectoryEntry"],"kind":"struct"},"996":{"crate_id":2,"path":["core","str","iter","MatchIndicesInternal"],"kind":"struct"},"1877":{"crate_id":16,"path":["gimli","constants","DwSectV2"],"kind":"struct"},"1323":{"crate_id":3,"path":["alloc","collections","vec_deque","truncate","Dropper"],"kind":"struct"},"442":{"crate_id":1,"path":["std","sync","mpsc","Iter"],"kind":"struct"},"2204":{"crate_id":17,"path":["object","read","macho","file","MachOComdatIterator"],"kind":"struct"},"769":{"crate_id":2,"path":["core","num","bignum","tests","Big8x3"],"kind":"struct"},"1650":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ifr_ifru"],"kind":"union"},"2531":{"crate_id":18,"path":["memchr","memchr","Memchr2"],"kind":"struct"},"215":{"crate_id":1,"path":["std","os","raw","c_char"],"kind":"type_alias"},"1096":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1977":{"crate_id":16,"path":["gimli","read","line","DebugLine"],"kind":"struct"},"1423":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Owned"],"kind":"enum"},"542":{"crate_id":1,"path":["std","sys","thread_local","native","eager","State"],"kind":"enum"},"2304":{"crate_id":17,"path":["object","elf","Rel64"],"kind":"struct"},"869":{"crate_id":2,"path":["core","core_arch","simd","i8x2"],"kind":"struct"},"1750":{"crate_id":8,"path":["miniz_oxide","inflate","TINFLStatus"],"kind":"enum"},"2631":{"crate_id":1,"path":["std","f128"],"kind":"primitive"},"2077":{"crate_id":17,"path":["object","endian","U64Bytes"],"kind":"struct"},"315":{"crate_id":1,"path":["std","collections","hash","set","Intersection"],"kind":"struct"},"1196":{"crate_id":2,"path":["core","ops","arith","RemAssign"],"kind":"trait"},"1523":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_absinfo"],"kind":"struct"},"642":{"crate_id":1,"path":["std","io","Read"],"kind":"trait"},"2404":{"crate_id":17,"path":["object","pe","AnonObjectHeaderBigobj"],"kind":"struct"},"969":{"crate_id":2,"path":["core","result","Iter"],"kind":"struct"},"1850":{"crate_id":16,"path":["gimli","common","DebugLocListsIndex"],"kind":"struct"},"2177":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsection"],"kind":"struct"},"415":{"crate_id":1,"path":["std","process","ChildStdin"],"kind":"struct"},"1296":{"crate_id":2,"path":["core","core_simd","simd","ptr","sealed","Sealed"],"kind":"trait"},"1623":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_ring_offset_v1"],"kind":"struct"},"742":{"crate_id":1,"path":["std","os","net","linux_ext","addr","SocketAddrExt"],"kind":"trait"},"2504":{"crate_id":18,"path":["memchr","arch","all","twoway","TwoWay"],"kind":"struct"},"1069":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1950":{"crate_id":16,"path":["gimli","read","dwarf","UnitRef"],"kind":"struct"},"2277":{"crate_id":17,"path":["object","read","Import"],"kind":"struct"},"515":{"crate_id":1,"path":["std","sys","process","unix","unix","ExitStatusError"],"kind":"struct"},"1396":{"crate_id":3,"path":["alloc","collections","binary_heap","Drain"],"kind":"struct"},"842":{"crate_id":2,"path":["core","net","parser","AddrParseError"],"kind":"struct"},"1723":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","statfs64"],"kind":"struct"},"2604":{"crate_id":5,"path":["libc"],"kind":"module"},"288":{"crate_id":1,"path":["std","thread","JoinHandle"],"kind":"struct"},"1169":{"crate_id":2,"path":["core","ops","function","Fn"],"kind":"trait"},"2050":{"crate_id":16,"path":["gimli","read","unit","EntriesTreeIter"],"kind":"struct"},"1496":{"crate_id":5,"path":["libc","unix","linux_like","linux","passwd"],"kind":"struct"},"615":{"crate_id":2,"path":["core","iter","adapters","take_while","TakeWhile"],"kind":"struct"},"2377":{"crate_id":17,"path":["object","macho","SymsegCommand"],"kind":"struct"},"942":{"crate_id":2,"path":["core","char","EscapeUnicode"],"kind":"struct"},"1823":{"crate_id":13,"path":["rustc_demangle","v0","Ident"],"kind":"struct"},"388":{"crate_id":1,"path":["std","os","unix","net","ancillary","AncillaryError"],"kind":"enum"},"1269":{"crate_id":2,"path":["core","str","pattern","MultiCharEqPattern"],"kind":"struct"},"2150":{"crate_id":17,"path":["object","read","elf","symbol","ElfSymbol"],"kind":"struct"},"1596":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aria_gcm_128"],"kind":"struct"},"715":{"crate_id":1,"path":["std","sys","stdio","unix","Stdin"],"kind":"struct"},"2477":{"crate_id":17,"path":["object","xcoff","CsectAux32"],"kind":"struct"},"1042":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1923":{"crate_id":16,"path":["gimli","read","cfi","CieOffsetEncoding"],"kind":"enum"},"2250":{"crate_id":17,"path":["object","read","xcoff","file","XcoffFile"],"kind":"struct"},"488":{"crate_id":1,"path":["std","sys","pal","unix","pipe","AnonPipe"],"kind":"struct"},"1369":{"crate_id":3,"path":["alloc","collections","btree","set","Difference"],"kind":"struct"},"1696":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ptrace_syscall_info"],"kind":"struct"},"815":{"crate_id":2,"path":["core","marker","variance","PhantomCovariantLifetime"],"kind":"struct"},"2577":{"crate_id":2,"path":["core","iter","sources","once","once"],"kind":"function"},"1142":{"crate_id":2,"path":["core","cell","BorrowError"],"kind":"struct"},"261":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_new_Error"],"kind":"function"},"2023":{"crate_id":16,"path":["gimli","read","pubtypes","DebugPubTypes"],"kind":"struct"},"2350":{"crate_id":17,"path":["object","macho","SubUmbrellaCommand"],"kind":"struct"},"588":{"crate_id":1,"path":["std","thread","thread_name_string","ThreadNameString"],"kind":"struct"},"1469":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_in6"],"kind":"struct"},"1796":{"crate_id":10,"path":["hashbrown","set","ExtractIf"],"kind":"struct"},"34":{"crate_id":2,"path":["core","convert","TryInto"],"kind":"trait"},"915":{"crate_id":2,"path":["core","core_arch","simd","u8x64"],"kind":"struct"},"361":{"crate_id":1,"path":["std","io","error","repr_bitpacked","Repr"],"kind":"struct"},"1242":{"crate_id":2,"path":["core","ops","async_function","AsyncFn"],"kind":"trait"},"2123":{"crate_id":17,"path":["object","read","coff","section","SectionTable"],"kind":"struct"},"2450":{"crate_id":17,"path":["object","pe","ImageAlpha64RuntimeFunctionEntry"],"kind":"struct"},"688":{"crate_id":2,"path":["core","slice","iter","SplitN"],"kind":"struct"},"1569":{"crate_id":5,"path":["libc","unix","linux_like","linux","nlmsghdr"],"kind":"struct"},"1015":{"crate_id":2,"path":["core","str","pattern","CharSliceSearcher"],"kind":"struct"},"1896":{"crate_id":16,"path":["gimli","constants","DwOrd"],"kind":"struct"},"461":{"crate_id":1,"path":["std","sync","nonpoison","WouldBlock"],"kind":"struct"},"1342":{"crate_id":3,"path":["alloc","vec","in_place_drop","InPlaceDrop"],"kind":"struct"},"2223":{"crate_id":17,"path":["object","read","pe","file","PeComdat"],"kind":"struct"},"2550":{"crate_id":1,"path":["std","ffi"],"kind":"module"},"788":{"crate_id":2,"path":["core","num","niche_types","NonZeroU32Inner"],"kind":"struct"},"1669":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ptp_perout_request_1"],"kind":"union"},"1115":{"crate_id":2,"path":["core","core_arch","x86","__m512"],"kind":"struct"},"234":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_sum_bytes"],"kind":"function"},"1996":{"crate_id":16,"path":["gimli","read","loclists","LocListIter"],"kind":"struct"},"561":{"crate_id":1,"path":["std","sys","process","unix","unix","posix_spawn","PosixSpawnFileActions"],"kind":"struct"},"1442":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","__c_anonymous_sockaddr_can_can_addr"],"kind":"union"},"2323":{"crate_id":17,"path":["object","macho","DyldCacheMappingInfo"],"kind":"struct"},"1769":{"crate_id":10,"path":["hashbrown","raw","RawIterHash"],"kind":"struct"},"7":{"crate_id":2,"path":["core","marker","Sync"],"kind":"trait"},"888":{"crate_id":2,"path":["core","core_arch","simd","i8x16"],"kind":"struct"},"1215":{"crate_id":2,"path":["core","slice","sort","stable","merge","MergeState"],"kind":"struct"},"334":{"crate_id":1,"path":["std","ffi","os_str","OsString"],"kind":"struct"},"2096":{"crate_id":17,"path":["object","read","any","Section"],"kind":"struct"},"2423":{"crate_id":17,"path":["object","pe","ImageThunkData32"],"kind":"struct"},"661":{"crate_id":2,"path":["core","ops","deref","Deref"],"kind":"trait"},"1542":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Sym"],"kind":"struct"},"1869":{"crate_id":16,"path":["gimli","arch","AArch64"],"kind":"struct"},"988":{"crate_id":2,"path":["core","str","iter","Split"],"kind":"struct"},"1315":{"crate_id":3,"path":["alloc","collections","linked_list","drop","DropGuard"],"kind":"struct"},"434":{"crate_id":1,"path":["std","sync","mpmc","select","Operation"],"kind":"struct"},"2196":{"crate_id":17,"path":["object","read","macho","dyld_cache","RelocationStateV3"],"kind":"enum"},"2523":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","Two"],"kind":"struct"},"761":{"crate_id":2,"path":["core","ops","arith","SubAssign"],"kind":"trait"},"1642":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf64_rela"],"kind":"struct"},"1969":{"crate_id":16,"path":["gimli","read","aranges","ArangeEntryIter"],"kind":"struct"},"207":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_double"],"kind":"function"},"1088":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"534":{"crate_id":1,"path":["std","os","linux","raw","arch","stat"],"kind":"struct"},"1415":{"crate_id":3,"path":["alloc","vec","peek_mut","PeekMut"],"kind":"struct"},"2296":{"crate_id":17,"path":["object","elf","CompressionHeader32"],"kind":"struct"},"2623":{"crate_id":1,"path":["std","pointer"],"kind":"primitive"},"861":{"crate_id":2,"path":["core","time","TryFromFloatSecsErrorKind"],"kind":"enum"},"1742":{"crate_id":5,"path":["libc","unix","DIR"],"kind":"enum"},"1188":{"crate_id":2,"path":["core","fmt","LowerHex"],"kind":"trait"},"307":{"crate_id":1,"path":["std","collections","hash","map","IntoValues"],"kind":"struct"},"2069":{"crate_id":17,"path":["object","common","SectionFlags"],"kind":"enum"},"634":{"crate_id":2,"path":["core","iter","adapters","copied","Copied"],"kind":"struct"},"1515":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_bd_ts"],"kind":"struct"},"2396":{"crate_id":17,"path":["object","pe","ImageRomOptionalHeader"],"kind":"struct"},"961":{"crate_id":2,"path":["core","iter","sources","repeat_with","RepeatWith"],"kind":"struct"},"1842":{"crate_id":16,"path":["gimli","common","DebugAddrBase"],"kind":"struct"},"1288":{"crate_id":2,"path":["core","core_simd","simd","num","uint","SimdUint"],"kind":"trait"},"407":{"crate_id":1,"path":["std","path","fmt","DebugHelper"],"kind":"struct"},"2169":{"crate_id":17,"path":["object","read","elf","version","Version"],"kind":"struct"},"734":{"crate_id":1,"path":["std","os","unix","fs","FileExt"],"kind":"trait"},"1615":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_barrierattr_t"],"kind":"struct"},"2496":{"crate_id":18,"path":["memchr","arch","all","memchr","ThreeIter"],"kind":"struct"},"1942":{"crate_id":16,"path":["gimli","read","cfi","UnwindExpression"],"kind":"struct"},"1061":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1388":{"crate_id":3,"path":["alloc","collections","btree","map","RangeMut"],"kind":"struct"},"507":{"crate_id":1,"path":["std","sys","os_str","bytes","Slice"],"kind":"struct"},"2269":{"crate_id":17,"path":["object","read","SectionIndex"],"kind":"struct"},"2596":{"crate_id":2,"path":["core","ptr","without_provenance"],"kind":"function"},"834":{"crate_id":2,"path":["core","char","convert","CharErrorKind"],"kind":"enum"},"1715":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","sigaction"],"kind":"struct"},"2042":{"crate_id":16,"path":["gimli","read","unit","DebuggingInformationEntry"],"kind":"struct"},"280":{"crate_id":2,"path":["core","fmt","Error"],"kind":"struct"},"1161":{"crate_id":2,"path":["core","sync","atomic","AtomicU32"],"kind":"struct"},"1488":{"crate_id":5,"path":["libc","unix","linux_like","statx_timestamp"],"kind":"struct"},"607":{"crate_id":2,"path":["core","iter","adapters","intersperse","Intersperse"],"kind":"struct"},"2369":{"crate_id":17,"path":["object","macho","FilesetEntryCommand"],"kind":"struct"},"934":{"crate_id":2,"path":["core","ptr","unique","Unique"],"kind":"struct"},"1815":{"crate_id":10,"path":["hashbrown","table","OccupiedEntry"],"kind":"struct"},"2142":{"crate_id":17,"path":["object","read","elf","segment","ElfSegmentIterator"],"kind":"struct"},"380":{"crate_id":1,"path":["std","io","Lines"],"kind":"struct"},"1261":{"crate_id":2,"path":["core","fmt","builders","PadAdapter"],"kind":"struct"},"707":{"crate_id":2,"path":["core","clone","CloneToUninit"],"kind":"trait"},"1588":{"crate_id":5,"path":["libc","unix","linux_like","linux","rlimit64"],"kind":"struct"},"2469":{"crate_id":17,"path":["object","xcoff","AuxHeader64"],"kind":"struct"},"1034":{"crate_id":2,"path":["core","future","ResumeTy"],"kind":"struct"},"1915":{"crate_id":16,"path":["gimli","read","addr","AddrHeader"],"kind":"struct"},"2242":{"crate_id":17,"path":["object","read","pe","relocation","Relocation"],"kind":"struct"},"480":{"crate_id":1,"path":["std","sync","poison","PoisonError"],"kind":"struct"},"1361":{"crate_id":3,"path":["alloc","collections","btree","merge_iter","Peeked"],"kind":"enum"},"807":{"crate_id":2,"path":["core","mem","manually_drop","ManuallyDrop"],"kind":"struct"},"1688":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","regex_t"],"kind":"struct"},"2569":{"crate_id":2,"path":["core","str","converts","from_utf8_unchecked"],"kind":"function"},"253":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_enabled"],"kind":"function"},"1134":{"crate_id":2,"path":["core","error","tags","Value"],"kind":"struct"},"2015":{"crate_id":16,"path":["gimli","read","op","EvaluationResult"],"kind":"enum"},"1461":{"crate_id":5,"path":["libc","unix","protoent"],"kind":"struct"},"580":{"crate_id":1,"path":["std","sys","backtrace","print","DisplayBacktrace"],"kind":"struct"},"2342":{"crate_id":17,"path":["object","macho","Section32"],"kind":"struct"},"907":{"crate_id":2,"path":["core","core_arch","simd","i64x4"],"kind":"struct"},"26":{"crate_id":2,"path":["core","convert","Into"],"kind":"trait"},"1788":{"crate_id":10,"path":["hashbrown","map","IntoValues"],"kind":"struct"},"2115":{"crate_id":17,"path":["object","read","archive","MemberHeader"],"kind":"enum"},"353":{"crate_id":1,"path":["std","io","buffered","linewriter","LineWriter"],"kind":"struct"},"1234":{"crate_id":2,"path":["core","iter","adapters","GenericShunt"],"kind":"struct"},"1561":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_vm"],"kind":"struct"},"680":{"crate_id":2,"path":["core","slice","iter","ChunkBy"],"kind":"struct"},"2442":{"crate_id":17,"path":["object","pe","ImageEpilogueDynamicRelocationHeader"],"kind":"struct"},"1007":{"crate_id":2,"path":["core","str","iter","EncodeUtf16"],"kind":"struct"},"1888":{"crate_id":16,"path":["gimli","constants","DwAccess"],"kind":"struct"},"2215":{"crate_id":17,"path":["object","read","macho","section","MachOSectionInternal"],"kind":"struct"},"453":{"crate_id":1,"path":["std","sync","once","Once"],"kind":"struct"},"1334":{"crate_id":3,"path":["alloc","sync","UniqueArcUninit"],"kind":"struct"},"1661":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_mutex_t"],"kind":"struct"},"780":{"crate_id":2,"path":["core","num","error","ParseIntError"],"kind":"struct"},"2542":{"crate_id":18,"path":["memchr","memmem","FindIter"],"kind":"struct"},"226":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_point_or_null"],"kind":"function"},"1107":{"crate_id":2,"path":["core","core_arch","x86","__m256i"],"kind":"struct"},"1988":{"crate_id":16,"path":["gimli","read","line","FileEntryFormat"],"kind":"struct"},"2315":{"crate_id":17,"path":["object","elf","Verneed"],"kind":"struct"},"553":{"crate_id":1,"path":["std","sync","mpmc","waker","Waker"],"kind":"struct"},"1434":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","feature_detect","Flags"],"kind":"struct"},"880":{"crate_id":2,"path":["core","core_arch","simd","i32x2"],"kind":"struct"},"1761":{"crate_id":10,"path":["hashbrown","raw","TableLayout"],"kind":"struct"},"2642":{"crate_id":1,"path":["std","isize"],"kind":"primitive"},"326":{"crate_id":1,"path":["std","env","VarsOs"],"kind":"struct"},"1207":{"crate_id":2,"path":["core","hint","select_unpredictable","DropOnPanic"],"kind":"struct"},"2088":{"crate_id":17,"path":["object","read","util","StringTable"],"kind":"struct"},"2415":{"crate_id":17,"path":["object","pe","ImageAuxSymbolCrc"],"kind":"struct"},"653":{"crate_id":2,"path":["core","ops","bit","BitXor"],"kind":"trait"},"1534":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_effect"],"kind":"struct"},"980":{"crate_id":2,"path":["core","hash","sip","Sip13Rounds"],"kind":"struct"},"99":{"crate_id":0,"path":["rustcalc","Describable"],"kind":"trait"},"1861":{"crate_id":16,"path":["gimli","common","DebugTypeSignature"],"kind":"struct"},"426":{"crate_id":1,"path":["std","random","DefaultRandomSource"],"kind":"struct"},"1307":{"crate_id":3,"path":["alloc","collections","binary_heap","DrainSorted"],"kind":"struct"},"2188":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheMappingIterator"],"kind":"struct"},"1634":{"crate_id":5,"path":["libc","unix","linux_like","linux","mnt_ns_info"],"kind":"struct"},"753":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt"],"kind":"trait"},"2515":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","OneIter"],"kind":"struct"},"1080":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"199":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_dispose"],"kind":"function"},"1961":{"crate_id":16,"path":["gimli","read","abbrev","AbbreviationsCache"],"kind":"struct"},"2288":{"crate_id":17,"path":["object","archive","AixHeader"],"kind":"struct"},"526":{"crate_id":1,"path":["std","thread","spawnhook","ChildSpawnHooks"],"kind":"struct"},"1407":{"crate_id":3,"path":["alloc","collections","btree","set","DifferenceInner"],"kind":"enum"},"1734":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","ptrace_rseq_configuration"],"kind":"struct"},"853":{"crate_id":2,"path":["core","fmt","DebugAsHex"],"kind":"enum"},"2615":{"crate_id":16,"path":["gimli"],"kind":"module"},"1180":{"crate_id":2,"path":["core","task","wake","ExtData"],"kind":"enum"},"299":{"crate_id":1,"path":["std","collections","hash","map","Entry"],"kind":"enum"},"2061":{"crate_id":17,"path":["object","common","SectionKind"],"kind":"enum"},"2388":{"crate_id":17,"path":["object","macho","ScatteredRelocationInfo"],"kind":"struct"},"626":{"crate_id":2,"path":["core","ops","try_trait","Try"],"kind":"trait"},"1507":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_hdr_variant1"],"kind":"struct"},"1834":{"crate_id":15,"path":["addr2line","DebugFile"],"kind":"enum"},"953":{"crate_id":2,"path":["core","iter","sources","empty","Empty"],"kind":"struct"},"399":{"crate_id":1,"path":["std","panic","BacktraceStyle"],"kind":"enum"},"1280":{"crate_id":2,"path":["core","core_simd","swizzle","resize","Resize"],"kind":"struct"},"2161":{"crate_id":17,"path":["object","read","elf","comdat","ElfComdatSectionIterator"],"kind":"struct"},"2488":{"crate_id":17,"path":["object","xcoff","Rel64"],"kind":"struct"},"726":{"crate_id":2,"path":["core","net","socket_addr","SocketAddrV4"],"kind":"struct"},"1607":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_pmkid_cand"],"kind":"struct"},"1934":{"crate_id":16,"path":["gimli","read","cfi","UnwindTable"],"kind":"struct"},"1053":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"499":{"crate_id":1,"path":["std","sys","fs","unix","OpenOptions"],"kind":"struct"},"1380":{"crate_id":3,"path":["alloc","collections","TryReserveErrorKind"],"kind":"enum"},"2261":{"crate_id":17,"path":["object","read","xcoff","comdat","XcoffComdat"],"kind":"struct"},"2588":{"crate_id":1,"path":["std","os","linux","fs","MetadataExt","st_atime"],"kind":"function"},"826":{"crate_id":2,"path":["core","ops","range","RangeTo"],"kind":"struct"},"1707":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","sifields_sigchld"],"kind":"struct"},"1153":{"crate_id":2,"path":["core","panicking","assert_matches_failed","Pattern"],"kind":"struct"},"272":{"crate_id":2,"path":["core","marker","MetaSized"],"kind":"trait"},"2034":{"crate_id":16,"path":["gimli","read","str","DebugStr"],"kind":"struct"},"599":{"crate_id":2,"path":["core","iter","traits","iterator","Iterator"],"kind":"trait"},"1480":{"crate_id":5,"path":["libc","unix","linux_like","arpreq"],"kind":"struct"},"2361":{"crate_id":17,"path":["object","macho","DylibModule64"],"kind":"struct"},"1807":{"crate_id":10,"path":["hashbrown","map","OccupiedError"],"kind":"struct"},"926":{"crate_id":2,"path":["core","core_arch","simd","i32x32"],"kind":"struct"},"1253":{"crate_id":2,"path":["core","ops","range","IntoBounds"],"kind":"trait"},"372":{"crate_id":1,"path":["std","io","util","Sink"],"kind":"struct"},"2134":{"crate_id":17,"path":["object","read","coff","comdat","CoffComdatIterator"],"kind":"struct"},"2461":{"crate_id":17,"path":["object","pe","ImageSeparateDebugHeader"],"kind":"struct"},"699":{"crate_id":2,"path":["core","slice","GetDisjointMutIndex"],"kind":"trait"},"1580":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_extts_event"],"kind":"struct"},"1907":{"crate_id":16,"path":["gimli","constants","DwEhPe"],"kind":"struct"},"145":{"crate_id":2,"path":["core","option","Option"],"kind":"enum"},"1026":{"crate_id":2,"path":["core","str","IsAsciiWhitespace"],"kind":"struct"},"1353":{"crate_id":3,"path":["alloc","collections","binary_heap","IntoIter"],"kind":"struct"},"472":{"crate_id":1,"path":["std","sync","poison","mutex","Mutex"],"kind":"struct"},"2234":{"crate_id":17,"path":["object","read","pe","import","ImportTable"],"kind":"struct"},"2561":{"crate_id":1,"path":["std","fs","exists"],"kind":"function"},"799":{"crate_id":2,"path":["core","num","niche_types","NonZeroIsizeInner"],"kind":"struct"},"1680":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","termios"],"kind":"struct"},"2007":{"crate_id":16,"path":["gimli","read","macros","MacroIter"],"kind":"struct"},"245":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_unit"],"kind":"function"},"1126":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"572":{"crate_id":2,"path":["core","marker","StructuralPartialEq"],"kind":"trait"},"1453":{"crate_id":5,"path":["libc","unix","iovec"],"kind":"struct"},"2334":{"crate_id":17,"path":["object","macho","FatArch32"],"kind":"struct"},"18":{"crate_id":2,"path":["core","marker","Sized"],"kind":"trait"},"899":{"crate_id":2,"path":["core","core_arch","simd","m64x2"],"kind":"struct"},"1780":{"crate_id":10,"path":["hashbrown","table","HashTable"],"kind":"struct"},"2107":{"crate_id":17,"path":["object","read","any","DynamicRelocationIterator"],"kind":"struct"},"345":{"crate_id":1,"path":["std","fs","FileType"],"kind":"struct"},"1226":{"crate_id":2,"path":["core","ops","deref","DerefPure"],"kind":"trait"},"672":{"crate_id":2,"path":["core","slice","iter","ChunksMut"],"kind":"struct"},"1553":{"crate_id":5,"path":["libc","unix","linux_like","linux","posix_spawnattr_t"],"kind":"struct"},"2434":{"crate_id":17,"path":["object","pe","ImageResourceDataEntry"],"kind":"struct"},"999":{"crate_id":2,"path":["core","str","iter","MatchesInternal"],"kind":"struct"},"1880":{"crate_id":16,"path":["gimli","constants","DwChildren"],"kind":"struct"},"1326":{"crate_id":3,"path":["alloc","rc","from_iter_exact","Guard"],"kind":"struct"},"445":{"crate_id":1,"path":["std","sync","mpsc","RecvError"],"kind":"struct"},"2207":{"crate_id":17,"path":["object","read","macho","load_command","LoadCommandIterator"],"kind":"struct"},"772":{"crate_id":2,"path":["core","num","dec2flt","decimal_seq","DecimalSeq"],"kind":"struct"},"1653":{"crate_id":5,"path":["libc","unix","linux_like","linux","ifconf"],"kind":"struct"},"2534":{"crate_id":18,"path":["memchr","memmem","searcher","SearcherKind"],"kind":"union"},"1980":{"crate_id":16,"path":["gimli","read","line","LineInstructions"],"kind":"struct"},"218":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_divide_or_null"],"kind":"function"},"1099":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1426":{"crate_id":3,"path":["alloc","string","ToString"],"kind":"trait"},"545":{"crate_id":1,"path":["std","backtrace_rs","print","PrintFmt"],"kind":"enum"},"2307":{"crate_id":17,"path":["object","elf","Relr64"],"kind":"struct"},"872":{"crate_id":2,"path":["core","core_arch","simd","i8x4"],"kind":"struct"},"1753":{"crate_id":8,"path":["miniz_oxide","MZError"],"kind":"enum"},"2634":{"crate_id":1,"path":["std","i32"],"kind":"primitive"},"2080":{"crate_id":17,"path":["object","endian","I64Bytes"],"kind":"struct"},"318":{"crate_id":2,"path":["core","hash","BuildHasher"],"kind":"trait"},"1199":{"crate_id":2,"path":["core","ops","arith","MulAssign"],"kind":"trait"},"1526":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_replay"],"kind":"struct"},"645":{"crate_id":1,"path":["std","os","unix","net","ancillary","ScmRights"],"kind":"struct"},"2407":{"crate_id":17,"path":["object","pe","ImageSymbolBytes"],"kind":"struct"},"972":{"crate_id":2,"path":["core","fmt","rt","Count"],"kind":"enum"},"1853":{"crate_id":16,"path":["gimli","common","RawRangeListsOffset"],"kind":"struct"},"2180":{"crate_id":17,"path":["object","read","elf","attributes","AttributeIndexIterator"],"kind":"struct"},"418":{"crate_id":1,"path":["std","process","Command"],"kind":"struct"},"1299":{"crate_id":2,"path":["core","core_simd","simd","cmp","ord","SimdPartialOrd"],"kind":"trait"},"1626":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_umem_reg_v1"],"kind":"struct"},"745":{"crate_id":1,"path":["std","sys","net","connection","socket","unix","Socket"],"kind":"struct"},"2507":{"crate_id":18,"path":["memchr","arch","all","twoway","SuffixOrdering"],"kind":"enum"},"191":{"crate_id":0,"path":["rustcalc","sum_all"],"kind":"function"},"1072":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1953":{"crate_id":16,"path":["gimli","read","endian_slice","EndianSlice"],"kind":"struct"},"2280":{"crate_id":17,"path":["object","read","RelocationTarget"],"kind":"enum"},"518":{"crate_id":1,"path":["std","alloc","System"],"kind":"struct"},"1399":{"crate_id":3,"path":["alloc","collections","btree","map","entry","OccupiedEntry"],"kind":"struct"},"845":{"crate_id":2,"path":["core","range","Range"],"kind":"struct"},"1726":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","_libc_fpxreg"],"kind":"struct"},"2607":{"crate_id":8,"path":["miniz_oxide"],"kind":"module"},"291":{"crate_id":1,"path":["std","backtrace","Backtrace"],"kind":"struct"},"1172":{"crate_id":2,"path":["core","cell","Ref"],"kind":"struct"},"2053":{"crate_id":16,"path":["gimli","read","value","ValueType"],"kind":"enum"},"1499":{"crate_id":5,"path":["libc","unix","linux_like","linux","signalfd_siginfo"],"kind":"struct"},"618":{"crate_id":2,"path":["core","iter","adapters","take","Take"],"kind":"struct"},"2380":{"crate_id":17,"path":["object","macho","EntryPointCommand"],"kind":"struct"},"945":{"crate_id":2,"path":["core","char","ToLowercase"],"kind":"struct"},"1826":{"crate_id":13,"path":["rustc_demangle","v0","ParseError"],"kind":"enum"},"391":{"crate_id":1,"path":["std","os","unix","net","listener","UnixListener"],"kind":"struct"},"1272":{"crate_id":2,"path":["core","core_simd","swizzle","rotate_elements_left","Rotate"],"kind":"struct"},"2153":{"crate_id":17,"path":["object","read","elf","relocation","ElfSectionRelocationIterator"],"kind":"struct"},"1599":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_point"],"kind":"struct"},"718":{"crate_id":1,"path":["std","sys","stdio","unix","Stdout"],"kind":"struct"},"2480":{"crate_id":17,"path":["object","xcoff","FunAux64"],"kind":"struct"},"1045":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"164":{"crate_id":3,"path":["alloc","vec","Vec"],"kind":"struct"},"1926":{"crate_id":16,"path":["gimli","read","cfi","CfiEntriesIter"],"kind":"struct"},"2253":{"crate_id":17,"path":["object","read","xcoff","section","SectionTable"],"kind":"struct"},"491":{"crate_id":1,"path":["std","sys","args","common","Args"],"kind":"struct"},"1372":{"crate_id":3,"path":["alloc","collections","btree","set","Union"],"kind":"struct"},"1699":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","tcp_info"],"kind":"struct"},"818":{"crate_id":2,"path":["core","marker","variance","PhantomCovariant"],"kind":"struct"},"2580":{"crate_id":2,"path":["core","ptr","metadata","metadata"],"kind":"function"},"264":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_Partial_get_value"],"kind":"function"},"1145":{"crate_id":2,"path":["core","ffi","c_void"],"kind":"enum"},"2026":{"crate_id":16,"path":["gimli","read","rnglists","DebugRngLists"],"kind":"struct"},"2353":{"crate_id":17,"path":["object","macho","DylinkerCommand"],"kind":"struct"},"591":{"crate_id":3,"path":["alloc","rc","Rc"],"kind":"struct"},"1472":{"crate_id":5,"path":["libc","unix","linux_like","fd_set"],"kind":"struct"},"1799":{"crate_id":10,"path":["hashbrown","table","IntoIter"],"kind":"struct"},"918":{"crate_id":2,"path":["core","core_arch","simd","i32x16"],"kind":"struct"},"364":{"crate_id":1,"path":["std","io","stdio","Stdin"],"kind":"struct"},"1245":{"crate_id":2,"path":["core","ops","try_trait","FromResidual"],"kind":"trait"},"2126":{"crate_id":17,"path":["object","read","coff","section","CoffSectionIterator"],"kind":"struct"},"2453":{"crate_id":17,"path":["object","pe","ImageEnclaveConfig32"],"kind":"struct"},"691":{"crate_id":2,"path":["core","slice","iter","RSplitNMut"],"kind":"struct"},"1572":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ifru_map"],"kind":"struct"},"1018":{"crate_id":2,"path":["core","str","pattern","StrSearcherImpl"],"kind":"enum"},"1899":{"crate_id":16,"path":["gimli","constants","DwDefaulted"],"kind":"struct"},"464":{"crate_id":1,"path":["std","sync","nonpoison","mutex","MutexGuard"],"kind":"struct"},"1345":{"crate_id":3,"path":["alloc","vec","dedup_by","FillGapOnDrop"],"kind":"struct"},"2226":{"crate_id":17,"path":["object","read","pe","section","PeSegment"],"kind":"struct"},"1672":{"crate_id":5,"path":["libc","unix","linux_like","linux","xsk_tx_metadata"],"kind":"struct"},"791":{"crate_id":2,"path":["core","num","niche_types","NonZeroI8Inner"],"kind":"struct"},"2553":{"crate_id":1,"path":["std","fs","metadata"],"kind":"function"},"1118":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"237":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_delayed_add"],"kind":"function"},"1999":{"crate_id":16,"path":["gimli","read","lookup","LookupEntryIter"],"kind":"struct"},"564":{"crate_id":1,"path":["std","sys","sync","once_box","OnceBox"],"kind":"struct"},"1445":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","can_filter"],"kind":"struct"},"2326":{"crate_id":17,"path":["object","macho","DyldCacheSlideInfo2"],"kind":"struct"},"1772":{"crate_id":10,"path":["hashbrown","map","Keys"],"kind":"struct"},"891":{"crate_id":2,"path":["core","core_arch","simd","i64x2"],"kind":"struct"},"1218":{"crate_id":2,"path":["core","slice","sort","shared","smallsort","CopyOnDrop"],"kind":"struct"},"337":{"crate_id":1,"path":["std","fs","ReadDir"],"kind":"struct"},"2099":{"crate_id":17,"path":["object","read","any","Comdat"],"kind":"struct"},"2426":{"crate_id":17,"path":["object","pe","ImageImportDescriptor"],"kind":"struct"},"664":{"crate_id":2,"path":["core","ascii","ascii_char","AsciiChar"],"kind":"enum"},"1545":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Shdr"],"kind":"struct"},"1872":{"crate_id":16,"path":["gimli","arch","RiscV"],"kind":"struct"},"991":{"crate_id":2,"path":["core","str","iter","SplitTerminator"],"kind":"struct"},"1318":{"crate_id":3,"path":["alloc","collections","vec_deque","into_iter","try_fold","Guard"],"kind":"struct"},"437":{"crate_id":1,"path":["std","sync","mpmc","Sender"],"kind":"struct"},"2199":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorV5"],"kind":"struct"},"2526":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","ThreeIter"],"kind":"struct"},"764":{"crate_id":2,"path":["core","alloc","global","GlobalAlloc"],"kind":"trait"},"1645":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_alg"],"kind":"struct"},"1972":{"crate_id":16,"path":["gimli","read","index","DebugTuIndex"],"kind":"struct"},"210":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_byte"],"kind":"function"},"1091":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"537":{"crate_id":1,"path":["std","sys","personality","dwarf","eh","EHContext"],"kind":"struct"},"1418":{"crate_id":3,"path":["alloc","collections","btree","append","MergeIter"],"kind":"struct"},"2299":{"crate_id":17,"path":["object","elf","Sym64"],"kind":"struct"},"2626":{"crate_id":1,"path":["std","str"],"kind":"primitive"},"864":{"crate_id":2,"path":["core","task","poll","Poll"],"kind":"enum"},"1745":{"crate_id":7,"path":["unwind","libunwind","_Unwind_Reason_Code"],"kind":"enum"},"1191":{"crate_id":2,"path":["core","fmt","UpperExp"],"kind":"trait"},"310":{"crate_id":1,"path":["std","collections","hash","set","HashSet"],"kind":"struct"},"2072":{"crate_id":17,"path":["object","endian","Endianness"],"kind":"enum"},"637":{"crate_id":2,"path":["core","iter","adapters","array_chunks","ArrayChunks"],"kind":"struct"},"1518":{"crate_id":5,"path":["libc","unix","linux_like","linux","if_nameindex"],"kind":"struct"},"2399":{"crate_id":17,"path":["object","pe","ImageNtHeaders32"],"kind":"struct"},"964":{"crate_id":2,"path":["core","option","Item"],"kind":"struct"},"1845":{"crate_id":16,"path":["gimli","common","DebugInfoOffset"],"kind":"struct"},"1291":{"crate_id":2,"path":["core","ptr","metadata","Pointee"],"kind":"trait"},"410":{"crate_id":1,"path":["std","path","StripPrefixError"],"kind":"struct"},"2172":{"crate_id":17,"path":["object","read","elf","version","VerdauxIterator"],"kind":"struct"},"737":{"crate_id":1,"path":["std","os","unix","fs","MetadataExt"],"kind":"trait"},"1618":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_pin_desc"],"kind":"struct"},"2499":{"crate_id":18,"path":["memchr","arch","all","rabinkarp","Finder"],"kind":"struct"},"1945":{"crate_id":16,"path":["gimli","read","dwarf","DwarfSections"],"kind":"struct"},"1064":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1391":{"crate_id":3,"path":["alloc","collections","btree","map","IntoValues"],"kind":"struct"},"510":{"crate_id":1,"path":["std","sys","process","unix","common","ProgramKind"],"kind":"enum"},"2272":{"crate_id":17,"path":["object","read","SymbolMap"],"kind":"struct"},"2599":{"crate_id":2,"path":["core","ptr","with_exposed_provenance_mut"],"kind":"function"},"837":{"crate_id":2,"path":["core","char","TryFromCharError"],"kind":"struct"},"1718":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","flock64"],"kind":"struct"},"2045":{"crate_id":16,"path":["gimli","read","unit","AttrsIter"],"kind":"struct"},"283":{"crate_id":1,"path":["std","thread","scoped","ScopedJoinHandle"],"kind":"struct"},"1164":{"crate_id":2,"path":["core","sync","atomic","AtomicIsize"],"kind":"struct"},"1491":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr_storage"],"kind":"struct"},"610":{"crate_id":2,"path":["core","iter","adapters","filter","Filter"],"kind":"struct"},"2372":{"crate_id":17,"path":["object","macho","VersionMinCommand"],"kind":"struct"},"937":{"crate_id":2,"path":["core","array","TryFromSliceError"],"kind":"struct"},"1818":{"crate_id":10,"path":["hashbrown","scopeguard","ScopeGuard"],"kind":"struct"},"2145":{"crate_id":17,"path":["object","read","elf","section","ElfSectionIterator"],"kind":"struct"},"383":{"crate_id":1,"path":["std","net","tcp","TcpStream"],"kind":"struct"},"1264":{"crate_id":2,"path":["core","random","Distribution"],"kind":"trait"},"710":{"crate_id":2,"path":["core","str","traits","FromStr"],"kind":"trait"},"1591":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aes_gcm_256"],"kind":"struct"},"2472":{"crate_id":17,"path":["object","xcoff","SymbolBytes"],"kind":"struct"},"1037":{"crate_id":2,"path":["core","escape","MaybeEscapedCharacter"],"kind":"union"},"1918":{"crate_id":16,"path":["gimli","read","cfi","EhFrameHdr"],"kind":"struct"},"1364":{"crate_id":3,"path":["alloc","collections","btree","navigate","LazyLeafHandle"],"kind":"enum"},"483":{"crate_id":1,"path":["std","time","SystemTimeError"],"kind":"struct"},"2245":{"crate_id":17,"path":["object","read","pe","resource","ResourceDirectoryEntryData"],"kind":"enum"},"810":{"crate_id":2,"path":["core","ptr","alignment","Alignment"],"kind":"struct"},"1691":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","seminfo"],"kind":"struct"},"2572":{"crate_id":2,"path":["core","fmt","Result"],"kind":"type_alias"},"256":{"crate_id":0,"path":["rustcalc","rustcalc_Operation_count"],"kind":"function"},"1137":{"crate_id":2,"path":["core","ops","try_trait","Yeet"],"kind":"struct"},"2018":{"crate_id":16,"path":["gimli","read","op","Evaluation"],"kind":"struct"},"1464":{"crate_id":5,"path":["libc","unix","linux_like","ip_mreq"],"kind":"struct"},"583":{"crate_id":1,"path":["std","panicking","begin_panic","Payload"],"kind":"struct"},"2345":{"crate_id":17,"path":["object","macho","FvmlibCommand"],"kind":"struct"},"910":{"crate_id":2,"path":["core","core_arch","simd","f64x4"],"kind":"struct"},"1791":{"crate_id":10,"path":["hashbrown","map","IntoIter"],"kind":"struct"},"2118":{"crate_id":17,"path":["object","read","archive","ArchiveSymbolIterator"],"kind":"struct"},"356":{"crate_id":1,"path":["std","io","cursor","Cursor"],"kind":"struct"},"1237":{"crate_id":2,"path":["core","str","pattern","ReverseSearcher"],"kind":"trait"},"1564":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_data"],"kind":"struct"},"683":{"crate_id":2,"path":["core","slice","iter","SplitMut"],"kind":"struct"},"2445":{"crate_id":17,"path":["object","pe","ImageHotPatchInfo"],"kind":"struct"},"1010":{"crate_id":2,"path":["core","str","iter","EscapeUnicode"],"kind":"struct"},"1891":{"crate_id":16,"path":["gimli","constants","DwLang"],"kind":"struct"},"2218":{"crate_id":17,"path":["object","read","macho","symbol","MachOSymbolIterator"],"kind":"struct"},"456":{"crate_id":1,"path":["std","sync","barrier","BarrierWaitResult"],"kind":"struct"},"1337":{"crate_id":3,"path":["alloc","vec","splice","Splice"],"kind":"struct"},"1664":{"crate_id":5,"path":["libc","unix","linux_like","linux","sock_txtime"],"kind":"struct"},"783":{"crate_id":2,"path":["core","num","saturating","Saturating"],"kind":"struct"},"2545":{"crate_id":18,"path":["memchr","memmem","FinderRev"],"kind":"struct"},"229":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_point"],"kind":"function"},"1110":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1991":{"crate_id":16,"path":["gimli","read","loclists","DebugLocLists"],"kind":"struct"},"2318":{"crate_id":17,"path":["object","elf","NoteHeader64"],"kind":"struct"},"556":{"crate_id":1,"path":["std","sys","pal","unix","stack_overflow","Handler"],"kind":"struct"},"1437":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","j1939","j1939_filter"],"kind":"struct"},"883":{"crate_id":2,"path":["core","core_arch","simd","f64x1"],"kind":"struct"},"2":{"crate_id":0,"path":["rustcalc","Operation","Multiply"],"kind":"variant"},"1764":{"crate_id":10,"path":["hashbrown","raw","Bucket"],"kind":"struct"},"2645":{"crate_id":1,"path":["std","fn"],"kind":"primitive"},"329":{"crate_id":1,"path":["std","env","JoinPathsError"],"kind":"struct"},"1210":{"crate_id":2,"path":["core","array","drain","Drain"],"kind":"struct"},"2091":{"crate_id":17,"path":["object","read","any","SegmentIteratorInternal"],"kind":"enum"},"2418":{"crate_id":17,"path":["object","pe","ImageBaseRelocation"],"kind":"struct"},"656":{"crate_id":1,"path":["std","error","Indented"],"kind":"struct"},"1537":{"crate_id":5,"path":["libc","unix","linux_like","linux","uinput_abs_setup"],"kind":"struct"},"983":{"crate_id":2,"path":["core","slice","ascii","EscapeByte"],"kind":"struct"},"102":{"crate_id":0,"path":["rustcalc","Calculator"],"kind":"struct"},"1864":{"crate_id":16,"path":["gimli","common","UnitSectionOffset"],"kind":"enum"},"429":{"crate_id":1,"path":["std","sync","mpmc","context","Inner"],"kind":"struct"},"1310":{"crate_id":3,"path":["alloc","collections","btree","map","IntoIter"],"kind":"struct"},"2191":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheSlideInfo"],"kind":"enum"},"1637":{"crate_id":5,"path":["libc","unix","linux_like","linux","dmabuf_token"],"kind":"struct"},"756":{"crate_id":1,"path":["std","os","net","linux_ext","tcp","TcpStreamExt"],"kind":"trait"},"2518":{"crate_id":18,"path":["memchr","arch","x86_64","avx2","memchr","Three"],"kind":"struct"},"1083":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"202":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_multiply"],"kind":"function"},"1964":{"crate_id":16,"path":["gimli","read","abbrev","Attributes"],"kind":"enum"},"2291":{"crate_id":17,"path":["object","elf","FileHeader32"],"kind":"struct"},"529":{"crate_id":1,"path":["std","panicking","Hook"],"kind":"enum"},"1410":{"crate_id":3,"path":["alloc","collections","btree","set","CursorMut"],"kind":"struct"},"1737":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","ucontext_t"],"kind":"struct"},"856":{"crate_id":2,"path":["core","str","error","ParseBoolError"],"kind":"struct"},"2618":{"crate_id":19,"path":["panic_unwind"],"kind":"module"},"1183":{"crate_id":2,"path":["core","ptr","metadata","Thin"],"kind":"trait_alias"},"302":{"crate_id":1,"path":["std","collections","hash","map","OccupiedError"],"kind":"struct"},"2064":{"crate_id":17,"path":["object","common","SymbolScope"],"kind":"enum"},"2391":{"crate_id":17,"path":["object","pe","ImageVxdHeader"],"kind":"struct"},"629":{"crate_id":2,"path":["core","iter","traits","double_ended","DoubleEndedIterator"],"kind":"trait"},"1510":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_req3"],"kind":"struct"},"1837":{"crate_id":16,"path":["gimli","common","Encoding"],"kind":"struct"},"956":{"crate_id":2,"path":["core","iter","sources","once","Once"],"kind":"struct"},"402":{"crate_id":1,"path":["std","path","PrefixComponent"],"kind":"struct"},"1283":{"crate_id":2,"path":["core","core_simd","cast","SimdCast"],"kind":"trait"},"2164":{"crate_id":17,"path":["object","read","elf","note","GnuPropertyIterator"],"kind":"struct"},"2491":{"crate_id":18,"path":["memchr","arch","all","memchr","One"],"kind":"struct"},"729":{"crate_id":2,"path":["core","net","ip_addr","Ipv4Addr"],"kind":"struct"},"1610":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_priv_args"],"kind":"struct"},"1056":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1937":{"crate_id":16,"path":["gimli","read","cfi","UnwindTableRow"],"kind":"struct"},"502":{"crate_id":1,"path":["std","sys","fs","unix","Mode"],"kind":"struct"},"1383":{"crate_id":3,"path":["alloc","ffi","c_str","IntoStringError"],"kind":"struct"},"2264":{"crate_id":17,"path":["object","read","xcoff","segment","XcoffSegment"],"kind":"struct"},"2591":{"crate_id":2,"path":["core","intrinsics","disjoint_bitor"],"kind":"function"},"829":{"crate_id":2,"path":["core","ops","range","Bound"],"kind":"enum"},"1710":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","sigset_t"],"kind":"struct"},"1156":{"crate_id":2,"path":["core","sync","atomic","AtomicI8"],"kind":"struct"},"275":{"crate_id":1,"path":["std","panic","catch_unwind"],"kind":"function"},"2037":{"crate_id":16,"path":["gimli","read","UnitOffset"],"kind":"struct"},"602":{"crate_id":2,"path":["core","array","iter","IntoIter"],"kind":"struct"},"1483":{"crate_id":5,"path":["libc","unix","linux_like","mmsghdr"],"kind":"struct"},"2364":{"crate_id":17,"path":["object","macho","TwolevelHint"],"kind":"struct"},"1810":{"crate_id":10,"path":["hashbrown","rustc_entry","RustcVacantEntry"],"kind":"struct"},"929":{"crate_id":2,"path":["core","core_simd","masks","mask_impl","Mask"],"kind":"struct"},"1256":{"crate_id":2,"path":["core","async_iter","async_iter","IntoAsyncIterator"],"kind":"trait"},"375":{"crate_id":1,"path":["std","io","SeekFrom"],"kind":"enum"},"2137":{"crate_id":17,"path":["object","read","coff","import","ImportFile"],"kind":"struct"},"2464":{"crate_id":17,"path":["object","pe","ImportObjectHeader"],"kind":"struct"},"702":{"crate_id":2,"path":["core","alloc","Allocator"],"kind":"trait"},"1583":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_sndinfo"],"kind":"struct"},"1910":{"crate_id":16,"path":["gimli","endianity","BigEndian"],"kind":"struct"},"1029":{"crate_id":2,"path":["core","str","UnsafeBytesToStr"],"kind":"struct"},"1356":{"crate_id":3,"path":["alloc","collections","btree","map","Keys"],"kind":"struct"},"475":{"crate_id":1,"path":["std","sync","poison","rwlock","RwLock"],"kind":"struct"},"2237":{"crate_id":17,"path":["object","read","pe","import","Import"],"kind":"enum"},"2564":{"crate_id":2,"path":["core","mem","forget"],"kind":"function"},"802":{"crate_id":2,"path":["core","num","niche_types","U64NotAllOnes"],"kind":"struct"},"1683":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","nl_pktinfo"],"kind":"struct"},"2010":{"crate_id":16,"path":["gimli","read","op","OperationEvaluationResult"],"kind":"enum"},"248":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_current"],"kind":"function"},"1129":{"crate_id":2,"path":["core","core_arch","x86","__m512h"],"kind":"struct"},"575":{"crate_id":1,"path":["std","sys","thread","unix","cgroups","Cgroup"],"kind":"enum"},"1456":{"crate_id":5,"path":["libc","unix","linger"],"kind":"struct"},"2337":{"crate_id":17,"path":["object","macho","MachHeader64"],"kind":"struct"},"902":{"crate_id":2,"path":["core","core_arch","simd","u32x8"],"kind":"struct"},"1783":{"crate_id":10,"path":["hashbrown","TryReserveError"],"kind":"enum"},"2110":{"crate_id":17,"path":["object","read","any","SectionRelocationIteratorInternal"],"kind":"enum"},"348":{"crate_id":1,"path":["std","hash","random","RandomState"],"kind":"struct"},"1229":{"crate_id":2,"path":["core","pin","PinCoerceUnsized"],"kind":"trait"},"675":{"crate_id":2,"path":["core","slice","iter","ArrayWindows"],"kind":"struct"},"1556":{"crate_id":5,"path":["libc","unix","linux_like","linux","arpd_request"],"kind":"struct"},"2437":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation32"],"kind":"struct"},"1002":{"crate_id":2,"path":["core","str","iter","Lines"],"kind":"struct"},"1883":{"crate_id":16,"path":["gimli","constants","DwForm"],"kind":"struct"},"1329":{"crate_id":3,"path":["alloc","slice","to_vec_in","to_vec","DropGuard"],"kind":"struct"},"448":{"crate_id":1,"path":["std","sync","mpsc","Sender"],"kind":"struct"},"2210":{"crate_id":17,"path":["object","read","macho","segment","MachOSegmentIterator"],"kind":"struct"},"775":{"crate_id":2,"path":["core","num","flt2dec","decoder","Decoded"],"kind":"struct"},"1656":{"crate_id":5,"path":["libc","unix","linux_like","linux","sched_attr"],"kind":"struct"},"2537":{"crate_id":18,"path":["memchr","memmem","searcher","SearcherRevKind"],"kind":"enum"},"1983":{"crate_id":16,"path":["gimli","read","line","LineSequence"],"kind":"struct"},"221":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_to_long_or_null"],"kind":"function"},"1102":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1429":{"crate_id":4,"path":["compiler_builtins","int","big","i256"],"kind":"struct"},"548":{"crate_id":1,"path":["std","thread","Packet"],"kind":"struct"},"2310":{"crate_id":17,"path":["object","elf","Dyn32"],"kind":"struct"},"2637":{"crate_id":1,"path":["std","u8"],"kind":"primitive"},"875":{"crate_id":2,"path":["core","core_arch","simd","u16x4"],"kind":"struct"},"1756":{"crate_id":9,"path":["adler2","Adler32"],"kind":"struct"},"2083":{"crate_id":17,"path":["object","read","read_cache","ReadCacheRange"],"kind":"struct"},"321":{"crate_id":1,"path":["std","collections","hash","set","Union"],"kind":"struct"},"1202":{"crate_id":2,"path":["core","ops","bit","BitAndAssign"],"kind":"trait"},"1529":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_constant_effect"],"kind":"struct"},"648":{"crate_id":1,"path":["std","os","unix","net","ancillary","AncillaryData"],"kind":"enum"},"2410":{"crate_id":17,"path":["object","pe","ImageAuxSymbolTokenDef"],"kind":"struct"},"975":{"crate_id":2,"path":["core","hash","sip","SipHasher13"],"kind":"struct"},"1856":{"crate_id":16,"path":["gimli","common","DebugRngListsIndex"],"kind":"struct"},"2183":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldSubCacheSlice"],"kind":"enum"},"421":{"crate_id":1,"path":["std","process","Output"],"kind":"struct"},"1302":{"crate_id":3,"path":["alloc","boxed","thin","ThinBox"],"kind":"struct"},"748":{"crate_id":1,"path":["std","os","fd","owned","AsFd"],"kind":"trait"},"1629":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_options"],"kind":"struct"},"2510":{"crate_id":18,"path":["memchr","arch","generic","memchr","Two"],"kind":"struct"},"194":{"crate_id":0,"path":["rustcalc","rustcalc_kne_getLastError"],"kind":"function"},"1075":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1956":{"crate_id":16,"path":["gimli","read","endian_slice","DebugLen"],"kind":"struct"},"2283":{"crate_id":17,"path":["object","read","RelocationMapEntry"],"kind":"struct"},"521":{"crate_id":1,"path":["std","backtrace_rs","symbolize","Symbol"],"kind":"struct"},"1402":{"crate_id":3,"path":["alloc","collections","btree","map","CursorMut"],"kind":"struct"},"848":{"crate_id":2,"path":["core","range","RangeToInclusive"],"kind":"struct"},"1729":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","user_regs_struct"],"kind":"struct"},"2610":{"crate_id":11,"path":["rustc_std_workspace_alloc"],"kind":"module"},"294":{"crate_id":1,"path":["std","backtrace","BytesOrWide"],"kind":"enum"},"1175":{"crate_id":2,"path":["core","cell","SyncUnsafeCell"],"kind":"struct"},"2056":{"crate_id":16,"path":["gimli","read","Error"],"kind":"enum"},"1502":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanout_args"],"kind":"struct"},"621":{"crate_id":2,"path":["core","iter","adapters","flatten","Flatten"],"kind":"struct"},"2383":{"crate_id":17,"path":["object","macho","NoteCommand"],"kind":"struct"},"948":{"crate_id":2,"path":["core","ffi","c_str","Bytes"],"kind":"struct"},"1829":{"crate_id":13,"path":["rustc_demangle","SizeLimitedFmtAdapter"],"kind":"struct"},"2156":{"crate_id":17,"path":["object","read","elf","relocation","CrelIteratorHeader"],"kind":"struct"},"394":{"crate_id":1,"path":["std","os","unix","net","ucred","UCred"],"kind":"struct"},"1275":{"crate_id":2,"path":["core","core_simd","swizzle","shift_elements_right","Shift"],"kind":"struct"},"1602":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_discarded"],"kind":"struct"},"721":{"crate_id":1,"path":["std","io","stdio","IsTerminal"],"kind":"trait"},"2483":{"crate_id":17,"path":["object","xcoff","BlockAux64"],"kind":"struct"},"1048":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1929":{"crate_id":16,"path":["gimli","read","cfi","AugmentationData"],"kind":"struct"},"2256":{"crate_id":17,"path":["object","read","xcoff","symbol","XcoffSymbolTable"],"kind":"struct"},"494":{"crate_id":1,"path":["std","sys","fd","unix","FileDesc"],"kind":"struct"},"1375":{"crate_id":3,"path":["alloc","collections","linked_list","Iter"],"kind":"struct"},"1702":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","sem_t"],"kind":"struct"},"821":{"crate_id":2,"path":["core","marker","PhantomData"],"kind":"struct"},"2583":{"crate_id":2,"path":["core","format_args"],"kind":"macro"},"267":{"crate_id":0,"path":["rustcalc","rustcalc_compute"],"kind":"function"},"1148":{"crate_id":2,"path":["core","option","IterMut"],"kind":"struct"},"2029":{"crate_id":16,"path":["gimli","read","rnglists","RawRngListIter"],"kind":"struct"},"2356":{"crate_id":17,"path":["object","macho","RoutinesCommand64"],"kind":"struct"},"594":{"crate_id":1,"path":["std","sys","pal","unix","weak","dlsym","DlsymWeak"],"kind":"struct"},"1475":{"crate_id":5,"path":["libc","unix","linux_like","Dl_info"],"kind":"struct"},"1802":{"crate_id":10,"path":["hashbrown","map","Entry"],"kind":"enum"},"40":{"crate_id":2,"path":["core","any","TypeId"],"kind":"struct"},"921":{"crate_id":2,"path":["core","core_arch","simd","f32x16"],"kind":"struct"},"367":{"crate_id":1,"path":["std","io","stdio","StdoutLock"],"kind":"struct"},"1248":{"crate_id":2,"path":["core","ops","deref","Receiver"],"kind":"trait"},"2129":{"crate_id":17,"path":["object","read","coff","symbol","SymbolIterator"],"kind":"struct"},"2456":{"crate_id":17,"path":["object","pe","ImageDebugDirectory"],"kind":"struct"},"694":{"crate_id":2,"path":["core","core_simd","vector","SimdElement"],"kind":"trait"},"1575":{"crate_id":5,"path":["libc","unix","linux_like","linux","open_how"],"kind":"struct"},"1021":{"crate_id":2,"path":["core","str","LinesMap"],"kind":"struct"},"1902":{"crate_id":16,"path":["gimli","constants","DwLnct"],"kind":"struct"},"467":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","RwLockReadGuard"],"kind":"struct"},"1348":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Immut"],"kind":"struct"},"2229":{"crate_id":17,"path":["object","read","pe","section","PeRelocationIterator"],"kind":"struct"},"1675":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__exit_status"],"kind":"struct"},"794":{"crate_id":2,"path":["core","num","niche_types","NonZeroI64Inner"],"kind":"struct"},"2556":{"crate_id":1,"path":["std","path","absolute"],"kind":"function"},"1121":{"crate_id":2,"path":["core","core_arch","x86","__m256bh"],"kind":"struct"},"240":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_delayed_noop"],"kind":"function"},"2002":{"crate_id":16,"path":["gimli","read","macros","DebugMacinfo"],"kind":"struct"},"2329":{"crate_id":17,"path":["object","macho","DyldCacheSlideInfo5"],"kind":"struct"},"567":{"crate_id":1,"path":["std","backtrace_rs","backtrace","libunwind","Bomb"],"kind":"struct"},"1448":{"crate_id":5,"path":["libc","unix","timeval"],"kind":"struct"},"1775":{"crate_id":10,"path":["hashbrown","set","Iter"],"kind":"struct"},"13":{"crate_id":2,"path":["core","panic","unwind_safe","UnwindSafe"],"kind":"trait"},"894":{"crate_id":2,"path":["core","core_arch","simd","f32x4"],"kind":"struct"},"1221":{"crate_id":2,"path":["core","intrinsics","fallback","FunnelShift"],"kind":"trait"},"340":{"crate_id":1,"path":["std","fs","Permissions"],"kind":"struct"},"2102":{"crate_id":17,"path":["object","read","any","SymbolTable"],"kind":"struct"},"2429":{"crate_id":17,"path":["object","pe","ImageDelayloadDescriptor"],"kind":"struct"},"667":{"crate_id":2,"path":["core","ops","range","Range"],"kind":"struct"},"1548":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_elf64_rel"],"kind":"struct"},"1875":{"crate_id":16,"path":["gimli","arch","PowerPc64"],"kind":"struct"},"994":{"crate_id":2,"path":["core","str","iter","SplitN"],"kind":"struct"},"440":{"crate_id":1,"path":["std","sync","mpmc","IntoIter"],"kind":"struct"},"1321":{"crate_id":3,"path":["alloc","collections","vec_deque","write_iter_wrapping","Guard"],"kind":"struct"},"2202":{"crate_id":17,"path":["object","read","macho","fat","MachOFatFile"],"kind":"struct"},"2529":{"crate_id":18,"path":["memchr","cow","Imp"],"kind":"struct"},"767":{"crate_id":2,"path":["core","ptr","non_null","NonNull"],"kind":"struct"},"1648":{"crate_id":5,"path":["libc","unix","linux_like","linux","af_alg_iv"],"kind":"struct"},"1975":{"crate_id":16,"path":["gimli","read","index","UnitIndexSection"],"kind":"struct"},"213":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_describe"],"kind":"function"},"1094":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"540":{"crate_id":1,"path":["std","sys","io","io_slice","iovec","IoSlice"],"kind":"struct"},"1421":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Mut"],"kind":"struct"},"2302":{"crate_id":17,"path":["object","elf","Rel32"],"kind":"struct"},"2629":{"crate_id":1,"path":["std","f32"],"kind":"primitive"},"867":{"crate_id":2,"path":["core","alloc","layout","LayoutError"],"kind":"struct"},"1748":{"crate_id":8,"path":["miniz_oxide","inflate","core","LocalVars"],"kind":"struct"},"1194":{"crate_id":2,"path":["core","ops","arith","DivAssign"],"kind":"trait"},"313":{"crate_id":1,"path":["std","collections","hash","set","Drain"],"kind":"struct"},"2075":{"crate_id":17,"path":["object","endian","U16Bytes"],"kind":"struct"},"640":{"crate_id":2,"path":["core","cmp","PartialOrd"],"kind":"trait"},"1521":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_event"],"kind":"struct"},"2402":{"crate_id":17,"path":["object","pe","AnonObjectHeader"],"kind":"struct"},"1848":{"crate_id":16,"path":["gimli","common","LocationListsOffset"],"kind":"struct"},"967":{"crate_id":2,"path":["core","range","iter","IterRangeInclusive"],"kind":"struct"},"1294":{"crate_id":2,"path":["core","core_simd","simd","num","int","SimdInt"],"kind":"trait"},"413":{"crate_id":1,"path":["std","path","Display"],"kind":"struct"},"2175":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSection"],"kind":"struct"},"740":{"crate_id":1,"path":["std","os","unix","fs","DirEntryExt2"],"kind":"trait"},"1621":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_ring_offset"],"kind":"struct"},"2502":{"crate_id":18,"path":["memchr","arch","all","twoway","Finder"],"kind":"struct"},"1948":{"crate_id":16,"path":["gimli","read","dwarf","DwarfPackage"],"kind":"struct"},"1067":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1394":{"crate_id":3,"path":["alloc","collections","vec_deque","iter_mut","IterMut"],"kind":"struct"},"513":{"crate_id":1,"path":["std","sys","process","unix","common","CommandArgs"],"kind":"struct"},"2275":{"crate_id":17,"path":["object","read","ObjectMapEntry"],"kind":"struct"},"2602":{"crate_id":3,"path":["alloc"],"kind":"module"},"840":{"crate_id":2,"path":["core","net","ip_addr","Ipv6MulticastScope"],"kind":"enum"},"1721":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","stat"],"kind":"struct"},"2048":{"crate_id":16,"path":["gimli","read","unit","EntriesTree"],"kind":"struct"},"286":{"crate_id":1,"path":["std","thread","ThreadId"],"kind":"struct"},"1167":{"crate_id":2,"path":["core","sync","atomic","AtomicPtr"],"kind":"struct"},"1494":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_versions"],"kind":"enum"},"613":{"crate_id":2,"path":["core","iter","adapters","peekable","Peekable"],"kind":"struct"},"2375":{"crate_id":17,"path":["object","macho","DyldInfoCommand"],"kind":"struct"},"940":{"crate_id":2,"path":["core","cell","BorrowRef"],"kind":"struct"},"1821":{"crate_id":13,"path":["rustc_demangle","legacy","Demangle"],"kind":"struct"},"2148":{"crate_id":17,"path":["object","read","elf","symbol","ElfSymbolTable"],"kind":"struct"},"386":{"crate_id":1,"path":["std","net","Shutdown"],"kind":"enum"},"1267":{"crate_id":2,"path":["core","slice","private_get_disjoint_mut_index","Sealed"],"kind":"trait"},"713":{"crate_id":1,"path":["std","io","stdio","StdinRaw"],"kind":"struct"},"1594":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_sm4_gcm"],"kind":"struct"},"2475":{"crate_id":17,"path":["object","xcoff","FileAux32"],"kind":"struct"},"1040":{"crate_id":2,"path":["core","escape","EscapeIterInner"],"kind":"struct"},"1921":{"crate_id":16,"path":["gimli","read","cfi","EhHdrTable"],"kind":"struct"},"1367":{"crate_id":3,"path":["alloc","collections","btree","set","Iter"],"kind":"struct"},"486":{"crate_id":1,"path":["std","sys","pal","unix","linux","pidfd","PidFd"],"kind":"struct"},"2248":{"crate_id":17,"path":["object","read","pe","rich","RichHeaderInfo"],"kind":"struct"},"813":{"crate_id":2,"path":["core","marker","FnPtr"],"kind":"trait"},"1694":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","__c_anonymous_ptrace_syscall_info_exit"],"kind":"struct"},"2575":{"crate_id":2,"path":["core","ptr","drop_in_place"],"kind":"function"},"259":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_new_Value"],"kind":"function"},"1140":{"crate_id":2,"path":["core","asserting","TryCaptureWithDebug"],"kind":"struct"},"2021":{"crate_id":16,"path":["gimli","read","pubnames","PubNamesEntryIter"],"kind":"struct"},"1467":{"crate_id":5,"path":["libc","unix","linux_like","sockaddr"],"kind":"struct"},"586":{"crate_id":2,"path":["core","error","Request"],"kind":"struct"},"2348":{"crate_id":17,"path":["object","macho","SubFrameworkCommand"],"kind":"struct"},"913":{"crate_id":2,"path":["core","core_arch","simd","m32x8"],"kind":"struct"},"32":{"crate_id":2,"path":["core","result","Result"],"kind":"enum"},"1794":{"crate_id":10,"path":["hashbrown","set","IntoIter"],"kind":"struct"},"2121":{"crate_id":17,"path":["object","read","coff","file","CoffCommon"],"kind":"struct"},"359":{"crate_id":1,"path":["std","io","error","Custom"],"kind":"struct"},"1240":{"crate_id":2,"path":["core","ops","async_function","AsyncFnMut"],"kind":"trait"},"1567":{"crate_id":5,"path":["libc","unix","linux_like","linux","seccomp_notif_resp"],"kind":"struct"},"686":{"crate_id":2,"path":["core","slice","iter","RSplit"],"kind":"struct"},"2448":{"crate_id":17,"path":["object","pe","ImageArmRuntimeFunctionEntry"],"kind":"struct"},"1013":{"crate_id":2,"path":["core","str","pattern","CharArraySearcher"],"kind":"struct"},"1894":{"crate_id":16,"path":["gimli","constants","DwCc"],"kind":"struct"},"2221":{"crate_id":17,"path":["object","read","pe","file","PeFile"],"kind":"struct"},"459":{"crate_id":1,"path":["std","sync","reentrant_lock","ReentrantLock"],"kind":"struct"},"1340":{"crate_id":3,"path":["alloc","vec","into_iter","drop","DropGuard"],"kind":"struct"},"1667":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_iwreq"],"kind":"union"},"786":{"crate_id":2,"path":["core","num","niche_types","NonZeroU8Inner"],"kind":"struct"},"2548":{"crate_id":18,"path":["memchr","arch","all","twoway","Suffix"],"kind":"struct"},"232":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_set_from_named"],"kind":"function"},"1113":{"crate_id":2,"path":["core","core_arch","x86","__m512i"],"kind":"struct"},"1994":{"crate_id":16,"path":["gimli","read","loclists","RawLocListIter"],"kind":"struct"},"2321":{"crate_id":17,"path":["object","macho","PtrauthKey"],"kind":"enum"},"559":{"crate_id":1,"path":["std","sys","net","connection","socket","LookupHost"],"kind":"struct"},"1440":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","canxl_frame"],"kind":"struct"},"886":{"crate_id":2,"path":["core","core_arch","simd","u32x4"],"kind":"struct"},"5":{"crate_id":2,"path":["core","marker","Send"],"kind":"trait"},"1767":{"crate_id":10,"path":["hashbrown","raw","RawIter"],"kind":"struct"},"332":{"crate_id":1,"path":["std","error","Report"],"kind":"struct"},"1213":{"crate_id":2,"path":["core","cell","BorrowRefMut"],"kind":"struct"},"2094":{"crate_id":17,"path":["object","read","any","SectionIterator"],"kind":"struct"},"1540":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf64_Ehdr"],"kind":"struct"},"659":{"crate_id":1,"path":["std","io","default_write_fmt","Adapter"],"kind":"struct"},"2421":{"crate_id":17,"path":["object","pe","ImageImportByName"],"kind":"struct"},"986":{"crate_id":2,"path":["core","str","iter","Bytes"],"kind":"struct"},"1867":{"crate_id":16,"path":["gimli","common","DwarfFileType"],"kind":"enum"},"432":{"crate_id":1,"path":["std","sync","mpmc","list","ListToken"],"kind":"struct"},"1313":{"crate_id":3,"path":["alloc","collections","btree","node","drop_key_val","Dropper"],"kind":"struct"},"2194":{"crate_id":17,"path":["object","read","macho","dyld_cache","RelocationStateV2"],"kind":"enum"},"1640":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_michaelmicfailure"],"kind":"struct"},"759":{"crate_id":2,"path":["core","ops","arith","Add"],"kind":"trait"},"2521":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","One"],"kind":"struct"},"1086":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"205":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_fail_always"],"kind":"function"},"1967":{"crate_id":16,"path":["gimli","read","aranges","ArangeHeaderIter"],"kind":"struct"},"2294":{"crate_id":17,"path":["object","elf","SectionHeader32"],"kind":"struct"},"532":{"crate_id":2,"path":["core","clone","Clone"],"kind":"trait"},"1413":{"crate_id":3,"path":["alloc","collections","linked_list","ExtractIf"],"kind":"struct"},"1740":{"crate_id":5,"path":["libc","unix","linux_like","linux","arch","generic","termios2"],"kind":"struct"},"859":{"crate_id":2,"path":["core","str","lossy","Utf8Chunk"],"kind":"struct"},"2621":{"crate_id":1,"path":["std","char"],"kind":"primitive"},"1186":{"crate_id":2,"path":["core","fmt","Binary"],"kind":"trait"},"305":{"crate_id":1,"path":["std","collections","hash","map","ValuesMut"],"kind":"struct"},"2067":{"crate_id":17,"path":["object","common","FileFlags"],"kind":"enum"},"2394":{"crate_id":17,"path":["object","pe","ImageDataDirectory"],"kind":"struct"},"632":{"crate_id":2,"path":["core","cmp","Ordering"],"kind":"enum"},"1513":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_stats_v3"],"kind":"struct"},"1840":{"crate_id":16,"path":["gimli","common","DebugAbbrevOffset"],"kind":"struct"},"959":{"crate_id":2,"path":["core","iter","sources","repeat_n","RepeatNInner"],"kind":"struct"},"405":{"crate_id":1,"path":["std","path","fmt","DebugHelper"],"kind":"struct"},"1286":{"crate_id":2,"path":["core","core_simd","masks","sealed","Sealed"],"kind":"trait"},"2167":{"crate_id":17,"path":["object","read","elf","hash","GnuHashTable"],"kind":"struct"},"2494":{"crate_id":18,"path":["memchr","arch","all","memchr","TwoIter"],"kind":"struct"},"732":{"crate_id":1,"path":["std","os","unix","ffi","os_str","OsStringExt"],"kind":"trait"},"1613":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_rwlockattr_t"],"kind":"struct"},"1059":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1940":{"crate_id":16,"path":["gimli","read","cfi","CallFrameInstruction"],"kind":"enum"},"505":{"crate_id":1,"path":["std","sys","net","connection","socket","UdpSocket"],"kind":"struct"},"1386":{"crate_id":3,"path":["alloc","wtf8","Wtf8Buf"],"kind":"struct"},"2267":{"crate_id":17,"path":["object","read","FileKind"],"kind":"enum"},"2594":{"crate_id":2,"path":["core","core_simd","simd","ptr","const_ptr","SimdConstPtr","with_addr"],"kind":"function"},"832":{"crate_id":2,"path":["core","cell","Cell"],"kind":"struct"},"1713":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","semid_ds"],"kind":"struct"},"1159":{"crate_id":2,"path":["core","sync","atomic","AtomicU16"],"kind":"struct"},"278":{"crate_id":2,"path":["core","marker","UnsafeUnpin"],"kind":"trait"},"2040":{"crate_id":16,"path":["gimli","read","unit","UnitType"],"kind":"enum"},"605":{"crate_id":2,"path":["core","iter","adapters","chain","Chain"],"kind":"struct"},"1486":{"crate_id":5,"path":["libc","unix","linux_like","sock_fprog"],"kind":"struct"},"2367":{"crate_id":17,"path":["object","macho","RpathCommand"],"kind":"struct"},"1813":{"crate_id":10,"path":["hashbrown","set","VacantEntry"],"kind":"struct"},"51":{"crate_id":0,"path":["rustcalc","CalcResult","Nothing"],"kind":"variant"},"932":{"crate_id":2,"path":["core","num","diy_float","Fp"],"kind":"struct"},"1259":{"crate_id":2,"path":["core","ffi","va_list","sealed","Sealed"],"kind":"trait"},"378":{"crate_id":1,"path":["std","io","Bytes"],"kind":"struct"},"2140":{"crate_id":17,"path":["object","read","coff","import","ImportObjectData"],"kind":"struct"},"2467":{"crate_id":17,"path":["object","xcoff","FileHeader64"],"kind":"struct"},"705":{"crate_id":2,"path":["core","ops","deref","DerefMut"],"kind":"trait"},"1586":{"crate_id":5,"path":["libc","unix","linux_like","linux","sctp_prinfo"],"kind":"struct"},"1913":{"crate_id":16,"path":["gimli","read","addr","DebugAddr"],"kind":"struct"},"1032":{"crate_id":2,"path":["core","future","pending","Pending"],"kind":"struct"},"1359":{"crate_id":3,"path":["alloc","collections","btree","map","Cursor"],"kind":"struct"},"478":{"crate_id":1,"path":["std","sync","poison","rwlock","MappedRwLockReadGuard"],"kind":"struct"},"2240":{"crate_id":17,"path":["object","read","pe","relocation","RelocationBlockIterator"],"kind":"struct"},"2567":{"crate_id":2,"path":["core","ptr"],"kind":"module"},"805":{"crate_id":2,"path":["core","num","FpCategory"],"kind":"enum"},"1686":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","rtentry"],"kind":"struct"},"2013":{"crate_id":16,"path":["gimli","read","op","EvaluationState"],"kind":"enum"},"251":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_get_scale"],"kind":"function"},"1132":{"crate_id":2,"path":["core","clone","UseCloned"],"kind":"trait"},"578":{"crate_id":1,"path":["std","sync","mpmc","counter","Receiver"],"kind":"struct"},"1459":{"crate_id":5,"path":["libc","unix","tms"],"kind":"struct"},"2340":{"crate_id":17,"path":["object","macho","SegmentCommand32"],"kind":"struct"},"905":{"crate_id":2,"path":["core","core_arch","simd","i16x16"],"kind":"struct"},"1786":{"crate_id":10,"path":["hashbrown","raw","RawDrain"],"kind":"struct"},"1232":{"crate_id":2,"path":["core","convert","num","FloatToInt"],"kind":"trait"},"351":{"crate_id":1,"path":["std","io","buffered","bufwriter","BufWriter"],"kind":"struct"},"2113":{"crate_id":17,"path":["object","read","archive","ArchiveFile"],"kind":"struct"},"678":{"crate_id":2,"path":["core","slice","iter","RChunksExact"],"kind":"struct"},"1559":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_event_info_header"],"kind":"struct"},"2440":{"crate_id":17,"path":["object","pe","ImageDynamicRelocation64V2"],"kind":"struct"},"1005":{"crate_id":2,"path":["core","str","iter","SplitAsciiWhitespace"],"kind":"struct"},"1886":{"crate_id":16,"path":["gimli","constants","DwDs"],"kind":"struct"},"1332":{"crate_id":3,"path":["alloc","sync","from_iter_exact","Guard"],"kind":"struct"},"451":{"crate_id":1,"path":["std","sync","mpsc","SendError"],"kind":"struct"},"2213":{"crate_id":17,"path":["object","read","macho","section","MachOSectionIterator"],"kind":"struct"},"778":{"crate_id":2,"path":["core","num","fmt","Part"],"kind":"enum"},"1659":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_block_desc"],"kind":"struct"},"2540":{"crate_id":18,"path":["memchr","memmem","searcher","PrefilterKind"],"kind":"union"},"1986":{"crate_id":16,"path":["gimli","read","line","CompleteLineProgram"],"kind":"struct"},"224":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_set_nickname"],"kind":"function"},"1105":{"crate_id":2,"path":["core","core_arch","x86","__m128d"],"kind":"struct"},"1432":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","env","Round"],"kind":"enum"},"551":{"crate_id":1,"path":["std","io","Guard"],"kind":"struct"},"2313":{"crate_id":17,"path":["object","elf","Verdef"],"kind":"struct"},"2640":{"crate_id":1,"path":["std","u64"],"kind":"primitive"},"878":{"crate_id":2,"path":["core","core_arch","simd","i8x8"],"kind":"struct"},"1759":{"crate_id":10,"path":["hashbrown","control","tag","Tag"],"kind":"struct"},"2086":{"crate_id":17,"path":["object","read","util","DebugLen"],"kind":"struct"},"324":{"crate_id":1,"path":["std","collections","hash","set","VacantEntry"],"kind":"struct"},"1205":{"crate_id":2,"path":["core","ops","bit","Shr"],"kind":"trait"},"1532":{"crate_id":5,"path":["libc","unix","linux_like","linux","ff_periodic_effect"],"kind":"struct"},"651":{"crate_id":2,"path":["core","ops","bit","BitOr"],"kind":"trait"},"2413":{"crate_id":17,"path":["object","pe","ImageAuxSymbolWeak"],"kind":"struct"},"978":{"crate_id":2,"path":["core","hash","sip","State"],"kind":"struct"},"1859":{"crate_id":16,"path":["gimli","common","DebugStrOffsetsIndex"],"kind":"struct"},"2186":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheImage"],"kind":"struct"},"424":{"crate_id":1,"path":["std","process","ExitStatusError"],"kind":"struct"},"1305":{"crate_id":3,"path":["alloc","collections","binary_heap","RebuildOnDrop"],"kind":"struct"},"751":{"crate_id":1,"path":["std","os","unix","process","ChildExt"],"kind":"trait"},"1632":{"crate_id":5,"path":["libc","unix","linux_like","linux","xsk_tx_metadata_request"],"kind":"struct"},"2513":{"crate_id":18,"path":["memchr","arch","generic","packedpair","Finder"],"kind":"struct"},"197":{"crate_id":0,"path":["rustcalc","rustcalc_kne_readStringRef"],"kind":"function"},"1078":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1959":{"crate_id":16,"path":["gimli","read","abbrev","DebugAbbrev"],"kind":"struct"},"2286":{"crate_id":17,"path":["object","read","CompressedData"],"kind":"struct"},"524":{"crate_id":1,"path":["std","backtrace_rs","types","BytesOrWideString"],"kind":"enum"},"1405":{"crate_id":3,"path":["alloc","collections","btree","set","entry","OccupiedEntry"],"kind":"struct"},"851":{"crate_id":2,"path":["core","fmt","Alignment"],"kind":"enum"},"1732":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","ipc_perm"],"kind":"struct"},"2613":{"crate_id":14,"path":["cfg_if"],"kind":"module"},"297":{"crate_id":1,"path":["std","collections","hash","map","Keys"],"kind":"struct"},"1178":{"crate_id":2,"path":["core","wtf8","fmt","CodeUnit"],"kind":"struct"},"2059":{"crate_id":17,"path":["object","common","AddressSize"],"kind":"enum"},"1505":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_auxdata"],"kind":"struct"},"624":{"crate_id":2,"path":["core","iter","adapters","inspect","Inspect"],"kind":"struct"},"2386":{"crate_id":17,"path":["object","macho","Relocation"],"kind":"struct"},"951":{"crate_id":2,"path":["core","iter","adapters","map_windows","Buffer"],"kind":"struct"},"1832":{"crate_id":15,"path":["addr2line","unit","SupUnits"],"kind":"struct"},"2159":{"crate_id":17,"path":["object","read","elf","comdat","ElfComdatIterator"],"kind":"struct"},"397":{"crate_id":1,"path":["std","os","fd","owned","OwnedFd"],"kind":"struct"},"1278":{"crate_id":2,"path":["core","core_simd","swizzle","deinterleave","Even"],"kind":"struct"},"1605":{"crate_id":5,"path":["libc","unix","linux_like","linux","iw_encode_ext"],"kind":"struct"},"724":{"crate_id":2,"path":["core","option","IntoIter"],"kind":"struct"},"2486":{"crate_id":17,"path":["object","xcoff","DwarfAux64"],"kind":"struct"},"1051":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1932":{"crate_id":16,"path":["gimli","read","cfi","FrameDescriptionEntry"],"kind":"struct"},"2259":{"crate_id":17,"path":["object","read","xcoff","relocation","XcoffRelocationIterator"],"kind":"struct"},"497":{"crate_id":1,"path":["std","sys","fs","unix","FilePermissions"],"kind":"struct"},"1378":{"crate_id":3,"path":["alloc","collections","vec_deque","into_iter","IntoIter"],"kind":"struct"},"1705":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","fpos_t"],"kind":"struct"},"824":{"crate_id":2,"path":["core","ops","index_range","IndexRange"],"kind":"struct"},"2586":{"crate_id":1,"path":["std","panic","always_abort"],"kind":"function"},"270":{"crate_id":0,"path":["rustcalc","rustcalc_find_max"],"kind":"function"},"1151":{"crate_id":2,"path":["core","panic","unwind_safe","AssertUnwindSafe"],"kind":"struct"},"2032":{"crate_id":16,"path":["gimli","read","rnglists","RawRange"],"kind":"struct"},"2359":{"crate_id":17,"path":["object","macho","DylibTableOfContents"],"kind":"struct"},"597":{"crate_id":2,"path":["core","ops","range","RangeFull"],"kind":"struct"},"1478":{"crate_id":5,"path":["libc","unix","linux_like","ifaddrs"],"kind":"struct"},"924":{"crate_id":2,"path":["core","core_arch","simd","f64x8"],"kind":"struct"},"1805":{"crate_id":10,"path":["hashbrown","map","EntryRef"],"kind":"enum"},"370":{"crate_id":1,"path":["std","io","util","Empty"],"kind":"struct"},"1251":{"crate_id":2,"path":["core","iter","adapters","zip","TrustedRandomAccess"],"kind":"trait"},"2132":{"crate_id":17,"path":["object","read","coff","symbol","CoffSymbol"],"kind":"struct"},"2459":{"crate_id":17,"path":["object","pe","ImageFunctionEntry"],"kind":"struct"},"697":{"crate_id":2,"path":["core","convert","AsMut"],"kind":"trait"},"1578":{"crate_id":5,"path":["libc","unix","linux_like","linux","ptp_sys_offset_extended"],"kind":"struct"},"1024":{"crate_id":2,"path":["core","str","CharEscapeDefault"],"kind":"struct"},"1905":{"crate_id":16,"path":["gimli","constants","DwRle"],"kind":"struct"},"470":{"crate_id":1,"path":["std","sync","nonpoison","rwlock","MappedRwLockWriteGuard"],"kind":"struct"},"1351":{"crate_id":3,"path":["alloc","collections","binary_heap","BinaryHeap"],"kind":"struct"},"2232":{"crate_id":17,"path":["object","read","pe","export","ExportTarget"],"kind":"enum"},"1678":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","msghdr"],"kind":"struct"},"797":{"crate_id":2,"path":["core","num","niche_types","UsizeNoHighBit"],"kind":"struct"},"2559":{"crate_id":1,"path":["std","fs","read_dir"],"kind":"function"},"1124":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"243":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_score_labels"],"kind":"function"},"2005":{"crate_id":16,"path":["gimli","read","macros","MacroString"],"kind":"enum"},"2332":{"crate_id":17,"path":["object","macho","DyldSubCacheEntryV2"],"kind":"struct"},"570":{"crate_id":2,"path":["core","marker","Copy"],"kind":"trait"},"1451":{"crate_id":5,"path":["libc","unix","ipv6_mreq"],"kind":"struct"},"1778":{"crate_id":10,"path":["hashbrown","set","SymmetricDifference"],"kind":"struct"},"897":{"crate_id":2,"path":["core","core_arch","simd","m16x8"],"kind":"struct"},"1224":{"crate_id":2,"path":["core","pin","helper","PinDerefMutHelper"],"kind":"trait"},"343":{"crate_id":1,"path":["std","fs","File"],"kind":"struct"},"2105":{"crate_id":17,"path":["object","read","any","SymbolIteratorInternal"],"kind":"enum"},"2432":{"crate_id":17,"path":["object","pe","ImageResourceDirectoryString"],"kind":"struct"},"670":{"crate_id":2,"path":["core","slice","iter","Windows"],"kind":"struct"},"1551":{"crate_id":5,"path":["libc","unix","linux_like","linux","mntent"],"kind":"struct"},"1878":{"crate_id":16,"path":["gimli","constants","DwUt"],"kind":"struct"},"997":{"crate_id":2,"path":["core","str","iter","MatchIndices"],"kind":"struct"},"443":{"crate_id":1,"path":["std","sync","mpsc","TryIter"],"kind":"struct"},"1324":{"crate_id":3,"path":["alloc","collections","vec_deque","truncate_front","Dropper"],"kind":"struct"},"2205":{"crate_id":17,"path":["object","read","macho","file","MachOComdat"],"kind":"struct"},"2532":{"crate_id":18,"path":["memchr","memchr","Memchr3"],"kind":"struct"},"770":{"crate_id":2,"path":["core","num","dec2flt","common","BiasedFp"],"kind":"struct"},"1651":{"crate_id":5,"path":["libc","unix","linux_like","linux","ifreq"],"kind":"struct"},"1978":{"crate_id":16,"path":["gimli","read","line","LineRows"],"kind":"struct"},"216":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_concat"],"kind":"function"},"1097":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"543":{"crate_id":1,"path":["std","sys","thread_local","native","lazy","State"],"kind":"enum"},"1424":{"crate_id":3,"path":["alloc","collections","btree","node","marker","Dying"],"kind":"enum"},"2305":{"crate_id":17,"path":["object","elf","Rela64"],"kind":"struct"},"2632":{"crate_id":1,"path":["std","i8"],"kind":"primitive"},"870":{"crate_id":2,"path":["core","core_arch","simd","u8x4"],"kind":"struct"},"1751":{"crate_id":8,"path":["miniz_oxide","MZFlush"],"kind":"enum"},"1197":{"crate_id":2,"path":["core","ops","arith","Neg"],"kind":"trait"},"316":{"crate_id":2,"path":["core","cmp","Eq"],"kind":"trait"},"2078":{"crate_id":17,"path":["object","endian","I16Bytes"],"kind":"struct"},"643":{"crate_id":1,"path":["std","io","BufRead"],"kind":"trait"},"1524":{"crate_id":5,"path":["libc","unix","linux_like","linux","input_keymap_entry"],"kind":"struct"},"2405":{"crate_id":17,"path":["object","pe","ImageSectionHeader"],"kind":"struct"},"1851":{"crate_id":16,"path":["gimli","common","DebugMacinfoOffset"],"kind":"struct"},"970":{"crate_id":2,"path":["core","result","IntoIter"],"kind":"struct"},"1297":{"crate_id":2,"path":["core","core_simd","simd","ptr","mut_ptr","SimdMutPtr"],"kind":"trait"},"416":{"crate_id":1,"path":["std","process","ChildStdout"],"kind":"struct"},"2178":{"crate_id":17,"path":["object","read","elf","attributes","AttributesSubsubsectionIterator"],"kind":"struct"},"2505":{"crate_id":18,"path":["memchr","arch","all","twoway","Shift"],"kind":"enum"},"743":{"crate_id":1,"path":["std","os","fd","raw","AsRawFd"],"kind":"trait"},"1624":{"crate_id":5,"path":["libc","unix","linux_like","linux","xdp_mmap_offsets_v1"],"kind":"struct"},"1951":{"crate_id":16,"path":["gimli","read","dwarf","RangeIter"],"kind":"struct"},"189":{"crate_id":0,"path":["rustcalc","compute"],"kind":"function"},"1070":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1397":{"crate_id":3,"path":["alloc","collections","btree","map","entry","Entry"],"kind":"enum"},"516":{"crate_id":1,"path":["std","sys","process","env","CommandEnv"],"kind":"struct"},"2278":{"crate_id":17,"path":["object","read","Export"],"kind":"struct"},"2605":{"crate_id":6,"path":["rustc_std_workspace_core"],"kind":"module"},"843":{"crate_id":2,"path":["core","panic","location","Location"],"kind":"struct"},"1724":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","statvfs64"],"kind":"struct"},"2051":{"crate_id":16,"path":["gimli","read","unit","DebugTypes"],"kind":"struct"},"289":{"crate_id":1,"path":["std","backtrace","BacktraceStatus"],"kind":"enum"},"1170":{"crate_id":2,"path":["core","fmt","num_buffer","NumBuffer"],"kind":"struct"},"616":{"crate_id":2,"path":["core","iter","adapters","map_while","MapWhile"],"kind":"struct"},"1497":{"crate_id":5,"path":["libc","unix","linux_like","linux","spwd"],"kind":"struct"},"2378":{"crate_id":17,"path":["object","macho","IdentCommand"],"kind":"struct"},"943":{"crate_id":2,"path":["core","char","EscapeDefault"],"kind":"struct"},"1824":{"crate_id":13,"path":["rustc_demangle","DemangleStyle"],"kind":"enum"},"2151":{"crate_id":17,"path":["object","read","elf","relocation","RelocationSections"],"kind":"struct"},"389":{"crate_id":1,"path":["std","os","unix","net","ancillary","SocketAncillary"],"kind":"struct"},"1270":{"crate_id":2,"path":["core","future","into_future","IntoFuture"],"kind":"trait"},"716":{"crate_id":1,"path":["std","io","stdio","StdoutRaw"],"kind":"struct"},"1597":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls12_crypto_info_aria_gcm_256"],"kind":"struct"},"2478":{"crate_id":17,"path":["object","xcoff","CsectAux64"],"kind":"struct"},"1043":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1924":{"crate_id":16,"path":["gimli","read","cfi","BaseAddresses"],"kind":"struct"},"1370":{"crate_id":3,"path":["alloc","collections","btree","set","SymmetricDifference"],"kind":"struct"},"489":{"crate_id":1,"path":["std","sys","pal","unix","time","SystemTime"],"kind":"struct"},"2251":{"crate_id":17,"path":["object","read","xcoff","section","XcoffSectionIterator"],"kind":"struct"},"816":{"crate_id":2,"path":["core","marker","variance","PhantomContravariantLifetime"],"kind":"struct"},"1697":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","ptrace_sud_config"],"kind":"struct"},"2578":{"crate_id":2,"path":["core","iter","adapters","zip","zip"],"kind":"function"},"2024":{"crate_id":16,"path":["gimli","read","pubtypes","PubTypesEntryIter"],"kind":"struct"},"262":{"crate_id":0,"path":["rustcalc","rustcalc_CalcResult_Error_get_value"],"kind":"function"},"1143":{"crate_id":2,"path":["core","cell","BorrowMutError"],"kind":"struct"},"1470":{"crate_id":5,"path":["libc","unix","linux_like","addrinfo"],"kind":"struct"},"589":{"crate_id":2,"path":["core","convert","AsRef"],"kind":"trait"},"2351":{"crate_id":17,"path":["object","macho","SubLibraryCommand"],"kind":"struct"},"916":{"crate_id":2,"path":["core","core_arch","simd","i16x32"],"kind":"struct"},"1797":{"crate_id":10,"path":["hashbrown","table","IterMut"],"kind":"struct"},"2124":{"crate_id":17,"path":["object","read","coff","section","CoffSegmentIterator"],"kind":"struct"},"362":{"crate_id":1,"path":["std","io","pipe","PipeReader"],"kind":"struct"},"1243":{"crate_id":2,"path":["core","future","future","Future"],"kind":"trait"},"1570":{"crate_id":5,"path":["libc","unix","linux_like","linux","nlmsgerr"],"kind":"struct"},"689":{"crate_id":2,"path":["core","slice","iter","SplitNMut"],"kind":"struct"},"2451":{"crate_id":17,"path":["object","pe","ImageAlphaRuntimeFunctionEntry"],"kind":"struct"},"1016":{"crate_id":2,"path":["core","str","pattern","CharPredicateSearcher"],"kind":"struct"},"1897":{"crate_id":16,"path":["gimli","constants","DwDsc"],"kind":"struct"},"2224":{"crate_id":17,"path":["object","read","pe","file","PeComdatSectionIterator"],"kind":"struct"},"462":{"crate_id":1,"path":["std","sync","nonpoison","condvar","Condvar"],"kind":"struct"},"1343":{"crate_id":3,"path":["alloc","vec","in_place_drop","InPlaceDstDataSrcBufDrop"],"kind":"struct"},"1670":{"crate_id":5,"path":["libc","unix","linux_like","linux","__c_anonymous_ptp_perout_request_2"],"kind":"union"},"789":{"crate_id":2,"path":["core","num","niche_types","NonZeroU64Inner"],"kind":"struct"},"2551":{"crate_id":2,"path":["core","char","REPLACEMENT_CHARACTER"],"kind":"constant"},"235":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_reverse_bytes"],"kind":"function"},"1116":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1997":{"crate_id":16,"path":["gimli","read","loclists","LocationListEntry"],"kind":"struct"},"2324":{"crate_id":17,"path":["object","macho","DyldCacheMappingAndSlideInfo"],"kind":"struct"},"562":{"crate_id":1,"path":["std","sys","process","unix","unix","posix_spawn","PosixSpawnattr"],"kind":"struct"},"1443":{"crate_id":5,"path":["libc","new","linux_uapi","linux","can","__c_anonymous_sockaddr_can_tp"],"kind":"struct"},"889":{"crate_id":2,"path":["core","core_arch","simd","i16x8"],"kind":"struct"},"1770":{"crate_id":10,"path":["hashbrown","map","HashMap"],"kind":"struct"},"335":{"crate_id":1,"path":["std","ffi","os_str","OsStr"],"kind":"struct"},"1216":{"crate_id":2,"path":["core","slice","sort","unstable","quicksort","GapGuard"],"kind":"struct"},"2097":{"crate_id":17,"path":["object","read","any","ComdatIterator"],"kind":"struct"},"1543":{"crate_id":5,"path":["libc","unix","linux_like","linux","Elf32_Phdr"],"kind":"struct"},"662":{"crate_id":2,"path":["core","ops","range","RangeBounds"],"kind":"trait"},"2424":{"crate_id":17,"path":["object","pe","ImageTlsDirectory64"],"kind":"struct"},"989":{"crate_id":2,"path":["core","str","pattern","Pattern"],"kind":"trait"},"1870":{"crate_id":16,"path":["gimli","arch","LoongArch"],"kind":"struct"},"435":{"crate_id":1,"path":["std","sync","mpmc","select","Selected"],"kind":"enum"},"1316":{"crate_id":3,"path":["alloc","collections","vec_deque","drain","Drain"],"kind":"struct"},"2197":{"crate_id":17,"path":["object","read","macho","dyld_cache","DyldCacheRelocationIteratorV3"],"kind":"struct"},"1643":{"crate_id":5,"path":["libc","unix","linux_like","linux","sockaddr_nl"],"kind":"struct"},"762":{"crate_id":1,"path":["std","std_float","StdFloat"],"kind":"trait"},"2524":{"crate_id":18,"path":["memchr","arch","x86_64","sse2","memchr","TwoIter"],"kind":"struct"},"1089":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"208":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_add_float"],"kind":"function"},"1970":{"crate_id":16,"path":["gimli","read","aranges","ArangeEntry"],"kind":"struct"},"2297":{"crate_id":17,"path":["object","elf","CompressionHeader64"],"kind":"struct"},"535":{"crate_id":1,"path":["std","sync","poison","Guard"],"kind":"struct"},"1416":{"crate_id":3,"path":["alloc","task","Wake"],"kind":"trait"},"1743":{"crate_id":5,"path":["libc","unix","FILE"],"kind":"enum"},"862":{"crate_id":2,"path":["core","wtf8","CodePoint"],"kind":"struct"},"2624":{"crate_id":1,"path":["std","array"],"kind":"primitive"},"308":{"crate_id":1,"path":["std","collections","hash","map","Drain"],"kind":"struct"},"1189":{"crate_id":2,"path":["core","fmt","UpperHex"],"kind":"trait"},"2070":{"crate_id":17,"path":["object","common","SymbolFlags"],"kind":"enum"},"2397":{"crate_id":17,"path":["object","pe","ImageOptionalHeader64"],"kind":"struct"},"635":{"crate_id":2,"path":["core","iter","adapters","cloned","Cloned"],"kind":"struct"},"1516":{"crate_id":5,"path":["libc","unix","linux_like","linux","tpacket_hdr_v1"],"kind":"struct"},"1843":{"crate_id":16,"path":["gimli","common","DebugAddrIndex"],"kind":"struct"},"962":{"crate_id":2,"path":["core","iter","sources","successors","Successors"],"kind":"struct"},"408":{"crate_id":1,"path":["std","path","Ancestors"],"kind":"struct"},"1289":{"crate_id":2,"path":["core","core_simd","to_bytes","sealed","Sealed"],"kind":"trait"},"2170":{"crate_id":17,"path":["object","read","elf","version","VersionTable"],"kind":"struct"},"2497":{"crate_id":18,"path":["memchr","arch","all","packedpair","Finder"],"kind":"struct"},"735":{"crate_id":1,"path":["std","os","unix","fs","PermissionsExt"],"kind":"trait"},"1616":{"crate_id":5,"path":["libc","unix","linux_like","linux","fanotify_event_metadata"],"kind":"struct"},"1062":{"crate_id":2,"path":["core","core_arch","simd","splat","JustOne"],"kind":"struct"},"1943":{"crate_id":16,"path":["gimli","read","cfi","Pointer"],"kind":"enum"},"508":{"crate_id":1,"path":["std","sys","process","unix","common","cstring_array","CStringArray"],"kind":"struct"},"1389":{"crate_id":3,"path":["alloc","collections","btree","map","ValuesMut"],"kind":"struct"},"2270":{"crate_id":17,"path":["object","read","SymbolIndex"],"kind":"struct"},"1716":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","b64","x86_64","statfs"],"kind":"struct"},"835":{"crate_id":2,"path":["core","char","convert","CharTryFromError"],"kind":"struct"},"2597":{"crate_id":2,"path":["core","ptr","with_exposed_provenance"],"kind":"function"},"1162":{"crate_id":2,"path":["core","sync","atomic","AtomicI64"],"kind":"struct"},"281":{"crate_id":2,"path":["core","fmt","Formatter"],"kind":"struct"},"2043":{"crate_id":16,"path":["gimli","read","unit","AttributeValue"],"kind":"enum"},"608":{"crate_id":2,"path":["core","iter","adapters","intersperse","IntersperseWith"],"kind":"struct"},"1489":{"crate_id":5,"path":["libc","unix","linux_like","epoll_event"],"kind":"struct"},"2370":{"crate_id":17,"path":["object","macho","EncryptionInfoCommand32"],"kind":"struct"},"1816":{"crate_id":10,"path":["hashbrown","table","VacantEntry"],"kind":"struct"},"935":{"crate_id":2,"path":["core","error","Source"],"kind":"struct"},"1262":{"crate_id":2,"path":["core","future","join","MaybeDone"],"kind":"enum"},"381":{"crate_id":1,"path":["std","net","tcp","Incoming"],"kind":"struct"},"2143":{"crate_id":17,"path":["object","read","elf","segment","ElfSegment"],"kind":"struct"},"2470":{"crate_id":17,"path":["object","xcoff","SectionHeader32"],"kind":"struct"},"708":{"crate_id":2,"path":["core","str","error","Utf8Error"],"kind":"struct"},"1589":{"crate_id":5,"path":["libc","unix","linux_like","linux","tls_crypto_info"],"kind":"struct"},"1916":{"crate_id":16,"path":["gimli","read","addr","AddrEntryIter"],"kind":"struct"},"1035":{"crate_id":2,"path":["core","task","wake","Waker"],"kind":"struct"},"1362":{"crate_id":3,"path":["alloc","collections","btree","merge_iter","MergeIterInner"],"kind":"struct"},"481":{"crate_id":1,"path":["std","sync","poison","TryLockError"],"kind":"enum"},"2243":{"crate_id":17,"path":["object","read","pe","resource","ResourceDirectory"],"kind":"struct"},"2570":{"crate_id":2,"path":["core","str","converts","from_utf8"],"kind":"function"},"808":{"crate_id":2,"path":["core","mem","transmutability","Assume"],"kind":"struct"},"1689":{"crate_id":5,"path":["libc","unix","linux_like","linux","gnu","Elf64_Chdr"],"kind":"struct"},"2016":{"crate_id":16,"path":["gimli","read","op","Expression"],"kind":"struct"},"254":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_set_enabled"],"kind":"function"},"1135":{"crate_id":2,"path":["core","error","tags","MaybeSizedValue"],"kind":"struct"},"581":{"crate_id":1,"path":["std","panicking","panic_handler","FormatStringPayload"],"kind":"struct"},"1462":{"crate_id":5,"path":["libc","unix","in6_addr"],"kind":"struct"},"2343":{"crate_id":17,"path":["object","macho","Section64"],"kind":"struct"},"908":{"crate_id":2,"path":["core","core_arch","simd","f16x16"],"kind":"struct"},"1789":{"crate_id":10,"path":["hashbrown","map","ExtractIf"],"kind":"struct"},"1235":{"crate_id":2,"path":["core","iter","range","Step"],"kind":"trait"},"354":{"crate_id":1,"path":["std","io","buffered","linewritershim","LineWriterShim"],"kind":"struct"},"2116":{"crate_id":17,"path":["object","read","archive","ArchiveMember"],"kind":"struct"},"681":{"crate_id":2,"path":["core","slice","iter","ChunkByMut"],"kind":"struct"},"1562":{"crate_id":5,"path":["libc","unix","linux_like","linux","regmatch_t"],"kind":"struct"},"2443":{"crate_id":17,"path":["object","pe","ImageLoadConfigDirectory32"],"kind":"struct"},"1008":{"crate_id":2,"path":["core","str","iter","EscapeDebug"],"kind":"struct"},"1889":{"crate_id":16,"path":["gimli","constants","DwVis"],"kind":"struct"},"1335":{"crate_id":3,"path":["alloc","sync","UniqueArc"],"kind":"struct"},"454":{"crate_id":1,"path":["std","sync","once","OnceState"],"kind":"struct"},"2216":{"crate_id":17,"path":["object","read","macho","symbol","SymbolTable"],"kind":"struct"},"781":{"crate_id":2,"path":["core","num","error","IntErrorKind"],"kind":"enum"},"1662":{"crate_id":5,"path":["libc","unix","linux_like","linux","pthread_rwlock_t"],"kind":"struct"},"2543":{"crate_id":18,"path":["memchr","memmem","FindRevIter"],"kind":"struct"},"1989":{"crate_id":16,"path":["gimli","read","lists","ListsHeader"],"kind":"struct"},"227":{"crate_id":0,"path":["rustcalc","rustcalc_Calculator_try_divide"],"kind":"function"},"1108":{"crate_id":2,"path":["core","core_arch","x86","splat","JustOne"],"kind":"struct"},"1435":{"crate_id":4,"path":["compiler_builtins","math","libm_math","support","hex_float","HexFloatParseError"],"kind":"struct"},"554":{"crate_id":1,"path":["std","sync","mpmc","waker","SyncWaker"],"kind":"struct"},"2316":{"crate_id":17,"path":["object","elf","Vernaux"],"kind":"struct"},"2643":{"crate_id":1,"path":["std","usize"],"kind":"primitive"},"881":{"crate_id":2,"path":["core","core_arch","simd","i64x1"],"kind":"struct"}},"external_crates":{"11":{"name":"rustc_std_workspace_alloc","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"3":{"name":"alloc","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"14":{"name":"cfg_if","html_root_url":"https://docs.rs/cfg-if/"},"6":{"name":"rustc_std_workspace_core","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"17":{"name":"object","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"9":{"name":"adler2","html_root_url":"https://docs.rs/adler2/2.0.0/"},"1":{"name":"std","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"12":{"name":"std_detect","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"4":{"name":"compiler_builtins","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"15":{"name":"addr2line","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"7":{"name":"unwind","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"18":{"name":"memchr","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"10":{"name":"hashbrown","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"2":{"name":"core","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"13":{"name":"rustc_demangle","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"5":{"name":"libc","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"16":{"name":"gimli","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"8":{"name":"miniz_oxide","html_root_url":"https://doc.rust-lang.org/1.92.0/"},"19":{"name":"panic_unwind","html_root_url":"https://doc.rust-lang.org/1.92.0/"}},"target":{"triple":"x86_64-unknown-linux-gnu","target_features":[{"name":"adx","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"aes","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"amx-avx512","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-bf16","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-complex","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-fp8","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-fp16","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-int8","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-movrs","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-tf32","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-tile","implies_features":[],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"amx-transpose","implies_features":["amx-tile"],"unstable_feature_gate":"x86_amx_intrinsics","globally_enabled":false},{"name":"apxf","implies_features":[],"unstable_feature_gate":"apx_target_feature","globally_enabled":false},{"name":"avx","implies_features":["sse4.2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx2","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx10.1","implies_features":["avx512bf16","avx512bitalg","avx512bw","avx512cd","avx512dq","avx512f","avx512fp16","avx512ifma","avx512vbmi","avx512vbmi2","avx512vl","avx512vnni","avx512vpopcntdq"],"unstable_feature_gate":"avx10_target_feature","globally_enabled":false},{"name":"avx10.2","implies_features":["avx10.1"],"unstable_feature_gate":"avx10_target_feature","globally_enabled":false},{"name":"avx512bf16","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512bitalg","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512bw","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512cd","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512dq","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512f","implies_features":["avx2","fma","f16c"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512fp16","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512ifma","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vbmi","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vbmi2","implies_features":["avx512bw"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vl","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vnni","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vp2intersect","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avx512vpopcntdq","implies_features":["avx512f"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxifma","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxneconvert","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxvnni","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxvnniint8","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"avxvnniint16","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"bmi1","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"bmi2","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"cmpxchg16b","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"ermsb","implies_features":[],"unstable_feature_gate":"ermsb_target_feature","globally_enabled":false},{"name":"f16c","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"fma","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"fxsr","implies_features":[],"unstable_feature_gate":null,"globally_enabled":true},{"name":"gfni","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"kl","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"lahfsahf","implies_features":[],"unstable_feature_gate":"lahfsahf_target_feature","globally_enabled":false},{"name":"lzcnt","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"movbe","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"movrs","implies_features":[],"unstable_feature_gate":"movrs_target_feature","globally_enabled":false},{"name":"pclmulqdq","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"popcnt","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"prfchw","implies_features":[],"unstable_feature_gate":"prfchw_target_feature","globally_enabled":false},{"name":"rdrand","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"rdseed","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"rtm","implies_features":[],"unstable_feature_gate":"rtm_target_feature","globally_enabled":false},{"name":"sha","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sha512","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sm3","implies_features":["avx"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sm4","implies_features":["avx2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"soft-float","implies_features":[],"unstable_feature_gate":"x87_target_feature","globally_enabled":false},{"name":"sse","implies_features":[],"unstable_feature_gate":null,"globally_enabled":true},{"name":"sse2","implies_features":["sse"],"unstable_feature_gate":null,"globally_enabled":true},{"name":"sse3","implies_features":["sse2"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sse4.1","implies_features":["ssse3"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sse4.2","implies_features":["sse4.1"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"sse4a","implies_features":["sse3"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"ssse3","implies_features":["sse3"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"tbm","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"vaes","implies_features":["avx2","aes"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"vpclmulqdq","implies_features":["avx","pclmulqdq"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"widekl","implies_features":["kl"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"x87","implies_features":[],"unstable_feature_gate":"x87_target_feature","globally_enabled":true},{"name":"xop","implies_features":["avx","sse4a"],"unstable_feature_gate":"xop_target_feature","globally_enabled":false},{"name":"xsave","implies_features":[],"unstable_feature_gate":null,"globally_enabled":false},{"name":"xsavec","implies_features":["xsave"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"xsaveopt","implies_features":["xsave"],"unstable_feature_gate":null,"globally_enabled":false},{"name":"xsaves","implies_features":["xsave"],"unstable_feature_gate":null,"globally_enabled":false}]},"format_version":56} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 02d5a592..2c3d4fe0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,3 +37,10 @@ rootProject.name = "io.github.kdroidfilter.nucleusnativeaccess" include(":examples:calculator") include(":examples:systeminfo") include(":examples:benchmark") +include(":examples:rust-calculator") +include(":examples:rust-benchmark") +include(":examples:rust-sysinfo") +include(":examples:rust-camera") +include(":examples:rust-symphonia") +include(":examples:rust-rfd") +include(":examples:rust-tray-icon") diff --git a/tests/rust-parity/mini-crate/rust-lib/Cargo.toml b/tests/rust-parity/mini-crate/rust-lib/Cargo.toml new file mode 100644 index 00000000..27a60565 --- /dev/null +++ b/tests/rust-parity/mini-crate/rust-lib/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "kne-test-calculator" +version = "0.1.0" +edition = "2021" + +[lib] +name = "kne_test_calculator" +crate-type = ["cdylib"] diff --git a/tests/rust-parity/mini-crate/rust-lib/src/lib.rs b/tests/rust-parity/mini-crate/rust-lib/src/lib.rs new file mode 100644 index 00000000..e5b77f2c --- /dev/null +++ b/tests/rust-parity/mini-crate/rust-lib/src/lib.rs @@ -0,0 +1,109 @@ +/// A simple calculator that accumulates a value. +pub struct Calculator { + pub value: i32, + pub name: String, +} + +impl Calculator { + /// Creates a new Calculator with an initial value. + pub fn new(initial_value: i32, name: String) -> Self { + Calculator { + value: initial_value, + name, + } + } + + /// Adds n to the current value and returns the result. + pub fn add(&self, n: i32) -> i32 { + self.value + n + } + + /// Subtracts n from the current value and returns the result. + pub fn subtract(&self, n: i32) -> i32 { + self.value - n + } + + /// Multiplies the current value by n and returns the result. + pub fn multiply(&self, n: i32) -> i32 { + self.value * n + } + + /// Returns the current value. + pub fn get_value(&self) -> i32 { + self.value + } + + /// Returns the name of this calculator. + pub fn get_name(&self) -> String { + self.name.clone() + } + + /// Resets the calculator to a new value and returns the old value. + pub fn reset(&mut self, new_value: i32) -> i32 { + let old = self.value; + self.value = new_value; + old + } +} + +/// Supported arithmetic operations. +pub enum Operation { + Add, + Subtract, + Multiply, + Divide, +} + +/// A simple point in 2D space. +pub struct Point { + pub x: f64, + pub y: f64, +} + +impl Point { + /// Creates a new Point. + pub fn new(x: f64, y: f64) -> Self { + Point { x, y } + } + + /// Computes the distance to another point. + pub fn distance_to(&self, other: &Point) -> f64 { + ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt() + } + + /// Returns a string representation. + pub fn to_string_repr(&self) -> String { + format!("({}, {})", self.x, self.y) + } +} + +/// Computes a binary operation on two integers. +pub fn compute(a: i32, b: i32, op: &Operation) -> i32 { + match op { + Operation::Add => a + b, + Operation::Subtract => a - b, + Operation::Multiply => a * b, + Operation::Divide => { + if b == 0 { + 0 + } else { + a / b + } + } + } +} + +/// Adds all numbers in a list. +pub fn sum_all(numbers: Vec) -> i32 { + numbers.iter().sum() +} + +/// Returns a greeting message. +pub fn greet(name: &str) -> String { + format!("Hello, {}!", name) +} + +/// Finds the maximum value in a list, or None if empty. +pub fn find_max(numbers: Vec) -> Option { + numbers.into_iter().max() +}