Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update syntax for trappable_error_type in bindgen! #7170

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 3 additions & 20 deletions crates/component-macro/src/bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,28 +318,11 @@ impl Parse for Opt {
}

fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<TrappableError> {
// Accept a Rust identifier or a string literal. This is required
// because not all wit identifiers are Rust identifiers, so we can
// smuggle the invalid ones inside quotes.
fn ident_or_str(input: ParseStream<'_>) -> Result<String> {
let l = input.lookahead1();
if l.peek(syn::LitStr) {
Ok(input.parse::<syn::LitStr>()?.value())
} else if l.peek(syn::Ident) {
Ok(input.parse::<syn::Ident>()?.to_string())
} else {
Err(l.error())
}
}

let wit_package_path = input.parse::<syn::LitStr>()?.value();
input.parse::<Token![::]>()?;
let wit_type_name = ident_or_str(input)?;
input.parse::<Token![:]>()?;
let wit_path = input.parse::<syn::LitStr>()?.value();
input.parse::<Token![=>]>()?;
let rust_type_name = input.parse::<syn::Path>()?.to_token_stream().to_string();
Ok(TrappableError {
wit_package_path,
wit_type_name,
wit_path,
rust_type_name,
})
}
Expand Down
4 changes: 2 additions & 2 deletions crates/component-macro/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ mod trappable_errors {
}
",
trappable_error_type: {
"demo:pkg/a"::"b": MyX,
"demo:pkg/c"::"b": MyX,
"demo:pkg/a/b" => MyX,
"demo:pkg/c/b" => MyX,
},
});

