Skip to content

Commit c7f4fe7

Browse files
rust: Fix cases where with provides resources (#855)
* rust: Fix cases where `with` provides resources This commit fixes a bug where if `with` was used to provide a resource then the internal `resources` map wasn't updated correctly. This could lead to "index not found" panics in #832. To fix this I've opted to remove the `resources` map entirely as the `owned` field was already not tracked and this additionally brings the Wasmtime and Rust generators a bit closer together. Instead now each `InterfaceId` has an entry of whether it was last seen as an export or an import and this is consulted when determining whether a resource is exported or not. This fix then required shuffling around where the stub implementations are generated to ensure that when the stubs for top-level exported functions are generated the right metadata of "was this interface last seen as an import" is generated correctly. Closes #832 * Update crates/rust/src/lib.rs Co-authored-by: Pat Hickey <pat@moreproductive.org> --------- Co-authored-by: Pat Hickey <pat@moreproductive.org>
1 parent 3c1164c commit c7f4fe7

File tree

4 files changed

+112
-104
lines changed

4 files changed

+112
-104
lines changed

crates/rust/src/bindgen.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{int_repr, to_rust_ident, wasm_type, Direction, InterfaceGenerator, RustFlagsRepr};
1+
use crate::{int_repr, to_rust_ident, wasm_type, InterfaceGenerator, RustFlagsRepr};
22
use heck::*;
33
use std::fmt::Write as _;
44
use std::mem;
@@ -411,11 +411,12 @@ impl Bindgen for FunctionBindgen<'_, '_> {
411411
} => {
412412
let op = &operands[0];
413413
let rt = self.gen.gen.runtime_path();
414-
let resource = dealias(self.gen.resolve, *resource);
415-
results.push(match self.gen.gen.resources[&resource].direction {
416-
Direction::Import => format!("({op}).take_handle() as i32"),
417-
Direction::Export => format!("{rt}::Resource::take_handle(&{op}) as i32"),
418-
});
414+
let result = if self.gen.is_exported_resource(*resource) {
415+
format!("{rt}::Resource::take_handle(&{op}) as i32")
416+
} else {
417+
format!("({op}).take_handle() as i32")
418+
};
419+
results.push(result);
419420
}
420421

421422
Instruction::HandleLower {
@@ -435,9 +436,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
435436

436437
let dealiased_resource = dealias(resolve, *resource);
437438

438-
let result = if let Direction::Export =
439-
self.gen.gen.resources[&dealiased_resource].direction
440-
{
439+
let result = if self.gen.is_exported_resource(*resource) {
441440
match handle {
442441
Handle::Borrow(_) => {
443442
let name = resolve.types[*resource]
@@ -834,7 +833,6 @@ impl Bindgen for FunctionBindgen<'_, '_> {
834833
));
835834
}
836835
FunctionKind::Constructor(ty) => {
837-
self.gen.mark_resource_owned(*ty);
838836
self.push_str(&format!(
839837
"Own{0}::new(<_{0}Impl as Guest{0}>::new",
840838
resolve.types[*ty]

crates/rust/src/interface.rs

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::bindgen::FunctionBindgen;
22
use crate::{
3-
int_repr, to_rust_ident, to_upper_camel_case, wasm_type, Direction, ExportKey, FnSig,
4-
Identifier, InterfaceName, Ownership, RustFlagsRepr, RustWasm,
3+
int_repr, to_rust_ident, to_upper_camel_case, wasm_type, ExportKey, FnSig, Identifier,
4+
InterfaceName, Ownership, RustFlagsRepr, RustWasm,
55
};
66
use anyhow::Result;
77
use heck::*;
@@ -1115,7 +1115,6 @@ impl InterfaceGenerator<'_> {
11151115
}
11161116

11171117
TypeDefKind::Handle(Handle::Own(ty)) => {
1118-
self.mark_resource_owned(*ty);
11191118
self.print_ty(&Type::Id(*ty), mode);
11201119
}
11211120

@@ -1596,7 +1595,6 @@ impl InterfaceGenerator<'_> {
15961595
// aliases) is used prior to generating declarations. For example,
15971596
// if only borrows are used, no need to generate the `Own{name}`
15981597
// version.
1599-
self.mark_resource_owned(target);
16001598
for prefix in ["Own", ""] {
16011599
self.rustdoc(docs);
16021600
self.push_str(&format!(
@@ -1700,31 +1698,25 @@ impl InterfaceGenerator<'_> {
17001698
self.push_str(&format!("{rt}::vec::Vec", rt = self.gen.runtime_path()));
17011699
}
17021700

1703-
fn is_exported_resource(&self, mut ty: TypeId) -> bool {
1704-
loop {
1705-
let def = &self.resolve.types[ty];
1706-
if let TypeOwner::World(_) = &def.owner {
1707-
// Worlds cannot export types of any kind as of this writing.
1708-
return false;
1709-
}
1710-
match &def.kind {
1711-
TypeDefKind::Type(Type::Id(id)) => ty = *id,
1712-
_ => break,
1713-
}
1701+
pub fn is_exported_resource(&self, ty: TypeId) -> bool {
1702+
let ty = dealias(self.resolve, ty);
1703+
let ty = &self.resolve.types[ty];
1704+
match &ty.kind {
1705+
TypeDefKind::Resource => {}
1706+
_ => return false,
17141707
}
17151708

1716-
matches!(
1717-
self.gen.resources.get(&ty).map(|info| info.direction),
1718-
Some(Direction::Export)
1719-
)
1720-
}
1709+
match ty.owner {
1710+
// Worlds cannot export types of any kind as of this writing.
1711+
TypeOwner::World(_) => false,
1712+
1713+
// Interfaces are "stateful" currently where whatever we last saw
1714+
// them as dictates whether it's exported or not.
1715+
TypeOwner::Interface(i) => !self.gen.interface_last_seen_as_import[&i],
17211716

1722-
pub fn mark_resource_owned(&mut self, resource: TypeId) {
1723-
self.gen
1724-
.resources
1725-
.entry(dealias(self.resolve, resource))
1726-
.or_default()
1727-
.owned = true;
1717+
// Shouldn't be the case for resources
1718+
TypeOwner::None => unreachable!(),
1719+
}
17281720
}
17291721

17301722
fn push_string_name(&mut self) {
@@ -1765,15 +1757,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> {
17651757
self.print_typedef_record(id, record, docs);
17661758
}
17671759

1768-
fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) {
1769-
let entry = self
1770-
.gen
1771-
.resources
1772-
.entry(dealias(self.resolve, id))
1773-
.or_default();
1774-
if !self.in_import {
1775-
entry.direction = Direction::Export;
1776-
}
1760+
fn type_resource(&mut self, _id: TypeId, name: &str, docs: &Docs) {
17771761
self.rustdoc(docs);
17781762
let camel = to_upper_camel_case(name);
17791763
let rt = self.gen.runtime_path();

crates/rust/src/lib.rs

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,12 @@ use std::process::{Command, Stdio};
99
use std::str::FromStr;
1010
use wit_bindgen_core::abi::{Bitcast, WasmType};
1111
use wit_bindgen_core::{
12-
uwriteln, wit_parser::*, Direction, Files, InterfaceGenerator as _, Source, Types,
13-
WorldGenerator,
12+
uwriteln, wit_parser::*, Files, InterfaceGenerator as _, Source, Types, WorldGenerator,
1413
};
1514

1615
mod bindgen;
1716
mod interface;
1817

19-
#[derive(Default)]
20-
struct ResourceInfo {
21-
// Note that a resource can be both imported and exported (e.g. when
22-
// importing and exporting the same interface which contains one or more
23-
// resources). In that case, this field will be `Import` while we're
24-
// importing the interface and later change to `Export` while we're
25-
// exporting the interface.
26-
direction: Direction,
27-
owned: bool,
28-
}
29-
3018
struct InterfaceName {
3119
/// True when this interface name has been remapped through the use of `with` in the `bindgen!`
3220
/// macro invocation.
@@ -45,12 +33,14 @@ struct RustWasm {
4533
export_modules: Vec<(String, Vec<String>)>,
4634
skip: HashSet<String>,
4735
interface_names: HashMap<InterfaceId, InterfaceName>,
48-
resources: HashMap<TypeId, ResourceInfo>,
36+
/// Each imported and exported interface is stored in this map. Value indicates if last use was import.
37+
interface_last_seen_as_import: HashMap<InterfaceId, bool>,
4938
import_funcs_called: bool,
5039
with_name_counter: usize,
5140
// Track the with options that were used. Remapped interfaces provided via `with`
5241
// are required to be used.
5342
used_with_opts: HashSet<String>,
43+
world: Option<WorldId>,
5444
}
5545

5646
#[cfg(feature = "clap")]
@@ -360,7 +350,7 @@ fn name_package_module(resolve: &Resolve, id: PackageId) -> String {
360350
}
361351

362352
impl WorldGenerator for RustWasm {
363-
fn preprocess(&mut self, resolve: &Resolve, _world: WorldId) {
353+
fn preprocess(&mut self, resolve: &Resolve, world: WorldId) {
364354
wit_bindgen_core::generated_preamble(&mut self.src, env!("CARGO_PKG_VERSION"));
365355

366356
// Render some generator options to assist with debugging and/or to help
@@ -391,6 +381,7 @@ impl WorldGenerator for RustWasm {
391381
uwriteln!(self.src, "// * with {with:?}");
392382
}
393383
self.types.analyze(resolve);
384+
self.world = Some(world);
394385
}
395386

396387
fn import_interface(
@@ -400,6 +391,7 @@ impl WorldGenerator for RustWasm {
400391
id: InterfaceId,
401392
_files: &mut Files,
402393
) {
394+
self.interface_last_seen_as_import.insert(id, true);
403395
let wasm_import_module = resolve.name_world_key(name);
404396
let mut gen = self.interface(
405397
Identifier::Interface(id, name),
@@ -442,6 +434,7 @@ impl WorldGenerator for RustWasm {
442434
id: InterfaceId,
443435
_files: &mut Files,
444436
) -> Result<()> {
437+
self.interface_last_seen_as_import.insert(id, false);
445438
let mut gen = self.interface(Identifier::Interface(id, name), None, resolve, false);
446439
let (snake, module_path) = gen.start_append_submodule(name);
447440
if gen.gen.name_interface(resolve, id, name, true) {
@@ -450,6 +443,31 @@ impl WorldGenerator for RustWasm {
450443
gen.types(id);
451444
gen.generate_exports(resolve.interfaces[id].functions.values())?;
452445
gen.finish_append_submodule(&snake, module_path);
446+
447+
if self.opts.stubs {
448+
let (pkg, name) = match name {
449+
WorldKey::Name(name) => (None, name),
450+
WorldKey::Interface(id) => {
451+
let interface = &resolve.interfaces[*id];
452+
(
453+
Some(interface.package.unwrap()),
454+
interface.name.as_ref().unwrap(),
455+
)
456+
}
457+
};
458+
for (resource, funcs) in group_by_resource(resolve.interfaces[id].functions.values()) {
459+
let world_id = self.world.unwrap();
460+
let mut gen = self.interface(Identifier::World(world_id), None, resolve, false);
461+
let pkg = pkg.map(|pid| {
462+
let namespace = resolve.packages[pid].name.namespace.clone();
463+
let package_module = name_package_module(resolve, pid);
464+
(namespace, package_module)
465+
});
466+
gen.generate_stub(resource, pkg, name, true, &funcs);
467+
let stub = gen.finish();
468+
self.src.push_str(&stub);
469+
}
470+
}
453471
Ok(())
454472
}
455473

@@ -464,6 +482,16 @@ impl WorldGenerator for RustWasm {
464482
gen.generate_exports(funcs.iter().map(|f| f.1))?;
465483
let src = gen.finish();
466484
self.src.push_str(&src);
485+
486+
if self.opts.stubs {
487+
for (resource, funcs) in group_by_resource(funcs.iter().map(|f| f.1)) {
488+
let mut gen = self.interface(Identifier::World(world), None, resolve, false);
489+
let world = &resolve.worlds[world];
490+
gen.generate_stub(resource, None, &world.name, false, &funcs);
491+
let stub = gen.finish();
492+
self.src.push_str(&stub);
493+
}
494+
}
467495
Ok(())
468496
}
469497

@@ -541,50 +569,6 @@ impl WorldGenerator for RustWasm {
541569

542570
if self.opts.stubs {
543571
self.src.push_str("\n#[derive(Debug)]\npub struct Stub;\n");
544-
let world_id = world;
545-
let world = &resolve.worlds[world];
546-
let mut funcs = Vec::new();
547-
for (name, export) in world.exports.iter() {
548-
let (pkg, name) = match name {
549-
WorldKey::Name(name) => (None, name),
550-
WorldKey::Interface(id) => {
551-
let interface = &resolve.interfaces[*id];
552-
(
553-
Some(interface.package.unwrap()),
554-
interface.name.as_ref().unwrap(),
555-
)
556-
}
557-
};
558-
match export {
559-
WorldItem::Function(func) => {
560-
funcs.push(func);
561-
}
562-
WorldItem::Interface(id) => {
563-
for (resource, funcs) in
564-
group_by_resource(resolve.interfaces[*id].functions.values())
565-
{
566-
let mut gen =
567-
self.interface(Identifier::World(world_id), None, resolve, false);
568-
let pkg = pkg.map(|pid| {
569-
let namespace = resolve.packages[pid].name.namespace.clone();
570-
let package_module = name_package_module(resolve, pid);
571-
(namespace, package_module)
572-
});
573-
gen.generate_stub(resource, pkg, name, true, &funcs);
574-
let stub = gen.finish();
575-
self.src.push_str(&stub);
576-
}
577-
}
578-
WorldItem::Type(_) => unreachable!(),
579-
}
580-
}
581-
582-
for (resource, funcs) in group_by_resource(funcs.into_iter()) {
583-
let mut gen = self.interface(Identifier::World(world_id), None, resolve, false);
584-
gen.generate_stub(resource, None, &world.name, false, &funcs);
585-
let stub = gen.finish();
586-
self.src.push_str(&stub);
587-
}
588572
}
589573

590574
let mut src = mem::take(&mut self.src);

crates/rust/tests/codegen.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,3 +415,45 @@ mod with {
415415
my::inline::bar::bar(&msg);
416416
}
417417
}
418+
419+
mod with_and_resources {
420+
wit_bindgen::generate!({
421+
inline: "
422+
package my:inline;
423+
424+
interface foo {
425+
resource a;
426+
}
427+
428+
interface bar {
429+
use foo.{a};
430+
431+
bar: func(m: a) -> list<a>;
432+
}
433+
434+
world baz {
435+
import bar;
436+
}
437+
",
438+
with: {
439+
"my:inline/foo": other::my::inline::foo,
440+
},
441+
});
442+
443+
pub mod other {
444+
wit_bindgen::generate!({
445+
inline: "
446+
package my:inline;
447+
448+
interface foo {
449+
resource a;
450+
}
451+
452+
world dummy {
453+
use foo.{a};
454+
import bar: func(m: a);
455+
}
456+
",
457+
});
458+
}
459+
}

0 commit comments

Comments
 (0)