Skip to content

Implement canonical interface names in wit-component - #2622

Open
chenyan2002 wants to merge 16 commits into
bytecodealliance:mainfrom
chenyan2002:canon-ver-3
Open

Implement canonical interface names in wit-component#2622
chenyan2002 wants to merge 16 commits into
bytecodealliance:mainfrom
chenyan2002:canon-ver-3

Conversation

@chenyan2002

@chenyan2002 chenyan2002 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #2556 and replaces #2602.

  • Add config flag emit_canonical_names in ComponentEncoder to emit canonical version.
  • When both implement and versionsuffix are present, the versionsuffix should refer to the version from implement, instead of the main package name.
  • Fixed a bug in merge_world_imports_based_on_semver that implements version is not updated during the merge.
  • Add CLI flag --emit-canonical-names to wasm-tools component new

During the transition period, upstream libraries, e.g., wac, can decode the original binary and re-encode with the emit_canonical_names flag enabled, so that we convert all binaries into their canonical encoding as a preprocessing step. Then wac can merge the interface via string matching directly.

@chenyan2002
chenyan2002 requested a review from a team as a code owner August 25, 2026 00:26
@chenyan2002
chenyan2002 requested review from dicej and removed request for a team August 25, 2026 00:26
Comment thread crates/wit-component/src/encoding/wit.rs Outdated
Comment thread src/bin/wasm-tools/component.rs Outdated
Comment thread src/bin/wasm-tools/component.rs Outdated
Comment thread crates/wit-parser/src/lib.rs Outdated
Comment thread crates/wit-parser/src/resolve/mod.rs Outdated
Comment thread crates/wit-component/src/encoding.rs Outdated
lukewagner pushed a commit to WebAssembly/component-model that referenced this pull request Aug 31, 2026
Tweak the string-splitting to more closely match the intent of versions
found in tooling, specifically:

* Document that build metadata (`+foo` in `0.0.1+foo`) is always split
  out and not part of the canonical name.
* Don't split out pre-release information since pre-release versions are
  incompatible with all other versions.

This additionally matches preexisting merging behavior in
`wit-component` as discovered in bytecodealliance/wasm-tools#2622
bail!(offset, "invalid interface version: {e}");
}
}
}

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.

Could this validation move up to the above method? I think it'd be a bit clearer to perform this validation when implements is specified rather than deferring it to happen later here. That'd probably require validating the version_suffix field before implements and reorderin the above if statements, but that should be fine to do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Neither way is perfect. We validate version_suffix with ComponentName here. Ideally, we would also validate the suffix with implements here. But moving up does make a smaller diff.

pub fn encode_with_options(
resolve: &Resolve,
package: PackageId,
canonical_names: bool,

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.

In retrospect reading over this again, I think let's just add the bool parameter to the preexisting function. Given the nature of publishing for this crate, which is that each release is a semver-major release, it's ok to change API signatures. And given that I think it makes more sense rather than to start a *_with_options convention because if more options are added in the future it'll just end up breaking this signature anyway. Having just one function helps reduce duplication and cognitive overhead too I think.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done


fn encode_interface_export(
&mut self,
export_name: &str,

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.

Right now this name logic is now split between the caller and the end of this function, but perhaps this parameter could be changed to ComponentExternName directly to avoid this duplication and instead have the logic in just one place?

external_id: resolve.external_id_value(key, item).map(|s| s.into()),
version_suffix: None,
let implements = resolve.implements_interface(key, item);
if canonical_names {

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.

In a previous review I was curiuos if it would be possible to deduplicate the number of places that a canonical-names option was taken into account and a ComponentExternName were created. I count currently four different locations doing very similar things:

  1. here
  2. below in this file in for interface in interfaces
  3. in encode_interface_import in encoding.rs
  4. in encode_interface_export in encoding.rs (split across two functions)

Were you able to take a look and see if these locations could be unified? Is there perhaps one, or maybe two at most, helpers that could be used to construct these names?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will think about it tomorrow.

Comment on lines +4 to +5
(import "a:b/c@0.1.1" "x" (func (param i32 i32)))
(import "a:b/c@0.1.1" "y" (func))

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.

In addition to importing these two functions could this test additionally import the 0.1.0 version of a:b/c to test that version-merging logic too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread crates/wit-parser/src/resolve/mod.rs
Comment on lines -2518 to +2549
continue;
if let WorldKey::Interface(_) = key {
continue;
}
// Keep labeled imports with `implements` version unchanged

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.

I'm a bit surprised that this is necessary since it's expected that there'd be a unique InterfaceId for each named import. If this is removed, do tests fail, however?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, merge-canon-with-implements.wit test would fail:

  import a:b/c@0.1.0;
  import a:b/c@0.1.1;
  import my-thing: a:b/c@0.1.0;

import my-thing: a:b/c@0.1.0 gets deleted, because 0.1.0 have been removed by the above imports.

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.

2 participants