Repository: microsoft/qsharp (rust)

Issue:
panic when referencing reexported UDT from another package

When a package references a re-exported UDT from a dependency, a panic occurs:

```
panicked at `compiler/qsc_frontend/src/typeck/convert.rs:123:13:
internal error: entered unreachable code: A path should never resolve to a local or a parameter, as there is syntactic differentiation.
```


This bug occurs if we try to use `TestCaseResult` from `qtest` package today (but you have to work around #1955 first)

https://github.com/microsoft/qsharp/blob/6d83547a003bcde6032398d46eb0d645c8224053/library/qtest/src/Main.qs#L4


**REPRO**

We need two Q# packages for this:


**`dep/qsharp.json`**

```json
{}
```

**`dep/src/Main.qs`**

```qsharp
namespace Foo {
    struct MyType { a: Int }
}

namespace Main {
    export Foo.MyType as MyType; // work around https://github.com/microsoft/qsharp/issues/1955 by using an alias
}
```

**`main/qsharp.json`**

```json
{
  "dependencies": {
    "dep": {
      "path": "../dep"
    }
  }
}
```

**`main/src/Main.qs`**

```qsharp
operation Main() : Unit {
    let x = new dep.MyType { a = 1 };  // this usage triggers the panic
}
```


panicked at `compiler/qsc_frontend/src/typeck/convert.rs:123:13:`
internal error: entered unreachable code: A path should never resolve to a local or a parameter, as there is syntactic differentiation.



Seen in 2a4c33552e94bce95c91d8936dcf45a601e6b2b2



Pull request description:
Fix UDT re-exports
Closes #2122


Patch:
diff --git a/compiler/qsc_circuit/src/circuit.rs b/compiler/qsc_circuit/src/circuit.rs
index e9493b8b..508845f3 100644
--- a/compiler/qsc_circuit/src/circuit.rs
+++ b/compiler/qsc_circuit/src/circuit.rs
@@ -95,7 +95,8 @@ impl Config {
     pub const DEFAULT_MAX_OPERATIONS: usize = 10001;
 }
 
-type ObjectsByColumn = FxHashMap<usize, String>;
+type ObjectsByColumn = FxHashMap<usize, CircuitObject>;
+type ColumnWidthsByColumn = FxHashMap<usize, usize>;
 
 struct Row {
     wire: Wire,
@@ -108,16 +109,18 @@ enum Wire {
     Classical { start_column: Option<usize> },
 }
 
+enum CircuitObject {
+    WireCross,
+    WireStart,
+    DashedCross,
+    Vertical,
+    VerticalDashed,
+    Object(String),
+}
+
 impl Row {
     fn add_object(&mut self, column: usize, object: &str) {
-        match &mut self.wire {
-            Wire::Qubit { .. } => {
-                self.add(column, fmt_on_qubit_wire(object));
-            }
-            Wire::Classical { .. } => {
-                self.add(column, fmt_on_classical_wire(object));
-            }
-        };
+        self.add(column, CircuitObject::Object(object.to_string()));
     }
 
     fn add_gate(&mut self, column: usize, gate: &str, args: Option<&str>, is_adjoint: bool) {
@@ -129,18 +132,19 @@ impl Row {
         if let Some(args) = args {
             let _ = write!(&mut gate_label, "({args})");
         }
-        self.add_object(column, &gate_label);
+
+        self.add_object(column, gate_label.as_str());
     }
 
     fn add_vertical(&mut self, column: usize) {
         if !self.objects.contains_key(&column) {
             match self.wire {
-                Wire::Qubit { .. } => self.add(column, QUBIT_WIRE_CROSS),
+                Wire::Qubit { .. } => self.add(column, CircuitObject::WireCross),
                 Wire::Classical { start_column } => {
                     if start_column.is_some() {
-                        self.add(column, CLASSICAL_WIRE_CROSS);
+                        self.add(column, CircuitObject::WireCross);
                     } else {
-                        self.add(column, VERTICAL);
+                        self.add(column, CircuitObject::Vertical);
                     }
                 }
             }
@@ -150,12 +154,12 @@ impl Row {
     fn add_dashed_vertical(&mut self, column: usize) {
         if !self.objects.contains_key(&column) {
             match self.wire {
-                Wire::Qubit { .. } => self.add(column, QUBIT_WIRE_DASHED_CROSS),
+                Wire::Qubit { .. } => self.add(column, CircuitObject::DashedCross),
                 Wire::Classical { start_column } => {
                     if start_column.is_some() {
-                        self.add(column, CLASSICAL_WIRE_DASHED_CROSS);
+                        self.add(column, CircuitObject::DashedCross);
                     } else {
-                        self.add(column, VERTICAL_DASHED);
+                        self.add(column, CircuitObject::VerticalDashed);
                     }
                 }
             }
@@ -163,18 +167,23 @@ impl Row {
     }
 
     fn start_classical(&mut self, column: usize) {
-        self.add(column, CLASSICAL_WIRE_START);
+        self.add(column, CircuitObject::WireStart);
         if let Wire::Classical { start_column } = &mut self.wire {
             start_column.replace(column);
         }
     }
 
-    fn add(&mut self, column: usize, v: impl Into<String>) {
-        self.objects.insert(column, v.into());
+    fn add(&mut self, column: usize, circuit_object: CircuitObject) {
+        self.objects.insert(column, circuit_object);
         self.next_column = column + 1;
     }
 
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>, end_column: usize) -> std::fmt::Result {
+    fn fmt(
+        &self,
+        f: &mut std::fmt::Formatter<'_>,
+        column_widths: &ColumnWidthsByColumn,
+        end_column: usize,
+    ) -> std::fmt::Result {
         // Temporary string so we can trim whitespace at the end
         let mut s = String::new();
         match &self.wire {
@@ -182,22 +191,24 @@ impl Row {
                 s.write_str(&fmt_qubit_label(*label))?;
                 for column in 1..end_column {
                     let val = self.objects.get(&column);
+                    let column_width = *column_widths.get(&column).unwrap_or(&MIN_COLUMN_WIDTH);
                     if let Some(v) = val {
-                        s.write_str(v)?;
+                        s.write_str(&fmt_qubit_circuit_object(v, column_width))?;
                     } else {
-                        s.write_str(QUBIT_WIRE)?;
+                        s.write_str(&get_qubit_wire(column_width))?;
                     }
                 }
             }
             Wire::Classical { start_column } => {
                 for column in 0..end_column {
                     let val = self.objects.get(&column);
+                    let column_width = *column_widths.get(&column).unwrap_or(&MIN_COLUMN_WIDTH);
                     if let Some(v) = val {
-                        s.write_str(v)?;
+                        s.write_str(&fmt_classical_circuit_object(v, column_width))?;
                     } else if start_column.map_or(false, |s| column > s) {
-                        s.write_str(CLASSICAL_WIRE)?;
+                        s.write_str(&get_classical_wire(column_width))?;
                     } else {
-                        s.write_str(BLANK)?;
+                        s.write_str(&get_blank(column_width))?;
                     }
                 }
             }
@@ -207,33 +218,103 @@ impl Row {
     }
 }
 
-const COLUMN_WIDTH: usize = 7;
-const QUBIT_WIRE: &str = "───────";
-const CLASSICAL_WIRE: &str = "═══════";
-const QUBIT_WIRE_CROSS: &str = "───┼───";
-const CLASSICAL_WIRE_CROSS: &str = "═══╪═══";
-const CLASSICAL_WIRE_START: &str = "   ╘═══";
-const QUBIT_WIRE_DASHED_CROSS: &str = "───┆───";
-const CLASSICAL_WIRE_DASHED_CROSS: &str = "═══┆═══";
-const VERTICAL_DASHED: &str = "   ┆   ";
-const VERTICAL: &str = "   │   ";
-const BLANK: &str = "       ";
+const MIN_COLUMN_WIDTH: usize = 7;
+
+/// "───────"
+fn get_qubit_wire(column_width: usize) -> String {
+    "─".repeat(column_width)
+}
+
+/// "═══════"
+fn get_classical_wire(column_width: usize) -> String {
+    "═".repeat(column_width)
+}
+
+/// "───┼───"
+fn get_qubit_wire_cross(column_width: usize) -> String {
+    let half_width = "─".repeat(column_width / 2);
+    format!("{}┼{}", half_width, half_width)
+}
+
+/// "═══╪═══"
+fn get_classical_wire_cross(column_width: usize) -> String {
+    let half_width = "═".repeat(column_width / 2);
+    format!("{}╪{}", half_width, half_width)
+}
+
+/// "   ╘═══"
+fn get_classical_wire_start(column_width: usize) -> String {
+    let first_half_width = " ".repeat(column_width / 2);
+    let second_half_width = "═".repeat(column_width / 2);
+    format!("{}╘{}", first_half_width, second_half_width)
+}
+
+/// "───┆───"
+fn get_qubit_wire_dashed_cross(column_width: usize) -> String {
+    let half_width = "─".repeat(column_width / 2);
+    format!("{}┆{}", half_width, half_width)
+}
+
+/// "═══┆═══"
+fn get_classical_wire_dashed_cross(column_width: usize) -> String {
+    let half_width = "═".repeat(column_width / 2);
+    format!("{}┆{}", half_width, half_width)
+}
+
+/// "   │   "
+fn get_vertical(column_width: usize) -> String {
+    let half_width = " ".repeat(column_width / 2);
+    format!("{}│{}", half_width, half_width)
+}
+
+/// "   ┆   "
+fn get_vertical_dashed(column_width: usize) -> String {
+    let half_width = " ".repeat(column_width / 2);
+    format!("{}┆{}", half_width, half_width)
+}
+
+/// "       "
+fn get_blank(column_width: usize) -> String {
+    " ".repeat(column_width)
+}
 
 /// "q_0  "
 #[allow(clippy::doc_markdown)]
 fn fmt_qubit_label(id: usize) -> String {
-    let rest = COLUMN_WIDTH - 2;
+    let rest = MIN_COLUMN_WIDTH - 2;
     format!("q_{id: <rest$}")
 }
 
 /// "── A ──"
-fn fmt_on_qubit_wire(obj: &str) -> String {
-    format!("{:─^COLUMN_WIDTH$}", format!(" {obj} "))
+fn fmt_on_qubit_wire(obj: &str, column_width: usize) -> String {
+    format!("{:─^column_width$}", format!(" {obj} "))
 }
 
 /// "══ A ══"
-fn fmt_on_classical_wire(obj: &str) -> String {
-    format!("{:═^COLUMN_WIDTH$}", format!(" {obj} "))
+fn fmt_on_classical_wire(obj: &str, column_width: usize) -> String {
+    format!("{:═^column_width$}", format!(" {obj} "))
+}
+
+fn fmt_classical_circuit_object(circuit_object: &CircuitObject, column_width: usize) -> String {
+    match circuit_object {
+        CircuitObject::WireCross => get_classical_wire_cross(column_width),
+        CircuitObject::WireStart => get_classical_wire_start(column_width),
+        CircuitObject::DashedCross => get_classical_wire_dashed_cross(column_width),
+        CircuitObject::Vertical => get_vertical(column_width),
+        CircuitObject::VerticalDashed => get_vertical_dashed(column_width),
+        CircuitObject::Object(label) => fmt_on_classical_wire(label.as_str(), column_width),
+    }
+}
+
+fn fmt_qubit_circuit_object(circuit_object: &CircuitObject, column_width: usize) -> String {
+    match circuit_object {
+        CircuitObject::WireCross => get_qubit_wire_cross(column_width),
+        CircuitObject::WireStart => get_blank(column_width), // This should never happen
+        CircuitObject::DashedCross => get_qubit_wire_dashed_cross(column_width),
+        CircuitObject::Vertical => get_vertical(column_width),
+        CircuitObject::VerticalDashed => get_vertical_dashed(column_width),
+        CircuitObject::Object(label) => fmt_on_qubit_wire(label.as_str(), column_width),
+    }
 }
 
 impl Display for Circuit {
@@ -289,6 +370,7 @@ impl Display for Circuit {
             let mut all_rows = targets.clone();
             all_rows.extend(controls.iter());
             all_rows.sort_unstable();
+
             // We'll need to know the entire range of rows for this operation so we can
             // figure out the starting column and also so we can draw any
             // vertical lines that cross wires.
@@ -298,12 +380,11 @@ impl Display for Circuit {
 
             // The starting column - the first available column in all
             // the rows that this operation spans.
-            let mut column = 1;
-            for row in &rows[begin..end] {
-                if row.next_column > column {
-                    column = row.next_column;
-                }
-            }
+            let column = rows[begin..end]
+                .iter()
+                .map(|r| r.next_column)
+                .max()
+                .unwrap_or(1);
 
             // Add the operation to the diagram
             for i in targets {
@@ -348,9 +429,28 @@ impl Display for Circuit {
             .max_by_key(|r| r.next_column)
             .map_or(1, |r| r.next_column);
 
+        // To be able to fit long-named operations, we calculate the required width for each column,
+        // based on the maximum length needed for gates, where a gate X is printed as "- X -".
+        let column_widths = (0..end_column)
+            .map(|column| {
+                (
+                    column,
+                    rows.iter()
+                        .filter_map(|row| row.objects.get(&column))
+                        .filter_map(|object| match object {
+                            CircuitObject::Object(string) => Some((string.len() + 4) | 1), // Column lengths need to be odd numbers
+                            _ => None,
+                        })
+                        .chain(std::iter::once(MIN_COLUMN_WIDTH))
+                        .max()
+                        .unwrap(),
+                )
+            })
+            .collect::<ColumnWidthsByColumn>();
+
         // Draw the diagram
         for row in rows {
-            row.fmt(f, end_column)?;
+            row.fmt(f, &column_widths, end_column)?;
         }
 
         Ok(())
diff --git a/compiler/qsc_frontend/src/resolve.rs b/compiler/qsc_frontend/src/resolve.rs
index f02f1113..5c3eeb01 100644
--- a/compiler/qsc_frontend/src/resolve.rs
+++ b/compiler/qsc_frontend/src/resolve.rs
@@ -1676,10 +1676,10 @@ impl GlobalTable {
                                 .insert_or_find_namespace(ns.iter().map(|s| s.name.clone()));
                         }
                         hir::ItemKind::Ty(..) => {
-                            self.scope
-                                .tys
-                                .get_mut_or_default(namespace)
-                                .insert(global.name.clone(), Res::ExportedItem(item_id, None));
+                            self.scope.tys.get_mut_or_default(namespace).insert(
+                                global.name.clone(),
+                                Res::Item(item_id, ItemStatus::Available),
+                            );
                         }
                         hir::ItemKind::Export(_, _) => {
                             unreachable!("find_item will never return an Export")
diff --git a/compiler/qsc_frontend/src/typeck/convert.rs b/compiler/qsc_frontend/src/typeck/convert.rs
index 17cfc432..f02adc39 100644
--- a/compiler/qsc_frontend/src/typeck/convert.rs
+++ b/compiler/qsc_frontend/src/typeck/convert.rs
@@ -117,13 +117,12 @@ pub(super) fn ty_from_path(names: &Names, path: &Path) -> Ty {
         // So realistically, by construction, `Param` here is unreachable.
         // A path can also never resolve to an export, because in typeck/check,
         // we resolve exports to their original definition.
-        Some(
-            resolve::Res::Local(_) | resolve::Res::Param { .. } | resolve::Res::ExportedItem(_, _),
-        ) => {
-            unreachable!(
-                "A path should never resolve \
+        Some(resolve::Res::Local(_) | resolve::Res::Param { .. }) => unreachable!(
+            " A path should never resolve \
             to a local or a parameter, as there is syntactic differentiation."
-            )
+        ),
+        Some(resolve::Res::ExportedItem(item_id, alias)) => {
+            unreachable!("Exported items should have been resolved to their original definition in type checking. Found {:?} with alias {:?}", item_id, alias);
         }
         None => Ty::Err,
     }
diff --git a/vscode/src/registry.json b/vscode/src/registry.json
index a1d8b89a..f8b713ba 100644
--- a/vscode/src/registry.json
+++ b/vscode/src/registry.json
@@ -26,7 +26,7 @@
             { "ref": "bd5a09c", "notes": "latest stable" },
             { "ref": "main", "notes": "nightly, unstable" }
           ],
-          "path": "library/signed"
+          "path": "library/fixed_point"
         }
       }
     },


Test patch:
diff --git a/compiler/qsc/src/interpret/circuit_tests.rs b/compiler/qsc/src/interpret/circuit_tests.rs
index 821b60b3..f3188cba 100644
--- a/compiler/qsc/src/interpret/circuit_tests.rs
+++ b/compiler/qsc/src/interpret/circuit_tests.rs
@@ -148,10 +148,8 @@ fn rotation_gate() {
         .circuit(CircuitEntryPoint::EntryPoint, false)
         .expect("circuit generation should succeed");
 
-    // The wire isn't visible here since the gate label is longer
-    // than the static column width, but we can live with it.
     expect![[r"
-        q_0     rx(1.5708)
+        q_0    ─ rx(1.5708) ──
     "]]
     .assert_eq(&circ.to_string());
 }
@@ -262,8 +260,8 @@ fn mresetz_unrestricted_profile() {
         .expect("circuit generation should succeed");
 
     expect![[r"
-        q_0    ── H ──── M ─── |0〉 ─
-                         ╘══════════
+        q_0    ── H ──── M ──── |0〉 ──
+                         ╘════════════
     "]]
     .assert_eq(&circ.to_string());
 }
@@ -349,10 +347,10 @@ fn unrestricted_profile_result_comparison() {
         .expect("circuit generation should succeed");
 
     expect![[r"
-        q_0    ── H ──── M ──── X ─── |0〉 ─
-                         ╘═════════════════
-        q_1    ── H ──── M ─── |0〉 ────────
-                         ╘═════════════════
+        q_0    ── H ──── M ───── X ───── |0〉 ──
+                         ╘═════════════════════
+        q_1    ── H ──── M ──── |0〉 ───────────
+                         ╘═════════════════════
     "]]
     .assert_eq(&circ.to_string());
 
@@ -366,10 +364,10 @@ fn unrestricted_profile_result_comparison() {
 
     let circuit = interpreter.get_circuit();
     expect![[r"
-        q_0    ── H ──── M ──── X ─── |0〉 ─
-                         ╘═════════════════
-        q_1    ── H ──── M ─── |0〉 ────────
-                         ╘═════════════════
+        q_0    ── H ──── M ───── X ───── |0〉 ──
+                         ╘═════════════════════
+        q_1    ── H ──── M ──── |0〉 ───────────
+                         ╘═════════════════════
     "]]
     .assert_eq(&circuit.to_string());
 }
@@ -456,10 +454,8 @@ fn custom_intrinsic_one_classical_arg() {
         .circuit(CircuitEntryPoint::EntryPoint, false)
         .expect("circuit generation should succeed");
 
-    // A custom intrinsic that doesn't take qubits just doesn't
-    // show up on the circuit.
     expect![[r"
-        q_0    ── X ── foo(4)
+        q_0    ── X ─── foo(4) ──
     "]]
     .assert_eq(&circ.to_string());
 }
@@ -497,16 +493,16 @@ fn custom_intrinsic_mixed_args() {
     // This is one gate that spans ten target wires, even though the
     // text visualization doesn't convey that clearly.
     expect![[r"
-        q_0     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_1     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_2     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_3     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_4     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_5     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_6     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_7     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_8     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
-        q_9     AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)
+        q_0    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_1    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_2    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_3    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_4    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_5    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_6    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_7    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_8    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
+        q_9    ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1) ──
     "]]
     .assert_eq(&circ.to_string());
 
@@ -536,7 +532,7 @@ fn custom_intrinsic_apply_idle_noise() {
     // ConfigurePauliNoise has no qubit arguments so it shouldn't show up.
     // ApplyIdleNoise is a quantum operation so it shows up.
     expect![[r#"
-        q_0     ApplyIdleNoise
+        q_0    ─ ApplyIdleNoise ──
     "#]]
     .assert_eq(&circ.to_string());
 }
@@ -834,6 +830,63 @@ fn operation_with_non_qubit_args() {
     .assert_debug_eq(&circ_err);
 }
 
+#[test]
+fn operation_with_long_gates_properly_aligned() {
+    let mut interpreter = interpreter(
+        r"
+            namespace Test {
+                import Std.Measurement.*;
+
+                @EntryPoint()
+                operation Main() : Result[] {
+                    use q0 = Qubit();
+                    use q1 = Qubit();
+
+                    H(q0);
+                    H(q1);
+                    X(q1);
+                    Ry(1.0, q1);
+                    CNOT(q0, q1);
+                    M(q0);
+
+                    use q2 = Qubit();
+
+                    H(q2);
+                    Rx(1.0, q2);
+                    H(q2);
+                    Rx(1.0, q2);
+                    H(q2);
+                    Rx(1.0, q2);
+
+                    use q3 = Qubit();
+
+                    Rxx(1.0, q1, q3);
+
+                    CNOT(q0, q3);
+
+                    [M(q1), M(q3)]
+                }
+            }
+        ",
+        Profile::Unrestricted,
+    );
+
+    let circ = interpreter
+        .circuit(CircuitEntryPoint::EntryPoint, false)
+        .expect("circuit generation should succeed");
+
+    expect![[r#"
+        q_0    ── H ────────────────────────────────────── ● ──────── M ────────────────────────────────── ● ─────────
+                                                           │          ╘════════════════════════════════════╪══════════
+        q_1    ── H ──────── X ─────── ry(1.0000) ──────── X ───────────────────────────── rxx(1.0000) ────┼───── M ──
+                                                                                                ┆          │      ╘═══
+        q_2    ── H ─── rx(1.0000) ──────── H ─────── rx(1.0000) ──── H ─── rx(1.0000) ─────────┆──────────┼──────────
+        q_3    ─────────────────────────────────────────────────────────────────────────── rxx(1.0000) ─── X ──── M ──
+                                                                                                                  ╘═══
+    "#]]
+    .assert_eq(&circ.to_string());
+}
+
 /// Tests that invoke circuit generation throught the debugger.
 mod debugger_stepping {
     use super::Debugger;
@@ -915,17 +968,17 @@ mod debugger_stepping {
             step:
             q_0    ── H ──
             step:
-            q_0    ── H ──── Z ───────────────────────
-            q_1    ── H ──── ● ──── H ──── M ─── |0〉 ─
-                                           ╘══════════
+            q_0    ── H ──── Z ─────────────────────────
+            q_1    ── H ──── ● ──── H ──── M ──── |0〉 ──
+                                           ╘════════════
             step:
-            q_0    ── H ──── Z ─── |0〉 ───────────────
-            q_1    ── H ──── ● ──── H ──── M ─── |0〉 ─
-                                           ╘══════════
+            q_0    ── H ──── Z ──── |0〉 ──────────────────
+            q_1    ── H ──── ● ───── H ───── M ──── |0〉 ──
+                                             ╘════════════
             step:
-            q_0    ── H ──── Z ─── |0〉 ───────────────
-            q_1    ── H ──── ● ──── H ──── M ─── |0〉 ─
-                                           ╘══════════
+            q_0    ── H ──── Z ──── |0〉 ──────────────────
+            q_1    ── H ──── ● ───── H ───── M ──── |0〉 ──
+                                             ╘════════════
         "]]
         .assert_eq(&circs);
     }
@@ -959,11 +1012,11 @@ mod debugger_stepping {
             q_0    ── H ──── M ──
                              ╘═══
             step:
-            q_0    ── H ──── M ─── |0〉 ─
-                             ╘══════════
+            q_0    ── H ──── M ──── |0〉 ──
+                             ╘════════════
             step:
-            q_0    ── H ──── M ─── |0〉 ─
-                             ╘══════════
+            q_0    ── H ──── M ──── |0〉 ──
+                             ╘════════════
         "]]
         .assert_eq(&circs);
     }
diff --git a/compiler/qsc_circuit/src/circuit/tests.rs b/compiler/qsc_circuit/src/circuit/tests.rs
index 3a9f992a..d62e0c70 100644
--- a/compiler/qsc_circuit/src/circuit/tests.rs
+++ b/compiler/qsc_circuit/src/circuit/tests.rs
@@ -221,10 +221,8 @@ fn with_args() {
         }],
     };
 
-    // This looks wonky because the gate label is longer
-    // than the static column width, but we can live with it.
     expect![[r"
-        q_0     rx(1.5708)
+        q_0    ─ rx(1.5708) ──
     "]]
     .assert_eq(&c.to_string());
 }
@@ -258,12 +256,10 @@ fn two_targets() {
         ],
     };
 
-    // This looks wonky because the gate label is longer
-    // than the static column width, but we can live with it.
     expect![[r"
-        q_0     rzz(1.0000)
-        q_1    ───┆───
-        q_2     rzz(1.0000)
+        q_0    ─ rzz(1.0000) ─
+        q_1    ───────┆───────
+        q_2    ─ rzz(1.0000) ─
     "]]
     .assert_eq(&c.to_string());
 }
diff --git a/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs b/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs
index 7fb0eb5e..d471b852 100644
--- a/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs
+++ b/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs
@@ -61,6 +61,54 @@ fn multiple_package_check_inner(packages: Vec<(&str, &str)>, expect: Option<&Exp
     }
 }
 
+/// This can be used to test multiple packages which internally have multiple source files, as opposed to the more simple `multiple_package_check`
+/// which only allows one source file per package (for easy and quick test creation).
+fn multiple_package_multiple_source_check(
+    packages: Vec<(&str, Vec<(&str, &str)>)>,
+    expect: Option<&Expect>,
+) {
+    let mut store = PackageStore::new(core());
+    let mut prev_id_and_name: Option<(PackageId, &str)> = None;
+    let num_packages = packages.len();
+    for (ix, (package_name, sources)) in packages.into_iter().enumerate() {
+        let is_last = ix == num_packages - 1;
+        let deps = if let Some((prev_id, prev_name)) = prev_id_and_name {
+            vec![(prev_id, Some(Arc::from(prev_name)))]
+        } else {
+            vec![]
+        };
+
+        let sources = SourceMap::new(
+            sources.iter().map(|(name, source)| {
+                (
+                    Arc::from(format!("{package_name}/{name}.qs")),
+                    Arc::from(*source),
+                )
+            }),
+            None,
+        );
+
+        let unit = compile(
+            &store,
+            &deps[..],
+            sources,
+            TargetCapabilityFlags::all(),
+            LanguageFeatures::default(),
+        );
+
+        match (is_last, &expect) {
+            (true, Some(expect)) => {
+                expect.assert_eq(&format!("{:#?}", unit.errors));
+            }
+            _ => {
+                assert!(unit.errors.is_empty(), "{:#?}", unit.errors);
+            }
+        }
+
+        prev_id_and_name = Some((store.insert(unit), package_name));
+    }
+}
+
 #[test]
 fn namespace_named_main_doesnt_create_main_namespace() {
     multiple_package_check_expect_err(
@@ -509,3 +557,48 @@ fn aliased_export_via_aliased_import() {
         ),
     ]);
 }
+
+#[test]
+fn udt_reexport_with_alias() {
+    multiple_package_multiple_source_check(
+        vec![
+            (
+                "A",
+                vec![
+                    ("FileOne", "struct Foo { content: Bool }"),
+                    ("FileTwo", "export FileOne.Foo as Bar;"),
+                ],
+            ),
+            ("B", vec![("FileThree", "export A.FileTwo.Bar as Baz;")]),
+            (
+                "C",
+                vec![(
+                    "FileFour",
+                    "@EntryPoint()
+            operation Main() : Unit {
+                let x = new B.FileThree.Baz { content = true };
+            }",
+                )],
+            ),
+        ],
+        Some(&expect!["[]"]),
+    );
+}
+
+#[test]
+fn callable_reexport() {
+    multiple_package_check(vec![
+        (
+            "A",
+            "function Foo() : Unit {  }
+                export Foo as Bar;",
+        ),
+        (
+            "B",
+            "           @EntryPoint()
+            operation Main() : Unit {
+                let x = A.A.Bar();
+            }",
+        ),
+    ]);
+}
diff --git a/library/qtest/src/Tests.qs b/library/qtest/src/Tests.qs
index 1f89e330..50a3b4c1 100644
--- a/library/qtest/src/Tests.qs
+++ b/library/qtest/src/Tests.qs
@@ -46,18 +46,18 @@ function FunctionTestMatrixTests() : Unit {
     Fact(All(result -> result.did_pass, results), "test matrix did not pass all test cases");
 }
 
-function BasicTests() : Unit {
-    let sample_tests = [
+function BasicTests() : (String, () -> Int, Int)[] {
+    return [
         ("Should return 42", TestCaseOne, 43),
         ("Should add one", () -> AddOne(5), 42),
         ("Should add one", () -> AddOne(5), 6)
-    ]
+    ];
 }
 
 @Test()
 function ReturnsFalseForFailingTest() : Unit {
     Fact(
-        not Functions.CheckAllTestCases(SampleTestData()),
+        not Functions.CheckAllTestCases(BasicTests()),
         "Test harness failed to return false for a failing tests."
     );
 }
@@ -72,7 +72,7 @@ function ReturnsTrueForPassingTest() : Unit {
 
 @Test()
 function RunAllTests() : Unit {
-    let run_all_result = Functions.RunAllTestCases(SampleTestData());
+    let run_all_result = Functions.RunAllTestCases(BasicTests());
 
     Fact(
         Length(run_all_result) == 3,