Repository: microsoft/qsharp
Pull request #1631 (2024-06-11 21:37:29)

Multi-Qubit Measurements: move code example out of details tag
This includes modifying the state used in the first Analyze exercise to match the state in the second exercise, and changing the code example to solve both exercises.


Patch:
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index b59e0b65..0361bb5d 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -964,11 +964,11 @@ impl<'a> Visitor<'a> for BreakpointCollector<'a> {
     fn visit_stmt(&mut self, stmt: StmtId) {
         let stmt_res = self.get_stmt(stmt);
         match stmt_res.kind {
-            qsc_fir::fir::StmtKind::Expr(expr) | qsc_fir::fir::StmtKind::Local(_, _, expr) => {
+            fir::StmtKind::Expr(expr) | fir::StmtKind::Local(_, _, expr) => {
                 self.add_stmt(stmt_res);
                 visit::walk_expr(self, expr);
             }
-            qsc_fir::fir::StmtKind::Item(_) | qsc_fir::fir::StmtKind::Semi(_) => {
+            fir::StmtKind::Item(_) | fir::StmtKind::Semi(_) => {
                 self.add_stmt(stmt_res);
             }
         };
diff --git a/compiler/qsc_ast/src/ast.rs b/compiler/qsc_ast/src/ast.rs
index 236d86c4..5f41f1aa 100644
--- a/compiler/qsc_ast/src/ast.rs
+++ b/compiler/qsc_ast/src/ast.rs
@@ -169,6 +169,16 @@ pub struct Namespace {
     pub items: Box<[Box<Item>]>,
 }
 
+impl Namespace {
+    /// Returns an iterator over the items in the namespace that are exported.
+    pub fn exports(&self) -> impl Iterator<Item = &ImportOrExportItem> {
+        self.items.iter().flat_map(|i| match i.kind.as_ref() {
+            ItemKind::ImportOrExport(decl) if decl.is_export() => &decl.items[..],
+            _ => &[],
+        })
+    }
+}
+
 impl Display for Namespace {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         let mut indent = set_indentation(indented(f), 0);
@@ -264,6 +274,8 @@ pub enum ItemKind {
     Ty(Box<Ident>, Box<TyDef>),
     /// A `struct` declaration.
     Struct(Box<StructDecl>),
+    /// An export declaration
+    ImportOrExport(ImportOrExportDecl),
 }
 
 impl Display for ItemKind {
@@ -277,6 +289,8 @@ impl Display for ItemKind {
             },
             ItemKind::Ty(name, t) => write!(f, "New Type ({name}): {t}")?,
             ItemKind::Struct(s) => write!(f, "{s}")?,
+            ItemKind::ImportOrExport(item) if item.is_export => write!(f, "Export ({item})")?,
+            ItemKind::ImportOrExport(item) => write!(f, "Import ({item})")?,
         }
         Ok(())
     }
@@ -1414,6 +1428,33 @@ pub struct Path {
     pub name: Box<Ident>,
 }
 
+impl From<Path> for Vec<Ident> {
+    fn from(val: Path) -> Self {
+        let mut buf = val.namespace.unwrap_or_default().0.to_vec();
+        buf.push(*val.name);
+        buf
+    }
+}
+
+impl From<Vec<Ident>> for Path {
+    fn from(mut v: Vec<Ident>) -> Self {
+        let name = v
+            .pop()
+            .expect("parser should never produce empty vector of idents");
+        let namespace: Option<Idents> = if v.is_empty() { None } else { Some(v.into()) };
+        let span = Span {
+            lo: namespace.as_ref().map_or(name.span.lo, |ns| ns.span().lo),
+            hi: name.span.hi,
+        };
+        Self {
+            namespace,
+            name: name.into(),
+            span,
+            id: NodeId::default(),
+        }
+    }
+}
+
 impl Display for Path {
     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
         if let Some(ns) = &self.namespace {
@@ -1567,6 +1608,15 @@ impl Idents {
         }
         Rc::from(buf)
     }
+
+    /// Appends another ident to this [`Idents`].
+    /// Returns a new [`Idents`] with the appended ident.
+    #[must_use = "this method returns a new value and does not mutate the original value"]
+    pub fn push(&self, other: Ident) -> Self {
+        let mut buf = self.0.to_vec();
+        buf.push(other);
+        Self(buf.into_boxed_slice())
+    }
 }
 
 impl Default for Ident {
@@ -1820,3 +1870,117 @@ pub enum SetOp {
     /// The set intersection.
     Intersect,
 }
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+/// Represents an export declaration.
+pub struct ImportOrExportDecl {
+    /// The span.
+    pub span: Span,
+    /// The items being exported from this namespace.
+    pub items: Box<[ImportOrExportItem]>,
+    /// Whether this is an export declaration or not. If `false`, then this is an `Import`.
+    is_export: bool,
+}
+
+impl Display for ImportOrExportDecl {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let items_str = self
+            .items
+            .iter()
+            .map(std::string::ToString::to_string)
+            .collect::<Vec<_>>()
+            .join(", ");
+        write!(f, "ImportOrExportDecl {}: [{items_str}]", self.span)
+    }
+}
+
+impl ImportOrExportDecl {
+    /// Creates a new `ImportOrExportDecl` with the given span, items, and export flag.
+    #[must_use]
+    pub fn new(span: Span, items: Box<[ImportOrExportItem]>, is_export: bool) -> Self {
+        Self {
+            span,
+            items,
+            is_export,
+        }
+    }
+
+    /// Returns true if this is an export declaration.
+    #[must_use]
+    pub fn is_export(&self) -> bool {
+        self.is_export
+    }
+
+    /// Returns true if this is an import declaration.
+    #[must_use]
+    pub fn is_import(&self) -> bool {
+        !self.is_export
+    }
+
+    /// Returns an iterator over the items being exported from this namespace.
+    pub fn items(&self) -> impl Iterator<Item = &ImportOrExportItem> {
+        self.items.iter()
+    }
+}
+
+/// An individual item within an [`ExportDecl`]. This can be a path or a path with an alias.
+#[derive(Clone, Debug, PartialEq, Eq, Default)]
+pub struct ImportOrExportItem {
+    /// The path to the item being exported.
+    pub path: Path,
+    /// An optional alias for the item being exported.
+    pub alias: Option<Ident>,
+    /// Whether this is a glob import/export.
+    pub is_glob: bool,
+}
+
+impl Display for ImportOrExportItem {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let ImportOrExportItem {
+            ref path,
+            ref alias,
+            is_glob,
+        } = self;
+        let is_glob = if *is_glob { ".*" } else { "" };
+        match alias {
+            Some(alias) => write!(f, "{path}{is_glob} as {alias}",),
+            None => write!(f, "{path}{is_glob}"),
+        }
+    }
+}
+
+impl WithSpan for ImportOrExportItem {
+    fn with_span(self, span: Span) -> Self {
+        ImportOrExportItem {
+            path: self.path.with_span(span),
+            alias: self.alias.map(|x| x.with_span(span)),
+            is_glob: self.is_glob,
+        }
+    }
+}
+
+impl ImportOrExportItem {
+    /// Returns the span of the export item. This includes the path and , if any exists, the alias.
+    #[must_use]
+    pub fn span(&self) -> Span {
+        match self.alias {
+            Some(ref alias) => {
+                // join the path and alias spans
+                Span {
+                    lo: self.path.span.lo,
+                    hi: alias.span.hi,
+                }
+            }
+            None => self.path.span,
+        }
+    }
+
+    /// Returns the alias ident, if any, or the name from the path if no alias is present.
+    #[must_use]
+    pub fn name(&self) -> &Ident {
+        match self.alias {
+            Some(ref alias) => alias,
+            None => &self.path.name,
+        }
+    }
+}
diff --git a/compiler/qsc_ast/src/mut_visit.rs b/compiler/qsc_ast/src/mut_visit.rs
index e3ff2a7c..a879bd85 100644
--- a/compiler/qsc_ast/src/mut_visit.rs
+++ b/compiler/qsc_ast/src/mut_visit.rs
@@ -129,6 +129,15 @@ pub fn walk_item(vis: &mut impl MutVisitor, item: &mut Item) {
             vis.visit_ty_def(def);
         }
         ItemKind::Struct(decl) => vis.visit_struct_decl(decl),
+        ItemKind::ImportOrExport(export) => {
+            vis.visit_span(&mut export.span);
+            for item in export.items.iter_mut() {
+                vis.visit_path(&mut item.path);
+                if let Some(ref mut alias) = item.alias {
+                    vis.visit_ident(alias);
+                }
+            }
+        }
     }
 }
 
diff --git a/compiler/qsc_ast/src/visit.rs b/compiler/qsc_ast/src/visit.rs
index 79a67ce7..0f275a74 100644
--- a/compiler/qsc_ast/src/visit.rs
+++ b/compiler/qsc_ast/src/visit.rs
@@ -118,6 +118,14 @@ pub fn walk_item<'a>(vis: &mut impl Visitor<'a>, item: &'a Item) {
             vis.visit_ty_def(def);
         }
         ItemKind::Struct(decl) => vis.visit_struct_decl(decl),
+        ItemKind::ImportOrExport(decl) => {
+            for item in decl.items.iter() {
+                vis.visit_path(&item.path);
+                if let Some(ref alias) = item.alias {
+                    vis.visit_ident(alias);
+                }
+            }
+        }
     }
 }
 
diff --git a/compiler/qsc_codegen/src/qsharp.rs b/compiler/qsc_codegen/src/qsharp.rs
index 5e2ef471..c7044e9b 100644
--- a/compiler/qsc_codegen/src/qsharp.rs
+++ b/compiler/qsc_codegen/src/qsharp.rs
@@ -15,10 +15,10 @@ use std::vec;
 
 use qsc_ast::ast::{
     self, Attr, BinOp, Block, CallableBody, CallableDecl, CallableKind, Expr, ExprKind, Functor,
-    FunctorExpr, FunctorExprKind, Ident, Idents, Item, ItemKind, Lit, Mutability, Pat, PatKind,
-    Path, Pauli, QubitInit, QubitInitKind, QubitSource, SetOp, SpecBody, SpecDecl, SpecGen, Stmt,
-    StmtKind, StringComponent, TernOp, TopLevelNode, Ty, TyDef, TyDefKind, TyKind, UnOp,
-    Visibility, VisibilityKind,
+    FunctorExpr, FunctorExprKind, Ident, Idents, ImportOrExportItem, Item, ItemKind, Lit,
+    Mutability, Pat, PatKind, Path, Pauli, QubitInit, QubitInitKind, QubitSource, SetOp, SpecBody,
+    SpecDecl, SpecGen, Stmt, StmtKind, StringComponent, TernOp, TopLevelNode, Ty, TyDef, TyDefKind,
+    TyKind, UnOp, Visibility, VisibilityKind,
 };
 use qsc_ast::ast::{Namespace, Package};
 use qsc_ast::visit::Visitor;
@@ -139,6 +139,33 @@ impl<W: Write> Visitor<'_> for QSharpGen<W> {
                 self.writeln(";");
             }
             ItemKind::Struct(decl) => self.visit_struct_decl(decl),
+            ItemKind::ImportOrExport(decl) => {
+                if decl.is_export() {
+                    self.write("export ");
+                } else {
+                    self.write("import ");
+                }
+
+                for (
+                    ix,
+                    ImportOrExportItem {
+                        ref path,
+                        ref is_glob,
+                        ..
+                    },
+                ) in decl.items.iter().enumerate()
+                {
+                    let is_last = ix == decl.items.len() - 1;
+                    self.visit_path(path);
+                    if *is_glob {
+                        self.write(".*");
+                    }
+                    if !is_last {
+                        self.write(", ");
+                    };
+                }
+                self.write(";");
+            }
         }
     }
 
