Skip to content
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
59 changes: 57 additions & 2 deletions docs/language-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ We document a lot so nothing feels hidden—but Rib is not heavyweight. For REPL

---

Companion WIT — [`example.wit`](example.wit) exports `inventory` (records, enums, plain funcs) and `shopping` (a `cart` resource) from world `guide-demo`. Start with [§1](#1-instance-and-calling-exports); [§0](#0-example-wit-examplewit) spells out the WIT if you want it on the page, and [§9 *`match`*](#9-match-and-patterns) onward when you need more than export calls.
Companion WIT — [`example.wit`](example.wit) exports `inventory` (records, enums, plain funcs) and `shopping` (a `cart` resource) from world `guide-demo`. Start with [§1](#1-instance-and-calling-exports); [§0](#0-example-wit-examplewit) spells out the WIT if you want it on the page, and [§9 *`match`*](#9-match-and-patterns) onward when you need more than export calls. [§16](#16-common-compilation-errors-examples) lists typical **compile-time** mistakes (arity, types, `match` coverage) with small broken snippets.

---

Expand All @@ -86,6 +86,7 @@ Companion WIT — [`example.wit`](example.wit) exports `inventory` (records, enu
13. [`option` and `result`](#13-option-and-result)
14. [String interpolation](#14-string-interpolation)
15. [Invoking resource methods](#15-invoking-resource-methods)
16. [Common compilation errors (examples)](#16-common-compilation-errors-examples)

---

Expand Down Expand Up @@ -425,6 +426,8 @@ match home {
}
```

<a id="sec-9-8"></a>

### 9.8 Compile-time errors (`match`, enums, calls)

Rib reports many mistakes **while compiling** Rib source (REPL line or script)—**before** your embedder invokes Wasm. A few common cases:
Expand All @@ -435,6 +438,8 @@ Rib reports many mistakes **while compiling** Rib source (REPL line or script)

**Calls** — Arguments are checked against the **`func`** signature. Example: **`validate-qty`** expects **`u32`**; passing a **string** or the wrong **record** shape to **`length`** is rejected **at compile time**, not as a failed Wasm call later.

Short **copy-paste examples** of invalid programs (and what goes wrong) are in [§16](#16-common-compilation-errors-examples).

---

## 10. List comprehensions (`for` … `yield`)
Expand Down Expand Up @@ -537,6 +542,56 @@ shopping-cart.line-count()

---

## 16. Common compilation errors (examples)

Assume [`example.wit`](example.wit) is loaded and **`let my-instance = instance();`** already exists. The snippets below are **invalid** Rib: the compiler rejects them **before** any Wasm runs. Exact wording of diagnostics can change between versions; the point is **why** each program fails.

### Wrong number of parameters

From WIT, **`validate-qty`** takes **one** `u32`, and **`length`** takes **one** `point` record:

```rust
// Invalid — missing argument
my-instance.validate-qty()

// Invalid — too many arguments
my-instance.length({ x: 1, y: 2 }, { x: 0, y: 0 })
```

Rib reports an **arity** / argument-count mismatch against the export’s signature.

### Type mismatch

**`validate-qty`** expects a **`u32`**, not a string; **`length`** expects a **`point`**, not a bare number:

```rust
// Invalid — string where u32 is required
my-instance.validate-qty("100")

// Invalid — s32 where a point record is required
my-instance.length(42)
```

The compiler compares your expression types to the WIT parameter types and stops with a **type** error.

### Non-exhaustive pattern match (`match`)

`variant payment-info` has **three** cases (`card`, `wallet`, `failed`). This `match` is **incomplete**—there is no arm for **`failed`**, and no **`_`** fallback:

```rust
// Invalid — not all variant cases covered (`p` has type `payment-info`)
match p {
card(_) => "c",
wallet => "w"
}
```

Either add **`failed(_) => …`** or a catch-all **`_ => …`**. The same idea applies to **`enum order-stage`**: every **`draft` / `placed` / `shipped`** arm must be present, or you use **`_`**.

Enum and variant **exhaustiveness** is checked at compile time (see also [§9.8](#sec-9-8)).

---

## Quick reference card

| Topic | Syntax / reminder |
Expand All @@ -547,7 +602,7 @@ shopping-cart.line-count()
| Inference | fixed-point over WIT; REPL tab completion (§5) |
| If | `if c then a else b` |
| Match | `match e { pat => x, _ => y }` |
| Compile-time | Exhaustive `match`, enum case names, call arity/types vs WIT (§9.8) |
| Compile-time | Exhaustive `match`, enum case names, call arity/types vs WIT (§9.8, §16) |
| Call | `f(a, b)` or `recv.method(a)` |
| Instance | `let my-instance = instance();` then `my-instance.lookup-sku(7)` (name is yours) |
| For | `for x in xs { yield y; }` |
Expand Down
8 changes: 4 additions & 4 deletions rib-lang/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,14 @@ impl RibCompilerConfig {
}
}

pub trait GenerateWorkerName {
fn generate_worker_name(&self) -> String;
pub trait GenerateInstanceName {
fn generate_instance_name(&self) -> String;
}

pub struct DefaultWorkerNameGenerator;

impl GenerateWorkerName for DefaultWorkerNameGenerator {
fn generate_worker_name(&self) -> String {
impl GenerateInstanceName for DefaultWorkerNameGenerator {
fn generate_instance_name(&self) -> String {
let uuid = uuid::Uuid::new_v4();
format!("instance-{uuid}")
}
Expand Down
62 changes: 62 additions & 0 deletions rib-lang/src/function_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,55 @@ impl ParsedFunctionName {
function: self.function.clone(),
}
}

/// Segments for resolving a WebAssembly component export via nested instance names, then the
/// function export name.
///
/// Packaged WIT paths like `component:pkg/iface.{fn}` identify the interface in metadata, but
/// component worlds usually export the interface **instance at the root** as `iface`, not as a
/// nested export named `component:pkg`. Runtimes should walk `Component::get_export_index` with
/// these segments (for example `["inventory", "lookup-sku"]`), not a path derived from splitting
/// the full [`ParsedFunctionSite::interface_name`] string.
pub fn wasm_component_export_path(&self) -> Vec<String> {
let mut segments: Vec<String> = match &self.site {
ParsedFunctionSite::Global => Vec::new(),
ParsedFunctionSite::Interface { name } => name
.split('/')
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect(),
ParsedFunctionSite::PackagedInterface { interface, .. } => vec![interface.clone()],
};
let leaf = match &self.function {
ParsedFunctionReference::Function { function } => function.clone(),
_ => self.function.function_name(),
};
segments.push(leaf);
segments
}

/// Like [`wasm_component_export_path`](Self::wasm_component_export_path), but includes common
/// alternate spellings for the **last** segment (WIT kebab-case vs snake_case in lowered names).
pub fn wasm_component_export_path_candidates(&self) -> Vec<Vec<String>> {
let primary = self.wasm_component_export_path();
let mut out: Vec<Vec<String>> = Vec::new();
let mut push = |p: Vec<String>| {
if !out.iter().any(|e| e == &p) {
out.push(p);
}
};
push(primary.clone());
if let Some(last) = primary.last() {
let snake = last.replace('-', "_");
if snake != *last {
let mut alt = primary.clone();
alt.pop();
alt.push(snake);
push(alt);
}
}
out
}
}

#[cfg(test)]
Expand Down Expand Up @@ -434,6 +483,19 @@ mod function_name_tests {
);
}

#[test]
fn wasm_component_export_path_packaged_matches_world_root() {
let parsed =
ParsedFunctionName::parse("component:rib-smoke/inventory.{lookup-sku}").expect("parse");
assert_eq!(
parsed.wasm_component_export_path(),
vec!["inventory", "lookup-sku"]
);
let cands = parsed.wasm_component_export_path_candidates();
assert!(cands.iter().any(|p| p == &vec!["inventory", "lookup-sku"]));
assert!(cands.iter().any(|p| p == &vec!["inventory", "lookup_sku"]));
}

#[test]
fn parse_function_name_in_exported_interface() {
let parsed = ParsedFunctionName::parse("ns:name/interface.{fn1}").expect("Parsing failed");
Expand Down
10 changes: 5 additions & 5 deletions rib-lang/src/interpreter/eval.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
DefaultWorkerNameGenerator, Expr, GenerateWorkerName, RibCompilationError, RibCompiler,
DefaultWorkerNameGenerator, Expr, GenerateInstanceName, RibCompilationError, RibCompiler,
RibCompilerConfig, RibComponentFunctionInvoke, RibInput, RibResult, RibRuntimeError,
};
use std::sync::Arc;
Expand All @@ -8,21 +8,21 @@ pub struct RibEvalConfig {
compiler_config: RibCompilerConfig,
rib_input: RibInput,
function_invoke: Arc<dyn RibComponentFunctionInvoke + Sync + Send>,
generate_worker_name: Arc<dyn GenerateWorkerName + Sync + Send>,
generate_instance_name: Arc<dyn GenerateInstanceName + Sync + Send>,
}

impl RibEvalConfig {
pub fn new(
compiler_config: RibCompilerConfig,
rib_input: RibInput,
function_invoke: Arc<dyn RibComponentFunctionInvoke + Sync + Send>,
generate_worker_name: Option<Arc<dyn GenerateWorkerName + Sync + Send>>,
generate_worker_name: Option<Arc<dyn GenerateInstanceName + Sync + Send>>,
) -> Self {
RibEvalConfig {
compiler_config,
rib_input,
function_invoke,
generate_worker_name: generate_worker_name
generate_instance_name: generate_worker_name
.unwrap_or_else(|| Arc::new(DefaultWorkerNameGenerator)),
}
}
Expand All @@ -47,7 +47,7 @@ impl RibEvaluator {
compiled.byte_code,
self.config.rib_input,
self.config.function_invoke,
Some(self.config.generate_worker_name.clone()),
Some(self.config.generate_instance_name.clone()),
)
.await?;

Expand Down
10 changes: 8 additions & 2 deletions rib-lang/src/interpreter/interpreter_stack_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,15 @@ impl fmt::Debug for RibInterpreterStackValue {
RibInterpreterStackValue::Unit => "unit".to_string(),
RibInterpreterStackValue::Val(value) => {
match &value.value {
Value::Handle { uri, resource_id } => {
Value::Handle {
uri,
resource_id,
instance_name,
} => {
// wasm-wave don't support resource handles yet
format!("handle:{{uri:{uri}, resource-id:{resource_id}}}")
format!(
"handle:{{uri:{uri}, resource-id:{resource_id}, instance:{instance_name}}}"
)
}

_ => value.to_string(),
Expand Down
6 changes: 3 additions & 3 deletions rib-lang/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ mod rib_interpreter;
mod rib_runtime_error;
mod stack;

use crate::{DefaultWorkerNameGenerator, GenerateWorkerName, RibByteCode};
use crate::{DefaultWorkerNameGenerator, GenerateInstanceName, RibByteCode};
use std::sync::Arc;

pub async fn interpret(
rib: RibByteCode,
rib_input: RibInput,
function_invoke: Arc<dyn RibComponentFunctionInvoke + Sync + Send>,
generate_worker_name: Option<Arc<dyn GenerateWorkerName + Sync + Send>>,
generate_worker_name: Option<Arc<dyn GenerateInstanceName + Sync + Send>>,
) -> Result<RibResult, RibRuntimeError> {
let mut interpreter = Interpreter::new(
rib_input,
Expand All @@ -43,7 +43,7 @@ pub async fn interpret(
pub async fn interpret_pure(
rib: RibByteCode,
rib_input: RibInput,
generate_worker_name: Option<Arc<dyn GenerateWorkerName + Sync + Send>>,
generate_worker_name: Option<Arc<dyn GenerateInstanceName + Sync + Send>>,
) -> Result<RibResult, RibRuntimeError> {
let mut interpreter = Interpreter::pure(
rib_input,
Expand Down
Loading
Loading