Repository: bytecodealliance/wasm-tools (rust)

Issue:
wit-parser provides poor error messages for a missing closing bracket
## Reproduction
`src/main.rs`
```rust
fn main() {
    wit_bindgen::generate!({
        world: "platform",
        path: "./wit"
    })
}
```
`Cargo.toml`
```
[package]
name = "wit-issue"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen = "0.25.0"
```
`wit/world.wit`
```wit
package qux:qaz@2.0.0;

world platform {
  import observe;
}
```
`wit/observe.wit`
```wit
interface observe {
  record bar {
    a : string,
  }
```

Running: `cargo build --release`
```
error: failed to resolve directory while parsing WIT for path [/Users/caleb/scratch/wit-issue/./wit]

       Caused by:
         failed to parse package: /Users/caleb/scratch/wit-issue/./wit

       Caused by:
         expected `type`, `resource` or `func`, found eof
            --> /Users/caleb/scratch/wit-issue/./wit/world.wit:4:14
             |
           4 |   import observe;
             |              ^
 --> src/main.rs:2:5
  |
2 | /     wit_bindgen::generate!({
3 | |         world: "platform",
4 | |         path: "./wit"
5 | |     })
  | |______^
  |
  = note: this error originates in the macro `wit_bindgen::generate` (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile `wit-issue` (bin "wit-issue") due to 1 previous error
```
## Expected
Some sort of error that tells me I'm missing a closing bracket.

Pull request:
Improve error message for missing delimiter at EOF
Due to the way source maps worked the span would point to the wrong file if the marker was pointing at the end of a file. While this still isn't necessarily a perfect error message it should be a significant improvement over before by actually pointing to the incorrect file.

Closes #1583

Patch:
diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs
index de176686..e8887fb2 100644
--- a/crates/wasm-encoder/src/core/code.rs
+++ b/crates/wasm-encoder/src/core/code.rs
@@ -3531,10 +3531,10 @@ impl<'a> TryFrom<wasmparser::ConstExpr<'a>> for ConstExpr {
             Some(Ok(wasmparser::Operator::I32Const { value })) => ConstExpr::i32_const(value),
             Some(Ok(wasmparser::Operator::I64Const { value })) => ConstExpr::i64_const(value),
             Some(Ok(wasmparser::Operator::F32Const { value })) => {
-                ConstExpr::f32_const(value.bits() as _)
+                ConstExpr::f32_const(f32::from_bits(value.bits()))
             }
             Some(Ok(wasmparser::Operator::F64Const { value })) => {
-                ConstExpr::f64_const(value.bits() as _)
+                ConstExpr::f64_const(f64::from_bits(value.bits()))
             }
             Some(Ok(wasmparser::Operator::V128Const { value })) => {
                 ConstExpr::v128_const(i128::from_le_bytes(*value.bytes()))
diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs
index e542db1c..99dfca38 100644
--- a/crates/wasmparser/src/binary_reader.rs
+++ b/crates/wasmparser/src/binary_reader.rs
@@ -1712,6 +1712,12 @@ impl<'a> BinaryReader<'a> {
         self.visit_operator(&mut OperatorFactory::new())
     }
 
+    /// Returns whether there is an `end` opcode followed by eof remaining in
+    /// this reader.
+    pub fn is_end_then_eof(&self) -> bool {
+        self.remaining_buffer() == &[0x0b]
+    }
+
     fn read_lane_index(&mut self, max: u8) -> Result<u8> {
         let index = self.read_u8()?;
         if index >= max {
diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs
index 252df15d..69f8baec 100644
--- a/crates/wasmparser/src/readers/core/operators.rs
+++ b/crates/wasmparser/src/readers/core/operators.rs
@@ -209,6 +209,12 @@ impl<'a> OperatorsReader<'a> {
     pub fn get_binary_reader(&self) -> BinaryReader<'a> {
         self.reader.clone()
     }
+
+    /// Returns whether there is an `end` opcode followed by eof remaining in
+    /// this reader.
+    pub fn is_end_then_eof(&self) -> bool {
+        self.reader.is_end_then_eof()
+    }
 }
 
 impl<'a> IntoIterator for OperatorsReader<'a> {
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index 415751de..9dcc9d9b 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -1281,9 +1281,9 @@ impl Printer {
 
         let nesting_start = self.nesting;
 
-        let mut buf = String::new();
-        let mut op_printer = operator::PrintOperator::new(self, state);
-        while !body.eof() {
+        let mut op_printer =
+            operator::PrintOperator::new(self, state, operator::OperatorSeparator::Newline);
+        while !body.is_end_then_eof() {
             // Branch hints are stored in increasing order of their body offset
             // so print them whenever their instruction comes up.
             if let Some(((hint_offset, hint), rest)) = branch_hints.split_first() {
@@ -1299,48 +1299,8 @@ impl Printer {
                 }
             }
 
-            let offset = body.original_position();
-            mem::swap(&mut buf, &mut op_printer.printer.result);
-            let op_kind = body.visit_operator(&mut op_printer)??;
-            mem::swap(&mut buf, &mut op_printer.printer.result);
-
-            match op_kind {
-                // The final `end` in a reader is not printed, it's implied
-                // in the text format.
-                operator::OpKind::End if body.eof() => break,
-
-                // When we start a block we newline to the current
-                // indentation, then we increase the indentation so further
-                // instructions are tabbed over.
-                operator::OpKind::BlockStart => {
-                    op_printer.printer.newline(offset);
-                    op_printer.printer.nesting += 1;
-                }
-
-                // `else`/`catch` are special in that it's printed at
-                // the previous indentation, but it doesn't actually change
-                // our nesting level.
-                operator::OpKind::BlockMid => {
-                    op_printer.printer.nesting -= 1;
-                    op_printer.printer.newline(offset);
-                    op_printer.printer.nesting += 1;
-                }
-
-                // Exiting a block prints `end` at the previous indentation
-                // level.
-                operator::OpKind::End | operator::OpKind::Delegate
-                    if op_printer.printer.nesting > nesting_start =>
-                {
-                    op_printer.printer.nesting -= 1;
-                    op_printer.printer.newline(offset);
-                }
-
-                // .. otherwise everything else just has a normal newline
-                // out in front.
-                _ => op_printer.printer.newline(offset),
-            }
-            op_printer.printer.result.push_str(&buf);
-            buf.truncate(0);
+            op_printer.op_offset = body.original_position();
+            body.visit_operator(&mut op_printer)??;
         }
 
         // If this was an invalid function body then the nesting may not
@@ -1596,40 +1556,14 @@ impl Printer {
         explicit: &str,
     ) -> Result<()> {
         self.start_group("");
-        let mut prev = mem::take(&mut self.result);
         let mut reader = expr.get_operators_reader();
-        let mut op_printer = operator::PrintOperator::new(self, state);
-        let mut first_op = None;
-        for i in 0.. {
-            if reader.eof() {
-                break;
-            }
-            match reader.visit_operator(&mut op_printer)?? {
-                operator::OpKind::End if reader.eof() => {}
 
-                _ if i == 0 => first_op = Some(mem::take(&mut op_printer.printer.result)),
-
-                _ => {
-                    if i == 1 {
-                        prev.push_str(explicit);
-                        prev.push(' ');
-                        prev.push_str(&first_op.take().unwrap());
-                    }
-                    prev.push(' ');
-                    prev.push_str(&op_printer.printer.result);
-                }
-            }
-
-            op_printer.printer.result.truncate(0);
+        if reader.read().is_ok() && !reader.is_end_then_eof() {
+            write!(self.result, "{explicit} ")?;
         }
 
-        // If `first_op` is still set here then it means we don't need to print
-        // an expression with `explicit` as the leading token, instead we can
-        // print the single operator.
-        if let Some(op) = first_op {
-            prev.push_str(&op);
-        }
-        self.result = prev;
+        self.print_const_expr(state, expr)?;
+
         self.end_group();
         Ok(())
     }
@@ -1639,23 +1573,16 @@ impl Printer {
         let mut reader = expr.get_operators_reader();
         let mut first = true;
 
-        let mut result = mem::take(&mut self.result);
-        let mut op_printer = operator::PrintOperator::new(self, state);
-        while !reader.eof() {
+        let mut op_printer =
+            operator::PrintOperator::new(self, state, operator::OperatorSeparator::None);
+        while !reader.is_end_then_eof() {
             if first {
                 first = false;
             } else {
                 op_printer.printer.result.push(' ');
             }
-            match reader.visit_operator(&mut op_printer)?? {
-                operator::OpKind::End if reader.eof() => {}
-                _ => {
-                    result.push_str(&op_printer.printer.result);
-                    op_printer.printer.result.truncate(0);
-                }
-            }
+            reader.visit_operator(&mut op_printer)??;
         }
-        self.result = result;
         Ok(())
     }
 
diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs
index 6912582a..1eb0e486 100644
--- a/crates/wasmprinter/src/operator.rs
+++ b/crates/wasmprinter/src/operator.rs
@@ -5,20 +5,33 @@ use wasmparser::{BlockType, BrTable, Catch, MemArg, Ordering, RefType, TryTable,
 
 pub struct PrintOperator<'a, 'b> {
     pub(super) printer: &'a mut Printer,
+    pub(super) op_offset: usize,
     nesting_start: u32,
     state: &'b mut State,
     label: u32,
     label_indices: Vec<u32>,
+    sep: OperatorSeparator,
+}
+
+pub enum OperatorSeparator {
+    Newline,
+    None,
 }
 
 impl<'a, 'b> PrintOperator<'a, 'b> {
-    pub(super) fn new(printer: &'a mut Printer, state: &'b mut State) -> PrintOperator<'a, 'b> {
+    pub(super) fn new(
+        printer: &'a mut Printer,
+        state: &'b mut State,
+        sep: OperatorSeparator,
+    ) -> PrintOperator<'a, 'b> {
         PrintOperator {
             nesting_start: printer.nesting,
+            op_offset: 0,
             printer,
             state,
             label: 0,
             label_indices: Vec::new(),
+            sep,
         }
     }
 
@@ -30,29 +43,35 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
         &mut self.printer.result
     }
 
-    /// This is called after every instruction and is used to manage the
-    /// `label_indices` stack.
-    fn update_label_stack(&mut self, kind: OpKind) {
-        match kind {
-            OpKind::Normal => {}
-
-            // The previous label was just defined, so add it to the stack.
-            OpKind::BlockStart => {
-                self.label_indices.push(self.label - 1);
+    fn separator(&mut self) {
+        match self.sep {
+            OperatorSeparator::Newline => {
+                self.printer.newline(self.op_offset);
             }
+            OperatorSeparator::None => {}
+        }
+    }
 
-            // The previous label is being defined at the same depth as the
-            // latest label, meaning it's overwriting its entry.
-            OpKind::BlockMid => {
-                if let Some(last) = self.label_indices.last_mut() {
-                    *last = self.label - 1;
-                }
-            }
+    /// Called just before an instruction that introduces a block such as
+    /// `block`, `if`, `loop`, etc.
+    fn block_start(&mut self) {
+        self.separator();
+        self.printer.nesting += 1;
+        self.label_indices.push(self.label);
+    }
 
-            // Label is out of scope so remove it from the stack.
-            OpKind::End | OpKind::Delegate => {
-                self.label_indices.pop();
-            }
+    /// Used for `else` and `delegate`
+    fn block_mid(&mut self) {
+        self.printer.nesting -= 1;
+        self.separator();
+        self.printer.nesting += 1;
+    }
+
+    /// Used for `end` to terminate the prior block.
+    fn block_end(&mut self) {
+        if self.printer.nesting > self.nesting_start {
+            self.printer.nesting -= 1;
+            self.separator();
         }
     }
 
@@ -62,11 +81,6 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
     }
 
     fn blockty_without_label_comment(&mut self, ty: BlockType) -> Result<bool> {
-        // Trim the trailing space, if any.
-        if self.result().ends_with(" ") {
-            self.result().pop();
-        }
-
         let key = (self.state.core.funcs, self.label);
         let has_name = match self.state.core.label_names.index_to_name.get(&key) {
             Some(name) => {
@@ -75,7 +89,10 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
                 true
             }
             None if self.printer.name_unnamed => {
-                let depth = self.cur_depth();
+                // Subtract one from the depth here because the label was
+                // already pushed onto our stack when the instruction was
+                // entered so its own label is one less.
+                let depth = self.cur_depth() - 1;
                 write!(self.result(), " $#label{depth}")?;
                 true
             }
@@ -101,10 +118,7 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
         if !has_name {
             let depth = self.cur_depth();
             self.push_str(" ");
-            // Note that 1 is added to the current depth here since if a block
-            // type is being printed then a block is being created which will
-            // increase the label depth of the block itself.
-            write!(self.result(), ";; label = @{}", depth + 1)?;
+            write!(self.result(), ";; label = @{}", depth)?;
         }
 
         self.label += 1;
@@ -116,11 +130,13 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
     }
 
     fn tag_index(&mut self, index: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.tag_names, index)?;
         Ok(())
     }
 
     fn relative_depth(&mut self, depth: u32) -> Result<()> {
+        self.push_str(" ");
         match self.cur_depth().checked_sub(depth) {
             // If this relative depth is in-range relative to the current depth,
             // then try to print a name for this label. Label names are tracked
@@ -194,29 +210,30 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
     }
 
     fn targets(&mut self, targets: BrTable<'_>) -> Result<()> {
-        for (i, item) in targets.targets().chain([Ok(targets.default())]).enumerate() {
-            if i > 0 {
-                self.push_str(" ");
-            }
+        for item in targets.targets().chain([Ok(targets.default())]) {
             self.relative_depth(item?)?;
         }
         Ok(())
     }
 
     fn function_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.func_names, idx)
     }
 
     fn local_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer
             .print_local_idx(self.state, self.state.core.funcs, idx)
     }
 
     fn global_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.global_names, idx)
     }
 
     fn table_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.table_names, idx)
     }
 
@@ -225,6 +242,7 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
     }
 
     fn memory_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.memory_names, idx)
     }
 
@@ -234,74 +252,74 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
     }
 
     fn array_type_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.type_names, idx)
     }
 
     fn array_type_index_dst(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.type_names, idx)
     }
 
     fn array_type_index_src(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.type_names, idx)
     }
 
     fn array_size(&mut self, array_size: u32) -> Result<()> {
-        write!(&mut self.printer.result, "{array_size}")?;
+        write!(&mut self.printer.result, " {array_size}")?;
         Ok(())
     }
 
     fn struct_type_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.type_names, idx)
     }
 
     fn from_ref_type(&mut self, ref_ty: RefType) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_reftype(self.state, ref_ty)
     }
 
     fn to_ref_type(&mut self, ref_ty: RefType) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_reftype(self.state, ref_ty)
     }
 
     fn data_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.data_names, idx)
     }
 
     fn array_data_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.data_names, idx)
     }
 
     fn elem_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.element_names, idx)
     }
 
     fn array_elem_index(&mut self, idx: u32) -> Result<()> {
+        self.push_str(" ");
         self.printer.print_idx(&self.state.core.element_names, idx)
     }
 
     fn lane(&mut self, lane: u8) -> Result<()> {
-        write!(self.result(), "{lane}")?;
+        write!(self.result(), " {lane}")?;
         Ok(())
     }
 
     fn lanes(&mut self, lanes: [u8; 16]) -> Result<()> {
-        for (i, lane) in lanes.iter().enumerate() {
-            if i > 0 {
-                self.push_str(" ");
-            }
-            write!(self.result(), "{lane}")?;
+        for lane in lanes.iter() {
+            write!(self.result(), " {lane}")?;
         }
         Ok(())
     }
 
     fn memarg(&mut self, memarg: MemArg) -> Result<()> {
-        // Remove the leading ' ' inserted by the macro below since memarg may
-        // not actually print anything if all of its parameters are defaulted.
-        // Ideally we wouldn't rely on the ability to pop here but this ends up
-        // being the easiest.
-        assert_eq!(self.printer.result.pop(), Some(' '));
-
         if memarg.memory != 0 {
-            self.result().push(' ');
             self.memory_index(memarg.memory)?;
         }
         if memarg.offset != 0 {
@@ -320,7 +338,7 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
     fn ordering(&mut self, ordering: Ordering) -> Result<()> {
         write!(
             self.result(),
-            "{}",
+            " {}",
             match ordering {
                 Ordering::SeqCst => "seq_cst",
                 Ordering::AcqRel => "acq_rel",
@@ -335,52 +353,43 @@ impl<'a, 'b> PrintOperator<'a, 'b> {
         // Nesting has already been incremented but labels for catch start above
         // this `try_table` not at the `try_table`. Temporarily decrement this
         // nesting count and increase it below after printing catch clauses.
-        self.printer.nesting -= 1;
+        self.printer.nesting -= 2;
+        let try_table_label = self.label_indices.pop().unwrap();
 
         for catch in table.catches {
             self.result().push(' ');
             match catch {
                 Catch::One { tag, label } => {
-                    self.printer.start_group("catch ");
+                    self.printer.start_group("catch");
                     self.tag_index(tag)?;
-                    self.result().push(' ');
                     self.relative_depth(label)?;
                     self.printer.end_group();
                 }
                 Catch::OneRef { tag, label } => {
-                    self.printer.start_group("catch_ref ");
+                    self.printer.start_group("catch_ref");
                     self.tag_index(tag)?;
-                    self.result().push(' ');
                     self.relative_depth(label)?;
                     self.printer.end_group();
                 }
                 Catch::All { label } => {
-                    self.printer.start_group("catch_all ");
+                    self.printer.start_group("catch_all");
                     self.relative_depth(label)?;
                     self.printer.end_group();
                 }
                 Catch::AllRef { label } => {
-                    self.printer.start_group("catch_all_ref ");
+                    self.printer.start_group("catch_all_ref");
                     self.relative_depth(label)?;
                     self.printer.end_group();
                 }
             }
         }
-        self.printer.nesting += 1;
+        self.label_indices.push(try_table_label);
+        self.printer.nesting += 2;
         self.maybe_blockty_label_comment(has_name)?;
         Ok(())
     }
 }
 
-#[derive(PartialEq, Copy, Clone)]
-pub enum OpKind {
-    BlockStart,
-    BlockMid,
-    End,
-    Delegate,
-    Normal,
-}
-
 macro_rules! define_visit {
     // General structure of all the operator printer methods:
     //
@@ -389,32 +398,37 @@ macro_rules! define_visit {
     // * Return the `OpKind`, as defined by this macro
     ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident )*) => ($(
         fn $visit(&mut self $( , $($arg: $argty),* )?) -> Self::Output {
+            define_visit!(before_op self $op);
             self.push_str(define_visit!(name $op));
             $(
                 define_visit!(payload self $op $($arg)*);
             )?
-            let kind = define_visit!(kind $op);
-            if kind != OpKind::Normal {
-                self.update_label_stack(kind);
-            }
-            Ok(kind)
+
+            define_visit!(after_op self $op);
+            Ok(())
         }
     )*);
 
-    // Macro case to classify instructions based on their `$op` naming into an
-    // `OpKind`. There are a few special cases here but the vast majority of
-    // operators fall into the `Normal` category.
-    (kind Block) => (OpKind::BlockStart);
-    (kind Loop) => (OpKind::BlockStart);
-    (kind If) => (OpKind::BlockStart);
-    (kind Try) => (OpKind::BlockStart);
-    (kind TryTable) => (OpKind::BlockStart);
-    (kind Catch) => (OpKind::BlockMid);
-    (kind CatchAll) => (OpKind::BlockMid);
-    (kind Delegate) => (OpKind::Delegate);
-    (kind Else) => (OpKind::BlockMid);
-    (kind End) => (OpKind::End);
-    (kind $other:tt) => (OpKind::Normal);
+    // Control-flow related opcodes have special handling to manage nested
+    // depth as well as the stack of labels.
+    //
+    // The catch-all for "before an op" is "print an newline"
+    (before_op $self:ident Loop) => ($self.block_start(););
+    (before_op $self:ident Block) => ($self.block_start(););
+    (before_op $self:ident If) => ($self.block_start(););
+    (before_op $self:ident Try) => ($self.block_start(););
+    (before_op $self:ident TryTable) => ($self.block_start(););
+    (before_op $self:ident Catch) => ($self.block_mid(););
+    (before_op $self:ident CatchAll) => ($self.block_mid(););
+    (before_op $self:ident Delegate) => ($self.block_end(););
+    (before_op $self:ident Else) => ($self.block_mid(););
+    (before_op $self:ident End) => ($self.block_end(););
+    (before_op $self:ident $op:ident) => ($self.separator(););
+
+    // After some opcodes the label stack is popped.
+    // (after_op $self:ident Delegate) => ($self.label_indices.pop(););
+    (after_op $self:ident End) => ($self.label_indices.pop(););
+    (after_op $self:ident $op:ident) => ();
 
     // How to print the payload of an instruction. There are a number of
     // instructions that have special cases such as avoiding printing anything
@@ -423,14 +437,12 @@ macro_rules! define_visit {
     // payload field.
     (payload $self:ident CallIndirect $ty:ident $table:ident) => (
         if $table != 0 {
-            $self.push_str(" ");
             $self.table_index($table)?;
         }
         $self.type_index($ty)?;
     );
     (payload $self:ident ReturnCallIndirect $ty:ident $table:ident) => (
         if $table != 0 {
-            $self.push_str(" ");
             $self.table_index($table)?;
         }
         $self.type_index($ty)?;
@@ -453,58 +465,46 @@ macro_rules! define_visit {
         $self.printer.print_heaptype($self.state, $hty)?;
     );
     (payload $self:ident TableInit $segment:ident $table:ident) => (
-        $self.push_str(" ");
         if $table != 0 {
             $self.table_index($table)?;
-            $self.push_str(" ");
         }
         $self.elem_index($segment)?;
     );
     (payload $self:ident TableCopy $dst:ident $src:ident) => (
         if $src != 0 || $dst != 0 {
-            $self.push_str(" ");
             $self.table_index($dst)?;
-            $self.push_str(" ");
             $self.table_index($src)?;
         }
     );
     (payload $self:ident MemoryGrow $mem:ident) => (
         if $mem != 0 {
-            $self.push_str(" ");
             $self.memory_index($mem)?;
         }
     );
     (payload $self:ident MemorySize $mem:ident) => (
         if $mem != 0 {
-            $self.push_str(" ");
             $self.memory_index($mem)?;
         }
     );
     (payload $self:ident MemoryInit $segment:ident $mem:ident) => (
         if $mem != 0 {
-            $self.push_str(" ");
             $self.memory_index($mem)?;
         }
-        $self.push_str(" ");
         $self.data_index($segment)?;
     );
     (payload $self:ident MemoryCopy $dst:ident $src:ident) => (
         if $src != 0 || $dst != 0 {
-            $self.push_str(" ");
             $self.memory_index($dst)?;
-            $self.push_str(" ");
             $self.memory_index($src)?;
         }
     );
     (payload $self:ident MemoryFill $mem:ident) => (
         if $mem != 0 {
-            $self.push_str(" ");
             $self.memory_index($mem)?;
         }
     );
     (payload $self:ident MemoryDiscard $mem:ident) => (
         if $mem != 0 {
-            $self.push_str(" ");
             $self.memory_index($mem)?;
         }
     );
@@ -556,34 +556,27 @@ macro_rules! define_visit {
         $self.printer.print_reftype($self.state, rty)?;
     );
     (payload $self:ident StructGet $ty:ident $field:ident) => (
-        $self.push_str(" ");
         $self.struct_type_index($ty)?;
         $self.push_str(" ");
         $self.printer.print_field_idx($self.state, $ty, $field)?;
     );
     (payload $self:ident StructGetS $ty:ident $field:ident) => (
-        $self.push_str(" ");
         $self.struct_type_index($ty)?;
         $self.push_str(" ");
         $self.printer.print_field_idx($self.state, $ty, $field)?;
     );
     (payload $self:ident StructGetU $ty:ident $field:ident) => (
-        $self.push_str(" ");
         $self.struct_type_index($ty)?;
         $self.push_str(" ");
         $self.printer.print_field_idx($self.state, $ty, $field)?;
     );
     (payload $self:ident StructSet $ty:ident $field:ident) => (
-        $self.push_str(" ");
         $self.struct_type_index($ty)?;
         $self.push_str(" ");
         $self.printer.print_field_idx($self.state, $ty, $field)?;
     );
     (payload $self:ident $op:ident $($arg:ident)*) => (
-        $(
-            $self.push_str(" ");
-            $self.$arg($arg)?;
-        )*
+        $($self.$arg($arg)?;)*
     );
 
     (name Block) => ("block");
@@ -1170,7 +1163,7 @@ macro_rules! define_visit {
 }
 
 impl<'a> VisitOperator<'a> for PrintOperator<'_, '_> {
-    type Output = Result<OpKind>;
+    type Output = Result<()>;
 
     wasmparser::for_each_operator!(define_visit);
 }
diff --git a/crates/wit-parser/src/ast.rs b/crates/wit-parser/src/ast.rs
index 05bd50cf..22a7d7d1 100644
--- a/crates/wit-parser/src/ast.rs
+++ b/crates/wit-parser/src/ast.rs
@@ -1385,10 +1385,7 @@ fn err_expected(
             format!("expected {}, found {}", expected, token.describe()),
         ),
         None => Error::new(
-            Span {
-                start: u32::try_from(tokens.input().len()).unwrap(),
-                end: u32::try_from(tokens.input().len()).unwrap(),
-            },
+            tokens.eof_span(),
             format!("expected {}, found eof", expected),
         ),
     }
@@ -1519,7 +1516,13 @@ impl SourceMap {
     /// interfaces and worlds defined together. Note that each file has its own
     /// personal namespace, however, for top-level `use` and such.
     pub fn push(&mut self, path: &Path, contents: impl Into<String>) {
-        let contents = contents.into();
+        let mut contents = contents.into();
+        // Guarantee that there's at least one character in these contents by
+        // appending a single newline to the end. This is excluded from
+        // tokenization below so it's only here to ensure that spans which point
+        // one byte beyond the end of a file (eof) point to the same original
+        // file.
+        contents.push('\n');
         let new_offset = self.offset + u32::try_from(contents.len()).unwrap();
         self.sources.push(Source {
             offset: self.offset,
@@ -1537,7 +1540,9 @@ impl SourceMap {
             srcs.sort_by_key(|src| &src.path);
             for src in srcs {
                 let mut tokens = Tokenizer::new(
-                    &src.contents,
+                    // chop off the forcibly appended `\n` character when
+                    // passing through the source to get tokenized.
+                    &src.contents[..src.contents.len() - 1],
                     src.offset,
                     self.require_semicolons,
                     self.require_f32_f64,
diff --git a/crates/wit-parser/src/ast/lex.rs b/crates/wit-parser/src/ast/lex.rs
index e7a45fcf..51a4623c 100644
--- a/crates/wit-parser/src/ast/lex.rs
+++ b/crates/wit-parser/src/ast/lex.rs
@@ -162,10 +162,6 @@ impl<'a> Tokenizer<'a> {
         Ok(())
     }
 
-    pub fn input(&self) -> &'a str {
-        self.input
-    }
-
     pub fn get_span(&self, span: Span) -> &'a str {
         let start = usize::try_from(span.start - self.span_offset).unwrap();
         let end = usize::try_from(span.end - self.span_offset).unwrap();
@@ -395,6 +391,11 @@ impl<'a> Tokenizer<'a> {
             _ => false,
         }
     }
+
+    pub fn eof_span(&self) -> Span {
+        let end = self.span_offset + u32::try_from(self.input.len()).unwrap();
+        Span { start: end, end }
+    }
 }
 
 impl<'a> Iterator for CrlfFold<'a> {


Test patch:
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter.wit.result b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter.wit.result
new file mode 100644
index 00000000..45400e63
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter.wit.result
@@ -0,0 +1,9 @@
+failed to resolve directory while parsing WIT for path [tests/ui/parse-fail/multi-file-missing-delimiter]
+
+Caused by:
+    0: failed to parse package: tests/ui/parse-fail/multi-file-missing-delimiter
+    1: expected `type`, `resource` or `func`, found eof
+            --> tests/ui/parse-fail/multi-file-missing-delimiter/observe.wit:6:1
+             |
+           6 | 
+             | ^
\ No newline at end of file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter/observe.wit b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter/observe.wit
new file mode 100644
index 00000000..07bbdf4e
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter/observe.wit
@@ -0,0 +1,5 @@
+interface observe {
+  record bar {
+    a : string,
+  }
+  // NB: intentionally missing a delimiter at the end of the file
diff --git a/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter/world.wit b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter/world.wit
new file mode 100644
index 00000000..729e8bfa
--- /dev/null
+++ b/crates/wit-parser/tests/ui/parse-fail/multi-file-missing-delimiter/world.wit
@@ -0,0 +1,5 @@
+package qux:qaz@2.0.0;
+
+world platform {
+  import observe;
+}
diff --git a/tests/cli/print-deprecated-exceptions2.wat.stdout b/tests/cli/print-deprecated-exceptions2.wat.stdout
index e7b40f2b..ec616143 100644
--- a/tests/cli/print-deprecated-exceptions2.wat.stdout
+++ b/tests/cli/print-deprecated-exceptions2.wat.stdout
@@ -32,7 +32,7 @@
   (func (;5;) (type 0)
     try ;; label = @1
       try ;; label = @2
-      delegate 0 (;@2;)
+      delegate 0 (;@1;)
     catch 0
     end
   )
diff --git a/tests/local/labels.wast b/tests/local/labels.wast
new file mode 100644
index 00000000..fd05940d
--- /dev/null
+++ b/tests/local/labels.wast
@@ -0,0 +1,33 @@
+(module
+  (func
+    br 0
+    try_table $a
+      br $a
+      br 1
+      loop $b
+        br $a
+        br $b
+        br 2
+        block $c
+          br $a
+          br $b
+          br $c
+          br 3
+          if $d
+            br $a
+            br $b
+            br $c
+            br $d
+            br 4
+          else
+            br $a
+            br $b
+            br $c
+            br $d
+            br 4
+          end
+        end
+      end
+    end
+  )
+)
diff --git a/tests/snapshots/local/labels.wast.json b/tests/snapshots/local/labels.wast.json
new file mode 100644
index 00000000..a1225ebd
--- /dev/null
+++ b/tests/snapshots/local/labels.wast.json
@@ -0,0 +1,10 @@
+{
+  "source_filename": "tests/local/labels.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 1,
+      "filename": "labels.0.wasm"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/labels.wast/0.print b/tests/snapshots/local/labels.wast/0.print
new file mode 100644
index 00000000..347694e0
--- /dev/null
+++ b/tests/snapshots/local/labels.wast/0.print
@@ -0,0 +1,34 @@
+(module
+  (type (;0;) (func))
+  (func (;0;) (type 0)
+    br 0
+    try_table $a
+      br $a
+      br 1
+      loop $b
+        br $a
+        br $b
+        br 2
+        block $c
+          br $a
+          br $b
+          br $c
+          br 3
+          if $d
+            br $a
+            br $b
+            br $c
+            br $d
+            br 4
+          else
+            br $a
+            br $b
+            br $c
+            br $d
+            br 4
+          end
+        end
+      end
+    end
+  )
+)