Skip to content

Commit b73cb4f

Browse files
authored
Support external-id on more items (#2558)
This commit extends the work done in #2555 to support `@external-id` on all component model items and notably in more locations in WIT. This required mostly work on the WIT side of things to ensure that it's plumbed everywhere.
1 parent 3290879 commit b73cb4f

26 files changed

Lines changed: 476 additions & 151 deletions

crates/wasmparser/src/validator/component.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4725,10 +4725,6 @@ impl ComponentNameContext {
47254725
"the `cm-implements` feature is not active",
47264726
offset,
47274727
)?;
4728-
match ty {
4729-
ComponentEntityType::Instance(_) => {}
4730-
_ => bail!(offset, "only instances can have an `external-id`"),
4731-
}
47324728
}
47334729

47344730
// Validate that the kebab name, if it has structure such as

crates/wit-component/src/encoding.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,10 @@ impl<'a> EncodingState<'a> {
549549
log::trace!("encoding function type for `{}`", func.name);
550550
let idx = encoder.encode_func_type(resolve, func)?;
551551

552-
encoder.ty.export(&func.name, ComponentTypeRef::Func(idx));
552+
encoder.ty.export(
553+
crate::encoding::types::extern_name(&func.name, func.external_id.as_deref()),
554+
ComponentTypeRef::Func(idx),
555+
);
553556
}
554557