Expand Down
14 changes: 6 additions & 8 deletions crates/wasi/src/preview2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ pub mod bindings {
",
tracing: true,
trappable_error_type: {
"wasi:io/streams"::"stream-error": StreamError,
"wasi:filesystem/types"::"error-code": FsError,
"wasi:io/streams/stream-error" => StreamError,
"wasi:filesystem/types/error-code" => FsError,
},
with: {
"wasi:clocks/wall-clock": crate::preview2::bindings::clocks::wall_clock,
Expand Down Expand Up @@ -149,17 +149,15 @@ pub mod bindings {
"poll-one",
],
},
with: {
"wasi:sockets/ip-name-lookup/resolve-address-stream": super::ip_name_lookup::ResolveAddressStream,
},
trappable_error_type: {
"wasi:io/streams"::"stream-error": crate::preview2::StreamError,
"wasi:filesystem/types"::"error-code": crate::preview2::FsError,
"wasi:sockets/network"::"error-code": crate::preview2::SocketError,
"wasi:io/streams/stream-error" => crate::preview2::StreamError,
"wasi:filesystem/types/error-code" => crate::preview2::FsError,
"wasi:sockets/network/error-code" => crate::preview2::SocketError,
},
with: {
"wasi:sockets/network/network": super::network::Network,
"wasi:sockets/tcp/tcp-socket": super::tcp::TcpSocket,
"wasi:sockets/ip-name-lookup/resolve-address-stream": super::ip_name_lookup::ResolveAddressStream,
"wasi:filesystem/types/directory-entry-stream": super::filesystem::ReaddirIterator,
"wasi:filesystem/types/descriptor": super::filesystem::Descriptor,
"wasi:io/streams/input-stream": super::stream::InputStream,
Expand Down
11 changes: 6 additions & 5 deletions crates/wasmtime/src/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,15 @@ pub(crate) use self::store::ComponentStoreData;
///
/// // This can be used to translate WIT return values of the form
/// // `result<T, error-type>` into `Result<T, RustErrorType>` in Rust.
/// // The `RustErrorType` structure will have an automatically generated
/// // implementation of `From<ErrorType> for RustErrorType`. The
/// // `RustErrorType` additionally can also represent a trap to
/// // conveniently flatten all errors into one container.
/// // Users must define `RustErrorType` and the `Host` trait for the
/// // interface which defines `error-type` will have a method
/// // called `convert_error_type` which converts `RustErrorType`
/// // into `wasmtime::Result<ErrorType>`. This conversion can either
/// // return the raw WIT error (`ErrorType` here) or a trap.
/// //
/// // By default this option is not specified.
/// trappable_error_type: {
/// interface::ErrorType: RustErrorType,
/// "wasi:io/streams/stream-error" => RustErrorType,
/// },
///
/// // All generated bindgen types are "owned" meaning types like `String`
Expand Down
95 changes: 22 additions & 73 deletions crates/wit-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::rust::{to_rust_ident, to_rust_upper_camel_case, RustGenerator, TypeMode};
use crate::types::{TypeInfo, Types};
use anyhow::{anyhow, bail, Context};
use anyhow::{anyhow, Context};
use heck::*;
use indexmap::{IndexMap, IndexSet};
use std::collections::{BTreeMap, HashMap, HashSet};
Expand Down Expand Up @@ -114,11 +114,8 @@ pub struct Opts {

#[derive(Debug, Clone)]
pub struct TrappableError {
/// The package and interface that define the error type being mapped.
pub wit_package_path: String,

/// The name of the error type in WIT that is being mapped.
pub wit_type_name: String,
/// Full path to the error, such as `wasi:io/streams/error`.
pub wit_path: String,

/// The name, in Rust, of the error type to generate.
pub rust_type_name: String,
Expand Down Expand Up @@ -220,7 +217,7 @@ impl Wasmtime {
fn generate(&mut self, resolve: &Resolve, id: WorldId) -> String {
self.types.analyze(resolve, id);
for (i, te) in self.opts.trappable_error_type.iter().enumerate() {
let id = resolve_type_in_package(resolve, &te.wit_package_path, &te.wit_type_name)
let id = resolve_type_in_package(resolve, &te.wit_path)
.context(format!("resolving {:?}", te))
.unwrap();
let name = format!("_TrappableError{i}");
Expand Down Expand Up @@ -770,77 +767,29 @@ impl Wasmtime {
}
}

fn resolve_type_in_package(
resolve: &Resolve,
package_path: &str,
type_name: &str,
) -> anyhow::Result<TypeId> {
fn resolve_type_in_package(resolve: &Resolve, wit_path: &str) -> anyhow::Result<TypeId> {
// foo:bar/baz

let (namespace, rest) = package_path
.split_once(':')
.ok_or_else(|| anyhow!("Invalid package path: missing package identifier"))?;

let (package_name, iface_name) = rest
.split_once('/')
.ok_or_else(|| anyhow!("Invalid package path: missing namespace separator"))?;

// TODO: we should handle version annotations
if package_name.contains('@') {
bail!("Invalid package path: version parsing is not currently handled");
}

let packages = Vec::from_iter(
resolve
.package_names
.iter()
.filter(|(pname, _)| pname.namespace == namespace && pname.name == package_name),
);

if packages.len() != 1 {
if packages.is_empty() {
bail!("No package named `{}`", namespace);
} else {
// Getting here is a bug, parsing version identifiers would disambiguate the intended
// package.
bail!(
"Multiple packages named `{}` found ({:?})",
namespace,
packages
);
}
}

let (_, &package_id) = packages[0];
let package = &resolve.packages[package_id];

let (_, &iface_id) = package
.interfaces
let (_, interface) = resolve
.packages
.iter()
.find(|(name, _)| name.as_str() == iface_name)
.ok_or_else(|| {
anyhow!(
"Unknown interface `{}` in package `{}`",
iface_name,
package_path
)
})?;

let iface = &resolve.interfaces[iface_id];

let (_, &type_id) = iface
.flat_map(|(_, p)| p.interfaces.iter())
.find(|(_, id)| wit_path.starts_with(&resolve.id_of(**id).unwrap()))
.ok_or_else(|| anyhow!("no package/interface found to match `{wit_path}`"))?;

let wit_path = wit_path
.strip_prefix(&resolve.id_of(*interface).unwrap())
.unwrap();
let wit_path = wit_path
.strip_prefix('/')
.ok_or_else(|| anyhow!("expected `/` after interface name"))?;

let (_, id) = resolve.interfaces[*interface]
.types
.iter()
.find(|(n, _)| n.as_str() == type_name)
.ok_or_else(|| {
anyhow!(
"No type named `{}` in package `{}`",
package_name,
package_path
)
})?;

Ok(type_id)
.find(|(name, _)| wit_path == name.as_str())
.ok_or_else(|| anyhow!("no types found to match `{wit_path}` in interface"))?;
Ok(*id)
}

struct InterfaceGenerator<'a> {
Expand Down
10 changes: 4 additions & 6 deletions tests/all/component_model/bindgen/results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ mod enum_error {
enum-error: func(a: float64) -> result<float64, e1>
}
}",
trappable_error_type: { "inline:inline/imports"::e1: TrappableE1 }
trappable_error_type: { "inline:inline/imports/e1" => TrappableE1 }
});

// You can create concrete trap types which make it all the way out to the
Expand Down Expand Up @@ -418,9 +418,7 @@ mod record_error {
record-error: func(a: float64) -> result<float64, e2>
}
}",
// Literal strings can be used for the interface and typename fields instead of
// identifiers, because wit identifiers arent always Rust identifiers.
trappable_error_type: { "inline:inline/imports"::"e2": TrappableE2 }
trappable_error_type: { "inline:inline/imports/e2" => TrappableE2 }
});

pub enum TrappableE2 {
Expand Down Expand Up @@ -591,7 +589,7 @@ mod variant_error {
variant-error: func(a: float64) -> result<float64, e3>
}
}",
trappable_error_type: { "inline:inline/imports"::e3: TrappableE3 }
trappable_error_type: { "inline:inline/imports/e3" => TrappableE3 }
});

pub enum TrappableE3 {
Expand Down Expand Up @@ -786,7 +784,7 @@ mod multiple_interfaces_error {
enum-error: func(a: float64) -> result<float64, e1>
}
}",
trappable_error_type: { "inline:inline/types"::e1: TrappableE1 }
trappable_error_type: { "inline:inline/types/e1" => TrappableE1 }
});

pub enum TrappableE1 {
Expand Down