Repository: bytecodealliance/wasmtime
Pull request #7645 (2023-12-06 08:37:44)

Better error message for wrong import implementation
This changes improve the error message when a component is importing/exporting something that the linker is either missing or has the wrong type.

Previously the error message for a missing function implementation would be:

```
Error: import `my-import` has the wrong type

Caused by:
    expected func found nothing
```

Now, this error will be:

```
Error: component imports `my-import`, but a correct implementation of `my-import` could not be found

Caused by:
    function implementation is missing
```

I went back and forth on whether the error message should reference the linker. For direct users of `Linker` this would surely help, but this error message also gets displayed to indirect users of `Linker` so talking at a higher level of abstraction seemed appropriate. 

rylev: @peterhuene I believe I've addressed your feedback. I agree that "implementation" isn't always the right word, but I think we'll need to let this sit for a while before the right word makes itself apparent. 

Patch:
diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs
index 9c5220336..26ae6bc3e 100644
--- a/cranelift/codegen/src/settings.rs
+++ b/cranelift/codegen/src/settings.rs
@@ -231,21 +231,10 @@ fn parse_bool_value(value: &str) -> SetResult<bool> {
 fn parse_enum_value(value: &str, choices: &[&str]) -> SetResult<u8> {
     match choices.iter().position(|&tag| tag == value) {
         Some(idx) => Ok(idx as u8),
-        None => {
-            // TODO: Use `join` instead of this code, once
-            // https://github.com/rust-lang/rust/issues/27747 is resolved.
-            let mut all_choices = String::new();
-            let mut first = true;
-            for choice in choices {
-                if first {
-                    first = false
-                } else {
-                    all_choices += ", ";
-                }
-                all_choices += choice;
-            }
-            Err(SetError::BadValue(format!("any among {}", all_choices)))
-        }
+        None => Err(SetError::BadValue(format!(
+            "any among {}",
+            choices.join(", ")
+        ))),
     }
 }
 
diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs
index 489b5707e..eecf00cf9 100644
--- a/crates/environ/src/component/types.rs
+++ b/crates/environ/src/component/types.rs
@@ -1007,7 +1007,7 @@ pub enum TypeDef {
     ComponentInstance(TypeComponentInstanceIndex),
     /// A component function, not to be confused with a core wasm function.
     ComponentFunc(TypeFuncIndex),
-    /// An interface type.
+    /// An type in an interface.
     Interface(InterfaceType),
     /// A core wasm module and its type.
     Module(TypeModuleIndex),
@@ -1020,6 +1020,21 @@ pub enum TypeDef {
     Resource(TypeResourceTableIndex),
 }
 
+impl TypeDef {
+    /// A human readable description of what kind of type definition this is.
+    pub fn desc(&self) -> &str {
+        match self {
+            TypeDef::Component(_) => "component",
+            TypeDef::ComponentInstance(_) => "instance",
+            TypeDef::ComponentFunc(_) => "function",
+            TypeDef::Interface(_) => "type",
+            TypeDef::Module(_) => "core module",
+            TypeDef::CoreFunc(_) => "core function",
+            TypeDef::Resource(_) => "resource",
+        }
+    }
+}
+
 // NB: Note that maps below are stored as an `IndexMap` now but the order
 // typically does not matter. As a minor implementation detail we want the
 // serialization of this type to always be deterministic and using `IndexMap`
diff --git a/crates/wasmtime/src/runtime/component/linker.rs b/crates/wasmtime/src/runtime/component/linker.rs
index 00bcb0a52..5c3847082 100644
--- a/crates/wasmtime/src/runtime/component/linker.rs
+++ b/crates/wasmtime/src/runtime/component/linker.rs
@@ -208,7 +208,7 @@ impl<T> Linker<T> {
         for (_idx, (name, ty)) in env_component.import_types.iter() {
             let import = self.map.get(name, &self.strings);
             cx.definition(ty, import)
-                .with_context(|| format!("import `{name}` has the wrong type"))?;
+                .with_context(|| format!("component imports {desc} `{name}`, but a matching implementation was not found in the linker", desc = ty.desc()))?;
         }
         Ok(cx)
     }
diff --git a/crates/wasmtime/src/runtime/component/matching.rs b/crates/wasmtime/src/runtime/component/matching.rs
index 7c47d98c9..7c8e36a79 100644
--- a/crates/wasmtime/src/runtime/component/matching.rs
+++ b/crates/wasmtime/src/runtime/component/matching.rs
@@ -36,19 +36,27 @@ impl TypeChecker<'_> {
         match *expected {
             TypeDef::Module(t) => match actual {
                 Some(Definition::Module(actual)) => self.module(&self.types[t], actual),
-                _ => bail!("expected module found {}", desc(actual)),
+                Some(actual) => bail!("expected module found {}", actual.desc()),
+                None => bail!("module implementation is missing"),
             },
             TypeDef::ComponentInstance(t) => match actual {
                 Some(Definition::Instance(actual)) => self.instance(&self.types[t], Some(actual)),
                 None => self.instance(&self.types[t], None),
-                _ => bail!("expected instance found {}", desc(actual)),
+                Some(actual) => bail!("expected instance found {}", actual.desc()),
             },
             TypeDef::ComponentFunc(t) => match actual {
                 Some(Definition::Func(actual)) => self.func(t, actual),
-                _ => bail!("expected func found {}", desc(actual)),
+                Some(actual) => bail!("expected function found {}", actual.desc()),
+                None => bail!("function implementation is missing"),
+            },
+            TypeDef::Component(_) => match actual {
+                Some(actual) => bail!("expected component found {}", actual.desc()),
+                None => bail!("component implementation is missing"),
+            },
+            TypeDef::Interface(_) => match actual {
+                Some(actual) => bail!("expected type found {}", actual.desc()),
+                None => bail!("type implementation is missing"),
             },
-            TypeDef::Component(_) => bail!("expected component found {}", desc(actual)),
-            TypeDef::Interface(_) => bail!("expected type found {}", desc(actual)),
 
             TypeDef::Resource(i) => {
                 let i = self.types[i].ty;
@@ -63,7 +71,8 @@ impl TypeChecker<'_> {
                     // Wasmtime API.
                     None if self.imported_resources.get(i).is_some() => return Ok(()),
 
-                    _ => bail!("expected resource found {}", desc(actual)),
+                    Some(actual) => bail!("expected resource found {}", actual.desc()),
+                    None => bail!("resource implementation is missing"),
                 };
 
                 match self.imported_resources.get(i) {
@@ -165,13 +174,6 @@ impl TypeChecker<'_> {
     }
 }
 
-fn desc(def: Option<&Definition>) -> &'static str {
-    match def {
-        Some(def) => def.desc(),
-        None => "nothing",
-    }
-}
-
 impl Definition {
     fn desc(&self) -> &'static str {
         match self {
diff --git a/docs/cli-options.md b/docs/cli-options.md
index d6ffe74ad..6cf534380 100644
--- a/docs/cli-options.md
+++ b/docs/cli-options.md
@@ -52,6 +52,29 @@ an exported function of the module to run.
 $ wasmtime run foo.wasm --invoke initialize
 ```
 
+## `serve`
+
+The `serve` subcommand runs a WebAssembly component in the `wasi:http/proxy`
+world via the WASI HTTP API, which is available since Wasmtime 18.0.0. The goal
+of this world is to support sending and receiving HTTP requests.
+
+The `serve` command takes one positional argument which is the name of the
+component to run:
+
+```sh
+$ wasmtime serve foo.wasm
+```
+
+Furthermore, an address can be specified via:
+
+```sh
+$ wasmtime serve --addr=0.0.0.0:8081 foo.wasm
+```
+
+At the time of writing, the `wasi:http/proxy` world is still experimental and
+requires setup of some `wit` dependencies. For more information, see
+the [hello-wasi-http](https://github.com/sunfishcode/hello-wasi-http/) example.
+
 ## `wast`
 
 The `wast` command executes a `*.wast` file which is the test format for the


Test patch:
diff --git a/tests/misc_testsuite/component-model/linking.wast b/tests/misc_testsuite/component-model/linking.wast
index 7cf4aad7d..966926f08 100644
--- a/tests/misc_testsuite/component-model/linking.wast
+++ b/tests/misc_testsuite/component-model/linking.wast
@@ -2,7 +2,7 @@
   (component
     (import "undefined-name" (core module))
   )
-  "expected module found nothing")
+  "was not found")
 (component $i)
 (component
   (import "i" (instance))
@@ -12,7 +12,7 @@
   "expected module found instance")
 (assert_unlinkable
   (component (import "i" (func)))
-  "expected func found instance")
+  "expected function found instance")
 (assert_unlinkable
   (component (import "i" (instance (export "x" (func)))))
-  "expected func found nothing")
+  "was not found")
diff --git a/tests/misc_testsuite/component-model/resources.wast b/tests/misc_testsuite/component-model/resources.wast
index 351ed85b3..c2868f838 100644
--- a/tests/misc_testsuite/component-model/resources.wast
+++ b/tests/misc_testsuite/component-model/resources.wast
@@ -167,7 +167,7 @@
       (export "missing" (type (sub resource)))
     ))
   )
-  "expected resource found nothing")
+  "was not found")
 (assert_unlinkable
   (component
     (import "host" (instance