555558
let ty = encoder.ty;
@@ -597,7 +600,10 @@ impl<'a> EncodingState<'a> {
597600
let idx = self
598601
.root_import_type_encoder(None)
599602
.encode_func_type(resolve, func)?;
600-
let func_idx = self.component.import(&name, ComponentTypeRef::Func(idx));
603+
let func_idx = self.component.import(
604+
crate::encoding::types::extern_name(name.as_str(), func.external_id.as_deref()),
605+
ComponentTypeRef::Func(idx),
606+
);
601607
let prev = self.imported_funcs.insert(name, func_idx);
602608
assert!(prev.is_none());
603609
}
@@ -735,8 +741,15 @@ impl<'a> EncodingState<'a> {
735741
.encode_func_type(resolve, func)?;
736742
let core_name = world_func_core_names[&func.name];
737743
let idx = self.encode_lift(module, &core_name, export_name, func, ty)?;
738-
self.component
739-
.export(export_string, ComponentExportKind::Func, idx, None);
744+
self.component.export(
745+
crate::encoding::types::extern_name(
746+
&export_string,
747+
func.external_id.as_deref(),
748+
),
749+
ComponentExportKind::Func,
750+
idx,
751+
None,
752+
);
740753
}
741754
item @ WorldItem::Interface { id, .. } => {
742755
let core_names = interface_func_core_names.get(export_name);
@@ -913,7 +926,10 @@ impl<'a> EncodingState<'a> {
913926
match ty.kind {
914927
TypeDefKind::Resource => {
915928
let idx = nested.component.export(
916-
ty.name.as_ref().expect("resources must be named"),
929+
crate::encoding::types::extern_name(
930+
ty.name.as_ref().expect("resources must be named"),
931+
ty.external_id.as_deref(),
932+
),
917933
ComponentExportKind::Type,
918934
resources[id],
919935
None,
@@ -929,7 +945,7 @@ impl<'a> EncodingState<'a> {
929945
for (i, (_, func)) in resolve.interfaces[export].functions.iter().enumerate() {
930946
let ty = nested.encode_func_type(resolve, func)?;
931947
nested.component.export(
932-
&func.name,
948+
crate::encoding::types::extern_name(&func.name, func.external_id.as_deref()),
933949
ComponentExportKind::Func,
934950
i as u32,
935951
Some(ComponentTypeRef::Func(ty)),
@@ -994,26 +1010,30 @@ impl<'a> EncodingState<'a> {
9941010
fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) {
9951011
self.component.type_function(None)
9961012
}
997-
fn export_type(&mut self, idx: u32, name: &'a str) -> Option<u32> {
1013+
fn export_type(
1014+
&mut self,
1015+
idx: u32,
1016+
name: wasm_encoder::ComponentExternName<'a>,
1017+
) -> Option<u32> {
9981018
if self.export_types {
9991019
Some(
10001020
self.component
10011021
.export(name, ComponentExportKind::Type, idx, None),
10021022
)
10031023
} else {
1004-
let name = self.unique_import_name(name);
1024+
let name = self.unique_import_name(&name.name);
10051025
let ret = self
10061026
.component
10071027
.import(&name, ComponentTypeRef::Type(TypeBounds::Eq(idx)));
10081028
self.imports.insert(name, ret);
10091029
Some(ret)
10101030
}
10111031
}
1012-
fn export_resource(&mut self, name: &'a str) -> u32 {
1032+
fn export_resource(&mut self, name: wasm_encoder::ComponentExternName<'a>) -> u32 {
10131033
if self.export_types {
10141034
panic!("resources should already be exported")
10151035
} else {
1016-
let name = self.unique_import_name(name);
1036+
let name = self.unique_import_name(&name.name);
10171037
let ret = self
10181038
.component
10191039
.import(&name, ComponentTypeRef::Type(TypeBounds::SubResource));
@@ -2944,6 +2964,7 @@ impl<'a> Shims<'a> {
29442964
docs: Default::default(),
29452965
stability: Stability::Unknown,
29462966
span: Default::default(),
2967+
external_id: None,
29472968
},
29482969
if async_ {
29492970
AbiVariant::GuestImportAsync
@@ -3046,6 +3067,7 @@ fn task_return_options_and_type(
30463067
docs: Default::default(),
30473068
stability: Stability::Unknown,
30483069
span: Default::default(),
3070+
external_id: None,
30493071
};
30503072
let abi = AbiVariant::GuestImport;
30513073
let mut options = RequiredOptions::for_import(resolve, func, abi);

crates/wit-component/src/encoding/types.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,11 @@ pub trait ValtypeEncoder<'a> {
107107
fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>);
108108

109109
/// Creates an export item for the specified type index.
110-
fn export_type(&mut self, index: u32, name: &'a str) -> Option<u32>;
110+
fn export_type(&mut self, index: u32, name: ComponentExternName<'a>) -> Option<u32>;
111111

112112
/// Creates a new `(type (sub resource))` export with the given name,
113113
/// returning the type index that refers to the fresh type created.
114-
fn export_resource(&mut self, name: &'a str) -> u32;
114+
fn export_resource(&mut self, name: ComponentExternName<'a>) -> u32;
115115

116116
/// Returns the encoding maps used to encoding types such as id-to-index
117117
/// maps.
@@ -235,7 +235,8 @@ pub trait ValtypeEncoder<'a> {
235235
TypeDefKind::Unknown => unreachable!(),
236236
TypeDefKind::Resource => {
237237
let name = ty.name.as_ref().expect("resources must be named");
238-
let index = self.export_resource(name);
238+
let index =
239+
self.export_resource(extern_name(name, ty.external_id.as_deref()));
239240
self.type_encoding_maps().id_to_index.insert(id, index);
240241
return Ok(ComponentValType::Type(index));
241242
}
@@ -270,7 +271,9 @@ pub trait ValtypeEncoder<'a> {
270271
index
271272
}
272273
};
273-
let index = self.export_type(index, name).unwrap_or(index);
274+
let index = self
275+
.export_type(index, extern_name(name, ty.external_id.as_deref()))
276+
.unwrap_or(index);
274277

275278
encoded = ComponentValType::Type(index);
276279
}
@@ -429,6 +432,17 @@ pub trait ValtypeEncoder<'a> {
429432
}
430433
}
431434

435+
/// Helper to create a `ComponentExternName` from its component parts found
436+
/// within a WIT AST node.
437+
pub fn extern_name<'a>(name: &'a str, external_id: Option<&'a str>) -> ComponentExternName<'a> {
438+
ComponentExternName {
439+
name: name.into(),
440+
implements: None,
441+
external_id: external_id.map(|s| s.into()),
442+
version_suffix: None,
443+
}
444+
}
445+
432446
pub struct RootTypeEncoder<'state, 'a> {
433447
pub state: &'state mut EncodingState<'a>,
434448
pub interface: Option<InterfaceId>,
@@ -445,7 +459,7 @@ impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> {
445459
fn interface(&self) -> Option<InterfaceId> {
446460
self.interface
447461
}
448-
fn export_type(&mut self, idx: u32, name: &'a str) -> Option<u32> {
462+
fn export_type(&mut self, idx: u32, name: ComponentExternName<'a>) -> Option<u32> {
449463
// When encoding types for the root the root component will export
450464
// this type, but when encoding types for a targeted interface then we
451465
// can't export types just yet. Interfaces will be created as an
@@ -466,7 +480,7 @@ impl<'a> ValtypeEncoder<'a> for RootTypeEncoder<'_, 'a> {
466480
None
467481
}
468482
}
469-
fn export_resource(&mut self, name: &'a str) -> u32 {
483+
fn export_resource(&mut self, name: ComponentExternName<'a>) -> u32 {
470484
assert!(self.interface.is_none());
471485
assert!(self.import_types);
472486
self.state
@@ -495,13 +509,13 @@ impl<'a> ValtypeEncoder<'a> for InstanceTypeEncoder<'_, 'a> {
495509
fn define_function_type(&mut self) -> (u32, ComponentFuncTypeEncoder<'_>) {
496510
(self.ty.type_count(), self.ty.ty().function())
497511
}
498-
fn export_type(&mut self, idx: u32, name: &str) -> Option<u32> {
512+
fn export_type(&mut self, idx: u32, name: ComponentExternName<'a>) -> Option<u32> {
499513
let ret = self.ty.type_count();
500514
self.ty
501515
.export(name, ComponentTypeRef::Type(TypeBounds::Eq(idx)));
502516
Some(ret)
503517
}
504-
fn export_resource(&mut self, name: &str) -> u32 {
518+
fn export_resource(&mut self, name: ComponentExternName<'a>) -> u32 {
505519
let ret = self.ty.type_count();
506520
self.ty
507521
.export(name, ComponentTypeRef::Type(TypeBounds::SubResource));

crates/wit-component/src/encoding/wit.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::encoding::types::{TypeEncodingMaps, ValtypeEncoder};
1+
use crate::encoding::types::{TypeEncodingMaps, ValtypeEncoder, extern_name};
22
use anyhow::Result;
33
use indexmap::IndexSet;
44
use std::collections::HashMap;
@@ -295,10 +295,10 @@ impl InterfaceEncoder<'_> {
295295

296296
for (name, func) in funcs {
297297
let ty = self.encode_func_type(self.resolve, func)?;
298-
self.ty
299-
.as_mut()
300-
.unwrap()
301-
.export(name, ComponentTypeRef::Func(ty));
298+
self.ty.as_mut().unwrap().export(
299+
extern_name(name, func.external_id.as_deref()),
300+
ComponentTypeRef::Func(ty),
301+
);
302302
}
303303
let instance = self.pop_instance();
304304
let idx = self.outer.type_count();
@@ -335,7 +335,7 @@ impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> {
335335
None => (self.outer.type_count(), self.outer.ty().function()),
336336
}
337337
}
338-
fn export_type(&mut self, index: u32, name: &'a str) -> Option<u32> {
338+
fn export_type(&mut self, index: u32, name: ComponentExternName<'a>) -> Option<u32> {
339339
match &mut self.ty {
340340
Some(ty) => {
341341
assert!(!self.import_types);
@@ -356,7 +356,7 @@ impl<'a> ValtypeEncoder<'a> for InterfaceEncoder<'a> {
356356
}
357357
}
358358
}
359-
fn export_resource(&mut self, name: &'a str) -> u32 {
359+
fn export_resource(&mut self, name: ComponentExternName<'a>) -> u32 {
360360
let type_ref = ComponentTypeRef::Type(TypeBounds::SubResource);
361361
match &mut self.ty {
362362
Some(ty) => {

crates/wit-component/src/encoding/world.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,14 @@ impl<'a> ComponentWorld<'a> {
261261
WorldItem::Interface { id, .. } => Some(*id),
262262
};
263263
let implements = resolve.implements_value(key, item);
264-
let external_id = resolve.external_id_value(key, item);
264+
// Note that `external_id` is only tracked for interface imports
265+
// here. World-level functions and types all share the `None` entry
266+
// in `import_map` but each item can have its own `external-id`
267+
// which is emitted on a per-item basis instead.
268+
let external_id = match item {
269+
WorldItem::Function(_) | WorldItem::Type { .. } => None,
270+
WorldItem::Interface { .. } => resolve.external_id_value(key, item),
271+
};
265272
let interface = import_map
266273
.entry(import_map_key)
267274
.or_insert_with(|| ImportedInterface {

crates/wit-component/src/printing.rs

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ impl<O: Output> WitPrinter<O> {
171171
self.new_item();
172172
self.print_docs(&func.docs);
173173
self.print_stability(&func.stability);
174+
self.print_external_id(func.external_id.as_deref());
174175
self.print_name_type(func.item_name(), TypeKind::FunctionFreestanding);
175176
self.output.str(": ");
176177
self.print_function(resolve, func)?;
@@ -193,29 +194,40 @@ impl<O: Output> WitPrinter<O> {
193194
// Partition types defined in this interface into either those imported
194195
// from foreign interfaces or those defined locally.
195196
let mut types_to_declare = Vec::new();
196-
let mut types_to_import: Vec<(_, &_, Vec<_>)> = Vec::new();
197+
let mut types_to_import: Vec<(_, &TypeDef, Vec<_>)> = Vec::new();
197198
for (name, ty_id) in types {
198199
let ty = &resolve.types[ty_id];
200+
201+
// If `ty` points to another type, `other`, then this might actually
202+
// be a `use`.
199203
if let TypeDefKind::Type(Type::Id(other)) = ty.kind {
200204
let other = &resolve.types[other];
201205
match other.owner {
202206
TypeOwner::None => {}
207+
208+
// `use` is only applicable when the owner of the current
209+
// set of types is different than the owner of `other`. Once
210+
// this is detected `types_to_import` is going to get
211+
// modified.
203212
other_owner if owner != other_owner => {
204213
let other_name = other
205214
.name
206215
.as_ref()
207216
.ok_or_else(|| anyhow!("cannot import unnamed type"))?;
208-
if let Some((owner, stability, list)) = types_to_import.last_mut() {
209-
if *owner == other_owner && ty.stability == **stability {
217+
218+
// As a convenience push onto the last set of types to
219+
// import if it's to the same interface and with
220+
// matching attributes.
221+
if let Some((prev_owner, prev_ty, list)) = types_to_import.last_mut() {
222+
if *prev_owner == other_owner
223+
&& ty.stability == prev_ty.stability
224+
&& ty.external_id == prev_ty.external_id
225+
{
210226
list.push((name, other_name));
211227
continue;
212228
}
213229
}
214-
types_to_import.push((
215-
other_owner,
216-
&ty.stability,
217-
vec![(name, other_name)],
218-
));
230+
types_to_import.push((other_owner, ty, vec![(name, other_name)]));
219231
continue;
220232
}
221233
_ => {}
@@ -231,9 +243,10 @@ impl<O: Output> WitPrinter<O> {
231243
TypeOwner::World(id) => resolve.worlds[id].package.unwrap(),
232244
TypeOwner::None => unreachable!(),
233245
};
234-
for (owner, stability, tys) in types_to_import {
246+
for (owner, ty, tys) in types_to_import {
235247
self.any_items = true;
236-
self.print_stability(stability);
248+
self.print_stability(&ty.stability);
249+
self.print_external_id(ty.external_id.as_deref());
237250
self.output.keyword("use");
238251
self.output.str(" ");
239252
let id = match owner {
@@ -266,6 +279,7 @@ impl<O: Output> WitPrinter<O> {
266279
self.new_item();
267280
self.print_docs(&resolve.types[id].docs);
268281
self.print_stability(&resolve.types[id].stability);
282+
self.print_external_id(resolve.types[id].external_id.as_deref());
269283
match resolve.types[id].kind {
270284
TypeDefKind::Resource => self.print_resource(
271285
resolve,
@@ -295,6 +309,7 @@ impl<O: Output> WitPrinter<O> {
295309
for func in funcs {
296310
self.print_docs(&func.docs);
297311
self.print_stability(&func.stability);
312+
self.print_external_id(func.external_id.as_deref());
298313

299314
match &func.kind {
300315
FunctionKind::Constructor(_) => {}
@@ -457,15 +472,7 @@ impl<O: Output> WitPrinter<O> {
457472
// `docs`); for an inline `import x: interface { .. }` with no statement
458473
// docs fall back to the interface definition's docs.
459474
let docs = match item {
460-
WorldItem::Interface {
461-
id,
462-
docs,
463-
external_id,
464-
..
465-
} => {
466-
if let Some(id) = external_id {
467-
self.print_external_id(id);
468-
}
475+
WorldItem::Interface { id, docs, .. } => {
469476
if docs.contents.is_some() {
470477
Some(docs)
471478
} else if matches!(name, WorldKey::Name(_)) {
@@ -483,6 +490,7 @@ impl<O: Output> WitPrinter<O> {
483490
}
484491

485492
self.print_stability(item.stability(resolve));
493+
self.print_external_id(resolve.external_id_value(name, item).as_deref());
486494
self.output.keyword(import_or_export_keyword);
487495
self.output.str(" ");
488496
match name {
@@ -1185,7 +1193,10 @@ impl<O: Output> WitPrinter<O> {
11851193
}
11861194
}
11871195

1188-
fn print_external_id(&mut self, id: &str) {
1196+
fn print_external_id(&mut self, id: Option<&str>) {
1197+
let Some(id) = id else {
1198+
return;
1199+
};
11891200
self.output.keyword("@external-id");
11901201
self.output.str("(\"");
11911202
let mut buf = [0; 4];

0 commit comments

Comments
 (0)