Repository: microsoft/qsharp
Pull request #1742 (2024-07-16 15:04:55)

Katas UI string review - Oracles


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 242492df..2a5fffaf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -757,6 +757,7 @@ dependencies = [
 name = "noisy_simulator"
 version = "0.0.0"
 dependencies = [
+ "criterion",
  "nalgebra",
  "num-complex",
  "rand",
diff --git a/compiler/qsc/benches/rca.rs b/compiler/qsc/benches/rca.rs
index 311f6750..3c9f8ada 100644
--- a/compiler/qsc/benches/rca.rs
+++ b/compiler/qsc/benches/rca.rs
@@ -151,7 +151,7 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore
     for (id, unit) in hir_package_store {
         fir_store.insert(
             map_hir_package_to_fir(id),
-            Lowerer::new().lower_package(&unit.package),
+            Lowerer::new().lower_package(&unit.package, &fir_store),
         );
     }
     fir_store
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index bdad7632..f6e72383 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -1,16 +1,15 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-mod debug;
-
 #[cfg(test)]
-mod tests;
-
+mod circuit_tests;
+mod debug;
 #[cfg(test)]
 mod debugger_tests;
-
 #[cfg(test)]
-mod circuit_tests;
+mod package_tests;
+#[cfg(test)]
+mod tests;
 
 use std::rc::Rc;
 
@@ -207,12 +206,10 @@ impl Interpreter {
 
         let mut fir_store = fir::PackageStore::new();
         for (id, unit) in compiler.package_store() {
-            fir_store.insert(
-                map_hir_package_to_fir(id),
-                qsc_lowerer::Lowerer::new()
-                    .with_debug(dbg)
-                    .lower_package(&unit.package),
-            );
+            let pkg = qsc_lowerer::Lowerer::new()
+                .with_debug(dbg)
+                .lower_package(&unit.package, &fir_store);
+            fir_store.insert(map_hir_package_to_fir(id), pkg);
         }
 
         let source_package_id = compiler.source_package_id();
@@ -265,10 +262,8 @@ impl Interpreter {
         let mut fir_store = fir::PackageStore::new();
         for (id, unit) in compiler.package_store() {
             let mut lowerer = qsc_lowerer::Lowerer::new();
-            fir_store.insert(
-                map_hir_package_to_fir(id),
-                lowerer.lower_package(&unit.package),
-            );
+            let pkg = lowerer.lower_package(&unit.package, &fir_store);
+            fir_store.insert(map_hir_package_to_fir(id), pkg);
         }
 
         let source_package_id = compiler.source_package_id();
@@ -634,20 +629,27 @@ impl Interpreter {
         if self.capabilities != TargetCapabilityFlags::all() {
             return self.run_fir_passes(unit_addition);
         }
-        let fir_package = self.fir_store.get_mut(self.package);
-        self.lowerer
-            .lower_and_update_package(fir_package, &unit_addition.hir);
+
+        self.lower_and_update_package(unit_addition);
         Ok((self.lowerer.take_exec_graph(), None))
     }
 
+    fn lower_and_update_package(&mut self, unit: &qsc_frontend::incremental::Increment) {
+        {
+            let fir_package = self.fir_store.get_mut(self.package);
+            self.lowerer
+                .lower_and_update_package(fir_package, &unit.hir);
+        }
+        let fir_package: &Package = self.fir_store.get(self.package);
+        qsc_fir::validate::validate(fir_package, &self.fir_store);
+    }
+
     fn run_fir_passes(
         &mut self,
         unit: &qsc_frontend::incremental::Increment,
     ) -> std::result::Result<(Vec<ExecGraphNode>, Option<PackageStoreComputeProperties>), Vec<Error>>
     {
-        let fir_package = self.fir_store.get_mut(self.package);
-        self.lowerer
-            .lower_and_update_package(fir_package, &unit.hir);
+        self.lower_and_update_package(unit);
 
         let cap_results =
             PassContext::run_fir_passes_on_fir(&self.fir_store, self.package, self.capabilities);
@@ -860,7 +862,7 @@ impl Debugger {
                 package,
                 self.position_encoding,
             );
-            collector.visit_package(package);
+            collector.visit_package(package, &self.interpreter.fir_store);
             let mut spans: Vec<_> = collector.statements.into_iter().collect();
 
             // Sort by start position (line first, column next)
diff --git a/compiler/qsc_ast/src/ast.rs b/compiler/qsc_ast/src/ast.rs
index 16090808..71582b08 100644
--- a/compiler/qsc_ast/src/ast.rs
+++ b/compiler/qsc_ast/src/ast.rs
@@ -23,7 +23,7 @@ fn set_indentation<'a, 'b>(
         0 => indent.with_str(""),
         1 => indent.with_str("    "),
         2 => indent.with_str("        "),
-        _ => unimplemented!("intentation level not supported"),
+        _ => unimplemented!("indentation level not supported"),
     }
 }
 
diff --git a/compiler/qsc_codegen/src/qir.rs b/compiler/qsc_codegen/src/qir.rs
index 568e7e73..45f7c04f 100644
--- a/compiler/qsc_codegen/src/qir.rs
+++ b/compiler/qsc_codegen/src/qir.rs
@@ -20,7 +20,7 @@ use qsc_rir::{
 fn lower_store(package_store: &qsc_frontend::compile::PackageStore) -> qsc_fir::fir::PackageStore {
     let mut fir_store = qsc_fir::fir::PackageStore::new();
     for (id, unit) in package_store {
-        let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package);
+        let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package, &fir_store);
         fir_store.insert(map_hir_package_to_fir(id), package);
     }
     fir_store
diff --git a/compiler/qsc_data_structures/src/namespaces.rs b/compiler/qsc_data_structures/src/namespaces.rs
index 1a1ca33b..2f3fc8bc 100644
--- a/compiler/qsc_data_structures/src/namespaces.rs
+++ b/compiler/qsc_data_structures/src/namespaces.rs
@@ -5,7 +5,7 @@
 mod tests;
 
 use rustc_hash::{FxHashMap, FxHashSet};
-use std::{cell::RefCell, fmt::Display, iter::Peekable, ops::Deref, rc::Rc};
+use std::{cell::RefCell, collections::BTreeMap, fmt::Display, iter::Peekable, ops::Deref, rc::Rc};
 
 pub const PRELUDE: [[&str; 3]; 4] = [
     ["Microsoft", "Quantum", "Canon"],
@@ -15,7 +15,7 @@ pub const PRELUDE: [[&str; 3]; 4] = [
 ];
 
 /// An ID that corresponds to a namespace in the global scope.
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Default, PartialOrd, Ord)]
 pub struct NamespaceId(usize);
 impl NamespaceId {
     /// Create a new namespace ID.
@@ -196,7 +196,75 @@ impl NamespaceTreeRoot {
             .borrow_mut()
             .insert_or_find_namespace(ns.into_iter().peekable(), &mut self.assigner);
 
-        id.expect("empty name should not be passed into namespace insertion")
+        id.expect("empty name checked for above")
+    }
+
+    pub fn insert_or_find_namespace_from_root_with_id(
+        &mut self,
+        mut ns: Vec<Rc<str>>,
+        root: NamespaceId,
+        base_id: NamespaceId,
+    ) {
+        if ns.is_empty() {
+            return;
+        }
+        let (_root_name, root_contents) = self.find_namespace_by_id(&root);
+        // split `ns` into [0..len - 1] and [len - 1]
+        let suffix = ns.split_off(ns.len() - 1)[0].clone();
+        let prefix = ns;
+
+        // if the prefix is empty, we are inserting into the root
+        if prefix.is_empty() {
+            self.insert_with_id(Some(root), base_id, &suffix);
+        } else {
+            let prefix_id = root_contents
+                .borrow_mut()
+                .insert_or_find_namespace(prefix.into_iter().peekable(), &mut self.assigner)
+                .expect("empty name checked for above");
+
+            self.insert_with_id(Some(prefix_id), base_id, &suffix);
+        }
+    }
+
+    /// Each item in this iterator is the same, single namespace. The reason there are multiple paths for it,
+    /// each represented by a `Vec<Rc<str>>`, is because there may be multiple paths to the same
+    /// namespace, through aliasing or re-exports.
+    pub fn iter(&self) -> std::collections::btree_map::IntoValues<NamespaceId, Vec<Vec<Rc<str>>>> {
+        let mut stack = vec![(vec![], self.tree.clone())];
+        let mut result: Vec<(NamespaceId, Vec<Rc<str>>)> = vec![];
+        while let Some((names, node)) = stack.pop() {
+            result.push((node.borrow().id, names.clone()));
+            for (name, child) in node.borrow().children() {
+                let mut new_names = names.clone();
+                new_names.push(name.clone());
+                stack.push((new_names, child.clone()));
+            }
+            if node.borrow().children().is_empty() {
+                result.push((node.borrow().id, names));
+            }
+        }
+
+        // flatten the result into a list of paths
+
+        // use a btree map here instead of a hash map for deterministic iteration --
+        // while it shouldn't be consequential, any nondeterminism in a compiler makes
+        // things more difficult to track down then they go wrong.
+        let mut flattened_result = BTreeMap::default();
+        for (id, names) in result {
+            let entry = flattened_result.entry(id).or_insert_with(Vec::new);
+            entry.push(names);
+        }
+
+        flattened_result.into_values()
+    }
+}
+
+impl IntoIterator for &NamespaceTreeRoot {
+    type Item = Vec<Vec<Rc<str>>>;
+    type IntoIter = std::collections::btree_map::IntoValues<NamespaceId, Vec<Vec<Rc<str>>>>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
     }
 }
 
diff --git a/compiler/qsc_doc_gen/src/generate_docs.rs b/compiler/qsc_doc_gen/src/generate_docs.rs
index 8b0c4fd1..f8f1d84f 100644
--- a/compiler/qsc_doc_gen/src/generate_docs.rs
+++ b/compiler/qsc_doc_gen/src/generate_docs.rs
@@ -255,6 +255,7 @@ impl Display for Metadata {
             MetadataKind::Function => "function",
             MetadataKind::Operation => "operation",
             MetadataKind::Udt => "udt",
+            MetadataKind::Export => "export",
         };
         write!(
             f,
@@ -277,6 +278,7 @@ enum MetadataKind {
     Function,
     Operation,
     Udt,
+    Export,
 }
 
 fn get_metadata(ns: Rc<str>, item: &Item, display: &CodeDisplay) -> Option<Metadata> {
@@ -295,6 +297,12 @@ fn get_metadata(ns: Rc<str>, item: &Item, display: &CodeDisplay) -> Option<Metad
             MetadataKind::Udt,
         )),
         ItemKind::Namespace(_, _) => None,
+        ItemKind::Export(name, _) => Some((
+            name.name.clone(),
+            // If we want to show docs for exports, we could do that here.
+            String::new(),
+            MetadataKind::Export,
+        )),
     }?;
 
     let summary = parse_doc_for_summary(&item.doc)
@@ -307,6 +315,7 @@ fn get_metadata(ns: Rc<str>, item: &Item, display: &CodeDisplay) -> Option<Metad
             MetadataKind::Function => format!("{name} function"),
             MetadataKind::Operation => format!("{name} operation"),
             MetadataKind::Udt => format!("{name} user defined type"),
+            MetadataKind::Export => format!("{name} exported item"),
         },
         topic: "managed-reference".to_string(),
         kind,
diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs
index c37eaa82..39c4b8ea 100644
--- a/compiler/qsc_fir/src/fir.rs
+++ b/compiler/qsc_fir/src/fir.rs
@@ -633,6 +633,7 @@ impl PackageLookup for Package {
             ItemKind::Callable(callable) => Some(Global::Callable(callable)),
             ItemKind::Namespace(..) => None,
             ItemKind::Ty(..) => Some(Global::Udt),
+            ItemKind::Export(_name, _id) => None,
         }
     }
 
@@ -707,6 +708,8 @@ pub enum ItemKind {
     Namespace(Ident, Vec<LocalItemId>),
     /// A `newtype` declaration.
     Ty(Ident, Udt),
+    /// An export referring to another item
+    Export(Ident, ItemId),
 }
 
 impl Display for ItemKind {
@@ -727,6 +730,7 @@ impl Display for ItemKind {
                 }
             }
             ItemKind::Ty(name, udt) => write!(f, "Type ({name}): {udt}"),
+            ItemKind::Export(name, item) => write!(f, "Export ({name}): {item}"),
         }
     }
 }
diff --git a/compiler/qsc_fir/src/mut_visit.rs b/compiler/qsc_fir/src/mut_visit.rs
index e2785264..46b91deb 100644
--- a/compiler/qsc_fir/src/mut_visit.rs
+++ b/compiler/qsc_fir/src/mut_visit.rs
@@ -64,7 +64,9 @@ pub fn walk_package<'a>(vis: &mut impl MutVisitor<'a>, package: &'a mut Package)
 pub fn walk_item<'a>(vis: &mut impl MutVisitor<'a>, item: &'a mut Item) {
     match &mut item.kind {
         ItemKind::Callable(decl) => vis.visit_callable_decl(decl),
-        ItemKind::Namespace(name, _) | ItemKind::Ty(name, _) => vis.visit_ident(name),
+        ItemKind::Namespace(name, _) | ItemKind::Ty(name, _) | ItemKind::Export(name, _) => {
+            vis.visit_ident(name);
+        }
     };
 }
 
diff --git a/compiler/qsc_fir/src/validate.rs b/compiler/qsc_fir/src/validate.rs
index cacc7739..32254eb2 100644
--- a/compiler/qsc_fir/src/validate.rs
+++ b/compiler/qsc_fir/src/validate.rs
@@ -10,16 +10,16 @@ pub struct Validator<'a> {
     pub package: &'a Package,
 }
 
