Visitar URL original
hir: resolve callback optional-opaque returns in the enclosing module by MaxFreedomPollard · Pull Request #1271 · rust-diplomat/diplomat · GitHub
Skip to content

hir: resolve callback optional-opaque returns in the enclosing module - #1271

Merged
Manishearth merged 1 commit into
rust-diplomat:mainfrom
MaxFreedomPollard:hir-callback-optional-opaque-return
Sep 9, 2026
Merged

Manishearth merged 1 commit into
rust-diplomat:mainfrom
MaxFreedomPollard:hir-callback-optional-opaque-return

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

A callback or trait method that returns Option<&T> or Option<Box<T>> aborts the tool with Diplomat error: Could not resolve symbol before any backend runs. This reproduces on main:

#[diplomat::bridge]
mod ffi {
    #[diplomat::opaque]
    pub struct Foo(u8);

    impl Foo {
        pub fn apply_callback(&self, cb: impl FnMut(&Foo) -> Option<&Foo>) {}
    }
}

The cause is one argument, at core/src/hir/lowering.rs:2246. lower_callback_return_type resolves the returned type so it can reject the DiplomatOption spelling for opaques, and it called t.resolve(&t.path, self.env). PathType::resolve_with_path treats that argument as the module to resolve from, and walks self.path starting there, so for Option<&Foo> it went looking for Foo inside a module named Foo, found nothing, and landed in the None arm's create_simple_report, which panics. Every other resolve call in the file passes in_path, the module the method is declared in.

Passing in_path is the whole fix. The rest of the diff is tests.

Two things fall out of it. Option<&T> and Option<&Self> now lower the same way they do in every other position, and the DiplomatOption<&T> error this branch was written to emit becomes reachable for the first time, so I snapshotted it.

Both tests are in core/src/hir/type_context.rs and both panic without the change. test_callback_optional_opaque_return covers a closure returning Option<&Foo>, a closure returning Option<&Self>, and a trait method returning Option<&Foo>, and asserts lowering reports nothing. test_callback_diplomat_option_opaque_return_fails snapshots the DiplomatOption<&Foo> rejection. That snapshot is the only new one; no existing snapshot changed.

Verified on macOS with rustc 1.95:

cargo fmt --all -- --check                                            clean
cargo clippy -p diplomat_core --all-targets --all-features -- -D warnings   clean
cargo test -p diplomat_core --all-features    77 passed, 0 failed (+ 4 doctests)
cargo test -p diplomat_core -- ast::logging    1 passed, 0 failed
cargo test -p diplomat-tool                   99 passed, 0 failed
cargo test -p diplomat                        16 passed, 0 failed

I also ran every generator into a scratch directory (cargo run -p diplomat-tool -- <backend> <out> for c, cpp, js, demo_gen, dart, dotnet, kotlin and nanobind, over both feature_tests and example) and diffed against the checked-in bindings. Byte identical, so the gen job stays clean. The change is confined to a path that previously always panicked, so no existing fixture could have been reaching it.

Not run here: the Dart, Kotlin, .NET and nanobind suites, and cargo +1.88 check, for want of those toolchains on this machine. The diff adds no syntax newer than the declared MSRV.

Happy to file an issue for this if you want one on record. It looked small enough to just send the fix.

A callback or trait method whose return type is `Option<&T>` or
`Option<Box<T>>` aborted lowering with "Diplomat error: Could not resolve
symbol", so no backend could generate bindings for it.

`LoweringContext::lower_callback_return_type` in core/src/hir/lowering.rs
looks the returned type up so it can reject the `DiplomatOption` spelling
for opaques. It called `t.resolve(&t.path, self.env)`, passing the type's
own path as the module to resolve from. `PathType::resolve_with_path`
treats that argument as the enclosing module and starts its walk there, so
for `Option<&Foo>` it searched for `Foo` inside a module named `Foo`, found
nothing, and hit the unconditional `create_report` in
`PathType::resolve_with_path`, which panics. Every other call site in the
file passes `in_path`, the path of the module the method is declared in.

Pass `in_path` here too. The check now resolves the type it was written to
check, so `Option<&T>` and `Option<&Self>` lower normally, and the
`DiplomatOption<&T>` diagnostic the branch exists to emit becomes
reachable for the first time.

Adds two tests to core/src/hir/type_context.rs. Both panic without the
change. `test_callback_optional_opaque_return` covers a closure returning
`Option<&Foo>`, a closure returning `Option<&Self>`, and a trait method
returning `Option<&Foo>`. `test_callback_diplomat_option_opaque_return_fails`
snapshots the `DiplomatOption<&T>` error.
@tyler-zeromatter

Copy link
Copy Markdown
Member

Taking a look right now

@tyler-zeromatter tyler-zeromatter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Manishearth
Manishearth merged commit e895d6a into rust-diplomat:main Sep 9, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants