Skip to content

Commit

Permalink
WIT: Implement @SInCE and @unstable annotations (#1508)
Browse files Browse the repository at this point in the history
* Add a span to all types when parsing

* Preserve error context when highlighting errors

Previously the entire error message was replaced, losing any attached
context. This commit updates error highlighting to only augment the
single error found in the chain that's being highlighted (in the most
common case). This required a small refactoring of the `Error` type and
changes all existing users to a method-based constructor rather than
explicit struct-based construction.

* Add initial parsing of attributes

Nothing uses the results of parsing yet, that's going to come in a
future commit.

* Record spans for all types in `UnresolvedPackage`

Will be used for errors in a future commit.

* Push stability attributes into top-level AST

This commit pushes stability attributes through the resolution process
to the next stage of AST. The top-level user-facing types in
`wit-parser` now have `Stability` annotations were they can be added.

This commit notably changes the `WorldItem::Interface` enum variant to
contain a stability attribute in addition to the id listed.

* Filter out `@unstable` items that aren't enabled

This finishes support for `@unstable` and `@since` in `Resolve` by
handling all items there and specifically filtering out any disabled
items.

* Add CLI support for WIT features

* Implement printing WIT stability attributes

* Round-trip stability through the wasm binary format

This involved a number of refactorings and "tricks" to get this to work
out. Namely when possible the old format of the custom section is still
emitted to ensure older/newer tools can interoperate when possible.

* Fix compile

* Fix some compile warnings
  • Loading branch information
alexcrichton authored May 28, 2024
1 parent 063f48f commit 01bec9c
Show file tree
Hide file tree
Showing 74 changed files with 3,862 additions and 1,118 deletions.
6 changes: 3 additions & 3 deletions crates/wit-component/src/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec<u8> {
push_tys(&mut wat, "result", &sig.results);
wat.push_str("))\n");
}
WorldItem::Interface(import) => {
WorldItem::Interface { id: import, .. } => {
let name = resolve.name_world_key(name);
for (_, func) in resolve.interfaces[*import].functions.iter() {
let sig = resolve.wasm_signature(AbiVariant::GuestImport, func);
Expand All @@ -39,7 +39,7 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec<u8> {
// Import any resource-related functions for exports.
for (name, export) in world.exports.iter() {
let export = match export {
WorldItem::Interface(export) => *export,
WorldItem::Interface { id, .. } => *id,
_ => continue,
};
let module = format!("[export]{}", resolve.name_world_key(name));
Expand All @@ -64,7 +64,7 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec<u8> {
WorldItem::Function(func) => {
push_func(&mut wat, &func.name, resolve, func);
}
WorldItem::Interface(export) => {
WorldItem::Interface { id: export, .. } => {
let name = resolve.name_world_key(name);
for (_, func) in resolve.interfaces[*export].functions.iter() {
let name = func.core_export_name(Some(&name));
Expand Down
8 changes: 4 additions & 4 deletions crates/wit-component/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ impl<'a> EncodingState<'a> {
for (name, item) in resolve.worlds[world].imports.iter() {
let func = match item {
WorldItem::Function(f) => f,
WorldItem::Interface(_) | WorldItem::Type(_) => continue,
WorldItem::Interface { .. } | WorldItem::Type(_) => continue,
};
let name = resolve.name_world_key(name);
if !info.lowerings.contains_key(&name) {
Expand Down Expand Up @@ -751,8 +751,8 @@ impl<'a> EncodingState<'a> {
self.component
.export(&export_string, ComponentExportKind::Func, idx, None);
}
WorldItem::Interface(export) => {
self.encode_interface_export(&export_string, module, *export)?;
WorldItem::Interface { id, .. } => {
self.encode_interface_export(&export_string, module, *id)?;
}
WorldItem::Type(_) => unreachable!(),
}
Expand Down Expand Up @@ -2001,7 +2001,7 @@ impl ComponentEncoder {
.with_context(|| {
format!("failed to merge WIT packages of adapter `{name}` into main packages")
})?
.worlds[metadata.world.index()];
.map_world(metadata.world, None)?;
self.metadata
.resolve
.merge_worlds(world, self.metadata.world)
Expand Down
18 changes: 9 additions & 9 deletions crates/wit-component/src/encoding/wit/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ pub fn encode_component(resolve: &Resolve, package: PackageId) -> Result<Compone
};
encoder.run()?;

let package_docs = PackageDocs::extract(resolve, package);
let package_metadata = PackageMetadata::extract(resolve, package);
encoder.component.custom_section(&CustomSection {
name: PackageDocs::SECTION_NAME.into(),
data: package_docs.encode()?.into(),
name: PackageMetadata::SECTION_NAME.into(),
data: package_metadata.encode()?.into(),
});

Ok(encoder.component)
Expand Down Expand Up @@ -351,9 +351,9 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result<ComponentTyp
let name = resolve.name_world_key(name);
log::trace!("encoding import {name}");
let ty = match import {
WorldItem::Interface(i) => {
component.interface = Some(*i);
let idx = component.encode_instance(*i)?;
WorldItem::Interface { id, .. } => {
component.interface = Some(*id);
let idx = component.encode_instance(*id)?;
ComponentTypeRef::Instance(idx)
}
WorldItem::Function(f) => {
Expand All @@ -376,9 +376,9 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result<ComponentTyp
let name = resolve.name_world_key(name);
log::trace!("encoding export {name}");
let ty = match export {
WorldItem::Interface(i) => {
component.interface = Some(*i);
let idx = component.encode_instance(*i)?;
WorldItem::Interface { id, .. } => {
component.interface = Some(*id);
let idx = component.encode_instance(*id)?;
ComponentTypeRef::Instance(idx)
}
WorldItem::Function(f) => {
Expand Down
6 changes: 3 additions & 3 deletions crates/wit-component/src/encoding/wit/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ pub fn encode_component(resolve: &Resolve, package: PackageId) -> Result<Compone
};
encoder.run()?;

let package_docs = PackageDocs::extract(resolve, package);
let package_metadata = PackageMetadata::extract(resolve, package);
encoder.component.custom_section(&CustomSection {
name: PackageDocs::SECTION_NAME.into(),
data: package_docs.encode()?.into(),
name: PackageMetadata::SECTION_NAME.into(),
data: package_metadata.encode()?.into(),
});

Ok(encoder.component)
Expand Down
18 changes: 10 additions & 8 deletions crates/wit-component/src/encoding/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ impl<'a> ComponentWorld<'a> {
.iter()
.all(|name| match &resolve.worlds[world].exports[name] {
WorldItem::Function(_) => false,
WorldItem::Interface(id) => resolve.interfaces[*id].functions.is_empty(),
WorldItem::Interface { id, .. } => {
resolve.interfaces[*id].functions.is_empty()
}
WorldItem::Type(_) => true,
})
};
Expand Down Expand Up @@ -209,7 +211,7 @@ impl<'a> ComponentWorld<'a> {
for name in required_exports {
match &resolve.worlds[world].exports[name] {
WorldItem::Function(func) => add_func(func, None),
WorldItem::Interface(id) => {
WorldItem::Interface { id, .. } => {
let name = resolve.name_world_key(name);
for (_, func) in resolve.interfaces[*id].functions.iter() {
add_func(func, Some(&name));
Expand Down Expand Up @@ -277,11 +279,11 @@ impl<'a> ComponentWorld<'a> {
let empty = IndexSet::new();
let import_map_key = match item {
WorldItem::Function(_) | WorldItem::Type(_) => None,
WorldItem::Interface(_) => Some(name),
WorldItem::Interface { .. } => Some(name),
};
let interface_id = match item {
WorldItem::Function(_) | WorldItem::Type(_) => None,
WorldItem::Interface(id) => Some(*id),
WorldItem::Interface { id, .. } => Some(*id),
};
let required = required
.get(import_map_key.as_deref().unwrap_or(BARE_FUNC_MODULE_NAME))
Expand All @@ -300,7 +302,7 @@ impl<'a> ComponentWorld<'a> {
WorldItem::Type(ty) => {
interface.add_type(required, resolve, *ty);
}
WorldItem::Interface(id) => {
WorldItem::Interface { id, .. } => {
for (_name, ty) in resolve.interfaces[*id].types.iter() {
interface.add_type(required, resolve, *ty);
}
Expand Down Expand Up @@ -344,7 +346,7 @@ impl<'a> ComponentWorld<'a> {
for (name, item) in resolve.worlds[world].exports.iter() {
log::trace!("add live world export `{}`", resolve.name_world_key(name));
let id = match item {
WorldItem::Interface(id) => id,
WorldItem::Interface { id, .. } => id,
WorldItem::Function(_) | WorldItem::Type(_) => {
live.add_world_item(resolve, item);
continue;
Expand Down Expand Up @@ -400,7 +402,7 @@ impl<'a> ComponentWorld<'a> {
log::trace!("add live function import `{name}`");
live.add_func(resolve, func);
}
WorldItem::Interface(id) => {
WorldItem::Interface { id, .. } => {
let required = match required.get(name.as_str()) {
Some(set) => set,
None => continue,
Expand Down Expand Up @@ -431,7 +433,7 @@ impl<'a> ComponentWorld<'a> {
for (_name, item) in exports.iter() {
let id = match item {
WorldItem::Function(_) => continue,
WorldItem::Interface(id) => *id,
WorldItem::Interface { id, .. } => *id,
WorldItem::Type(_) => unreachable!(),
};
let mut set = HashSet::new();
Expand Down
11 changes: 6 additions & 5 deletions crates/wit-component/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ impl Default for Bindgen {
includes: Default::default(),
include_names: Default::default(),
package: Some(package),
stability: Default::default(),
});
resolve.packages[package]
.worlds
Expand Down Expand Up @@ -306,7 +307,7 @@ impl Bindgen {
.resolve
.merge(resolve)
.context("failed to merge WIT package sets together")?
.worlds[world.index()];
.map_world(world, None)?;
self.resolve
.merge_worlds(world, self.world)
.context("failed to merge worlds from two documents")?;
Expand Down Expand Up @@ -361,8 +362,8 @@ impl ModuleMetadata {
.insert((BARE_FUNC_MODULE_NAME.to_string(), name.clone()), encoding);
assert!(prev.is_none());
}
WorldItem::Interface(i) => {
for (func, _) in resolve.interfaces[*i].functions.iter() {
WorldItem::Interface { id, .. } => {
for (func, _) in resolve.interfaces[*id].functions.iter() {
let prev = ret
.import_encodings
.insert((name.clone(), func.clone()), encoding);
Expand All @@ -381,8 +382,8 @@ impl ModuleMetadata {
let prev = ret.export_encodings.insert(name.clone(), encoding);
assert!(prev.is_none());
}
WorldItem::Interface(i) => {
for (_, func) in resolve.interfaces[*i].functions.iter() {
WorldItem::Interface { id, .. } => {
for (_, func) in resolve.interfaces[*id].functions.iter() {
let name = func.core_export_name(Some(&name)).into_owned();
let prev = ret.export_encodings.insert(name, encoding);
assert!(prev.is_none());
Expand Down
55 changes: 42 additions & 13 deletions crates/wit-component/src/printing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ impl WitPrinter {
self.output.push_str("\n\n");
for (name, id) in pkg.interfaces.iter() {
self.print_docs(&resolve.interfaces[*id].docs);
self.print_stability(&resolve.interfaces[*id].stability);
self.output.push_str("interface ");
self.print_name(name);
self.output.push_str(" {\n");
Expand All @@ -74,6 +75,7 @@ impl WitPrinter {

for (name, id) in pkg.worlds.iter() {
self.print_docs(&resolve.worlds[*id].docs);
self.print_stability(&resolve.worlds[*id].stability);
self.output.push_str("world ");
self.print_name(name);
self.output.push_str(" {\n");
Expand Down Expand Up @@ -125,6 +127,7 @@ impl WitPrinter {
for (name, func) in freestanding {
self.new_item();
self.print_docs(&func.docs);
self.print_stability(&func.stability);
self.print_name(name);
self.output.push_str(": ");
self.print_function(resolve, func)?;
Expand All @@ -147,7 +150,7 @@ impl WitPrinter {
// Partition types defined in this interface into either those imported
// from foreign interfaces or those defined locally.
let mut types_to_declare = Vec::new();
let mut types_to_import: Vec<(_, Vec<_>)> = Vec::new();
let mut types_to_import: Vec<(_, &_, Vec<_>)> = Vec::new();
for (name, ty_id) in types {
let ty = &resolve.types[ty_id];
if let TypeDefKind::Type(Type::Id(other)) = ty.kind {
Expand All @@ -159,13 +162,17 @@ impl WitPrinter {
.name
.as_ref()
.ok_or_else(|| anyhow!("cannot import unnamed type"))?;
if let Some((owner, list)) = types_to_import.last_mut() {
if *owner == other_owner {
if let Some((owner, stability, list)) = types_to_import.last_mut() {
if *owner == other_owner && ty.stability == **stability {
list.push((name, other_name));
continue;
}
}
types_to_import.push((other_owner, vec![(name, other_name)]));
types_to_import.push((
other_owner,
&ty.stability,
vec![(name, other_name)],
));
continue;
}
_ => {}
Expand All @@ -181,8 +188,9 @@ impl WitPrinter {
TypeOwner::World(id) => resolve.worlds[id].package.unwrap(),
TypeOwner::None => unreachable!(),
};
for (owner, tys) in types_to_import {
for (owner, stability, tys) in types_to_import {
self.any_items = true;
self.print_stability(stability);
write!(&mut self.output, "use ")?;
let id = match owner {
TypeOwner::Interface(id) => id,
Expand Down Expand Up @@ -212,6 +220,7 @@ impl WitPrinter {
for id in types_to_declare {
self.new_item();
self.print_docs(&resolve.types[id].docs);
self.print_stability(&resolve.types[id].stability);
match resolve.types[id].kind {
TypeDefKind::Resource => self.print_resource(
resolve,
Expand All @@ -236,17 +245,16 @@ impl WitPrinter {
}
self.output.push_str(" {\n");
for func in funcs {
self.print_docs(&func.docs);
self.print_stability(&func.stability);

match &func.kind {
FunctionKind::Constructor(_) => {
self.print_docs(&func.docs);
}
FunctionKind::Constructor(_) => {}
FunctionKind::Method(_) => {
self.print_docs(&func.docs);
self.print_name(func.item_name());
self.output.push_str(": ");
}
FunctionKind::Static(_) => {
self.print_docs(&func.docs);
self.print_name(func.item_name());
self.output.push_str(": ");
self.output.push_str("static ");
Expand Down Expand Up @@ -367,21 +375,22 @@ impl WitPrinter {
// Print inline item docs
if matches!(name, WorldKey::Name(_)) {
self.print_docs(match item {
WorldItem::Interface(id) => &resolve.interfaces[*id].docs,
WorldItem::Interface { id, .. } => &resolve.interfaces[*id].docs,
WorldItem::Function(f) => &f.docs,
// Types are handled separately
WorldItem::Type(_) => unreachable!(),
});
}

self.print_stability(item.stability(resolve));
self.output.push_str(desc);
self.output.push_str(" ");
match name {
WorldKey::Name(name) => {
self.print_name(name);
self.output.push_str(": ");
match item {
WorldItem::Interface(id) => {
WorldItem::Interface { id, .. } => {
assert!(resolve.interfaces[*id].name.is_none());
writeln!(self.output, "interface {{")?;
self.print_interface(resolve, *id)?;
Expand All @@ -398,7 +407,7 @@ impl WitPrinter {
}
WorldKey::Interface(id) => {
match item {
WorldItem::Interface(id2) => assert_eq!(id, id2),
WorldItem::Interface { id: id2, .. } => assert_eq!(id, id2),
_ => unreachable!(),
}
self.print_path_to_interface(resolve, *id, cur_pkg)?;
Expand Down Expand Up @@ -868,6 +877,26 @@ impl WitPrinter {
}
}
}

fn print_stability(&mut self, stability: &Stability) {
match stability {
Stability::Unknown => {}
Stability::Stable { since, feature } => {
self.output.push_str("@since(version = ");
self.output.push_str(&since.to_string());
if let Some(feature) = feature {
self.output.push_str(", feature = ");
self.output.push_str(feature);
}
self.output.push_str(")\n");
}
Stability::Unstable { feature } => {
self.output.push_str("@unstable(feature = ");
self.output.push_str(feature);
self.output.push_str(")\n");
}
}
}
}

fn resource_func(f: &Function) -> Option<TypeId> {
Expand Down
Loading

0 comments on commit 01bec9c

Please sign in to comment.