From 07256da6341dce1623a60b5d71dcac56bf54bad1 Mon Sep 17 00:00:00 2001 From: Mees Molenaar Date: Thu, 28 May 2026 15:02:08 +0200 Subject: [PATCH 1/2] wit-component, wit-parser: preserve doc comments through embed path --- crates/wit-component/src/metadata.rs | 10 +++++++++- crates/wit-parser/src/decoding.rs | 17 ++++++++++++++++- .../cli/component-embed-only-custom.wit.stdout | 1 + tests/cli/component-embed-preserves-docs.wit | 17 +++++++++++++++++ .../component-embed-preserves-docs.wit.stdout | 17 +++++++++++++++++ .../cli/component-wit-of-core-module.wit.stdout | 1 + tests/cli/importize.wit.simple-component.stdout | 10 ++++++++++ .../cli/multiple-packages-one-world.wit.stdout | 1 + tests/cli/print-core-wasm-wit.wit.stdout | 1 + ...ent-anonymous-interfaces-on-merge.wit.stdout | 5 +++++ .../world-merge-add-resource-func.wit.stdout | 3 +++ tests/cli/world-merging-add-imports.wit.stdout | 1 + tests/cli/world-merging-same-imports.wit.stdout | 1 + tests/cli/world-merging.wit.stdout | 3 +++ 14 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 tests/cli/component-embed-preserves-docs.wit create mode 100644 tests/cli/component-embed-preserves-docs.wit.stdout diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index d6f52df07a..10bd64cfc7 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -50,7 +50,9 @@ use wasm_encoder::{ }; use wasm_metadata::Producers; use wasmparser::{BinaryReader, Encoding, Parser, Payload}; -use wit_parser::{CloneMaps, Package, PackageName, Resolve, World, WorldId, WorldItem, WorldKey}; +use wit_parser::{ + CloneMaps, Package, PackageMetadata, PackageName, Resolve, World, WorldId, WorldItem, WorldKey, +}; const CURRENT_VERSION: u8 = 0x04; const CUSTOM_SECTION_NAME: &str = "wit-component-encoding"; @@ -292,6 +294,12 @@ pub fn encode( data: Cow::Borrowed(&[CURRENT_VERSION, string_encoding]), }); + let package_docs = PackageMetadata::extract(resolve, world.package.unwrap()); + builder.custom_section(&CustomSection { + name: PackageMetadata::SECTION_NAME.into(), + data: package_docs.encode()?.into(), + }); + let ty = builder.type_component(None, &outer_ty); builder.export(&world.name, ComponentExportKind::Type, ty, None); diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index feda2174ff..7eebcedfc1 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -429,6 +429,8 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { let mut exports = Vec::new(); let mut depth = 1; let mut types = None; + #[cfg(feature = "serde")] + let mut package_metadata: Option = None; for payload in Parser::new(0).parse_all(wasm) { let payload = payload?; @@ -451,6 +453,15 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { exports.push(export?); } } + #[cfg(feature = "serde")] + Payload::CustomSection(s) + if depth == 1 && s.name() == PackageMetadata::SECTION_NAME => + { + if package_metadata.is_some() { + bail!("multiple {:?} sections", PackageMetadata::SECTION_NAME); + } + package_metadata = Some(PackageMetadata::decode(s.data())?); + } _ => {} } } @@ -489,12 +500,16 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { worlds: &mut worlds, }, )?; - let (resolve, pkg) = decoder.finish(Package { + let (mut resolve, pkg) = decoder.finish(Package { name, interfaces, worlds, docs: Default::default(), }); + #[cfg(feature = "serde")] + if let Some(metadata) = package_metadata { + metadata.inject(&mut resolve, pkg)?; + } // The package decoded here should only have a single world so extract that // here to return. let world = *resolve.packages[pkg].worlds.iter().next().unwrap().1; diff --git a/tests/cli/component-embed-only-custom.wit.stdout b/tests/cli/component-embed-only-custom.wit.stdout index 74e8d4d90a..18e7916519 100644 --- a/tests/cli/component-embed-only-custom.wit.stdout +++ b/tests/cli/component-embed-only-custom.wit.stdout @@ -1,5 +1,6 @@ (component (@custom "wit-component-encoding" "/04/00") + (@custom "package-docs" "/01{/22docs/22:/22RUN: component embed --only-custom -w foo % | print/22}") (type (;0;) (component (type (;0;) diff --git a/tests/cli/component-embed-preserves-docs.wit b/tests/cli/component-embed-preserves-docs.wit new file mode 100644 index 0000000000..4218f548ff --- /dev/null +++ b/tests/cli/component-embed-preserves-docs.wit @@ -0,0 +1,17 @@ +// RUN: component embed --dummy % | component wit + +package foo:docs-test; + +/// The interface. +interface my-interface { + /// The function. + my-func: func( + /// The function's argument. + my-arg: string, + ) -> string; +} + +/// A world with documentation. +world my-world { + export my-interface; +} diff --git a/tests/cli/component-embed-preserves-docs.wit.stdout b/tests/cli/component-embed-preserves-docs.wit.stdout new file mode 100644 index 0000000000..2a9865e7da --- /dev/null +++ b/tests/cli/component-embed-preserves-docs.wit.stdout @@ -0,0 +1,17 @@ +package root:root; + +world root { + export foo:docs-test/my-interface; +} +/// RUN: component embed --dummy % | component wit +package foo:docs-test { + /// The interface. + interface my-interface { + /// The function. + my-func: func(my-arg: string) -> string; + } + /// A world with documentation. + world my-world { + export my-interface; + } +} diff --git a/tests/cli/component-wit-of-core-module.wit.stdout b/tests/cli/component-wit-of-core-module.wit.stdout index 6321454f2d..417b2a8760 100644 --- a/tests/cli/component-wit-of-core-module.wit.stdout +++ b/tests/cli/component-wit-of-core-module.wit.stdout @@ -5,6 +5,7 @@ world root { export y: func(); } +/// RUN: component embed --dummy % | component wit package a:b { world foo { import x: func(); diff --git a/tests/cli/importize.wit.simple-component.stdout b/tests/cli/importize.wit.simple-component.stdout index f9a7f62d68..07fe789907 100644 --- a/tests/cli/importize.wit.simple-component.stdout +++ b/tests/cli/importize.wit.simple-component.stdout @@ -3,6 +3,16 @@ package root:root; world root-importized { import importize:importize/t; } +/// RUN[simple]: component wit --importize-world simple % +/// RUN[simple-rename]: component wit --importize-world simple-rename --importize-out-world-name test-rename % +/// RUN[simple-component]: component embed --dummy --world simple % | / +/// component wit --importize +/// RUN[with-deps]: component wit --importize-world with-deps % +/// RUN[simple-toplevel]: component wit --importize-world simple-toplevel % +/// RUN[toplevel-deps]: component wit --importize-world toplevel-deps % +/// FAIL[fail1]: component wit --importize-world fail1 % +/// RUN[trim-imports]: component wit --importize-world trim-imports % +/// RUN[tricky-import]: component wit --importize-world tricky-import % package importize:importize { interface t { resource r; diff --git a/tests/cli/multiple-packages-one-world.wit.stdout b/tests/cli/multiple-packages-one-world.wit.stdout index 96952d2b86..6cadb2137d 100644 --- a/tests/cli/multiple-packages-one-world.wit.stdout +++ b/tests/cli/multiple-packages-one-world.wit.stdout @@ -19,6 +19,7 @@ package test:foo2 { } +/// RUN: component embed --dummy --wat % | component wit package foo:root { world hello { import test:foo1/bar; diff --git a/tests/cli/print-core-wasm-wit.wit.stdout b/tests/cli/print-core-wasm-wit.wit.stdout index 3cb25875b0..4316ffeced 100644 --- a/tests/cli/print-core-wasm-wit.wit.stdout +++ b/tests/cli/print-core-wasm-wit.wit.stdout @@ -3,6 +3,7 @@ package root:root; world root { import foo:foo/my-interface; } +/// RUN: component embed --dummy % | component wit package foo:foo { interface my-interface { foo: func(); diff --git a/tests/cli/reparent-anonymous-interfaces-on-merge.wit.stdout b/tests/cli/reparent-anonymous-interfaces-on-merge.wit.stdout index 2b8ca640f6..71061ea653 100644 --- a/tests/cli/reparent-anonymous-interfaces-on-merge.wit.stdout +++ b/tests/cli/reparent-anonymous-interfaces-on-merge.wit.stdout @@ -11,6 +11,11 @@ world root { export b: interface { } } +/// RUN: component embed --dummy --world w1 % | / +/// component embed --world w2 % | / +/// component embed --world w3 % | / +/// component embed --world w4 % | / +/// component wit package a:b { world w1 { } diff --git a/tests/cli/world-merge-add-resource-func.wit.stdout b/tests/cli/world-merge-add-resource-func.wit.stdout index 43a3bb6d90..ab85de6423 100644 --- a/tests/cli/world-merge-add-resource-func.wit.stdout +++ b/tests/cli/world-merge-add-resource-func.wit.stdout @@ -5,6 +5,9 @@ world root { constructor(); } } +/// RUN: component embed --dummy --world a % | / +/// component embed --world b % | / +/// component wit package a:b { world a { resource a; diff --git a/tests/cli/world-merging-add-imports.wit.stdout b/tests/cli/world-merging-add-imports.wit.stdout index 80d325e6c7..a7bd13fca9 100644 --- a/tests/cli/world-merging-add-imports.wit.stdout +++ b/tests/cli/world-merging-add-imports.wit.stdout @@ -7,6 +7,7 @@ world root { use a:b/b.{t}; import c: func() -> t; } +/// RUN: component embed --dummy --world into % | component embed --world %from % | component wit package a:b { interface b { type t = u32; diff --git a/tests/cli/world-merging-same-imports.wit.stdout b/tests/cli/world-merging-same-imports.wit.stdout index e5c366b4dc..06242da4ec 100644 --- a/tests/cli/world-merging-same-imports.wit.stdout +++ b/tests/cli/world-merging-same-imports.wit.stdout @@ -7,6 +7,7 @@ world root { use a:b/b.{t}; import c: func(); } +/// RUN: component embed --dummy --world into % | component embed --world %from % | component wit package a:b { interface b { type t = u32; diff --git a/tests/cli/world-merging.wit.stdout b/tests/cli/world-merging.wit.stdout index d18e6f53cd..b59da0a717 100644 --- a/tests/cli/world-merging.wit.stdout +++ b/tests/cli/world-merging.wit.stdout @@ -10,6 +10,9 @@ world root { export x: interface { } } +/// RUN: component embed --dummy --world foo % | component embed --world foo % | component wit +/// This test embeds this world twice and then ensures that merging the two +/// worlds together works (it's the same definition anyway). package a:b { interface x { type t = u32; From db517c1fcba6d48ce81bad78be8039e822cb724b Mon Sep 17 00:00:00 2001 From: Mees Molenaar Date: Fri, 29 May 2026 15:21:34 +0200 Subject: [PATCH 2/2] wit-parser: don't propagate package-docs errors in decode_world --- crates/wit-parser/src/decoding.rs | 14 ++++++++------ tests/cli/wit-deep-record.wit.abi.stderr | 2 +- tests/cli/wit-deep-tuple.wit.abi.stderr | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/wit-parser/src/decoding.rs b/crates/wit-parser/src/decoding.rs index 7eebcedfc1..bab660d717 100644 --- a/crates/wit-parser/src/decoding.rs +++ b/crates/wit-parser/src/decoding.rs @@ -453,14 +453,15 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { exports.push(export?); } } + // Best-effort: a bad section just drops docs, not the world. + // Guard takes the first top-level package-docs section; duplicates are ignored. #[cfg(feature = "serde")] Payload::CustomSection(s) - if depth == 1 && s.name() == PackageMetadata::SECTION_NAME => + if depth == 1 + && s.name() == PackageMetadata::SECTION_NAME + && package_metadata.is_none() => { - if package_metadata.is_some() { - bail!("multiple {:?} sections", PackageMetadata::SECTION_NAME); - } - package_metadata = Some(PackageMetadata::decode(s.data())?); + package_metadata = PackageMetadata::decode(s.data()).ok(); } _ => {} } @@ -508,7 +509,8 @@ pub fn decode_world(wasm: &[u8]) -> Result<(Resolve, WorldId)> { }); #[cfg(feature = "serde")] if let Some(metadata) = package_metadata { - metadata.inject(&mut resolve, pkg)?; + // Best-effort: a failure here just drops some docs. + let _ = metadata.inject(&mut resolve, pkg); } // The package decoded here should only have a single world so extract that // here to return. diff --git a/tests/cli/wit-deep-record.wit.abi.stderr b/tests/cli/wit-deep-record.wit.abi.stderr index 22fec7a96b..bae0214bf0 100644 --- a/tests/cli/wit-deep-record.wit.abi.stderr +++ b/tests/cli/wit-deep-record.wit.abi.stderr @@ -1,4 +1,4 @@ error: decoding custom section component-type Caused by: - 0: effective type size exceeds the limit of 1000000 (at offset 0x27) + 0: effective type size exceeds the limit of 1000000 (at offset 0x97) diff --git a/tests/cli/wit-deep-tuple.wit.abi.stderr b/tests/cli/wit-deep-tuple.wit.abi.stderr index 22fec7a96b..bae0214bf0 100644 --- a/tests/cli/wit-deep-tuple.wit.abi.stderr +++ b/tests/cli/wit-deep-tuple.wit.abi.stderr @@ -1,4 +1,4 @@ error: decoding custom section component-type Caused by: - 0: effective type size exceeds the limit of 1000000 (at offset 0x27) + 0: effective type size exceeds the limit of 1000000 (at offset 0x97)