-pub fn validate(package: &Package) {
+pub fn validate(package: &Package, store: &crate::fir::PackageStore) {
     let mut v = Validator { package };
-    v.validate();
+    v.validate(store);
 }
 
 /// Validates that the FIR is well-formed.
 /// Running `validate` will validate the entire package.
 impl Validator<'_> {
-    pub fn validate(&mut self) {
-        self.visit_package(self.package);
+    pub fn validate(&mut self, store: &crate::fir::PackageStore) {
+        self.visit_package(self.package, store);
     }
 }
 
diff --git a/compiler/qsc_fir/src/visit.rs b/compiler/qsc_fir/src/visit.rs
index 45023820..0bc86051 100644
--- a/compiler/qsc_fir/src/visit.rs
+++ b/compiler/qsc_fir/src/visit.rs
@@ -8,7 +8,7 @@ use crate::fir::{
 };
 
 pub trait Visitor<'a>: Sized {
-    fn visit_package(&mut self, package: &'a Package) {
+    fn visit_package(&mut self, package: &'a Package, _: &crate::fir::PackageStore) {
         walk_package(self, package);
     }
 
@@ -65,6 +65,9 @@ pub fn walk_item<'a>(vis: &mut impl Visitor<'a>, item: &'a Item) {
     match &item.kind {
         ItemKind::Callable(decl) => vis.visit_callable_decl(decl),
         ItemKind::Namespace(name, _) | ItemKind::Ty(name, _) => vis.visit_ident(name),
+        ItemKind::Export(name, _) => {
+            vis.visit_ident(name);
+        }
     };
 }
 
diff --git a/compiler/qsc_frontend/src/compile.rs b/compiler/qsc_frontend/src/compile.rs
index 6e1d5588..30a4f433 100644
--- a/compiler/qsc_frontend/src/compile.rs
+++ b/compiler/qsc_frontend/src/compile.rs
@@ -380,7 +380,12 @@ pub fn compile_ast(
     ast_assigner.visit_package(&mut ast_package);
     AstValidator::default().visit_package(&ast_package);
     let mut hir_assigner = HirAssigner::new();
-    let (names, locals, name_errors) = resolve_all(
+    let ResolveResult {
+        names,
+        locals,
+        errors: name_errors,
+        namespaces,
+    } = resolve_all(
         store,
         dependencies,
         &mut hir_assigner,
@@ -391,7 +396,7 @@ pub fn compile_ast(
     let mut lowerer = Lowerer::new();
     let package = lowerer
         .with(&mut hir_assigner, &names, &tys)
-        .lower_package(&ast_package);
+        .lower_package(&ast_package, namespaces);
     HirValidator::default().visit_package(&package);
     let lower_errors = lowerer.drain_errors();
 
@@ -510,16 +515,23 @@ fn parse_all(
     (package, errors)
 }
 
+pub(crate) struct ResolveResult {
+    pub names: Names,
+    pub locals: Locals,
+    pub namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot,
+    pub errors: Vec<resolve::Error>,
+}
+
 fn resolve_all(
     store: &PackageStore,
     dependencies: &Dependencies,
     assigner: &mut HirAssigner,
     package: &ast::Package,
     mut dropped_names: Vec<TrackedName>,
-) -> (Names, Locals, Vec<resolve::Error>) {
+) -> ResolveResult {
     let mut globals = resolve::GlobalTable::new();
     if let Some(unit) = store.get(PackageId::CORE) {
-        globals.add_external_package(PackageId::CORE, &unit.package, &None);
+        globals.add_external_package(PackageId::CORE, &unit.package, store, &None);
         dropped_names.extend(unit.dropped_names.iter().cloned());
     }
 
@@ -527,7 +539,7 @@ fn resolve_all(
         let unit = store
             .get(*id)
             .expect("dependency should be in package store before compilation");
-        globals.add_external_package(*id, &unit.package, alias);
+        globals.add_external_package(*id, &unit.package, store, alias);
         dropped_names.extend(unit.dropped_names.iter().cloned());
     }
 
@@ -540,9 +552,15 @@ fn resolve_all(
 
     // resolve all symbols
     resolver.with(assigner).visit_package(package);
-    let (names, locals, mut resolver_errors, _namespaces) = resolver.into_result();
+    let (names, locals, mut resolver_errors, namespaces) = resolver.into_result();
     errors.append(&mut resolver_errors);
-    (names, locals, errors)
+
+    ResolveResult {
+        names,
+        locals,
+        namespaces,
+        errors,
+    }
 }
 
 fn typeck_all(
@@ -553,7 +571,7 @@ fn typeck_all(
 ) -> (typeck::Table, Vec<typeck::Error>) {
     let mut globals = typeck::GlobalTable::new();
     if let Some(unit) = store.get(PackageId::CORE) {
-        globals.add_external_package(PackageId::CORE, &unit.package);
+        globals.add_external_package(PackageId::CORE, &unit.package, store);
     }
 
     for (id, _alias) in dependencies {
@@ -564,7 +582,7 @@ fn typeck_all(
         // typechecker doesn't do any name resolution -- it only operates on item ids.
         // because of this, the typechecker doesn't actually need to care about visibility
         // or the names of items at all.
-        globals.add_external_package(*id, &unit.package);
+        globals.add_external_package(*id, &unit.package, store);
     }
 
     let mut checker = Checker::new(globals);
diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs
index ffef95ad..4935face 100644
--- a/compiler/qsc_frontend/src/incremental.rs
+++ b/compiler/qsc_frontend/src/incremental.rs
@@ -70,8 +70,8 @@ impl Compiler {
         let mut typeck_globals = typeck::GlobalTable::new();
         let mut dropped_names = Vec::new();
         if let Some(unit) = store.get(PackageId::CORE) {
-            resolve_globals.add_external_package(PackageId::CORE, &unit.package, &None);
-            typeck_globals.add_external_package(PackageId::CORE, &unit.package);
+            resolve_globals.add_external_package(PackageId::CORE, &unit.package, store, &None);
+            typeck_globals.add_external_package(PackageId::CORE, &unit.package, store);
             dropped_names.extend(unit.dropped_names.iter().cloned());
         }
 
@@ -79,8 +79,8 @@ impl Compiler {
             let unit = store
                 .get(*id)
                 .expect("dependency should be added to package store before compilation");
-            resolve_globals.add_external_package(*id, &unit.package, alias);
-            typeck_globals.add_external_package(*id, &unit.package);
+            resolve_globals.add_external_package(*id, &unit.package, store, alias);
+            typeck_globals.add_external_package(*id, &unit.package, store);
             dropped_names.extend(unit.dropped_names.iter().cloned());
         }
 
@@ -269,7 +269,13 @@ impl Compiler {
         self.checker.check_package(self.resolver.names(), ast);
         self.checker.solve(self.resolver.names());
 
-        let package = self.lower(&mut unit.assigner, &*ast);
+        let package = self.lower(
+            &mut unit.assigner,
+            &*ast,
+            // not an ideal clone, but it is once per fragment, and the namespace tree is
+            // relatively lightweight
+            self.resolver.namespaces().clone(),
+        );
 
         let errors = self
             .resolver
@@ -376,10 +382,15 @@ impl Compiler {
         (package, with_source(errors, sources, offset))
     }
 
-    fn lower(&mut self, hir_assigner: &mut HirAssigner, package: &ast::Package) -> hir::Package {
+    fn lower(
+        &mut self,
+        hir_assigner: &mut HirAssigner,
+        package: &ast::Package,
+        namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot,
+    ) -> hir::Package {
         self.lowerer
             .with(hir_assigner, self.resolver.names(), self.checker.table())
-            .lower_package(package)
+            .lower_package(package, namespaces)
     }
 }
 
diff --git a/compiler/qsc_frontend/src/lower.rs b/compiler/qsc_frontend/src/lower.rs
index bd9bf51d..a1033a76 100644
--- a/compiler/qsc_frontend/src/lower.rs
+++ b/compiler/qsc_frontend/src/lower.rs
@@ -94,7 +94,11 @@ pub(super) struct With<'a> {
 }
 
 impl With<'_> {
-    pub(super) fn lower_package(&mut self, package: &ast::Package) -> hir::Package {
+    pub(super) fn lower_package(
+        &mut self,
+        package: &ast::Package,
+        namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot,
+    ) -> hir::Package {
         let mut stmts = Vec::new();
         for node in &*package.nodes {
             match node {
@@ -111,6 +115,7 @@ impl With<'_> {
         let items = self.lowerer.items.drain(..).map(|i| (i.id, i)).collect();
         hir::Package {
             items,
+            namespaces,
             stmts,
             entry,
         }
@@ -126,15 +131,23 @@ impl With<'_> {
         self.lowerer.parent = Some(id);
 
         // Exports are `Res` items, which contain `hir::ItemId`s.
-        let exports = namespace
+        // The second element in the tuple is the optional alias
+        let exports: Vec<(_, Option<ast::Ident>)> = namespace
             .exports()
-            .filter_map(|item| self.names.get(item.name().id))
+            .filter_map(|item| {
+                self.names
+                    .get(item.path.id)
+                    .map(|x| (x, item.alias.clone()))
+            })
             .collect::<Vec<_>>();
 
         let exported_hir_ids = exports
             .iter()
-            .filter_map(|res| match res {
-                resolve::Res::Item(hir::ItemId { item: id, .. }, _) => Some(*id),
+            .filter_map(|(res, alias)| match res {
+                resolve::Res::ExportedItem(id, hir_alias) => {
+                    Some((*id, hir_alias.as_ref().or(alias.as_ref())))
+                }
+                resolve::Res::Item(id, _) => Some((*id, alias.as_ref())),
                 _ => None,
             })
             .collect::<Vec<_>>();
@@ -146,6 +159,7 @@ impl With<'_> {
             .collect::<Vec<_>>();
 
         let name = self.lower_idents(&namespace.name);
+
         self.lowerer.items.push(hir::Item {
             id,
             span: namespace.span,
@@ -162,26 +176,57 @@ impl With<'_> {
     fn lower_item(
         &mut self,
         item: &ast::Item,
-        exported_ids: &[hir::LocalItemId],
+        // the optional ident is the export alias, if any
+        exported_ids: &[(hir::ItemId, Option<&ast::Ident>)],
     ) -> Option<LocalItemId> {
-        let attrs = item
+        let attrs: Vec<_> = item
             .attrs
             .iter()
             .filter_map(|a| self.lower_attr(a))
             .collect();
 
         let resolve_id = |id| match self.names.get(id) {
-            Some(&resolve::Res::Item(item, _)) => item,
-            _ => panic!("item should have item ID"),
+            Some(&resolve::Res::ExportedItem(item, ref hir_alias)) => {
+                Some((item, hir_alias.clone()))
+            }
+            Some(&resolve::Res::Item(item, _)) => Some((item, None)),
+            _otherwise => None,
         };
 
         let (id, kind) = match &*item.kind {
-            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::Err | ast::ItemKind::Open(..) => return None,
 
-            ast::ItemKind::ImportOrExport(_) => return None,
+            ast::ItemKind::ImportOrExport(item) => {
+                if item.is_import() {
+                    return None;
+                }
+                for item in item.items.iter() {
+                    let Some((id, alias)) = resolve_id(item.name().id) else {
+                        continue;
+                    };
+                    let is_reexport = id.package.is_some() || alias.is_some();
+                    // if the package is Some, then this is a re-export and we
+                    // need to preserve the reference to the original `ItemId`
+                    if is_reexport {
+                        let mut name = self.lower_ident(item.name());
+                        name.id = self.assigner.next_node();
+                        let kind = hir::ItemKind::Export(name, id);
+                        self.lowerer.items.push(hir::Item {
+                            id: self.assigner.next_item(),
+                            span: item.span(),
+                            parent: self.lowerer.parent,
+                            doc: "".into(),
+                            // attrs on exports not supported
+                            attrs: Vec::new(),
+                            visibility: Visibility::Public,
+                            kind,
+                        });
+                    }
+                }
+                return None;
+            }
             ast::ItemKind::Callable(callable) => {
-                let id = resolve_id(callable.name.id);
+                let (id, _) = resolve_id(callable.name.id)?;
                 let grandparent = self.lowerer.parent;
                 self.lowerer.parent = Some(id.item);
                 let callable = self.lower_callable_decl(callable);
@@ -189,7 +234,7 @@ impl With<'_> {
                 (id, hir::ItemKind::Callable(callable))
             }
             ast::ItemKind::Ty(name, _) => {
-                let id = resolve_id(name.id);
+                let (id, _) = resolve_id(name.id)?;
                 let udt = self
                     .tys
                     .udts
@@ -199,7 +244,7 @@ impl With<'_> {
                 (id, hir::ItemKind::Ty(self.lower_ident(name), udt.clone()))
             }
             ast::ItemKind::Struct(decl) => {
-                let id = resolve_id(decl.name.id);
+                let (id, _) = resolve_id(decl.name.id)?;
                 let strct = self
                     .tys
                     .udts
@@ -213,10 +258,28 @@ impl With<'_> {
             }
         };
 
-        let visibility = if exported_ids.contains(&id.item) {
-            Visibility::Public
-        } else {
-            Visibility::Internal
+        let export_info = exported_ids.iter().find(|(hir_id, _)| hir_id == &id);
+        let visibility = match export_info {
+            Some((id, Some(alias))) => {
+                // this is the special case where this item _is_ exported,
+                // but it is exported with an alias.
+                // We want to hide the original item, making it private, but make
+                // the alias itself public.
+                let mut alias = self.lower_ident(alias);
+                alias.id = self.assigner.next_node();
+                self.lowerer.items.push(hir::Item {
+                    id: self.assigner.next_item(),
+                    span: alias.span,
+                    parent: self.lowerer.parent,
+                    doc: Rc::clone(&item.doc),
+                    attrs: attrs.clone(),
+                    visibility: Visibility::Public,
+                    kind: hir::ItemKind::Export(alias, *id),
+                });
+                Visibility::Internal
+            }
+            Some((_, None)) => Visibility::Public,
+            None => Visibility::Internal,
         };
 
         self.lowerer.items.push(hir::Item {
@@ -781,6 +844,9 @@ impl With<'_> {
         match self.names.get(id) {
             Some(&resolve::Res::Item(item, _)) => hir::Res::Item(item),
             Some(&resolve::Res::Local(node)) => hir::Res::Local(self.lower_id(node)),
+            // Exported items are just pass-throughs to the items they reference, and should be
+            // treated as Res to that original item.
+            Some(&resolve::Res::ExportedItem(item_id, _)) => hir::Res::Item(item_id),
             Some(resolve::Res::PrimTy(_) | resolve::Res::UnitTy | resolve::Res::Param(_))
             | None => hir::Res::Err,
         }
diff --git a/compiler/qsc_frontend/src/resolve.rs b/compiler/qsc_frontend/src/resolve.rs
index 3479bdf0..e7767419 100644
--- a/compiler/qsc_frontend/src/resolve.rs
+++ b/compiler/qsc_frontend/src/resolve.rs
@@ -57,7 +57,7 @@ pub fn path_as_field_accessor(
 
 /// A resolution. This connects a usage of a name with the declaration of that name by uniquely
 /// identifying the node that declared it.
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+#[derive(Clone, Debug, Eq, Hash, PartialEq)]
 pub enum Res {
     /// A global or local item.
     Item(ItemId, ItemStatus),
@@ -69,6 +69,8 @@ pub enum Res {
     PrimTy(Prim),
     /// The unit type.
     UnitTy,
+    /// An export, which could be from another package.
+    ExportedItem(ItemId, Option<Ident>),
 }
 
 #[derive(Clone, Debug, Diagnostic, Error, PartialEq)]
@@ -160,6 +162,16 @@ pub(super) enum Error {
     #[error("glob exports are not supported")]
     #[diagnostic(code("Qsc.Resolve.GlobExportNotSupported"))]
     GlobExportNotSupported(#[label] Span),
+
+    #[error("aliasing a glob import is invalid")]
+    #[diagnostic(help("try `import {namespace_name} as {alias}` instead"))]
+    #[diagnostic(code("Qsc.Resolve.GlobImportAliasNotSupported"))]
+    GlobImportAliasNotSupported {
+        namespace_name: String,
+        alias: String,
+        #[label]
+        span: Span,
+    },
 }
 
 #[derive(Debug, Clone)]
@@ -199,10 +211,11 @@ impl ScopeItemEntry {
     }
 }
 
-#[derive(PartialEq, Debug, Clone, Default, Copy)]
+#[derive(PartialEq, Debug, Clone, Default)]
 pub enum ItemSource {
     Exported,
-    Imported,
+    // if the item was imported with an alias, the alias is stored here
+    Imported(Option<Ident>),
     #[default]
     Declared,
 }
@@ -365,6 +378,16 @@ impl GlobalScope {
     ) -> NamespaceId {
         self.namespaces.insert_or_find_namespace_from_root(ns, root)
     }
+
+    fn insert_or_find_namespace_from_root_with_id(
+        &mut self,
+        name: Vec<Rc<str>>,
+        root: NamespaceId,
+        base_id: NamespaceId,
+    ) {
+        self.namespaces
+            .insert_or_find_namespace_from_root_with_id(name, root, base_id);
+    }
 }
 
 #[derive(Debug, Clone, Eq, PartialEq)]
@@ -536,6 +559,10 @@ impl Resolver {
         &self.names
     }
 
+    pub(crate) fn namespaces(&self) -> &qsc_data_structures::namespaces::NamespaceTreeRoot {
+        &self.globals.namespaces
+    }
+
     pub(super) fn locals(&self) -> &Locals {
         &self.locals
     }
@@ -585,7 +612,7 @@ impl Resolver {
         }
     }
 
-    fn check_item_status(&mut self, res: Res, name: String, span: Span) {
+    fn check_item_status(&mut self, res: &Res, name: String, span: Span) {
         if let Res::Item(_, ItemStatus::Unimplemented) = res {
             self.errors.push(Error::Unimplemented(name, span));
         }
@@ -600,7 +627,7 @@ impl Resolver {
             &None,
         ) {
             Ok(res) => {
-                self.check_item_status(res, name.name.to_string(), name.span);
+                self.check_item_status(&res, name.name.to_string(), name.span);
                 self.names.insert(name.id, res);
             }
             Err(err) => self.errors.push(err),
@@ -627,7 +654,7 @@ impl Resolver {
             ) {
                 Ok(res) if matches!(res, Res::Local(_)) => {
                     // The path is a field accessor.
-                    self.names.insert(first.id, res);
+                    self.names.insert(first.id, res.clone());
                     return Ok(res);
                 }
                 Err(err) if !matches!(err, Error::NotFound(_, _)) => return Err(err), // Local was found but has issues.
@@ -646,8 +673,8 @@ impl Resolver {
             segments,
         ) {
             Ok(res) => {
-                self.check_item_status(res, path.name.name.to_string(), path.span);
-                self.names.insert(path.id, res);
+                self.check_item_status(&res, path.name.name.to_string(), path.span);
+                self.names.insert(path.id, res.clone());
                 Ok(res)
             }
             Err(err) => {
@@ -834,7 +861,7 @@ impl Resolver {
         let current_namespace_name: Option<Rc<str>> = current_namespace_name.map(Idents::name);
         let is_export = decl.is_export();
 
-        for item in decl
+        for decl_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
@@ -854,22 +881,29 @@ impl Resolver {
             })
             .collect::<Vec<_>>()
         {
-            if item.is_glob {
-                self.bind_glob_import_or_export(item, decl.is_export());
+            let mut decl_alias = decl_item.alias.clone();
+            if decl_item.is_glob {
+                self.bind_glob_import_or_export(decl_item, decl.is_export());
                 continue;
             }
+
             let (term_result, ty_result) = (
-                self.resolve_path(NameKind::Term, &item.path),
-                self.resolve_path(NameKind::Ty, &item.path),
+                self.resolve_path(NameKind::Term, &decl_item.path),
+                self.resolve_path(NameKind::Ty, &decl_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);
+                self.handle_namespace_import_or_export(
+                    is_export,
+                    decl_item,
+                    current_namespace,
+                    err,
+                );
                 continue;
             };
 
-            let local_name = item.name().name.clone();
+            let local_name = decl_item.name().name.clone();
 
             {
                 let scope = self.current_scope_mut();
@@ -881,31 +915,43 @@ impl Resolver {
                     (true, Some(entry), _) | (true, _, Some(entry))
                         if entry.source == ItemSource::Exported =>
                     {
-                        let err = Error::DuplicateExport(local_name.to_string(), item.name().span);
+                        let err =
+                            Error::DuplicateExport(local_name.to_string(), decl_item.name().span);
                         self.errors.push(err);
                         continue;
                     }
                     (false, Some(entry), _) | (true, _, Some(entry))
-                        if entry.source == ItemSource::Imported =>
+                        if matches!(entry.source, ItemSource::Imported(..)) =>
                     {
                         let err =
-                            Error::ImportedDuplicate(local_name.to_string(), item.name().span);
+                            Error::ImportedDuplicate(local_name.to_string(), decl_item.name().span);
                         self.errors.push(err);
                         continue;
                     }
+                    // special case:
+                    // if this is an export of an import with an alias,
+                    // we treat it as an aliased export of the original underlying item
+                    (true, Some(entry), _) | (true, _, Some(entry)) => {
+                        if let ItemSource::Imported(Some(ref alias)) = entry.source {
+                            decl_alias = decl_alias.or(Some(alias.clone()));
+                        }
+                    }
                     _ => (),
                 }
             }
 
+            let local_name = decl_alias
+                .as_ref()
+                .map(|x| x.name.clone())
+                .unwrap_or(local_name);
+
             let item_source = if is_export {
                 ItemSource::Exported
             } else {
-                ItemSource::Imported
+                ItemSource::Imported(decl_item.alias.clone())
             };
 
-            //            if self.dropped_names.contains(TrackedName { name: item.name(), namespace: () }
-
-            if let Ok(Res::Item(id, _)) = term_result {
+            if let Ok(Res::Item(id, _) | Res::ExportedItem(id, _)) = term_result {
                 if is_export {
                     if let Some(namespace) = current_namespace {
                         self.globals
@@ -915,12 +961,13 @@ impl Resolver {
                     }
                 }
                 let scope = self.current_scope_mut();
-                scope
-                    .terms
-                    .insert(local_name.clone(), ScopeItemEntry::new(id, item_source));
+                scope.terms.insert(
+                    local_name.clone(),
+                    ScopeItemEntry::new(id, item_source.clone()),
+                );
             }
 
-            if let Ok(Res::Item(id, _)) = ty_result {
+            if let Ok(Res::Item(id, _) | Res::ExportedItem(id, _)) = ty_result {
                 if is_export {
                     if let Some(namespace) = current_namespace {
                         self.globals
@@ -930,30 +977,56 @@ impl Resolver {
                     }
                 }
                 let scope = self.current_scope_mut();
-                scope
-                    .tys
-                    .insert(local_name.clone(), ScopeItemEntry::new(id, item_source));
+                scope.tys.insert(
+                    local_name.clone(),
+                    ScopeItemEntry::new(id, item_source.clone()),
+                );
             }
 
+            // This is kind of a messy match, it is merged and formatted this way
+            // to appease clippy and rustfmt.
             let res = match (term_result, ty_result) {
-                (Ok(res @ Res::Item(..)), _) | (_, Ok(res @ Res::Item(..))) => res,
+                // If either a term or a ty exists for this item already,
+                // as either an item or an export, then we should use that res.
+                (Ok(res @ (Res::Item(..) | Res::ExportedItem(..))), _)
+                | (_, Ok(res @ (Res::Item(..) | Res::ExportedItem(..)))) => res,
+                // Then, if the item was found as either a term or ty but is _not_ an item or export, this export
+                // refers to an invalid res.
                 (Ok(_), _) | (_, Ok(_)) => {
                     let err = if is_export {
                         Error::ExportedNonItem
                     } else {
                         Error::ImportedNonItem
                     };
-                    let err = err(item.path.span);
+                    let err = err(decl_item.path.span);
                     self.errors.push(err);
                     continue;
                 }
+                // Lastly, if neither was found, use the error from the term_result to report a not
+                // found error.
                 (Err(err), _) => {
                     self.errors.push(err);
                     continue;
                 }
             };
-            // insert the item into the names we know about
-            self.names.insert(item.name().id, res);
+            match res {
+                // There's a bit of special casing here -- if this item is an export,
+                // and it originates from another package, we want to track the res as
+                // a separate exported item which points to the original package where
+                // the definition comes from.
+                Res::Item(item_id, _) if item_id.package.is_some() && is_export => {
+                    self.names
+                        .insert(decl_item.name().id, Res::ExportedItem(item_id, decl_alias));
+                }
+                Res::Item(underlying_item_id, _) if decl_alias.is_some() && is_export => {
+                    // insert the export's alias
+                    self.names.insert(
+                        decl_item.name().id,
+                        Res::ExportedItem(underlying_item_id, decl_alias),
+                    );
+                }
+                _ => self.names.insert(decl_item.name().id, res),
+            }
         }
     }
 
@@ -966,9 +1039,19 @@ impl Resolver {
                 .push(Error::GlobExportNotSupported(item.path.span));
             return;
         }
+
+        if let Some(alias) = &item.alias {
+            self.errors.push(Error::GlobImportAliasNotSupported {
+                span: item.span(),
+                namespace_name: Into::<Idents>::into(item.path.clone()).name().to_string(),
+                alias: alias.name.to_string(),
+            });
+            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(),
@@ -977,7 +1060,7 @@ impl Resolver {
             return;
         };
         if !is_export {
-            self.bind_open(&items, &alias, ns);
+            self.bind_open(&items, &None, ns);
         }
     }
 
@@ -1363,15 +1446,35 @@ impl GlobalTable {
         &mut self,
         id: PackageId,
         package: &hir::Package,
+        store: &crate::compile::PackageStore,
         alias: &Option<Arc<str>>,
     ) {
+        // if there is a package-level alias defined, use that for the root namespace.
         let root = match alias {
             Some(alias) => self
                 .scope
                 .insert_or_find_namespace(vec![Rc::from(&**alias)]),
+            // otherwise, these namespaces will be inserted into the root of the local package
+            // without any alias.
             None => self.scope.namespaces.root_id(),
         };
 
+        // iterate over the tree from the package and recreate it here
+        for names_for_same_namespace in &package.namespaces {
+            let mut names_iter = names_for_same_namespace.into_iter();
+            let base_id = self.scope.insert_or_find_namespace_from_root(
+                names_iter
+                    .next()
+                    .expect("should always be at least one name"),
+                root,
+            );
+
+            for name in names_iter {
+                self.scope
+                    .insert_or_find_namespace_from_root_with_id(name, root, base_id);
+            }
+        }
+
         for global in global::iter_package(Some(id), package).filter(|global| {
             global.visibility == hir::Visibility::Public
                 || matches!(&global.kind, global::Kind::Term(t) if t.intrinsic)
@@ -1410,12 +1513,53 @@ impl GlobalTable {
                 (global::Kind::Namespace, hir::Visibility::Public) => {
                     self.scope.insert_or_find_namespace(global.namespace);
                 }
+                (global::Kind::Export(item_id), _) => {
+                    let Some(item) = find_item(store, item_id, id) else {
+                        return;
+                    };
+                    match item.kind {
+                        hir::ItemKind::Callable(..) => {
+                            self.scope
+                                .terms
+                                .get_mut_or_default(namespace)
+                                .insert(global.name.clone(), Res::ExportedItem(item_id, None));
+                        }
+                        hir::ItemKind::Namespace(ns, _items) => {
+                            self.scope.insert_or_find_namespace(ns);
+                        }
+                        hir::ItemKind::Ty(..) => {
+                            self.scope
+                                .tys
+                                .get_mut_or_default(namespace)
+                                .insert(global.name.clone(), Res::ExportedItem(item_id, None));
+                        }
+                        hir::ItemKind::Export(_, _) => {
+                            unreachable!("find_item will never return an Export")
+                        }
+                    };
+                }
                 (_, hir::Visibility::Internal) => {}
             }
         }
     }
 }
 
+fn find_item(
+    store: &crate::compile::PackageStore,
+    item: ItemId,
+    this_package: PackageId,
+) -> Option<hir::Item> {
+    let package_id = item.package.unwrap_or(this_package);
+    let package = store.get(package_id)?;
+    let item = package.package.items.get(item.item)?;
+    Some(match &item.kind {
+        hir::ItemKind::Callable(_) | hir::ItemKind::Namespace(_, _) | hir::ItemKind::Ty(_, _) => {
+            item.clone()
+        }
+        hir::ItemKind::Export(_alias, item) => return find_item(store, *item, package_id),
+    })
+}
+
 /// Given some namespace `namespace`, add all the globals declared within it to the global scope.
 fn bind_global_items(
     names: &mut IndexMap<NodeId, Res>,
@@ -1491,7 +1635,7 @@ fn bind_global_item(
     names: &mut Names,
     scope: &mut GlobalScope,
     namespace: NamespaceId,
-    next_id: impl FnOnce() -> ItemId,
+    mut next_id: impl FnMut() -> ItemId,
     item: &ast::Item,
 ) -> Result<(), Vec<Error>> {
     match &*item.kind {
@@ -1500,7 +1644,52 @@ 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(..) | ast::ItemKind::ImportOrExport(..) => Ok(()),
+        ast::ItemKind::ImportOrExport(decl) => {
+            if decl.is_import() {
+                Ok(())
+            } else {
+                for decl_item in decl.items.iter() {
+                    // if the item is a namespace, bind it here as an item
+                    let Some(ns) = scope
+                        .namespaces
+                        .get_namespace_id(Into::<Idents>::into(decl_item.path.clone()).str_iter())
+                    else {
+                        continue;
+                    };
+                    let item_id = next_id();
+                    let res = Res::Item(item_id, ItemStatus::Available);
+                    names.insert(decl_item.name().id, res.clone());
+                    match scope
+                        .terms
+                        .get_mut_or_default(namespace)
+                        .entry(Rc::clone(&decl_item.name().name))
+                    {
+                        Entry::Occupied(_) => {
+                            let namespace_name = scope
+                                .namespaces
+                                .find_namespace_by_id(&namespace)
+                                .0
+                                .join(".");
+                            return Err(vec![Error::Duplicate(
+                                decl_item.name().name.to_string(),
+                                namespace_name,
+                                decl_item.name().span,
+                            )]);
+                        }
+                        Entry::Vacant(entry) => {
+                            entry.insert(res);
+                        }
+                    }
+
+                    // and update the namespace tree
+                    scope
+                        .namespaces
+                        .insert_with_id(Some(namespace), ns, &decl_item.name().name);
+                }
+                Ok(())
+            }
+        }
+        ast::ItemKind::Err | ast::ItemKind::Open(..) => Ok(()),
     }
 }
 
@@ -1515,7 +1704,7 @@ fn bind_callable(
     let item_id = next_id();
     let status = ItemStatus::from_attrs(&ast_attrs_as_hir_attrs(item.attrs.as_ref()));
     let res = Res::Item(item_id, status);
-    names.insert(decl.name.id, res);
+    names.insert(decl.name.id, res.clone());
     let mut errors = Vec::new();
     match scope
         .terms
@@ -1565,7 +1754,7 @@ fn bind_ty(
 
     let status = ItemStatus::from_attrs(&ast_attrs_as_hir_attrs(item.attrs.as_ref()));
     let res = Res::Item(item_id, status);
-    names.insert(name.id, res);
+    names.insert(name.id, res.clone());
     match (
         scope
             .terms
@@ -1589,7 +1778,7 @@ fn bind_ty(
             )])
         }
         (Entry::Vacant(term_entry), Entry::Vacant(ty_entry)) => {
-            term_entry.insert(res);
+            term_entry.insert(res.clone());
             ty_entry.insert(res);
             Ok(())
         }
@@ -1770,6 +1959,7 @@ fn check_scoped_resolutions(
             return Some(Ok(res));
         }
     }
+
     let aliases = scope
         .opens
         .iter()
@@ -1889,13 +2079,13 @@ where
             candidates.extend(&mut opens.iter().filter_map(|(ns_id, open)| {
                 globals
                     .get(kind, *ns_id, &provided_symbol_name.name)
-                    .map(|res| (*res, open.clone()))
+                    .map(|res| (res.clone(), open.clone()))
             }));
         }
     }
 
     for (candidate_namespace_id, open) in namespaces_to_search {
-        if find_symbol_in_namespace(
+        find_symbol_in_namespace(
             kind,
             globals,
             provided_namespace_name,
@@ -1903,16 +2093,14 @@ where
             &mut candidates,
             candidate_namespace_id,
             open,
-        ) {
-            continue;
-        }
+        );
     }
 
     if candidates.len() > 1 {
         // If there are multiple candidates, remove unimplemented items. This allows resolution to
         // succeed in cases where both an older, unimplemented API and newer, implemented API with the
         // same name are both in scope without forcing the user to fully qualify the name.
-        candidates.retain(|&res, _| !matches!(res, Res::Item(_, ItemStatus::Unimplemented)));
+        candidates.retain(|res, _| !matches!(res, &Res::Item(_, ItemStatus::Unimplemented)));
     }
     candidates
 }
@@ -1927,8 +2115,7 @@ fn find_symbol_in_namespace<O>(
     candidates: &mut FxHashMap<Res, O>,
     candidate_namespace_id: NamespaceId,
     open: O,
-) -> bool
-where
+) where
     O: Clone + std::fmt::Debug,
 {
     // Retrieve the namespace associated with the candidate_namespace_id from the global namespaces
@@ -1947,7 +2134,7 @@ where
     // 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;
+        return;
     }
 
     // Attempt to get the symbol from the global scope. If the namespace is None, use the candidate_namespace_id as a fallback
@@ -1955,9 +2142,8 @@ where
 
     // If a symbol was found, insert it into the candidates map
     if let Some(res) = res {
-        candidates.insert(*res, open);
+        candidates.insert(res.clone(), open);
     }
-    false
 }
 
 /// Fetch the name and namespace ID of all prelude namespaces.
@@ -2011,8 +2197,8 @@ fn resolve_scope_locals(
     }
 
     if let ScopeKind::Namespace(namespace) = &scope.kind {
-        if let Some(&res) = globals.get(kind, *namespace, name) {
-            return Some(res);
+        if let Some(res) = globals.get(kind, *namespace, name) {
+            return Some(res.clone());
         }
     }
 
diff --git a/compiler/qsc_frontend/src/typeck/check.rs b/compiler/qsc_frontend/src/typeck/check.rs
index 3d85d53f..c0a96bb4 100644
--- a/compiler/qsc_frontend/src/typeck/check.rs
+++ b/compiler/qsc_frontend/src/typeck/check.rs
@@ -37,26 +37,61 @@ impl GlobalTable {
         }
     }
 
-    pub(crate) fn add_external_package(&mut self, id: PackageId, package: &hir::Package) {
+    pub(crate) fn add_external_package(
+        &mut self,
+        package_id: PackageId,
+        package: &hir::Package,
+        store: &crate::compile::PackageStore,
+    ) {
         for item in package.items.values() {
-            let item_id = ItemId {
-                package: Some(id),
-                item: item.id,
-            };
-
-            match &item.kind {
-                hir::ItemKind::Callable(decl) => {
-                    self.terms.insert(item_id, decl.scheme().with_package(id))
-                }
-                hir::ItemKind::Namespace(..) => None,
-                hir::ItemKind::Ty(_, udt) => {
-                    self.udts.insert(item_id, udt.clone());
-                    self.terms
-                        .insert(item_id, udt.cons_scheme(item_id).with_package(id))
-                }
-            };
+            self.handle_item(item, package_id, store);
         }
     }
+
+    fn handle_item(
+        &mut self,
+        item: &hir::Item,
+        package_id: PackageId,
+        store: &crate::compile::PackageStore,
+    ) {
+        let item_id = ItemId {
+            package: Some(package_id),
+            item: item.id,
+        };
+        match &item.kind {
+            hir::ItemKind::Callable(decl) => {
+                self.terms
+                    .insert(item_id, decl.scheme().with_package(package_id));
+            }
+            hir::ItemKind::Namespace(..) => (),
+            hir::ItemKind::Ty(_, udt) => {
+                self.udts.insert(item_id, udt.clone());
+                self.terms
+                    .insert(item_id, udt.cons_scheme(item_id).with_package(package_id));
+            }
+            hir::ItemKind::Export(
+                _,
+                ItemId {
+                    package: other_package,
+                    item: exported_item,
+                },
+            ) => {
+                // If this item is an export, then we need to grab the ID that it references.
+                // It could be from the same package, or it could be from another package.
+                let package_id = other_package.unwrap_or(package_id);
+                // So, we get the correct package first,
+                let package = store.get(package_id).expect("package should exist");
+                // find the actual item
+                let resolved_export = package
+                    .package
+                    .items
+                    .get(*exported_item)
+                    .expect("exported item should exist");
+                // and recursively resolve it (it could be another export, i.e. a chain of exports.
+                self.handle_item(resolved_export, package_id, store);
+            }
+        };
+    }
 }
 
 pub(crate) struct Checker {
diff --git a/compiler/qsc_frontend/src/typeck/convert.rs b/compiler/qsc_frontend/src/typeck/convert.rs
index 9f2c5caf..71eaf317 100644
--- a/compiler/qsc_frontend/src/typeck/convert.rs
+++ b/compiler/qsc_frontend/src/typeck/convert.rs
@@ -74,10 +74,16 @@ pub(super) fn ty_from_path(names: &Names, path: &Path) -> Ty {
         // as there is a syntactic difference between
         // paths and parameters.
         // So realistically, by construction, `Param` here is unreachable.
-        Some(resolve::Res::Local(_) | resolve::Res::Param(_)) => unreachable!(
-            "A path should never resolve \
+        // A path can also never resolve to an export, because in typeck/check,
+        // we resolve exports to their original definition.
+        Some(
+            resolve::Res::Local(_) | resolve::Res::Param(_) | resolve::Res::ExportedItem(_, _),
+        ) => {
+            unreachable!(
+                "A path should never resolve \
             to a local or a parameter, as there is syntactic differentiation."
-        ),
+            )
+        }
         None => Ty::Err,
     }
 }
diff --git a/compiler/qsc_frontend/src/typeck/rules.rs b/compiler/qsc_frontend/src/typeck/rules.rs
index 6a7a6868..7cb9679c 100644
--- a/compiler/qsc_frontend/src/typeck/rules.rs
+++ b/compiler/qsc_frontend/src/typeck/rules.rs
@@ -114,7 +114,13 @@ impl<'a> Context<'a> {
                 // as there is a syntactic difference between
                 // paths and parameters.
                 // So realistically, by construction, `Param` here is unreachable.
-                Some(resolve::Res::Local(_) | resolve::Res::Param(_)) => unreachable!(
+                // A path can also never resolve to an export, because in typeck/check,
+                // we resolve exports to their original definition.
+                Some(
+                    resolve::Res::Local(_)
+                    | resolve::Res::Param(_)
+                    | resolve::Res::ExportedItem(_, _),
+                ) => unreachable!(
                     "A path should never resolve \
                     to a local or a parameter, as there is syntactic differentiation."
                 ),
@@ -582,6 +588,13 @@ impl<'a> Context<'a> {
                         .expect("local should have type")
                         .clone(),
                 ),
+                Some(Res::ExportedItem(item, _)) => {
+                    // get the underlying item this refers to
+                    let item_scheme = self.globals.get(item).expect("item should have scheme");
+                    let (ty, args) = self.inferrer.instantiate(item_scheme, expr.span);
+                    self.table.generics.insert(expr.id, args);
+                    converge(Ty::Arrow(Box::new(ty)))
+                }
                 Some(Res::PrimTy(_) | Res::UnitTy | Res::Param(_)) => {
                     panic!("expression should not resolve to type reference")
                 }
diff --git a/compiler/qsc_hir/src/global.rs b/compiler/qsc_hir/src/global.rs
index cc9f230d..754daf48 100644
--- a/compiler/qsc_hir/src/global.rs
+++ b/compiler/qsc_hir/src/global.rs
@@ -2,7 +2,9 @@
 // Licensed under the MIT License.
 
 use crate::{
-    hir::{Item, ItemId, ItemKind, ItemStatus, Package, PackageId, SpecBody, SpecGen, Visibility},
+    hir::{
+        self, Item, ItemId, ItemKind, ItemStatus, Package, PackageId, SpecBody, SpecGen, Visibility,
+    },
     ty::Scheme,
 };
 use qsc_data_structures::{
@@ -25,6 +27,7 @@ pub enum Kind {
     Namespace,
     Ty(Ty),
     Term(Term),
+    Export(ItemId),
 }
 
 impl std::fmt::Debug for Kind {
@@ -33,6 +36,7 @@ impl std::fmt::Debug for Kind {
             Kind::Namespace => write!(f, "Namespace"),
             Kind::Ty(ty) => write!(f, "Ty({})", ty.id),
             Kind::Term(term) => write!(f, "Term({})", term.id),
+            Kind::Export(id) => write!(f, "Export({id:?})"),
         }
     }
 }
@@ -92,7 +96,7 @@ impl FromIterator<Global> for Table {
                         .or_default()
                         .insert(global.name, term);
                 }
-                Kind::Namespace => {}
+                Kind::Namespace | Kind::Export(_) => {}
             }
         }
 
@@ -121,17 +125,31 @@ impl PackageIter<'_> {
                 .expect("parent should exist")
                 .kind
         });
-        let id = ItemId {
-            package: self.id,
-            item: item.id,
+        let (id, visibility, alias) = match &item.kind {
+            ItemKind::Export(name, item_id) => (
+                ItemId {
+                    package: item_id.package.or(self.id),
+                    item: item_id.item,
+                },
+                hir::Visibility::Public,
+                Some(name),
+            ),
+            _ => (
+                ItemId {
+                    package: self.id,
+                    item: item.id,
+                },
+                item.visibility,
+                None,
+            ),
         };
         let status = ItemStatus::from_attrs(item.attrs.as_ref());
 
         match (&item.kind, &parent) {
             (ItemKind::Callable(decl), Some(ItemKind::Namespace(namespace, _))) => Some(Global {
                 namespace: namespace.into(),
-                name: Rc::clone(&decl.name.name),
-                visibility: item.visibility,
+                name: alias.map_or_else(|| Rc::clone(&decl.name.name), |alias| alias.name.clone()),
+                visibility,
                 status,
                 kind: Kind::Term(Term {
                     id,
@@ -142,8 +160,8 @@ impl PackageIter<'_> {
             (ItemKind::Ty(name, def), Some(ItemKind::Namespace(namespace, _))) => {
                 self.next = Some(Global {
                     namespace: namespace.into(),
-                    name: Rc::clone(&name.name),
-                    visibility: item.visibility,
+                    name: alias.map_or_else(|| Rc::clone(&name.name), |alias| alias.name.clone()),
+                    visibility,
                     status,
                     kind: Kind::Term(Term {
                         id,
@@ -155,7 +173,7 @@ impl PackageIter<'_> {
                 Some(Global {
                     namespace: namespace.into(),
                     name: Rc::clone(&name.name),
-                    visibility: item.visibility,
+                    visibility,
                     status,
                     kind: Kind::Ty(Ty { id }),
                 })
@@ -167,6 +185,24 @@ impl PackageIter<'_> {
                 status,
                 kind: Kind::Namespace,
             }),
+            (
+                ItemKind::Export(name, ItemId { package, .. }),
+                Some(ItemKind::Namespace(namespace, _)),
+            ) => {
+                if package.is_none() && alias.is_none() {
+                    // if there is no package, then this was declared in this package
+                    // and this is a noop -- it will be marked as public on export
+                    None
+                } else {
+                    Some(Global {
+                        namespace: namespace.into(),
+                        name: name.name.clone(),
+                        visibility,
+                        status,
+                        kind: Kind::Export(id),
+                    })
+                }
+            }
             _ => None,
         }
     }
diff --git a/compiler/qsc_hir/src/hir.rs b/compiler/qsc_hir/src/hir.rs
index 06806823..e727dad5 100644
--- a/compiler/qsc_hir/src/hir.rs
+++ b/compiler/qsc_hir/src/hir.rs
@@ -25,7 +25,7 @@ fn set_indentation<'a, 'b>(
         0 => indent.with_str(""),
         1 => indent.with_str("    "),
         2 => indent.with_str("        "),
-        _ => unimplemented!("intentation level not supported"),
+        _ => unimplemented!("indentation level not supported"),
     }
 }
 
@@ -253,6 +253,8 @@ impl Display for Res {
 pub struct Package {
     /// The items in the package.
     pub items: IndexMap<LocalItemId, Item>,
+    /// The namespace tree defined by this package
+    pub namespaces: qsc_data_structures::namespaces::NamespaceTreeRoot,
     /// The top-level statements in the package.
     pub stmts: Vec<Stmt>,
     /// The entry expression for an executable package.
@@ -335,6 +337,8 @@ pub enum ItemKind {
     Namespace(Idents, Vec<LocalItemId>),
     /// A `newtype` declaration.
     Ty(Ident, Udt),
+    /// An export of an item.
+    Export(Ident, ItemId),
 }
 
 impl Display for ItemKind {
@@ -355,6 +359,7 @@ impl Display for ItemKind {
                 }
             }
             ItemKind::Ty(name, udt) => write!(f, "Type ({name}): {udt}"),
+            ItemKind::Export(name, export) => write!(f, "Export ({name}): {export}"),
         }
     }
 }
diff --git a/compiler/qsc_hir/src/mut_visit.rs b/compiler/qsc_hir/src/mut_visit.rs
index c016b73d..be3904a1 100644
--- a/compiler/qsc_hir/src/mut_visit.rs
+++ b/compiler/qsc_hir/src/mut_visit.rs
@@ -71,7 +71,7 @@ pub fn walk_item(vis: &mut impl MutVisitor, item: &mut Item) {
     match &mut item.kind {
         ItemKind::Callable(decl) => vis.visit_callable_decl(decl),
         ItemKind::Namespace(name, _) => vis.visit_idents(name),
-        ItemKind::Ty(name, _) => vis.visit_ident(name),
+        ItemKind::Ty(name, _) | ItemKind::Export(name, _) => vis.visit_ident(name),
     }
 }
 
diff --git a/compiler/qsc_hir/src/ty.rs b/compiler/qsc_hir/src/ty.rs
index 7a852a6e..0ea18ed9 100644
--- a/compiler/qsc_hir/src/ty.rs
+++ b/compiler/qsc_hir/src/ty.rs
@@ -147,6 +147,7 @@ impl Display for Ty {
     }
 }
 
+#[derive(Debug)]
 /// A type scheme.
 pub struct Scheme {
     params: Vec<GenericParam>,
diff --git a/compiler/qsc_hir/src/visit.rs b/compiler/qsc_hir/src/visit.rs
index 1a473126..a6e8a6fd 100644
--- a/compiler/qsc_hir/src/visit.rs
+++ b/compiler/qsc_hir/src/visit.rs
@@ -64,7 +64,7 @@ pub fn walk_item<'a>(vis: &mut impl Visitor<'a>, item: &'a Item) {
     match &item.kind {
         ItemKind::Callable(decl) => vis.visit_callable_decl(decl),
         ItemKind::Namespace(name, _) => vis.visit_idents(name),
-        ItemKind::Ty(name, _) => vis.visit_ident(name),
+        ItemKind::Ty(name, _) | ItemKind::Export(name, _) => vis.visit_ident(name),
     }
 }
 
diff --git a/compiler/qsc_lowerer/src/lib.rs b/compiler/qsc_lowerer/src/lib.rs
index ff83e42a..e59d5b3f 100644
--- a/compiler/qsc_lowerer/src/lib.rs
+++ b/compiler/qsc_lowerer/src/lib.rs
@@ -87,7 +87,11 @@ impl Lowerer {
             .collect()
     }
 
-    pub fn lower_package(&mut self, package: &hir::Package) -> fir::Package {
+    pub fn lower_package(
+        &mut self,
+        package: &hir::Package,
+        store: &fir::PackageStore,
+    ) -> fir::Package {
         let entry = package.entry.as_ref().map(|e| self.lower_expr(e));
         let entry_exec_graph = self.exec_graph.drain(..).collect();
         let items: IndexMap<LocalItemId, fir::Item> = package
@@ -116,7 +120,7 @@ impl Lowerer {
             pats,
             stmts,
         };
-        qsc_fir::validate::validate(&package);
+        qsc_fir::validate::validate(&package, store);
         package
     }
 
@@ -153,8 +157,6 @@ impl Lowerer {
         }
 
         fir_package.entry = entry;
-
-        qsc_fir::validate::validate(fir_package);
     }
 
     pub fn revert_last_increment(&mut self, package: &mut fir::Package) {
@@ -224,6 +226,12 @@ impl Lowerer {
 
                 fir::ItemKind::Ty(name, udt)
             }
+            hir::ItemKind::Export(name, item) => {
+                let name = self.lower_ident(name);
+                let item = lower_item_id(item);
+
+                fir::ItemKind::Export(name, item)
+            }
         };
         let attrs = lower_attrs(&item.attrs);
         fir::Item {
diff --git a/compiler/qsc_passes/src/capabilitiesck.rs b/compiler/qsc_passes/src/capabilitiesck.rs
index 0b068a8a..3ccfac31 100644
--- a/compiler/qsc_passes/src/capabilitiesck.rs
+++ b/compiler/qsc_passes/src/capabilitiesck.rs
@@ -39,7 +39,7 @@ pub fn lower_store(
 ) -> qsc_fir::fir::PackageStore {
     let mut fir_store = qsc_fir::fir::PackageStore::new();
     for (id, unit) in package_store {
-        let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package);
+        let package = qsc_lowerer::Lowerer::new().lower_package(&unit.package, &fir_store);
         fir_store.insert(map_hir_package_to_fir(id), package);
     }
     fir_store
@@ -55,8 +55,12 @@ pub fn run_rca_pass(
     let fir_package = fir_store.get(package_id);
 
     let package_compute_properties = compute_properties.get(package_id);
-    let mut errors =
-        check_supported_capabilities(fir_package, package_compute_properties, capabilities);
+    let mut errors = check_supported_capabilities(
+        fir_package,
+        package_compute_properties,
+        capabilities,
+        fir_store,
+    );
 
     if errors.is_empty() {
         Ok(compute_properties)
@@ -74,6 +78,7 @@ pub fn check_supported_capabilities(
     package: &Package,
     compute_properties: &PackageComputeProperties,
     capabilities: TargetCapabilityFlags,
+    store: &qsc_fir::fir::PackageStore,
 ) -> Vec<Error> {
     let checker = Checker {
         package,
@@ -81,6 +86,7 @@ pub fn check_supported_capabilities(
         target_capabilities: capabilities,
         current_callable: None,
         missing_features_map: FxHashMap::<Span, RuntimeFeatureFlags>::default(),
+        store,
     };
 
     checker.check_all()
@@ -92,6 +98,7 @@ struct Checker<'a> {
     target_capabilities: TargetCapabilityFlags,
     current_callable: Option<LocalItemId>,
     missing_features_map: FxHashMap<Span, RuntimeFeatureFlags>,
+    store: &'a qsc_fir::fir::PackageStore,
 }
 
 impl<'a> Visitor<'a> for Checker<'a> {
@@ -111,7 +118,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
         self.package.get_stmt(id)
     }
 
-    fn visit_package(&mut self, package: &'a Package) {
+    fn visit_package(&mut self, package: &'a Package, _: &crate::fir::PackageStore) {
         package
             .items
             .iter()
@@ -192,7 +199,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
 
 impl<'a> Checker<'a> {
     pub fn check_all(mut self) -> Vec<Error> {
-        self.visit_package(self.package);
+        self.visit_package(self.package, self.store);
         self.generate_errors()
     }
 
diff --git a/compiler/qsc_passes/src/lib.rs b/compiler/qsc_passes/src/lib.rs
index a9d58e9c..7b168b3d 100644
--- a/compiler/qsc_passes/src/lib.rs
+++ b/compiler/qsc_passes/src/lib.rs
@@ -165,9 +165,10 @@ pub fn run_fir_passes(
     package: &fir::Package,
     compute_properties: &PackageComputeProperties,
     capabilities: TargetCapabilityFlags,
+    store: &fir::PackageStore,
 ) -> Vec<Error> {
     let capabilities_errors =
-        check_supported_capabilities(package, compute_properties, capabilities);
+        check_supported_capabilities(package, compute_properties, capabilities, store);
     capabilities_errors
         .into_iter()
         .map(Error::CapabilitiesCk)
diff --git a/compiler/qsc_rca/src/core.rs b/compiler/qsc_rca/src/core.rs
index 42f26fd3..b710d1f7 100644
--- a/compiler/qsc_rca/src/core.rs
+++ b/compiler/qsc_rca/src/core.rs
@@ -1927,10 +1927,28 @@ impl<'a> Visitor<'a> for Analyzer<'a> {
             ItemKind::Callable(decl) => {
                 self.visit_callable_decl(decl);
             }
+            ItemKind::Export(
+                _,
+                qsc_fir::fir::ItemId {
+                    package: Some(package),
+                    item,
+                },
+            ) => {
+                let package = self.package_store.get(*package);
+                let item = package
+                    .items
+                    .get(*item)
+                    .expect("item should exist in package");
+                self.visit_item(item);
+            }
+            ItemKind::Export(_, qsc_fir::fir::ItemId { package: None, .. }) => {
+                // if the package is none, then we know this item was defined in this package
+                // and therefore doesn't need to be analyzed -- the item itself will be analyzed.
+            }
         };
     }
 
-    fn visit_package(&mut self, _: &'a Package) {
+    fn visit_package(&mut self, _: &'a Package, _: &PackageStore) {
         // Should never be called.
         unimplemented!("should never be called");
     }
diff --git a/compiler/qsc_rca/src/cycle_detection.rs b/compiler/qsc_rca/src/cycle_detection.rs
index 7a61c756..33b8c96f 100644
--- a/compiler/qsc_rca/src/cycle_detection.rs
+++ b/compiler/qsc_rca/src/cycle_detection.rs
@@ -7,8 +7,8 @@ use crate::common::{
 use qsc_fir::{
     fir::{
         Block, BlockId, CallableDecl, CallableImpl, Expr, ExprId, ExprKind, Item, ItemKind,
-        LocalVarId, Mutability, Package, PackageId, PackageLookup, Pat, PatId, PatKind, SpecDecl,
-        Stmt, StmtId, StmtKind,
+        LocalVarId, Mutability, Package, PackageId, PackageLookup, PackageStore, Pat, PatId,
+        PatKind, SpecDecl, Stmt, StmtId, StmtKind,
     },
     ty::FunctorSetValue,
     visit::{walk_expr, Visitor},
@@ -22,21 +22,23 @@ pub struct CycleDetector<'a> {
     stack: CallStack,
     specializations_locals: FxHashMap<LocalSpecId, FxHashMap<LocalVarId, Local>>,
     specializations_with_cycles: FxHashSet<LocalSpecId>,
+    store: &'a PackageStore,
 }
 
 impl<'a> CycleDetector<'a> {
-    pub fn new(package_id: PackageId, package: &'a Package) -> Self {
+    pub fn new(package_id: PackageId, package: &'a Package, store: &'a PackageStore) -> Self {
         Self {
             package_id,
             package,
             stack: CallStack::default(),
             specializations_locals: FxHashMap::default(),
             specializations_with_cycles: FxHashSet::<LocalSpecId>::default(),
+            store,
         }
     }
 
     pub fn detect_specializations_with_cycles(mut self) -> Vec<LocalSpecId> {
-        self.visit_package(self.package);
+        self.visit_package(self.package, self.store);
         self.specializations_with_cycles.drain().collect()
     }
 
@@ -116,19 +118,34 @@ impl<'a> CycleDetector<'a> {
                 return;
             }
             let item = self.package.get_item(callee.item.item);
-            match &item.kind {
-                ItemKind::Callable(callable_decl) => self.walk_callable_decl(
-                    (callee.item.item, callee.functor_app.functor_set_value()).into(),
-                    callable_decl,
-                ),
-                ItemKind::Namespace(_, _) => panic!("calls to namespaces are invalid"),
-                ItemKind::Ty(_, _) => {
-                    // Ignore "calls" to types.
-                }
+            self.handle_item(item, &callee);
+        }
+    }
+
+    fn handle_item(&mut self, item: &'a Item, callee: &crate::common::Callee) {
+        match &item.kind {
+            ItemKind::Callable(callable_decl) => self.walk_callable_decl(
+                (callee.item.item, callee.functor_app.functor_set_value()).into(),
+                callable_decl,
+            ),
+            ItemKind::Namespace(_, _) => panic!("calls to namespaces are invalid"),
+            ItemKind::Ty(_, _) => {
+                // Ignore "calls" to types.
+            }
+            ItemKind::Export(_, id) => {
+                // resolve the item, which may exist in another package
+                let item = self.resolve_item(*id);
+                self.handle_item(item, callee);
             }
         }
     }
 
+    fn resolve_item(&self, item: qsc_fir::fir::ItemId) -> &'a Item {
+        let package_id = item.package.unwrap_or(self.package_id);
+        let package = self.store.get(package_id);
+        package.get_item(item.item)
+    }
+
     fn walk_spec_decl(&mut self, local_spec_id: LocalSpecId, spec_decl: &'a SpecDecl) {
         // If the specialization is already in the stack, it means the callable has a cycle.
         if self.stack.contains(&local_spec_id) {
@@ -230,7 +247,7 @@ impl<'a> Visitor<'a> for CycleDetector<'a> {
         }
     }
 
-    fn visit_package(&mut self, package: &'a Package) {
+    fn visit_package(&mut self, package: &'a Package, _: &PackageStore) {
         // We are only interested in visiting items.
         package.items.values().for_each(|i| self.visit_item(i));
     }
diff --git a/compiler/qsc_rca/src/cyclic_callables.rs b/compiler/qsc_rca/src/cyclic_callables.rs
index 184ac2d0..c4e762dd 100644
--- a/compiler/qsc_rca/src/cyclic_callables.rs
+++ b/compiler/qsc_rca/src/cyclic_callables.rs
@@ -123,7 +123,7 @@ impl<'a> Analyzer<'a> {
 
     fn analyze_package_internal(&mut self, package_id: PackageId, package: &'a Package) {
         self.current_package = Some(package_id);
-        self.visit_package(package);
+        self.visit_package(package, self.package_store);
         self.current_package = None;
     }
 
@@ -223,9 +223,9 @@ impl<'a> Visitor<'a> for Analyzer<'a> {
         unimplemented!();
     }
 
-    fn visit_package(&mut self, package: &'a Package) {
+    fn visit_package(&mut self, package: &'a Package, store: &PackageStore) {
         let package_id = self.get_current_package();
-        let cycle_detector = CycleDetector::new(package_id, package);
+        let cycle_detector = CycleDetector::new(package_id, package, store);
         let specializations_with_cycles = cycle_detector.detect_specializations_with_cycles();
         for cyclic_specialization in specializations_with_cycles {
             self.analyze_cyclic_specialization(cyclic_specialization);
diff --git a/compiler/qsc_rca/src/overrider.rs b/compiler/qsc_rca/src/overrider.rs
index 7f9dc36a..afa946d3 100644
--- a/compiler/qsc_rca/src/overrider.rs
+++ b/compiler/qsc_rca/src/overrider.rs
@@ -106,7 +106,7 @@ impl<'a> Overrider<'a> {
 
     fn populate_package_internal(&mut self, package_id: PackageId, package: &'a Package) {
         self.current_package = Some(package_id);
-        self.visit_package(package);
+        self.visit_package(package, self.package_store);
         self.current_package = None;
     }
 
@@ -197,7 +197,7 @@ impl<'a> Visitor<'a> for Overrider<'a> {
             .insert_expr((package_id, id).into(), application_generator_set);
     }
 
-    fn visit_package(&mut self, package: &'a Package) {
+    fn visit_package(&mut self, package: &'a Package, _: &PackageStore) {
         // Go through each namespace, identifying the callables for which we have overrides.
         let namespaces = package
             .items
diff --git a/compiler/qsc_rir/src/rir.rs b/compiler/qsc_rir/src/rir.rs
index 7e0dbb63..5d529042 100644
--- a/compiler/qsc_rir/src/rir.rs
+++ b/compiler/qsc_rir/src/rir.rs
@@ -617,6 +617,6 @@ fn set_indentation<'a, 'b>(
         0 => indent.with_str(""),
         1 => indent.with_str("    "),
         2 => indent.with_str("        "),
-        _ => unimplemented!("intentation level not supported"),
+        _ => unimplemented!("indentation level not supported"),
     }
 }
diff --git a/katas/content/oracles/bit_pattern_challenge/index.md b/katas/content/oracles/bit_pattern_challenge/index.md
index fee7083c..84b7ecba 100644
--- a/katas/content/oracles/bit_pattern_challenge/index.md
+++ b/katas/content/oracles/bit_pattern_challenge/index.md
@@ -17,7 +17,7 @@ represented by `pattern`.
 <br/>
 <details>
   <summary><b>Before implementing this oracle, answer the question: are you implementing a marking or a phase oracle?</b></summary>
-    This is a phase oracle, because we are changing the phase of the input state $\ket{x}$ based on the value of the function $f(x)$.
+    This is a phase oracle, because you're changing the phase of the input state $\ket{x}$ based on the value of the function $f(x)$.
 </details>
 
 <br/>
diff --git a/katas/content/oracles/bit_pattern_oracle/index.md b/katas/content/oracles/bit_pattern_oracle/index.md
index 08eb798b..f50ce451 100644
--- a/katas/content/oracles/bit_pattern_oracle/index.md
+++ b/katas/content/oracles/bit_pattern_oracle/index.md
@@ -17,7 +17,7 @@ represented by `pattern`.
 <br/>
 <details>
   <summary><b>Before implementing this oracle, answer the question: are you implementing a marking or a phase oracle?</b></summary>
-    This is a marking oracle, because we are flipping the state of the target qubit $\ket{y}$ based on the state of the input $\ket{x}$.
+    This is a marking oracle, because you're flipping the state of the target qubit $\ket{y}$ based on the state of the input $\ket{x}$.
 </details>
 
 <br/>
diff --git a/katas/content/oracles/classical_oracles/solution.md b/katas/content/oracles/classical_oracles/solution.md
index 8475e0db..d6133e1f 100644
--- a/katas/content/oracles/classical_oracles/solution.md
+++ b/katas/content/oracles/classical_oracles/solution.md
@@ -1,7 +1,7 @@
-The binary notation of $7$ is $111_2$. This means that our solution should return `true` if and only if each bit of the input is `true`.
+The binary notation of $7$ is $111_2$. This means that your solution should return `true` if and only if each bit of the input is `true`.
 
 Notice that in this problem the solution doesn't depend on whether the bit string is converted into an integer using 
-big endian or little endian notation. In general this is an important distinction that you need to take into account.
+big endian or little endian notation. In general, this is an important distinction that you need to take into account.
 
 @[solution]({
     "id": "oracles__classical_oracles_solution",
diff --git a/katas/content/oracles/index.md b/katas/content/oracles/index.md
index 1b997f92..28b6c52b 100644
--- a/katas/content/oracles/index.md
+++ b/katas/content/oracles/index.md
@@ -16,23 +16,23 @@ Quantum oracles are a key part of many quantum algorithms that rely on quantum i
 
 **What you should know to start working on this kata:**
 
-- Fundamental quantum concepts
-- Multi-qubit gates (especially controlled gates)
+- Basic knowledge of fundamental quantum concepts
+- Basic knowledge of multi-qubit gates, especially controlled gates. If you're not familiar with these concepts, you can find them in the Multi-Qubit Gates kata. 
 
 @[section]({
     "id": "oracles__classical_oracles",
     "title": "Classical Oracles"
 })
 
-In classical computing, we often discuss "black box" versus "white box" testing.  In "white box" testing, the implementation of a function is visible to the tester,  thus they can verify specific runtime or memory complexity expectations for the algorithm.  
+In classical computing, the terms "black box" versus "white box" are often used to discuss the testing of a function. In "white box" testing, the implementation of a function is visible to the tester,  thus they can verify specific runtime or memory complexity expectations for the algorithm.  
 However, in "black box" testing, the tester doesn't have access to the details of the function implementation. They only have access to the "black box" that takes an input and produces the corresponding output. This means the tester can only test the functionality and expected behavior of the function, but not the implementation, which is hidden behind abstraction.
 
 Formally, a **classical oracle** is a function that, provided some input, produces a *deterministic* output
 (the same input *always* results in the same output).
 
-Some classical problems (typically <a href="https://en.wikipedia.org/wiki/Decision_problem" target="_blank">decision problems</a>) are also expressed in terms of oracles; in this case we do not care about how the function is implemented, but only about the functionality that it provides.  
+Some classical problems (typically <a href="https://en.wikipedia.org/wiki/Decision_problem" target="_blank">decision problems</a>) are also expressed in terms of oracles; in this case the implementation of the function isn't important, but the functionality that it provides.  
 
-> Suppose I provided you a function which takes two list parameters as input, where these lists represent the availability of two employees at a company during the week.  The function returns true if there is a day (Monday, Tuesday, Wednesday, Thursday, or Friday) for which they are both free and could schedule a meeting, and false if no such date exists.
+> Suppose you've been given a function which takes two list parameters as input, where these lists represent the availability of two employees at a company during the week.  The function returns true if there is a day (Monday, Tuesday, Wednesday, Thursday, or Friday) for which they are both free and could schedule a meeting, and false if no such date exists.
 >
 > This function is an example of a classical oracle.
 
@@ -49,9 +49,9 @@ Some classical problems (typically <a href="https://en.wikipedia.org/wiki/Decisi
 
 An oracle in the quantum world is a "black box" operation that is used as input to an algorithm (such as Deutsch-Jozsa algorithm or Grover's search algorithm).
 Many quantum algorithms assume an oracle implementation of some classical function as input, but this is a very strong assumption - sometimes implementing the oracle for a function is a lot more complex than the algorithm that will use this oracle!  
-In this kata, you will learn the properties of quantum oracles and how to implement them.
+In this kata, you'll learn the properties of quantum oracles and how to implement them.
 
-A quantum oracle implements a function $f: \{0,1\}^n \rightarrow \{0,1\}^m$, where the input is $n$-bits of the form $x = (x_{0}, x_{1}, \dots, x_{n-1})$. In most commonly used cases $m=1$, that is, the function can return values $0$ or $1$. In this kata, we will focus on this class of functions.
+A quantum oracle implements a function $f: \{0,1\}^n \rightarrow \{0,1\}^m$, where the input is $n$-bits of the form $x = (x_{0}, x_{1}, \dots, x_{n-1})$. In most commonly used cases $m=1$, that is, the function can return values $0$ or $1$. In this kata, you'll focus on this class of functions.
 
 Quantum oracles operate on qubit arrays (and can take classical parameters as well).  The classical input is encoded into the state of an $n$-qubit register:  
 $$\ket{\vec{x}} = \ket{x_0} \otimes \ket{x_1} \otimes ... \otimes \ket{x_{n-1}},$$
@@ -60,7 +60,7 @@ where $\ket{x_i}$ represents the state of the $i$-th qubit.
 Oracles must be unitary transformations, and follow the same rules of linear algebra as other quantum operations.
 This allows us to define quantum oracles based on their effect on the basis states - tensor products of single-qubit basis states $\ket{0}$ and $\ket{1}$.
 
-> For example, an oracle that implements a function that takes two bits of input will be defined using its effect on basis states $\ket{00}$, $\ket{01}$, $\ket{10}$, and $\ket{11}$.  
+> For example, an oracle that implements a function that takes two bits of input is defined using its effect on basis states $\ket{00}$, $\ket{01}$, $\ket{10}$, and $\ket{11}$.  
 
 There are two types of quantum oracles: phase oracles and marking oracles.  Let's take a closer look at them.
 
@@ -75,7 +75,7 @@ $$U_{phase} \ket{\vec{x}} = (-1)^{f(x)}\ket{\vec{x}}$$
 
 Thus, the phase oracle $U_{\text{phase}}$ doesn't change the phase of the basis states for which $f(x)=0$, but multiplies the phase of the basis states for which $f(x)=1$ by $-1$.
 
-The effect of such an oracle on any single basis state is not particularly interesting: it just adds a global phase which is not something you can observe. However, if you apply this oracle to a *superposition* of basis states, its effect becomes noticeable.
+The effect of such an oracle on any single basis state isn't particularly interesting: it just adds a global phase which is not something you can observe. However, if you apply this oracle to a *superposition* of basis states, its effect becomes noticeable.
 Remember that quantum operations are linear: if you define the effect of an operation on the basis states, you'll be able to deduce its effect on superposition states (which are just linear combinations of the basis states) using its linearity.
 
 A phase oracle doesn't have an "output", unlike the function it implements; the effect of the oracle application is the change in the state of the system.
@@ -87,12 +87,12 @@ A phase oracle doesn't have an "output", unlike the function it implements; the
 
 Consider the function $f(x)$ that takes three bits of input and returns $1$ if $x=101$ or $x=010$, and $0$ otherwise.
 
-The phase oracle that implements this function will take an array of three qubits as an input, flip the sign of basis states $\ket{101}$ and $\ket{010}$, and leave the rest of the basis states unchanged. Let's see the effect of this oracle on a superposition state.
+The phase oracle that implements this function takes an array of three qubits as an input, flip the sign of basis states $\ket{101}$ and $\ket{010}$, and leave the rest of the basis states unchanged. Let's see the effect of this oracle on a superposition state.
 
 @[example]({"id": "oracles__phase_oracle_alt_bit", "codePath": "./examples/PhaseOracleAltBit.qs"})
 
-We introduced the function `ApplyControlledOnBitString` provided by the Q# standard library when we discussed controlled gates.
-It defines a variant of a gate controlled on a state specified by a bit mask; for example, bit mask `[true, false]` means that the gate should be applied only if the two control qubits are in the $\ket{10}$ state.
+The Multi-Qubit Gates kata introduced the `ApplyControlledOnBitString` function provided by the Q# standard library when discussing controlled gates.
+The `ApplyControlledOnBitString` function defines a variant of a gate controlled on a state specified by a bit mask. For example, bit mask `[true, false]` means that the gate should be applied only if the two control qubits are in the $\ket{10}$ state.
 
 The sequence of steps that implement this variant are:
 
@@ -102,9 +102,9 @@ The sequence of steps that implement this variant are:
 
 > Notice that the input state in the demo above is an equal superposition of all basis states.
 After applying the oracle the absolute values of all amplitudes are the same, but the states $\ket{010}$ and $\ket{101}$ had their phase flipped to negative.
-> Recall that these two states are exactly the inputs for which $f(x) = 1$, thus they are exactly the two states we expect to experience a phase flip!
+> Recall that these two states are exactly the inputs for which $f(x) = 1$, thus they're exactly the two states we expect to experience a phase flip!
 
-In the next exercise you will implement the classical oracle that you've implemented in the first exercise, this time as a quantum phase oracle $U_{7,\text{phase}}$ that encodes the number 7.
+In the next exercise, you'll implement the classical oracle that you've implemented in the first exercise, this time as a quantum phase oracle $U_{7,\text{phase}}$ that encodes the number 7.
 
 @[exercise]({
     "id": "oracles__phase_oracle_seven",
@@ -123,28 +123,28 @@ $$U_{mark}\ket{\vec{x}} \ket{y} = U_{mark}\big(\ket{\vec{x}} \otimes \ket{y}\big
 
 Again, since all quantum operations are linear, you can figure out the effect of this operation on superposition state knowing its effect on the basis states using its linearity.
 
-A marking oracle has distinct "input" and "output" qubits, but in general the effect of the oracle application is the change in the state of the whole system rather than of the "output" qubits only. We will look at this closer in a moment.
+A marking oracle has distinct "input" and "output" qubits, but in general the effect of the oracle application is the change in the state of the whole system rather than of the "output" qubits only. You'll look at this closer in a moment.
 
 @[section]({
     "id": "oracles__marking_oracle_alternating_bits",
     "title": "Marking Oracle for Alternating Bit Pattern Function"
 })
 
-Consider the function $f(x)$ that takes three bits of input and returns $1$ if $x=101$ or $x=010$, and $0$ otherwise (it is the same function we've seen in the lesson "Phase Oracle for Alternating Bit Pattern Function").
+Consider the function $f(x)$ that takes three bits of input and returns $1$ if $x=101$ or $x=010$, and $0$ otherwise (it's the function in the lesson "Phase Oracle for Alternating Bit Pattern Function").
 
-The marking oracle that implements this function will take an array of three qubits as an "input" register and an "output" qubit, and will flip the state of the output qubit if the input qubit was in basis state $\ket{101}$ or $\ket{010}$, and do nothing otherwise. Let's see the effect of this oracle on a superposition state.
+The marking oracle that implements this function takes an array of three qubits as an "input" register and an "output" qubit, and flips the state of the output qubit if the input qubit is in basis state $\ket{101}$ or $\ket{010}$, and does nothing otherwise. Let's see the effect of this oracle on a superposition state.
 
 @[example]({"id": "oracles__marking_oracle_alt_bit", "codePath": "./examples/MarkingOracleAltBit.qs"})
 
 > Let's compare the initial state to the final state from the above demo.
-In the initial state we had a tensor product of an equal superposition of all three-qubit basis states and the state $\ket{0}$.  In the final state, this is no longer the case.
+In the initial state, there's a tensor product of an equal superposition of all three-qubit basis states and the state $\ket{0}$.  In the final state, this is no longer the case.
 The basis states $\ket{010} \otimes \ket{0}$ and $\ket{101} \otimes \ket{0}$ no longer have non-zero amplitudes, and instead $\ket{010} \otimes \ket{1}$ and $\ket{101} \otimes \ket{1}$ have non-zero amplitudes.
 >
-> This is exactly the result that we expect.  Recall our function $f(x)$: $f(x)=1$ if and only if $x=010$ or $x=101$.  The first three qubits (variable `x`) represent the input state $\ket{x}$, and the last qubit (variable `y`) represents the output state $\ket{y}$.  Thus when we have the two basis states, $\ket{x}=\ket{010}$ or $\ket{x}=\ket{101}$, we will flip the state of the qubit $\ket{y}$, causing these two initial states to be tensored with $\ket{1}$ in the final state where originally they were tensored with $\ket{0}$.
+> This is exactly the expected result. Recall the function $f(x)$: $f(x)=1$ if and only if $x=010$ or $x=101$.  The first three qubits (variable `x`) represent the input state $\ket{x}$, and the last qubit (variable `y`) represents the output state $\ket{y}$.  Thus for the two basis states, $\ket{x}=\ket{010}$ or $\ket{x}=\ket{101}$, the function flips the state of the qubit $\ket{y}$, causing these two initial states to be tensored with $\ket{1}$ in the final state where originally they were tensored with $\ket{0}$.
 >
 > Since the rest of the basis states correspond to $f(x) = 0$, all other basis states in the initial superposition remain unchanged.
 
-Now you will implement the same function you've seen in the first two exercises as a marking oracle $U_{7,mark}$.
+Now you'll implement the same function you've seen in the first two exercises as a marking oracle $U_{7,mark}$.
 
 @[exercise]({
     "id": "oracles__marking_oracle_seven",
@@ -159,7 +159,7 @@ Now you will implement the same function you've seen in the first two exercises
 
 Previously we considered applying marking oracles when the register $\ket{x}$ was in a basis state or a superposition state, and the target qubit $\ket{y}$ was in a basis state.  How might the effect of applying marking oracles change if the target is also in a superposition state?  In this case we might observe **phase kickback** - the relative phase from the target qubit affecting ("kicked back" into) the state of the input qubits.
 
-In order to observe phase kickback, we use the target qubit $\ket{y}=\ket{-}$.
+In order to observe phase kickback, the target qubit is $\ket{y}=\ket{-}$.
 
 > This is the standard choice for two reasons.
 > First, for phase kickback to occur, the target qubit must have a difference in relative phase between the basis states $\ket{0}$ and $\ket{1}$.
@@ -188,12 +188,12 @@ $$= \frac{1}{\sqrt{2}} \big( U_{mark}\ket{b_1}\ket{-} + U_{mark}\ket{b_2}\ket{-}
 
 $$= \frac{1}{\sqrt{2}} \big( (-1)^{f(b_1)}\ket{b_1} + (-1)^{f(b_2)}\ket{b_2}\big) \ket{-}$$
 
-We see that in both cases applying $U_{mark}$ does not change the state of the target qubit, but it does change the state of the input register.
-Thus we can drop the target qubit without any repercussions after the application of the oracle.
+In both cases applying $U_{mark}$ doesn't change the state of the target qubit, but it does change the state of the input register.
+Thus you can drop the target qubit without any repercussions after the application of the oracle.
 Notice that the input register is now in the following state:
 $$\ket{\psi} = \frac{1}{\sqrt{2}} \big( (-1)^{f(b_1)}\ket{b_1} + (-1)^{f(b_2)}\ket{b_2}\big),$$
 
-which looks exactly as if we applied a phase oracle to $\ket{x}$ instead of applying a marking oracle to $\ket{x}\ket{-}$!  This is a very important application of phase kickback: it allows to convert a marking oracle into a phase oracle - which you will implement in the next task.
+which looks exactly as if you apply a phase oracle to $\ket{x}$ instead of applying a marking oracle to $\ket{x}\ket{-}$!  This is a very important application of phase kickback: it allows to convert a marking oracle into a phase oracle - which you'll implement in the next task.
 
 > Another important application of this effect is **phase estimation** algorithm, which allows to estimate an eigenvalue of an eigenvector.
 
@@ -211,7 +211,7 @@ $$= \frac{1}{2} \big( U_{7,mark}\ket{110}\ket{0} + U_{7,mark}\ket{111}\ket{0} -
 
 $$= \frac{1}{2} \big(\ket{110}\ket{0} + \ket{111}\ket{1} - \ket{110}\ket{1} - \ket{111}\ket{0} \big) := \ket{\xi}$$
 
-Now we would like to observe how our input state $\ket{\eta}$ was modified by the oracle.  Let's simplify the resulting state $\ket{\xi}$:
+Now you want to observe how your input state $\ket{\eta}$ is modified by the oracle.  Let's simplify the resulting state $\ket{\xi}$:
 
 $$\ket{\xi} = \frac{1}{2} \big(\ket{110}\ket{0} + \ket{111}\ket{1} - \ket{110}\ket{1} - \ket{111}\ket{0}\big) =$$
 
@@ -227,7 +227,7 @@ Finally, let's compare $\ket{\eta}$ and $\ket{\xi}$; below are the final equatio
 $$\ket{\eta} = \Big[\frac{1}{\sqrt{2}}\big(\ket{110} + \ket{111}\big)\Big] \otimes \ket{-}$$
 $$\ket{\xi} = \Big[\frac{1}{\sqrt{2}}\big(\ket{110} - \ket{111}\big)\Big] \otimes \ket{-}$$
 
-We can see that these two equations are identical, except for the $-1$ phase that appeared on the $\ket{111}$ basis state - our marked state.  This is a specific example of the phase kickback effect, as the phase from $\ket{-}$ has been *kicked back* into $\ket{x}$.
+You can see that these two equations are identical, except for the $-1$ phase that appeared on the $\ket{111}$ basis state - our marked state.  This is a specific example of the phase kickback effect, as the phase from $\ket{-}$ has been *kicked back* into $\ket{x}$.
 
 
 @[exercise]({
@@ -241,13 +241,13 @@ We can see that these two equations are identical, except for the $-1$ phase tha
     "title": "Converting Marking Oracles to Phase Oracles"
 })
 
-In this demo we will use a reference implementation of `ApplyMarkingOracleAsPhaseOracle` operation to convert marking oracle `IsSeven_MarkingOracle` to a phase oracle. Then we will compare this converted oracle to the reference implementation of the phase oracle `IsSeven_PhaseOracle`. You already implemented these oracles in the previous tasks.
+In this demo you'll use a reference implementation of `ApplyMarkingOracleAsPhaseOracle` operation to convert marking oracle `IsSeven_MarkingOracle` to a phase oracle. Then you'll compare this converted oracle to the reference implementation of the phase oracle `IsSeven_PhaseOracle`. You already implemented these oracles in the previous tasks.
 
 @[example]({"id": "oracles__oracle_converter_demo", "codePath": "./examples/OracleConverterDemo.qs"})
 
 > Notice from the above demo that your phase oracle $U_{7,phase}$ behaves the same as the converted version of your marking oracle $U_{7,mark}$, both of which induce a phase flip on the basis state $\ket{111}$!
 
-This way to convert a marking oracle to a phase oracle is useful because many quantum algorithms, such as Grover's search algorithm, rely on a phase oracle, but it is often easier to implement the function as a marking oracle.
+This way to convert a marking oracle to a phase oracle is useful because many quantum algorithms, such as Grover's search algorithm, rely on a phase oracle, but it's often easier to implement the function as a marking oracle.
 This converter provides a way to implement the function of interest as a marking oracle and then convert it into a phase oracle, which could then be leveraged in a quantum algorithm.
 
 @[section]({
@@ -255,10 +255,10 @@ This converter provides a way to implement the function of interest as a marking
     "title": "Implementing Quantum Oracles"
 })
 
-In this section you will implement a few more complicated quantum oracles.
+In this section you'll implement a few more complicated quantum oracles.
 Some of them - both phase and marking - can take extra "classical" parameters.
 A useful tool for implementing quantum oracles is allocating auxiliary qubits to assist in a computation. 
-You will practice that in some of the exercises below.
+You'll practice that in some of the exercises below.
 
 > Notice that the operation declarations below require adjoint and controlled variants of the oracle to be automatically generated. This is common practice that makes testing and reusing the code easier. Typically Q# compiler will easily generate these variants, as long as you don't use mutable variables or operations that don't support these functors.
 
@@ -303,13 +303,13 @@ You will practice that in some of the exercises below.
     "title": "Testing an Oracle Implementation"
 })
 
-In this demo we show how you could test an oracle that you've implemented for your own problem.
-For all of the previous oracles that you've implemented, we've been testing your oracle against a reference solution for that task.
-However, if you're designing an oracle for a new problem, you do not have a reference solution for it - if you did, there would be no point for you to implement the oracle in the first place!
+This demo shows how to test an oracle that you've implemented for your own problem.
+For all of the previous oracles that you've implemented, you've been testing your oracle against a reference solution for that task.
+However, if you're designing an oracle for a new problem, you don't have a reference solution for it - if you did, there'd be no point for you to implement the oracle in the first place!
 
 A good way to test a quantum oracle of interest is to write a classical oracle that performs the same computation classically, and then compare the effect of your quantum oracle on the basis states with the output of the classical oracle for every input (or a certain percentage of the inputs if you are constrained by runtime) to ensure that they match.
 
-Here we will compare the reference implementation of `Meeting_Oracle` to the classical code implementing the same function.
+Here you'll compare the reference implementation of `Meeting_Oracle` to the classical code implementing the same function.
 
 @[example]({"id": "oracles__test_meeting_oracle", "codePath": "./examples/TestMeetingOracle.qs"})
 
@@ -318,7 +318,7 @@ Here we will compare the reference implementation of `Meeting_Oracle` to the cla
     "title": "Conclusion"
 })
 
-Congratulations! In this kata you have learned to build quantum oracles. Here are a few key concepts to keep in mind:
+Congratulations! In this kata you've learned to build quantum oracles. Here are a few key concepts to keep in mind:
 
 - A quantum oracle is an "opaque box" operation that implements a classical computation. 
 - Quantum oracles are used to convert classical problems into inputs to quantum algorithms, such as Grover's search algorithm.
diff --git a/katas/content/oracles/kth_bit_oracle/index.md b/katas/content/oracles/kth_bit_oracle/index.md
index 8974623f..2efdb083 100644
--- a/katas/content/oracles/kth_bit_oracle/index.md
+++ b/katas/content/oracles/kth_bit_oracle/index.md
@@ -16,5 +16,5 @@ Flip the sign of the input state $\ket{x}$ if the $k$-th bit of $x$ is $1$.
 <br/>
 <details>
   <summary><b>Before implementing this oracle, answer the question: are you implementing a marking or a phase oracle?</b></summary>
-    This is a phase oracle, because we are changing the phase of the input state $\ket{x}$ based on the value of the function $f(x)$.
+    This is a phase oracle, because you're changing the phase of the input state $\ket{x}$ based on the value of the function $f(x)$.
 </details>
diff --git a/katas/content/oracles/kth_bit_oracle/solution.md b/katas/content/oracles/kth_bit_oracle/solution.md
index 60aebac4..8d6d4eab 100644
--- a/katas/content/oracles/kth_bit_oracle/solution.md
+++ b/katas/content/oracles/kth_bit_oracle/solution.md
@@ -1,5 +1,5 @@
-Since the effect of this oracle depends only on the value of the $k$-th qubit, we can ignore the rest of the qubits and focus on just `x[k]`.
-We need to flip the phase of the qubit if it is in the $\ket{1}$ state and leave it unchanged otherwise - this is exactly the effect of the $Z$ gate.
+Since the effect of this oracle depends only on the value of the $k$-th qubit, you can ignore the rest of the qubits and focus on just `x[k]`.
+You need to flip the phase of the qubit if it is in the $\ket{1}$ state and leave it unchanged otherwise - this is exactly the effect of the $Z$ gate.
 
 @[solution]({
     "id": "oracles__kth_bit_oracle_solution",
diff --git a/katas/content/oracles/marking_oracle_as_phase/solution.md b/katas/content/oracles/marking_oracle_as_phase/solution.md
index fd78c157..12e3ce9f 100644
--- a/katas/content/oracles/marking_oracle_as_phase/solution.md
+++ b/katas/content/oracles/marking_oracle_as_phase/solution.md
@@ -1,4 +1,4 @@
-As we just saw, we can allocate an additional qubit in the $\ket{-}$ state and use it as the target for our marking oracle.
+As you just saw, you can allocate an additional qubit in the $\ket{-}$ state and use it as the target for our marking oracle.
 This will kick back the $-1$ relative phase for the basis states $\ket{x}$ of the input register for which $f(x) = 1$.
 
 @[solution]({
diff --git a/katas/content/oracles/marking_oracle_seven/solution.md b/katas/content/oracles/marking_oracle_seven/solution.md
index ee4b908e..341ed88b 100644
--- a/katas/content/oracles/marking_oracle_seven/solution.md
+++ b/katas/content/oracles/marking_oracle_seven/solution.md
@@ -21,7 +21,7 @@ Now let's again apply this oracle to a superposition state $\ket{\alpha}$ such t
 $$\ket{\alpha} = \frac{1}{\sqrt{2}}\big(\ket{110} + \ket{111}\big)\ket{0} = 
 \ket{11} \otimes \frac{1}{\sqrt{2}} \big(\ket{0} + \ket{1}\big) \otimes \ket{0} = \ket{11+} \ket{0}$$
 
-Let's consider how our operator $U_{7,mark}$ acts on this state.
+Let's consider how the operator $U_{7,mark}$ acts on this state.
 
 > Recall that oracles are linear operators, thus they can be applied to each term individually.
 
@@ -29,15 +29,15 @@ $$U_{7,mark} \ket{\alpha} = \frac{1}{\sqrt{2}} \big(U_{7,mark}\ket{110} \ket{0}
 
 $$= \frac{1}{\sqrt{2}} \big(\ket{110} \ket{0} + \ket{111} \ket{1}\big) := \ket{\epsilon}$$
 
-Was our input state modified during this operation?  Let's simplify the resulting state $\ket{\epsilon}$:
+Was your input state modified during this operation?  Let's simplify the resulting state $\ket{\epsilon}$:
 
 $$\ket{\epsilon} = \frac{1}{\sqrt{2}} \big(\ket{110} \ket{0} + \ket{111} \ket{1}\big) = \ket{11} \otimes \frac{1}{\sqrt{2}} \big(\ket{0} \ket{0} + \ket{1} \ket{1}\big) =$$
 
 $$= \ket{11} \otimes \frac{1}{\sqrt{2}} \big(\ket{00} + \ket{11}\big) = \ket{11} \otimes \ket{\Phi^+} = \ket{11\Phi^+}$$
 
-We have entangled the states of qubits $\ket{x}$ and $\ket{y}$!  This is a common occurrence for marking oracles when the input is a superposition of basis states: after applying the oracle, the input $\ket{x}$ will often become entangled with $\ket{y}$. Thus, while applying the marking oracle to a basis state will leave the input array unchanged, applying the marking oracle to a superposition state will change the state of both the input array and the output qubit.
+You have entangled the states of qubits $\ket{x}$ and $\ket{y}$!  This is a common occurrence for marking oracles when the input is a superposition of basis states: after applying the oracle, the input $\ket{x}$ will often become entangled with $\ket{y}$. Thus, while applying the marking oracle to a basis state will leave the input array unchanged, applying the marking oracle to a superposition state will change the state of both the input array and the output qubit.
 
->As an exercise, what entangled state would we get in the previous example if $\ket{y} = \ket{1}$ instead of $\ket{y} = \ket{0}$?
+>As an exercise, what entangled state would you get in the previous example if $\ket{y} = \ket{1}$ instead of $\ket{y} = \ket{0}$?
 >
 > <details>
 >   <summary><b>Answer</b></summary>
diff --git a/katas/content/oracles/meeting_oracle/index.md b/katas/content/oracles/meeting_oracle/index.md
index 5397ce94..cf92fe0a 100644
--- a/katas/content/oracles/meeting_oracle/index.md
+++ b/katas/content/oracles/meeting_oracle/index.md
@@ -1,6 +1,6 @@
-Suppose that you would like to schedule a meeting with your co-worker Jasmine.
+Suppose that you'd like to schedule a meeting with your co-worker Jasmine.
 You both work five day workweeks, and $\ket{x}$ and $\ket{jasmine}$ are 5-bit states representing your and Jasmine's schedules.
-The schedules are indicators of a person being busy on that day: a $1$ bit means that person is busy on that day, and $0$ means they're free for a meeting that day. Implement a function that determines if you and Jasmine can schedule a meeting during the week, i.e., whether there is a day when both schedules have a $0$ simultaneously.
+The schedules are indicators of a person being busy on that day: a $1$ bit means that person is busy on that day, and $0$ means they're free for a meeting that day. Implement a function that determines if you and Jasmine can schedule a meeting during the week, that is, whether there is a day when both schedules have a $0$ simultaneously.
 
 **Inputs:**
 
@@ -22,5 +22,5 @@ Flip the state of $\ket{y}$ if you and Jasmine are both free on the same day for
 <br/>
 <details>
   <summary><b>Before implementing this oracle, answer the question: are you implementing a marking or a phase oracle?</b></summary>
-    This is a marking oracle, because we are flipping the state of the target qubit $\ket{y}$ based on the state of the inputs $\ket{x}$ and $\ket{jasmine}$. Notice that even though we do not have the typical single-input-register situation that we saw earlier, this is still a marking oracle.
+    This is a marking oracle, because you're flipping the state of the target qubit $\ket{y}$ based on the state of the inputs $\ket{x}$ and $\ket{jasmine}$. Notice that even though you don't have the typical single-input-register situation that we saw earlier, this is still a marking oracle.
 </details>
diff --git a/katas/content/oracles/meeting_oracle/solution.md b/katas/content/oracles/meeting_oracle/solution.md
index 61f9e893..4b368870 100644
--- a/katas/content/oracles/meeting_oracle/solution.md
+++ b/katas/content/oracles/meeting_oracle/solution.md
@@ -1,9 +1,9 @@
-In this problem we'll need to allocate extra qubits, one per each day of the week, to serve as temporary variables.
+In this problem you'll need to allocate extra qubits, one per each day of the week, to serve as temporary variables.
 Each of these variables will track whether both people are free on that day, that is, whether both input qubit arrays 
-have bit $0$ in the corresponding position. We can compute each of these variables using a $CCNOT$ gate after flipping 
+have bit $0$ in the corresponding position. You can compute each of these variables using a $CCNOT$ gate after flipping 
 the states of both qubits using $X$ gates. 
 
-After this, we can reuse the marking OR oracle to check whether any of the days are in $\ket{1}$ state.
+After this, you can reuse the marking OR oracle to check whether any of the days are in $\ket{1}$ state.
 
 When you allocate qubits for temporary use during a larger computation, you need to make sure they are returned to the $\ket{0}$ 
 state before being released. Unlike your previous experience allocating qubits in demos, though, this time you cannot 
diff --git a/katas/content/oracles/or_but_kth_oracle/index.md b/katas/content/oracles/or_but_kth_oracle/index.md
index 59bba2bb..58bea031 100644
--- a/katas/content/oracles/or_but_kth_oracle/index.md
+++ b/katas/content/oracles/or_but_kth_oracle/index.md
@@ -5,7 +5,7 @@
 
 **Goal:**
 
-Flip the sign of the basis state $\ket{x}$ if any of the bits of $x$ (not considering the $k$-th bit) are $1$ in the input register. In other words, the input register with the $k$-th qubit excluded should not be in the all zero state to flip the sign of the input register. The state of the $k$-th qubit does not affect the result.
+Flip the sign of the basis state $\ket{x}$ if any of the bits of $x$ (not considering the $k$-th bit) are $1$ in the input register. In other words, the input register with the $k$-th qubit excluded shouldn't be in the all zero state to flip the sign of the input register. The state of the $k$-th qubit does not affect the result.
 
 *Feel free to explore implementing this operation with or without auxiliary qubits.*
 
@@ -16,7 +16,7 @@ Flip the sign of the basis state $\ket{x}$ if any of the bits of $x$ (not consid
 
 <details>
   <summary><b>Before implementing this oracle, answer the question: are you implementing a marking or a phase oracle?</b></summary>
-    This is a phase oracle, because we are changing the phase of the input state $\ket{x}$ based on the value of the function $f(x)$.
+    This is a phase oracle, because you're changing the phase of the input state $\ket{x}$ based on the value of the function $f(x)$.
 </details>
 
 <details>
diff --git a/katas/content/oracles/or_oracle/index.md b/katas/content/oracles/or_oracle/index.md
index b3cafdd1..23c18421 100644
--- a/katas/content/oracles/or_oracle/index.md
+++ b/katas/content/oracles/or_oracle/index.md
@@ -16,7 +16,7 @@ except for $\ket{00...0}$ (the all zero state).
 <br/>
 <details>
   <summary><b>Before implementing this oracle, answer the question: are you implementing a marking or a phase oracle?</b></summary>
-    This is a marking oracle, because we are flipping the state of the target qubit $\ket{y}$ based on the state of the input $\ket{x}$.
+    This is a marking oracle, because you're flipping the state of the target qubit $\ket{y}$ based on the state of the input $\ket{x}$.
 </details>
 
 <br/>
diff --git a/katas/content/oracles/or_oracle/solution.md b/katas/content/oracles/or_oracle/solution.md
index ad00b4e9..2e387feb 100644
--- a/katas/content/oracles/or_oracle/solution.md
+++ b/katas/content/oracles/or_oracle/solution.md
@@ -1,10 +1,10 @@
-What if you needed to flip the state of the target qubit only if the input register was in the $\ket{0...0}$ state?
+What if you needed to flip the state of the target qubit only if the input register is in the $\ket{0...0}$ state?
 In that case, you could use `ApplyControlledOnInt` with the control pattern $0$ that corresponds to all control qubits in $\ket{0}$ state.
 
 In this problem, you need to separate the $\ket{0...0}$ basis state from all others, but with the opposite effect on the target qubit: instead of flipping it for this input state, you need to flip it for all other input states.
 Or, you can think of it as first flipping the state of the target qubit for all states, and then un-flipping it (or flipping it again) for just this basis state. You can do this by applying the $X$ gate before or after `ApplyControlledOnInt`.
 
-Notice that you can modify the state of the input register during your computations (this is what `ApplyControlledOnInt` function does under the hood). However, it is essential to undo those modifications ("uncompute" the changes), except the final one, so that the oracle will preserve the input if it is a basis state.
+Notice that you can modify the state of the input register during your computations (this is what `ApplyControlledOnInt` function does under the hood). However, it's essential to undo those modifications ("uncompute" the changes), except the final one, so that the oracle will preserve the input if it's a basis state.
 
 @[solution]({
     "id": "oracles__or_oracle_solution",
diff --git a/katas/content/oracles/phase_oracle_seven/solution.md b/katas/content/oracles/phase_oracle_seven/solution.md
index b343b1df..c2dc87a0 100644
--- a/katas/content/oracles/phase_oracle_seven/solution.md
+++ b/katas/content/oracles/phase_oracle_seven/solution.md
@@ -9,14 +9,14 @@ Consider how this oracle acts on two basis states:
 $$U_{7,phase} \ket{111} = -\ket{111}$$
 $$U_{7,phase} \ket{110} = \ket{110}$$
 
-You can see that $U_{7,phase}$ does not change the input if it's a basis state (other than adding a global phase), and $U_{7,phase}$ does not change the norm of the state ($U_{7,phase}$ is a unitary operator).  
+You can see that $U_{7,phase}$ doesn't change the input if it's a basis state (other than adding a global phase), and $U_{7,phase}$ doesn't change the norm of the state ($U_{7,phase}$ is a unitary operator).  
 
-However, if we applied this oracle to a superposition state instead, what will that look like?
+However, if you applied this oracle to a superposition state instead, what will that look like?
 
 Suppose that $\ket{\beta}$ is an equal superposition of the states  $\ket{110}$ and $\ket{111}$: 
 $$\ket{\beta} = \frac{1}{\sqrt{2}} \big(\ket{110} + \ket{111}\big) = \ket{11} \otimes \frac{1}{\sqrt{2}} \big(\ket{0} + \ket{1}\big) = \ket{11} \otimes \ket{+} = \ket{11+}$$
 
-Let's consider how our operator $U_{7,phase}$ acts on this state:
+Let's consider how the operator $U_{7,phase}$ acts on this state:
 
 $$U_{7,phase} \ket{\beta} = U_{7,phase} \Big[\frac{1}{\sqrt{2}} \big(\ket{110} + \ket{111}\big)\Big] =$$
 
@@ -24,7 +24,7 @@ $$= \frac{1}{\sqrt{2}} \big(U_{7,phase} \ket{110} + U_{7,phase} \ket{111}\big) =
 
 $$= \frac{1}{\sqrt{2}} \big(\ket{110} - \ket{111}\big) := \ket{\gamma}$$
 
-Was our input state modified during this operation? Let's simplify $\ket{\gamma}$:
+Was your input state modified during this operation? Let's simplify $\ket{\gamma}$:
 
 $$\ket{\gamma} = \frac{1}{\sqrt{2}} \big(\ket{110} - \ket{111}\big) =$$
 
@@ -32,8 +32,8 @@ $$= \ket{11} \otimes \frac{1}{\sqrt{2}} \big(\ket{0} - \ket{1}\big) =$$
 
 $$= \ket{11} \otimes \ket{-} = \ket{11-} \neq \ket{\beta}$$
 
-Here we see that the oracle modifies the input, if the input state was a *superposition* of the basis states, as a phase oracle will only modify the sign of the basis states.  Thus when a superposition state is provided as input to an oracle, the input state can be modified via the application of the quantum oracle.
+Here you see that the oracle modifies the input, if the input state is a *superposition* of the basis states, as a phase oracle will only modify the sign of the basis states.  Thus when a superposition state is provided as input to an oracle, the input state can be modified via the application of the quantum oracle.
 
-> It is also worth noting that while the oracle modified the input when provided a superposition state, it did *not* modify the norm of that state.  As an exercise, you can verify this yourself by taking the norm of $\ket{\beta}$ and $\ket{\gamma}$, which both will result in a value of $1$.
+> It's also worth noting that while the oracle modified the input when provided a superposition state, it did *not* modify the norm of that state.  As an exercise, you can verify this yourself by taking the norm of $\ket{\beta}$ and $\ket{\gamma}$, which both will result in a value of $1$.
 >
-> As another exercise, consider how you could distinguish between the input and output state programmatically?  Is there an operation that you could apply to the initial state $\ket{\beta}$ and the final state $\ket{\gamma}$ to show that the two states are not equivalent through measurement?  As a hint, think about how you could convert the superposition states $\ket{\beta}$ and $\ket{\gamma}$ into the basis states.
+> As another exercise, consider how you could distinguish between the input and output state programmatically?  Is there an operation that you could apply to the initial state $\ket{\beta}$ and the final state $\ket{\gamma}$ to show that the two states aren't equivalent through measurement?  As a hint, think about how you could convert the superposition states $\ket{\beta}$ and $\ket{\gamma}$ into the basis states.
diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs
index 70e672a9..552ae74b 100644
--- a/language_service/src/compilation.rs
+++ b/language_service/src/compilation.rs
@@ -450,11 +450,38 @@ impl Lookup for Compilation {
             .get(package_id)
             .expect("package should exist in store")
             .package;
-        (
-            package
+
+        let mut item: &hir::Item = package
+            .items
+            .get(item_id.item)
+            .expect("item id should exist");
+
+        // follow chain of exports, if it is an aexport
+        while let hir::ItemKind::Export(
+            _,
+            hir::ItemId {
+                package: package_id,
+                item: local_item_id,
+            },
+        ) = &item.kind
+        {
+            let package: &hir::Package = if let Some(id) = package_id {
+                &self
+                    .package_store
+                    .get(*id)
+                    .expect("package should exist in store")
+                    .package
+            } else {
+                package
+            };
+
+            item = package
                 .items
-                .get(item_id.item)
-                .expect("item id should exist"),
+                .get(*local_item_id)
+                .expect("exported item should exist");
+        }
+        (
+            item,
             package,
             hir::ItemId {
                 package: Some(package_id),
diff --git a/language_service/src/completion.rs b/language_service/src/completion.rs
index d3387a5b..4a65207f 100644
--- a/language_service/src/completion.rs
+++ b/language_service/src/completion.rs
@@ -646,6 +646,8 @@ fn local_completion(
                         CompletionItemKind::Interface,
                     )
                 }
+                // We don't want completions for items exported from the local scope
+                ItemKind::Export(_, _) => return None,
             };
             (kind, detail)
         }
diff --git a/language_service/src/name_locator.rs b/language_service/src/name_locator.rs
index de414d65..4b486fa4 100644
--- a/language_service/src/name_locator.rs
+++ b/language_service/src/name_locator.rs
@@ -447,6 +447,9 @@ impl<'inner, 'package, T: Handler<'package>> Visitor<'package> for Locator<'inne
                                     path.id
                                 )
                             }
+                            hir::ItemKind::Export(_, _) => {
+                                unreachable!("handled in fn resolve_item_relative_to_user_package")
+                            }
                         }
                     }
                     Some(resolve::Res::Local(node_id)) => {
diff --git a/language_service/src/references.rs b/language_service/src/references.rs
index fb1c7f41..46e4c319 100644
--- a/language_service/src/references.rs
+++ b/language_service/src/references.rs
@@ -203,7 +203,7 @@ impl<'a> ReferenceFinder<'a> {
             let def_span = match &def.kind {
                 hir::ItemKind::Callable(decl) => decl.name.span,
                 hir::ItemKind::Namespace(name, _) => name.span(),
-                hir::ItemKind::Ty(name, _) => name.span,
+                hir::ItemKind::Ty(name, _) | hir::ItemKind::Export(name, _) => name.span,
             };
             locations.push(
                 self.location(
diff --git a/library/qs_source/src/std/modern_api.qs b/library/qs_source/src/std/modern_api.qs
new file mode 100644
index 00000000..f647e11a
--- /dev/null
+++ b/library/qs_source/src/std/modern_api.qs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+
+// This file re-exports the standard library under the name `Std`, which will be the preferred standard library API going forward.
+
+namespace Std {
+    export
+        Microsoft.Quantum.Arrays,
+        Microsoft.Quantum.Canon,
+        Microsoft.Quantum.Convert,
+        Microsoft.Quantum.Core,
+        Microsoft.Quantum.Diagnostics,
+        Microsoft.Quantum.Logical,
+        Microsoft.Quantum.Intrinsic,
+        Microsoft.Quantum.Math,
+        Microsoft.Quantum.Measurement,
+        Microsoft.Quantum.Random,
+        Microsoft.Quantum.ResourceEstimation,
+        Microsoft.Quantum.Unstable;
+}
diff --git a/library/src/lib.rs b/library/src/lib.rs
index 15a60da3..de58fef9 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -89,4 +89,8 @@ pub const STD_LIB: &[(&str, &str)] = &[
         "qsharp-library-source:unstable_table_lookup.qs",
         include_str!("../qs_source/src/std/unstable_table_lookup.qs"),
     ),
+    (
+        "qsharp-library-source:modern_api.qs",
+        include_str!("../qs_source/src/std/modern_api.qs"),
+    ),
 ];
diff --git a/noisy_simulator/Cargo.toml b/noisy_simulator/Cargo.toml
index 2f6b6fed..c7ce99f3 100644
--- a/noisy_simulator/Cargo.toml
+++ b/noisy_simulator/Cargo.toml
@@ -14,3 +14,16 @@ nalgebra = { workspace = true }
 num-complex = { workspace = true }
 rand = { workspace = true }
 thiserror = { workspace = true }
+
+[dev-dependencies]
+criterion = { workspace = true, features = ["cargo_bench_support"]}
+
+[lints]
+workspace = true
+
+[lib]
+bench = false
+
+[[bench]]
+name = "noisy_simulator"
+harness = false
diff --git a/noisy_simulator/benches/noisy_simulator.rs b/noisy_simulator/benches/noisy_simulator.rs
new file mode 100644
index 00000000..81790df4
--- /dev/null
+++ b/noisy_simulator/benches/noisy_simulator.rs
@@ -0,0 +1,89 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+use std::time::Duration;
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use nalgebra::dmatrix;
+use noisy_simulator::{
+    DensityMatrixSimulator, Error, NoisySimulator, Operation, StateVectorSimulator,
+};
+
+fn ten_qubits_ten_operations<NS: NoisySimulator>() -> Result<(), Error> {
+    let x_gate = Operation::new(vec![
+        dmatrix![
+            0.974_679_43.into(), 0.0.into();
+            0.0.into(), 0.974_679_43.into();
+        ],
+        dmatrix![
+            0.0.into(), 0.223_606_8.into();
+            2.236_067_98e-01.into(), (-4.965_068_31e-17).into();
+        ],
+    ])?;
+
+    let swap_gate = Operation::new(vec![
+        dmatrix![
+            (-9.486_832_98e-01).into(),( 1.490_606_84e-17).into(),( 0.000_000_00e+00).into(),( 9.676_169_54e-34).into();
+            (-4.680_505_48e-16).into(),(-9.486_832_98e-01).into(),( 1.966_320_58e-35).into(),(-2.169_220_51e-33).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),(-9.000_000_00e-01).into(),( 1.414_113_81e-17).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),(-1.414_113_81e-17).into(),(-9.000_000_00e-01).into();
+        ],
+        dmatrix![
+            ( 4.024_638_47e-16).into(),( 3.162_277_66e-01).into(),( 0.000_000_00e+00).into(),( 2.052_770_32e-17).into();
+            (-3.162_277_66e-01).into(),(-1.882_065_45e-16).into(),( 4.171_490_09e-19).into(),(-4.601_936_18e-17).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),(-1.240_452_23e-16).into(),( 3.000_000_00e-01).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),(-3.000_000_00e-01).into(),(-1.240_452_23e-16).into();
+        ],
+        dmatrix![
+            ( 0.000_000_00e+00).into(),( 2.764_311_39e-18).into(),( 3.000_000_00e-01).into(),(-1.078_211_93e-16).into();
+            ( 9.544_580_26e-18).into(),( 2.573_665_38e-18).into(),( 7.727_798_82e-18).into(),( 3.000_000_00e-01).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),(-1.182_297_27e-17).into(),( 6.864_964_58e-18).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),(-8.363_581_31e-19).into(),(-1.182_297_27e-17).into();
+        ],
+        dmatrix![
+            (-0.000_000_00e+00).into(),( 7.412_309_43e-17).into(),( 1.815_419_41e-17).into(),(-1.000_000_00e-01).into();
+            ( 1.124_874_17e-17).into(),(-2.225_607_46e-17).into(),( 1.000_000_00e-01).into(),(-3.937_497_49e-17).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),( 3.668_529_08e-18).into(),(-9.049_669_92e-17).into();
+            ( 0.000_000_00e+00).into(),( 0.000_000_00e+00).into(),(-1.838_421_17e-17).into(),( 3.668_529_08e-18).into();
+        ],
+    ])?;
+
+    let mut sim = NS::new_with_seed(10, 42);
+    sim.apply_operation(&x_gate, &[0])?;
+    sim.apply_operation(&swap_gate, &[0, 1])?;
+    sim.apply_operation(&swap_gate, &[1, 2])?;
+    sim.apply_operation(&swap_gate, &[2, 3])?;
+    sim.apply_operation(&swap_gate, &[3, 4])?;
+    sim.apply_operation(&swap_gate, &[4, 5])?;
+    sim.apply_operation(&swap_gate, &[5, 6])?;
+    sim.apply_operation(&swap_gate, &[6, 7])?;
+    sim.apply_operation(&swap_gate, &[7, 8])?;
+    sim.apply_operation(&swap_gate, &[8, 9])?;
+
+    Ok(())
+}
+
+pub fn density_matrix_simulator(c: &mut Criterion) {
+    c.benchmark_group("density_matrix_simulator")
+        .measurement_time(Duration::from_secs(25))
+        .bench_function("10 qubits and 10 operations circuit", |b| {
+            b.iter(|| {
+                ten_qubits_ten_operations::<DensityMatrixSimulator>()
+                    .expect("bench should succeed");
+            });
+        });
+}
+
+pub fn state_vector_simulator(c: &mut Criterion) {
+    c.benchmark_group("state_vector_simulator").bench_function(
+        "10 qubits and 10 operations circuit",
+        |b| {
+            b.iter(|| {
+                ten_qubits_ten_operations::<StateVectorSimulator>().expect("bench should succeed");
+            });
+        },
+    );
+}
+
+criterion_group!(benches, density_matrix_simulator, state_vector_simulator);
+criterion_main!(benches);
diff --git a/noisy_simulator/src/density_matrix_simulator.rs b/noisy_simulator/src/density_matrix_simulator.rs
index ea9966f4..5b42392c 100644
--- a/noisy_simulator/src/density_matrix_simulator.rs
+++ b/noisy_simulator/src/density_matrix_simulator.rs
@@ -43,7 +43,7 @@ impl DensityMatrix {
     /// Builds a `DensityMatrix` from its raw fields. Returns `None` if
     ///  the provided args don't represent a valid `DensityMatrix`.
     ///
-    /// This method is to be used from the PyO3 wrapper.
+    /// This method is to be used from the `PyO3` wrapper.
     pub fn try_from(
         dimension: usize,
         number_of_qubits: usize,
@@ -76,16 +76,19 @@ impl DensityMatrix {
     }
 
     /// Returns a reference to the vector containing the density matrix's data.
+    #[must_use]
     pub fn data(&self) -> &ComplexVector {
         &self.data
     }
 
     /// Returns dimension of the matrix. E.g.: If the matrix is 5 x 5, then dimension is 5.
+    #[must_use]
     pub fn dimension(&self) -> usize {
         self.dimension
     }
 
     /// Returns the number of qubits in the system.
+    #[must_use]
     pub fn number_of_qubits(&self) -> usize {
         self.number_of_qubits
     }
@@ -128,6 +131,7 @@ impl DensityMatrix {
     /// Return theoretical change in trace due to operations that have been applied so far.
     /// In reality, the density matrix is always renormalized after instruments / operations
     /// have been applied.
+    #[must_use]
     pub fn trace_change(&self) -> f64 {
         self.trace_change
     }
diff --git a/noisy_simulator/src/instrument.rs b/noisy_simulator/src/instrument.rs
index b02c5c22..1e842a87 100644
--- a/noisy_simulator/src/instrument.rs
+++ b/noisy_simulator/src/instrument.rs
@@ -41,8 +41,8 @@ impl Instrument {
             ));
         }
 
-        let summed_operation: SquareMatrix = operations.iter().map(|op| op.matrix()).sum();
-        let summed_effect: SquareMatrix = operations.iter().map(|op| op.effect_matrix()).sum();
+        let summed_operation: SquareMatrix = operations.iter().map(Operation::matrix).sum();
+        let summed_effect: SquareMatrix = operations.iter().map(Operation::effect_matrix).sum();
         let summed_effect_transpose = summed_effect.transpose();
         let summed_kraus_operators = summed_kraus_operators(&operations)?;
 
@@ -56,35 +56,41 @@ impl Instrument {
     }
 
     /// Return operation corresponding to the i-th outcome.
+    #[must_use]
     pub fn operation(&self, i: usize) -> &Operation {
         &self.operations[i]
     }
 
     /// Return the matrix corresponding to the sum over all
     /// operations in this instrument:
-    /// $$ \sum_i \sum_k K_{ik} \otimes (K_{ik})* $$
-    /// where \otimes denotes the Kronecker product
+    /// Σᵢ Σₖ (Kᵢₖ ⊗ Kᵢₖ*)
+    /// where ⊗ denotes the Kronecker product
     /// and * denotes the complex conjugate (entry-wise).
+    #[must_use]
     pub fn non_selective_operation_matrix(&self) -> &SquareMatrix {
         &self.summed_operation
     }
 
     /// Return Kraus operators of the operation corresponding to non selective evolution.
+    #[must_use]
     pub fn non_selective_kraus_operators(&self) -> &Vec<SquareMatrix> {
         &self.summed_kraus_operators
     }
 
-    /// Return total effect $(\sum_i \sum_k K_{ik}^\dagger K_{ik})$.
+    /// Return total effect Σᵢ Σₖ (Kᵢₖ† Kᵢₖ), where † denotes the adjoint.
+    #[must_use]
     pub fn total_effect(&self) -> &SquareMatrix {
         &self.summed_effect
     }
 
-    /// Return transposed total effect $(\sum_i \sum_k K_{ik}^\dagger K_{ik})^T$.
+    /// Return transposed total effect Σᵢ Σₖ (Kᵢₖ† Kᵢₖ)^T, where † denotes the adjoint.
+    #[must_use]
     pub fn total_effect_transposed(&self) -> &SquareMatrix {
         &self.summed_effect_transpose
     }
 
     /// Return number of operations/outcomes in this instrument.
+    #[must_use]
     pub fn num_operations(&self) -> usize {
         self.operations.len()
     }
@@ -102,7 +108,9 @@ fn summed_kraus_operators(operations: &[Operation]) -> Result<Vec<SquareMatrix>,
                 .iter()
                 .map(|k| {
                     // This code is doing the equivalent to:
-                    // choi_matrix += vec(K) * vec(K)^dagger
+                    // choi_matrix += vectorized(K) * vectorized(K).adjoint()
+                    // Note that if you multiply a column vector times a row
+                    // vector you get a matrix.
                     let dim = k.shape().0.pow(2);
                     let mut choi = SquareMatrix::zeros(dim, dim);
                     for row in 0..dim {
diff --git a/noisy_simulator/src/kernel.rs b/noisy_simulator/src/kernel.rs
index a545e607..1fa212a3 100644
--- a/noisy_simulator/src/kernel.rs
+++ b/noisy_simulator/src/kernel.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-//! This module contains the apply_kernel function used by the `DensityMatrixSimualtor`
+//! This module contains the `apply_kernel` function used by the `DensityMatrixSimualtor`
 //! and the `TrajectorySimulator`.
 
 use crate::{ComplexVector, Error, SquareMatrix};
diff --git a/noisy_simulator/src/lib.rs b/noisy_simulator/src/lib.rs
index f6549fbc..7d3e8504 100644
--- a/noisy_simulator/src/lib.rs
+++ b/noisy_simulator/src/lib.rs
@@ -12,15 +12,15 @@
 //! of the system as a whole, so you only need to run the simulation once, and take all
 //! the samples you need from the final density matrix. However, it is more memory intensive.
 //!
-//! A density matrix has 2 ^ (2 * number_of_qubits) complex numbers entries.
+//! A density matrix has 2 ^ (2 * `number_of_qubits`) complex numbers entries.
 //! If each complex number is represented as two 64-bits floating point numbers,
-//! the density matrix will be 2 ^ (2 * number_of_qubits) * 16 bytes. E.g., a density
+//! the density matrix will be 2 ^ (2 * `number_of_qubits`) * 16 bytes. E.g., a density
 //! matrix representing a 20 qubits system will be 17592186044416 bytes, or 16.4 TB.
 //!
 //! # State vector simulator
-//! The state vector simulator allocates less memory, however if you want 1_000_000 shots
-//! of the circuit, you need to run the simulation 1_000_000 times.
-//! A state vector has 2 ^ (number_of_qubits) complex entries. So, a state vector of a 20
+//! The state vector simulator allocates less memory, however if you want 1,000,000 shots
+//! of the circuit, you need to run the simulation 1,000,000 times.
+//! A state vector has 2 ^ (`number_of_qubits`) complex entries. So, a state vector of a 20
 //! qubits system will be 16777216 bytes, or 16 MB.
 //!
 //! # Which one should I use?
diff --git a/noisy_simulator/src/operation.rs b/noisy_simulator/src/operation.rs
index e5972c4c..2c8e7139 100644
--- a/noisy_simulator/src/operation.rs
+++ b/noisy_simulator/src/operation.rs
@@ -60,14 +60,14 @@ impl Operation {
             ))?
             .shape();
 
-        let number_of_qubits = (dim as f64).log2() as usize;
+        let number_of_qubits = dim.ilog2() as usize;
         if 1 << number_of_qubits != dim {
             return Err(Error::FailedToConstructOperation(
                 "kraus operators should have dimensions 2^k x 2^k".to_string(),
             ));
         }
 
-        for kraus_operator in kraus_operators.iter() {
+        for kraus_operator in &kraus_operators {
             let (rows, cols) = kraus_operator.shape();
             if rows != dim || cols != dim {
                 return Err(Error::FailedToConstructOperation(
@@ -96,30 +96,38 @@ impl Operation {
     }
 
     /// Return matrix representation:
-    /// $$ \sum_i K_i \otimes K_{i}* $$
-    /// where $K_i$ are Kraus operators.
+    /// Σᵢ (Kᵢ ⊗ Kᵢ*)
+    /// where Kᵢ are Kraus operators, ⊗ is the Kronecker product
+    /// and * denotes the complex conjugate of the matrix.
+    #[must_use]
     pub fn matrix(&self) -> &SquareMatrix {
         &self.operation_matrix
     }
 
     /// Returns effect matrix:
-    /// $$ (\sum_i K_i^{\dagger} K_i) $$
+    /// Σᵢ (Kᵢ Kᵢ†)
+    /// where Kᵢ are Kraus operators and † denotes the adjoint of the matrix.
+    #[must_use]
     pub fn effect_matrix(&self) -> &SquareMatrix {
         &self.effect_matrix
     }
 
     /// Return transpose of effect matrix:
-    /// $$ (\sum_i K_i^{\dagger} K_i)^T $$
+    /// Σᵢ (Kᵢ Kᵢ†)^T
+    /// where Kᵢ are Kraus operators and † denotes the adjoint of the matrix.
+    #[must_use]
     pub fn effect_matrix_transpose(&self) -> &SquareMatrix {
         &self.effect_matrix_transpose
     }
 
     /// Return list of Kraus operators.
+    #[must_use]
     pub fn kraus_operators(&self) -> &Vec<SquareMatrix> {
         &self.kraus_operators
     }
 
     /// Return the number of qubits that the operation acts on.
+    #[must_use]
     pub fn number_of_qubits(&self) -> usize {
         self.number_of_qubits
     }
diff --git a/noisy_simulator/src/state_vector_simulator.rs b/noisy_simulator/src/state_vector_simulator.rs
index 45d4f947..f26c14f0 100644
--- a/noisy_simulator/src/state_vector_simulator.rs
+++ b/noisy_simulator/src/state_vector_simulator.rs
@@ -42,7 +42,7 @@ impl StateVector {
     /// Builds a `StateVector` from its raw fields. Returns `None` if
     ///  the provided args don't represent a valid `StateVector`.
     ///
-    /// This method is to be used from the PyO3 wrapper.
+    /// This method is to be used from the `PyO3` wrapper.
     pub fn try_from(
         dimension: usize,
         number_of_qubits: usize,
@@ -55,7 +55,7 @@ impl StateVector {
             )));
         }
 
-        if data.len() != dimension * dimension {
+        if data.len() != dimension {
             return Err(Error::StateVectorTryFromError(format!(
                 "state vector dimension is {} but data has {} entries, {} != {}",
                 dimension,
@@ -74,16 +74,19 @@ impl StateVector {
     }
 
     /// Returns a reference to the vector containing the density matrix's data.
+    #[must_use]
     pub fn data(&self) -> &ComplexVector {
         &self.data
     }
 
     /// Returns dimension of the matrix. E.g.: If the matrix is 5 x 5, then dimension is 5.
+    #[must_use]
     pub fn dimension(&self) -> usize {
         self.dimension
     }
 
     /// Returns the number of qubits in the system.
+    #[must_use]
     pub fn number_of_qubits(&self) -> usize {
         self.number_of_qubits
     }
@@ -101,6 +104,7 @@ impl StateVector {
     /// Return theoretical change in trace due to operations that have been applied so far.
     /// In reality, the density matrix is always renormalized after instruments / operations
     /// have been applied.
+    #[must_use]
     pub fn trace_change(&self) -> f64 {
         self.trace_change
     }
diff --git a/pip/qsharp/noisy_simulator/_noisy_simulator.pyi b/pip/qsharp/noisy_simulator/_noisy_simulator.pyi
index 5fedd0ef..c5761e9d 100644
--- a/pip/qsharp/noisy_simulator/_noisy_simulator.pyi
+++ b/pip/qsharp/noisy_simulator/_noisy_simulator.pyi
@@ -1,7 +1,7 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from typing import Optional, List
+from typing import Optional, List, Any
 
 class NoisySimulatorError(BaseException):
     """
@@ -20,11 +20,14 @@ class Operation:
     transformation that maps a valid density matrix to another valid density matrices.
     """
 
-    def __init__(self, kraus_operators: List[List[List[complex]]]) -> None:
+    def __init__(self, kraus_operators: Any) -> None:
         """
         Construct an operation from a list of Kraus operators.
         Matrices must be of dimension 2^k x 2^k, where k is an integer.
         Raises a `NoisySimulatorError` if the Kraus matrices are ill formed.
+
+        Input:
+            kraus_operators: List[List[List[complex]]], can be a Python list or a numpy array.
         """
         ...
 
diff --git a/vscode/src/projectSystem.ts b/vscode/src/projectSystem.ts
index 4c5ed39b..85687c11 100644
--- a/vscode/src/projectSystem.ts
+++ b/vscode/src/projectSystem.ts
@@ -11,6 +11,7 @@ import {
 import * as vscode from "vscode";
 import { URI, Utils } from "vscode-uri";
 import { invokeAndReportCommandDiagnostics } from "./diagnostics";
+import { sendTelemetryEvent, EventType } from "./telemetry";
 
 /** Returns the manifest document if one is found
  * returns null otherwise
@@ -247,6 +248,14 @@ export async function fetchGithubRaw(
   const uri = `${githubEndpoint}/${owner}/${repo}/${ref}/${pathNoLeadingSlash}`;
   log.info(`making request to ${uri}`);
   const response = await fetch(uri);
+  // note that if the above fetch fails, we will never send this telemetry event.
+  // however, this is okay, because if a network request to github is failing, it is likely
+  // that the user's network itself is suspect and the telemetry wouldn't send anyway.
+  sendTelemetryEvent(
+    EventType.FetchGitHub,
+    { status: response.status.toString() },
+    {},
+  );
   if (!response.ok) {
     log.warn(
       `fetchGithubRaw: ${owner}/${repo}/${ref}/${path} -> ${response.status} ${response.statusText}`,
diff --git a/vscode/src/telemetry.ts b/vscode/src/telemetry.ts
index be2c4f65..f119253c 100644
--- a/vscode/src/telemetry.ts
+++ b/vscode/src/telemetry.ts
@@ -43,6 +43,7 @@ export enum EventType {
   FormatStart = "Qsharp.FormatStart",
   FormatEnd = "Qsharp.FormatEnd",
   CreateProject = "Qsharp.CreateProject",
+  FetchGitHub = "Qsharp.FetchGitHub",
   TriggerCircuit = "Qsharp.TriggerCircuit",
   CircuitStart = "Qsharp.CircuitStart",
   CircuitEnd = "Qsharp.CircuitEnd",
@@ -224,6 +225,10 @@ type EventTypes = {
     properties: Empty;
     measurements: Empty;
   };
+  [EventType.FetchGitHub]: {
+    properties: { status: string };
+    measurements: Empty;
+  };
   [EventType.TriggerCircuit]: {
     properties: {
       associationId: string;


Test patch:
diff --git a/compiler/qsc/src/interpret/package_tests.rs b/compiler/qsc/src/interpret/package_tests.rs
new file mode 100644
index 00000000..e4539015
--- /dev/null
+++ b/compiler/qsc/src/interpret/package_tests.rs
@@ -0,0 +1,172 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#![allow(clippy::needless_raw_string_hashes)]
+
+use crate::{interpret::Interpreter, packages::BuildableProgram};
+use indoc::indoc;
+use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
+use qsc_eval::output::CursorReceiver;
+use qsc_frontend::compile::SourceMap;
+use qsc_passes::PackageType;
+use qsc_project::{PackageGraphSources, PackageInfo};
+use rustc_hash::FxHashMap;
+
+#[test]
+fn import_and_call_reexport() {
+    let pkg_graph: PackageGraphSources = PackageGraphSources {
+        root: PackageInfo {
+            sources: vec![(
+                "PackageB.qs".into(),
+                indoc! {"
+                    import Foo.DependencyA.Foo;
+                    function Main() : Unit {
+                        Foo([1, 2]); 
+                        Foo.DependencyA.MagicFunction();
+                    }"}
+                .into(),
+            )],
+            language_features: LanguageFeatures::default(),
+            dependencies: [("Foo".into(), "PackageAKey".into())].into_iter().collect(),
+            package_type: None,
+        },
+        packages: [(
+            "PackageAKey".into(),
+            PackageInfo {
+                sources: vec![(
+                    "Foo.qs".into(),
+                    r#"
+                    namespace DependencyA {
+                        function MagicFunction() : Unit {
+                            Message("hello from dependency A!");
+                        }
+                        export MagicFunction, Microsoft.Quantum.Core.Length as Foo;
+                    }
+                    "#
+                    .into(),
+                )],
+                language_features: LanguageFeatures::default(),
+                dependencies: FxHashMap::default(),
+                package_type: None,
+            },
+        )]
+        .into_iter()
+        .collect(),
+    };
+
+    // This builds all the dependencies
+    let buildable_program = BuildableProgram::new(TargetCapabilityFlags::all(), pkg_graph);
+
+    assert!(
+        buildable_program.dependency_errors.is_empty(),
+        "dependencies should be built without errors"
+    );
+
+    let BuildableProgram {
+        store,
+        user_code,
+        user_code_dependencies,
+        ..
+    } = buildable_program;
+
+    let user_code = SourceMap::new(user_code.sources, None);
+
+    let mut interpreter = Interpreter::new(
+        user_code,
+        PackageType::Exe,
+        TargetCapabilityFlags::all(),
+        LanguageFeatures::default(),
+        store,
+        &user_code_dependencies,
+    )
+    .expect("interpreter creation should succeed");
+
+    let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
+    let mut receiver = CursorReceiver::new(&mut cursor);
+    let res = interpreter.eval_entry(&mut receiver);
+
+    assert!(res.is_ok(), "evaluation should succeed");
+
+    let output = String::from_utf8(cursor.into_inner()).expect("output should be valid utf-8");
+
+    assert_eq!(output, "hello from dependency A!\n");
+}
+
+#[test]
+fn directly_call_reexport() {
+    let pkg_graph: PackageGraphSources = PackageGraphSources {
+        root: PackageInfo {
+            sources: vec![(
+                "PackageB.qs".into(),
+                indoc! {"
+                function Main() : Unit {
+      Foo.DependencyA.Foo([1, 2]);
+      Foo.DependencyA.MagicFunction();
+  }"}
+                .into(),
+            )],
+            language_features: LanguageFeatures::default(),
+            dependencies: [("Foo".into(), "PackageAKey".into())].into_iter().collect(),
+            package_type: None,
+        },
+        packages: [(
+            "PackageAKey".into(),
+            PackageInfo {
+                sources: vec![(
+                    "Foo.qs".into(),
+                    r#"
+                        namespace DependencyA {
+                            function MagicFunction() : Unit {
+                                Message("hello from dependency A!");
+                            }
+                            export MagicFunction, Microsoft.Quantum.Core.Length as Foo;
+                        }
+                    "#
+                    .into(),
+                )],
+                language_features: LanguageFeatures::default(),
+                dependencies: FxHashMap::default(),
+                package_type: None,
+            },
+        )]
+        .into_iter()
+        .collect(),
+    };
+
+    // This builds all the dependencies
+    let buildable_program = BuildableProgram::new(TargetCapabilityFlags::all(), pkg_graph);
+
+    assert!(
+        buildable_program.dependency_errors.is_empty(),
+        "dependencies should be built without errors"
+    );
+
+    let BuildableProgram {
+        store,
+        user_code,
+        user_code_dependencies,
+        ..
+    } = buildable_program;
+
+    let user_code = SourceMap::new(user_code.sources, None);
+
+    let mut interpreter = Interpreter::new(
+        user_code,
+        PackageType::Exe,
+        TargetCapabilityFlags::all(),
+        LanguageFeatures::default(),
+        store,
+        &user_code_dependencies,
+    )
+    .expect("interpreter creation should succeed");
+
+    let mut cursor = std::io::Cursor::new(Vec::<u8>::new());
+    let mut receiver = CursorReceiver::new(&mut cursor);
+    let res = interpreter.eval_entry(&mut receiver);
+
+    assert!(res.is_ok(), "evaluation should succeed");
+
+    let output = String::from_utf8(cursor.into_inner()).expect("output should be valid utf-8");
+
+    assert_eq!(output, "hello from dependency A!\n");
+}
diff --git a/compiler/qsc_data_structures/src/namespaces/tests.rs b/compiler/qsc_data_structures/src/namespaces/tests.rs
index 859cf802..8ecad156 100644
--- a/compiler/qsc_data_structures/src/namespaces/tests.rs
+++ b/compiler/qsc_data_structures/src/namespaces/tests.rs
@@ -329,3 +329,190 @@ fn test_get_namespace_id() {
     "#]]
     .assert_debug_eq(&result_buf);
 }
+
+#[allow(clippy::too_many_lines)]
+#[test]
+fn test_tree_iter() {
+    let mut root = NamespaceTreeRoot::default();
+    for i in 0..3 {
+        for j in 'a'..'d' {
+            let _ = root.insert_or_find_namespace(
+                vec![Rc::from(format!("ns{i}")), Rc::from(format!("ns{j}"))].into_iter(),
+            );
+        }
+    }
+
+    let result = root.iter().collect::<Vec<_>>();
+    expect![[r#"
+        [
+            [
+                [],
+            ],
+            [
+                [
+                    "Microsoft",
+                ],
+            ],
+            [
+                [
+                    "Microsoft",
+                    "Quantum",
+                ],
+            ],
+            [
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Canon",
+                ],
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Canon",
+                ],
+            ],
+            [
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Core",
+                ],
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Core",
+                ],
+            ],
+            [
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Intrinsic",
+                ],
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Intrinsic",
+                ],
+            ],
+            [
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Measurement",
+                ],
+                [
+                    "Microsoft",
+                    "Quantum",
+                    "Measurement",
+                ],
+            ],
+            [
+                [
+                    "ns0",
+                ],
+            ],
+            [
+                [
+                    "ns0",
+                    "nsa",
+                ],
+                [
+                    "ns0",
+                    "nsa",
+                ],
+            ],
+            [
+                [
+                    "ns0",
+                    "nsb",
+                ],
+                [
+                    "ns0",
+                    "nsb",
+                ],
+            ],
+            [
+                [
+                    "ns0",
+                    "nsc",
+                ],
+                [
+                    "ns0",
+                    "nsc",
+                ],
+            ],
+            [
+                [
+                    "ns1",
+                ],
+            ],
+            [
+                [
+                    "ns1",
+                    "nsa",
+                ],
+                [
+                    "ns1",
+                    "nsa",
+                ],
+            ],
+            [
+                [
+                    "ns1",
+                    "nsb",
+                ],
+                [
+                    "ns1",
+                    "nsb",
+                ],
+            ],
+            [
+                [
+                    "ns1",
+                    "nsc",
+                ],
+                [
+                    "ns1",
+                    "nsc",
+                ],
+            ],
+            [
+                [
+                    "ns2",
+                ],
+            ],
+            [
+                [
+                    "ns2",
+                    "nsa",
+                ],
+                [
+                    "ns2",
+                    "nsa",
+                ],
+            ],
+            [
+                [
+                    "ns2",
+                    "nsb",
+                ],
+                [
+                    "ns2",
+                    "nsb",
+                ],
+            ],
+            [
+                [
+                    "ns2",
+                    "nsc",
+                ],
+                [
+                    "ns2",
+                    "nsc",
+                ],
+            ],
+        ]
+    "#]]
+    .assert_debug_eq(&result);
+}
diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs
index fa241b47..4e8dda19 100644
--- a/compiler/qsc_eval/src/intrinsic/tests.rs
+++ b/compiler/qsc_eval/src/intrinsic/tests.rs
@@ -149,13 +149,14 @@ impl Backend for CustomSim {
 fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result<Value, Error> {
     let mut core = compile::core();
     run_core_passes(&mut core);
-    let core_fir = qsc_lowerer::Lowerer::new().lower_package(&core.package);
+    let fir_store = fir::PackageStore::new();
+    let core_fir = qsc_lowerer::Lowerer::new().lower_package(&core.package, &fir_store);
     let mut store = PackageStore::new(core);
 
     let mut std = compile::std(&store, TargetCapabilityFlags::all());
     assert!(std.errors.is_empty());
     assert!(run_default_passes(store.core(), &mut std, PackageType::Lib).is_empty());
-    let std_fir = qsc_lowerer::Lowerer::new().lower_package(&std.package);
+    let std_fir = qsc_lowerer::Lowerer::new().lower_package(&std.package, &fir_store);
     let std_id = store.insert(std);
 
     let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into()));
@@ -168,7 +169,7 @@ fn check_intrinsic(file: &str, expr: &str, out: &mut impl Receiver) -> Result<Va
     );
     assert!(unit.errors.is_empty());
     assert!(run_default_passes(store.core(), &mut unit, PackageType::Lib).is_empty());
-    let unit_fir = qsc_lowerer::Lowerer::new().lower_package(&unit.package);
+    let unit_fir = qsc_lowerer::Lowerer::new().lower_package(&unit.package, &fir_store);
     let entry = unit_fir.entry_exec_graph.clone();
 
     let id = store.insert(unit);
diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs
index e4b5dc01..5b72f24e 100644
--- a/compiler/qsc_eval/src/tests.rs
+++ b/compiler/qsc_eval/src/tests.rs
@@ -46,13 +46,15 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) {
     let mut fir_lowerer = qsc_lowerer::Lowerer::new();
     let mut core = compile::core();
     run_core_passes(&mut core);
-    let core_fir = fir_lowerer.lower_package(&core.package);
+    let fir_store = fir::PackageStore::new();
+    // store can be empty since core doesn't have any dependencies
+    let core_fir = fir_lowerer.lower_package(&core.package, &fir_store);
     let mut store = PackageStore::new(core);
 
     let mut std = compile::std(&store, TargetCapabilityFlags::all());
     assert!(std.errors.is_empty());
     assert!(run_default_passes(store.core(), &mut std, PackageType::Lib).is_empty());
-    let std_fir = fir_lowerer.lower_package(&std.package);
+    let std_fir = fir_lowerer.lower_package(&std.package, &fir_store);
     let std_id = store.insert(std);
 
     let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into()));
@@ -66,7 +68,7 @@ fn check_expr(file: &str, expr: &str, expect: &Expect) {
     assert!(unit.errors.is_empty(), "{:?}", unit.errors);
     let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib);
     assert!(pass_errors.is_empty(), "{pass_errors:?}");
-    let unit_fir = fir_lowerer.lower_package(&unit.package);
+    let unit_fir = fir_lowerer.lower_package(&unit.package, &fir_store);
     let entry = unit_fir.entry_exec_graph.clone();
     let id = store.insert(unit);
 
@@ -101,13 +103,14 @@ fn check_partial_eval_stmt(
 ) {
     let mut core = compile::core();
     run_core_passes(&mut core);
-    let core_fir = qsc_lowerer::Lowerer::new().lower_package(&core.package);
+    let fir_store = fir::PackageStore::new();
+    let core_fir = qsc_lowerer::Lowerer::new().lower_package(&core.package, &fir_store);
     let mut store = PackageStore::new(core);
 
     let mut std = compile::std(&store, TargetCapabilityFlags::all());
     assert!(std.errors.is_empty());
     assert!(run_default_passes(store.core(), &mut std, PackageType::Lib).is_empty());
-    let std_fir = qsc_lowerer::Lowerer::new().lower_package(&std.package);
+    let std_fir = qsc_lowerer::Lowerer::new().lower_package(&std.package, &fir_store);
     let std_id = store.insert(std);
 
     let sources = SourceMap::new([("test".into(), file.into())], Some(expr.into()));
@@ -121,7 +124,7 @@ fn check_partial_eval_stmt(
     assert!(unit.errors.is_empty(), "{:?}", unit.errors);
     let pass_errors = run_default_passes(store.core(), &mut unit, PackageType::Lib);
     assert!(pass_errors.is_empty(), "{pass_errors:?}");
-    let unit_fir = qsc_lowerer::Lowerer::new().lower_package(&unit.package);
+    let unit_fir = qsc_lowerer::Lowerer::new().lower_package(&unit.package, &fir_store);
     fir_expect.assert_eq(&unit_fir.to_string());
 
     let entry = unit_fir.entry_exec_graph.clone();
diff --git a/compiler/qsc_frontend/src/compile/tests.rs b/compiler/qsc_frontend/src/compile/tests.rs
index ad6c9c7c..85df7301 100644
--- a/compiler/qsc_frontend/src/compile/tests.rs
+++ b/compiler/qsc_frontend/src/compile/tests.rs
@@ -973,17 +973,17 @@ fn unimplemented_call_from_dependency_produces_error() {
     );
     expect![[r#"
         [
-            Error(
-                Resolve(
-                     Unimplemented(
-                         "Bar",
-                         Span {
-                            lo: 69,
-                            hi: 72,
+           Error(
+               Resolve(
+                   Unimplemented(
+                        "Bar",
+                        Span {
+                           lo: 69,
+                           hi: 72,
                         },
-                    ),
-                ),
-            ),
+                   ),
+               ),
+           ),
         ]
     "#]]
     .assert_debug_eq(&unit.errors);
@@ -1471,180 +1471,3 @@ fn test_longest_common_prefix_only_root_common() {
 fn test_longest_common_prefix_only_root_common_no_leading() {
     expect![""].assert_eq(longest_common_prefix(&["a/b", "b/c"]));
 }
-
-#[test]
-fn multiple_packages_reference_exports() {
-    let mut store = PackageStore::new(super::core());
-
-    let package_a = SourceMap::new(
-        [(
-            "PackageA.qs".into(),
-            indoc! {"
-                function FunctionA() : Int {
-                    1
-                }
-                export FunctionA;
-            "}
-            .into(),
-        )],
-        None,
-    );
-
-    let package_a = compile(
-        &store,
-        &[],
-        package_a,
-        TargetCapabilityFlags::all(),
-        LanguageFeatures::default(),
-    );
-    assert!(package_a.errors.is_empty(), "{:#?}", package_a.errors);
-
-    let package_b = SourceMap::new(
-        [(
-            "PackageB".into(),
-            indoc! {"
-                function FunctionB() : Int {
-                    1
-                }
-                export FunctionB;
-            "}
-            .into(),
-        )],
-        None,
-    );
-
-    let package_b = compile(
-        &store,
-        &[],
-        package_b,
-        TargetCapabilityFlags::all(),
-        LanguageFeatures::default(),
-    );
-
-    assert!(package_b.errors.is_empty(), "{:#?}", package_b.errors);
-
-    let package_a = store.insert(package_a);
-    let package_b = store.insert(package_b);
-
-    let user_code = SourceMap::new(
-        [(
-            "UserCode".into(),
-            indoc! {"
-                    import A.PackageA.FunctionA;
-                    import B.PackageB.FunctionB;
-                    @EntryPoint()
-                    function Main() : Unit {
-                       FunctionA();
-                       FunctionB();
-                    }
-                "}
-            .into(),
-        )],
-        None,
-    );
-
-    let user_code = compile(
-        &store,
-        &[
-            (package_a, Some(Arc::from("A"))),
-            (package_b, Some(Arc::from("B"))),
-        ],
-        user_code,
-        TargetCapabilityFlags::all(),
-        LanguageFeatures::default(),
-    );
-
-    assert!(user_code.errors.is_empty(), "{:#?}", user_code.errors);
-}
-
-#[test]
-#[allow(clippy::too_many_lines)]
-fn multiple_packages_disallow_unexported_imports() {
-    let mut store = PackageStore::new(super::core());
-
-    let package_a = SourceMap::new(
-        [(
-            "PackageA.qs".into(),
-            indoc! {"
-                function FunctionA() : Int {
-                    1
-                }
-            "}
-            .into(),
-        )],
-        None,
-    );
-
-    let package_a = compile(
-        &store,
-        &[],
-        package_a,
-        TargetCapabilityFlags::all(),
-        LanguageFeatures::default(),
-    );
-    assert!(package_a.errors.is_empty(), "{:#?}", package_a.errors);
-
-    let package_a = store.insert(package_a);
-
-    let user_code = SourceMap::new(
-        [(
-            "UserCode".into(),
-            indoc! {"
-                    import A.PackageA.FunctionA;
-                    @EntryPoint()
-                    function Main() : Unit {
-                       FunctionA();
-                    }
-                "}
-            .into(),
-        )],
-        None,
-    );
-
-    let user_code = compile(
-        &store,
-        &[(package_a, Some(Arc::from("A")))],
-        user_code,
-        TargetCapabilityFlags::all(),
-        LanguageFeatures::default(),
-    );
-
-    expect![[r#"
-        [
-            Error(
-                Resolve(
-                    NotFound(
-                        "A.PackageA.FunctionA",
-                        Span {
-                            lo: 7,
-                            hi: 27,
-                        },
-                    ),
-                ),
-            ),
-            Error(
-                Resolve(
-                    NotFound(
-                        "FunctionA",
-                        Span {
-                            lo: 71,
-                            hi: 80,
-                        },
-                    ),
-                ),
-            ),
-            Error(
-                Type(
-                    Error(
-                        AmbiguousTy(
-                            Span {
-                                lo: 71,
-                                hi: 82,
-                            },
-                        ),
-                    ),
-                ),
-            ),
-        ]"#]]
-    .assert_eq(&format!("{:#?}", user_code.errors));
-}
diff --git a/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs b/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs
index c7eaa325..c55f4ab9 100644
--- a/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs
+++ b/compiler/qsc_frontend/src/compile/tests/multiple_packages.rs
@@ -6,6 +6,7 @@ use crate::compile::TargetCapabilityFlags;
 use crate::compile::core;
 use expect_test::expect;
 use expect_test::Expect;
+use indoc::indoc;
 use qsc_data_structures::language_features::LanguageFeatures;
 use qsc_hir::hir::PackageId;
 
@@ -146,6 +147,304 @@ fn namespaces_named_main_treated_as_root() {
     ]);
 }
 
+#[test]
+fn multiple_packages_reference_exports() {
+    multiple_package_check(vec![
+        (
+            "PackageA",
+            indoc! {"
+                    operation Foo(x: Int, y: Bool) : Int {
+                        x
+                    }
+                    export Foo;
+                "},
+        ),
+        (
+            "PackageB",
+            indoc! {"
+                    import PackageA.PackageA.Foo;
+                    export Foo;
+                "},
+        ),
+        (
+            "PackageC",
+            indoc! {"
+                    import PackageB.PackageB.Foo;
+                    @EntryPoint()
+                    operation Main() : Unit {
+                        Foo(10, true);
+                    }
+                "},
+        ),
+    ]);
+}
+
+#[test]
+#[allow(clippy::too_many_lines)]
+fn multiple_packages_disallow_unexported_imports() {
+    multiple_package_check_expect_err(
+        vec![
+            (
+                "PackageA",
+                indoc! {"
+                    function FunctionA() : Int {
+                        1
+                    }
+                "},
+            ),
+            (
+                "PackageB",
+                indoc! {"
+                    import PackageA.PackageA.FunctionA;
+                    @EntryPoint()
+                    function Main() : Unit {
+                       FunctionA();
+                    }
+                "},
+            ),
+        ],
+        &expect![[r#"
+            [
+                Error(
+                    Resolve(
+                        NotFound(
+                            "PackageA.PackageA.FunctionA",
+                            Span {
+                                lo: 7,
+                                hi: 34,
+                            },
+                        ),
+                    ),
+                ),
+                Error(
+                    Resolve(
+                        NotFound(
+                            "FunctionA",
+                            Span {
+                                lo: 78,
+                                hi: 87,
+                            },
+                        ),
+                    ),
+                ),
+                Error(
+                    Type(
+                        Error(
+                            AmbiguousTy(
+                                Span {
+                                    lo: 78,
+                                    hi: 89,
+                                },
+                            ),
+                        ),
+                    ),
+                ),
+            ]"#]],
+    );
+}
+
+#[test]
+fn reexport() {
+    multiple_package_check(vec![
+        (
+            "PackageA",
+            indoc! {"
+                    export Microsoft.Quantum.Core.Length as Foo;
+                "},
+        ),
+        (
+            "PackageB",
+            indoc! {"
+
+                    import PackageA.PackageA.Foo;
+                    @EntryPoint()
+                    function Main() : Unit {
+                        use qs = Qubit[2];
+                        let len = Foo(qs);
+                    }
+                "},
+        ),
+    ]);
+}
+
+#[test]
+fn reexport_export_has_alias() {
+    multiple_package_check(vec![
+        (
+            "PackageA",
+            indoc! {"
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo as Bar;
+                "},
+        ),
+        (
+            "PackageB",
+            indoc! {"
+                import PackageA.PackageA.Bar;
+                "},
+        ),
+    ]);
+}
+
+#[test]
+fn reexport_import_has_alias() {
+    multiple_package_check(vec![
+        (
+            "PackageA",
+            "operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            ",
+        ),
+        (
+            "PackageB",
+            "
+                import PackageA.PackageA.Foo as Bar;
+
+                export Bar;
+            ",
+        ),
+    ]);
+}
+
+#[test]
+fn reexport_reexport_has_alias() {
+    multiple_package_check(vec![
+        (
+            "PackageA",
+            "
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            ",
+        ),
+        (
+            "PackageB",
+            "
+                import PackageA.PackageA.Foo;
+                export Foo as Bar;
+            ",
+        ),
+        (
+            "PackageC",
+            "
+                import PackageB.PackageB.Bar;
+                @EntryPoint()
+                operation Main() : Unit {
+                    Bar(10, true);
+                }
+            ",
+        ),
+    ]);
+}
+
+#[test]
+fn reexport_callable_combined_aliases() {
+    multiple_package_check(vec![
+        (
+            "PackageA",
+            "
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            ",
+        ),
+        (
+            "PackageB",
+            "
+                import PackageA.PackageA.Foo;
+                import PackageA.PackageA.Foo as Foo2;
+                export Foo, Foo as Bar, Foo2, Foo2 as Bar2;
+            ",
+        ),
+        (
+            "PackageC",
+            "
+                import PackageB.PackageB.Foo, PackageB.PackageB.Bar, PackageB.PackageB.Foo2, PackageB.PackageB.Bar2;
+                @EntryPoint()
+                operation Main() : Unit {
+                    Foo(10, true);
+                    Foo2(10, true);
+                    Bar(10, true);
+                    Bar2(10, true);
+                }
+            ",
+        ),
+    ]);
+}
+
+#[test]
+fn direct_reexport() {
+    multiple_package_check(vec![
+        (
+            "A",
+            "operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo as Bar;",
+        ),
+        ("B", "export A.A.Bar as Baz;"),
+        (
+            "C",
+            "import B.B.Baz as Quux;
+                    @EntryPoint()
+                    operation Main() : Unit {
+                        Quux(10, true);
+                    }",
+        ),
+    ]);
+}
+
+#[test]
+fn reexports_still_type_check() {
+    multiple_package_check_expect_err(
+        vec![
+            (
+                "A",
+                "operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo as Bar;",
+            ),
+            (
+                "B",
+                "
+                 export A.A.Bar as Baz;",
+            ),
+            (
+                "C",
+                "import B.B.Baz as Quux;
+                    @EntryPoint()
+                    operation Main() : Unit {
+                        Quux(10, 10);
+                    }",
+            ),
+        ],
+        &expect![[r#"
+            [
+                Error(
+                    Type(
+                        Error(
+                            TyMismatch(
+                                "Bool",
+                                "Int",
+                                Span {
+                                    lo: 128,
+                                    hi: 140,
+                                },
+                            ),
+                        ),
+                    ),
+                ),
+            ]"#]],
+    );
+}
+
 #[test]
 fn namespaces_named_lowercase_main_not_treated_as_root() {
     multiple_package_check(vec![
@@ -167,3 +466,46 @@ fn namespaces_named_lowercase_main_not_treated_as_root() {
         ),
     ]);
 }
+
+#[test]
+fn aliased_export_via_aliased_import() {
+    multiple_package_check(vec![
+        (
+            "MyGithubLibrary",
+            r#"
+        namespace TestPackage {
+
+            import Subpackage.Subpackage.Hello as SubHello;
+
+            export HelloFromGithub;
+            export SubHello;
+
+            /// This is a Doc String!
+            function HelloFromGithub() : Unit {
+                SubHello();
+            }
+        }
+
+        namespace Subpackage.Subpackage {
+            function Hello() : Unit {}
+            export Hello;
+        }
+
+        "#,
+        ),
+        (
+            "UserCode",
+            r#"
+           import MyGithubLibrary.TestPackage.SubHello;
+           import MyGithubLibrary.TestPackage.HelloFromGithub;
+           import MyGithubLibrary.Subpackage.Subpackage as P;
+
+            function Main() : Unit {
+                HelloFromGithub();
+                SubHello();
+                P.Hello();
+            }
+         "#,
+        ),
+    ]);
+}
diff --git a/compiler/qsc_frontend/src/incremental/tests.rs b/compiler/qsc_frontend/src/incremental/tests.rs
index 8332e9df..fe2fe642 100644
--- a/compiler/qsc_frontend/src/incremental/tests.rs
+++ b/compiler/qsc_frontend/src/incremental/tests.rs
@@ -3,14 +3,14 @@
 
 use super::{Compiler, Increment};
 use crate::{
-    compile::{self, CompileUnit, PackageStore},
+    compile::{self, CompileUnit, PackageStore, SourceMap},
     incremental::Error,
 };
 use expect_test::{expect, Expect};
 use indoc::indoc;
 use miette::Diagnostic;
 use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
-use std::fmt::Write;
+use std::{fmt::Write, sync::Arc};
 
 #[allow(clippy::too_many_lines)]
 #[test]
@@ -273,8 +273,8 @@ fn conditional_compilation_not_available() {
 
 #[test]
 fn errors_across_multiple_lines() {
-    let mut store = PackageStore::new(crate::compile::core());
-    let std_id = store.insert(crate::compile::std(&store, TargetCapabilityFlags::all()));
+    let mut store = PackageStore::new(compile::core());
+    let std_id = store.insert(compile::std(&store, TargetCapabilityFlags::all()));
     let mut compiler = Compiler::new(
         &store,
         &[(std_id, None)],
@@ -468,7 +468,213 @@ fn continue_after_lower_error() {
         ]
     "#]].assert_debug_eq(&errors);
 }
+#[test]
+fn import_foo() {
+    multi_package_test(
+        vec![(
+            "PackageA.qs",
+            indoc! {"
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            "},
+        )],
+        vec![(
+            "PackageB.qs",
+            indoc! {"
+                import A.PackageA.Foo;
+            "},
+        )],
+        &[("A", "PackageA")],
+        "",
+    );
+}
+
+#[test]
+fn import_foo_with_alias() {
+    multi_package_test(
+        vec![(
+            "PackageA.qs",
+            indoc! {"
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            "},
+        )],
+        vec![(
+            "PackageB.qs",
+            indoc! {"
+                import A.PackageA.Foo as Foo2;
+            "},
+        )],
+        &[("A", "PackageA")],
+        "",
+    );
+}
+
+#[test]
+fn export_foo_with_alias() {
+    multi_package_test(
+        vec![(
+            "PackageA.qs",
+            indoc! {"
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            "},
+        )],
+        vec![(
+            "PackageB.qs",
+            indoc! {"
+                import A.PackageA.Foo;
+                export Foo as Bar;
+            "},
+        )],
+        &[("A", "PackageA")],
+        "",
+    );
+}
+
+#[test]
+fn combined_import_export() {
+    multi_package_test(
+        vec![(
+            "PackageA.qs",
+            indoc! {"
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            "},
+        )],
+        vec![(
+            "PackageB.qs",
+            indoc! {"
+                import A.PackageA.Foo;
+                import A.PackageA.Foo as Foo2;
+                export Foo, Foo as Bar, Foo2, Foo2 as Bar2;
+            "},
+        )],
+        &[("A", "PackageA")],
+        indoc! {"
+            import B.PackageB.Foo, B.PackageB.Bar, B.PackageB.Foo2, B.PackageB.Bar2;
+            @EntryPoint()
+            function Main() : Unit {
+                Foo(10, true);
+                Foo2(10, true);
+                Bar(10, true);
+                Bar2(10, true);
+            }
+        "},
+    );
+}
+
+#[test]
+fn reexport_operation_from_a_dependency() {
+    multi_package_test(
+        vec![(
+            "PackageA.qs",
+            indoc! {"
+                operation Foo(x: Int, y: Bool) : Int {
+                    x
+                }
+                export Foo;
+            "},
+        )],
+        vec![(
+            "PackageB.qs",
+            indoc! {"
+                import A.PackageA.Foo;
+                export Foo as Bar;
+            "},
+        )],
+        &[("A", "PackageA")],
+        indoc! {"
+            import B.PackageB.Bar;
+            @EntryPoint()
+            function Main() : Unit {
+                Bar(10, true);
+            }
+        "},
+    );
+}
+
+fn multi_package_test(
+    packages: Vec<(&str, &str)>,
+    dependencies: Vec<(&str, &str)>,
+    imports: &[(&str, &str)],
+    user_code: &str,
+) {
+    let mut store = PackageStore::new(compile::core());
+
+    let packages = packages
+        .into_iter()
+        .map(|(name, code)| {
+            let source_map = SourceMap::new([(name.into(), code.into())], None);
+            let compiled_package = compile::compile(
+                &store,
+                &[],
+                source_map,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+            );
+            assert!(
+                compiled_package.errors.is_empty(),
+                "{:#?}",
+                compiled_package.errors
+            );
+            store.insert(compiled_package)
+        })
+        .collect::<Vec<_>>();
+
+    let dependencies = dependencies
+        .into_iter()
+        .map(|(name, code)| {
+            let source_map = SourceMap::new([(name.into(), code.into())], None);
+            let compiled_package = compile::compile(
+                &store,
+                &imports
+                    .iter()
+                    .map(|(alias, _)| (packages[0], Some(Arc::from(*alias))))
+                    .collect::<Vec<_>>(),
+                source_map,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+            );
+            assert!(
+                compiled_package.errors.is_empty(),
+                "{:#?}",
+                compiled_package.errors
+            );
+            store.insert(compiled_package)
+        })
+        .collect::<Vec<_>>();
 
+    let mut compiler = Compiler::new(
+        &store,
+        &dependencies
+            .iter()
+            .map(|&pkg| (pkg, Some(Arc::from("B"))))
+            .collect::<Vec<_>>(),
+        TargetCapabilityFlags::all(),
+        LanguageFeatures::default(),
+    );
+    let mut unit = CompileUnit::default();
+
+    let mut errors = Vec::new();
+
+    compiler
+        .compile_fragments(&mut unit, "UserCode", user_code, |e| -> Result<(), ()> {
+            errors = e;
+            Ok(())
+        })
+        .expect("compile_fragments should succeed");
+
+    expect!["[]"].assert_eq(&format!("{errors:#?}"));
+}
 fn check_unit(expect: &Expect, actual: &Increment) {
     let ast = format!("ast:\n{}", actual.ast.package);
 
diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs
index 54e0035f..2283ed77 100644
--- a/compiler/qsc_frontend/src/resolve/tests.rs
+++ b/compiler/qsc_frontend/src/resolve/tests.rs
@@ -84,22 +84,26 @@ impl<'a> Renamer<'a> {
             Res::PrimTy(prim) => format!("{prim:?}"),
             Res::UnitTy => "Unit".to_string(),
             Res::Param(id) => format!("param{id}"),
+            Res::ExportedItem(item, _) => match item.package {
+                None => format!("exported_item{}", item.item),
+                Some(package) => format!("reexport_from_{package}:{}", item.item),
+            },
         }
     }
 }
 
 impl Visitor<'_> for Renamer<'_> {
     fn visit_path(&mut self, path: &Path) {
-        if let Some(&id) = self.names.get(path.id) {
-            self.changes.push((path.span, id.into()));
+        if let Some(res) = self.names.get(path.id) {
+            self.changes.push((path.span, res.clone().into()));
         } else {
             visit::walk_path(self, path);
         }
     }
 
     fn visit_ident(&mut self, ident: &Ident) {
-        if let Some(&id) = self.names.get(ident.id) {
-            self.changes.push((ident.span, id.into()));
+        if let Some(res) = self.names.get(ident.id) {
+            self.changes.push((ident.span, res.clone().into()));
         }
     }
 
@@ -113,8 +117,8 @@ impl Visitor<'_> for Renamer<'_> {
             }
             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()));
+                    if let Some(res) = self.names.get(item.path.id) {
+                        self.changes.push((item.span(), (res.clone()).into()));
                     } else if let Some(namespace_id) = self
                         .namespaces
                         .get_namespace_id(Into::<Idents>::into(item.clone().path).str_iter())
@@ -132,8 +136,8 @@ impl Visitor<'_> for Renamer<'_> {
     fn visit_idents(&mut self, vec_ident: &Idents) {
         let parts: Vec<Ident> = vec_ident.clone().into();
         let first = parts.first().expect("should contain at least one item");
-        if let Some(&id) = self.names.get(first.id) {
-            self.changes.push((first.span, id.into()));
+        if let Some(res) = self.names.get(first.id) {
+            self.changes.push((first.span, res.clone().into()));
             return;
         }
 
@@ -4742,11 +4746,11 @@ fn export_namespace() {
                 operation item2() : Unit {}
             }
             namespace namespace8 {
-                export namespace7;
+                export item4;
             }
             namespace namespace9 {
                 open namespace7;
-                operation item5() : Unit {
+                operation item6() : Unit {
                     item1();
                     item2();
                 }
@@ -4777,11 +4781,11 @@ fn export_namespace_contains_children() {
                 operation item1() : Unit {}
             }
             namespace namespace9 {
-                export namespace7;
+                export item3;
             }
             namespace namespace10 {
                 open namespace8;
-                operation item4() : Unit {
+                operation item5() : Unit {
                     item1();
                 }
             }
@@ -4810,12 +4814,12 @@ fn export_namespace_cyclic() {
                 export namespace8;
             }
             namespace namespace8 {
-                export namespace7;
-                operation item2() : Unit {}
+                export item2;
+                operation item3() : Unit {}
             }
             namespace namespace9 {
                 open namespace8;
-                operation item4() : Unit { item2(); }
+                operation item5() : Unit { item3(); }
             }
         "#]],
     );
@@ -4836,12 +4840,12 @@ fn export_direct_cycle() {
         "},
         &expect![[r#"
             namespace namespace7 {
-                export namespace7;
+                export item1;
             }
 
             namespace namespace8 {
                 open namespace7;
-                operation item2() : Unit { }
+                operation item3() : Unit { }
             }
         "#]],
     );
@@ -4874,7 +4878,7 @@ fn export_namespace_with_alias() {
             }
             namespace namespace10 {
                 open namespace8;
-                operation item4() : Unit {
+                operation item5() : Unit {
                     item1();
                     item1();
                 }
@@ -5090,3 +5094,23 @@ fn import_newtype() {
             }"#]],
     );
 }
+
+#[test]
+fn disallow_glob_alias_import() {
+    check(
+        indoc! {r#"
+                namespace Bar {}
+                namespace Main {
+                    import Bar.* as B;
+                }
+                "#},
+        &expect![[r#"
+            namespace namespace7 {}
+            namespace namespace8 {
+                import namespace7;
+            }
+
+            // GlobImportAliasNotSupported { namespace_name: "Bar", alias: "B", span: Span { lo: 45, hi: 55 } }
+        "#]],
+    );
+}
diff --git a/compiler/qsc_partial_eval/src/tests.rs b/compiler/qsc_partial_eval/src/tests.rs
index 74b57372..84be5027 100644
--- a/compiler/qsc_partial_eval/src/tests.rs
+++ b/compiler/qsc_partial_eval/src/tests.rs
@@ -160,10 +160,8 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore
     let mut fir_store = PackageStore::new();
     for (id, unit) in hir_package_store {
         let mut lowerer = Lowerer::new();
-        fir_store.insert(
-            map_hir_package_to_fir(id),
-            lowerer.lower_package(&unit.package),
-        );
+        let lowered_package = lowerer.lower_package(&unit.package, &fir_store);
+        fir_store.insert(map_hir_package_to_fir(id), lowered_package);
     }
     fir_store
 }
diff --git a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs
index ea711f7b..93a5580a 100644
--- a/compiler/qsc_passes/src/capabilitiesck/tests_common.rs
+++ b/compiler/qsc_passes/src/capabilitiesck/tests_common.rs
@@ -16,14 +16,24 @@ use qsc_rca::{Analyzer, PackageComputeProperties, PackageStoreComputeProperties}
 pub fn check(source: &str, expect: &Expect, capabilities: TargetCapabilityFlags) {
     let compilation_context = CompilationContext::new(source);
     let (package, compute_properties) = compilation_context.get_package_compute_properties_tuple();
-    let errors = check_supported_capabilities(package, compute_properties, capabilities);
+    let errors = check_supported_capabilities(
+        package,
+        compute_properties,
+        capabilities,
+        &compilation_context.fir_store,
+    );
     expect.assert_debug_eq(&errors);
 }
 
 pub fn check_for_exe(source: &str, expect: &Expect, capabilities: TargetCapabilityFlags) {
     let compilation_context = CompilationContext::new_for_exe(source);
     let (package, compute_properties) = compilation_context.get_package_compute_properties_tuple();
-    let errors = check_supported_capabilities(package, compute_properties, capabilities);
+    let errors = check_supported_capabilities(
+        package,
+        compute_properties,
+        capabilities,
+        &compilation_context.fir_store,
+    );
     expect.assert_debug_eq(&errors);
 }
 
@@ -33,10 +43,8 @@ fn lower_hir_package_store(
 ) -> PackageStore {
     let mut fir_store = PackageStore::new();
     for (id, unit) in hir_package_store {
-        fir_store.insert(
-            map_hir_package_to_fir(id),
-            lowerer.lower_package(&unit.package),
-        );
+        let pkg = lowerer.lower_package(&unit.package, &fir_store);
+        fir_store.insert(map_hir_package_to_fir(id), pkg);
     }
     fir_store
 }
@@ -49,7 +57,8 @@ struct CompilationContext {
 
 impl CompilationContext {
     fn new(source: &str) -> Self {
-        let (std_id, store) = qsc::compile::package_store_with_stdlib(TargetCapabilityFlags::all());
+        let mut store = qsc::PackageStore::new(qsc::compile::core());
+        let std_id = store.insert(qsc::compile::std(&store, TargetCapabilityFlags::all()));
         let mut compiler = Compiler::new(
             SourceMap::default(),
             PackageType::Lib,
diff --git a/compiler/qsc_rca/src/tests.rs b/compiler/qsc_rca/src/tests.rs
index 722b1b22..419e6b25 100644
--- a/compiler/qsc_rca/src/tests.rs
+++ b/compiler/qsc_rca/src/tests.rs
@@ -170,7 +170,7 @@ fn lower_hir_package_store(hir_package_store: &HirPackageStore) -> PackageStore
         let mut lowerer = Lowerer::new();
         fir_store.insert(
             map_hir_package_to_fir(id),
-            lowerer.lower_package(&unit.package),
+            lowerer.lower_package(&unit.package, &fir_store),
         );
     }
     fir_store
diff --git a/library/src/tests.rs b/library/src/tests.rs
index b51d5b85..b0177103 100644
--- a/library/src/tests.rs
+++ b/library/src/tests.rs
@@ -204,3 +204,15 @@ fn check_base_profile_measure_resets_aux_qubits() {
         &Value::RESULT_ONE,
     );
 }
+
+// just tests a single case of the stdlib reexports for the modern api,
+// to ensure that reexporting functionality doesn't break
+#[test]
+fn stdlib_reexport_single_case() {
+    test_expression(
+        r#" {
+    import Std.Arrays.Count;
+    }"#,
+        &Value::Tuple(vec![].into()),
+    );
+}
diff --git a/noisy_simulator/src/instrument/tests.rs b/noisy_simulator/src/instrument/tests.rs
index 9aab8b68..d560ee64 100644
--- a/noisy_simulator/src/instrument/tests.rs
+++ b/noisy_simulator/src/instrument/tests.rs
@@ -13,6 +13,21 @@ use rand::{rngs::StdRng, Rng, SeedableRng};
 /// Seed for the random number generators.
 const SEED: u64 = 42;
 
+#[test]
+#[should_panic(expected = "instrument should be invalid")]
+fn check_ill_formed_instrument_throws_error() {
+    let op0 = operation!([1., 0.;
+                          0., 0.;])
+    .expect("operation should be valid");
+    let op1 = operation!([1., 0., 0., 0.;
+                          0., 0., 0., 0.;
+                          0., 0., 0., 0.;
+                          0., 0., 0., 0.;])
+    .expect("operation should be valid");
+
+    let _instrument = Instrument::new(vec![op0, op1]).expect("instrument should be invalid");
+}
+
 /// Check that the inner matrices of the instrument are constructed correctly.
 #[test]
 fn check_non_selective_operation_matrix_is_computed_correctly() {
@@ -48,7 +63,7 @@ fn check_non_selective_evolution_operator_is_computed_correctly() {
         .collect();
     let op0 = Operation::new(kraus_operators).expect("operation should be valid");
     let instrument_0 = Instrument::new(vec![op0]).expect("instrument should be valid");
-    let kraus_operators: Vec<SquareMatrix> = instrument_0.non_selective_kraus_operators().to_vec();
+    let kraus_operators: Vec<SquareMatrix> = instrument_0.non_selective_kraus_operators().clone();
     let op1 = Operation::new(kraus_operators).expect("operation should be valid");
     let instrument_1 = Instrument::new(vec![op1]).expect("instrument should be valid");
     let m0 = instrument_0.non_selective_operation_matrix();
diff --git a/noisy_simulator/src/operation/tests.rs b/noisy_simulator/src/operation/tests.rs
index 8f69e160..40447df2 100644
--- a/noisy_simulator/src/operation/tests.rs
+++ b/noisy_simulator/src/operation/tests.rs
@@ -60,7 +60,7 @@ fn check_effect_matrix_is_computed_correctly() {
          9., 12., 23., 37.;
         15., 19., 37., 61.;
     ]
-    .map(|z| z.into());
+    .map(std::convert::Into::into);
 
     let eff1 = dmatrix! [
         11.3225 + 0.   * I, 6.1 - 1.85 * I, 9.  - 1.95 * I, 15. - 3.4 * I;
@@ -101,7 +101,7 @@ fn check_operation_matrix_is_computed_correctly() {
         3.,  3.,  9.,  15.,  1.,  1.,  3.,  5. ,  1.,  1.,  3.,  5. ,  2. ,  2. ,  6. ,  10.;
         9.,  3.,  3.,  6. ,  3.,  1.,  1.,  2. ,  3.,  1.,  1.,  2. ,  6. ,  2. ,  2. ,  4. ;
     ]
-    .map(|z| z.into());
+    .map(std::convert::Into::into);
 
     let op1 = dmatrix![
         0.01   + 0.   * I, 0.1  + 0.1  * I, 0. + 0.2  * I, 0.  + 0.4  * I, 0.1  - 0.1  * I, 2. + 0. * I, 2. + 2. * I, 4.  + 4. * I, 0. - 0.2  * I, 2. - 2. * I, 4. + 0. * I, 8.  + 0. * I, 0.  - 0.4  * I, 4.  - 4. * I, 8.  + 0. * I, 16. + 0. * I;
diff --git a/noisy_simulator/src/tests.rs b/noisy_simulator/src/tests.rs
index 0ada6e7f..24523a50 100644
--- a/noisy_simulator/src/tests.rs
+++ b/noisy_simulator/src/tests.rs
@@ -12,7 +12,8 @@ pub fn assert_approx_eq(left: f64, right: f64) {
 }
 
 pub fn assert_approx_eq_with_tolerance(left: f64, right: f64, tolerance: f64) {
-    if (left - right).abs() > tolerance {
-        panic!("aprox_equal failed, left = {left}, right = {right}");
-    }
+    assert!(
+        (left - right).abs() <= tolerance,
+        "aprox_equal failed, left = {left}, right = {right}"
+    );
 }
diff --git a/noisy_simulator/src/tests/noisy_tests.rs b/noisy_simulator/src/tests/noisy_tests.rs
index b681d4fd..8010788d 100644
--- a/noisy_simulator/src/tests/noisy_tests.rs
+++ b/noisy_simulator/src/tests/noisy_tests.rs
@@ -50,24 +50,25 @@ fn depolarizing_channel(lambda: f64) -> Operation {
 }
 
 pub fn check_noisy_identity_yields_same_qubit_with_right_probability<NS: NoisySimulator>() {
+    const SHOTS: u64 = 500_000;
+
     let depolarizing_channel = depolarizing_channel(0.1);
     let mz = noiseless_tests::noiseless_mz();
 
-    let mut total_outcome: f64 = 0.0;
+    let mut total_outcome: usize = 0;
 
-    // Run 1000 simulations, on average we should measure the wrong outcome, i.e. 1,
+    // Run `SHOTS` simulations, on average we should measure the wrong outcome, i.e. 1,
     // 5% of the times (𝜆 / 2).
-    const SHOTS: u64 = 500_000;
-
     for seed in 0..SHOTS {
         let mut sim = NS::new_with_seed(1, seed);
         sim.apply_operation(&depolarizing_channel, &[0])
             .expect("operation should succeed");
         total_outcome += sim
             .sample_instrument(&mz, &[0])
-            .expect("operation should succeed") as f64;
+            .expect("operation should succeed");
     }
 
-    total_outcome /= SHOTS as f64;
-    assert_approx_eq_with_tolerance(0.05, total_outcome, 0.001);
+    #[allow(clippy::cast_precision_loss)]
+    let expected_outcome: f64 = (total_outcome as f64) / (SHOTS as f64);
+    assert_approx_eq_with_tolerance(0.05, expected_outcome, 0.001);
 }
diff --git a/pip/tests/test_noisy_simulator.py b/pip/tests/test_noisy_simulator.py
index 55d4de70..5ceda6c9 100644
--- a/pip/tests/test_noisy_simulator.py
+++ b/pip/tests/test_noisy_simulator.py
@@ -13,6 +13,8 @@ import pytest
 
 # Tests for the Q# noisy simulator.
 
+# Operation tests
+
 
 def test_operation_number_of_qubits_is_mapped_correctly():
     op = Operation([[[1, 0], [0, 0]]])
@@ -66,12 +68,37 @@ def test_constructed_an_ill_formed_operation_throws_exception():
     assert str(excinfo.value) == "ill formed matrix, all rows should be the same length"
 
 
+# Instrument tests
+
+
 def test_constructing_an_instrument_with_a_valid_operation_succeeds():
     op = Operation([[[1, 0], [0, 0]]])
     _ = Instrument([op])
 
 
-def test_empty_instrument_error():
+def test_constructing_an_ill_formed_instrument_throws_exception():
+    op0 = Operation([[[1, 0], [0, 0]]])
+    op1 = Operation(
+        [
+            [
+                [1, 0, 0, 0],
+                [0, 0, 0, 0],
+                [0, 0, 0, 0],
+                [0, 0, 0, 0],
+            ]
+        ]
+    )
+
+    with pytest.raises(NoisySimulatorError) as excinfo:
+        _ = Instrument([op0, op1])
+
+    assert (
+        str(excinfo.value)
+        == "error when building instrument: all Operations should target the same number of qubits"
+    )
+
+
+def test_constructing_an_empty_instrument_throws_exception():
     with pytest.raises(NoisySimulatorError) as excinfo:
         inst = Instrument([])
     assert (
@@ -80,21 +107,47 @@ def test_empty_instrument_error():
     )
 
 
+# DensityMatrixSimulator tests
+
+
 def test_density_matrix_simulator_apply_operation_is_mapped_correctly():
     op = Operation([[[1, 0], [0, 0]]])
     sim = DensityMatrixSimulator(1, seed=42)
     sim.apply_operation(op, [0])
 
 
+def test_density_matrix_simulator_apply_instrument_is_mapped_correctly():
+    mz0 = Operation([[[1, 0], [0, 0]]])
+    mz = Instrument([mz0])
+    sim = DensityMatrixSimulator(1, seed=42)
+    sim.apply_instrument(mz, [0])
+
+
 def test_density_matrix_simulator_sample_instrument_is_mapped_correctly():
-    f = 0.5**0.5
-    h = Operation([[[f, f], [f, -f]]])
     mz0 = Operation([[[1, 0], [0, 0]]])
     mz = Instrument([mz0])
     sim = DensityMatrixSimulator(1, seed=42)
+    assert 0 == sim.sample_instrument(mz, [0])
+
+
+def test_density_matrix_simulator_get_state_is_mapped_correctly():
+    sim = DensityMatrixSimulator(1)
+    assert sim.get_state().data() == [1, 0, 0, 0]
+
+
+def test_density_matrix_simulator_set_state_is_mapped_correctly():
+    f = 0.5**0.5
+    h = Operation([[[f, f], [f, -f]]])
+    sim = DensityMatrixSimulator(1)
+    inital_state = sim.get_state()
     sim.apply_operation(h, [0])
+    sim.set_state(inital_state)
+    assert sim.get_state().data() == [1, 0, 0, 0]
 
-    assert 0 == sim.sample_instrument(mz, [0])
+
+def test_density_matrix_simulator_set_trace_is_mapped_correctly():
+    sim = DensityMatrixSimulator(1)
+    sim.set_trace(0.5)
 
 
 def test_density_matrix_simulator_out_of_bounds_qubit():
@@ -108,21 +161,47 @@ def test_density_matrix_simulator_out_of_bounds_qubit():
     assert str(excinfo.value) == "qubit id out of bounds: 1"
 
 
+# StateVectorSimulator tests
+
+
 def test_state_vector_simulator_apply_operation_is_mapped_correctly():
     op = Operation([[[1, 0], [0, 0]]])
     sim = StateVectorSimulator(1, seed=42)
     sim.apply_operation(op, [0])
 
 
+def test_state_vector_simulator_apply_instrument_is_mapped_correctly():
+    mz0 = Operation([[[1, 0], [0, 0]]])
+    mz = Instrument([mz0])
+    sim = StateVectorSimulator(1, seed=42)
+    sim.apply_instrument(mz, [0])
+
+
 def test_state_vector_simulator_sample_instrument_is_mapped_correctly():
-    f = 0.5**0.5
-    h = Operation([[[f, f], [f, -f]]])
     mz0 = Operation([[[1, 0], [0, 0]]])
     mz = Instrument([mz0])
     sim = StateVectorSimulator(1, seed=42)
+    assert 0 == sim.sample_instrument(mz, [0])
+
+
+def test_state_vector_simulator_get_state_is_mapped_correctly():
+    sim = StateVectorSimulator(1)
+    assert sim.get_state().data() == [1, 0]
+
+
+def test_state_vector_simulator_set_state_is_mapped_correctly():
+    f = 0.5**0.5
+    h = Operation([[[f, f], [f, -f]]])
+    sim = StateVectorSimulator(1)
+    inital_state = sim.get_state()
     sim.apply_operation(h, [0])
+    sim.set_state(inital_state)
+    assert sim.get_state().data() == [1, 0]
 
-    assert 0 == sim.sample_instrument(mz, [0])
+
+def test_state_vector_simulator_set_trace_is_mapped_correctly():
+    sim = StateVectorSimulator(1)
+    sim.set_trace(0.5)
 
 
 def test_state_vector_simulator_out_of_bounds_qubit():