diff --git a/compiler/qsc_data_structures/src/namespaces.rs b/compiler/qsc_data_structures/src/namespaces.rs
index f80eda11..7ac215fd 100644
--- a/compiler/qsc_data_structures/src/namespaces.rs
+++ b/compiler/qsc_data_structures/src/namespaces.rs
@@ -4,7 +4,7 @@
 #[cfg(test)]
 mod tests;
 
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
 use std::{cell::RefCell, fmt::Display, iter::Peekable, ops::Deref, rc::Rc};
 
 pub const PRELUDE: [[&str; 3]; 4] = [
@@ -76,13 +76,31 @@ type MemoEntry = (Vec<Rc<str>>, NamespaceTreeCell);
 /// less efficient, as it performs a breadth-first search. Because of this inefficiency, the results of this lookup are memoized.
 /// [`NamespaceTreeNode`]s are all stored in [`NamespaceTreeCell`]s, which are reference counted and support interior mutability for namespace
 /// insertions and clone-free lookups.
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub struct NamespaceTreeRoot {
     assigner: usize,
     tree: NamespaceTreeCell,
     memo: RefCell<FxHashMap<NamespaceId, MemoEntry>>,
 }
 
+impl std::fmt::Debug for NamespaceTreeRoot {
+    // manual implementation to avoid infinite loops in printing
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(
+            f,
+            "NamespaceTreeRoot\n{}",
+            self.tree.borrow().debug_print(0, &mut FxHashSet::default())
+        )
+    }
+}
+
+impl std::fmt::Debug for NamespaceTreeNode {
+    // manual implementation to avoid infinite loops in printing
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "{}", self.debug_print(0, &mut FxHashSet::default()))
+    }
+}
+
 impl NamespaceTreeRoot {
     /// Create a new namespace tree root. The assigner is used to assign new namespace IDs.
     #[must_use]
@@ -132,7 +150,7 @@ impl NamespaceTreeRoot {
         let (names, node) = self
             .tree
             .borrow()
-            .find_namespace_by_id(*id, &[])
+            .find_namespace_by_id(*id, &[], &mut FxHashSet::default())
             .unwrap_or_else(|| (vec![], self.tree.clone()));
 
         self.memo
@@ -145,6 +163,22 @@ impl NamespaceTreeRoot {
     pub fn root_id(&self) -> NamespaceId {
         self.tree.borrow().id
     }
+
+    /// Inserts an alias for an existing namespace into the tree, where the child namespace already exists.
+    pub fn insert_with_id(
+        &mut self,
+        parent: Option<NamespaceId>,
+        new_child: NamespaceId,
+        alias: &str,
+    ) {
+        let parent = parent.unwrap_or_else(|| self.root_id());
+        let (_, parent_node) = self.find_namespace_by_id(&parent);
+        let (_, existing_ns) = self.find_namespace_by_id(&new_child);
+        parent_node
+            .borrow_mut()
+            .children
+            .insert(Rc::from(alias), existing_ns);
+    }
 }
 
 impl Default for NamespaceTreeRoot {
@@ -168,11 +202,12 @@ impl Default for NamespaceTreeRoot {
 
 /// A node in the namespace tree. Each node has a unique ID and a map of children.
 /// Supports interior mutability of children for inserting new nodes.
-#[derive(Debug, Clone)]
+#[derive(Clone)]
 pub struct NamespaceTreeNode {
     pub children: FxHashMap<Rc<str>, NamespaceTreeCell>,
     pub id: NamespaceId,
 }
+
 impl NamespaceTreeNode {
     /// Create a new namespace tree node with the given ID and children. The `id` should come from the `NamespaceTreeRoot` assigner.
     #[must_use]
@@ -210,18 +245,18 @@ impl NamespaceTreeNode {
         &self,
         ns: impl IntoIterator<Item = &'a str>,
     ) -> Option<NamespaceId> {
-        let mut buf: Option<NamespaceTreeCell> = None;
+        let mut rover: Option<NamespaceTreeCell> = None;
         for component in ns {
-            if let Some(next_ns) = match buf {
+            if let Some(next_ns) = match rover {
                 None => self.get(&Rc::from(component)),
                 Some(buf) => buf.borrow().get(&Rc::from(component)),
             } {
-                buf = Some(next_ns);
+                rover = Some(next_ns);
             } else {
                 return None;
             }
         }
-        Some(buf.map_or_else(|| self.id, |x| x.borrow().id))
+        Some(rover.map_or_else(|| self.id, |x| x.borrow().id))
     }
 
     /// Inserts a new namespace into the tree, if it does not yet exist.
@@ -268,7 +303,13 @@ impl NamespaceTreeNode {
         &self,
         id: NamespaceId,
         names_buf: &[Rc<str>],
+        // `ids_visited` is used to avoid infinite loops in the case of cycles in the namespace tree.
+        ids_visited: &mut FxHashSet<NamespaceId>,
     ) -> Option<(Vec<Rc<str>>, NamespaceTreeCell)> {
+        if ids_visited.contains(&self.id) {
+            return None;
+        }
+        ids_visited.insert(self.id);
         // first, check if any children are the one we are looking for
         for (name, node) in &self.children {
             if node.borrow().id == id {
@@ -282,7 +323,8 @@ impl NamespaceTreeNode {
         for (name, node) in &self.children {
             let mut names = names_buf.to_vec();
             names.push(name.clone());
-            let Some((names, node)) = node.borrow().find_namespace_by_id(id, &names) else {
+            let Some((names, node)) = node.borrow().find_namespace_by_id(id, &names, ids_visited)
+            else {
                 continue;
             };
             return Some((names, node));
@@ -290,4 +332,44 @@ impl NamespaceTreeNode {
 
         None
     }
+
+    fn debug_print(
+        &self,
+        indentation_level: usize,
+        visited_nodes: &mut FxHashSet<NamespaceId>,
+    ) -> String {
+        let indentation = "  ".repeat(indentation_level);
+
+        if visited_nodes.contains(&self.id) {
+            return format!("\n{indentation}Cycle Detected");
+        }
+
+        visited_nodes.insert(self.id);
+
+        let mut result = String::new();
+
+        if self.children.is_empty() {
+            result.push_str("empty node");
+        } else {
+            result.push_str(&format!("\n{indentation}  children: ["));
+            for (name, node) in &self.children {
+                result.push_str(&format!(
+                    "\n{}    {}(id {}) {{",
+                    indentation,
+                    name,
+                    Into::<usize>::into(node.borrow().id)
+                ));
+                result.push_str(
+                    node.borrow()
+                        .debug_print(indentation_level + 2, visited_nodes)
+                        .as_str(),
+                );
+                result.push(',');
+            }
+            result.push_str(&format!("\n{indentation}  ]"));
+            result.push_str(&format!("\n{indentation}"));
+        }
+        result.push('}');
+        result
+    }
 }
diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs
index 2283e805..45ec7765 100644
--- a/compiler/qsc_frontend/src/compile.rs
+++ b/compiler/qsc_frontend/src/compile.rs
@@ -514,8 +514,14 @@ fn resolve_all(
         dropped_names.extend(unit.dropped_names.iter().cloned());
     }
 
+    // bind all symbols in `add_local_package`
     let mut errors = globals.add_local_package(assigner, package);
     let mut resolver = Resolver::new(globals, dropped_names);
+
+    // bind all exported symbols in a follow-on step
+    resolver.bind_and_resolve_imports_and_exports(package);
+
+    // resolve all symbols
     resolver.with(assigner).visit_package(package);
     let (names, locals, mut resolver_errors, _namespaces) = resolver.into_result();
     errors.append(&mut resolver_errors);
diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs
index 75a3d8f4..3b13c8f8 100644
--- a/compiler/qsc_frontend/src/incremental.rs
+++ b/compiler/qsc_frontend/src/incremental.rs
@@ -255,6 +255,7 @@ impl Compiler {
         self.resolver
             .extend_dropped_names(cond_compile.into_names());
         self.resolver.bind_fragments(ast, &mut unit.assigner);
+        self.resolver.bind_and_resolve_imports_and_exports(ast);
         self.resolver.with(&mut unit.assigner).visit_package(ast);
 
         self.checker.check_package(self.resolver.names(), ast);
diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs
index 9f76ef74..20f63db6 100644
--- a/compiler/qsc_frontend/src/lower.rs
+++ b/compiler/qsc_frontend/src/lower.rs
@@ -130,7 +130,7 @@ impl With<'_> {
         };
 
         self.lowerer.parent = Some(id);
-        let items = namespace
+        let items: Vec<LocalItemId> = namespace
             .items
             .iter()
             .filter_map(|i| self.lower_item(ItemScope::Global, i))
@@ -171,7 +171,10 @@ impl With<'_> {
         };
 
         let (id, kind) = match &*item.kind {
-            ast::ItemKind::Err | ast::ItemKind::Open(..) => return None,
+            ast::ItemKind::Err | ast::ItemKind::Open(..) |
+            // exports are handled in namespace resolution (see resolve.rs) -- we don't need them in any lowered representations
+
+            ast::ItemKind::ImportOrExport(_) => return None,
             ast::ItemKind::Callable(callable) => {
                 let id = resolve_id(callable.name.id);
                 let grandparent = self.lowerer.parent;
diff --git a/compiler/qsc_frontend/src/resolve.rs b/compiler/qsc_frontend/src/resolve.rs
index 5349f385..ff55d9ab 100644
--- a/compiler/qsc_frontend/src/resolve.rs
+++ b/compiler/qsc_frontend/src/resolve.rs
@@ -12,6 +12,7 @@ use qsc_ast::{
     visit::{self as ast_visit, walk_attr, Visitor as AstVisitor},
 };
 
+use qsc_ast::ast::{ImportOrExportDecl, ImportOrExportItem, Item, ItemKind, Package};
 use qsc_data_structures::{
     index_map::IndexMap,
     namespaces::{NamespaceId, NamespaceTreeRoot, PRELUDE},
@@ -50,7 +51,7 @@ pub enum Res {
     UnitTy,
 }
 
-#[derive(Clone, Debug, Diagnostic, Error)]
+#[derive(Clone, Debug, Diagnostic, Error, PartialEq)]
 pub(super) enum Error {
     #[error("`{name}` could refer to the item in `{first_open}` or `{second_open}`")]
     #[diagnostic(code("Qsc.Resolve.Ambiguous"))]
@@ -93,6 +94,10 @@ pub(super) enum Error {
     #[diagnostic(code("Qsc.Resolve.DuplicateIntrinsic"))]
     DuplicateIntrinsic(String, #[label] Span),
 
+    #[error("duplicate export of `{0}`")]
+    #[diagnostic(code("Qsc.Resolve.DuplicateExport"))]
+    DuplicateExport(String, #[label] Span),
+
     #[error("`{0}` not found")]
     #[diagnostic(code("Qsc.Resolve.NotFound"))]
     NotFound(String, #[label] Span),
@@ -108,6 +113,33 @@ pub(super) enum Error {
     #[diagnostic(help("this item is not implemented and cannot be used"))]
     #[diagnostic(code("Qsc.Resolve.Unimplemented"))]
     Unimplemented(String, #[label] Span),
+
+    #[error("this export is not a callable or type")]
+    #[diagnostic(code("Qsc.Resolve.ExportedNonItem"))]
+    ExportedNonItem(#[label] Span),
+
+    #[error("export statements are not allowed in a local scope")]
+    #[diagnostic(code("Qsc.Resolve.ExportFromLocalScope"))]
+    ExportFromLocalScope(#[label] Span),
+
+    #[error("imported non-item")]
+    #[diagnostic(help("only callables, namespaces, and non-primitive types can be imported"))]
+    #[diagnostic(code("Qsc.Resolve.ImportedNonItem"))]
+    ImportedNonItem(#[label] Span),
+
+    #[error("imported symbol that already exists in scope")]
+    #[diagnostic(help("alias this import or rename the existing symbol"))]
+    #[diagnostic(code("Qsc.Resolve.ImportedDuplicate"))]
+    ImportedDuplicate(String, #[label] Span),
+
+    #[error("glob import does not resolve to a namespace")]
+    #[diagnostic(help("ensure the path {0} exists and is a namespace"))]
+    #[diagnostic(code("Qsc.Resolve.GlobImportNamespaceNotFound"))]
+    GlobImportNamespaceNotFound(String, #[label] Span),
+
+    #[error("glob exports are not supported")]
+    #[diagnostic(code("Qsc.Resolve.GlobExportNotSupported"))]
+    GlobExportNotSupported(#[label] Span),
 }
 
 #[derive(Debug, Clone)]
@@ -119,9 +151,9 @@ pub struct Scope {
     /// Open statements. The key is the namespace name or alias.
     opens: FxHashMap<Vec<Rc<str>>, Vec<Open>>,
     /// Local newtype declarations.
-    tys: FxHashMap<Rc<str>, ItemId>,
+    tys: FxHashMap<Rc<str>, ScopeItemEntry>,
     /// Local callable and newtype declarations.
-    terms: FxHashMap<Rc<str>, ItemId>,
+    terms: FxHashMap<Rc<str>, ScopeItemEntry>,
     /// Local variables, including callable parameters, for loop bindings, etc.
     /// The u32 is the `valid_at` offset - the lowest offset at which the variable name is available.
     /// It's used to determine which variables are visible at a specific offset in the scope.
@@ -134,6 +166,27 @@ pub struct Scope {
     ty_vars: FxHashMap<Rc<str>, ParamId>,
 }
 
+#[derive(Debug, Clone)]
+pub struct ScopeItemEntry {
+    pub id: ItemId,
+    pub source: ItemSource,
+}
+
+impl ScopeItemEntry {
+    #[must_use]
+    pub fn new(id: ItemId, source: ItemSource) -> Self {
+        Self { id, source }
+    }
+}
+
+#[derive(PartialEq, Debug, Clone, Default, Copy)]
+pub enum ItemSource {
+    Exported,
+    Imported,
+    #[default]
+    Declared,
+}
+
 impl Scope {
     fn new(kind: ScopeKind, span: Span) -> Self {
         Self {
@@ -152,7 +205,12 @@ impl Scope {
             NameKind::Ty => &self.tys,
             NameKind::Term => &self.terms,
         };
-        items.get(name)
+        items.get(name).map(|x| &x.id)
+    }
+
+    /// A `ScopeKind, Span` pair uniquely identifies a scope.
+    fn key(&self) -> (&ScopeKind, Span) {
+        (&self.kind, self.span)
     }
 }
 
@@ -174,9 +232,18 @@ impl Locals {
         })
     }
 
-    fn push_scope(&mut self, s: Scope) -> ScopeId {
+    fn push_scope(&mut self, kind: ScopeKind, span: Span) -> ScopeId {
+        // First, check if this scope has already been created in a prior pass.
+        for (id, existing_scope) in self.scopes.iter_mut().enumerate() {
+            if existing_scope.key() == (&kind, span) {
+                // If the scope already exists, return that.
+                return id;
+            }
+        }
+
+        // Add it to the list of known scopes.
         let id = self.scopes.len();
-        self.scopes.insert(id, s);
+        self.scopes.insert(id, Scope::new(kind, span));
         id
     }
 
@@ -186,6 +253,12 @@ impl Locals {
             .unwrap_or_else(|| panic!("scope with id {id:?} should exist"))
     }
 
+    fn get_scope(&self, id: ScopeId) -> &Scope {
+        self.scopes
+            .get(id)
+            .unwrap_or_else(|| panic!("scope with id {id:?} should exist"))
+    }
+
     #[must_use]
     pub fn get_all_at_offset(&self, offset: u32) -> Vec<Local> {
         let mut vars = true;
@@ -272,7 +345,7 @@ enum ScopeKind {
     Block,
 }
 
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
 enum NameKind {
     Ty,
     Term,
@@ -299,7 +372,7 @@ impl PartialOrd<Self> for Open {
 }
 
 impl Ord for Open {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+    fn cmp(&self, other: &Self) -> Ordering {
         let a: usize = self.namespace.into();
         let b: usize = other.namespace.into();
         a.cmp(&b)
@@ -316,7 +389,88 @@ pub(super) struct Resolver {
     errors: Vec<Error>,
 }
 
+/// This visitor is used for an intermediate step between binding and full resolution.
+/// We use this visitor to resolve all exported and imported symbols, so that they are
+/// available during the resolution stage.
+struct ExportImportVisitor<'a> {
+    resolver: &'a mut Resolver,
+}
+
+impl ExportImportVisitor<'_> {
+    /// Given a function, apply that function to `Self` within a scope. In other words, this
+    /// function automatically pushes a scope before `f` and pops it after.
+    fn with_scope(&mut self, span: Span, kind: ScopeKind, f: impl FnOnce(&mut Self)) {
+        self.resolver.push_scope(span, kind);
+        f(self);
+        self.resolver.pop_scope();
+    }
+}
+
+impl AstVisitor<'_> for ExportImportVisitor<'_> {
+    fn visit_item(&mut self, item: &Item) {
+        item.attrs.iter().for_each(|a| self.visit_attr(a));
+        item.visibility
+            .iter()
+            .for_each(|v| self.visit_visibility(v));
+        match &*item.kind {
+            ItemKind::ImportOrExport(decl) if decl.is_export() => self
+                .resolver
+                .errors
+                .push(Error::ExportFromLocalScope(item.span)),
+            ItemKind::Open(..) => (),
+            _ => ast_visit::walk_item(self, item),
+        }
+    }
+
+    fn visit_namespace(&mut self, namespace: &ast::Namespace) {
+        let ns = self
+            .resolver
+            .globals
+            .find_namespace(namespace.name.str_iter())
+            .expect("namespace should exist by this point");
+        let root_id = self.resolver.globals.namespaces.root_id();
+        let kind = ScopeKind::Namespace(ns);
+        self.with_scope(namespace.span, kind, |visitor| {
+            // the below line ensures that this namespace opens itself, in case
+            // we are re-opening a namespace. This is important, as without this,
+            // a re-opened namespace would only have knowledge of its scopes.
+            visitor.resolver.bind_open(&namespace.name, &None, root_id);
+            for item in &*namespace.items {
+                match &*item.kind {
+                    ItemKind::ImportOrExport(decl) => {
+                        visitor
+                            .resolver
+                            .bind_import_or_export(decl, Some((ns, &namespace.name)));
+                    }
+                    ItemKind::Open(name, alias) => {
+                        // we only need to bind opens that are in top-level namespaces, outside of callables.
+                        // this is because this is for the intermediate export-binding pass
+                        // and in the export-binding pass, we only need to know what symbols are available
+                        // in the scope of the exports. Exports are only allowed from namespaces scopes.
+                        // Put another way,
+                        // ```
+                        // // this is allowed, so we need to bind the "open B" for the export to work
+                        // namespace A { open B; export { SomethingFromB }; }
+                        //
+                        // // this is disallowed, so we don't need to bind the "open B" for the export
+                        // namespace A { callable foo() { open B; export { SomethingFromB }; } }
+                        //                                        ^^^^^^ export from non-namespace scope is not allowed
+                        // ```
+                        visitor.resolver.bind_open(name, alias, ns);
+                    }
+                    _ => ast_visit::walk_item(visitor, item),
+                }
+            }
+        });
+    }
+}
+
 impl Resolver {
+    pub(crate) fn bind_and_resolve_imports_and_exports(&mut self, package: &Package) {
+        let mut visitor = ExportImportVisitor { resolver: self };
+        visitor.visit_package(package);
+    }
+
     pub(super) fn new(globals: GlobalTable, dropped_names: Vec<TrackedName>) -> Self {
         Self {
             names: globals.names,
@@ -334,13 +488,13 @@ impl Resolver {
         dropped_names: Vec<TrackedName>,
     ) -> Self {
         let mut locals = Locals::default();
-        let scope_id = locals.push_scope(Scope::new(
+        let scope_id = locals.push_scope(
             ScopeKind::Block,
             Span {
                 lo: 0,
                 hi: u32::MAX,
             },
-        ));
+        );
         Self {
             names: globals.names,
             dropped_names,
@@ -398,7 +552,7 @@ impl Resolver {
                 }
                 TopLevelNode::Stmt(stmt) => {
                     if let ast::StmtKind::Item(item) = stmt.kind.as_ref() {
-                        self.bind_local_item(assigner, item);
+                        self.bind_local_item(assigner, item, None);
                     }
                 }
             }
@@ -427,10 +581,9 @@ impl Resolver {
         }
     }
 
-    fn resolve_path(&mut self, kind: NameKind, path: &ast::Path) {
+    fn resolve_path(&mut self, kind: NameKind, path: &ast::Path) -> Result<Res, Error> {
         let name = &path.name;
         let namespace = &path.namespace;
-
         match resolve(
             kind,
             &self.globals,
@@ -441,22 +594,23 @@ impl Resolver {
             Ok(res) => {
                 self.check_item_status(res, path.name.name.to_string(), path.span);
                 self.names.insert(path.id, res);
+                Ok(res)
             }
             Err(err) => {
                 if let Error::NotFound(name, span) = err {
                     if let Some(dropped_name) =
                         self.dropped_names.iter().find(|n| n.name.as_ref() == name)
                     {
-                        self.errors.push(Error::NotAvailable(
+                        Err(Error::NotAvailable(
                             name,
                             format!("{}.{}", dropped_name.namespace, dropped_name.name),
                             span,
-                        ));
+                        ))
                     } else {
-                        self.errors.push(Error::NotFound(name, span));
+                        Err(Error::NotFound(name, span))
                     }
                 } else {
-                    self.errors.push(err);
+                    Err(err)
                 }
             }
         }
@@ -510,7 +664,6 @@ impl Resolver {
             .namespaces
             .find_namespace_by_id(&current_namespace);
         // try scoping from the current namespace, and then use the absolute namespace as the backup
-
         let id = if let Some(id) = (*current_namespace)
             .borrow()
             .get_namespace_id(name.str_iter())
@@ -519,29 +672,42 @@ impl Resolver {
         } else if let Some(id) = self.globals.namespaces.get_namespace_id(name.str_iter()) {
             id
         } else {
-            return self
-                .errors
-                .push(Error::NotFound(name.name().to_string(), name.span()));
+            let error = Error::NotFound(name.name().to_string(), name.span());
+            self.errors.push(error);
+            return;
         };
 
-        let alias = alias
-            .as_ref()
-            .map_or(name.into(), |a| vec![Rc::clone(&a.name)]);
+        let alias = alias.as_ref().map_or(vec![], |a| vec![Rc::clone(&a.name)]);
+        {
+            let current_opens = self
+                .current_scope_mut()
+                .opens
+                .entry(alias.clone())
+                .or_default();
 
-        self.current_scope_mut()
-            .opens
-            .entry(alias)
-            .or_default()
-            .push(Open {
+            let open = Open {
                 namespace: id,
                 span: name.span(),
-            });
+            };
+            if !current_opens.contains(&open) {
+                current_opens.push(open);
+            }
+        }
     }
 
-    pub(super) fn bind_local_item(&mut self, assigner: &mut Assigner, item: &ast::Item) {
+    pub(super) fn bind_local_item(
+        &mut self,
+        assigner: &mut Assigner,
+        item: &ast::Item,
+        namespace: Option<NamespaceId>,
+    ) {
         match &*item.kind {
             ast::ItemKind::Open(name, alias) => {
-                self.bind_open(name, alias, self.globals.namespaces.root_id());
+                self.bind_open(
+                    name,
+                    alias,
+                    namespace.unwrap_or_else(|| self.globals.namespaces.root_id()),
+                );
             }
             ast::ItemKind::Callable(decl) => {
                 let id = intrapackage(assigner.next_item());
@@ -552,9 +718,10 @@ impl Resolver {
                         ItemStatus::from_attrs(&ast_attrs_as_hir_attrs(&item.attrs)),
                     ),
                 );
-                self.current_scope_mut()
-                    .terms
-                    .insert(Rc::clone(&decl.name.name), id);
+                self.current_scope_mut().terms.insert(
+                    Rc::clone(&decl.name.name),
+                    ScopeItemEntry::new(id, ItemSource::Declared),
+                );
             }
             ast::ItemKind::Ty(name, _) => {
                 let id = intrapackage(assigner.next_item());
@@ -566,8 +733,14 @@ impl Resolver {
                     ),
                 );
                 let scope = self.current_scope_mut();
-                scope.tys.insert(Rc::clone(&name.name), id);
-                scope.terms.insert(Rc::clone(&name.name), id);
+                scope.tys.insert(
+                    Rc::clone(&name.name),
+                    ScopeItemEntry::new(id, ItemSource::Declared),
+                );
+                scope.terms.insert(
+                    Rc::clone(&name.name),
+                    ScopeItemEntry::new(id, ItemSource::Declared),
+                );
             }
             ast::ItemKind::Struct(decl) => {
                 let id = intrapackage(assigner.next_item());
@@ -579,10 +752,176 @@ impl Resolver {
                     ),
                 );
                 let scope = self.current_scope_mut();
-                scope.tys.insert(Rc::clone(&decl.name.name), id);
-                scope.terms.insert(Rc::clone(&decl.name.name), id);
+                scope.tys.insert(
+                    Rc::clone(&decl.name.name),
+                    ScopeItemEntry::new(id, ItemSource::Declared),
+                );
+                scope.terms.insert(
+                    Rc::clone(&decl.name.name),
+                    ScopeItemEntry::new(id, ItemSource::Declared),
+                );
+            }
+            ast::ItemKind::Err | ast::ItemKind::ImportOrExport(..) => (),
+        }
+    }
+
+    #[allow(clippy::too_many_lines)]
+    fn bind_import_or_export(
+        &mut self,
+        decl: &ImportOrExportDecl,
+        current_namespace: Option<(NamespaceId, &Idents)>,
+    ) {
+        let (current_namespace, current_namespace_name) = if let Some((a, b)) = current_namespace {
+            (Some(a), Some(b))
+        } else {
+            (None, None)
+        };
+
+        let current_namespace_name: Option<Rc<str>> = current_namespace_name.map(Idents::name);
+        let is_export = decl.is_export();
+
+        for item in decl
+            .items()
+            // filter out any dropped names
+            // this is so you can still export an item that has been conditionally removed from compilation
+            // without a resolution error in the export statement itself
+            // This is not a perfect solution, re-exporting an aliased name from another namespace that has been
+            // conditionally compiled out will still fail. However, this is the only way to solve this
+            // problem without upleveling the preprocessor into the resolver, so it can do resolution-aware
+            // dropped_names population.
+            .filter(|item| {
+                if let Some(ref current_namespace_name) = current_namespace_name {
+                    let item_as_tracked_name =
+                        path_as_tracked_name(&item.path, current_namespace_name);
+                    !self.dropped_names.contains(&item_as_tracked_name)
+                } else {
+                    true
+                }
+            })
+            .collect::<Vec<_>>()
+        {
+            if item.is_glob {
+                self.bind_glob_import_or_export(item, decl.is_export());
+                continue;
+            }
+            let (term_result, ty_result) = (
+                self.resolve_path(NameKind::Term, &item.path),
+                self.resolve_path(NameKind::Ty, &item.path),
+            );
+
+            if let (Err(err), Err(_)) = (&term_result, &ty_result) {
+                // try to see if it is a namespace
+                self.handle_namespace_import_or_export(is_export, item, current_namespace, err);
+                continue;
+            };
+
+            let local_name = item.name().name.clone();
+
+            {
+                let scope = self.current_scope_mut();
+                // if the item already exists in the scope, return a duplicate error
+                let scope_term_result = scope.terms.get(&local_name);
+                let scope_ty_result = scope.tys.get(&local_name);
+                match (is_export, scope_term_result, scope_ty_result) {
+                    // if either has already been exported or imported, generate a duplicate import or export error
+                    (true, Some(entry), _) | (true, _, Some(entry))
+                        if entry.source == ItemSource::Exported =>
+                    {
+                        let err = Error::DuplicateExport(local_name.to_string(), item.name().span);
+                        self.errors.push(err);
+                        continue;
+                    }
+                    (false, Some(entry), _) | (true, _, Some(entry))
+                        if entry.source == ItemSource::Imported =>
+                    {
+                        let err =
+                            Error::ImportedDuplicate(local_name.to_string(), item.name().span);
+                        self.errors.push(err);
+                        continue;
+                    }
+                    _ => (),
+                }
             }
-            ast::ItemKind::Err => {}
+
+            let item_source = if is_export {
+                ItemSource::Exported
+            } else {
+                ItemSource::Imported
+            };
+
+            if let Ok(Res::Item(id, _)) = term_result {
+                if is_export {
+                    if let Some(namespace) = current_namespace {
+                        self.globals
+                            .terms
+                            .get_mut_or_default(namespace)
+                            .insert(local_name.clone(), Res::Item(id, ItemStatus::Available));
+                    }
+                }
+                let scope = self.current_scope_mut();
+                scope
+                    .terms
+                    .insert(local_name.clone(), ScopeItemEntry::new(id, item_source));
+            }
+
+            if let Ok(Res::Item(id, _)) = ty_result {
+                if is_export {
+                    if let Some(namespace) = current_namespace {
+                        self.globals
+                            .tys
+                            .get_mut_or_default(namespace)
+                            .insert(local_name.clone(), Res::Item(id, ItemStatus::Available));
+                    }
+                }
+                let scope = self.current_scope_mut();
+                scope
+                    .tys
+                    .insert(local_name.clone(), ScopeItemEntry::new(id, item_source));
+            }
+
+            let res = match (term_result, ty_result) {
+                (Ok(res @ Res::Item(..)), _) | (_, Ok(res @ Res::Item(..))) => res,
+                (Ok(_), _) | (_, Ok(_)) => {
+                    let err = if is_export {
+                        Error::ExportedNonItem
+                    } else {
+                        Error::ImportedNonItem
+                    };
+                    let err = err(item.path.span);
+                    self.errors.push(err);
+                    continue;
+                }
+                (Err(err), _) => {
+                    self.errors.push(err);
+                    continue;
+                }
+            };
+            // insert the item into the names we know about
+            self.names.insert(item.name().id, res);
+        }
+    }
+
+    /// Very similar to [`bind_import`], but for glob imports.
+    /// Globs can only be attached to namespaces, and
+    /// they import all items from the namespace into the current scope.
+    fn bind_glob_import_or_export(&mut self, item: &ImportOrExportItem, is_export: bool) {
+        if is_export {
+            self.errors
+                .push(Error::GlobExportNotSupported(item.path.span));
+            return;
+        }
+        let items = Into::<Idents>::into(item.path.clone());
+        let ns = self.globals.find_namespace(items.str_iter());
+        let alias = item.alias.as_ref().map(|x| Box::new(x.clone()));
+        let Some(ns) = ns else {
+            self.errors.push(Error::GlobImportNamespaceNotFound(
+                item.path.name.to_string(),
+                item.path.span,
+            ));
+            return;
+        };
+        if !is_export {
+            self.bind_open(&items, &alias, ns);
         }
     }
 
@@ -596,7 +935,7 @@ impl Resolver {
     }
 
     fn push_scope(&mut self, span: Span, kind: ScopeKind) {
-        let scope_id = self.locals.push_scope(Scope::new(kind, span));
+        let scope_id = self.locals.push_scope(kind, span);
         self.curr_scope_chain.push(scope_id);
     }
 
@@ -615,6 +954,44 @@ impl Resolver {
 
         self.locals.get_scope_mut(scope_id)
     }
+
+    fn handle_namespace_import_or_export(
+        &mut self,
+        is_export: bool,
+        item: &ast::ImportOrExportItem,
+        current_namespace: Option<NamespaceId>,
+        err: &Error,
+    ) {
+        let items = Into::<Idents>::into(item.path.clone());
+        let ns = self.globals.find_namespace(items.str_iter());
+        let alias = item
+            .alias
+            .as_ref()
+            .map(|x| Box::new(x.clone()))
+            .or(Some(item.path.name.clone()));
+        if let Some(ns) = ns {
+            if is_export {
+                // for exports, we update the namespace tree accordingly:
+                // update the namespace tree to include the new namespace
+                let alias = alias.unwrap_or(item.path.name.clone());
+                self.globals
+                    .namespaces
+                    .insert_with_id(current_namespace, ns, &alias.name);
+            } else {
+                // for imports, we just bind the namespace as an open
+                self.bind_open(&items, &alias, ns);
+            }
+        } else {
+            self.errors.push(err.clone());
+        }
+    }
+}
+
+fn path_as_tracked_name(path: &ast::Path, current_namespace_name: &Rc<str>) -> TrackedName {
+    TrackedName {
+        name: path.name.name.clone(),
+        namespace: current_namespace_name.clone(),
+    }
 }
 
 /// Constructed from a [Resolver] and an [Assigner], this structure implements `Visitor`
@@ -672,23 +1049,47 @@ impl AstVisitor<'_> for With<'_> {
             .find_namespace(namespace.name.str_iter())
             .expect("namespace should exist by this point");
 
-        let root_id = self.resolver.globals.namespaces.root_id();
-
         let kind = ScopeKind::Namespace(ns);
         self.with_scope(namespace.span, kind, |visitor| {
-            // the below line ensures that this namespace opens itself, in case
-            // we are re-opening a namespace. This is important, as without this,
-            // a re-opened namespace would only have knowledge of its scopes.
-            visitor.resolver.bind_open(&namespace.name, &None, root_id);
             for item in &*namespace.items {
-                if let ast::ItemKind::Open(name, alias) = &*item.kind {
-                    visitor.resolver.bind_open(name, alias, ns);
+                match &*item.kind {
+                    ItemKind::ImportOrExport(..) | ItemKind::Open(..) => {
+                        // Global imports and exports should have been handled
+                        // at this point.
+                    }
+                    _ => ast_visit::walk_item(visitor, item),
                 }
             }
-            ast_visit::walk_namespace(visitor, namespace);
         });
     }
 
+    fn visit_item(&mut self, item: &ast::Item) {
+        match &*item.kind {
+            ItemKind::ImportOrExport(decl) if decl.is_import() => {
+                // Only locally scoped imports and exports are handled here.
+                self.resolver.bind_import_or_export(decl, None);
+            }
+            ItemKind::Open(name, alias) => {
+                let scopes = self.resolver.curr_scope_chain.iter().rev();
+                let namespace = scopes
+                    .into_iter()
+                    .find_map(|scope| {
+                        let scope = self.resolver.locals.get_scope(*scope);
+                        if let ScopeKind::Namespace(id) = scope.kind {
+                            Some(id)
+                        } else {
+                            None
+                        }
+                    })
+                    .unwrap_or_else(|| self.resolver.globals.namespaces.root_id());
+                // Only locally scoped opens are handled here.
+                // There is only a namespace parent scope if we aren't executing incremental fragments.
+                self.resolver.bind_open(name, alias, namespace);
+            }
+            _ => ast_visit::walk_item(self, item),
+        }
+    }
+
     fn visit_attr(&mut self, attr: &ast::Attr) {
         // The Config attribute arguments do not go through name resolution.
         if hir::Attr::from_str(attr.name.name.as_ref()) != Ok(hir::Attr::Config) {
@@ -735,7 +1136,9 @@ impl AstVisitor<'_> for With<'_> {
     fn visit_ty(&mut self, ty: &ast::Ty) {
         match &*ty.kind {
             ast::TyKind::Path(path) => {
-                self.resolver.resolve_path(NameKind::Ty, path);
+                if let Err(e) = self.resolver.resolve_path(NameKind::Ty, path) {
+                    self.resolver.errors.push(e);
+                }
             }
             ast::TyKind::Param(ident) => {
                 self.resolver.resolve_ident(NameKind::Ty, ident);
@@ -748,7 +1151,9 @@ impl AstVisitor<'_> for With<'_> {
         self.with_scope(block.span, ScopeKind::Block, |visitor| {
             for stmt in &*block.stmts {
                 if let ast::StmtKind::Item(item) = &*stmt.kind {
-                    visitor.resolver.bind_local_item(visitor.assigner, item);
+                    visitor
+                        .resolver
+                        .bind_local_item(visitor.assigner, item, None);
                 }
             }
 
@@ -797,7 +1202,11 @@ impl AstVisitor<'_> for With<'_> {
                     visitor.visit_expr(output);
                 });
             }
-            ast::ExprKind::Path(path) => self.resolver.resolve_path(NameKind::Term, path),
+            ast::ExprKind::Path(path) => {
+                if let Err(e) = self.resolver.resolve_path(NameKind::Term, path) {
+                    self.resolver.errors.push(e);
+                };
+            }
             ast::ExprKind::TernOp(ast::TernOp::Update, container, index, replace)
             | ast::ExprKind::AssignUpdate(container, index, replace) => {
                 self.visit_expr(container);
@@ -813,7 +1222,9 @@ impl AstVisitor<'_> for With<'_> {
                 self.visit_expr(replace);
             }
             ast::ExprKind::Struct(path, copy, fields) => {
-                self.resolver.resolve_path(NameKind::Ty, path);
+                if let Err(e) = self.resolver.resolve_path(NameKind::Ty, path) {
+                    self.resolver.errors.push(e);
+                };
                 copy.iter().for_each(|c| self.visit_expr(c));
                 fields.iter().for_each(|f| self.visit_field_assign(f));
             }
@@ -1012,7 +1423,7 @@ fn bind_global_item(
         }
         ast::ItemKind::Ty(name, _) => bind_ty(name, namespace, next_id, item, names, scope),
         ast::ItemKind::Struct(decl) => bind_ty(&decl.name, namespace, next_id, item, names, scope),
-        ast::ItemKind::Err | ast::ItemKind::Open(..) => Ok(()),
+        ast::ItemKind::Err | ast::ItemKind::Open(..) | ast::ItemKind::ImportOrExport(..) => Ok(()),
     }
 }
 
@@ -1152,9 +1563,9 @@ fn resolve<'a>(
             globals,
             provided_namespace_name,
             provided_symbol_name,
-            prelude_namespaces(globals).into_iter().map(|(a, b)| (b, a)),
-            // there are no aliases in the prelude
-            &FxHashMap::default(),
+            prelude_namespaces(globals).into_iter(),
+            // prelude is opened by default
+            &(std::iter::once((vec![], prelude_namespaces(globals))).collect()),
         );
 
         if prelude_candidates.len() > 1 {
@@ -1186,7 +1597,8 @@ fn resolve<'a>(
         }
     }
 
-    // lastly, check unopened globals
+    // Lastly, check unopened globals. This is anything declared in the root namespace, which is
+    // therefore globally available to the package
     let global_candidates = find_symbol_in_namespaces(
         kind,
         globals,
@@ -1203,10 +1615,19 @@ fn resolve<'a>(
         return Ok(res);
     }
 
-    Err(Error::NotFound(
-        provided_symbol_name.name.to_string(),
-        provided_symbol_name.span,
-    ))
+    Err(match provided_namespace_name {
+        Some(ns) => Error::NotFound(
+            ns.push(provided_symbol_name.clone()).name().to_string(),
+            Span {
+                lo: ns.span().lo,
+                hi: provided_symbol_name.span.hi,
+            },
+        ),
+        None => Error::NotFound(
+            provided_symbol_name.name.to_string(),
+            provided_symbol_name.span,
+        ),
+    })
 }
 /// Checks all given scopes, in the correct order, for a resolution.
 /// Calls `check_scoped_resolutions` on each scope, and tracks if we should allow local variables in closures in parent scopes
@@ -1293,6 +1714,7 @@ fn check_scoped_resolutions(
             .map(|open @ Open { namespace, .. }| (*namespace, open)),
         &aliases,
     );
+
     match explicit_open_candidates.len() {
         1 => {
             return Some(Ok(single(explicit_open_candidates.into_keys())
@@ -1357,52 +1779,54 @@ where
     T: Iterator<Item = (NamespaceId, O)>,
     O: Clone + std::fmt::Debug,
 {
-    // check aliases to see if the provided namespace is actually an alias
-    if let Some(provided_namespace_name) = provided_namespace_name {
-        if let Some(opens) = aliases.get(&(Into::<Vec<Rc<_>>>::into(provided_namespace_name))) {
-            let candidates: FxHashMap<_, _> = opens
+    let opens = match provided_namespace_name {
+        None => aliases.get(&Vec::new()),
+        Some(namespace_name) => aliases.get(
+            &namespace_name
                 .iter()
-                .filter_map(|(ns_id, open)| {
-                    globals
-                        .get(kind, *ns_id, &provided_symbol_name.name)
-                        .map(|res| (*res, open.clone()))
-                })
-                .collect();
-            if !candidates.is_empty() {
-                return candidates;
-            }
+                .next()
+                .map(|x| vec![x.name.clone()])
+                .unwrap_or_default(),
+        ),
+    };
+
+    let mut candidates = FxHashMap::default();
+    if let Some(opens) = opens {
+        for open in opens {
+            find_symbol_in_namespace(
+                kind,
+                globals,
+                &provided_namespace_name
+                    .as_ref()
+                    .map(|x| x.iter().skip(1).cloned().collect::<Vec<_>>().into()),
+                provided_symbol_name,
+                &mut candidates,
+                open.0,
+                open.1.clone(),
+            );
+        }
+        // check aliases to see if the provided namespace is actually an alias
+        if provided_namespace_name.is_none() {
+            candidates.extend(&mut opens.iter().filter_map(|(ns_id, open)| {
+                globals
+                    .get(kind, *ns_id, &provided_symbol_name.name)
+                    .map(|res| (*res, open.clone()))
+            }));
         }
     }
 
-    let mut candidates = FxHashMap::default();
     for (candidate_namespace_id, open) in namespaces_to_search {
-        // Retrieve the namespace associated with the candidate_namespace_id from the global namespaces
-        let candidate_namespace = globals
-            .namespaces
-            .find_namespace_by_id(&candidate_namespace_id)
-            .1;
-
-        // Attempt to find a namespace within the candidate_namespace that matches the provided_namespace_name
-        let namespace = provided_namespace_name.as_ref().and_then(|name| {
-            candidate_namespace
-                .borrow()
-                .get_namespace_id(name.str_iter())
-        });
-
-        // if a namespace was provided, but not found, then this is not the correct namespace.
-        if provided_namespace_name.is_some() && namespace.is_none() {
+        if find_symbol_in_namespace(
+            kind,
+            globals,
+            provided_namespace_name,
+            provided_symbol_name,
+            &mut candidates,
+            candidate_namespace_id,
+            open,
+        ) {
             continue;
         }
-
-        // Attempt to get the symbol from the global scope. If the namespace is None, use the candidate_namespace_id as a fallback
-        let res = namespace
-            .or(Some(candidate_namespace_id))
-            .and_then(|ns_id| globals.get(kind, ns_id, &provided_symbol_name.name));
-
-        // If a symbol was found, insert it into the candidates map
-        if let Some(res) = res {
-            candidates.insert(*res, open);
-        }
     }
 
     if candidates.len() > 1 {
@@ -1414,18 +1838,63 @@ where
     candidates
 }
 
+/// returns `true` if the namespace should be skipped/is incorrect, so the caller can
+/// iterate to the next namespace.
+fn find_symbol_in_namespace<O>(
+    kind: NameKind,
+    globals: &GlobalScope,
+    provided_namespace_name: &Option<Idents>,
+    provided_symbol_name: &Ident,
+    candidates: &mut FxHashMap<Res, O>,
+    candidate_namespace_id: NamespaceId,
+    open: O,
+) -> bool
+where
+    O: Clone + std::fmt::Debug,
+{
+    // Retrieve the namespace associated with the candidate_namespace_id from the global namespaces
+    let (_, candidate_namespace) = globals
+        .namespaces
+        .find_namespace_by_id(&candidate_namespace_id);
+
+    // Attempt to find a namespace within the candidate_namespace that matches the provided_namespace_name
+    let namespace = provided_namespace_name.as_ref().and_then(|name| {
+        candidate_namespace
+            .borrow()
+            .get_namespace_id(name.str_iter())
+    });
+
+    // if a namespace was provided, but not found, then this is not the correct namespace.
+    // for example, if the query is `Foo.Bar.Baz`, we know there must exist a `Foo.Bar` somewhere.
+    // If we didn't find it above, then even if we find `Baz` here, it is not the correct location.
+    if provided_namespace_name.is_some() && namespace.is_none() {
+        return true;
+    }
+
+    // Attempt to get the symbol from the global scope. If the namespace is None, use the candidate_namespace_id as a fallback
+    let res = namespace
+        //  .or(Some(candidate_namespace_id))
+        .and_then(|ns_id| globals.get(kind, ns_id, &provided_symbol_name.name));
+
+    // If a symbol was found, insert it into the candidates map
+    if let Some(res) = res {
+        candidates.insert(*res, open);
+    }
+    false
+}
+
 /// Fetch the name and namespace ID of all prelude namespaces.
-fn prelude_namespaces(globals: &GlobalScope) -> Vec<(String, NamespaceId)> {
+pub fn prelude_namespaces(globals: &GlobalScope) -> Vec<(NamespaceId, String)> {
     let mut prelude = Vec::with_capacity(PRELUDE.len());
 
     // add prelude to the list of candidate namespaces last, as they are the final fallback for a symbol
     for prelude_namespace in PRELUDE {
         prelude.push((
-            prelude_namespace.join("."),
             globals
                 .namespaces
                 .get_namespace_id(prelude_namespace)
                 .expect("prelude should always exist in the namespace map"),
+            prelude_namespace.join("."),
         ));
     }
     prelude
@@ -1502,7 +1971,7 @@ fn get_scope_locals(scope: &Scope, offset: u32, vars: bool) -> Vec<Local> {
     // skip adding newtypes since they're already in the terms map
     names.extend(scope.terms.iter().map(|term| Local {
         name: term.0.clone(),
-        kind: LocalKind::Item(*term.1),
+        kind: LocalKind::Item(term.1.id),
     }));
 
     names
diff --git a/compiler/qsc_hir/src/hir.rs b/compiler/qsc_hir/src/hir.rs
index 13a4160a..a8b35881 100644
--- a/compiler/qsc_hir/src/hir.rs
+++ b/compiler/qsc_hir/src/hir.rs
@@ -1586,3 +1586,12 @@ pub enum BinOp {
     /// Bitwise XOR: `^^^`.
     XorB,
 }
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+/// Represents an export declaration.
+pub struct ExportDecl {
+    /// The span.
+    pub span: Span,
+    /// The items being exported from this namespace.
+    pub items: Vec<Idents>,
+}
diff --git a/compiler/qsc_parse/src/item.rs b/compiler/qsc_parse/src/item.rs
index 1cab7d6e..c3a66163 100644
--- a/compiler/qsc_parse/src/item.rs
+++ b/compiler/qsc_parse/src/item.rs
@@ -16,6 +16,8 @@ use super::{
     ty::{self, ty},
     Error, Result,
 };
+
+use crate::lex::ClosedBinOp;
 use crate::{
     lex::{Delim, TokenKind},
     prim::{barrier, path, recovering, recovering_token, shorten},
@@ -24,9 +26,10 @@ use crate::{
     ErrorKind,
 };
 use qsc_ast::ast::{
-    Attr, Block, CallableBody, CallableDecl, CallableKind, FieldDef, Ident, Idents, Item, ItemKind,
-    Namespace, NodeId, Pat, PatKind, Path, Spec, SpecBody, SpecDecl, SpecGen, StmtKind, StructDecl,
-    TopLevelNode, Ty, TyDef, TyDefKind, TyKind, Visibility, VisibilityKind,
+    Attr, Block, CallableBody, CallableDecl, CallableKind, FieldDef, Ident, Idents,
+    ImportOrExportDecl, ImportOrExportItem, Item, ItemKind, Namespace, NodeId, Pat, PatKind, Path,
+    Spec, SpecBody, SpecDecl, SpecGen, StmtKind, StructDecl, TopLevelNode, Ty, TyDef, TyDefKind,
+    TyKind, Visibility, VisibilityKind,
 };
 use qsc_data_structures::language_features::LanguageFeatures;
 use qsc_data_structures::span::Span;
@@ -44,6 +47,8 @@ pub(super) fn parse(s: &mut ParserContext) -> Result<Box<Item>> {
         strct
     } else if let Some(callable) = opt(s, parse_callable_decl)? {
         Box::new(ItemKind::Callable(callable))
+    } else if let Some(decl) = opt(s, parse_import_or_export)? {
+        Box::new(ItemKind::ImportOrExport(decl))
     } else if visibility.is_some() {
         let err_item = default(s.span(lo));
         s.push_error(Error(ErrorKind::FloatingVisibility(err_item.span)));
@@ -135,6 +140,7 @@ fn parse_top_level_node(s: &mut ParserContext) -> Result<TopLevelNode> {
         Ok(TopLevelNode::Stmt(stmt))
     }
 }
+
 pub fn parse_implicit_namespace(source_name: &str, s: &mut ParserContext) -> Result<Namespace> {
     if s.peek().kind == TokenKind::Eof {
         return Ok(Namespace {
@@ -541,6 +547,7 @@ fn parse_spec_gen(s: &mut ParserContext) -> Result<SpecGen> {
         )))
     }
 }
+
 /// Checks that the inputs of the callable are surrounded by parens
 pub(super) fn check_input_parens(inputs: &Pat) -> Result<()> {
     if matches!(*inputs.kind, PatKind::Paren(_) | PatKind::Tuple(_)) {
@@ -549,3 +556,61 @@ pub(super) fn check_input_parens(inputs: &Pat) -> Result<()> {
         Err(Error(ErrorKind::MissingParens(inputs.span)))
     }
 }
+
+/// Parses an import or export statement. Exports start with the `export` keyword, followed by a
+/// list of items.
+/// Imports are the same, but with the `import` keyword.
+///
+/// ```qsharp
+/// export
+///     Foo,
+///     Bar.Baz,
+///     Bar.Quux as Corge;
+/// ```
+fn parse_import_or_export(s: &mut ParserContext) -> Result<ImportOrExportDecl> {
+    let lo = s.peek().span.lo;
+    let _doc = parse_doc(s);
+    let is_export = match s.peek().kind {
+        TokenKind::Keyword(Keyword::Export) => true,
+        TokenKind::Keyword(Keyword::Import) => false,
+        _ => {
+            return Err(Error(ErrorKind::Rule(
+                "import or export",
+                s.peek().kind,
+                s.peek().span,
+            )))
+        }
+    };
+    s.advance();
+    let (items, _) = seq(s, parse_import_or_export_item)?;
+    token(s, TokenKind::Semi)?;
+    Ok(ImportOrExportDecl::new(
+        s.span(lo),
+        items.into_boxed_slice(),
+        is_export,
+    ))
+}
+
+fn parse_import_or_export_item(s: &mut ParserContext) -> Result<ImportOrExportItem> {
+    let mut is_glob = false;
+    let mut parts = vec![*ident(s)?];
+    while token(s, TokenKind::Dot).is_ok() {
+        if token(s, TokenKind::ClosedBinOp(ClosedBinOp::Star)).is_ok() {
+            is_glob = true;
+            break;
+        }
+        parts.push(*ident(s)?);
+    }
+
+    let alias = if token(s, TokenKind::Keyword(Keyword::As)).is_ok() {
+        Some(*(ident(s)?))
+    } else {
+        None
+    };
+
+    Ok(ImportOrExportItem {
+        path: parts.into(),
+        alias,
+        is_glob,
+    })
+}
diff --git a/compiler/qsc_parse/src/keyword.rs b/compiler/qsc_parse/src/keyword.rs
index 708d8685..e87c0ef6 100644
--- a/compiler/qsc_parse/src/keyword.rs
+++ b/compiler/qsc_parse/src/keyword.rs
@@ -24,12 +24,14 @@ pub enum Keyword {
     Distribute,
     Elif,
     Else,
+    Export,
     Fail,
     False,
     Fixup,
     For,
     Function,
     If,
+    Import,
     In,
     Internal,
     Intrinsic,
@@ -81,12 +83,14 @@ impl Keyword {
             Self::Distribute => "distribute",
             Self::Elif => "elif",
             Self::Else => "else",
+            Self::Export => "export",
             Self::Fail => "fail",
             Self::False => "false",
             Self::Fixup => "fixup",
             Self::For => "for",
             Self::Function => "function",
             Self::If => "if",
+            Self::Import => "import",
             Self::In => "in",
             Self::Internal => "internal",
             Self::Intrinsic => "intrinsic",
@@ -158,6 +162,8 @@ impl FromStr for Keyword {
             "Zero" => Ok(Self::Zero),
             "One" => Ok(Self::One),
             "namespace" => Ok(Self::Namespace),
+            "export" => Ok(Self::Export),
+            "import" => Ok(Self::Import),
             "mutable" => Ok(Self::Mutable),
             "internal" => Ok(Self::Internal),
             "PauliZ" => Ok(Self::PauliZ),
diff --git a/compiler/qsc_parse/src/lib.rs b/compiler/qsc_parse/src/lib.rs
index 1b72607a..32e8f405 100644
--- a/compiler/qsc_parse/src/lib.rs
+++ b/compiler/qsc_parse/src/lib.rs
@@ -83,7 +83,7 @@ enum ErrorKind {
     #[error("file name {1} could not be converted into valid namespace name")]
     #[diagnostic(code("Qsc.Parse.InvalidFileName"))]
     InvalidFileName(#[label] Span, String),
-    #[error("expected an item or EOF, found {0}")]
+    #[error("expected an item or closing brace, found {0}")]
     #[diagnostic(code("Qsc.Parse.ExpectedItem"))]
     ExpectedItem(TokenKind, #[label] Span),
 }
diff --git a/compiler/qsc_parse/src/prim.rs b/compiler/qsc_parse/src/prim.rs
index c7118bd8..1254a269 100644
--- a/compiler/qsc_parse/src/prim.rs
+++ b/compiler/qsc_parse/src/prim.rs
@@ -165,7 +165,8 @@ pub(super) fn many<T>(s: &mut ParserContext, mut p: impl Parser<T>) -> Result<Ve
     }
     Ok(xs)
 }
-
+/// Parses a sequence of items separated by commas.
+/// Supports recovering on missing items.
 pub(super) fn seq<T>(s: &mut ParserContext, mut p: impl Parser<T>) -> Result<(Vec<T>, FinalSep)>
 where
     T: Default + WithSpan,
diff --git a/katas/content/multi_qubit_measurements/examples/MultiQubitProbabilities.qs b/katas/content/multi_qubit_measurements/examples/MultiQubitProbabilities.qs
index 6b2de2d3..73c18740 100644
--- a/katas/content/multi_qubit_measurements/examples/MultiQubitProbabilities.qs
+++ b/katas/content/multi_qubit_measurements/examples/MultiQubitProbabilities.qs
@@ -9,7 +9,8 @@ namespace Kata {
         Ry(2. * ArcCos(2. / 3.), qs[1]);
         Controlled Ry([qs[1]], (2. * ArcCos(1. / Sqrt(5.)), qs[0]));
 
-        Message("The initial state of the system:");
+        Message("The initial state of the system");
+        Message("(includes measurement outcome probabilities in computational basis):");
         DumpMachine();
 
         // Change the basis
@@ -17,6 +18,7 @@ namespace Kata {
         H(qs[1]);
 
         Message("Final state of the two-qubit system:");
+        Message("(includes measurement outcome probabilities in Pauli X basis):");
         DumpMachine();
 
         // Return the qubit array into state |00❭.
diff --git a/katas/content/multi_qubit_measurements/index.md b/katas/content/multi_qubit_measurements/index.md
index 34b2f32f..695b00c3 100644
--- a/katas/content/multi_qubit_measurements/index.md
+++ b/katas/content/multi_qubit_measurements/index.md
@@ -72,14 +72,14 @@ This can be summarized in the following table:
 ### Multi-Qubit Measurement Outcome Probabilities I
 
 You are given a two-qubit system in the following state:
-$$\ket \psi =  \frac{1}{3}\ket {00} + \frac{2}{3} \ket {01} + \frac{2}{3}\ket {11}$$
+$$\ket \psi =  \frac{2}{3}\ket {00} + \frac{1}{3} \ket {01} + \frac{2}{3}\ket {11}$$
 
 If all the qubits are measured simultaneously in the computational basis, what are the outcome probabilities?
 
 <details>
 <summary><b>Solution</b></summary>
 
-The wave function $\ket{\psi}$ is normalized, since $\left(\frac{1}{3}\right)^2 + \left(\frac{2}{3}\right)^2 + \left(\frac{2}{3}\right)^2 = 1$. Hence, the probabilities of measuring each of the computational basis states is simply the square of the absolute value of the corresponding coefficients. That is, the probabilities of measuring $00$, $01$ and $11$ are $\frac{1}{9}$, $\frac{4}{9}$ and $\frac{4}{9}$, respectively, and the probability of measuring the basis state $10$ that is not part of the superposition is $0$:
+The wave function $\ket{\psi}$ is normalized, since $\left(\frac{2}{3}\right)^2 + \left(\frac{1}{3}\right)^2 + \left(\frac{2}{3}\right)^2 = 1$. Hence, the probabilities of measuring each of the computational basis states is simply the square of the absolute value of the corresponding coefficients. That is, the probabilities of measuring $00$, $01$ and $11$ are $\frac{4}{9}$, $\frac{1}{9}$ and $\frac{4}{9}$, respectively, and the probability of measuring the basis state $10$ that is not part of the superposition is $0$:
 
 <table>
     <tr>
@@ -88,11 +88,11 @@ The wave function $\ket{\psi}$ is normalized, since $\left(\frac{1}{3}\right)^2
     </tr>
     <tr>
         <td>$00$</td>
-        <td>$\left( \frac{1}{3}\right)^2 = \frac{1}{9}$</td>
+        <td>$\left( \frac{2}{3}\right)^2 = \frac{4}{9}$</td>
     </tr>
     <tr>
         <td>$01$</td>
-        <td>$\left( \frac{2}{3}\right)^2 = \frac{4}{9}$</td>
+        <td>$\left( \frac{1}{3}\right)^2 = \frac{1}{9}$</td>
     </tr>
     <tr>
         <td>$10$</td>
@@ -107,13 +107,13 @@ The wave function $\ket{\psi}$ is normalized, since $\left(\frac{1}{3}\right)^2
 
 ### Multi-Qubit Measurement Outcome Probabilities II
 
-You are given a two-qubit system in the following state:
+You are given a two-qubit system in the same state as in the previous exercise:
 $$\ket \psi = \frac{2}{3}\ket {00} + \frac{1}{3} \ket {01} + \frac{2}{3}\ket {11}$$
 
 If all the qubits are measured simultaneously in the Pauli X basis, that is, in the $\{ \ket{++}, \ket{+-}, \ket{-+}, \ket{--}\}$ basis, what are the outcome probabilities?
 
 <details>
-<summary><b>Analytical Solution</b></summary>
+<summary><b>Solution</b></summary>
 
 Using the expressions $\ket{0} = \frac{1}{\sqrt{2}} \big( \ket{+} + \ket{-} \big)$ and $\ket{1} = \frac{1}{\sqrt{2}} \big( \ket{+} - \ket{-} \big)$, we first express $\ket{\psi}$ in the Pauli X basis. This gives us
 $$\ket \psi =  \frac{2}{3}\ket {00} + \frac{1}{3} \ket {01} + \frac{2}{3}\ket {11} =$$
@@ -154,14 +154,16 @@ After this, the probabilities of measuring each of the four basis vectors is giv
 
 </details>
 
-<details>
-<summary><b>Code-Based Solution</b></summary>
 
-We can also use Q# to solve this problem. It can be achieved in three steps:
+Now, let's see how we can use Q# to solve these two problems.
 
-1. Prepare the state $\ket \psi$.
-2. Apply a transformation that maps the 2-qubit Pauli X basis into the 2-qubit computational basis. This transformation just applies a Hadamard gate to each of the qubits.
-3. View probabilities of each basis state with the `DumpMachine` function. Thanks to the previous step, the following state equivalence holds:
+1. We start by preparing the state $\ket \psi$.
+   To do this, we can represent $\ket \psi$ as follows:
+   $$\frac 2 3 \ket{00} + \big( \frac 1 {\sqrt 5} \ket{0} + \frac 2 {\sqrt 5} \ket{1} \big) \frac {\sqrt 5} 3 \ket{1}$$
+   This representation tells us how we should rotate individual qubits.
+2. To figure out the measurement outcome probabilities in the computational basis, we can just use the `DumpMachine` function that lists probabilities associated with each basis state present in the superposition.
+3. To figure out the measurement outcome probabilities in the Pauli X basis, we can apply a transformation that maps the two-qubit Pauli X basis into the two-qubit computational basis. This transformation just applies a Hadamard gate to each of the qubits.
+4. View probabilities of each basis state with the `DumpMachine` function. Thanks to the previous step, the following state equivalence holds:
 
 <table>
     <tr>
@@ -188,18 +190,11 @@ We can also use Q# to solve this problem. It can be achieved in three steps:
 
 The amplitudes of the computational basis states after the transformation are the same as the amplitudes of the basis states of the Pauli X basis before the transformation!
 
->To implement the first step, we can represent $\ket \psi$ as  
->$$\frac 2 3 \ket {00} + {\big (} \frac 1 {\sqrt 5} \ket {0} + \frac 2 {\sqrt 5} \ket {1} {\big )} \frac {\sqrt 5} 3 \ket {1}$$
->This representation tells us how we should rotate individual qubits.
->
->Notice that we start by rotating the second qubit, as this gives a simpler implementation. If we started by rotating the first qubit, we would need to use a $CNOT$ gate and a controlled $R_y$ gate to achieve the same result.
-
 @[example]({
-    "id": "multi_qubit_measurements__multi_qubit_probabilities_2_example",
+    "id": "multi_qubit_measurements__multi_qubit_probabilities_example",
     "codePath": "./examples/MultiQubitProbabilities.qs"
 })
 
-</details>
 
 
 ## Measuring Each Qubit in a System Sequentially


Test patch:
diff --git a/compiler/qsc/src/interpret/circuit_tests.rs b/compiler/qsc/src/interpret/circuit_tests.rs
index 6b9a4fa7..4a17be1d 100644
--- a/compiler/qsc/src/interpret/circuit_tests.rs
+++ b/compiler/qsc/src/interpret/circuit_tests.rs
@@ -720,9 +720,9 @@ fn internal_operation() {
                         Error(
                             Resolve(
                                 NotFound(
-                                    "Test",
+                                    "Test.Test",
                                     Span {
-                                        lo: 5,
+                                        lo: 0,
                                         hi: 9,
                                     },
                                 ),
diff --git a/compiler/qsc_data_structures/src/namespaces/tests.rs b/compiler/qsc_data_structures/src/namespaces/tests.rs
index baed9abc..859cf802 100644
--- a/compiler/qsc_data_structures/src/namespaces/tests.rs
+++ b/compiler/qsc_data_structures/src/namespaces/tests.rs
@@ -22,169 +22,43 @@ fn test_tree_construction() {
         }
     }
     expect![[r#"
-        NamespaceTreeRoot {
-            assigner: 18,
-            tree: RefCell {
-                value: NamespaceTreeNode {
-                    children: {
-                        "ns1": RefCell {
-                            value: NamespaceTreeNode {
-                                children: {
-                                    "nsc": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                14,
-                                            ),
-                                        },
-                                    },
-                                    "nsb": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                13,
-                                            ),
-                                        },
-                                    },
-                                    "nsa": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                12,
-                                            ),
-                                        },
-                                    },
-                                },
-                                id: NamespaceId(
-                                    11,
-                                ),
-                            },
-                        },
-                        "ns0": RefCell {
-                            value: NamespaceTreeNode {
-                                children: {
-                                    "nsc": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                10,
-                                            ),
-                                        },
-                                    },
-                                    "nsb": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                9,
-                                            ),
-                                        },
-                                    },
-                                    "nsa": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                8,
-                                            ),
-                                        },
-                                    },
-                                },
-                                id: NamespaceId(
-                                    7,
-                                ),
-                            },
-                        },
-                        "Microsoft": RefCell {
-                            value: NamespaceTreeNode {
-                                children: {
-                                    "Quantum": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {
-                                                "Canon": RefCell {
-                                                    value: NamespaceTreeNode {
-                                                        children: {},
-                                                        id: NamespaceId(
-                                                            3,
-                                                        ),
-                                                    },
-                                                },
-                                                "Measurement": RefCell {
-                                                    value: NamespaceTreeNode {
-                                                        children: {},
-                                                        id: NamespaceId(
-                                                            6,
-                                                        ),
-                                                    },
-                                                },
-                                                "Core": RefCell {
-                                                    value: NamespaceTreeNode {
-                                                        children: {},
-                                                        id: NamespaceId(
-                                                            4,
-                                                        ),
-                                                    },
-                                                },
-                                                "Intrinsic": RefCell {
-                                                    value: NamespaceTreeNode {
-                                                        children: {},
-                                                        id: NamespaceId(
-                                                            5,
-                                                        ),
-                                                    },
-                                                },
-                                            },
-                                            id: NamespaceId(
-                                                2,
-                                            ),
-                                        },
-                                    },
-                                },
-                                id: NamespaceId(
-                                    1,
-                                ),
-                            },
-                        },
-                        "ns2": RefCell {
-                            value: NamespaceTreeNode {
-                                children: {
-                                    "nsc": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                18,
-                                            ),
-                                        },
-                                    },
-                                    "nsb": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                17,
-                                            ),
-                                        },
-                                    },
-                                    "nsa": RefCell {
-                                        value: NamespaceTreeNode {
-                                            children: {},
-                                            id: NamespaceId(
-                                                16,
-                                            ),
-                                        },
-                                    },
-                                },
-                                id: NamespaceId(
-                                    15,
-                                ),
-                            },
-                        },
-                    },
-                    id: NamespaceId(
-                        0,
-                    ),
+        NamespaceTreeRoot
+
+          children: [
+            ns1(id 11) {
+              children: [
+                nsc(id 14) {empty node},
+                nsb(id 13) {empty node},
+                nsa(id 12) {empty node},
+              ]
+            },
+            ns0(id 7) {
+              children: [
+                nsc(id 10) {empty node},
+                nsb(id 9) {empty node},
+                nsa(id 8) {empty node},
+              ]
+            },
+            Microsoft(id 1) {
+              children: [
+                Quantum(id 2) {
+                  children: [
+                    Canon(id 3) {empty node},
+                    Measurement(id 6) {empty node},
+                    Core(id 4) {empty node},
+                    Intrinsic(id 5) {empty node},
+                  ]
                 },
+              ]
             },
-            memo: RefCell {
-                value: {},
+            ns2(id 15) {
+              children: [
+                nsc(id 18) {empty node},
+                nsb(id 17) {empty node},
+                nsa(id 16) {empty node},
+              ]
             },
+          ]
         }
     "#]]
     .assert_debug_eq(&root);
@@ -215,12 +89,7 @@ fn test_find_id() {
                     "nsa",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            8,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -229,12 +98,7 @@ fn test_find_id() {
                     "nsb",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            9,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -243,12 +107,7 @@ fn test_find_id() {
                     "nsc",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            10,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -256,36 +115,12 @@ fn test_find_id() {
                     "ns0",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {
-                            "nsc": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        10,
-                                    ),
-                                },
-                            },
-                            "nsb": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        9,
-                                    ),
-                                },
-                            },
-                            "nsa": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        8,
-                                    ),
-                                },
-                            },
-                        },
-                        id: NamespaceId(
-                            7,
-                        ),
+                    value: 
+                      children: [
+                        nsc(id 10) {empty node},
+                        nsb(id 9) {empty node},
+                        nsa(id 8) {empty node},
+                      ]
                     },
                 },
             ),
@@ -295,12 +130,7 @@ fn test_find_id() {
                     "nsa",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            12,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -309,12 +139,7 @@ fn test_find_id() {
                     "nsb",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            13,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -323,12 +148,7 @@ fn test_find_id() {
                     "nsc",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            14,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -336,36 +156,12 @@ fn test_find_id() {
                     "ns1",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {
-                            "nsc": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        14,
-                                    ),
-                                },
-                            },
-                            "nsb": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        13,
-                                    ),
-                                },
-                            },
-                            "nsa": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        12,
-                                    ),
-                                },
-                            },
-                        },
-                        id: NamespaceId(
-                            11,
-                        ),
+                    value: 
+                      children: [
+                        nsc(id 14) {empty node},
+                        nsb(id 13) {empty node},
+                        nsa(id 12) {empty node},
+                      ]
                     },
                 },
             ),
@@ -375,12 +171,7 @@ fn test_find_id() {
                     "nsa",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            16,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -389,12 +180,7 @@ fn test_find_id() {
                     "nsb",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            17,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -403,12 +189,7 @@ fn test_find_id() {
                     "nsc",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {},
-                        id: NamespaceId(
-                            18,
-                        ),
-                    },
+                    value: empty node},
                 },
             ),
             (
@@ -416,36 +197,12 @@ fn test_find_id() {
                     "ns2",
                 ],
                 RefCell {
-                    value: NamespaceTreeNode {
-                        children: {
-                            "nsc": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        18,
-                                    ),
-                                },
-                            },
-                            "nsb": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        17,
-                                    ),
-                                },
-                            },
-                            "nsa": RefCell {
-                                value: NamespaceTreeNode {
-                                    children: {},
-                                    id: NamespaceId(
-                                        16,
-                                    ),
-                                },
-                            },
-                        },
-                        id: NamespaceId(
-                            15,
-                        ),
+                    value: 
+                      children: [
+                        nsc(id 18) {empty node},
+                        nsb(id 17) {empty node},
+                        nsa(id 16) {empty node},
+                      ]
                     },
                 },
             ),
diff --git a/compiler/qsc_frontend/src/compile/tests.rs b/compiler/qsc_frontend/src/compile/tests.rs
index c7227aea..b80064c7 100644
--- a/compiler/qsc_frontend/src/compile/tests.rs
+++ b/compiler/qsc_frontend/src/compile/tests.rs
@@ -273,7 +273,7 @@ fn entry_error() {
 
     let unit = default_compile(sources);
     assert_eq!(
-        ("<entry>", Span { lo: 4, hi: 5 }),
+        ("<entry>", Span { lo: 0, hi: 5 }),
         source_span(&unit.sources, &unit.errors[0])
     );
 }
@@ -550,7 +550,7 @@ fn package_dependency_internal_error() {
         .iter()
         .map(|error| source_span(&unit2.sources, error))
         .collect();
-    assert_eq!(vec![("test", Span { lo: 65, hi: 68 }),], errors);
+    assert_eq!(vec![("test", Span { lo: 56, hi: 68 }),], errors);
 
     expect![[r#"
         Package:
diff --git a/compiler/qsc_frontend/src/incremental/tests.rs b/compiler/qsc_frontend/src/incremental/tests.rs
index 44d906e7..6cf2b2dc 100644
--- a/compiler/qsc_frontend/src/incremental/tests.rs
+++ b/compiler/qsc_frontend/src/incremental/tests.rs
@@ -72,9 +72,7 @@ fn one_callable() {
                             ),
                         ),
                         opens: {
-                            [
-                                "Foo",
-                            ]: [
+                            []: [
                                 Open {
                                     namespace: NamespaceId(
                                         9,
diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs
index de299d94..4b9591db 100644
--- a/compiler/qsc_frontend/src/resolve/tests.rs
+++ b/compiler/qsc_frontend/src/resolve/tests.rs
@@ -29,6 +29,7 @@ use rustc_hash::FxHashMap;
 use std::fmt::Write;
 use std::rc::Rc;
 
+#[derive(Debug)]
 enum Change {
     Res(Res),
     NamespaceId(NamespaceId),
@@ -50,7 +51,7 @@ struct Renamer<'a> {
     names: &'a Names,
     changes: Vec<(Span, Change)>,
     namespaces: NamespaceTreeRoot,
-    aliases: FxHashMap<Vec<std::rc::Rc<str>>, NamespaceId>,
+    aliases: FxHashMap<Vec<Rc<str>>, NamespaceId>,
 }
 
 impl<'a> Renamer<'a> {
@@ -66,21 +67,25 @@ impl<'a> Renamer<'a> {
     fn rename(&self, input: &mut String) {
         for (span, change) in self.changes.iter().rev() {
             let name = match change {
-                Change::Res(res) => match res {
-                    Res::Item(item, _) => match item.package {
-                        None => format!("item{}", item.item),
-                        Some(package) => format!("package{package}_item{}", item.item),
-                    },
-                    Res::Local(node) => format!("local{node}"),
-                    Res::PrimTy(prim) => format!("{prim:?}"),
-                    Res::UnitTy => "Unit".to_string(),
-                    Res::Param(id) => format!("param{id}"),
-                },
+                Change::Res(res) => Self::format_res(res),
                 Change::NamespaceId(ns_id) => format!("namespace{}", Into::<usize>::into(ns_id)),
             };
             input.replace_range((span.lo as usize)..(span.hi as usize), &name);
         }
     }
+
+    fn format_res(res: &Res) -> String {
+        match res {
+            Res::Item(item, _) => match item.package {
+                None => format!("item{}", item.item),
+                Some(package) => format!("package{package}_item{}", item.item),
+            },
+            Res::Local(node) => format!("local{node}"),
+            Res::PrimTy(prim) => format!("{prim:?}"),
+            Res::UnitTy => "Unit".to_string(),
+            Res::Param(id) => format!("param{id}"),
+        }
+    }
 }
 
 impl Visitor<'_> for Renamer<'_> {
@@ -99,11 +104,27 @@ impl Visitor<'_> for Renamer<'_> {
     }
 
     fn visit_item(&mut self, item: &'_ Item) {
-        if let ItemKind::Open(namespace, Some(alias)) = &*item.kind {
-            let Some(ns_id) = self.namespaces.get_namespace_id(namespace.str_iter()) else {
+        match &*item.kind {
+            ItemKind::Open(namespace, Some(alias)) => {
+                let Some(ns_id) = self.namespaces.get_namespace_id(namespace.str_iter()) else {
+                    return;
+                };
+                self.aliases.insert(vec![alias.name.clone()], ns_id);
+            }
+            ItemKind::ImportOrExport(export) => {
+                for item in export.items() {
+                    if let Some(resolved_id) = self.names.get(item.path.id) {
+                        self.changes.push((item.span(), (*resolved_id).into()));
+                    } else if let Some(namespace_id) = self
+                        .namespaces
+                        .get_namespace_id(Into::<Idents>::into(item.clone().path).str_iter())
+                    {
+                        self.changes.push((item.span(), namespace_id.into()));
+                    }
+                }
                 return;
-            };
-            self.aliases.insert(vec![alias.name.clone()], ns_id);
+            }
+            _ => (),
         }
         visit::walk_item(self, item);
     }
@@ -175,6 +196,7 @@ fn compile(
     let mut globals = super::GlobalTable::new();
     let mut errors = globals.add_local_package(&mut assigner, &package);
     let mut resolver = Resolver::new(globals, dropped_names);
+    resolver.bind_and_resolve_imports_and_exports(&package);
     resolver.with(&mut assigner).visit_package(&package);
     let (names, locals, mut resolve_errors, namespaces) = resolver.into_result();
     errors.append(&mut resolve_errors);
@@ -3521,3 +3543,1393 @@ namespace Foo {
             }"#]],
     );
 }
+
+#[test]
+fn test_export_statement() {
+    check(
+        indoc! {"namespace Foo {
+    operation ApplyX() : Unit {
+    }
+    export ApplyX;
+}
+" },
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {
+                }
+                export item1;
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn test_complicated_nested_export_statement() {
+    check(
+        indoc! {
+"
+
+namespace Foo {
+    export Foo.Bar.Baz.Quux.HelloWorld;
+}
+namespace Foo.Bar.Baz.Quux {
+    function HelloWorld() : Unit {}
+}
+
+namespace Foo.Bar {
+   export Baz.Quux.HelloWorld;
+}
+
+namespace Foo.Bar.Baz {
+    export Quux.HelloWorld;
+}
+
+namespace Foo.Bar.Graule {
+    // HelloWorld should be available from all namespaces
+    operation Main() : Unit {
+        Foo.Bar.Baz.Quux.HelloWorld();
+        Foo.Bar.Baz.HelloWorld();
+        Foo.Bar.HelloWorld();
+        Foo.HelloWorld();
+        open Foo;
+        HelloWorld();
+    }
+    // and we should be able to re-export it
+    export Foo.HelloWorld;
+}" },
+        &expect![[r#"
+
+            namespace namespace7 {
+                export item2;
+            }
+            namespace namespace10 {
+                function item2() : Unit {}
+            }
+
+            namespace namespace8 {
+               export item2;
+            }
+
+            namespace namespace9 {
+                export item2;
+            }
+
+            namespace namespace11 {
+                // HelloWorld should be available from all namespaces
+                operation item6() : Unit {
+                    item2();
+                    item2();
+                    item2();
+                    item2();
+                    open namespace7;
+                    item2();
+                }
+                // and we should be able to re-export it
+                export item2;
+            }"#]],
+    );
+}
+
+#[test]
+fn exports_aware_of_opens() {
+    check(
+        indoc! {r#"
+            namespace Foo {
+                operation F() : Unit {}
+            }
+            namespace Main {
+                open Foo;
+                export F;
+            }
+            "# },
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+            }
+            namespace namespace8 {
+                open namespace7;
+                export item1;
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn export_symbol_and_call_it() {
+    check(
+        indoc! {
+"
+namespace Foo {
+    export Foo.Bar.Baz.Quux.Function;
+}
+namespace Foo.Bar.Baz.Quux {
+    function Function() : Unit {}
+}
+
+namespace Main {
+  open Foo;
+  operation Main() : Unit {
+    Foo.Function();
+    Function();
+  }
+}" },
+        &expect![[r#"
+            namespace namespace7 {
+                export item2;
+            }
+            namespace namespace10 {
+                function item2() : Unit {}
+            }
+
+            namespace namespace11 {
+              open namespace7;
+              operation item4() : Unit {
+                item2();
+                item2();
+              }
+            }"#]],
+    );
+}
+
+#[test]
+fn export_non_existent_symbol() {
+    check(
+        indoc! {"
+            namespace Foo {
+                export NonExistent;
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                export NonExistent;
+            }
+
+            // NotFound("NonExistent", Span { lo: 27, hi: 38 })
+        "#]],
+    );
+}
+
+#[test]
+fn export_symbol_from_nested_namespace() {
+    check(
+        indoc! {"
+            namespace Foo.Bar  {
+                operation ApplyX() : Unit {}
+            }
+            namespace Foo {
+                export Bar.ApplyX;
+            }
+            namespace Main {
+                open Foo;
+                operation Main() : Unit {
+                    Bar.ApplyX();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace8  {
+                operation item1() : Unit {}
+            }
+            namespace namespace7 {
+                export item1;
+            }
+            namespace namespace9 {
+                open namespace7;
+                operation item4() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn disallow_exporting_local_vars() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation Main() : Unit {
+                    let x = 5;
+                }
+                export x;
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {
+                    let local13 = 5;
+                }
+                export x;
+            }
+
+            // NotFound("x", Span { lo: 82, hi: 83 })
+        "#]],
+    );
+}
+
+#[test]
+fn export_non_item() {
+    check(
+        indoc! {"
+            namespace Bar {}
+            namespace Foo {
+                operation Main() : Unit {
+                }
+                export Unit;
+
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {}
+            namespace namespace8 {
+                operation item2() : Unit {
+                }
+                export Unit;
+
+            }
+
+            // ExportedNonItem(Span { lo: 80, hi: 84 })
+        "#]],
+    );
+}
+
+#[test]
+fn export_with_alias() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                export ApplyX as SomeAlias;
+            }
+            namespace Main {
+                open Foo;
+                operation Main() : Unit {
+                    SomeAlias();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                export item1;
+            }
+            namespace namespace8 {
+                open namespace7;
+                operation item3() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn multiple_exports_with_aliases() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+                export ApplyX as SomeAlias, ApplyY as AnotherAlias;
+            }
+            namespace Main {
+                open Foo;
+                operation Main() : Unit {
+                    SomeAlias();
+                    AnotherAlias();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+                export item1, item2;
+            }
+            namespace namespace8 {
+                open namespace7;
+                operation item4() : Unit {
+                    item1();
+                    item2();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn aliased_exports_call_with_qualified_paths() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+                export ApplyX as SomeAlias, ApplyY as AnotherAlias;
+            }
+            namespace Main {
+                open Foo;
+                operation Main() : Unit {
+                    Foo.SomeAlias();
+                    Foo.AnotherAlias();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+                export item1, item2;
+            }
+            namespace namespace8 {
+                open namespace7;
+                operation item4() : Unit {
+                    item1();
+                    item2();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn reexport_from_full_path_with_alias() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                export ApplyX as SomeAlias;
+            }
+            namespace Main {
+                open Foo;
+                export Foo.SomeAlias as AnotherAlias;
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                export item1;
+            }
+            namespace namespace8 {
+                open namespace7;
+                export item1;
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn disallow_repeated_exports() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                export ApplyX;
+                export ApplyX;
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                export item1;
+                export item1;
+            }
+
+            // DuplicateExport("ApplyX", Span { lo: 79, hi: 85 })
+        "#]],
+    );
+}
+
+#[test]
+fn disallow_repeated_exports_inline() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                export ApplyX, ApplyX;
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                export item1, item1;
+            }
+
+            // DuplicateExport("ApplyX", Span { lo: 68, hi: 74 })
+        "#]],
+    );
+}
+
+#[test]
+fn order_of_exports_does_not_matter() {
+    check(
+        indoc! {"
+            namespace Bar {
+                export Foo.ApplyX;
+                export ApplyY;
+                operation ApplyY() : Unit {}
+            }
+            namespace Foo {
+                operation ApplyX() : Unit {}
+            }
+
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                export item3;
+                export item1;
+                operation item1() : Unit {}
+            }
+            namespace namespace8 {
+                operation item3() : Unit {}
+            }
+
+        "#]],
+    );
+}
+
+#[test]
+fn export_udt_and_construct_it() {
+    check(
+        indoc! {"
+            namespace Foo {
+                newtype Pair = (First: Int, Second: Int);
+                export Pair;
+            }
+            namespace Main {
+                open Foo;
+                operation Main() : Unit {
+                    let z: Pair = Pair(1, 2);
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                newtype item1 = (First: Int, Second: Int);
+                export item1;
+            }
+            namespace namespace8 {
+                open namespace7;
+                operation item3() : Unit {
+                    let local33: item1 = item1(1, 2);
+                }
+            }
+        "#]],
+    );
+}
+#[test]
+fn import_single_item() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function Bar() : Unit {}
+            }
+            namespace Main {
+                import Foo.Bar;
+                operation Main() : Unit {
+                    Bar();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                function item1() : Unit {}
+            }
+            namespace namespace8 {
+                import item1;
+                operation item3() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_namespace() {
+    check(
+        indoc! {"
+            namespace Foo.Bar {
+                function Baz() : Unit {}
+            }
+            namespace Main {
+                import Foo.Bar;
+                operation Main() : Unit {
+                    Bar.Baz();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace8 {
+                function item1() : Unit {}
+            }
+            namespace namespace9 {
+                import namespace8;
+                operation item3() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_non_existent_item() {
+    check(
+        indoc! {"
+            namespace Foo {
+            }
+            namespace Main {
+                import Foo.Bar;
+                operation Main() : Unit {
+                    Bar();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+            }
+            namespace namespace8 {
+                import Foo.Bar;
+                operation item2() : Unit {
+                    Bar();
+                }
+            }
+
+            // NotFound("Foo.Bar", Span { lo: 46, hi: 53 })
+            // NotFound("Bar", Span { lo: 93, hi: 96 })
+        "#]],
+    );
+}
+
+#[test]
+fn import_shadowing() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function Bar() : Unit {}
+            }
+            namespace Main {
+                function Bar() : Unit {}
+                import Foo.Bar;
+                operation Main() : Unit {
+                    Bar();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                function item1() : Unit {}
+            }
+            namespace namespace8 {
+                function item3() : Unit {}
+                import item1;
+                operation item4() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_with_alias() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function Bar() : Unit {}
+            }
+            namespace Main {
+                import Foo.Bar as Baz;
+                operation Main() : Unit {
+                    Baz();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                function item1() : Unit {}
+            }
+            namespace namespace8 {
+                import item1;
+                operation item3() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_non_item() {
+    check(
+        indoc! {"
+            namespace Main {
+                import Unit;
+                operation Main() : Unit {
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                import Unit;
+                operation item1() : Unit {
+                }
+            }
+
+            // ImportedNonItem(Span { lo: 28, hi: 32 })
+        "#]],
+    );
+}
+
+#[test]
+fn import_namespace_nested() {
+    check(
+        indoc! {"
+            namespace Foo.Bar.Baz {
+                operation Quux() : Unit {}
+            }
+            namespace Main {
+                import Foo.Bar;
+                operation Main() : Unit {
+                    Bar.Baz.Quux();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace9 {
+                operation item1() : Unit {}
+            }
+            namespace namespace10 {
+                import namespace8;
+                operation item3() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_single_namespace() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation Bar() : Unit {}
+            }
+            namespace Main {
+                import Foo;
+
+                operation Main() : Unit {
+                    Foo.Bar();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+            }
+            namespace namespace8 {
+                import namespace7;
+
+                operation item3() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_shadowing_function() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation Bar() : Unit {}
+            }
+            namespace Main {
+                operation Bar() : Unit {}
+                operation Main() : Unit {
+                    import Foo.Bar;
+                    Bar();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+            }
+            namespace namespace8 {
+                operation item3() : Unit {}
+                operation item4() : Unit {
+                    import item1;
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_non_existent_namespace() {
+    check(
+        indoc! {"
+            namespace Main {
+                operation Main() : Unit {
+                    import NonExistent;
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {
+                    import NonExistent;
+                }
+            }
+
+            // NotFound("NonExistent", Span { lo: 62, hi: 73 })
+        "#]],
+    );
+}
+
+#[test]
+fn import_self() {
+    check(
+        indoc! {"
+            namespace Main {
+                operation Foo() : Unit {
+                    import Foo;
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {
+                    import item1;
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_duplicate_symbol() {
+    check(
+        indoc! { r#"
+        namespace Main {
+            import Foo.Bar.Baz, Foo.Bar.Baz;
+        }
+        namespace Foo.Bar {
+            operation Baz() : Unit {}
+        }
+"# },
+        &expect![[r#"
+            namespace namespace7 {
+                import item2, item2;
+            }
+            namespace namespace9 {
+                operation item2() : Unit {}
+            }
+
+            // ImportedDuplicate("Baz", Span { lo: 49, hi: 52 })
+        "#]],
+    );
+}
+
+#[test]
+fn import_duplicate_symbol_different_name() {
+    check(
+        indoc! { r#"
+        namespace Main {
+            import Foo.Bar.Baz, Foo.Bar;
+            import Bar.Baz;
+        }
+        namespace Foo.Bar {
+            operation Baz() : Unit {}
+        }
+"# },
+        &expect![[r#"
+            namespace namespace7 {
+                import item2, namespace9;
+                import item2;
+            }
+            namespace namespace9 {
+                operation item2() : Unit {}
+            }
+
+            // ImportedDuplicate("Baz", Span { lo: 65, hi: 68 })
+        "#]],
+    );
+}
+#[test]
+fn import_takes_precedence_over_local_decl() {
+    check(
+        indoc! { r#"
+        namespace Main {
+
+            operation Baz() : Unit {
+                import Foo.Bar.Baz;
+                Baz();
+            }
+
+        }
+
+        namespace Foo.Bar {
+            operation Baz() : Unit {}
+        }
+"# },
+        &expect![[r#"
+            namespace namespace7 {
+
+                operation item1() : Unit {
+                    import item3;
+                    item3();
+                }
+
+            }
+
+            namespace namespace9 {
+                operation item3() : Unit {}
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_then_export() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation Bar() : Unit {}
+            }
+            namespace Main {
+                import Foo.Bar;
+                export Bar;
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+            }
+            namespace namespace8 {
+                import item1;
+                export item1;
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_namespace_advanced() {
+    check(
+        indoc! {"
+            namespace A.B.C.D.E {
+                operation DumpMachine() : Unit {}
+            }
+            namespace TestOne {
+                import A;
+                operation Main() : Unit {
+                    A.B.C.D.E.DumpMachine();
+                }
+            }
+            namespace TestTwo {
+                import A.B;
+                operation Main() : Unit {
+                    B.C.D.E.DumpMachine();
+                }
+            }
+            namespace TestThree {
+                import A.B.C;
+                operation Main() : Unit {
+                    C.D.E.DumpMachine();
+                }
+            }
+            namespace TestFour {
+                import A.B.C.D;
+                operation Main() : Unit {
+                    D.E.DumpMachine();
+                }
+            }
+            namespace TestFive {
+                import A.B.C.D.E;
+                operation Main() : Unit {
+                    E.DumpMachine();
+                }
+            }
+            namespace TestSix {
+                import A.B.C.D.E.DumpMachine;
+                operation Main() : Unit {
+                    DumpMachine();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace11 {
+                operation item1() : Unit {}
+            }
+            namespace namespace12 {
+                import namespace7;
+                operation item3() : Unit {
+                    item1();
+                }
+            }
+            namespace namespace13 {
+                import namespace8;
+                operation item5() : Unit {
+                    item1();
+                }
+            }
+            namespace namespace14 {
+                import namespace9;
+                operation item7() : Unit {
+                    item1();
+                }
+            }
+            namespace namespace15 {
+                import namespace10;
+                operation item9() : Unit {
+                    item1();
+                }
+            }
+            namespace namespace16 {
+                import namespace11;
+                operation item11() : Unit {
+                    item1();
+                }
+            }
+            namespace namespace17 {
+                import item1;
+                operation item13() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_namespace_does_not_open_it() {
+    check(
+        indoc! {"
+            namespace Microsoft.Quantum.Diagnostics {
+                operation DumpMachine() : Unit {}
+            }
+            namespace Main {
+                import Microsoft.Quantum.Diagnostics;
+                operation Main() : Unit {
+                    Diagnostics.DumpMachine();
+                    DumpMachine();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+            }
+            namespace namespace8 {
+                import namespace7;
+                operation item3() : Unit {
+                    item1();
+                    DumpMachine();
+                }
+            }
+
+            // NotFound("DumpMachine", Span { lo: 214, hi: 225 })
+        "#]],
+    );
+}
+
+#[test]
+fn invalid_import() {
+    check(
+        indoc! {"
+            namespace Main {
+                import A.B.C;
+                operation Main() : Unit {
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                import A.B.C;
+                operation item1() : Unit {
+                }
+            }
+
+            // NotFound("A.B.C", Span { lo: 28, hi: 33 })
+        "#]],
+    );
+}
+
+#[test]
+fn export_namespace() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+            }
+            namespace Main {
+                export Foo;
+            }
+            namespace Test {
+                open Main.Foo;
+                operation Main() : Unit {
+                    ApplyX();
+                    ApplyY();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+            }
+            namespace namespace8 {
+                export namespace7;
+            }
+            namespace namespace9 {
+                open namespace7;
+                operation item5() : Unit {
+                    item1();
+                    item2();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn export_namespace_contains_children() {
+    check(
+        indoc! {"
+            namespace Foo.Bar {
+                operation ApplyX() : Unit {}
+            }
+            namespace Main {
+                export Foo;
+            }
+            namespace Test {
+                open Main.Foo.Bar;
+                operation Main() : Unit {
+                    ApplyX();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace8 {
+                operation item1() : Unit {}
+            }
+            namespace namespace9 {
+                export namespace7;
+            }
+            namespace namespace10 {
+                open namespace8;
+                operation item4() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn export_namespace_cyclic() {
+    check(
+        indoc! {"
+            namespace Foo {
+                export Bar;
+            }
+            namespace Bar {
+                export Foo;
+                operation Hello() : Unit {}
+            }
+            namespace Main {
+                open Foo.Bar.Foo.Bar.Foo.Bar;
+                operation Main() : Unit { Hello(); }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                export namespace8;
+            }
+            namespace namespace8 {
+                export namespace7;
+                operation item2() : Unit {}
+            }
+            namespace namespace9 {
+                open namespace8;
+                operation item4() : Unit { item2(); }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn export_direct_cycle() {
+    check(
+        indoc! {"
+            namespace Foo {
+                export Foo;
+            }
+
+            namespace Main {
+                open Foo.Foo.Foo.Foo.Foo;
+                operation Main() : Unit { }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                export namespace7;
+            }
+
+            namespace namespace8 {
+                open namespace7;
+                operation item2() : Unit { }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn export_namespace_with_alias() {
+    check(
+        indoc! {"
+            namespace Foo.Bar {
+                operation ApplyX() : Unit {}
+            }
+            namespace Main {
+                export Foo.Bar as Baz;
+            }
+            namespace Test {
+                open Main.Baz;
+                operation Main() : Unit {
+                    ApplyX();
+                    Main.Baz.ApplyX();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace8 {
+                operation item1() : Unit {}
+            }
+            namespace namespace9 {
+                export namespace8;
+            }
+            namespace namespace10 {
+                open namespace8;
+                operation item4() : Unit {
+                    item1();
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_glob() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+            }
+            namespace Main {
+                import Foo.*;
+                operation Main() : Unit {
+                    ApplyX();
+                    ApplyY();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+            }
+            namespace namespace8 {
+                import namespace7.*;
+                operation item4() : Unit {
+                    item1();
+                    item2();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_aliased_glob() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+            }
+            namespace Main {
+                import Foo as Bar;
+                operation Main() : Unit {
+                    Bar.ApplyX();
+                    Bar.ApplyY();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+            }
+            namespace namespace8 {
+                import namespace7;
+                operation item4() : Unit {
+                    item1();
+                    item2();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn disallow_glob_export() {
+    check(
+        indoc! {"
+            namespace Foo {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+            }
+            namespace Bar {
+                export Foo.*;
+            }
+        "},
+        &expect![[r#"
+            namespace namespace7 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+            }
+            namespace namespace8 {
+                export namespace7.*;
+            }
+
+            // GlobExportNotSupported(Span { lo: 111, hi: 114 })
+        "#]],
+    );
+}
+
+#[test]
+fn import_glob_in_list() {
+    check(
+        indoc! {"
+            namespace Foo.Bar {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+            }
+            namespace Foo.Bar.Baz {
+                operation ApplyZ() : Unit {}
+            }
+            namespace Main {
+                import Foo.Bar.*, Foo.Bar.Baz.ApplyZ;
+                operation Main() : Unit {
+                    ApplyX();
+                    ApplyY();
+                    Baz.ApplyZ();
+                    ApplyZ();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace8 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+            }
+            namespace namespace9 {
+                operation item4() : Unit {}
+            }
+            namespace namespace10 {
+                import namespace8.*, item4;
+                operation item6() : Unit {
+                    item1();
+                    item2();
+                    item4();
+                    item4();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_glob_in_list_with_alias() {
+    check(
+        indoc! {"
+            namespace Foo.Bar {
+                operation ApplyX() : Unit {}
+                operation ApplyY() : Unit {}
+            }
+            namespace Foo.Bar.Baz {
+                operation ApplyZ() : Unit {}
+            }
+            namespace Main {
+                import Foo.Bar as Alias, Foo.Bar.Baz.ApplyZ as Foo;
+                operation Main() : Unit {
+                    Alias.ApplyX();
+                    Alias.ApplyY();
+                    Alias.Baz.ApplyZ();
+                    Foo();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace namespace8 {
+                operation item1() : Unit {}
+                operation item2() : Unit {}
+            }
+            namespace namespace9 {
+                operation item4() : Unit {}
+            }
+            namespace namespace10 {
+                import namespace8, item4;
+                operation item6() : Unit {
+                    item1();
+                    item2();
+                    item4();
+                    item4();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn import_newtype() {
+    check(
+        indoc! {r#"
+                namespace Foo {
+                    import Bar.NewType; // no error
+
+                    operation FooOperation() : Unit {
+                        let x: NewType = NewType("a");
+                    }
+                }
+
+                namespace Bar {
+                    newtype NewType = String;
+                    export NewType;
+
+                }"#},
+        &expect![[r#"
+            namespace namespace7 {
+                import item3; // no error
+
+                operation item1() : Unit {
+                    let local17: item3 = item3("a");
+                }
+            }
+
+            namespace namespace8 {
+                newtype item3 = String;
+                export item3;
+
+            }"#]],
+    );
+}
diff --git a/compiler/qsc_frontend/src/typeck/tests.rs b/compiler/qsc_frontend/src/typeck/tests.rs
index 4041d910..e2fc8870 100644
--- a/compiler/qsc_frontend/src/typeck/tests.rs
+++ b/compiler/qsc_frontend/src/typeck/tests.rs
@@ -87,9 +87,12 @@ fn compile(input: &str, entry_expr: &str) -> (Package, super::Table, Vec<compile
     let mut globals = resolve::GlobalTable::new();
     let mut errors = globals.add_local_package(&mut assigner, &package);
     let mut resolver = Resolver::new(globals, Vec::new());
+    resolver.bind_and_resolve_imports_and_exports(&package);
+
     resolver.with(&mut assigner).visit_package(&package);
     let (names, _, mut resolve_errors, _namespaces) = resolver.into_result();
     errors.append(&mut resolve_errors);
+
     let mut checker = Checker::new(super::GlobalTable::new());
     checker.check_package(&names, &package);
     let (tys, ty_errors) = checker.into_table();
diff --git a/compiler/qsc_parse/src/item/tests.rs b/compiler/qsc_parse/src/item/tests.rs
index 14051847..8ae0fb76 100644
--- a/compiler/qsc_parse/src/item/tests.rs
+++ b/compiler/qsc_parse/src/item/tests.rs
@@ -3,7 +3,9 @@
 
 #![allow(clippy::needless_raw_string_hashes)]
 
-use super::{parse, parse_attr, parse_spec_decl, source_name_to_namespace_name};
+use super::{
+    parse, parse_attr, parse_import_or_export, parse_spec_decl, source_name_to_namespace_name,
+};
 use crate::{
     scan::ParserContext,
     tests::{check, check_vec, check_vec_v2_preview},
@@ -247,6 +249,7 @@ fn udt_item_doc() {
                         Type _id_ [115-118]: Path: Path _id_ [115-118] (Ident _id_ [115-118] "Int")"#]],
     );
 }
+
 #[test]
 fn callable_param_doc() {
     check(
@@ -268,6 +271,7 @@ fn callable_param_doc() {
                     body: Block: Block _id_ [74-76]: <empty>"#]],
     );
 }
+
 #[test]
 fn callable_return_doc() {
     check(
@@ -1777,3 +1781,276 @@ fn helpful_error_on_dotted_alias() {
             ]"#]],
     );
 }
+
+#[test]
+fn parse_export_basic() {
+    check_vec(
+        parse_namespaces,
+        "namespace Foo {
+               operation Bar() : Unit {}
+               export Bar;
+        }",
+        &expect![[r#"
+            Namespace _id_ [0-93] (Ident _id_ [10-13] "Foo"):
+                Item _id_ [31-56]:
+                    Callable _id_ [31-56] (Operation):
+                        name: Ident _id_ [41-44] "Bar"
+                        input: Pat _id_ [44-46]: Unit
+                        output: Type _id_ [49-53]: Path: Path _id_ [49-53] (Ident _id_ [49-53] "Unit")
+                        body: Block: Block _id_ [54-56]: <empty>
+                Item _id_ [72-83]:
+                    Export (ImportOrExportDecl [72-83]: [Path _id_ [79-82] (Ident _id_ [79-82] "Bar")])"#]],
+    );
+}
+
+#[test]
+fn parse_export_list() {
+    check_vec(
+        parse_namespaces,
+        "namespace Foo {
+               operation Bar() : Unit {}
+               export Bar, Baz.Quux, Math.Quantum.Some.Nested, Math.Quantum.Some.Other.Nested;
+        }",
+        &expect![[r#"
+            Namespace _id_ [0-161] (Ident _id_ [10-13] "Foo"):
+                Item _id_ [31-56]:
+                    Callable _id_ [31-56] (Operation):
+                        name: Ident _id_ [41-44] "Bar"
+                        input: Pat _id_ [44-46]: Unit
+                        output: Type _id_ [49-53]: Path: Path _id_ [49-53] (Ident _id_ [49-53] "Unit")
+                        body: Block: Block _id_ [54-56]: <empty>
+                Item _id_ [72-151]:
+                    Export (ImportOrExportDecl [72-151]: [Path _id_ [79-82] (Ident _id_ [79-82] "Bar"), Path _id_ [84-92] (Ident _id_ [84-87] "Baz") (Ident _id_ [88-92] "Quux"), Path _id_ [94-118] ([Ident _id_ [94-98] "Math", Ident _id_ [99-106] "Quantum", Ident _id_ [107-111] "Some"]) (Ident _id_ [112-118] "Nested"), Path _id_ [120-150] ([Ident _id_ [120-124] "Math", Ident _id_ [125-132] "Quantum", Ident _id_ [133-137] "Some", Ident _id_ [138-143] "Other"]) (Ident _id_ [144-150] "Nested")])"#]],
+    );
+}
+
+#[test]
+fn parse_single_import() {
+    check(
+        parse_import_or_export,
+        "import Foo;",
+        &expect![[r#"ImportOrExportDecl [0-11]: [Path _id_ [7-10] (Ident _id_ [7-10] "Foo")]"#]],
+    );
+}
+
+#[test]
+fn parse_multiple_imports() {
+    check(
+        parse_import_or_export,
+        "import Foo.Bar, Foo.Baz;",
+        &expect![[
+            r#"ImportOrExportDecl [0-24]: [Path _id_ [7-14] (Ident _id_ [7-10] "Foo") (Ident _id_ [11-14] "Bar"), Path _id_ [16-23] (Ident _id_ [16-19] "Foo") (Ident _id_ [20-23] "Baz")]"#
+        ]],
+    );
+}
+
+#[test]
+fn parse_import_with_alias() {
+    check(
+        parse_import_or_export,
+        "import Foo as Bar;",
+        &expect![[
+            r#"ImportOrExportDecl [0-18]: [Path _id_ [7-10] (Ident _id_ [7-10] "Foo") as Ident _id_ [14-17] "Bar"]"#
+        ]],
+    );
+}
+
+#[test]
+fn multi_import_with_alias() {
+    check(
+        parse_import_or_export,
+        "import Foo.Bar as Baz, Foo.Quux;",
+        &expect![[
+            r#"ImportOrExportDecl [0-32]: [Path _id_ [7-14] (Ident _id_ [7-10] "Foo") (Ident _id_ [11-14] "Bar") as Ident _id_ [18-21] "Baz", Path _id_ [23-31] (Ident _id_ [23-26] "Foo") (Ident _id_ [27-31] "Quux")]"#
+        ]],
+    );
+}
+
+#[test]
+fn empty_import_statement() {
+    check(
+        parse_import_or_export,
+        "import;",
+        &expect!["ImportOrExportDecl [0-7]: []"],
+    );
+}
+
+#[test]
+fn parse_export_empty() {
+    check_vec(
+        parse_namespaces,
+        "namespace Foo {
+               operation Bar() : Unit {}
+               export;
+        }",
+        &expect![[r#"
+            Namespace _id_ [0-89] (Ident _id_ [10-13] "Foo"):
+                Item _id_ [31-56]:
+                    Callable _id_ [31-56] (Operation):
+                        name: Ident _id_ [41-44] "Bar"
+                        input: Pat _id_ [44-46]: Unit
+                        output: Type _id_ [49-53]: Path: Path _id_ [49-53] (Ident _id_ [49-53] "Unit")
+                        body: Block: Block _id_ [54-56]: <empty>
+                Item _id_ [72-79]:
+                    Export (ImportOrExportDecl [72-79]: [])"#]],
+    );
+}
+
+#[test]
+fn parse_glob_import() {
+    check(
+        parse_import_or_export,
+        "import Foo.*;",
+        &expect![[r#"ImportOrExportDecl [0-13]: [Path _id_ [7-10] (Ident _id_ [7-10] "Foo").*]"#]],
+    );
+}
+
+#[test]
+fn parse_glob_import_in_list() {
+    check(
+        parse_import_or_export,
+        "import Foo.Bar, Foo.Baz.*;",
+        &expect![
+            r#"ImportOrExportDecl [0-26]: [Path _id_ [7-14] (Ident _id_ [7-10] "Foo") (Ident _id_ [11-14] "Bar"), Path _id_ [16-23] (Ident _id_ [16-19] "Foo") (Ident _id_ [20-23] "Baz").*]"#
+        ],
+    );
+}
+
+#[test]
+fn parse_glob_import_of_parent_in_list() {
+    check(
+        parse_import_or_export,
+        "import Foo.Bar, Foo.Baz, Foo.*;",
+        &expect![[
+            r#"ImportOrExportDecl [0-31]: [Path _id_ [7-14] (Ident _id_ [7-10] "Foo") (Ident _id_ [11-14] "Bar"), Path _id_ [16-23] (Ident _id_ [16-19] "Foo") (Ident _id_ [20-23] "Baz"), Path _id_ [25-28] (Ident _id_ [25-28] "Foo").*]"#
+        ]],
+    );
+}
+
+#[test]
+fn parse_glob_import_with_alias() {
+    check(
+        parse_import_or_export,
+        "import Foo.* as Foo;",
+        &expect![[
+            r#"ImportOrExportDecl [0-20]: [Path _id_ [7-10] (Ident _id_ [7-10] "Foo").* as Ident _id_ [16-19] "Foo"]"#
+        ]],
+    );
+}
+
+#[test]
+fn parse_aliased_glob_import_in_list() {
+    check(
+        parse_import_or_export,
+        "import Foo.Bar, Foo.Baz.* as Quux;",
+        &expect![[
+            r#"ImportOrExportDecl [0-34]: [Path _id_ [7-14] (Ident _id_ [7-10] "Foo") (Ident _id_ [11-14] "Bar"), Path _id_ [16-23] (Ident _id_ [16-19] "Foo") (Ident _id_ [20-23] "Baz").* as Ident _id_ [29-33] "Quux"]"#
+        ]],
+    );
+}
+
+#[test]
+fn invalid_glob_syntax_extra_asterisk() {
+    check(
+        parse_import_or_export,
+        "import Foo.**;",
+        &expect![[r#"
+            Error(
+                Token(
+                    Semi,
+                    ClosedBinOp(
+                        Star,
+                    ),
+                    Span {
+                        lo: 12,
+                        hi: 13,
+                    },
+                ),
+            )
+        "#]],
+    );
+}
+
+#[test]
+fn invalid_glob_syntax_missing_dot() {
+    check(
+        parse_import_or_export,
+        "import Foo.Bar**;",
+        &expect![[r#"
+            Error(
+                Token(
+                    Semi,
+                    ClosedBinOp(
+                        Star,
+                    ),
+                    Span {
+                        lo: 14,
+                        hi: 15,
+                    },
+                ),
+            )
+        "#]],
+    );
+}
+
+#[test]
+fn invalid_glob_syntax_multiple_asterisks_in_path() {
+    check(
+        parse_import_or_export,
+        "import Foo.Bar.*.*;",
+        &expect![[r#"
+            Error(
+                Token(
+                    Semi,
+                    Dot,
+                    Span {
+                        lo: 16,
+                        hi: 17,
+                    },
+                ),
+            )
+        "#]],
+    );
+}
+
+#[test]
+fn invalid_glob_syntax_with_following_ident() {
+    check(
+        parse_import_or_export,
+        "import Foo.*.Bar;",
+        &expect![[r#"
+            Error(
+                Token(
+                    Semi,
+                    Dot,
+                    Span {
+                        lo: 12,
+                        hi: 13,
+                    },
+                ),
+            )
+        "#]],
+    );
+}
+
+#[test]
+fn disallow_top_level_recursive_glob() {
+    check(
+        parse_import_or_export,
+        "import *;",
+        &expect![[r#"
+            Error(
+                Token(
+                    Semi,
+                    ClosedBinOp(
+                        Star,
+                    ),
+                    Span {
+                        lo: 7,
+                        hi: 8,
+                    },
+                ),
+            )
+        "#]],
+    );
+}