Repository: microsoft/qsharp
Pull request #1239 (2024-03-07 09:20:41)

Update type inference to constrain indexed container as an array
This change updates the type inference logic to have an additional constraint on indexed containers that they must be an array of a new inference type. This is enough to allow further inference to determine the type of the array if items from the array are used in places with inferred types. If any other types support the `HasIndex` class in the future this approach can be revisited.

This fixes an issue where certain code with inferrable types would produce type ambiguous errors, such as [this code](https://microsoft.github.io/qsharp/?code=H4sIAAAAAAAAEz3OMWvDMBAF4F2%2F4oEXe6k6t6R0aMkUiNsOBePhal8TgXVSpBMlhPz3YqX0xu8e785aiwbv5OPCxlqLLas6OSArJeV5teoNXjhPyUV1Qap8HF2GyyB4J87Tgr5BTOGQyEOPpJhI8MUomWdouFXiJ7l6oG8whZnvjJDnHGli7M59IdHi938tF2MA4PlVNJ33wYm2XZUQOdH6CXbkpO3wgDfOZdFhxKUm1llY8Y0NThmbJ3y2pzzcj93j%2Fz6xliQYxhtdzdX8Al5OMIEQAQAA):

```qsharp
let f = qs => X(qs[0);
```

github-actions[bot]: ## Benchmark for 6a806e7
  <details>
    <summary>Click to view benchmark</summary>

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | 760.7±6.73µs | 759.5±17.80µs | -0.16% |
| Array literal evaluation | 543.2±33.15µs | 551.7±35.52µs | +1.56% |
| Array update evaluation | 755.7±13.38µs | 752.3±5.64µs | -0.45% |
| Deutsch-Jozsa evaluation | 7.1±0.15ms | **7.0±0.08ms** | **-1.41%** |
| Large file parity evaluation | 35.4±0.14ms | **35.2±0.27ms** | **-0.56%** |
| Large input file | 42.4±1.20ms | **41.2±0.95ms** | **-2.83%** |
| Large nested iteration | 78.9±4.30ms | **74.5±0.25ms** | **-5.58%** |
| Standard library | 24.2±0.70ms | 23.8±0.85ms | -1.65% |
| Teleport evaluation | 97.2±1.71µs | 97.2±5.13µs | 0.00% |

  </details>
  

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 24ee757b..9a24a5ab 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -75,11 +75,6 @@ jobs:
     strategy:
       matrix:
         os: [windows-latest, ubuntu-latest, macos-14]
-        is_pr:
-          - ${{ github.event_name == 'pull_request' }}
-        exclude:
-          - os: macos-14
-            is_pr: true
 
     runs-on: ${{matrix.os}}
 
@@ -114,11 +109,6 @@ jobs:
     strategy:
       matrix:
         os: [windows-latest, ubuntu-latest, macos-14]
-        is_pr:
-          - ${{ github.event_name == 'pull_request' }}
-        exclude:
-          - os: macos-14
-            is_pr: true
 
     runs-on: ${{matrix.os}}
 
diff --git a/compiler/qsc/src/compile.rs b/compiler/qsc/src/compile.rs
index 5db96f5d..61068c26 100644
--- a/compiler/qsc/src/compile.rs
+++ b/compiler/qsc/src/compile.rs
@@ -16,8 +16,16 @@ pub type Error = WithSource<ErrorKind>;
 #[derive(Clone, Debug, Diagnostic, Error)]
 #[diagnostic(transparent)]
 #[error(transparent)]
+/// `ErrorKind` represents the different kinds of errors that can occur in the compiler.
+/// Each variant of the enum corresponds to a different stage of the compilation process.
 pub enum ErrorKind {
+    /// `Frontend` variant represents errors that occur during the frontend stage of the compiler.
+    /// These errors are typically related to syntax and semantic checks.
     Frontend(#[from] qsc_frontend::compile::Error),
+
+    /// `Pass` variant represents errors that occur during the `qsc_passes` stage of the compiler.
+    /// These errors are typically related to optimization, transformation, code generation, passes,
+    /// and static analysis passes.
     Pass(#[from] qsc_passes::Error),
 }
 
diff --git a/compiler/qsc/src/incremental.rs b/compiler/qsc/src/incremental.rs
index f1496ea4..53b1c8f4 100644
--- a/compiler/qsc/src/incremental.rs
+++ b/compiler/qsc/src/incremental.rs
@@ -149,12 +149,12 @@ impl Compiler {
     /// get information about the newly added items, or do other modifications.
     /// It is then the caller's responsibility to merge
     /// these packages into the current `CompileUnit` using the `update()` method.
-    pub fn compile_expr(&mut self, expr: &str) -> Result<Increment, Errors> {
+    pub fn compile_entry_expr(&mut self, expr: &str) -> Result<Increment, Errors> {
         let (core, unit) = self.store.get_open_mut();
 
         let mut increment = self
             .frontend
-            .compile_expr(unit, "<entry>", expr)
+            .compile_entry_expr(unit, expr)
             .map_err(into_errors)?;
 
         let pass_errors = self.passes.run_default_passes(
@@ -173,6 +173,7 @@ impl Compiler {
 
     /// Updates the current compilation with the AST and HIR packages,
     /// and any associated context, returned from a previous incremental compilation.
+    /// Entry expressions are ignored.
     pub fn update(&mut self, new: Increment) {
         let (_, unit) = self.store.get_open_mut();
 
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index 2a0a489f..7d0819cf 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -20,6 +20,7 @@ pub use qsc_eval::{
 use crate::{
     error::{self, WithStack},
     incremental::Compiler,
+    location::Location,
 };
 use debug::format_call_stack;
 use miette::Diagnostic;
@@ -299,7 +300,8 @@ impl Interpreter {
         receiver: &mut impl Receiver,
         expr: &str,
     ) -> std::result::Result<InterpretResult, Vec<Error>> {
-        let stmt_id = self.compile_expr_to_stmt(expr)?;
+        let expr_id = self.compile_entry_expr(expr)?;
+
         if self.quantum_seed.is_some() {
             sim.set_seed(self.quantum_seed);
         }
@@ -307,7 +309,7 @@ impl Interpreter {
         Ok(eval(
             self.package,
             self.classical_seed,
-            stmt_id.into(),
+            expr_id.into(),
             self.compiler.package_store(),
             &self.fir_store,
             &mut Env::default(),
@@ -316,10 +318,21 @@ impl Interpreter {
         ))
     }
 
-    fn compile_expr_to_stmt(&mut self, expr: &str) -> std::result::Result<StmtId, Vec<Error>> {
-        let increment = self.compiler.compile_expr(expr).map_err(into_errors)?;
+    fn compile_entry_expr(&mut self, expr: &str) -> std::result::Result<ExprId, Vec<Error>> {
+        let increment = self
+            .compiler
+            .compile_entry_expr(expr)
+            .map_err(into_errors)?;
 
-        let stmts = self.lower(&increment);
+        // `lower` will update the entry expression in the FIR store,
+        // and it will always return an empty list of statements.
+        let _ = self.lower(&increment);
+
+        // The AST and HIR packages in `increment` only contain an entry
+        // expression and no statements. The HIR *can* contain items if the entry
+        // expression defined any items.
+        assert!(increment.hir.stmts.is_empty());
+        assert!(increment.ast.package.nodes.is_empty());
 
         // Updating the compiler state with the new AST/HIR nodes
         // is not necessary for the interpreter to function, as all
@@ -329,10 +342,10 @@ impl Interpreter {
         // here to keep the package stores consistent.
         self.compiler.update(increment);
 
-        assert!(stmts.len() == 1, "expected exactly one statement");
-        let stmt_id = stmts.first().expect("expected exactly one statement");
+        let unit = self.fir_store.get(self.package);
+        let entry = unit.entry.expect("package should have an entry expression");
 
-        Ok(*stmt_id)
+        Ok(entry)
     }
 
     fn lower(&mut self, unit_addition: &qsc_frontend::incremental::Increment) -> Vec<StmtId> {
@@ -437,25 +450,15 @@ impl Debugger {
                     Global::Udt => "udt".into(),
                 };
 
-                let hir_package = self
-                    .interpreter
-                    .compiler
-                    .package_store()
-                    .get(map_fir_package_to_hir(frame.id.package))
-                    .expect("package should exist");
-                let source = hir_package
-                    .sources
-                    .find_by_offset(frame.span.lo)
-                    .expect("frame should have a source");
-                let path = source.name.to_string();
                 StackFrame {
                     name,
                     functor,
-                    path,
-                    range: Range::from_span(
+                    location: Location::from(
+                        frame.span,
+                        map_fir_package_to_hir(frame.id.package),
+                        self.interpreter.compiler.package_store(),
+                        map_fir_package_to_hir(self.interpreter.source_package),
                         self.position_encoding,
-                        &source.contents,
-                        &(frame.span - source.offset),
                     ),
                 }
             })
@@ -541,10 +544,8 @@ pub struct StackFrame {
     pub name: String,
     /// The functor of the callable.
     pub functor: String,
-    /// The path of the source file.
-    pub path: String,
-    /// The source range of the call site.
-    pub range: Range,
+    /// The source location of the call site.
+    pub location: Location,
 }
 
 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
diff --git a/compiler/qsc_ast/src/ast.rs b/compiler/qsc_ast/src/ast.rs
index dcf9efa3..be3f5996 100644
--- a/compiler/qsc_ast/src/ast.rs
+++ b/compiler/qsc_ast/src/ast.rs
@@ -28,6 +28,9 @@ fn set_indentation<'a, 'b>(
 }
 
 /// The unique identifier for an AST node.
+/// This could be assigned or unassigned. If unassigned, the value will be `u32::MAX`.
+/// Assignment happens after symbol resolution. Use [`NodeId::is_default`] to check if the node
+/// has been assigned yet.
 #[derive(Clone, Copy, Debug)]
 pub struct NodeId(u32);
 
diff --git a/compiler/qsc_eval/src/lower.rs b/compiler/qsc_eval/src/lower.rs
index 50e6d307..4fd96b46 100644
--- a/compiler/qsc_eval/src/lower.rs
+++ b/compiler/qsc_eval/src/lower.rs
@@ -94,12 +94,16 @@ impl Lowerer {
             .map(|s| self.lower_stmt(s))
             .collect();
 
+        let entry = hir_package.entry.as_ref().map(|e| self.lower_expr(e));
+
         self.update_package(fir_package);
 
         for (k, v) in items {
             fir_package.items.insert(k, v);
         }
 
+        fir_package.entry = entry;
+
         qsc_fir::validate::validate(fir_package);
 
         new_stmts
diff --git a/compiler/qsc_fir/src/fir.rs b/compiler/qsc_fir/src/fir.rs
index 4cc7a325..05c518fe 100644
--- a/compiler/qsc_fir/src/fir.rs
+++ b/compiler/qsc_fir/src/fir.rs
@@ -1387,66 +1387,6 @@ impl Display for PatKind {
     }
 }
 
-/// A qubit initializer.
-#[derive(Clone, Debug, PartialEq)]
-pub struct QubitInit {
-    /// The node ID.
-    pub id: NodeId,
-    /// The span.
-    pub span: Span,
-    /// The qubit initializer type.
-    pub ty: Ty,
-    /// The qubit initializer kind.
-    pub kind: QubitInitKind,
-}
-
-impl Display for QubitInit {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        write!(
-            f,
-            "QubitInit {} {} [Type {}]: {}",
-            self.id, self.span, self.ty, self.kind
-        )
-    }
-}
-
-/// A qubit initializer kind.
-#[derive(Clone, Debug, PartialEq)]
-pub enum QubitInitKind {
-    /// An array of qubits: `Qubit[a]`.
-    Array(ExprId),
-    /// A single qubit: `Qubit()`.
-    Single,
-    /// A tuple: `(a, b, c)`.
-    Tuple(Vec<QubitInit>),
-}
-
-impl Display for QubitInitKind {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let mut indent = set_indentation(indented(f), 0);
-        match self {
-            QubitInitKind::Array(e) => {
-                write!(indent, "Array:")?;
-                indent = set_indentation(indent, 1);
-                write!(indent, "\n{e}")?;
-            }
-            QubitInitKind::Single => write!(indent, "Single")?,
-            QubitInitKind::Tuple(qis) => {
-                if qis.is_empty() {
-                    write!(indent, "Unit")?;
-                } else {
-                    write!(indent, "Tuple:")?;
-                    indent = set_indentation(indent, 1);
-                    for qi in qis {
-                        write!(indent, "\n{qi}")?;
-                    }
-                }
-            }
-        }
-        Ok(())
-    }
-}
-
 /// An identifier.
 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
 pub struct Ident {
diff --git a/compiler/qsc_fir/src/mut_visit.rs b/compiler/qsc_fir/src/mut_visit.rs
index 2133c8f7..1e03d038 100644
--- a/compiler/qsc_fir/src/mut_visit.rs
+++ b/compiler/qsc_fir/src/mut_visit.rs
@@ -3,8 +3,7 @@
 
 use crate::fir::{
     Block, BlockId, CallableDecl, CallableImpl, Expr, ExprId, ExprKind, Ident, Item, ItemKind,
-    Package, Pat, PatId, PatKind, QubitInit, QubitInitKind, SpecDecl, SpecImpl, Stmt, StmtId,
-    StmtKind, StringComponent,
+    Package, Pat, PatId, PatKind, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StringComponent,
 };
 
 pub trait MutVisitor<'a>: Sized {
@@ -48,10 +47,6 @@ pub trait MutVisitor<'a>: Sized {
         walk_pat(self, pat);
     }
 
-    fn visit_qubit_init(&mut self, init: &'a mut QubitInit) {
-        walk_qubit_init(self, init);
-    }
-
     fn visit_ident(&mut self, _: &'a mut Ident) {}
 
     fn get_block(&mut self, id: BlockId) -> &'a mut Block;
@@ -202,11 +197,3 @@ pub fn walk_pat<'a>(vis: &mut impl MutVisitor<'a>, pat: PatId) {
         PatKind::Tuple(pats) => pats.iter().for_each(|p| vis.visit_pat(*p)),
     }
 }
-
-pub fn walk_qubit_init<'a>(vis: &mut impl MutVisitor<'a>, init: &'a mut QubitInit) {
-    match &mut init.kind {
-        QubitInitKind::Array(len) => vis.visit_expr(*len),
-        QubitInitKind::Single => {}
-        QubitInitKind::Tuple(inits) => inits.iter_mut().for_each(|i| vis.visit_qubit_init(i)),
-    }
-}
diff --git a/compiler/qsc_fir/src/visit.rs b/compiler/qsc_fir/src/visit.rs
index 4fd3fcc8..37edaa3a 100644
--- a/compiler/qsc_fir/src/visit.rs
+++ b/compiler/qsc_fir/src/visit.rs
@@ -3,8 +3,7 @@
 
 use crate::fir::{
     Block, BlockId, CallableDecl, CallableImpl, Expr, ExprId, ExprKind, Ident, Item, ItemKind,
-    Package, Pat, PatId, PatKind, QubitInit, QubitInitKind, SpecDecl, SpecImpl, Stmt, StmtId,
-    StmtKind, StringComponent,
+    Package, Pat, PatId, PatKind, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, StringComponent,
 };
 
 pub trait Visitor<'a>: Sized {
@@ -48,10 +47,6 @@ pub trait Visitor<'a>: Sized {
         walk_pat(self, pat);
     }
 
-    fn visit_qubit_init(&mut self, init: &'a QubitInit) {
-        walk_qubit_init(self, init);
-    }
-
     fn visit_ident(&mut self, _: &'a Ident) {}
 
     fn get_block(&self, id: BlockId) -> &'a Block;
@@ -202,11 +197,3 @@ pub fn walk_pat<'a>(vis: &mut impl Visitor<'a>, pat: PatId) {
         PatKind::Tuple(pats) => pats.iter().for_each(|p| vis.visit_pat(*p)),
     }
 }
-
-pub fn walk_qubit_init<'a>(vis: &mut impl Visitor<'a>, init: &'a QubitInit) {
-    match &init.kind {
-        QubitInitKind::Array(len) => vis.visit_expr(*len),
-        QubitInitKind::Single => {}
-        QubitInitKind::Tuple(inits) => inits.iter().for_each(|i| vis.visit_qubit_init(i)),
-    }
-}
diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs
index 49847c8e..3d435dc7 100644
--- a/compiler/qsc_frontend/src/incremental.rs
+++ b/compiler/qsc_frontend/src/incremental.rs
@@ -16,7 +16,7 @@ use crate::{
 };
 use qsc_ast::{
     assigner::Assigner as AstAssigner,
-    ast::{self, Stmt, TopLevelNode},
+    ast::{self},
     mut_visit::MutVisitor,
     validate::Validator as AstValidator,
     visit::Visitor as AstVisitor,
@@ -149,18 +149,13 @@ impl Compiler {
     /// get information about the newly added items, or do other modifications.
     /// It is then the caller's responsibility to merge
     /// these packages into the current `CompileUnit`.
-    pub fn compile_expr(
+    pub fn compile_entry_expr(
         &mut self,
         unit: &mut CompileUnit,
-        source_name: &str,
         source_contents: &str,
     ) -> Result<Increment, Vec<Error>> {
-        let (mut ast, parse_errors) = Self::parse_expr(
-            &mut unit.sources,
-            source_name,
-            source_contents,
-            self.language_features,
-        );
+        let (mut ast, parse_errors) =
+            Self::parse_entry_expr(&mut unit.sources, source_contents, self.language_features);
 
         if !parse_errors.is_empty() {
             return Err(parse_errors);
@@ -247,9 +242,6 @@ impl Compiler {
     /// Entry expressions are ignored.
     #[must_use]
     fn concat_ast(&mut self, mut left: ast::Package, right: ast::Package) -> ast::Package {
-        assert!(right.entry.is_none(), "package should not have entry expr");
-        assert!(left.entry.is_none(), "package should not have entry expr");
-
         let mut nodes = Vec::with_capacity(left.nodes.len() + right.nodes.len());
         nodes.extend(left.nodes.into_vec());
         nodes.extend(right.nodes.into_vec());
@@ -260,30 +252,22 @@ impl Compiler {
         left
     }
 
-    fn parse_expr(
+    fn parse_entry_expr(
         sources: &mut SourceMap,
-        source_name: &str,
         source_contents: &str,
         language_features: LanguageFeatures,
     ) -> (ast::Package, Vec<Error>) {
-        let offset = sources.push(source_name.into(), source_contents.into());
+        let offset = sources.push("<entry>".into(), source_contents.into());
 
-        let (expr, errors) = qsc_parse::expr(source_contents, language_features);
-        let mut stmt = Box::new(Stmt {
-            id: ast::NodeId::default(),
-            span: expr.span,
-            kind: Box::new(ast::StmtKind::Expr(expr)),
-        });
+        let (mut expr, errors) = qsc_parse::expr(source_contents, language_features);
 
         let mut offsetter = Offsetter(offset);
-        offsetter.visit_stmt(&mut stmt);
-
-        let top_level_nodes = Box::new([TopLevelNode::Stmt(stmt)]);
+        offsetter.visit_expr(&mut expr);
 
         let package = ast::Package {
             id: ast::NodeId::default(),
-            nodes: top_level_nodes,
-            entry: None,
+            nodes: Box::default(),
+            entry: Some(expr),
         };
 
         (package, with_source(errors, sources, offset))
diff --git a/compiler/qsc_frontend/src/resolve.rs b/compiler/qsc_frontend/src/resolve.rs
index 2f90d425..98cd5249 100644
--- a/compiler/qsc_frontend/src/resolve.rs
+++ b/compiler/qsc_frontend/src/resolve.rs
@@ -26,6 +26,7 @@ const PRELUDE: &[&str] = &[
     "Microsoft.Quantum.Canon",
     "Microsoft.Quantum.Core",
     "Microsoft.Quantum.Intrinsic",
+    "Microsoft.Quantum.Measurement",
 ];
 
 // All AST Path nodes get mapped
@@ -543,18 +544,24 @@ impl Resolver {
     }
 }
 
+/// Constructed from a [Resolver] and an [Assigner], this structure implements `Visitor`
+/// on the package AST where it resolves identifiers, and assigns IDs to them.
 pub(super) struct With<'a> {
     resolver: &'a mut Resolver,
     assigner: &'a mut Assigner,
 }
 
 impl With<'_> {
+    /// Given a function, apply that function to `Self` within a scope. In other words, this
+    /// function automatically pushes a scope before `f` and pops it after.
     fn with_scope(&mut self, span: Span, kind: ScopeKind, f: impl FnOnce(&mut Self)) {
         self.resolver.push_scope(span, kind);
         f(self);
         self.resolver.pop_scope();
     }
 
+    /// Apply `f` to self while a pattern's constituent identifiers are in scope. Removes those
+    /// identifiers from the scope after `f`.
     fn with_pat(&mut self, span: Span, kind: ScopeKind, pat: &ast::Pat, f: impl FnOnce(&mut Self)) {
         self.with_scope(span, kind, |visitor| {
             // The bindings are valid from the beginning of the scope
diff --git a/compiler/qsc_frontend/src/typeck/infer.rs b/compiler/qsc_frontend/src/typeck/infer.rs
index f66f4cea..817701e8 100644
--- a/compiler/qsc_frontend/src/typeck/infer.rs
+++ b/compiler/qsc_frontend/src/typeck/infer.rs
@@ -570,7 +570,8 @@ impl Solver {
                 constraints.append(&mut self.unify(&arrow1.output, &arrow2.output, span));
 
                 match (arrow1.functors, arrow2.functors) {
-                    (FunctorSet::Value(value1), FunctorSet::Value(value2)) if value1 == value2 => {}
+                    (FunctorSet::Value(value1), FunctorSet::Value(value2))
+                        if value2.satisfies(&value1) => {}
                     (FunctorSet::Infer(infer1), FunctorSet::Infer(infer2)) if infer1 == infer2 => {}
                     (FunctorSet::Infer(infer), functors) | (functors, FunctorSet::Infer(infer)) => {
                         constraints.append(&mut self.bind_functor(infer, functors, span));
diff --git a/compiler/qsc_frontend/src/typeck/rules.rs b/compiler/qsc_frontend/src/typeck/rules.rs
index 1db4b398..48077901 100644
--- a/compiler/qsc_frontend/src/typeck/rules.rs
+++ b/compiler/qsc_frontend/src/typeck/rules.rs
@@ -312,9 +312,18 @@ impl<'a> Context<'a> {
                 )
             }
             ExprKind::Index(container, index) => {
+                let container_span = container.span;
                 let container = self.infer_expr(container);
                 let index = self.infer_expr(index);
                 let item_ty = self.inferrer.fresh_ty(TySource::not_divergent(expr.span));
+                let container_item_ty = self
+                    .inferrer
+                    .fresh_ty(TySource::not_divergent(container_span));
+                self.inferrer.eq(
+                    container_span,
+                    container.ty.clone(),
+                    Ty::Array(Box::new(container_item_ty)),
+                );
                 self.inferrer.class(
                     expr.span,
                     Class::HasIndex {
diff --git a/compiler/qsc_hir/src/assigner.rs b/compiler/qsc_hir/src/assigner.rs
index ae805124..fd42e2e0 100644
--- a/compiler/qsc_hir/src/assigner.rs
+++ b/compiler/qsc_hir/src/assigner.rs
@@ -6,6 +6,10 @@ use crate::{
     mut_visit::{self, MutVisitor},
 };
 
+/// The [Assigner] tracks the current state of IDs being handed out within a pass of the resolver.
+/// It is used when visiting a package to assign IDs to all elements. Identifiers are resolved and
+/// replaced with canonical IDs in this process. The AST gets all IDs resolved after the symbol resolution
+/// run.
 #[derive(Debug)]
 pub struct Assigner {
     next_node: NodeId,
diff --git a/compiler/qsc_hir/src/ty.rs b/compiler/qsc_hir/src/ty.rs
index b006e88c..d5001695 100644
--- a/compiler/qsc_hir/src/ty.rs
+++ b/compiler/qsc_hir/src/ty.rs
@@ -498,6 +498,17 @@ impl FunctorSetValue {
             | (Self::Ctl, Self::Adj) => Self::CtlAdj,
         }
     }
+
+    #[must_use]
+    pub fn satisfies(&self, other: &Self) -> bool {
+        matches!(
+            (self, other),
+            (_, Self::Empty)
+                | (Self::Adj | Self::CtlAdj, Self::Adj)
+                | (Self::Ctl | Self::CtlAdj, Self::Ctl)
+                | (Self::CtlAdj, Self::CtlAdj)
+        )
+    }
 }
 
 impl Display for FunctorSetValue {
diff --git a/compiler/qsc_parse/src/item.rs b/compiler/qsc_parse/src/item.rs
index ac085a6f..14375d5b 100644
--- a/compiler/qsc_parse/src/item.rs
+++ b/compiler/qsc_parse/src/item.rs
@@ -1,6 +1,9 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+//! Something declared at the top level (within a namespace or module) is an [Item]. This could be a newtype, callable
+//! declaration, import or open statement, or other top-level declaration or statement.
+
 #[cfg(test)]
 mod tests;
 
diff --git a/compiler/qsc_parse/src/prim.rs b/compiler/qsc_parse/src/prim.rs
index 8b1027e1..96f64734 100644
--- a/compiler/qsc_parse/src/prim.rs
+++ b/compiler/qsc_parse/src/prim.rs
@@ -77,6 +77,8 @@ pub(super) fn ident(s: &mut ParserContext) -> Result<Box<Ident>> {
     }
 }
 
+/// This function parses a [Path] from the given context
+/// and converts it into a single ident, which contains dots (`.`)
 pub(super) fn dot_ident(s: &mut ParserContext) -> Result<Box<Ident>> {
     let p = path(s)?;
     let mut name = String::new();
@@ -93,6 +95,9 @@ pub(super) fn dot_ident(s: &mut ParserContext) -> Result<Box<Ident>> {
     }))
 }
 
+/// A `path` is a dot-separated list of idents like "Foo.Bar.Baz"
+/// this can be either a namespace name (in an open statement or namespace declaration) or
+/// it can be a direct reference to something in a namespace, like `Microsoft.Quantum.Diagnostics.DumpMachine()`
 pub(super) fn path(s: &mut ParserContext) -> Result<Box<Path>> {
     let lo = s.peek().span.lo;
     let mut parts = vec![ident(s)?];
diff --git a/compiler/qsc_passes/src/baseprofck.rs b/compiler/qsc_passes/src/baseprofck.rs
index b9284b3f..73650d33 100644
--- a/compiler/qsc_passes/src/baseprofck.rs
+++ b/compiler/qsc_passes/src/baseprofck.rs
@@ -7,12 +7,9 @@ mod tests;
 use miette::Diagnostic;
 use qsc_data_structures::span::Span;
 use qsc_hir::{
-    hir::{
-        BinOp, CallableKind, Expr, ExprKind, Item, ItemKind, Lit, Package, SpecBody, SpecGen,
-        StmtKind,
-    },
+    hir::{BinOp, CallableKind, Expr, ExprKind, Item, ItemKind, Lit, Package, SpecBody, SpecGen},
     ty::{Prim, Ty},
-    visit::{walk_expr, walk_item, walk_package, Visitor},
+    visit::{walk_expr, walk_item, Visitor},
 };
 use thiserror::Error;
 
@@ -65,16 +62,6 @@ struct Checker {
 }
 
 impl<'a> Visitor<'a> for Checker {
-    fn visit_package(&mut self, package: &'a Package) {
-        if let Some(StmtKind::Expr(expr)) = &package.stmts.last().map(|stmt| &stmt.kind) {
-            if any_non_result_ty(&expr.ty) {
-                self.errors.push(Error::ReturnNonResult(expr.span));
-            }
-        }
-
-        walk_package(self, package);
-    }
-
     fn visit_item(&mut self, item: &'a Item) {
         match &item.kind {
             ItemKind::Callable(callable)
diff --git a/compiler/qsc_passes/src/loop_unification.rs b/compiler/qsc_passes/src/loop_unification.rs
index 30ab6bf8..147bbb4e 100644
--- a/compiler/qsc_passes/src/loop_unification.rs
+++ b/compiler/qsc_passes/src/loop_unification.rs
@@ -147,7 +147,7 @@ impl LoopUni<'_> {
             Expr {
                 id: self.assigner.next_node(),
                 span: array_id.span,
-                ty: array_id.ty.clone(),
+                ty: Ty::Prim(Prim::Int),
                 kind: ExprKind::Call(
                     Box::new(len_callee),
                     Box::new(array_id.gen_local_ref(self.assigner)),
diff --git a/compiler/qsc_passes/src/replace_qubit_allocation.rs b/compiler/qsc_passes/src/replace_qubit_allocation.rs
index 3a40e5ad..4f92984c 100644
--- a/compiler/qsc_passes/src/replace_qubit_allocation.rs
+++ b/compiler/qsc_passes/src/replace_qubit_allocation.rs
@@ -480,10 +480,15 @@ fn create_qubit_alloc_call_expr(
     call_expr: Expr,
     array_size: Option<Expr>,
 ) -> Expr {
+    let ty = if array_size.is_none() {
+        Ty::Prim(Prim::Qubit)
+    } else {
+        Ty::Array(Box::new(Ty::Prim(Prim::Qubit)))
+    };
     Expr {
         id: assigner.next_node(),
         span,
-        ty: Ty::Prim(Prim::Qubit),
+        ty,
         kind: ExprKind::Call(
             Box::new(call_expr),
             Box::new(array_size.unwrap_or(Expr {
diff --git a/katas/content/complex_arithmetic/Common.qs b/katas/content/complex_arithmetic/Common.qs
new file mode 100644
index 00000000..e3ec47df
--- /dev/null
+++ b/katas/content/complex_arithmetic/Common.qs
@@ -0,0 +1,53 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Random;    
+    open Microsoft.Quantum.Convert;
+
+    operation DrawRandomComplex() : Complex {
+        // Generates a random complex number. 
+        let real = DrawRandomDouble(-10., 10.);
+        let imag = DrawRandomDouble(-10., 10.);
+        return Complex(real, imag);
+    }
+
+    operation ComplexAsString(x : Complex) : String {
+        if x::Imag < 0.0 {
+            $"{x::Real} - {AbsD(x::Imag)}i"
+        } else {
+            $"{x::Real} + {x::Imag}i"
+        }
+    }
+
+    operation ComplexPolarAsString(x : ComplexPolar) : String {
+        $"{x::Magnitude} * exp({x::Argument}i)"
+    }
+
+    operation CheckTwoComplexOpsAreSame(sol : (Complex, Complex) -> Complex, ref : (Complex, Complex) -> Complex) : Bool {
+        for _ in 0 .. 24 {
+            let x = DrawRandomComplex();
+            let y = DrawRandomComplex();
+
+            let expected = ref(x, y);
+            let actual = sol(x, y);
+        
+            if not ComplexEqual(expected, actual) {
+                Message("Incorrect");
+                Message($"For x = {ComplexAsString(x)}, y = {ComplexAsString(y)} expected return {ComplexAsString(expected)}, actual return {ComplexAsString(actual)}.");
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        return true;
+    }
+
+    function ComplexEqual(x : Complex, y : Complex) : Bool { 
+        // Tests two complex numbers for equality.
+        AbsD(x::Real - y::Real) <= 0.001 and AbsD(x::Imag - y::Imag) <= 0.001
+    }
+
+    function ComplexPolarEqual(x : ComplexPolar, y : ComplexPolar) : Bool { 
+        // Tests two complex polar numbers for equality.
+        AbsD(x::Magnitude - y::Magnitude) <= 0.001 and AbsD(x::Argument - y::Argument) <= 0.001
+    }
+}
diff --git a/katas/content/complex_arithmetic/cartesian_to_polar/Placeholder.qs b/katas/content/complex_arithmetic/cartesian_to_polar/Placeholder.qs
new file mode 100644
index 00000000..a77fa43c
--- /dev/null
+++ b/katas/content/complex_arithmetic/cartesian_to_polar/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexToComplexPolar(x : Complex) : ComplexPolar {
+        // Implement your solution here...
+        return ComplexPolar(0., 0.);
+    }
+}
diff --git a/katas/content/complex_arithmetic/cartesian_to_polar/Solution.qs b/katas/content/complex_arithmetic/cartesian_to_polar/Solution.qs
new file mode 100644
index 00000000..5144acad
--- /dev/null
+++ b/katas/content/complex_arithmetic/cartesian_to_polar/Solution.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexToComplexPolar(x : Complex) : ComplexPolar {
+        let (a, b) = x!;
+        return ComplexPolar(Sqrt(a * a + b * b), ArcTan2(b, a));
+    }
+}
diff --git a/katas/content/complex_arithmetic/cartesian_to_polar/Verification.qs b/katas/content/complex_arithmetic/cartesian_to_polar/Verification.qs
new file mode 100644
index 00000000..ec9e26ac
--- /dev/null
+++ b/katas/content/complex_arithmetic/cartesian_to_polar/Verification.qs
@@ -0,0 +1,23 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Convert;
+    open Microsoft.Quantum.Math;
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {        
+        for _ in 0 .. 24 {
+            let x = DrawRandomComplex();
+
+            let expected = ComplexAsComplexPolar(x);
+            let actual = Kata.ComplexToComplexPolar(x);
+        
+            if not ComplexPolarEqual(expected, actual) {
+                Message("Incorrect");
+                Message($"For x = {ComplexAsString(x)} expected return {ComplexPolarAsString(expected)}, actual return {ComplexPolarAsString(actual)}.");
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/katas/content/complex_arithmetic/cartesian_to_polar/index.md b/katas/content/complex_arithmetic/cartesian_to_polar/index.md
new file mode 100644
index 00000000..30cf726c
--- /dev/null
+++ b/katas/content/complex_arithmetic/cartesian_to_polar/index.md
@@ -0,0 +1,19 @@
+**Input:**
+A complex number $x = a + bi$.
+
+**Goal:**
+Return the polar representation of $x = re^{i\theta}$, i.e., the distance from origin $r$ and phase $\theta$ as a `ComplexPolar`.
+
+* $r$ should be non-negative: $r \geq 0$
+* $\theta$ should be between $-\pi$ and $\pi$: $-\pi < \theta \leq \pi$
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  
+A video explanation of this conversion can be found [here](https://www.youtube.com/watch?v=8RasCV_Lggg).
+
+  Q# namespace `Microsoft.Quantum.Math` includes a useful function `ArcTan2()`.
+
+</details>
+
+> Q# function `ComplexAsComplexPolar` from `Microsoft.Quantum.Math` namespace converts a complex number of type `Complex` to a complex number of type `ComplexPolar`. For educational purposes, try to do this task by hand.
diff --git a/katas/content/complex_arithmetic/cartesian_to_polar/solution.md b/katas/content/complex_arithmetic/cartesian_to_polar/solution.md
new file mode 100644
index 00000000..641f9b4c
--- /dev/null
+++ b/katas/content/complex_arithmetic/cartesian_to_polar/solution.md
@@ -0,0 +1,24 @@
+We need to calculate the $r$ and $\theta$ values as seen in the complex plane. 
+$r$ should be familiar to you already, since it is the modulus of a number (exercise 6):
+
+$$ r = \sqrt{a^2 + b^2} $$
+
+$\theta$ can be calculated using trigonometry: since we know that the polar and the Cartesian forms of the number represent the same value, we can write
+
+$$ re^{i \theta} = a + bi $$
+
+Euler's formula allows us to express the left part of the equation as 
+
+$$ re^{i \theta} = r \cos \theta + i r \sin \theta $$
+
+For two complex numbers to be equal, their real and imaginary parts have to be equal. This gives us the following system of equations:
+
+$$ \begin{cases} a = r \cos \theta \\ b = r \sin \theta \end{cases} $$
+
+To calculate $\theta$, we can divide the second equation by the first one to get
+
+$$ \tan \theta = \frac{b}{a} $$
+
+$$ \theta = \arctan \left(\frac{b}{a}\right) $$
+
+@[solution]({"id": "complex_arithmetic__cartesian_to_polar_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/complex_addition/Placeholder.qs b/katas/content/complex_arithmetic/complex_addition/Placeholder.qs
new file mode 100644
index 00000000..140b7c2c
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_addition/Placeholder.qs
@@ -0,0 +1,11 @@
+namespace Kata {    
+    open Microsoft.Quantum.Math;
+    
+    function ComplexAdd(x : Complex, y : Complex) : Complex {    
+        // Extract real and imaginary components of the inputs.
+        let (a, b) = x!;
+        let (c, d) = (y::Real, y::Imag);
+        // Implement your solution here...
+        return Complex(0., 0.);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_addition/Solution.qs b/katas/content/complex_arithmetic/complex_addition/Solution.qs
new file mode 100644
index 00000000..fd146333
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_addition/Solution.qs
@@ -0,0 +1,9 @@
+namespace Kata {    
+    open Microsoft.Quantum.Math;
+     
+    function ComplexAdd(x : Complex, y: Complex) : Complex {        
+        let (a, b) = x!;
+        let (c, d) = y!;
+        return Complex(a + c, b + d);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_addition/Verification.qs b/katas/content/complex_arithmetic/complex_addition/Verification.qs
new file mode 100644
index 00000000..55d061a7
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_addition/Verification.qs
@@ -0,0 +1,8 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Math;
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        CheckTwoComplexOpsAreSame(Kata.ComplexAdd, PlusC)
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_addition/index.md b/katas/content/complex_arithmetic/complex_addition/index.md
new file mode 100644
index 00000000..3488b6e2
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_addition/index.md
@@ -0,0 +1,17 @@
+**Inputs:**
+
+1. A complex number $x = a + bi$.
+2. A complex number $y = c + di$.
+
+**Goal:**
+Return the sum of $x$ and $y$ as a complex number.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  
+Adding complex numbers is just like adding polynomials. Add components of the same type - add the real part to the real part, add the imaginary part to the imaginary part.
+
+A video explanation of adding complex numbers can be found [here](https://www.youtube.com/watch?v=SfbjqVyQljk).
+</details>
+
+> Q# function `PlusC` from `Microsoft.Quantum.Math` namespace adds two complex numbers. For educational purposes, try to do this task by hand.
diff --git a/katas/content/complex_arithmetic/complex_addition/solution.md b/katas/content/complex_arithmetic/complex_addition/solution.md
new file mode 100644
index 00000000..ebb6dda5
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_addition/solution.md
@@ -0,0 +1,5 @@
+Adding two complex numbers can be done by separately adding the real parts of the numbers and the imaginary parts:  
+
+$$x + y = (a + bi) + (c + di) = \underset{real}{\underbrace{(a + c)}} + \underset{imaginary}{\underbrace{(b + d)}}i $$
+
+@[solution]({"id": "complex_arithmetic__complex_addition_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/complex_conjugate/Placeholder.qs b/katas/content/complex_arithmetic/complex_conjugate/Placeholder.qs
new file mode 100644
index 00000000..1c845617
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_conjugate/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {    
+    open Microsoft.Quantum.Math;
+    
+    function ComplexConjugate(x : Complex) : Complex {        
+        // Implement your solution here...
+        return Complex(0., 0.);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_conjugate/Solution.qs b/katas/content/complex_arithmetic/complex_conjugate/Solution.qs
new file mode 100644
index 00000000..607edf38
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_conjugate/Solution.qs
@@ -0,0 +1,7 @@
+namespace Kata {    
+    open Microsoft.Quantum.Math;    
+
+    operation ComplexConjugate(x : Complex) : Complex {
+        return Complex(x::Real, -x::Imag);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_conjugate/Verification.qs b/katas/content/complex_arithmetic/complex_conjugate/Verification.qs
new file mode 100644
index 00000000..4042b4f8
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_conjugate/Verification.qs
@@ -0,0 +1,27 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Math;
+
+    function ComplexConjugate_Reference(x : Complex) : Complex {
+        // Return the complex conjugate  
+        Complex(x::Real, -x::Imag)
+    }    
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {        
+        for _ in 0 .. 24 {
+            let x = DrawRandomComplex();
+
+            let expected = ComplexConjugate_Reference(x);
+            let actual = Kata.ComplexConjugate(x);
+        
+            if not ComplexEqual(expected, actual) {
+                Message("Incorrect");
+                Message($"For x = {ComplexAsString(x)} expected return {ComplexAsString(expected)}, actual return {ComplexAsString(actual)}.");
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/katas/content/complex_arithmetic/complex_conjugate/index.md b/katas/content/complex_arithmetic/complex_conjugate/index.md
new file mode 100644
index 00000000..58a7e136
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_conjugate/index.md
@@ -0,0 +1,12 @@
+**Input:**
+A complex number $x = a + bi$.
+
+**Goal:**
+Return the complex conjugate of $x$ $\overline{x} = a - bi$.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  
+A video explanation of the complex conjugate can be found [here](https://www.youtube.com/watch?v=BZxZ_eEuJBM).
+
+</details>
diff --git a/katas/content/complex_arithmetic/complex_conjugate/solution.md b/katas/content/complex_arithmetic/complex_conjugate/solution.md
new file mode 100644
index 00000000..4c11ac9f
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_conjugate/solution.md
@@ -0,0 +1,5 @@
+To get the complex conjugate of a complex number, you multiply the imaginary part of that complex number by $-1$:
+
+$$\overline{x} = a - bi$$
+
+@[solution]({"id": "complex_arithmetic__complex_conjugate_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/complex_division/Placeholder.qs b/katas/content/complex_arithmetic/complex_division/Placeholder.qs
new file mode 100644
index 00000000..721eb195
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_division/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;    
+    
+    function ComplexDiv(x : Complex, y : Complex) : Complex {
+        // Implement your solution here...
+        return Complex(0., 0.);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_division/Solution.qs b/katas/content/complex_arithmetic/complex_division/Solution.qs
new file mode 100644
index 00000000..4b9ad0ff
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_division/Solution.qs
@@ -0,0 +1,12 @@
+namespace Kata {    
+    open Microsoft.Quantum.Math;
+    
+    function ComplexDiv(x : Complex, y: Complex) : Complex {
+        let (a, b) = x!;
+        let (c, d) = y!;
+        let denominator = c * c + d * d;
+        let real = (a * c + b * d) / denominator;
+        let imag = (- a * d + b * c) / denominator;
+        return Complex(real, imag);
+    }
+}
\ No newline at end of file
diff --git a/katas/content/complex_arithmetic/complex_division/Verification.qs b/katas/content/complex_arithmetic/complex_division/Verification.qs
new file mode 100644
index 00000000..97d4e2aa
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_division/Verification.qs
@@ -0,0 +1,8 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Math;
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        CheckTwoComplexOpsAreSame(Kata.ComplexDiv, DividedByC)
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_division/index.md b/katas/content/complex_arithmetic/complex_division/index.md
new file mode 100644
index 00000000..11566a51
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_division/index.md
@@ -0,0 +1,15 @@
+**Inputs:**
+
+1. A complex number $x = a + bi$.
+2. A complex number $y = c + di \neq 0$.
+
+**Goal:** Return the result of the division $\frac{x}{y} = \frac{a + bi}{c + di}$.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  
+A video explanation of complex division can be found [here](https://www.youtube.com/watch?v=Z8j5RDOibV4).
+
+</details>
+
+> Q# function `DividedByC` from `Microsoft.Quantum.Math` namespace divides two complex numbers. For educational purposes, try to do this task by hand.
diff --git a/katas/content/complex_arithmetic/complex_division/solution.md b/katas/content/complex_arithmetic/complex_division/solution.md
new file mode 100644
index 00000000..68bcbc27
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_division/solution.md
@@ -0,0 +1,7 @@
+
+$$\frac{x}{y} = \frac{x}{y} \cdot 1 = \frac{x}{y} \cdot \frac{\overline{y}}{\overline{y}} = 
+    \frac{x\overline{y}}{y\overline{y}} = \frac{(a + bi)(c - di)}{(c + di)(c - di)} =$$
+$$= \frac{a \cdot c + bi \cdot c - a \cdot di - bi \cdot di}{c \cdot c + di \cdot c - c \cdot di - di \cdot di} =
+    \frac{(a \cdot c + b \cdot d) + (a \cdot (-d) + c \cdot b)i}{c^2 + d^2}$$
+
+@[solution]({"id": "complex_arithmetic__complex_division_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/complex_exponents/Placeholder.qs b/katas/content/complex_arithmetic/complex_exponents/Placeholder.qs
new file mode 100644
index 00000000..9db107a6
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_exponents/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexExponent(x : Complex) : Complex {
+        // Implement your solution here...
+        return Complex(0.0, 0.0);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_exponents/Solution.qs b/katas/content/complex_arithmetic/complex_exponents/Solution.qs
new file mode 100644
index 00000000..59221ef0
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_exponents/Solution.qs
@@ -0,0 +1,8 @@
+namespace Kata { 
+    open Microsoft.Quantum.Math;
+
+    function ComplexExponent (x : Complex) : Complex {
+        let (a, b) = x!;
+        return Complex(E()^a * Cos(b), E()^a * Sin(b));
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_exponents/Verification.qs b/katas/content/complex_arithmetic/complex_exponents/Verification.qs
new file mode 100644
index 00000000..bf1cbf2c
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_exponents/Verification.qs
@@ -0,0 +1,28 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Random;    
+    
+    function ComplexExponent_Reference(x : Complex) : Complex {
+        let expa = E() ^ x::Real;
+        return Complex(expa * Cos(x::Imag), expa * Sin(x::Imag));
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        for _ in 0 .. 24 {
+            let x = DrawRandomComplex();
+
+            let expected = ComplexExponent_Reference(x);
+            let actual = Kata.ComplexExponent(x);
+        
+            if not ComplexEqual(expected, actual) {
+                Message("Incorrect");
+                Message($"For x = {ComplexAsString(x)} expected return {ComplexAsString(expected)}, actual return {ComplexAsString(actual)}.");
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        return true;
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_exponents/index.md b/katas/content/complex_arithmetic/complex_exponents/index.md
new file mode 100644
index 00000000..c9a833a0
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_exponents/index.md
@@ -0,0 +1,8 @@
+**Input:** A complex number $x = a + bi$.
+
+**Goal:** Return the complex number $e^x = e^{a + bi}$.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  Q# namespace `Microsoft.Quantum.Math` includes a function `E()` that returns the value of the constant $e$.
+</details>
diff --git a/katas/content/complex_arithmetic/complex_exponents/solution.md b/katas/content/complex_arithmetic/complex_exponents/solution.md
new file mode 100644
index 00000000..d3a9a240
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_exponents/solution.md
@@ -0,0 +1,7 @@
+To start, we will rewrite the expression $e^{a + bi}$ as a product of two simpler expressions: $ e^a \cdot\ e^{bi} $.
+The first part is a real number.
+The second part can be expressed using the formula $e^{i\theta} = \cos \theta + i\sin \theta$.
+Substituting this into our expression gives:
+$$ e^a(\cos b + i\sin b) = \underset{real}{\underbrace{e^a \cos b}} + \underset{imaginary}{\underbrace{e^a \sin b}}i  $$
+
+@[solution]({"id": "complex_arithmetic__complex_exponents_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/complex_modulus/Placeholder.qs b/katas/content/complex_arithmetic/complex_modulus/Placeholder.qs
new file mode 100644
index 00000000..4150f055
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_modulus/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexModulus(x : Complex) : Double {
+        // Implement your solution here...
+        return 0.;
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_modulus/Solution.qs b/katas/content/complex_arithmetic/complex_modulus/Solution.qs
new file mode 100644
index 00000000..15655865
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_modulus/Solution.qs
@@ -0,0 +1,8 @@
+namespace Kata { 
+    open Microsoft.Quantum.Math;
+
+    function ComplexModulus(x : Complex) : Double {
+        let (a, b) = x!;
+        return Sqrt(a * a + b * b);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_modulus/Verification.qs b/katas/content/complex_arithmetic/complex_modulus/Verification.qs
new file mode 100644
index 00000000..f29ba78c
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_modulus/Verification.qs
@@ -0,0 +1,22 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Math;
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        for _ in 0 .. 24 {
+            let x = DrawRandomComplex();
+
+            let expected = AbsComplex(x);
+            let actual = Kata.ComplexModulus(x);
+        
+            if  AbsD(expected - actual) > 1e-6 {
+                Message("Incorrect");
+                Message($"For x = {ComplexAsString(x)} expected return {expected}, actual return {actual}.");
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        return true;
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_modulus/index.md b/katas/content/complex_arithmetic/complex_modulus/index.md
new file mode 100644
index 00000000..fd51808b
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_modulus/index.md
@@ -0,0 +1,12 @@
+**Input:** A complex number $x = a + bi$.
+
+**Goal:** Return the modulus of this number, $|x|$.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+
+  A video explanation of absolute value and complex numbers can be found [here](https://www.youtube.com/watch?v=FwuPXchH2rA).
+
+</details>
+
+> Q# function `AbsComplex` from `Microsoft.Quantum.Math` namespace gets the absolute value of a complex number. For educational purposes, try to do this task by hand.
diff --git a/katas/content/complex_arithmetic/complex_modulus/solution.md b/katas/content/complex_arithmetic/complex_modulus/solution.md
new file mode 100644
index 00000000..f7444ac5
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_modulus/solution.md
@@ -0,0 +1,3 @@
+The modulus of the complex number can be seen as the distance from the origin 0 to the point $x$ in the complex plane. This can be calculated using the Pythagorean theorem: $c^2 = a^2 + b^2$, which means $ c = \sqrt{a^2 + b^2}$
+
+@[solution]({"id": "complex_arithmetic__complex_modulus_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/complex_multiplication/Placeholder.qs b/katas/content/complex_arithmetic/complex_multiplication/Placeholder.qs
new file mode 100644
index 00000000..e6125e39
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_multiplication/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexMult(x : Complex, y: Complex) : Complex {
+        // Implement your solution here...
+        return Complex(0., 0.);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_multiplication/Solution.qs b/katas/content/complex_arithmetic/complex_multiplication/Solution.qs
new file mode 100644
index 00000000..e3f2c997
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_multiplication/Solution.qs
@@ -0,0 +1,9 @@
+namespace Kata {    
+    open Microsoft.Quantum.Math;
+    
+    function ComplexMult(x : Complex, y: Complex) : Complex {
+        let (a, b) = x!;
+        let (c, d) = y!;
+        return Complex(a * c - b * d, a * d + b * c);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_multiplication/Verification.qs b/katas/content/complex_arithmetic/complex_multiplication/Verification.qs
new file mode 100644
index 00000000..c5f67481
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_multiplication/Verification.qs
@@ -0,0 +1,8 @@
+namespace Kata.Verification {        
+    open Microsoft.Quantum.Math;
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        CheckTwoComplexOpsAreSame(Kata.ComplexMult, TimesC)
+    }
+}
\ No newline at end of file
diff --git a/katas/content/complex_arithmetic/complex_multiplication/index.md b/katas/content/complex_arithmetic/complex_multiplication/index.md
new file mode 100644
index 00000000..7818d250
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_multiplication/index.md
@@ -0,0 +1,20 @@
+**Inputs:**
+
+1. A complex number $x = a + bi$.
+2. A complex number $y = c + di$.
+
+**Goal:**
+Return the product of $x$ and $y$ as a complex number.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  
+Multiplying complex numbers is just like multiplying polynomials. Distribute one of the complex numbers: 
+$$(a + bi)(c + di) = a(c + di) + bi(c + di)$$ 
+Then multiply through, keeping in mind that $i^2=-1$, and group the real and imaginary terms together.
+
+A video explanation of multiplying complex numbers can be found [here](https://www.youtube.com/watch?v=cWn6g8Qqvs4).
+
+</details>
+
+> Q# function `TimesC` from `Microsoft.Quantum.Math` namespace multiplies two complex numbers. For educational purposes, try to do this task by hand.
diff --git a/katas/content/complex_arithmetic/complex_multiplication/solution.md b/katas/content/complex_arithmetic/complex_multiplication/solution.md
new file mode 100644
index 00000000..9d5e0b9d
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_multiplication/solution.md
@@ -0,0 +1,5 @@
+Multiplying complex numbers is just like multiplying polynomials. Distribute one of the complex numbers, then multiply through (keep in mind that $i^2 =-1$), and group the real and imaginary terms together:
+
+$x \cdot y = (a + bi)(c + di) = a \cdot c + a \cdot di + c \cdot bi + bi \cdot di = \underset{real}{\underbrace{a \cdot c - b \cdot d}} + \underset{imaginary}{\underbrace{(a \cdot d + c \cdot b)}}i $
+
+@[solution]({"id": "complex_arithmetic__complex_multiplication_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/complex_powers_real/Placeholder.qs b/katas/content/complex_arithmetic/complex_powers_real/Placeholder.qs
new file mode 100644
index 00000000..ce5f5669
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_powers_real/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+
+    function ComplexExpReal (r : Double, x : Complex) : Complex {
+        // Implement your solution here...
+        return Complex(0.0, 0.0);
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_powers_real/Solution.qs b/katas/content/complex_arithmetic/complex_powers_real/Solution.qs
new file mode 100644
index 00000000..f78649a7
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_powers_real/Solution.qs
@@ -0,0 +1,14 @@
+namespace Kata { 
+    open Microsoft.Quantum.Math;
+
+    function ComplexExpReal(r : Double, x : Complex) : Complex {
+        if AbsD(r) < 1e-9 {
+            return Complex(0.0, 0.0);
+        }
+        
+        let (a, b) = x!;        
+        let ra = r ^ a;
+        let lnr = Log(r);
+        return Complex(ra * Cos(b * lnr), ra * Sin(b * lnr));
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_powers_real/Verification.qs b/katas/content/complex_arithmetic/complex_powers_real/Verification.qs
new file mode 100644
index 00000000..c0e2586c
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_powers_real/Verification.qs
@@ -0,0 +1,33 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Math;
+    open Microsoft.Quantum.Random;    
+    
+    function ComplexExpReal_Reference(r : Double, x : Complex) : Complex {
+        if AbsD(r) < 1e-9 {
+            return Complex(0.0, 0.0);
+        }
+        let real = r ^ x::Real * Cos(x::Imag * Log(r));
+        let imaginary = r ^ x::Real * Sin(x::Imag * Log(r));
+        return Complex(real, imaginary);
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        for ind in 0 .. 24 {
+            let x = DrawRandomComplex();
+            let r = ind == 0 ? 0.0 | DrawRandomDouble(0., 10.); 
+
+            let expected = ComplexExpReal_Reference(r, x); 
+            let actual = Kata.ComplexExpReal(r, x);        
+        
+            if not ComplexEqual(expected, actual) {
+                Message("Incorrect");
+                Message($"For x = {ComplexAsString(x)} and r = {r} expected return {ComplexAsString(expected)}, actual return {ComplexAsString(actual)}.");
+                return false;
+            }                
+        }            
+
+        Message("Correct!");
+        return true;        
+    }
+}
diff --git a/katas/content/complex_arithmetic/complex_powers_real/index.md b/katas/content/complex_arithmetic/complex_powers_real/index.md
new file mode 100644
index 00000000..7b1fa942
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_powers_real/index.md
@@ -0,0 +1,13 @@
+**Inputs:**
+
+1. A non-negative real number $r$.
+2. A complex number $x = a + bi$.
+
+**Goal:** Return the complex number $r^x = r^{a + bi}$.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  You can use the fact that $r = e^{\ln r}$ to convert exponent bases. Remember though, $\ln r$ is only defined for positive numbers - make sure to check for $r = 0$ separately!
+
+  Q# namespace `Microsoft.Quantum.Math` includes useful functions `Log()`, `Sin()`, and `Cos()`.
+</details>
diff --git a/katas/content/complex_arithmetic/complex_powers_real/solution.md b/katas/content/complex_arithmetic/complex_powers_real/solution.md
new file mode 100644
index 00000000..a6f2eb88
--- /dev/null
+++ b/katas/content/complex_arithmetic/complex_powers_real/solution.md
@@ -0,0 +1,14 @@
+Rewrite the expression $r^x$ to use Euler's constant, which will allow us to use an approach similar to the solution to the previous exercise.
+
+First, rewrite $r^x$ into a product of two powers: $$ r^{a+bi} = r^a \cdot r^{bi} $$
+
+Given that $r = e^{\ln r} $ ($\ln$ is the natural logarithm), we can rewrite the second part of the product as follows: 
+    $$ r^{bi} =  e^{bi\ln r} $$
+
+Now, given $e^{i\theta} = \cos \theta + i\sin \theta$, we can rewrite it further as follows: 
+    $$ e^{bi\ln r} = \cos( b \cdot \ln r) + i \sin(b \cdot \ln r) $$
+
+When substituting this into the original expression, we get:
+    $$ \underset{real}{\underbrace{r^a \cos(b \cdot \ln r)}} + \underset{imaginary}{\underbrace{r^a \sin(b \cdot \ln r)}} i $$
+
+@[solution]({"id": "complex_arithmetic__complex_powers_real_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/index.md b/katas/content/complex_arithmetic/index.md
new file mode 100644
index 00000000..a927cea3
--- /dev/null
+++ b/katas/content/complex_arithmetic/index.md
@@ -0,0 +1,299 @@
+# Complex Arithmetic
+
+@[section]({
+    "id": "complex_arithmetic__overview", 
+    "title": "Overview" 
+})
+
+This kata introduces you to complex arithmetic. This topic isn't particularly expansive, but it's important to understand it to be able to work with quantum computing.
+
+This tutorial covers the following topics:
+
+* Imaginary and complex numbers
+* Basic complex arithmetic
+* Complex plane
+* Modulus operator
+* Imaginary exponents
+* Polar representation
+
+If you are curious to learn more, you can find more information at [Wikipedia](https://en.wikipedia.org/wiki/Complex_number).
+
+This kata has several tasks that require you to write Q# code to test your understanding of the concepts. It will introduce the necessary Q# language constructs as it goes.
+
+@[section]({ 
+    "id": "complex_arithmetic__algebraic_perspective", 
+    "title": "Imaginary Numbers" 
+})
+
+For some purposes, real numbers aren't enough. Probably the most famous example is the equation:
+
+$$x^{2} = -1$$
+
+This equation has no solution among real numbers. If, however, we abandon that constraint, we can do something interesting - we can define our own number. Let's say there exists some number that solves that equation. Let's call that number $i$.
+
+$$i^2 = -1$$
+
+As we said before, $i$ can't be a real number. In that case, we'll call it an **imaginary unit**. However, there is no reason for us to define it as acting any different from any other number, other than the fact that $i^2 = -1$:
+
+$$i+i=2i$$
+
+$$i-i=0$$
+
+$$-1 \\cdot i=-i$$
+
+$$(-i)^{2} = -1$$
+
+We'll call the number $i$ and its real multiples (numbers obtained by multiplying $i$ by a real number) **imaginary numbers**.
+
+> A good video introduction to imaginary numbers can be found [here](https://youtu.be/SP-YJe7Vldo).
+
+@[exercise]({ 
+    "id": "complex_arithmetic__powers_of_i", 
+    "title": "Powers of $i$", 
+    "path": "./powers_of_i/", 
+    "qsDependencies": [] 
+})
+
+@[section]({ 
+    "id": "complex_arithmetic__complex_numbers", 
+    "title": "Complex Numbers" 
+})
+
+Adding imaginary numbers to each other is quite simple, but what happens when we add a real number to an imaginary number? The result of that addition will be partly real and partly imaginary, otherwise known as a **complex number**. A complex number is simply the real part and the imaginary part being treated as a single number. Complex numbers are generally written as the sum of their two parts $a$ and $bi$, where both $a$ and $b$ are real numbers: 
+
+$$a+bi$$
+
+For example, $3+4i$ or $-5-7i$ are valid complex numbers. Note that purely real or purely imaginary numbers can also be written as complex numbers: $2$ is $2+0i$, and $-3i$ is $0-3i$.
+
+When performing operations on complex numbers, it is often helpful to treat them as polynomials in terms of $i$.
+Let's see how to do the main arithmetic operations on complex numbers.
+
+> In Q#, complex numbers are represented as user-defined type `Complex` from the `Microsoft.Quantum.Math` namespace. 
+> 
+> You can convert a complex number $x = a + bi$ into a tuple of two `Double` numbers using unwrap operator and tuple deconstruction: `let (a, b) = x!;`, 
+> or access its real and imaginary parts using their names: `let (a, b) = (x::Real, x::Imag);`.
+> 
+> You can construct a complex number from its real and imaginary parts as follows: `let x = Complex(a, b);`.
+
+@[exercise]({ 
+    "id": "complex_arithmetic__complex_addition", 
+    "title": "Complex addition", 
+    "path": "./complex_addition/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[exercise]({ 
+    "id": "complex_arithmetic__complex_multiplication", 
+    "title": "Complex Multiplication", 
+    "path": "./complex_multiplication/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[section]({ 
+    "id": "complex_arithmetic__complex_conjugate", 
+    "title": "Complex Conjugate" 
+})
+
+Before we discuss any other operations on complex numbers, we have to cover the **complex conjugate**. The conjugate is a simple operation: given a complex number  $x = a + bi$, its complex conjugate is $\overline{x} = a - bi$.
+
+The conjugate allows us to do some interesting things. The first and probably most important is multiplying a complex number by its conjugate:
+
+$$x \cdot \overline{x} = (a + bi)(a - bi)$$
+
+Notice that the second expression is a difference of squares:
+
+$$(a + bi)(a - bi) = a^2 - (bi)^2 = a^2 - b^2i^2 = a^2 + b^2$$
+
+This means that a complex number multiplied by its conjugate always produces a non-negative real number.
+
+Another property of the conjugate is that it distributes over both complex addition and complex multiplication:
+
+$$\overline{x + y} = \overline{x} + \overline{y}$$
+$$\overline{x \cdot y} = \overline{x} \cdot \overline{y}$$
+
+@[exercise]({ 
+    "id": "complex_arithmetic__complex_conjugate_exercise", 
+    "title": "Complex Conjugate", 
+    "path": "./complex_conjugate/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[section]({ 
+    "id": "complex_arithmetic__complex_division", 
+    "title": "Complex Division" 
+})
+
+The next use for the conjugate is complex division. Let's take two complex numbers: $x = a + bi$ and $y = c + di \neq 0$ (not even complex numbers let you divide by $0$). What does $\frac{x}{y}$ mean?
+
+Let's expand $x$ and $y$ into their component forms:
+
+$$\frac{x}{y} = \frac{a + bi}{c + di}$$
+
+Unfortunately, it isn't very clear what it means to divide by a complex number. We need some way to move either all real parts or all imaginary parts into the numerator. And thanks to the conjugate, we can do just that. Using the fact that any number (except $0$) divided by itself equals $1$, and any number multiplied by $1$ equals itself, we get:
+
+$$\frac{x}{y} = \frac{x}{y} \cdot 1 = \frac{x}{y} \cdot \frac{\overline{y}}{\overline{y}} = \frac{x\overline{y}}{y\overline{y}} = \frac{(a + bi)(c - di)}{(c + di)(c - di)} = \frac{(a + bi)(c - di)}{c^2 + d^2}$$
+
+By doing this, we re-wrote our division problem to have a complex multiplication expression in the numerator, and a real number in the denominator. We already know how to multiply complex numbers, and dividing a complex number by a real number is as simple as dividing both parts of the complex number separately:
+
+$$\frac{a + bi}{r} = \frac{a}{r} + \frac{b}{r}i$$
+
+@[exercise]({ 
+    "id": "complex_arithmetic__complex_division_exercise", 
+    "title": "Complex division", 
+    "path": "./complex_division/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[section]({ 
+    "id": "complex_arithmetic__geometric_perspective", 
+    "title": "Geometric Perspective: the Complex Plane" 
+})
+
+You may recall that real numbers can be represented geometrically using the number line - a line on which each point represents a real number. We can extend this representation to include imaginary and complex numbers, which gives rise to an entirely different number line: the imaginary number line, which is orthogonal to the real number line and only intersects with it at $0$.
+
+A complex number has two components - a real component and an imaginary component. As you no doubt noticed from the exercises, these can be represented by two real numbers - the real component, and the real coefficient of the imaginary component. This allows us to map complex numbers onto a two-dimensional plane - the **complex plane**. The most common mapping is the obvious one: $a+bi$ can be represented by the point $(a,b)$ in the **Cartesian coordinate system**.
+
+This mapping allows us to apply complex arithmetic to geometry, and, more importantly, apply geometric concepts to complex numbers. Many properties of complex numbers become easier to understand when viewed through a geometric lens.
+
+## Modulus
+
+One such property is the **modulus operator**. This operator generalizes the **absolute value** operator on real numbers to the complex plane. Just like the absolute value of a number is its distance from $0$, the modulus of a complex number is its distance from $0+0i$. Using the distance formula, if $x=a+bi$, then:
+
+$$|x| = \sqrt{a^2 + b^2}$$
+
+There is also a slightly different, but algebraically equivalent definition: 
+
+$$|x| = \sqrt{x \cdot \overline{x}}$$
+
+Like the conjugate, the modulus distributes over multiplication.
+
+$$|x \cdot y| = |x| \cdot |y|$$
+
+Unlike the conjugate, however, the modulus doesn't distribute over addition. Instead, the interaction of the two comes from the triangle inequality:
+
+$$|x + y| \leq |x| + |y|$$
+
+@[exercise]({ 
+    "id": "complex_arithmetic__complex_modulus_exercise", 
+    "title": "Complex Modulus", 
+    "path": "./complex_modulus/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[section]({ 
+    "id": "complex_arithmetic__imaginary_exponents", 
+    "title": "Imaginary Exponents" 
+})
+
+The next complex operation we're going to need is exponentiation. Raising an imaginary number to an integer power is a fairly simple task, but raising a number to an imaginary power, or raising an imaginary (or complex) number to a real power isn't quite as simple.
+
+Let's start with raising real numbers to imaginary powers. Specifically, let's start with a rather special real number - Euler's constant, $e$:
+
+$$e^{i\theta} = \cos \theta + i\sin \theta$$
+
+Here and later in this tutorial $\theta$ is measured in radians.
+
+> Explaining why that happens is somewhat beyond the scope of this tutorial, as it requires some calculus, so we won't do that here. If you are curious, you can see [this video](https://youtu.be/v0YEaeIClKY) for a beautiful intuitive explanation, or [this Wikipedia article](https://en.wikipedia.org/wiki/Complex_number) for a more mathematically rigorous proof.
+
+Here are some examples of this formula in action:
+
+$$e^{i\pi/4} = \frac{1}{\sqrt{2}} + \frac{i}{\sqrt{2}}$$
+$$e^{i\pi/2} = i$$
+$$e^{i\pi} = -1$$
+$$e^{2i\pi} = 1$$
+
+> One interesting consequence of this is Euler's identity:
+> 
+> $$e^{i\pi} + 1 = 0$$
+> 
+> While this doesn't have any notable uses, it is still an interesting identity to consider, as it combines five fundamental constants of algebra into one expression.
+
+We can also calculate complex powers of $e$ as follows:
+
+$$e^{a + bi} = e^a \cdot e^{bi}$$
+
+Finally, using logarithms to express the base of the exponent as $r = e^{\ln r}$, we can use this to find complex powers of any positive real number.
+
+@[exercise]({ 
+    "id": "complex_arithmetic__complex_exponents_exercise", 
+    "title": "Complex Exponents", 
+    "path": "./complex_exponents/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[exercise]({ 
+    "id": "complex_arithmetic__complex_powers_real_exercise", 
+    "title": "Complex powers of real numbers", 
+    "path": "./complex_powers_real/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[section]({ 
+    "id": "complex_arithmetic__polar_coordinates", 
+    "title": "Polar Coordinates" 
+})
+
+Consider the expression  $e^{i\theta} = \cos\theta + i\sin\theta$. Notice that if we map this number onto the complex plane, it will land on a **unit circle** around $0 + 0i$. This means that its modulus is always $1$. You can also verify this algebraically: $\cos^2\theta + \sin^2\theta = 1$.
+
+Using this fact we can represent complex numbers using **polar coordinates**. In a polar coordinate system, a point is represented by two numbers: its direction from origin, represented by an angle from the $x$ axis, and how far away it is in that direction.
+
+Another way to think about this is that we're taking a point that is $1$ unit away (which is on the unit circle) in the specified direction, and multiplying it by the desired distance. And to get the point on the unit circle, we can use $e^{i\theta}$.
+
+A complex number of the format $r \cdot e^{i\theta}$ will be represented by a point which is $r$ units away from the origin, in the direction specified by the angle $\theta$.
+Sometimes $\theta$ will be referred to as the number's **argument** or **phase**.
+
+> In Q#, complex numbers in polar form are represented as user-defined type `ComplexPolar` from the `Microsoft.Quantum.Math` namespace. 
+> 
+> You can convert a complex number $x = $r \cdot e^{i\theta}$ into a tuple of two `Double` numbers using unwrap operator and tuple deconstruction: `let (r, theta) = x!;`, 
+> or access its real and imaginary parts using their names: `let (r, theta) = (x::Magnitude, x::Argument);`.
+> 
+> You can construct a complex number from its real and imaginary parts as follows: `let x = ComplexPolar(r, theta);`.
+
+@[exercise]({ 
+    "id": "complex_arithmetic__cartesian_to_polar", 
+    "title": "Cartesian to Polar Conversion", 
+    "path": "./cartesian_to_polar/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[exercise]({ 
+    "id": "complex_arithmetic__polar_to_cartesian", 
+    "title": "Polar to Cartesian Conversion", 
+    "path": "./polar_to_cartesian/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+@[exercise]({ 
+    "id": "complex_arithmetic__polar_multiplication", 
+    "title": "Polar Multiplication", 
+    "path": "./polar_multiplication/", 
+    "qsDependencies": [ 
+        "./Common.qs"
+    ] 
+})
+
+
+@[section]({
+    "id": "complex_arithmetic__conclusion", 
+    "title": "Conclusion" 
+})
+
+Congratulations! You should now know enough complex arithmetic to get started with quantum computing!
diff --git a/katas/content/complex_arithmetic/polar_multiplication/Placeholder.qs b/katas/content/complex_arithmetic/polar_multiplication/Placeholder.qs
new file mode 100644
index 00000000..de8b60e8
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_multiplication/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexPolarMult(x : ComplexPolar, y: ComplexPolar) : ComplexPolar {
+        // Implement your solution here...
+        return ComplexPolar(0., 0.);
+    }
+}
diff --git a/katas/content/complex_arithmetic/polar_multiplication/Solution.qs b/katas/content/complex_arithmetic/polar_multiplication/Solution.qs
new file mode 100644
index 00000000..618e5062
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_multiplication/Solution.qs
@@ -0,0 +1,16 @@
+namespace Kata {    
+    open Microsoft.Quantum.Math;
+    
+    function ComplexPolarMult(x : ComplexPolar, y: ComplexPolar) : ComplexPolar {
+        let (r1, theta1) = x!;
+        let (r2, theta2) = y!;
+        mutable theta3 = theta1 + theta2;
+        if theta3 > PI() {
+            set theta3 -= 2.0 * PI();
+        }
+        if theta3 < -PI() {
+            set theta3 += 2.0 * PI();
+        }
+        return ComplexPolar(r1 * r2, theta3);
+    }
+}
diff --git a/katas/content/complex_arithmetic/polar_multiplication/Verification.qs b/katas/content/complex_arithmetic/polar_multiplication/Verification.qs
new file mode 100644
index 00000000..1d6c3e51
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_multiplication/Verification.qs
@@ -0,0 +1,27 @@
+namespace Kata.Verification {        
+    open Microsoft.Quantum.Convert;
+    open Microsoft.Quantum.Math;
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        for _ in 0 .. 24 {
+            let x = DrawRandomComplex();
+            let y = DrawRandomComplex();
+            let xp = ComplexAsComplexPolar(x);
+            let yp = ComplexAsComplexPolar(y);
+
+            let expected = ComplexAsComplexPolar(TimesC(x, y));
+            let actual = Kata.ComplexPolarMult(xp, yp);
+        
+            if not ComplexPolarEqual(expected, actual) {
+                Message("Incorrect");
+                Message($"For x = {ComplexPolarAsString(xp)}, y = {ComplexPolarAsString(yp)} " + 
+                    $"expected return {ComplexPolarAsString(expected)}, actual return {ComplexPolarAsString(actual)}.");
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/katas/content/complex_arithmetic/polar_multiplication/index.md b/katas/content/complex_arithmetic/polar_multiplication/index.md
new file mode 100644
index 00000000..34a9b048
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_multiplication/index.md
@@ -0,0 +1,21 @@
+**Inputs:**
+
+1. A complex polar number $x = r_{1}e^{i\theta_1}$.
+2. A complex polar number $y = r_{2}e^{i\theta_2}$.
+
+**Goal:**
+Return the product of $x$ and $y$ as a complex polar number $x \cdot y = r_{3}e^{i\theta_3}$.
+
+* $r_3$ should be non-negative: $r_3 \geq 0$
+* $\theta_3$ should be between $-\pi$ and $\pi$: $-\pi < \theta_3 \leq \pi$
+* Try to avoid converting the numbers into their Cartesian form.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  
+  Remember, a number written in polar form already involves multiplication. What is $r_1e^{i\theta_1} \cdot r_2e^{i\theta_2}$?
+
+  Is the value of $\theta$ in the product incorrect? Remember you might have to check your boundaries and adjust it to be in the range requested.
+</details>
+
+> Q# function `TimesCP` from `Microsoft.Quantum.Math` namespace multiplies two complex numbers, but it doesn't normalize the argument of the resulting number. For educational purposes, try to do this task by hand.
diff --git a/katas/content/complex_arithmetic/polar_multiplication/solution.md b/katas/content/complex_arithmetic/polar_multiplication/solution.md
new file mode 100644
index 00000000..3dabd237
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_multiplication/solution.md
@@ -0,0 +1,18 @@
+Multiplying two complex numbers in polar form can be done efficiently in the following way:
+
+$$ z = x \cdot y = r_{1}e^{\theta_1 i} \cdot r_{2}e^{\theta_2 i} = r_{1}r_{2} \cdot e^{(\theta_1 + \theta_2)i} $$  
+
+> Here is the longer approach of converting the numbers to the Cartesian from and doing multiplication in it:  
+> $$ x = r_{1}e^{i\theta_1} = r_{1}(\cos \theta_1 + i \sin \theta_1) $$
+> $$ y = r_{2}e^{i\theta_2} = r_{2}(\cos \theta_2 + i \sin \theta_2) $$
+> $$ z = x \cdot y = r_1r_2 \cdot \left( \cos \theta_1 \cos \theta_2 − \sin \theta_1 \sin \theta_2 + 
+i (\sin \theta_1 \cos \theta_2 + \sin \theta_2 \cos \theta_1 ) \right) $$
+>
+> We can simplify this using the following trigonometric identities:
+> * $\cos a \cos b  \mp \sin a \sin b = \cos(a \pm b)$
+> * $\sin a \cos b  \pm \sin b \cos a = \sin(a \pm b)$
+>
+> Finally, this solution gives the same answer as the short solution above:
+>$$r_{1}r_{2}(\cos(\theta_1 + \theta_2) + i \sin(\theta_1 + \theta_2)) = r_{1}r_{2} \cdot e^{(\theta_1 + \theta_2)i} $$
+
+@[solution]({"id": "complex_arithmetic__polar_multiplication_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/polar_to_cartesian/Placeholder.qs b/katas/content/complex_arithmetic/polar_to_cartesian/Placeholder.qs
new file mode 100644
index 00000000..dd41f307
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_to_cartesian/Placeholder.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexPolarToComplex(x : ComplexPolar) : Complex {
+        // Implement your solution here...
+        return Complex(0., 0.);
+    }
+}
diff --git a/katas/content/complex_arithmetic/polar_to_cartesian/Solution.qs b/katas/content/complex_arithmetic/polar_to_cartesian/Solution.qs
new file mode 100644
index 00000000..681f2af5
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_to_cartesian/Solution.qs
@@ -0,0 +1,8 @@
+namespace Kata {
+    open Microsoft.Quantum.Math;
+    
+    function ComplexPolarToComplex(x : ComplexPolar) : Complex {
+        let (r, theta) = x!;
+        return Complex(r * Cos(theta), r * Sin(theta));
+    }
+}
diff --git a/katas/content/complex_arithmetic/polar_to_cartesian/Verification.qs b/katas/content/complex_arithmetic/polar_to_cartesian/Verification.qs
new file mode 100644
index 00000000..24dbac48
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_to_cartesian/Verification.qs
@@ -0,0 +1,23 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Convert;
+    open Microsoft.Quantum.Math;
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {        
+        for _ in 0 .. 24 {
+            let x = ComplexAsComplexPolar(DrawRandomComplex());
+
+            let expected = ComplexPolarAsComplex(x);
+            let actual = Kata.ComplexPolarToComplex(x);
+        
+            if not ComplexEqual(expected, actual) {
+                Message("Incorrect");
+                Message($"For x = {ComplexPolarAsString(x)} expected return {ComplexAsString(expected)}, actual return {ComplexAsString(actual)}.");
+                return false;
+            }
+        }
+
+        Message("Correct!");
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/katas/content/complex_arithmetic/polar_to_cartesian/index.md b/katas/content/complex_arithmetic/polar_to_cartesian/index.md
new file mode 100644
index 00000000..a8cbb5b7
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_to_cartesian/index.md
@@ -0,0 +1,14 @@
+**Input:**
+A complex number in polar form $x = re^{i\theta}$.
+
+**Goal:**
+Return the Cartesian representation of $x = a + bi$ as a `Complex`.
+
+<details>
+  <summary><b>Need a hint?</b></summary>
+  
+A video explanation of this conversion can be found [here](https://www.youtube.com/watch?v=auywa7dydAk).
+
+</details>
+
+> Q# function `ComplexPolarAsComplex` from `Microsoft.Quantum.Math` namespace converts a complex number of type `ComplexPolar` to a complex number of type `Complex`. For educational purposes, try to do this task by hand.
diff --git a/katas/content/complex_arithmetic/polar_to_cartesian/solution.md b/katas/content/complex_arithmetic/polar_to_cartesian/solution.md
new file mode 100644
index 00000000..17a0a34d
--- /dev/null
+++ b/katas/content/complex_arithmetic/polar_to_cartesian/solution.md
@@ -0,0 +1,5 @@
+Euler's formula allows us to express the complex polar number as 
+
+$$ re^{i \theta} = \underset{real}{\underbrace{r \cos \theta}} + i \underset{imaginary}{\underbrace{r \sin \theta}}$$
+
+@[solution]({"id": "complex_arithmetic__polar_to_cartesian_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/complex_arithmetic/powers_of_i/Placeholder.qs b/katas/content/complex_arithmetic/powers_of_i/Placeholder.qs
new file mode 100644
index 00000000..27a55d1d
--- /dev/null
+++ b/katas/content/complex_arithmetic/powers_of_i/Placeholder.qs
@@ -0,0 +1,10 @@
+namespace Kata {
+    function PowersOfI(n : Int) : Int {
+        // Replace the return values with correct answers.
+        if n % 4 == 0 {
+            return 0;
+        } else {
+            return 0;
+        }
+   }
+}
diff --git a/katas/content/complex_arithmetic/powers_of_i/Solution.qs b/katas/content/complex_arithmetic/powers_of_i/Solution.qs
new file mode 100644
index 00000000..641da6eb
--- /dev/null
+++ b/katas/content/complex_arithmetic/powers_of_i/Solution.qs
@@ -0,0 +1,10 @@
+namespace Kata {
+    function PowersOfI(n : Int) : Int {
+        // If n is divisible by 4
+        if n % 4 == 0 {
+            return 1;
+        } else {
+            return -1;
+        }
+    }
+}
diff --git a/katas/content/complex_arithmetic/powers_of_i/Verification.qs b/katas/content/complex_arithmetic/powers_of_i/Verification.qs
new file mode 100644
index 00000000..2eb2d420
--- /dev/null
+++ b/katas/content/complex_arithmetic/powers_of_i/Verification.qs
@@ -0,0 +1,26 @@
+namespace Kata.Verification {
+   
+    operation PowersOfI_Reference(n : Int) : Int{
+        // If n is divisible by 4
+        if n % 4 == 0 { 
+            return 1;
+        } else {
+            return -1;
+        }
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        for n in -20 .. 2 .. 20 {   
+            let expected = PowersOfI_Reference(n);
+            let actual = Kata.PowersOfI(n);
+            if expected != actual {
+                Message("Incorrect.");
+                Message($"Result of exponentiation doesn't match expected value: expected i^({n}) = {expected}, got {actual}");
+                return false; 
+            }
+        }
+        Message("Correct!");
+        return true; 
+    }
+}
diff --git a/katas/content/complex_arithmetic/powers_of_i/index.md b/katas/content/complex_arithmetic/powers_of_i/index.md
new file mode 100644
index 00000000..d83ca757
--- /dev/null
+++ b/katas/content/complex_arithmetic/powers_of_i/index.md
@@ -0,0 +1,3 @@
+**Input:** An even integer $n$ (can be negative).
+
+**Goal:** Return the $n$-th power of $i$ ($i^n$).
diff --git a/katas/content/complex_arithmetic/powers_of_i/solution.md b/katas/content/complex_arithmetic/powers_of_i/solution.md
new file mode 100644
index 00000000..5e3ef149
--- /dev/null
+++ b/katas/content/complex_arithmetic/powers_of_i/solution.md
@@ -0,0 +1,20 @@
+When raising $i$ to an integer power, the answer will vary according to a certain pattern.
+To figure it out, notice that raising $i$ to the power of $4$ gives: 
+
+$$i^4 = i^2 \cdot i^2 = (-1) \cdot (-1) = 1$$
+
+Thus, when the exponent $n$ is divisible by 4, $i^n$ will always be 1.
+
+When the exponent $n$ is not divisible by 4, you can use the previous observation to see that $i^n = i^{n \mod 4}$.
+For an even exponent $n$ that is not divisible by 4 you'll have $i^n = i^2 = -1.$
+
+Here is the complete pattern that arises when raising $i$ to non-negative powers. Note that it is periodic with period $4$.
+
+|Power of $i$ | $i^0$ | $i^1$ | $i^2$ | $i^3$ | $i^4$ | $i^5$ | $i^6$ | $i^7$ | $i^8$ | $\dots$ |
+|----|----|----|----|----|----|----|----|----|----|----|
+|Result | $1$ | $i$ | $-1$ | $-i$ | $1$ | $i$ | $-1$ | $-i$ | $1$ | $\dots$ |
+
+> `%` is the Q# modulo operator which returns the remainder of a division. For example, `7%2` gives $1$, because $1$ is the remainder of dividing $7$ by $2$.  
+> We can use this operator to determine if the exponent $n$ is divisible by $4$.
+
+@[solution]({"id": "complex_arithmetic__powers_of_i_solution", "codePath": "Solution.qs"})
diff --git a/katas/content/index.json b/katas/content/index.json
index ee2b9641..a47443ca 100644
--- a/katas/content/index.json
+++ b/katas/content/index.json
@@ -1,5 +1,6 @@
 [
   "getting_started",
+  "complex_arithmetic",
   "qubit",
   "single_qubit_gates",
   "multi_qubit_systems",
diff --git a/katas/content/superposition_kata/bell_state/Placeholder.qs b/katas/content/superposition_kata/bell_state/Placeholder.qs
new file mode 100644
index 00000000..0f1abffd
--- /dev/null
+++ b/katas/content/superposition_kata/bell_state/Placeholder.qs
@@ -0,0 +1,7 @@
+namespace Kata {
+    operation BellState (qs : Qubit[]) : Unit is Adj + Ctl {
+        // Implement your solution here...
+        
+    }
+}
+
diff --git a/katas/content/superposition_kata/bell_state/Solution.qs b/katas/content/superposition_kata/bell_state/Solution.qs
new file mode 100644
index 00000000..db9310d7
--- /dev/null
+++ b/katas/content/superposition_kata/bell_state/Solution.qs
@@ -0,0 +1,6 @@
+namespace Kata {
+    operation BellState (qs : Qubit[]) : Unit is Adj + Ctl {
+        H(qs[0]);
+        CNOT(qs[0], qs[1]);
+    }
+}
diff --git a/katas/content/superposition_kata/bell_state/Verification.qs b/katas/content/superposition_kata/bell_state/Verification.qs
new file mode 100644
index 00000000..0cdf6eff
--- /dev/null
+++ b/katas/content/superposition_kata/bell_state/Verification.qs
@@ -0,0 +1,16 @@
+namespace Kata.Verification {
+    open Microsoft.Quantum.Katas;
+
+    operation BellState_Reference (qs : Qubit[]) : Unit is Adj + Ctl {
+        H(qs[0]);
+        CNOT(qs[0], qs[1]);
+    }
+
+    @EntryPoint()
+    operation CheckSolution() : Bool {
+        CheckOperationsEquivalenceOnZeroStateWithFeedback(
+            Kata.BellState,
+            BellState_Reference,
+            2)
+    }
+}
diff --git a/katas/content/superposition_kata/bell_state/index.md b/katas/content/superposition_kata/bell_state/index.md
new file mode 100644
index 00000000..58b4bafc
--- /dev/null
+++ b/katas/content/superposition_kata/bell_state/index.md
@@ -0,0 +1,5 @@
+**Input:** Two qubits in the $|00\rangle$ state (stored in an array of length 2).
+
+**Goal:**  Change the state of the qubits to $|\Phi^{+}\rangle = \frac{1}{\sqrt{2}} \big (|00\rangle + |11\rangle\big)$.
+
+> You can find detailed coverage of Bell states and their creation [in this blog post](https://blogs.msdn.microsoft.com/uk_faculty_connection/2018/02/06/a-beginners-guide-to-quantum-computing-and-q/).
diff --git a/katas/content/superposition_kata/bell_state/solution.md b/katas/content/superposition_kata/bell_state/solution.md
new file mode 100644
index 00000000..05326a43
--- /dev/null
+++ b/katas/content/superposition_kata/bell_state/solution.md
@@ -0,0 +1,56 @@
+The first thing we notice is that, unlike in the previous task, we cannot represent this state as a tensor product of two individual qubit states - this goal state is NOT separable. 
+
+> How can we see this? Let's assume that this state can be represented as a tensor product of two qubit states: 
+>
+> $$|\psi_1\rangle \otimes |\psi_2\rangle = (\alpha_1|0\rangle + \beta_1|1\rangle) \otimes (\alpha_2|0\rangle + \beta_2|1\rangle) = \alpha_1\alpha_2|00\rangle + \alpha_1\beta_2|01\rangle + \beta_1\alpha_2|10\rangle + \beta_1\beta_2|11\rangle$$ 
+>
+>In order for this state to be equal to $\frac{1}{\sqrt2}\big(|00\rangle + |11\rangle\big)$, we need to have $\alpha_1\alpha_2 = \beta_1\beta_2 = \frac{1}{\sqrt2}$ and at the same time $\alpha_1\beta_2 = \beta_1\alpha_2 = 0$, which is impossible.
+>
+>This is the phenomena called **entanglement**, in which the states of the qubits are linked together and can not be considered individually.  
+
+Let's see what steps we can take to prepare this state without factoring it into states of individual qubits.
+
+---
+
+First, we notice that we should end with a superposition of two of the four computational basis for two qubits: $|00\rangle, |01\rangle, |10\rangle, |11\rangle$.
+
+This gives us a hint that we should start by preparing a superposition on at least one of the qubits. Let’s try creating a superposition on the first qubit with a Hadamard gate: 
+
+$$H|0\rangle \otimes |0\rangle = \frac{1}{\sqrt2} (|0\rangle + |1\rangle) \otimes |0\rangle = \frac{1}{\sqrt2} (|00\rangle + |10\rangle)$$
+
+Well, we got pretty close, except we need to transform the $|10\rangle$ state to $|11\rangle$.
+How can we do this? 
+
+We can take advantage of controlled gates, specifically the [controlled NOT gate](https://en.wikipedia.org/wiki/Controlled_NOT_gate), also referred to as $CNOT$. This gate acts on two qubits, hence it is represented as a $4 \times 4$ unitary matrix. The $CNOT$ gate changes the target qubit from state $|0\rangle$ to $|1\rangle$ and vice versa when the control qubit is $|1\rangle$ and does nothing to the target qubit when the control qubit is $|0\rangle$. The control qubit always remains unchanged. 
+
+$$\text{CNOT} = \begin{bmatrix} 1 & 0 & 0 & 0 \\\ 0 & 1 & 0 & 0 \\\ 0 & 0 & 0 & 1 \\\ 0 & 0 & 1 & 0 \end{bmatrix}$$
+
+If we apply the CNOT gate to the state $\frac{1}{\sqrt2} (|00\rangle + |10\rangle)$, taking the first qubit as the control and the second one as target, we'll get exactly the desired goal state. 
+ 
+Steps required to reach goal state:
+1. Apply a Hadamard gate to the first qubit.
+2. Applying a CNOT with first qubit as control and second qubit as target.
+
+In matrix representation we can represent this operation as a product of two $4 \times 4$ matrices, with the matrix corresponding to the first step being the tensor product of a Hadamard gate on the first qubit and identity gate on the second qubit.
+
+$$H \otimes I = \frac{1}{\sqrt2} \begin{bmatrix} 1 & 1  \\\ 1 & -1 \end{bmatrix} \otimes \begin{bmatrix} 1 & 0  \\\ 0 & 1 \end{bmatrix} = 
+\frac{1}{\sqrt2}\begin{bmatrix} 1 & 0 & 1 & 0 \\\ 0 & 1 & 0 & 1 \\\ 1 & 0 & -1 & 0 \\\ 0 & 1 & 0 & -1 \end{bmatrix}$$
+
+$$\underset{\text{CNOT}}{\underbrace{\begin{bmatrix} 1 & 0 & 0 & 0 \\\ 0 & 1 & 0 & 0 \\\ 0 & 0 & 0 & 1 \\\ 0 & 0 & 1 & 0 \end{bmatrix}}} 
+\cdot 
+\underset{H \otimes I}{\underbrace{\frac{1}{\sqrt2} \begin{bmatrix} 1 & 0 & 1 & 0 \\\ 0 & 1 & 0 & 1 \\\ 1 & 0 & -1 & 0 \\\ 0 & 1 & 0 & -1 \end{bmatrix}}}
+\cdot
+\underset{|0\rangle}{\underbrace{ \begin{bmatrix} 1 \\\ 0 \\\ 0 \\\ 0 \end{bmatrix}}}
+= \frac{1}{\sqrt2} \begin{bmatrix} 1 & 0 & 1 & 0 \\\ 0 & 1 & 0 & 1 \\\ 0 & 1 & 0 & -1 \\\ 1 & 0 & -1 & 0 \end{bmatrix}
+\cdot
+\begin{bmatrix} 1 \\\ 0 \\\ 0 \\\ 0 \end{bmatrix}
+= \underset{goal}{\underbrace{ \frac{1}{\sqrt2} \begin{bmatrix} 1 \\\ 0 \\\ 0 \\\ 1 \end{bmatrix}}}
+\label{6.1} \tag{6.1}
+$$
+
+Note that in the matrix representation and in Dirac notation the gates are applied from right to left (the rightmost operation happens firts), while in circuit notation the operations are applied from left to right (the leftmost operation happens first).
+
+@[solution]({
+    "id": "superposition__bell_state_solution",
+    "codePath": "./Solution.qs"
+})
diff --git a/katas/content/superposition_kata/even_sup_two_qubits/solution.md b/katas/content/superposition_kata/even_sup_two_qubits/solution.md
index 20eb9d78..ccc40028 100644
--- a/katas/content/superposition_kata/even_sup_two_qubits/solution.md
+++ b/katas/content/superposition_kata/even_sup_two_qubits/solution.md
@@ -15,5 +15,5 @@ Q# arrays are similar to arrays in other languages: you can access the $i$-th el
 
 @[solution]({
     "id": "superposition__even_sup_two_qubits_solution",
-    "codePath": "./solution.qs"
+    "codePath": "./Solution.qs"
 })
diff --git a/katas/content/superposition_kata/even_sup_two_qubits_phase_flip/solution.md b/katas/content/superposition_kata/even_sup_two_qubits_phase_flip/solution.md
index d21e07c8..3b04107b 100644
--- a/katas/content/superposition_kata/even_sup_two_qubits_phase_flip/solution.md
+++ b/katas/content/superposition_kata/even_sup_two_qubits_phase_flip/solution.md
@@ -12,5 +12,5 @@ In our case we only want to flip the phase of the $|11\rangle$ state and not the
 
 @[solution]({
     "id": "superposition__even_sup_two_qubits_phase_flip_solution",
-    "codePath": "./solution.qs"
+    "codePath": "./Solution.qs"
 })
diff --git a/katas/content/superposition_kata/index.md b/katas/content/superposition_kata/index.md
index 13c74cba..6d4e233b 100644
--- a/katas/content/superposition_kata/index.md
+++ b/katas/content/superposition_kata/index.md
@@ -73,6 +73,16 @@ This kata is designed to get you familiar with the concept of superposition and
     ]
 })
 
+@[exercise]({
+    "id": "superposition__bell_state",
+    "title": "Bell State",
+    "path": "./bell_state/",
+    "qsDependencies": [
+        "../KatasLibrary.qs",
+        "./Common.qs"
+    ]
+})
+
 @[section]({
     "id": "superposition__uneven_superpositions",
     "title": "Uneven superpositions"
diff --git a/katas/content/superposition_kata/minus_state/solution.md b/katas/content/superposition_kata/minus_state/solution.md
index 45e8df4f..72105a1d 100644
--- a/katas/content/superposition_kata/minus_state/solution.md
+++ b/katas/content/superposition_kata/minus_state/solution.md
@@ -11,5 +11,5 @@ Thus, our solution should apply the X gate to our qubit, followed by the Hadamar
 
 @[solution]({
     "id": "superposition__minus_state_solution",
-    "codePath": "./solution.qs"
+    "codePath": "./Solution.qs"
 })
diff --git a/katas/content/superposition_kata/plus_state/solution.md b/katas/content/superposition_kata/plus_state/solution.md
index 592fa0ed..96e91434 100644
--- a/katas/content/superposition_kata/plus_state/solution.md
+++ b/katas/content/superposition_kata/plus_state/solution.md
@@ -9,5 +9,5 @@ It is open in any Q# source files by default, so you can use it right away.
 
 @[solution]({
     "id": "superposition__plus_state_solution",
-    "codePath": "./solution.qs"
+    "codePath": "./Solution.qs"
 })
diff --git a/library/std/internal.qs b/library/std/internal.qs
index 5c8c27f4..6a2f4dc4 100644
--- a/library/std/internal.qs
+++ b/library/std/internal.qs
@@ -2,7 +2,6 @@
 // Licensed under the MIT License.
 
 namespace Microsoft.Quantum.Intrinsic {
-    open Microsoft.Quantum.Measurement;
     open Microsoft.Quantum.Arrays;
     open Microsoft.Quantum.Core;
     open Microsoft.Quantum.Math;
diff --git a/library/std/unstable_table_lookup.qs b/library/std/unstable_table_lookup.qs
index 74e5e6c6..5cb06452 100644
--- a/library/std/unstable_table_lookup.qs
+++ b/library/std/unstable_table_lookup.qs
@@ -6,7 +6,6 @@ namespace Microsoft.Quantum.Unstable.TableLookup {
     open Microsoft.Quantum.Convert;
     open Microsoft.Quantum.Diagnostics;
     open Microsoft.Quantum.Math;
-    open Microsoft.Quantum.Measurement;
     open Microsoft.Quantum.ResourceEstimation;
     open Microsoft.Quantum.Unstable.Arithmetic;
 
diff --git a/npm/src/compiler/events.ts b/npm/src/compiler/events.ts
index cfa7eab9..fa3ada9b 100644
--- a/npm/src/compiler/events.ts
+++ b/npm/src/compiler/events.ts
@@ -2,15 +2,14 @@
 // Licensed under the MIT License.
 
 import { ShotResult, Dump, Result } from "./common.js";
-import { TelemetryEvent, log } from "../log.js";
+import { log } from "../log.js";
 import { IServiceEventTarget } from "../workers/common.js";
 
 // Create strongly typed compiler events
 export type QscEventData =
   | { type: "Message"; detail: string }
   | { type: "DumpMachine"; detail: { state: Dump; stateLatex: string } }
-  | { type: "Result"; detail: Result }
-  | { type: "telemetry-event"; detail: TelemetryEvent };
+  | { type: "Result"; detail: Result };
 
 export type QscEvents = Event & QscEventData;
 
diff --git a/npm/src/workers/common.ts b/npm/src/workers/common.ts
index 8f369762..0cbc4d08 100644
--- a/npm/src/workers/common.ts
+++ b/npm/src/workers/common.ts
@@ -84,6 +84,17 @@ export interface IServiceEventMessage {
   detail: unknown;
 }
 
+/**
+ * Common event types all workers can send.
+ */
+type CommonEvent =
+  | { type: "telemetry-event"; detail: TelemetryEvent }
+  | {
+      type: "log";
+      detail: { level: number; target: string; data: any[] };
+    };
+type CommonEventMessage = CommonEvent & { messageType: "common-event" };
+
 /**
  * Strongly typed EventTarget interface. Used as a constraint for the
  * event target that "requestWithProgress" methods should take in the service.
@@ -211,18 +222,31 @@ export function createProxyInternal<
   }
 
   function onMsgFromWorker(
-    msg: ResponseMessage<TService> | EventMessage<TServiceEventMsg>,
+    msg:
+      | ResponseMessage<TService>
+      | EventMessage<TServiceEventMsg>
+      | CommonEventMessage,
   ) {
     if (log.getLogLevel() >= 4)
       log.debug("Proxy: Received message from worker: %s", JSON.stringify(msg));
 
-    if (msg.messageType === "event") {
-      // For telemetry events, just log and exit. There is nothing else waiting to consume them.
-      if (msg.type === "telemetry-event") {
-        const detail = msg.detail as TelemetryEvent;
-        log.logTelemetry(detail);
-        return;
+    if (msg.messageType === "common-event") {
+      const commonEvent = msg; // assignment is necessary here for TypeScript to narrow the type
+      switch (commonEvent.type) {
+        case "telemetry-event":
+          {
+            const detail = commonEvent.detail;
+            log.logTelemetry(detail);
+          }
+          break;
+        case "log":
+          {
+            const detail = commonEvent.detail;
+            log.logWithLevel(detail.level, detail.target, ...detail.data);
+          }
+          break;
       }
+    } else if (msg.messageType === "event") {
       const event = new Event(msg.type) as Event & TServiceEventMsg;
       event.detail = msg.detail;
 
@@ -250,7 +274,6 @@ export function createProxyInternal<
         curr = undefined;
         doNextRequest();
       }
-      return;
     }
   }
 
@@ -425,28 +448,61 @@ export function initService<
   TServiceEventMsg extends IServiceEventMessage,
 >(
   postMessage: (
-    msg: ResponseMessage<TService> | EventMessage<TServiceEventMsg>,
+    msg:
+      | ResponseMessage<TService>
+      | EventMessage<TServiceEventMsg>
+      | CommonEventMessage,
   ) => void,
   serviceProtocol: ServiceProtocol<TService, TServiceEventMsg>,
   wasm: Wasm,
   qscLogLevel?: number,
 ): (req: RequestMessage<TService>) => Promise<void> {
-  function telemetryHandler(telemetry: TelemetryEvent) {
+  function postTelemetryMessage(telemetry: TelemetryEvent) {
     postMessage({
-      messageType: "event",
+      messageType: "common-event",
       type: "telemetry-event",
       detail: telemetry,
     });
   }
 
+  function postLogMessage(level: number, target: string, ...args: any) {
+    if (log.getLogLevel() < level) {
+      return;
+    }
+
+    let data = args;
+    try {
+      // Only structured cloneable objects can be sent in worker messages.
+      // Test if this is the case.
+      structuredClone(args);
+    } catch (e) {
+      // Uncloneable object.
+      // Use String(args) instead of ${args} to handle all possible values
+      // without throwing. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_coercion
+      data = ["unsupported log data " + String(args)];
+    }
+    postMessage({
+      messageType: "common-event",
+      type: "log",
+      detail: { level, target, data },
+    });
+  }
+
+  // Override the global logger
+  log.error = (...args) => postLogMessage(1, "worker", ...args);
+  log.warn = (...args) => postLogMessage(2, "worker", ...args);
+  log.info = (...args) => postLogMessage(3, "worker", ...args);
+  log.debug = (...args) => postLogMessage(4, "worker", ...args);
+  log.trace = (...args) => postLogMessage(5, "worker", ...args);
+
   if (qscLogLevel !== undefined) {
     log.setLogLevel(qscLogLevel);
   }
 
   // Set up logging and telemetry as soon as possible after instantiating
   log.onLevelChanged = (level) => wasm.setLogLevel(level);
-  log.setTelemetryCollector(telemetryHandler);
-  wasm.initLogging(log.logWithLevel, log.getLogLevel());
+  log.setTelemetryCollector(postTelemetryMessage);
+  wasm.initLogging(postLogMessage, log.getLogLevel());
 
   // Create the actual service and return the dispatcher method
   const service = new serviceProtocol.class(wasm);
diff --git a/resource_estimator/src/counts.rs b/resource_estimator/src/counts.rs
index 6690a5f1..2371b606 100644
--- a/resource_estimator/src/counts.rs
+++ b/resource_estimator/src/counts.rs
@@ -4,7 +4,6 @@
 #[cfg(test)]
 mod tests;
 
-use super::LogicalResources;
 use num_bigint::BigUint;
 use num_complex::Complex;
 use qsc::{interpret::Value, Backend};
@@ -18,6 +17,8 @@ use std::{
     iter::Sum,
 };
 
+use crate::system::LogicalResourceCounts;
+
 /// Resource counter implementation
 ///
 /// This counter tracks all resources while executing a QIR program.  It takes
@@ -74,14 +75,15 @@ impl Default for LogicalCounter {
 
 impl LogicalCounter {
     #[must_use]
-    pub fn logical_resources(&self) -> LogicalResources {
-        LogicalResources {
-            num_qubits: self.next_free,
-            t_count: self.t_count,
-            rotation_count: self.r_count,
-            rotation_depth: self.layers.iter().filter(|layer| layer.r != 0).count(),
-            ccz_count: self.ccz_count,
-            measurement_count: self.m_count,
+    pub fn logical_resources(&self) -> LogicalResourceCounts {
+        LogicalResourceCounts {
+            num_qubits: self.next_free as _,
+            t_count: self.t_count as _,
+            rotation_count: self.r_count as _,
+            rotation_depth: self.layers.iter().filter(|layer| layer.r != 0).count() as _,
+            ccz_count: self.ccz_count as _,
+            ccix_count: 0,
+            measurement_count: self.m_count as _,
         }
     }
 
diff --git a/resource_estimator/src/estimates/physical_estimation.rs b/resource_estimator/src/estimates/physical_estimation.rs
index 130aed97..81a590f8 100644
--- a/resource_estimator/src/estimates/physical_estimation.rs
+++ b/resource_estimator/src/estimates/physical_estimation.rs
@@ -140,14 +140,14 @@ pub struct PhysicalResourceEstimationResult<E: ErrorCorrection, F, L> {
     physical_qubits: u64,
     runtime: u64,
     rqops: u64,
-    layout_overhead: L,
+    layout_overhead: Rc<L>,
     error_budget: ErrorBudget,
 }
 
 impl<
         E: ErrorCorrection<Parameter = impl Clone>,
         F: Factory<Parameter = E::Parameter> + Clone,
-        L: Overhead + Clone,
+        L: Overhead,
     > PhysicalResourceEstimationResult<E, F, L>
 {
     pub fn new(
@@ -209,7 +209,7 @@ impl<
             physical_qubits,
             runtime,
             rqops,
-            layout_overhead: estimation.layout_overhead().clone(),
+            layout_overhead: estimation.layout_overhead.clone(),
             error_budget: estimation.error_budget().clone(),
         }
     }
@@ -283,7 +283,7 @@ impl<
         self.rqops
     }
 
-    pub fn layout_overhead(&self) -> &L {
+    pub fn layout_overhead(&self) -> &Rc<L> {
         &self.layout_overhead
     }
 
@@ -313,7 +313,7 @@ pub struct PhysicalResourceEstimation<E: ErrorCorrection, Builder, L> {
     ftp: E,
     qubit: Rc<E::Qubit>,
     factory_builder: Builder,
-    layout_overhead: L,
+    layout_overhead: Rc<L>,
     error_budget: ErrorBudget,
     // optional constraint parameters
     logical_depth_factor: Option<f64>,
@@ -325,14 +325,14 @@ pub struct PhysicalResourceEstimation<E: ErrorCorrection, Builder, L> {
 impl<
         E: ErrorCorrection<Parameter = impl Clone>,
         Builder: FactoryBuilder<E, Factory = impl Factory<Parameter = E::Parameter> + Clone>,
-        L: Overhead + Clone,
+        L: Overhead,
     > PhysicalResourceEstimation<E, Builder, L>
 {
     pub fn new(
         ftp: E,
         qubit: Rc<E::Qubit>,
         factory_builder: Builder,
-        layout_overhead: L,
+        layout_overhead: Rc<L>,
         error_budget: ErrorBudget,
     ) -> Self {
         Self {
diff --git a/resource_estimator/src/lib.rs b/resource_estimator/src/lib.rs
index 47f7a819..65a53841 100644
--- a/resource_estimator/src/lib.rs
+++ b/resource_estimator/src/lib.rs
@@ -29,16 +29,6 @@ use qsc::interpret::{self, GenericReceiver, Interpreter};
 use system::estimate_physical_resources;
 use thiserror::Error;
 
-#[derive(Debug)]
-pub struct LogicalResources {
-    pub num_qubits: usize,
-    pub t_count: usize,
-    pub rotation_count: usize,
-    pub rotation_depth: usize,
-    pub ccz_count: usize,
-    pub measurement_count: usize,
-}
-
 #[derive(Debug, Diagnostic, Error)]
 #[error(transparent)]
 #[diagnostic(transparent)]
@@ -54,7 +44,7 @@ pub fn estimate_entry(interpreter: &mut Interpreter, params: &str) -> Result<Str
     interpreter
         .eval_entry_with_sim(&mut counter, &mut out)
         .map_err(|e| e.into_iter().map(Error::Interpreter).collect::<Vec<_>>())?;
-    estimate_physical_resources(&counter.logical_resources(), params)
+    estimate_physical_resources(counter.logical_resources(), params)
         .map_err(|e| vec![Error::Estimation(e)])
 }
 
@@ -70,6 +60,6 @@ pub fn estimate_expr(
         .run_with_sim(&mut counter, &mut out, expr)
         .map_err(|e| e.into_iter().map(Error::Interpreter).collect::<Vec<_>>())?
         .map_err(|e| vec![Error::Interpreter(e[0].clone())])?;
-    estimate_physical_resources(&counter.logical_resources(), params)
+    estimate_physical_resources(counter.logical_resources(), params)
         .map_err(|e| vec![Error::Estimation(e)])
 }
diff --git a/resource_estimator/src/system.rs b/resource_estimator/src/system.rs
index 386e4a1a..dd6ce9d7 100644
--- a/resource_estimator/src/system.rs
+++ b/resource_estimator/src/system.rs
@@ -19,32 +19,32 @@ mod modeling;
 mod optimization;
 mod serialization;
 
-use crate::estimates::PhysicalResourceEstimation;
+use crate::estimates::{Overhead, PhysicalResourceEstimation};
+use std::rc::Rc;
 
+pub use self::{data::LogicalResourceCounts, error::Error};
 use self::{modeling::Protocol, optimization::TFactoryBuilder};
-use super::LogicalResources;
-use data::{EstimateType, JobParams, LogicalResourceCounts};
-pub use error::Error;
+use data::{EstimateType, JobParams};
+pub use data::{LayoutReportData, PartitioningOverhead};
+use serde::Serialize;
 
 pub(crate) type Result<T> = std::result::Result<T, error::Error>;
 
-pub fn estimate_physical_resources(
-    logical_resources: &LogicalResources,
-    params: &str,
-) -> Result<String> {
-    estimate(logical_resources.into(), params)
-}
-
 pub fn estimate_physical_resources_from_json(
     logical_resources: &str,
     params: &str,
 ) -> std::result::Result<String, Error> {
-    let logical_resources = serde_json::from_str(logical_resources)
+    let logical_resources: LogicalResourceCounts = serde_json::from_str(logical_resources)
         .map_err(|e| error::Error::IO(error::IO::CannotParseJSON(e)))?;
-    estimate(logical_resources, params)
+    estimate_physical_resources(logical_resources, params)
 }
 
-fn estimate(logical_resources: LogicalResourceCounts, params: &str) -> Result<String> {
+pub fn estimate_physical_resources<
+    L: Overhead + LayoutReportData + PartitioningOverhead + Serialize,
+>(
+    logical_resources: L,
+    params: &str,
+) -> Result<String> {
     let job_params_array = if params.is_empty() {
         vec![JobParams::default()]
     } else {
@@ -52,8 +52,9 @@ fn estimate(logical_resources: LogicalResourceCounts, params: &str) -> Result<St
     };
 
     let mut results: Vec<String> = Vec::with_capacity(job_params_array.len());
+    let logical_resources = Rc::new(logical_resources);
     for job_params in job_params_array {
-        let result = estimate_single(logical_resources, job_params);
+        let result = estimate_single(logical_resources.clone(), job_params);
         match result {
             Ok(result) => results.push(
                 serde_json::to_string(&result).expect("serializing to json string should succeed"),
@@ -67,10 +68,10 @@ fn estimate(logical_resources: LogicalResourceCounts, params: &str) -> Result<St
     Ok(format!("[{}]", results.join(",")))
 }
 
-fn estimate_single(
-    logical_resources: LogicalResourceCounts,
+fn estimate_single<L: Overhead + LayoutReportData + PartitioningOverhead + Serialize>(
+    logical_resources: Rc<L>,
     mut job_params: JobParams,
-) -> Result<data::Success> {
+) -> Result<data::Success<L>> {
     let qubit = job_params.qubit_params().clone();
 
     let ftp = Protocol::load_from_specification(job_params.qec_scheme_mut(), &qubit)?;
@@ -78,7 +79,9 @@ fn estimate_single(
         .distillation_unit_specifications()
         .as_templates()?;
     // create error budget partitioning
-    let partitioning = job_params.error_budget().partitioning(&logical_resources)?;
+    let partitioning = job_params
+        .error_budget()
+        .partitioning(logical_resources.as_ref())?;
 
     let mut estimation = PhysicalResourceEstimation::new(
         ftp,
@@ -118,14 +121,11 @@ fn estimate_single(
             let estimation_result = estimation
                 .build_frontier()
                 .map_err(std::convert::Into::into);
-            estimation_result.map(|result| {
-                data::Success::new_from_multiple(logical_resources, job_params, result)
-            })
+            estimation_result.map(|result| data::Success::new_from_multiple(job_params, result))
         }
         EstimateType::SinglePoint => {
             let estimation_result = estimation.estimate().map_err(std::convert::Into::into);
-            estimation_result
-                .map(|result| data::Success::new(logical_resources, job_params, result))
+            estimation_result.map(|result| data::Success::new(job_params, result))
         }
     }
 }
diff --git a/resource_estimator/src/system/data.rs b/resource_estimator/src/system/data.rs
index e3cc6b0e..8eb40f31 100644
--- a/resource_estimator/src/system/data.rs
+++ b/resource_estimator/src/system/data.rs
@@ -10,8 +10,8 @@ mod result;
 mod tfactory;
 
 pub use constraints::Constraints;
-pub use job_params::{EstimateType, JobParams};
-pub use logical_counts::LogicalResourceCounts;
+pub use job_params::{EstimateType, JobParams, PartitioningOverhead};
+pub use logical_counts::{LayoutReportData, LogicalResourceCounts};
 pub use physical_counts::{PhysicalResourceCounts, PhysicalResourceCountsBreakdown};
 pub use report::{FormattedPhysicalResourceCounts, Report};
 pub use result::{Failure, Success};
diff --git a/resource_estimator/src/system/data/job_params.rs b/resource_estimator/src/system/data/job_params.rs
index 79ce84b8..7e7505f0 100644
--- a/resource_estimator/src/system/data/job_params.rs
+++ b/resource_estimator/src/system/data/job_params.rs
@@ -8,7 +8,6 @@ use serde::{Deserialize, Serialize};
 use super::super::{
     error::InvalidInput::{self, InvalidErrorBudget},
     modeling::{PhysicalQubit, ProtocolSpecification},
-    LogicalResourceCounts,
 };
 use crate::estimates::ErrorBudget;
 
@@ -127,21 +126,15 @@ impl ErrorBudgetSpecification {
 
     pub fn partitioning(
         &self,
-        counts: &LogicalResourceCounts,
+        counts: &impl PartitioningOverhead,
     ) -> core::result::Result<ErrorBudget, InvalidInput> {
-        let has_tgates = counts.t_count > 0
-            || counts.ccz_count > 0
-            || counts.ccix_count > 0
-            || counts.rotation_count > 0;
-        let has_rotations = counts.rotation_count > 0;
-
         match *self {
             ErrorBudgetSpecification::Total(total) => {
                 if total <= 0.0 || total >= 1.0 {
                     return Err(InvalidErrorBudget(total));
                 }
 
-                Ok(match (has_tgates, has_rotations) {
+                Ok(match (counts.has_tgates(), counts.has_rotations()) {
                     (true, true) => ErrorBudget::new(total / 3.0, total / 3.0, total / 3.0),
                     (true, false) => ErrorBudget::new(total / 2.0, total / 2.0, 0.0),
                     (false, false) => ErrorBudget::new(total, 0.0, 0.0),
@@ -162,11 +155,11 @@ impl ErrorBudgetSpecification {
                     return Err(InvalidErrorBudget(logical));
                 }
 
-                if tstates < 0.0 || (has_tgates && tstates == 0.0) {
+                if tstates < 0.0 || (counts.has_tgates() && tstates == 0.0) {
                     return Err(InvalidErrorBudget(tstates));
                 }
 
-                if rotations < 0.0 || (has_rotations && rotations == 0.0) {
+                if rotations < 0.0 || (counts.has_rotations() && rotations == 0.0) {
                     return Err(InvalidErrorBudget(rotations));
                 }
 
@@ -182,6 +175,15 @@ impl Default for ErrorBudgetSpecification {
     }
 }
 
+/// Logical overheads need ot implement this trait, if one wants to partition an
+/// error budget based on the overhead.
+pub trait PartitioningOverhead {
+    /// There are T gates in the overhead
+    fn has_tgates(&self) -> bool;
+    /// There are rotations in the overhead
+    fn has_rotations(&self) -> bool;
+}
+
 #[derive(Debug, Deserialize, Serialize)]
 #[serde(rename_all(serialize = "camelCase", deserialize = "camelCase"))]
 pub enum EstimateType {
diff --git a/resource_estimator/src/system/data/logical_counts.rs b/resource_estimator/src/system/data/logical_counts.rs
index 7936bb5f..b1d83166 100644
--- a/resource_estimator/src/system/data/logical_counts.rs
+++ b/resource_estimator/src/system/data/logical_counts.rs
@@ -7,13 +7,23 @@ use crate::{
         NUM_MEASUREMENTS_PER_R, NUM_MEASUREMENTS_PER_TOF, NUM_TS_PER_ROTATION_A_COEFFICIENT,
         NUM_TS_PER_ROTATION_B_COEFFICIENT,
     },
-    LogicalResources,
 };
 use serde::{Deserialize, Serialize};
-use std::convert::From;
+
+use super::PartitioningOverhead;
+
+pub trait LayoutReportData {
+    fn num_qubits(&self) -> u64;
+    fn t_count(&self) -> u64;
+    fn rotation_count(&self) -> u64;
+    fn rotation_depth(&self) -> u64;
+    fn ccz_count(&self) -> u64;
+    fn ccix_count(&self) -> u64;
+    fn measurement_count(&self) -> u64;
+}
 
 /// Resource counts output from `qir_estimate_counts` program
-#[derive(Default, Debug, Deserialize, Serialize, Clone, Copy)]
+#[derive(Default, Debug, Deserialize, Serialize)]
 #[serde(
     rename_all(deserialize = "camelCase", serialize = "camelCase"),
     deny_unknown_fields
@@ -34,20 +44,6 @@ pub struct LogicalResourceCounts {
     pub measurement_count: u64,
 }
 
-impl From<&LogicalResources> for LogicalResourceCounts {
-    fn from(logical_resources: &LogicalResources) -> Self {
-        Self {
-            num_qubits: logical_resources.num_qubits as _,
-            t_count: logical_resources.t_count as _,
-            rotation_count: logical_resources.rotation_count as _,
-            rotation_depth: logical_resources.rotation_depth as _,
-            ccz_count: logical_resources.ccz_count as _,
-            ccix_count: 0,
-            measurement_count: logical_resources.measurement_count as _,
-        }
-    }
-}
-
 /// Models the logical resources after layout
 ///
 /// The logical resources comprise the logical depth, the number of qubits, and
@@ -91,3 +87,43 @@ impl Overhead for LogicalResourceCounts {
         }
     }
 }
+
+impl PartitioningOverhead for LogicalResourceCounts {
+    fn has_tgates(&self) -> bool {
+        self.t_count > 0 || self.ccz_count > 0 || self.ccix_count > 0 || self.rotation_count > 0
+    }
+
+    fn has_rotations(&self) -> bool {
+        self.rotation_count > 0
+    }
+}
+
+impl LayoutReportData for LogicalResourceCounts {
+    fn num_qubits(&self) -> u64 {
+        self.num_qubits
+    }
+
+    fn t_count(&self) -> u64 {
+        self.t_count
+    }
+
+    fn rotation_count(&self) -> u64 {
+        self.rotation_count
+    }
+
+    fn rotation_depth(&self) -> u64 {
+        self.rotation_depth
+    }
+
+    fn ccz_count(&self) -> u64 {
+        self.ccz_count
+    }
+
+    fn ccix_count(&self) -> u64 {
+        self.ccix_count
+    }
+
+    fn measurement_count(&self) -> u64 {
+        self.measurement_count
+    }
+}
diff --git a/resource_estimator/src/system/data/report.rs b/resource_estimator/src/system/data/report.rs
index 1f8f2b09..c1ba49cd 100644
--- a/resource_estimator/src/system/data/report.rs
+++ b/resource_estimator/src/system/data/report.rs
@@ -11,10 +11,10 @@ use serde::Serialize;
 use crate::estimates::{Factory, Overhead, PhysicalResourceEstimationResult};
 use crate::system::modeling::Protocol;
 
+use super::LayoutReportData;
 use super::{
     super::modeling::{PhysicalInstructionSet, TFactory},
     job_params::JobParams,
-    LogicalResourceCounts,
 };
 
 #[derive(Serialize)]
@@ -26,12 +26,17 @@ pub struct Report {
 
 impl Report {
     #[allow(clippy::vec_init_then_push, clippy::too_many_lines)]
-    pub fn new<L: Overhead + Clone>(
-        logical_counts: &LogicalResourceCounts,
+    pub fn new(
         job_params: &JobParams,
-        result: &PhysicalResourceEstimationResult<Protocol, TFactory, L>,
+        result: &PhysicalResourceEstimationResult<
+            Protocol,
+            TFactory,
+            impl Overhead + LayoutReportData,
+        >,
         formatted_counts: &FormattedPhysicalResourceCounts,
     ) -> Self {
+        let logical_counts = result.layout_overhead();
+
         // THIS CODE HAS BEEN AUTOMATICALLY GENERATED WITH resource_estimator/scripts/generate_report_code.py from docs/output_data.md
         let mut groups = vec![];
 
@@ -46,18 +51,18 @@ impl Report {
         });
 
         let mut entries = vec![];
-        entries.push(ReportEntry::new("physicalCountsFormatted/algorithmicLogicalQubits", "Logical algorithmic qubits", r#"Number of logical qubits for the algorithm after layout"#, &format!(r#"Laying out the logical qubits in the presence of nearest-neighbor constraints requires additional logical qubits.  In particular, to layout the $Q_{{\rm alg}} = {}$ logical qubits in the input algorithm, we require in total $2 \cdot Q_{{\rm alg}} + \lceil \sqrt{{8 \cdot Q_{{\rm alg}}}}\rceil + 1 = {}$ logical qubits."#, format_thousand_sep(&logical_counts.num_qubits), format_thousand_sep(&result.layout_overhead().logical_qubits()))));
-        entries.push(ReportEntry::new("physicalCountsFormatted/algorithmicLogicalDepth", "Algorithmic depth", r#"Number of logical cycles for the algorithm"#, &format!(r#"To execute the algorithm using _Parallel Synthesis Sequential Pauli Computation_ (PSSPC), operations are scheduled in terms of multi-qubit Pauli measurements, for which assume an execution time of one logical cycle.  Based on the input algorithm, we require one multi-qubit measurement for the {} single-qubit measurements, the {} arbitrary single-qubit rotations, and the {} T gates, three multi-qubit measurements for each of the {} CCZ and {} CCiX gates in the input program, as well as {} multi-qubit measurements for each of the {} non-Clifford layers in which there is at least one single-qubit rotation with an arbitrary angle rotation."#, format_thousand_sep(&logical_counts.measurement_count), format_thousand_sep(&logical_counts.rotation_count), format_thousand_sep(&logical_counts.t_count), format_thousand_sep(&logical_counts.ccz_count), format_thousand_sep(&logical_counts.ccix_count), formatted_counts.num_ts_per_rotation, format_thousand_sep(&logical_counts.rotation_depth))));
+        entries.push(ReportEntry::new("physicalCountsFormatted/algorithmicLogicalQubits", "Logical algorithmic qubits", r#"Number of logical qubits for the algorithm after layout"#, &format!(r#"Laying out the logical qubits in the presence of nearest-neighbor constraints requires additional logical qubits.  In particular, to layout the $Q_{{\rm alg}} = {}$ logical qubits in the input algorithm, we require in total $2 \cdot Q_{{\rm alg}} + \lceil \sqrt{{8 \cdot Q_{{\rm alg}}}}\rceil + 1 = {}$ logical qubits."#, format_thousand_sep(&logical_counts.num_qubits()), format_thousand_sep(&result.layout_overhead().logical_qubits()))));
+        entries.push(ReportEntry::new("physicalCountsFormatted/algorithmicLogicalDepth", "Algorithmic depth", r#"Number of logical cycles for the algorithm"#, &format!(r#"To execute the algorithm using _Parallel Synthesis Sequential Pauli Computation_ (PSSPC), operations are scheduled in terms of multi-qubit Pauli measurements, for which assume an execution time of one logical cycle.  Based on the input algorithm, we require one multi-qubit measurement for the {} single-qubit measurements, the {} arbitrary single-qubit rotations, and the {} T gates, three multi-qubit measurements for each of the {} CCZ and {} CCiX gates in the input program, as well as {} multi-qubit measurements for each of the {} non-Clifford layers in which there is at least one single-qubit rotation with an arbitrary angle rotation."#, format_thousand_sep(&logical_counts.measurement_count()), format_thousand_sep(&logical_counts.rotation_count()), format_thousand_sep(&logical_counts.t_count()), format_thousand_sep(&logical_counts.ccz_count()), format_thousand_sep(&logical_counts.ccix_count()), formatted_counts.num_ts_per_rotation, format_thousand_sep(&logical_counts.rotation_depth()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/logicalDepth", "Logical depth", r#"Number of logical cycles performed"#, &format!(r#"This number is usually equal to the logical depth of the algorithm, which is {}.  However, in the case in which a single T factory is slower than the execution time of the algorithm, we adjust the logical cycle depth to exceed the T factory's execution time."#, format_thousand_sep(&result.algorithmic_logical_depth()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/clockFrequency", "Clock frequency", r#"Number of logical cycles per second"#, &format!(r#"This is the number of logical cycles that can be performed within one second.  The logical cycle time is {}."#, formatted_counts.logical_cycle_time)));
-        entries.push(ReportEntry::new("physicalCountsFormatted/numTstates", "Number of T states", r#"Number of T states consumed by the algorithm"#, &format!(r#"To execute the algorithm, we require one T state for each of the {} T gates, four T states for each of the {} CCZ and {} CCiX gates, as well as {} for each of the {} single-qubit rotation gates with arbitrary angle rotation."#, format_thousand_sep(&logical_counts.t_count), format_thousand_sep(&logical_counts.ccz_count), format_thousand_sep(&logical_counts.ccix_count), formatted_counts.num_ts_per_rotation, format_thousand_sep(&logical_counts.rotation_count))));
+        entries.push(ReportEntry::new("physicalCountsFormatted/numTstates", "Number of T states", r#"Number of T states consumed by the algorithm"#, &format!(r#"To execute the algorithm, we require one T state for each of the {} T gates, four T states for each of the {} CCZ and {} CCiX gates, as well as {} for each of the {} single-qubit rotation gates with arbitrary angle rotation."#, format_thousand_sep(&logical_counts.t_count()), format_thousand_sep(&logical_counts.ccz_count()), format_thousand_sep(&logical_counts.ccix_count()), formatted_counts.num_ts_per_rotation, format_thousand_sep(&logical_counts.rotation_count()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/numTfactories", "Number of T factories", &format!(r#"Number of T factories capable of producing the demanded {} T states during the algorithm's runtime"#, format_thousand_sep(&result.num_magic_states())), &format!(r#"The total number of T factories {} that are executed in parallel is computed as $\left\lceil\dfrac{{\text{{T states}}\cdot\text{{T factory duration}}}}{{\text{{T states per T factory}}\cdot\text{{algorithm runtime}}}}\right\rceil = \left\lceil\dfrac{{{} \cdot {}\;\text{{ns}}}}{{{} \cdot {}\;\text{{ns}}}}\right\rceil$"#, format_thousand_sep(&result.num_factories()), format_thousand_sep(&result.num_magic_states()), format_thousand_sep(&result.factory().map_or(0, TFactory::duration)), format_thousand_sep(&result.factory().map_or(0, TFactory::num_output_states)), format_thousand_sep(&result.runtime()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/numTfactoryRuns", "Number of T factory invocations", r#"Number of times all T factories are invoked"#, &format!(r#"In order to prepare the {} T states, the {} copies of the T factory are repeatedly invoked {} times."#, format_thousand_sep(&result.num_magic_states()), format_thousand_sep(&result.num_factories()), format_thousand_sep(&result.num_factory_runs()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/physicalQubitsForAlgorithm", "Physical algorithmic qubits", r#"Number of physical qubits for the algorithm after layout"#, &format!(r#"The {} are the product of the {} logical qubits after layout and the {} physical qubits that encode a single logical qubit."#, format_thousand_sep(&result.physical_qubits_for_algorithm()), format_thousand_sep(&result.layout_overhead().logical_qubits()), format_thousand_sep(&result.logical_patch().physical_qubits()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/physicalQubitsForTfactories", "Physical T factory qubits", r#"Number of physical qubits for the T factories"#, &format!(r#"Each T factory requires {} physical qubits and we run {} in parallel, therefore we need ${} = {} \cdot {}$ qubits."#, format_thousand_sep(&result.factory().map_or(0, TFactory::physical_qubits)), format_thousand_sep(&result.num_factories()), format_thousand_sep(&result.physical_qubits_for_factories()), format_thousand_sep(&result.factory().map_or(0, TFactory::physical_qubits)), format_thousand_sep(&result.num_factories()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/requiredLogicalQubitErrorRate", "Required logical qubit error rate", r#"The minimum logical qubit error rate required to run the algorithm within the error budget"#, &format!(r#"The minimum logical qubit error rate is obtained by dividing the logical error probability {} by the product of {} logical qubits and the total cycle count {}."#, formatted_counts.error_budget_logical, format_thousand_sep(&result.layout_overhead().logical_qubits()), format_thousand_sep(&result.num_cycles()))));
         entries.push(ReportEntry::new("physicalCountsFormatted/requiredLogicalTstateErrorRate", "Required logical T state error rate", r#"The minimum T state error rate required for distilled T states"#, &format!(r#"The minimum T state error rate is obtained by dividing the T distillation error probability {} by the total number of T states {}."#, formatted_counts.error_budget_tstates, format_thousand_sep(&result.num_magic_states()))));
-        entries.push(ReportEntry::new("physicalCountsFormatted/numTsPerRotation", "Number of T states per rotation", r#"Number of T states to implement a rotation with an arbitrary angle"#, &format!(r#"The number of T states to implement a rotation with an arbitrary angle is $\lceil 0.53 \log_2({} / {}) + 4.86\rceil$ [[arXiv:2203.10064](https://arxiv.org/abs/2203.10064)].  For simplicity, we use this formula for all single-qubit arbitrary angle rotations, and do not distinguish between best, worst, and average cases."#, format_thousand_sep(&logical_counts.rotation_count), result.error_budget().rotations())));
+        entries.push(ReportEntry::new("physicalCountsFormatted/numTsPerRotation", "Number of T states per rotation", r#"Number of T states to implement a rotation with an arbitrary angle"#, &format!(r#"The number of T states to implement a rotation with an arbitrary angle is $\lceil 0.53 \log_2({} / {}) + 4.86\rceil$ [[arXiv:2203.10064](https://arxiv.org/abs/2203.10064)].  For simplicity, we use this formula for all single-qubit arbitrary angle rotations, and do not distinguish between best, worst, and average cases."#, format_thousand_sep(&logical_counts.rotation_count()), result.error_budget().rotations())));
         groups.push(ReportEntryGroup {
             title: "Resource estimates breakdown".into(),
             always_visible: false,
@@ -353,9 +358,12 @@ pub struct FormattedPhysicalResourceCounts {
 
 impl FormattedPhysicalResourceCounts {
     #[allow(clippy::too_many_lines)]
-    pub fn new<L: Overhead + Clone>(
-        result: &PhysicalResourceEstimationResult<Protocol, TFactory, L>,
-        logical_resources: &LogicalResourceCounts,
+    pub fn new(
+        result: &PhysicalResourceEstimationResult<
+            Protocol,
+            TFactory,
+            impl Overhead + LayoutReportData,
+        >,
         job_params: &JobParams,
     ) -> Self {
         // Physical resource estimates
@@ -485,14 +493,16 @@ impl FormattedPhysicalResourceCounts {
             });
 
         // Pre-layout logical resources
-        let logical_counts_num_qubits = format_metric_prefix(logical_resources.num_qubits);
-        let logical_counts_t_count = format_metric_prefix(logical_resources.t_count);
-        let logical_counts_rotation_count = format_metric_prefix(logical_resources.rotation_count);
-        let logical_counts_rotation_depth = format_metric_prefix(logical_resources.rotation_depth);
-        let logical_counts_ccz_count = format_metric_prefix(logical_resources.ccz_count);
-        let logical_counts_ccix_count = format_metric_prefix(logical_resources.ccix_count);
+        let logical_counts_num_qubits = format_metric_prefix(result.layout_overhead().num_qubits());
+        let logical_counts_t_count = format_metric_prefix(result.layout_overhead().t_count());
+        let logical_counts_rotation_count =
+            format_metric_prefix(result.layout_overhead().rotation_count());
+        let logical_counts_rotation_depth =
+            format_metric_prefix(result.layout_overhead().rotation_depth());
+        let logical_counts_ccz_count = format_metric_prefix(result.layout_overhead().ccz_count());
+        let logical_counts_ccix_count = format_metric_prefix(result.layout_overhead().ccix_count());
         let logical_counts_measurement_count =
-            format_metric_prefix(logical_resources.measurement_count);
+            format_metric_prefix(result.layout_overhead().measurement_count());
 
         // Assumed error budget
         let error_budget = format!("{:.2e}", job_params.error_budget().total());
diff --git a/resource_estimator/src/system/data/result.rs b/resource_estimator/src/system/data/result.rs
index 1082d54e..fb201c87 100644
--- a/resource_estimator/src/system/data/result.rs
+++ b/resource_estimator/src/system/data/result.rs
@@ -1,19 +1,22 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
+use std::rc::Rc;
+
 use crate::estimates::{ErrorBudget, LogicalPatch, Overhead, PhysicalResourceEstimationResult};
 use crate::system::modeling::{Protocol, TFactory};
 
+use super::LayoutReportData;
 use super::{
-    super::Error, FormattedPhysicalResourceCounts, JobParams, LogicalResourceCounts,
-    PhysicalResourceCounts, PhysicalResourceCountsBreakdown, Report,
+    super::Error, FormattedPhysicalResourceCounts, JobParams, PhysicalResourceCounts,
+    PhysicalResourceCountsBreakdown, Report,
 };
 use miette::Diagnostic;
 use serde::{ser::SerializeMap, Serialize, Serializer};
 
 #[derive(Serialize)]
 #[serde(rename_all(serialize = "camelCase"))]
-pub struct Success {
+pub struct Success<L: Serialize> {
     status: &'static str,
     job_params: JobParams,
     #[serde(skip_serializing_if = "Option::is_none")]
@@ -26,25 +29,25 @@ pub struct Success {
     tfactory: Option<TFactory>,
     #[serde(skip_serializing_if = "Option::is_none")]
     error_budget: Option<ErrorBudget>,
-    logical_counts: LogicalResourceCounts,
+    logical_counts: Rc<L>,
     report_data: Report,
     #[serde(skip_serializing_if = "Vec::is_empty")]
     frontier_entries: Vec<FrontierEntry>,
 }
 
-impl Success {
-    pub fn new<L: Overhead + Clone>(
-        logical_resources: LogicalResourceCounts,
+impl<L: Overhead + LayoutReportData + Serialize> Success<L> {
+    pub fn new(
         job_params: JobParams,
         result: PhysicalResourceEstimationResult<Protocol, TFactory, L>,
     ) -> Self {
         let counts = create_physical_resource_counts(&result);
 
         let formatted_counts: FormattedPhysicalResourceCounts =
-            FormattedPhysicalResourceCounts::new(&result, &logical_resources, &job_params);
+            FormattedPhysicalResourceCounts::new(&result, &job_params);
 
-        let report_data = Report::new(&logical_resources, &job_params, &result, &formatted_counts);
+        let report_data = Report::new(&job_params, &result, &formatted_counts);
 
+        let logical_counts = result.layout_overhead().clone();
         let (logical_qubit, tfactory, error_budget) = result.take();
 
         Self {
@@ -55,14 +58,13 @@ impl Success {
             logical_qubit: Some(logical_qubit),
             tfactory,
             error_budget: Some(error_budget),
-            logical_counts: logical_resources,
+            logical_counts,
             report_data,
             frontier_entries: Vec::new(),
         }
     }
 
-    pub fn new_from_multiple<L: Overhead + Clone>(
-        logical_resources: LogicalResourceCounts,
+    pub fn new_from_multiple(
         job_params: JobParams,
         mut results: Vec<PhysicalResourceEstimationResult<Protocol, TFactory, L>>,
     ) -> Self {
@@ -70,15 +72,13 @@ impl Success {
 
         let mut frontier_entries: Vec<FrontierEntry> = Vec::new();
 
+        let logical_counts = results[0].layout_overhead().clone();
+
         // we will pick the shortest runtime result as the first result.
         results.sort_by_key(PhysicalResourceEstimationResult::runtime);
         for result in results {
-            let (frontier_entry, report) = create_frontier_entry(
-                &logical_resources,
-                &job_params,
-                result,
-                report_data.is_none(),
-            );
+            let (frontier_entry, report) =
+                create_frontier_entry(&job_params, result, report_data.is_none());
 
             if report_data.is_none() {
                 report_data = Some(report.expect("error should have report"));
@@ -95,7 +95,7 @@ impl Success {
             logical_qubit: None,
             tfactory: None,
             error_budget: None,
-            logical_counts: logical_resources,
+            logical_counts,
             report_data: report_data.expect("error should have report"), // Here we assume that at least a single solution was found.
             frontier_entries,
         }
@@ -112,24 +112,18 @@ pub struct FrontierEntry {
     pub physical_counts_formatted: FormattedPhysicalResourceCounts,
 }
 
-fn create_frontier_entry<L: Overhead + Clone>(
-    logical_resources: &LogicalResourceCounts,
+fn create_frontier_entry(
     job_params: &JobParams,
-    result: PhysicalResourceEstimationResult<Protocol, TFactory, L>,
+    result: PhysicalResourceEstimationResult<Protocol, TFactory, impl Overhead + LayoutReportData>,
     create_report: bool,
 ) -> (FrontierEntry, Option<Report>) {
     let physical_counts = create_physical_resource_counts(&result);
 
     let physical_counts_formatted: FormattedPhysicalResourceCounts =
-        FormattedPhysicalResourceCounts::new(&result, logical_resources, job_params);
+        FormattedPhysicalResourceCounts::new(&result, job_params);
 
     let report_data = if create_report {
-        Some(Report::new(
-            logical_resources,
-            job_params,
-            &result,
-            &physical_counts_formatted,
-        ))
+        Some(Report::new(job_params, &result, &physical_counts_formatted))
     } else {
         None
     };
@@ -148,8 +142,8 @@ fn create_frontier_entry<L: Overhead + Clone>(
     )
 }
 
-fn create_physical_resource_counts<L: Overhead + Clone>(
-    result: &PhysicalResourceEstimationResult<Protocol, TFactory, L>,
+fn create_physical_resource_counts(
+    result: &PhysicalResourceEstimationResult<Protocol, TFactory, impl Overhead>,
 ) -> PhysicalResourceCounts {
     let breakdown = create_physical_resource_counts_breakdown(result);
 
@@ -161,8 +155,8 @@ fn create_physical_resource_counts<L: Overhead + Clone>(
     }
 }
 
-fn create_physical_resource_counts_breakdown<L: Overhead + Clone>(
-    result: &PhysicalResourceEstimationResult<Protocol, TFactory, L>,
+fn create_physical_resource_counts_breakdown(
+    result: &PhysicalResourceEstimationResult<Protocol, TFactory, impl Overhead>,
 ) -> PhysicalResourceCountsBreakdown {
     let num_ts_per_rotation = result
         .layout_overhead()
diff --git a/vscode/src/common.ts b/vscode/src/common.ts
index 6baf9294..29217adc 100644
--- a/vscode/src/common.ts
+++ b/vscode/src/common.ts
@@ -21,14 +21,6 @@ export function isQsharpNotebookCell(document: TextDocument): boolean {
 
 export const qsharpExtensionId = "qsharp-vscode";
 
-export interface FileAccessor {
-  normalizePath(path: string): string;
-  convertToWindowsPathSeparator(path: string): string;
-  resolvePathToUri(path: string): Uri;
-  openPath(path: string): Promise<TextDocument>;
-  openUri(uri: Uri): Promise<TextDocument>;
-}
-
 export function basename(path: string): string | undefined {
   return path.replace(/\/+$/, "").split("/").pop();
 }
diff --git a/vscode/src/debugger/activate.ts b/vscode/src/debugger/activate.ts
index f1e291c3..f9177a00 100644
--- a/vscode/src/debugger/activate.ts
+++ b/vscode/src/debugger/activate.ts
@@ -3,8 +3,8 @@
 
 /* eslint-disable @typescript-eslint/no-unused-vars */
 
-import { IDebugServiceWorker, getDebugServiceWorker } from "qsharp-lang";
-import { qsharpExtensionId, isQsharpDocument, FileAccessor } from "../common";
+import { IDebugServiceWorker, getDebugServiceWorker, log } from "qsharp-lang";
+import { qsharpExtensionId, isQsharpDocument } from "../common";
 import { QscDebugSession } from "./session";
 import { getRandomGuid } from "../utils";
 
@@ -49,13 +49,18 @@ function registerCommands(context: vscode.ExtensionContext) {
         }
 
         if (targetResource) {
+          // We'll omit config.program and let the configuration
+          // resolver fill it in with the currently open editor's URI.
+          // This will also let us correctly handle untitled files
+          // where the save prompt pops up before the debugger is launched,
+          // potentially causing the active editor URI to change if
+          // the file is saved with a different name.
           vscode.debug.startDebugging(
             undefined,
             {
               type: "qsharp",
               name: "Run Q# File",
               request: "launch",
-              program: targetResource.toString(),
               shots: 1,
               stopOnEntry: false,
             },
@@ -73,11 +78,16 @@ function registerCommands(context: vscode.ExtensionContext) {
         }
 
         if (targetResource) {
+          // We'll omit config.program and let the configuration
+          // resolver fill it in with the currently open editor's URI.
+          // This will also let us correctly handle untitled files
+          // where the save prompt pops up before the debugger is launched,
+          // potentially causing the active editor URI to change if
+          // the file is saved with a different name.
           vscode.debug.startDebugging(undefined, {
             type: "qsharp",
             name: "Debug Q# File",
             request: "launch",
-            program: targetResource.toString(),
             shots: 1,
             stopOnEntry: true,
             noDebug: false,
@@ -101,29 +111,51 @@ class QsDebugConfigProvider implements vscode.DebugConfigurationProvider {
         config.type = "qsharp";
         config.name = "Launch";
         config.request = "launch";
-        config.program = editor.document.uri.toString();
+        config.programUri = editor.document.uri.toString();
         config.shots = 1;
         config.noDebug = "noDebug" in config ? config.noDebug : false;
         config.stopOnEntry = !config.noDebug;
       }
+    } else if (config.program && folder) {
+      // A program is specified in launch.json.
+      //
+      // Variable substitution is a bit odd in VS Code. Variables such as
+      // ${file} and ${workspaceFolder} are expanded to absolute filesystem
+      // paths with platform-specific separators. To correctly convert them
+      // back to a URI, we need to use the vscode.Uri.file constructor.
+      //
+      // However, this gives us the URI scheme file:// , which is not correct
+      // when the workspace uses a virtual filesystem such as qsharp-vfs://
+      // or vscode-test-web://. So now we also need the workspace folder URI
+      // to use as the basis for our file URI.
+      //
+      // Examples of program paths that can come through variable substitution:
+      // C:\foo\bar.qs
+      // \foo\bar.qs
+      // /foo/bar.qs
+      const fileUri = vscode.Uri.file(config.program);
+      config.programUri = folder.uri
+        .with({
+          path: fileUri.path,
+        })
+        .toString();
     } else {
-      // we have a launch config, resolve the program path
-
-      // ensure we have the program uri correctly formatted
-      // this is a user specified path.
-      if (config.program) {
-        const uri = workspaceFileAccessor.resolvePathToUri(config.program);
-        config.program = uri.toString();
-      } else {
-        // Use the active editor if no program or ${file} is specified.
-        const editor = vscode.window.activeTextEditor;
-        if (editor && isQsharpDocument(editor.document)) {
-          config.program = editor.document.uri.toString();
-        }
+      // Use the active editor if no program is specified.
+      const editor = vscode.window.activeTextEditor;
+      if (editor && isQsharpDocument(editor.document)) {
+        config.programUri = editor.document.uri.toString();
       }
     }
 
-    if (!config.program) {
+    log.trace(
+      `resolveDebugConfigurationWithSubstitutedVariables config.program=${
+        config.program
+      } folder.uri=${folder?.uri.toString()} config.programUri=${
+        config.programUri
+      }`,
+    );
+
+    if (!config.programUri) {
       // abort launch
       return vscode.window
         .showInformationMessage("Cannot find a Q# program to debug")
@@ -157,35 +189,6 @@ class QsDebugConfigProvider implements vscode.DebugConfigurationProvider {
   }
 }
 
-// The path normalization, fallbacks, and uri resolution are necessary
-// due to https://github.com/microsoft/vscode-debugadapter-node/issues/298
-// We can't specify that the debug adapter should use Uri for paths and can't
-// use the DebugSession conversion functions because they don't work in the web.
-export const workspaceFileAccessor: FileAccessor = {
-  normalizePath(path: string): string {
-    return path.replace(/\\/g, "/");
-  },
-  convertToWindowsPathSeparator(path: string): string {
-    return path.replace(/\//g, "\\");
-  },
-  resolvePathToUri(path: string): vscode.Uri {
-    const normalizedPath = this.normalizePath(path);
-    return vscode.Uri.parse(normalizedPath, false);
-  },
-  async openPath(path: string): Promise<vscode.TextDocument> {
-    const uri: vscode.Uri = this.resolvePathToUri(path);
-    return this.openUri(uri);
-  },
-  async openUri(uri: vscode.Uri): Promise<vscode.TextDocument> {
-    try {
-      return await vscode.workspace.openTextDocument(uri);
-    } catch {
-      const path = this.convertToWindowsPathSeparator(uri.toString());
-      return await vscode.workspace.openTextDocument(vscode.Uri.file(path));
-    }
-  },
-};
-
 class InlineDebugAdapterFactory
   implements vscode.DebugAdapterDescriptorFactory
 {
@@ -194,12 +197,9 @@ class InlineDebugAdapterFactory
     _executable: vscode.DebugAdapterExecutable | undefined,
   ): Promise<vscode.DebugAdapterDescriptor> {
     const worker = debugServiceWorkerFactory();
-    const uri = workspaceFileAccessor.resolvePathToUri(
-      session.configuration.program,
-    );
+    const uri = vscode.Uri.parse(session.configuration.programUri);
     const project = await loadProject(uri);
     const qscSession = new QscDebugSession(
-      workspaceFileAccessor,
       worker,
       session.configuration,
       project.sources,
diff --git a/vscode/src/debugger/session.ts b/vscode/src/debugger/session.ts
index 11dc2b8b..89e5adee 100644
--- a/vscode/src/debugger/session.ts
+++ b/vscode/src/debugger/session.ts
@@ -21,12 +21,7 @@ import {
   Handles,
   Scope,
 } from "@vscode/debugadapter";
-import {
-  FileAccessor,
-  basename,
-  isQsharpDocument,
-  toVscodeRange,
-} from "../common";
+import { basename, isQsharpDocument, toVscodeRange } from "../common";
 import { DebugProtocol } from "@vscode/debugprotocol";
 import {
   IDebugServiceWorker,
@@ -34,7 +29,6 @@ import {
   StepResultId,
   IStructStepResult,
   QscEventTarget,
-  qsharpLibraryUriScheme,
 } from "qsharp-lang";
 import { createDebugConsoleEventTarget } from "./output";
 import { ILaunchRequestArguments } from "./types";
@@ -68,6 +62,8 @@ interface IBreakpointLocationData {
 export class QscDebugSession extends LoggingDebugSession {
   private static threadID = 1;
 
+  private readonly knownPaths = new Map<string, string>();
+
   private breakpointLocations: Map<string, IBreakpointLocationData[]>;
   private breakpoints: Map<string, DebugProtocol.Breakpoint[]>;
   private variableHandles = new Handles<"locals" | "quantum">();
@@ -76,7 +72,6 @@ export class QscDebugSession extends LoggingDebugSession {
   private supportsVariableType = false;
 
   public constructor(
-    private fileAccessor: FileAccessor,
     private debugService: IDebugServiceWorker,
     private config: vscode.DebugConfiguration,
     private sources: [string, string][],
@@ -93,7 +88,19 @@ export class QscDebugSession extends LoggingDebugSession {
     this.breakpoints = new Map<string, DebugProtocol.Breakpoint[]>();
     this.setDebuggerLinesStartAt1(false);
     this.setDebuggerColumnsStartAt1(false);
-    this.languageFeatures = languageFeatures;
+
+    for (const source of sources) {
+      const uri = vscode.Uri.parse(source[0], true);
+
+      // In Debug Protocol requests, the VS Code debug adapter client
+      // will strip file URIs to just the filesystem path.
+      // Keep track of the filesystem paths we know about so that
+      // we can resolve them back to the original URI when handling requests.
+      // See `asUri()` for more details.
+      if (uri.scheme === "file") {
+        this.knownPaths.set(uri.fsPath, uri.toString());
+      }
+    }
   }
 
   public async init(associationId: string): Promise<void> {
@@ -468,27 +475,15 @@ export class QscDebugSession extends LoggingDebugSession {
       breakpoints: [],
     };
 
-    const file = await this.fileAccessor
-      .openPath(args.source.path ?? "")
-      .catch((e) => {
-        log.trace(`Failed to open file: ${e}`);
-        const fileUri = this.fileAccessor.resolvePathToUri(
-          args.source.path ?? "",
-        );
-        log.trace(
-          "breakpointLocationsRequest, target file: " + fileUri.toString(),
-        );
-        return undefined;
-      });
+    const doc = await this.tryLoadSource(args.source);
+    log.trace(
+      `breakpointLocationsRequest: path=${args.source.path} resolved to ${doc?.uri}`,
+    );
 
-    // If we couldn't find the file, or it wasn't a Q# file, or
-    // the range is longer than the file, just return
+    // If we couldn't find the document, or if
+    // the range is longer than the document, just return
     const targetLineNumber = this.convertClientLineToDebugger(args.line);
-    if (
-      !file ||
-      !isQsharpDocument(file) ||
-      targetLineNumber >= file.lineCount
-    ) {
+    if (!doc || targetLineNumber >= doc.lineCount) {
       log.trace(`setBreakPointsResponse: %O`, response);
       this.sendResponse(response);
       return;
@@ -498,7 +493,7 @@ export class QscDebugSession extends LoggingDebugSession {
     // everything from `file` is 0 based, everything from `args` is 1 based
     // so we have to convert anything from `args` to 0 based
 
-    const line = file.lineAt(targetLineNumber);
+    const line = doc.lineAt(targetLineNumber);
     const lineRange = line.range;
     // If the column isn't specified, it is a line breakpoint so that we
     // use the whole line's range for breakpoint finding.
@@ -534,7 +529,7 @@ export class QscDebugSession extends LoggingDebugSession {
     // column offset, so we need to check if the startOffset is within range.
     const bps =
       this.breakpointLocations
-        .get(file.uri.toString())
+        .get(doc.uri.toString())
         ?.filter((bp) =>
           isLineBreakpoint
             ? bp.range.start.line == requestRange.start.line
@@ -563,31 +558,25 @@ export class QscDebugSession extends LoggingDebugSession {
   ): Promise<void> {
     log.trace(`setBreakPointsRequest: %O`, args);
 
-    const file = await this.fileAccessor
-      .openPath(args.source.path ?? "")
-      .catch((e) => {
-        log.trace(`setBreakPointsRequest - Failed to open file: ${e}`);
-        const fileUri = this.fileAccessor.resolvePathToUri(
-          args.source.path ?? "",
-        );
-        log.trace("setBreakPointsRequest, target file: " + fileUri.toString());
-        return undefined;
-      });
+    const doc = await this.tryLoadSource(args.source);
+    log.trace(
+      `setBreakPointsRequest: path=${args.source.path} resolved to ${doc?.uri}`,
+    );
 
-    // If we couldn't find the file, or it wasn't a Q# file, just return
-    if (!file || !isQsharpDocument(file)) {
+    // If we couldn't find the document, just return
+    if (!doc) {
       log.trace(`setBreakPointsResponse: %O`, response);
       this.sendResponse(response);
       return;
     }
 
     log.trace(`setBreakPointsRequest: looking`);
-    this.breakpoints.set(file.uri.toString(), []);
+    this.breakpoints.set(doc.uri.toString(), []);
     log.trace(
       `setBreakPointsRequest: files in cache %O`,
       this.breakpointLocations.keys(),
     );
-    const locations = this.breakpointLocations.get(file.uri.toString()) ?? [];
+    const locations = this.breakpointLocations.get(doc.uri.toString()) ?? [];
     log.trace(`setBreakPointsRequest: got locations %O`, locations);
     const desiredBpOffsets: {
       range: vscode.Range;
@@ -597,12 +586,12 @@ export class QscDebugSession extends LoggingDebugSession {
       .filter(
         (sourceBreakpoint) =>
           this.convertClientLineToDebugger(sourceBreakpoint.line) <
-          file.lineCount,
+          doc.lineCount,
       )
       .map((sourceBreakpoint) => {
         const isLineBreakpoint = !sourceBreakpoint.column;
         const line = this.convertClientLineToDebugger(sourceBreakpoint.line);
-        const lineRange = file.lineAt(line).range;
+        const lineRange = doc.lineAt(line).range;
         const startCol = sourceBreakpoint.column
           ? this.convertClientColumnToDebugger(sourceBreakpoint.column)
           : lineRange.start.character;
@@ -644,7 +633,7 @@ export class QscDebugSession extends LoggingDebugSession {
     }
 
     // Update our breakpoint list for the given file
-    this.breakpoints.set(file.uri.toString(), bps);
+    this.breakpoints.set(doc.uri.toString(), bps);
 
     response.body = {
       breakpoints: bps,
@@ -675,79 +664,29 @@ export class QscDebugSession extends LoggingDebugSession {
     const mappedStackFrames = await Promise.all(
       debuggerStackFrames
         .map(async (f, id) => {
-          log.trace(`frames: path %O`, f.path);
-
-          const file = await this.fileAccessor
-            .openPath(f.path ?? "")
-            .catch((e) => {
-              log.error(`stackTraceRequest - Failed to open file: ${e}`);
-              const fileUri = this.fileAccessor.resolvePathToUri(f.path ?? "");
-              log.trace(
-                "stackTraceRequest, target file: " + fileUri.toString(),
-              );
-            });
-          if (file) {
-            log.trace(`frames: file %O`, file);
-            const sf: DebugProtocol.StackFrame = new StackFrame(
-              id,
-              f.name,
-              new Source(
-                basename(f.path) ?? f.path,
-                file.uri.toString(true),
-                undefined,
-                undefined,
-                "qsharp-adapter-data",
-              ),
-              this.convertDebuggerLineToClient(f.range.start.line),
-              this.convertDebuggerColumnToClient(f.range.start.character),
-            );
-            sf.endLine = this.convertDebuggerLineToClient(f.range.end.line);
-            sf.endColumn = this.convertDebuggerColumnToClient(
-              f.range.end.character,
-            );
-            return sf;
-          } else {
-            try {
-              // This file isn't part of the workspace, so we'll
-              // create a URI which can try to load it from the core and std lib
-              // There is a custom content provider subscribed to this scheme.
-              // Opening the text document by that uri will use the content
-              // provider to look for the source code.
-              const uri = vscode.Uri.from({
-                scheme: qsharpLibraryUriScheme,
-                path: f.path,
-              });
-              const source = new Source(
-                basename(f.path) ?? f.path,
-                uri.toString(),
-                0,
-                "internal core/std library",
-                "qsharp-adapter-data",
-              ) as DebugProtocol.Source;
-              const sf = new StackFrame(
-                id,
-                f.name,
-                source as Source,
-                this.convertDebuggerLineToClient(f.range.start.line),
-                this.convertDebuggerColumnToClient(f.range.start.character),
-              );
-              sf.endLine = this.convertDebuggerLineToClient(f.range.end.line);
-              sf.endColumn = this.convertDebuggerColumnToClient(
-                f.range.end.character,
-              );
-
-              return sf as DebugProtocol.StackFrame;
-            } catch (e: any) {
-              log.warn(e.message);
-              return new StackFrame(
-                id,
-                f.name,
-                undefined,
-                undefined,
-                undefined,
-              );
-            }
-          }
+          log.trace(`frames: location %O`, f.location);
+
+          const uri = f.location.source;
+          const sf: DebugProtocol.StackFrame = new StackFrame(
+            id,
+            f.name,
+            new Source(
+              basename(vscode.Uri.parse(uri).path) ?? uri,
+              uri,
+              undefined,
+              undefined,
+              "qsharp-adapter-data",
+            ),
+            this.convertDebuggerLineToClient(f.location.span.start.line),
+            this.convertDebuggerColumnToClient(f.location.span.start.character),
+          );
+          sf.endLine = this.convertDebuggerLineToClient(
+            f.location.span.end.line,
+          );
+          sf.endColumn = this.convertDebuggerColumnToClient(
+            f.location.span.end.character,
+          );
+          return sf;
         })
         .filter(filterUndefined),
     );
@@ -883,4 +822,65 @@ export class QscDebugSession extends LoggingDebugSession {
     );
     this.sendEvent(evt);
   }
+
+  /**
+   * Attempts to find the Source in the current session and returns the
+   * TextDocument if it exists.
+   *
+   * This method *may* return a valid result even when the requested
+   * path does not belong in the current program (e.g. another Q# file
+   * in the workspace).
+   */
+  async tryLoadSource(source: DebugProtocol.Source) {
+    if (!source.path) {
+      return;
+    }
+
+    const uri = this.asUri(source.path);
+    if (!uri) {
+      return;
+    }
+
+    try {
+      const doc = await vscode.workspace.openTextDocument(uri);
+      if (!isQsharpDocument(doc)) {
+        return;
+      }
+      return doc;
+    } catch (e) {
+      log.trace(`Failed to open ${uri}: ${e}`);
+    }
+  }
+
+  /**
+   * Attemps to resolve a DebugProtocol.Source.path to a URI.
+   *
+   * In Debug Protocol requests, the VS Code debug adapter client
+   * will strip file URIs to just the filesystem path part.
+   * But for non-file URIs, the full URI is sent.
+   *
+   * See: https://github.com/microsoft/vscode/blob/3246d63177e1e5ae211029e7ab0021c33342a3c7/src/vs/workbench/contrib/debug/common/debugSource.ts#L90
+   *
+   * Here, we need the original URI, but we don't know if we're
+   * dealing with a filesystem path or URI. We cannot determine
+   * which one it is based on the input alone (the syntax is ambiguous).
+   * But we do have a set of *known* filesystem paths that we
+   * constructed at initialization, and we can use that to resolve
+   * any known fileystem paths back to the original URI.
+   *
+   * Filesystem paths we don't know about *won't* be resolved,
+   * and that's ok in this use case.
+   *
+   * If the path was originally constructed from a URI, it won't
+   * be in our known paths map, so we'll treat the string as a URI.
+   */
+  asUri(pathOrUri: string): vscode.Uri | undefined {
+    pathOrUri = this.knownPaths.get(pathOrUri) || pathOrUri;
+
+    try {
+      return vscode.Uri.parse(pathOrUri);
+    } catch (e) {
+      log.trace(`Could not resolve path ${pathOrUri}`);
+    }
+  }
 }
diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts
index 8c440dd5..5b27331a 100644
--- a/vscode/src/extension.ts
+++ b/vscode/src/extension.ts
@@ -103,6 +103,21 @@ function registerDocumentUpdateHandlers(languageService: ILanguageService) {
     updateIfQsharpDocument(document);
   });
 
+  // we manually send an OpenDocument telemetry event if this is a Q# document, because the
+  // below subscriptions won't fire for documents that are already open when the extension is activated
+  vscode.workspace.textDocuments.forEach((document) => {
+    if (isQsharpDocument(document)) {
+      const documentType = isQsharpNotebookCell(document)
+        ? QsharpDocumentType.JupyterCell
+        : QsharpDocumentType.Qsharp;
+      sendTelemetryEvent(
+        EventType.OpenedDocument,
+        { documentType },
+        { linesOfCode: document.lineCount },
+      );
+    }
+  });
+
   const subscriptions = [];
   subscriptions.push(
     vscode.workspace.onDidOpenTextDocument((document) => {
diff --git a/wasm/src/debug_service.rs b/wasm/src/debug_service.rs
index 27143cb4..2fd30c0f 100644
--- a/wasm/src/debug_service.rs
+++ b/wasm/src/debug_service.rs
@@ -8,7 +8,7 @@ use qsc::interpret::{Debugger, Error, StepAction, StepResult};
 use qsc::line_column::Encoding;
 use qsc::{fmt_complex, target::Profile, LanguageFeatures};
 
-use crate::line_column::Range;
+use crate::line_column::{Location, Range};
 use crate::{get_source_map, serializable_type, CallbackReceiver};
 use serde::{Deserialize, Serialize};
 use serde_json::json;
@@ -70,11 +70,10 @@ impl DebugService {
 
         StackFrameList {
             frames: frames
-                .iter()
+                .into_iter()
                 .map(|s| StackFrame {
                     name: format!("{} {}", s.name, s.functor),
-                    path: s.path.clone(),
-                    range: s.range.into(),
+                    location: s.location.into(),
                 })
                 .collect(),
         }
@@ -326,13 +325,11 @@ serializable_type! {
     StackFrame,
     {
         pub name: String,
-        pub path: String,
-        pub range: Range
+        pub location: Location
     },
     r#"export interface IStackFrame {
         name: string;
-        path: string;
-        range: IRange;
+        location: ILocation
     }"#
 }
 
diff --git a/wasm/src/logging.rs b/wasm/src/logging.rs
index 2d4caed1..81efa2b2 100644
--- a/wasm/src/logging.rs
+++ b/wasm/src/logging.rs
@@ -16,6 +16,12 @@ extern "C" {
 
     #[wasm_bindgen(structural, method, getter)]
     fn stack(error: &Error) -> String;
+
+    #[wasm_bindgen(static_method_of = Error, getter = stackTraceLimit)]
+    fn get_stack_trace_limit() -> Option<u32>;
+
+    #[wasm_bindgen(static_method_of = Error, setter = stackTraceLimit)]
+    fn set_stack_trace_limit(val: u32);
 }
 
 static MY_LOGGER: MyLogger = MyLogger;
@@ -65,7 +71,14 @@ pub fn hook(info: &std::panic::PanicInfo) {
     // for capturing the JS stack as well as the panic info
     let mut msg = info.to_string();
     msg.push_str("\n\nStack:\n\n");
+
+    // In debug builds, the logging and panic handling code itself takes
+    // up over 10 frames. Increase the stack trace limit temporarily.
+    let prev_limit = Error::get_stack_trace_limit().unwrap_or(10);
+    Error::set_stack_trace_limit(prev_limit.max(20));
     let e = Error::new();
+    Error::set_stack_trace_limit(prev_limit);
+
     let stack = e.stack();
     msg.push_str(&stack);
     msg.push_str("\n\n");


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index 34c89fb4..dde9d7a4 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -1044,6 +1044,20 @@ mod given_interpreter {
                 );
             }
         }
+
+        #[test]
+        fn base_prof_non_result_return() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                RuntimeCapabilityFlags::empty(),
+                LanguageFeatures::default(),
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(&mut interpreter, "123");
+            is_only_value(&result, &output, &Value::Int(123));
+        }
     }
 
     fn get_interpreter() -> Interpreter {
diff --git a/compiler/qsc_codegen/src/qir_base/tests.rs b/compiler/qsc_codegen/src/qir_base/tests.rs
index 00a47d21..84c332bf 100644
--- a/compiler/qsc_codegen/src/qir_base/tests.rs
+++ b/compiler/qsc_codegen/src/qir_base/tests.rs
@@ -440,7 +440,6 @@ fn qubit_allocation_allows_reuse_of_unmeasured_qubits() {
     check(
         "",
         Some(indoc! {"{
-            open Microsoft.Quantum.Measurement;
             { use (c, q) = (Qubit(), Qubit()); CNOT(c, q); MResetZ(q); }
             { use (c, q) = (Qubit(), Qubit()); CNOT(c, q); MResetZ(q) }
         }"}),
@@ -604,7 +603,6 @@ fn complex_program_is_valid() {
     check(
         "",
         Some(indoc! {"{
-            open Microsoft.Quantum.Measurement;
             open Microsoft.Quantum.Math;
 
             operation SWAPfromExp(q1 : Qubit, q2 : Qubit) : Unit is Ctl + Adj {
@@ -911,7 +909,6 @@ fn qubit_ids_properly_reused() {
         namespace Test {
 
             open Microsoft.Quantum.Intrinsic;
-            open Microsoft.Quantum.Measurement;
 
             // Verifies the use of the CNOT quantum gate from Q#'s Microsoft.Quantum.Intrinsic namespace.
             // Expected simulation output: ([0, 0], [1, 1]).
@@ -1011,7 +1008,6 @@ fn custom_intrinsic_on_single_qubit() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : (Result, Result) {
                 use (q1, q2) = (Qubit(), Qubit());
@@ -1086,7 +1082,6 @@ fn multiple_custom_intrinsic_calls() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : (Result, Result) {
                 use (q0, q1) = (Qubit(), Qubit());
@@ -1173,7 +1168,6 @@ fn custom_intrinsic_on_qubit_and_double() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1244,7 +1238,6 @@ fn custom_intrinsic_on_qubit_and_bool() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1315,7 +1308,6 @@ fn custom_intrinsic_on_qubit_and_int() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1386,7 +1378,6 @@ fn custom_intrinsic_fail_on_result_arg() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1410,8 +1401,8 @@ fn custom_intrinsic_fail_on_result_arg() {
                         2,
                     ),
                     span: Span {
-                        lo: 217,
-                        hi: 301,
+                        lo: 177,
+                        hi: 261,
                     },
                 },
             )
@@ -1424,7 +1415,6 @@ fn custom_intrinsic_fail_on_bigint_arg() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1448,8 +1438,8 @@ fn custom_intrinsic_fail_on_bigint_arg() {
                         2,
                     ),
                     span: Span {
-                        lo: 219,
-                        hi: 303,
+                        lo: 179,
+                        hi: 263,
                     },
                 },
             )
@@ -1462,7 +1452,6 @@ fn custom_intrinsic_fail_on_string_arg() {
     check(
         indoc! {r#"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1486,8 +1475,8 @@ fn custom_intrinsic_fail_on_string_arg() {
                         2,
                     ),
                     span: Span {
-                        lo: 230,
-                        hi: 314,
+                        lo: 190,
+                        hi: 274,
                     },
                 },
             )
@@ -1500,7 +1489,6 @@ fn custom_intrinsic_fail_on_array_arg() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1524,8 +1512,8 @@ fn custom_intrinsic_fail_on_array_arg() {
                         2,
                     ),
                     span: Span {
-                        lo: 225,
-                        hi: 308,
+                        lo: 185,
+                        hi: 268,
                     },
                 },
             )
@@ -1538,7 +1526,6 @@ fn custom_intrinsic_fail_on_tuple_arg() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1562,8 +1549,8 @@ fn custom_intrinsic_fail_on_tuple_arg() {
                         2,
                     ),
                     span: Span {
-                        lo: 225,
-                        hi: 318,
+                        lo: 185,
+                        hi: 278,
                     },
                 },
             )
@@ -1576,7 +1563,6 @@ fn custom_intrinsic_fail_on_non_unit_return() {
     check(
         indoc! {"
         namespace Test {
-            open Microsoft.Quantum.Measurement;
             @EntryPoint()
             operation Test() : Result {
                 use q = Qubit();
@@ -1598,8 +1584,8 @@ fn custom_intrinsic_fail_on_non_unit_return() {
                         2,
                     ),
                     span: Span {
-                        lo: 195,
-                        hi: 265,
+                        lo: 155,
+                        hi: 225,
                     },
                 },
             )
diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs
index 9e35ebf5..17fcf1a4 100644
--- a/compiler/qsc_eval/src/tests.rs
+++ b/compiler/qsc_eval/src/tests.rs
@@ -3410,8 +3410,6 @@ fn lambda_operation_empty_closure() {
     check_expr(
         "
             namespace A {
-                open Microsoft.Quantum.Measurement;
-
                 operation Foo(op : Qubit => ()) : Result {
                     use q = Qubit();
                     op(q);
@@ -3431,7 +3429,6 @@ fn lambda_operation_closure() {
     check_expr(
         "
             namespace A {
-                open Microsoft.Quantum.Measurement;
                 operation Foo(op : () => Result) : Result { op() }
                 operation Bar() : Result {
                     use q = Qubit();
@@ -3450,7 +3447,6 @@ fn lambda_operation_controlled() {
     check_expr(
         "
             namespace A {
-                open Microsoft.Quantum.Measurement;
                 operation Foo(op : Qubit => Unit is Adj + Ctl, q : Qubit) : Unit is Adj + Ctl { op(q) }
                 operation Bar() : Result[] {
                     mutable output = [];
@@ -3478,7 +3474,6 @@ fn lambda_operation_controlled_controlled() {
     check_expr(
         "
             namespace A {
-                open Microsoft.Quantum.Measurement;
                 operation Foo(op : Qubit => Unit is Adj + Ctl, q : Qubit) : Unit is Adj + Ctl { op(q) }
                 operation Bar() : Result[] {
                     mutable output = [];
diff --git a/compiler/qsc_frontend/src/resolve/tests.rs b/compiler/qsc_frontend/src/resolve/tests.rs
index da85adac..da9dd59e 100644
--- a/compiler/qsc_frontend/src/resolve/tests.rs
+++ b/compiler/qsc_frontend/src/resolve/tests.rs
@@ -2402,3 +2402,255 @@ fn get_locals_block_scope_boundary_begin() {
         &expect![""],
     );
 }
+
+#[test]
+fn use_after_scope() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function A() : Unit {
+                    {
+                        let x = 42;
+                    }
+                    x; // x should not be accessible here
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                function item1() : Unit {
+                    {
+                        let local16 = 42;
+                    }
+                    x; // x should not be accessible here
+                }
+            }
+
+            // NotFound("x", Span { lo: 94, hi: 95 })
+        "#]],
+    );
+}
+
+#[test]
+fn nested_function_definition() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function A() : Unit {
+                    function B() : Unit {
+                        function C() : Unit {}
+                        C();
+                    }
+                    B();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                function item1() : Unit {
+                    function item2() : Unit {
+                        function item3() : Unit {}
+                        item3();
+                    }
+                    item2();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn variable_in_nested_blocks() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function A() : Unit {
+                    {
+                        let x = 10;
+                        {
+                            let y = x + 5;
+                            y; // Should be accessible
+                        }
+                        y; // Should not be accessible
+                    }
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                function item1() : Unit {
+                    {
+                        let local16 = 10;
+                        {
+                            let local23 = local16 + 5;
+                            local23; // Should be accessible
+                        }
+                        y; // Should not be accessible
+                    }
+                }
+            }
+
+            // NotFound("y", Span { lo: 190, hi: 191 })
+        "#]],
+    );
+}
+
+#[test]
+fn function_call_with_namespace_alias() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function A() : Unit {}
+            }
+            namespace Bar {
+                open Foo as F;
+                function B() : Unit {
+                    F.A();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                function item1() : Unit {}
+            }
+            namespace item2 {
+                open Foo as F;
+                function item3() : Unit {
+                    item1();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn type_alias_in_function_scope() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function A() : Unit {
+                    newtype MyInt = Int;
+                    let x : MyInt = MyInt(5);
+                }
+                function B() : Unit {
+                    let z: MyInt = MyInt(5); // this should be a different type (and unresolved)
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                function item1() : Unit {
+                    newtype item3 = Int;
+                    let local20 : item3 = item3(5);
+                }
+                function item2() : Unit {
+                    let local40: MyInt = MyInt(5); // this should be a different type (and unresolved)
+                }
+            }
+
+            // NotFound("MyInt", Span { lo: 152, hi: 157 })
+            // NotFound("MyInt", Span { lo: 160, hi: 165 })
+        "#]],
+    );
+}
+
+#[test]
+fn lambda_inside_lambda() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function A() : Unit {
+                    let f = () -> {
+                        let g = (x) -> x + 1;
+                        g(10);
+                    };
+                    f();
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                function item1() : Unit {
+                    let local13 = () -> {
+                        let local20 = (local24) -> local24 + 1;
+                        local20(10);
+                    };
+                    local13();
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn nested_namespaces_with_same_function_name() {
+    check(
+        indoc! {"
+            namespace Foo {
+                function A() : Unit {}
+            }
+            namespace Bar {
+                function A() : Unit {}
+                function B() : Unit {
+                    Foo.A();
+                    A(); // Should call Bar.A without needing to qualify
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                function item1() : Unit {}
+            }
+            namespace item2 {
+                function item3() : Unit {}
+                function item4() : Unit {
+                    item1();
+                    item3(); // Should call Bar.A without needing to qualify
+                }
+            }
+        "#]],
+    );
+}
+
+#[test]
+fn newtype_with_invalid_field_type() {
+    check(
+        indoc! {"
+            namespace Foo {
+                newtype Complex = (Re: Real, Im: Imaginary); // Imaginary is not a valid type
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                newtype item1 = (Re: Real, Im: Imaginary); // Imaginary is not a valid type
+            }
+
+            // NotFound("Real", Span { lo: 43, hi: 47 })
+            // NotFound("Imaginary", Span { lo: 53, hi: 62 })
+        "#]],
+    );
+}
+
+#[test]
+fn newtype_with_tuple_destructuring() {
+    check(
+        indoc! {"
+            namespace Foo {
+                newtype Pair = (First: Int, Second: Int);
+                function Destructure(pair: Pair) : Int {
+                    let (first, second) = pair;
+                    first + second
+                }
+            }
+        "},
+        &expect![[r#"
+            namespace item0 {
+                newtype item1 = (First: Int, Second: Int);
+                function item2(local21: item1) : Int {
+                    let (local32, local34) = local21;
+                    local32 + local34
+                }
+            }
+        "#]],
+    );
+}
diff --git a/compiler/qsc_frontend/src/typeck/tests.rs b/compiler/qsc_frontend/src/typeck/tests.rs
index c313e195..1bc0886f 100644
--- a/compiler/qsc_frontend/src/typeck/tests.rs
+++ b/compiler/qsc_frontend/src/typeck/tests.rs
@@ -1677,6 +1677,129 @@ fn return_mismatch() {
     );
 }
 
+#[test]
+fn return_with_satisfying_specialization_succeeds() {
+    check(
+        indoc! {"
+            namespace test {
+                operation E() : Unit {}
+                operation A() : Unit is Adj {}
+                operation C() : Unit is Ctl {}
+                operation AC() : Unit is Adj + Ctl {}
+
+                function returns_A_as_A() : (Unit => Unit is Adj) { A }
+                function returns_AC_as_A() : (Unit => Unit is Adj) { AC }
+
+                function returns_AC_as_C() : (Unit => Unit is Ctl) { AC }
+                function returns_C_as_C() : (Unit => Unit is Ctl) { C }
+
+                function returns_A_as_E() : (Unit => Unit) { A }
+                function returns_AC_as_E() : (Unit => Unit) { AC }
+                function returns_C_as_E() : (Unit => Unit) { C }
+                function returns_E_as_E() : (Unit => Unit) { E }
+            }
+        "},
+        "",
+        &expect![[r##"
+            #6 32-34 "()" : Unit
+            #10 42-44 "{}" : Unit
+            #14 60-62 "()" : Unit
+            #19 77-79 "{}" : Unit
+            #23 95-97 "()" : Unit
+            #28 112-114 "{}" : Unit
+            #32 131-133 "()" : Unit
+            #39 154-156 "{}" : Unit
+            #43 185-187 "()" : Unit
+            #53 212-217 "{ A }" : (Unit => Unit is Adj)
+            #55 214-215 "A" : (Unit => Unit is Adj)
+            #61 246-248 "()" : Unit
+            #71 273-279 "{ AC }" : (Unit => Unit is Adj + Ctl)
+            #73 275-277 "AC" : (Unit => Unit is Adj + Ctl)
+            #79 309-311 "()" : Unit
+            #89 336-342 "{ AC }" : (Unit => Unit is Adj + Ctl)
+            #91 338-340 "AC" : (Unit => Unit is Adj + Ctl)
+            #97 370-372 "()" : Unit
+            #107 397-402 "{ C }" : (Unit => Unit is Ctl)
+            #109 399-400 "C" : (Unit => Unit is Ctl)
+            #115 431-433 "()" : Unit
+            #124 451-456 "{ A }" : (Unit => Unit is Adj)
+            #126 453-454 "A" : (Unit => Unit is Adj)
+            #132 485-487 "()" : Unit
+            #141 505-511 "{ AC }" : (Unit => Unit is Adj + Ctl)
+            #143 507-509 "AC" : (Unit => Unit is Adj + Ctl)
+            #149 539-541 "()" : Unit
+            #158 559-564 "{ C }" : (Unit => Unit is Ctl)
+            #160 561-562 "C" : (Unit => Unit is Ctl)
+            #166 592-594 "()" : Unit
+            #175 612-617 "{ E }" : (Unit => Unit)
+            #177 614-615 "E" : (Unit => Unit)
+        "##]],
+    );
+}
+
+#[test]
+fn return_with_unsatisfying_specialization_fails() {
+    check(
+        indoc! {"
+            namespace test {
+                operation E() : Unit {}
+                operation A() : Unit is Adj {}
+                operation C() : Unit is Ctl {}
+                operation AC() : Unit is Adj + Ctl {}
+
+                function returns_E_as_A() : (Unit => Unit is Adj) { E }
+                function returns_C_as_A() : (Unit => Unit is Adj) { C }
+
+                function returns_E_as_C() : (Unit => Unit is Ctl) { E }
+                function returns_A_as_C() : (Unit => Unit is Ctl) { A }
+
+                function returns_E_as_AC() : (Unit => Unit is Adj + Ctl) { E }
+                function returns_A_as_AC() : (Unit => Unit is Adj + Ctl) { A }
+                function returns_C_as_AC() : (Unit => Unit is Adj + Ctl) { C }
+            }
+        "},
+        "",
+        &expect![[r##"
+            #6 32-34 "()" : Unit
+            #10 42-44 "{}" : Unit
+            #14 60-62 "()" : Unit
+            #19 77-79 "{}" : Unit
+            #23 95-97 "()" : Unit
+            #28 112-114 "{}" : Unit
+            #32 131-133 "()" : Unit
+            #39 154-156 "{}" : Unit
+            #43 185-187 "()" : Unit
+            #53 212-217 "{ E }" : (Unit => Unit)
+            #55 214-215 "E" : (Unit => Unit)
+            #61 245-247 "()" : Unit
+            #71 272-277 "{ C }" : (Unit => Unit is Ctl)
+            #73 274-275 "C" : (Unit => Unit is Ctl)
+            #79 306-308 "()" : Unit
+            #89 333-338 "{ E }" : (Unit => Unit)
+            #91 335-336 "E" : (Unit => Unit)
+            #97 366-368 "()" : Unit
+            #107 393-398 "{ A }" : (Unit => Unit is Adj)
+            #109 395-396 "A" : (Unit => Unit is Adj)
+            #115 428-430 "()" : Unit
+            #127 461-466 "{ E }" : (Unit => Unit)
+            #129 463-464 "E" : (Unit => Unit)
+            #135 495-497 "()" : Unit
+            #147 528-533 "{ A }" : (Unit => Unit is Adj)
+            #149 530-531 "A" : (Unit => Unit is Adj)
+            #155 562-564 "()" : Unit
+            #167 595-600 "{ C }" : (Unit => Unit is Ctl)
+            #169 597-598 "C" : (Unit => Unit is Ctl)
+            Error(Type(Error(FunctorMismatch(Value(Adj), Value(Empty), Span { lo: 214, hi: 215 }))))
+            Error(Type(Error(FunctorMismatch(Value(Adj), Value(Ctl), Span { lo: 274, hi: 275 }))))
+            Error(Type(Error(FunctorMismatch(Value(Ctl), Value(Empty), Span { lo: 335, hi: 336 }))))
+            Error(Type(Error(FunctorMismatch(Value(Ctl), Value(Adj), Span { lo: 395, hi: 396 }))))
+            Error(Type(Error(FunctorMismatch(Value(CtlAdj), Value(Empty), Span { lo: 463, hi: 464 }))))
+            Error(Type(Error(FunctorMismatch(Value(CtlAdj), Value(Adj), Span { lo: 530, hi: 531 }))))
+            Error(Type(Error(FunctorMismatch(Value(CtlAdj), Value(Ctl), Span { lo: 597, hi: 598 }))))
+        "##]],
+    );
+}
+
 #[test]
 fn array_unknown_field_error() {
     check(
@@ -3215,8 +3338,6 @@ fn functors_in_array_mixed() {
             #47 180-183 "Foo" : (Qubit => Unit)
             #50 185-188 "Bar" : (Qubit => Unit is Adj)
             #53 190-193 "Baz" : (Qubit => Unit is Adj + Ctl)
-            Error(Type(Error(FunctorMismatch(Value(Empty), Value(Adj), Span { lo: 185, hi: 188 }))))
-            Error(Type(Error(FunctorMismatch(Value(Empty), Value(CtlAdj), Span { lo: 190, hi: 193 }))))
         "#]],
     );
 }
@@ -3337,7 +3458,6 @@ fn functors_in_arg_bound_to_let_becomes_monotype() {
             #63 232-235 "foo" : ((Qubit => Unit is Adj) => Unit)
             #66 235-240 "(Baz)" : (Qubit => Unit is Adj + Ctl)
             #67 236-239 "Baz" : (Qubit => Unit is Adj + Ctl)
-            Error(Type(Error(FunctorMismatch(Value(Adj), Value(CtlAdj), Span { lo: 232, hi: 240 }))))
         "#]],
     );
 }
@@ -3630,3 +3750,34 @@ fn use_range_field_names_in_udt() {
         "#]],
     );
 }
+
+#[test]
+fn lambda_on_array_where_item_used_in_call_should_be_inferred() {
+    check(
+        indoc! {"
+            namespace A {
+                function B() : Unit {
+                    let f = qs => C(qs[0]);
+                }
+                operation C(q : Qubit) : Unit is Ctl + Adj { }
+            }
+        "},
+        "",
+        &expect![[r##"
+            #6 28-30 "()" : Unit
+            #10 38-77 "{\n        let f = qs => C(qs[0]);\n    }" : Unit
+            #12 52-53 "f" : (Qubit[] => Unit)
+            #14 56-70 "qs => C(qs[0])" : (Qubit[] => Unit)
+            #15 56-58 "qs" : Qubit[]
+            #17 62-70 "C(qs[0])" : Unit
+            #18 62-63 "C" : (Qubit => Unit is Adj + Ctl)
+            #21 63-70 "(qs[0])" : Qubit
+            #22 64-69 "qs[0]" : Qubit
+            #23 64-66 "qs" : Qubit[]
+            #26 67-68 "0" : Int
+            #30 93-104 "(q : Qubit)" : Qubit
+            #31 94-103 "q : Qubit" : Qubit
+            #42 125-128 "{ }" : Unit
+        "##]],
+    );
+}
diff --git a/compiler/qsc_parse/src/item/tests.rs b/compiler/qsc_parse/src/item/tests.rs
index d5e0aa85..d6c1de40 100644
--- a/compiler/qsc_parse/src/item/tests.rs
+++ b/compiler/qsc_parse/src/item/tests.rs
@@ -1557,3 +1557,139 @@ fn disallow_qubit_scoped_block() {
             ]"#]],
     );
 }
+
+#[test]
+fn reject_nested_namespace_with_items() {
+    check_vec(
+        parse_namespaces,
+        "namespace Outer {
+            namespace Inner {
+                function NestedFunction() : Unit {}
+                newtype NestedType = Int;
+            }
+        }",
+        &expect![[r#"
+            Namespace _id_ [0-99] (Ident _id_ [10-15] "Outer"):
+
+            [
+                Error(
+                    Token(
+                        Close(
+                            Brace,
+                        ),
+                        Keyword(
+                            Namespace,
+                        ),
+                        Span {
+                            lo: 30,
+                            hi: 39,
+                        },
+                    ),
+                ),
+                Error(
+                    Token(
+                        Eof,
+                        Keyword(
+                            Newtype,
+                        ),
+                        Span {
+                            lo: 116,
+                            hi: 123,
+                        },
+                    ),
+                ),
+            ]"#]],
+    );
+}
+
+#[test]
+fn reject_namespace_with_multiple_nested_levels() {
+    check_vec(
+        parse_namespaces,
+        "namespace LevelOne {
+            namespace LevelTwo {
+                namespace LevelThree {
+                    function DeepFunction() : Unit {}
+                }
+            }
+        }",
+        &expect![[r#"
+            Namespace _id_ [0-146] (Ident _id_ [10-18] "LevelOne"):
+
+            [
+                Error(
+                    Token(
+                        Close(
+                            Brace,
+                        ),
+                        Keyword(
+                            Namespace,
+                        ),
+                        Span {
+                            lo: 33,
+                            hi: 42,
+                        },
+                    ),
+                ),
+                Error(
+                    Token(
+                        Eof,
+                        Close(
+                            Brace,
+                        ),
+                        Span {
+                            lo: 163,
+                            hi: 164,
+                        },
+                    ),
+                ),
+            ]"#]],
+    );
+}
+
+#[test]
+fn namespace_with_attributes_and_docs() {
+    check_vec(
+        parse_namespaces,
+        "/// Documentation for LevelOne
+        namespace LevelOne {
+            @ExampleAttr()
+            /// Documentation that shouldn't show up, since docstrings go above attrs
+            function InnerItem() : Unit {}
+        }",
+        &expect![[r#"
+            Namespace _id_ [0-225] (Ident _id_ [49-57] "LevelOne"):
+                doc:
+                    Documentation for LevelOne
+                Item _id_ [72-215]:
+                    Attr _id_ [72-86] (Ident _id_ [73-84] "ExampleAttr"):
+                        Expr _id_ [84-86]: Unit
+                    Callable _id_ [99-215] (Function):
+                        name: Ident _id_ [194-203] "InnerItem"
+                        input: Pat _id_ [203-205]: Unit
+                        output: Type _id_ [208-212]: Path: Path _id_ [208-212] (Ident _id_ [208-212] "Unit")
+                        body: Block: Block _id_ [213-215]: <empty>"#]],
+    );
+}
+
+#[test]
+fn namespace_with_conflicting_names() {
+    check_vec(
+        parse_namespaces,
+        "namespace Conflicts {
+            function Item() : Unit {}
+            newtype Item = Int;
+        }",
+        &expect![[r#"
+            Namespace _id_ [0-101] (Ident _id_ [10-19] "Conflicts"):
+                Item _id_ [34-59]:
+                    Callable _id_ [34-59] (Function):
+                        name: Ident _id_ [43-47] "Item"
+                        input: Pat _id_ [47-49]: Unit
+                        output: Type _id_ [52-56]: Path: Path _id_ [52-56] (Ident _id_ [52-56] "Unit")
+                        body: Block: Block _id_ [57-59]: <empty>
+                Item _id_ [72-91]:
+                    New Type (Ident _id_ [80-84] "Item"): TyDef _id_ [87-90]: Field:
+                        Type _id_ [87-90]: Path: Path _id_ [87-90] (Ident _id_ [87-90] "Int")"#]],
+    );
+}
diff --git a/compiler/qsc_passes/src/loop_unification/tests.rs b/compiler/qsc_passes/src/loop_unification/tests.rs
index 92e2551f..79cf44cf 100644
--- a/compiler/qsc_passes/src/loop_unification/tests.rs
+++ b/compiler/qsc_passes/src/loop_unification/tests.rs
@@ -62,7 +62,7 @@ fn convert_for_array() {
                                         Expr 10 [75-78] [Type Int[]]: Var: Local 3
                                     Stmt 24 [0-0]: Local (Immutable):
                                         Pat 25 [75-78] [Type Int]: Bind: Ident 21 [75-78] "@len_id_21"
-                                        Expr 22 [75-78] [Type Int[]]: Call:
+                                        Expr 22 [75-78] [Type Int]: Call:
                                             Expr 20 [75-78] [Type (Int[] -> Int)]: Var:
                                                 res: Item 1 (Package 0)
                                                 generics:
@@ -125,7 +125,7 @@ fn convert_for_array_deconstruct() {
                                         Expr 13 [90-93] [Type (Int, Double)[]]: Var: Local 3
                                     Stmt 27 [0-0]: Local (Immutable):
                                         Pat 28 [90-93] [Type Int]: Bind: Ident 24 [90-93] "@len_id_24"
-                                        Expr 25 [90-93] [Type (Int, Double)[]]: Call:
+                                        Expr 25 [90-93] [Type Int]: Call:
                                             Expr 23 [90-93] [Type ((Int, Double)[] -> Int)]: Var:
                                                 res: Item 1 (Package 0)
                                                 generics:
@@ -196,7 +196,7 @@ fn convert_for_slice() {
                                                 Expr 16 [86-87] [Type Int]: Lit: Int(2)
                                     Stmt 30 [0-0]: Local (Immutable):
                                         Pat 31 [75-88] [Type Int]: Bind: Ident 27 [75-88] "@len_id_27"
-                                        Expr 28 [75-88] [Type Int[]]: Call:
+                                        Expr 28 [75-88] [Type Int]: Call:
                                             Expr 26 [75-88] [Type (Int[] -> Int)]: Var:
                                                 res: Item 1 (Package 0)
                                                 generics:
diff --git a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs
index 97a2b08c..4c6594a0 100644
--- a/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs
+++ b/compiler/qsc_passes/src/replace_qubit_allocation/tests.rs
@@ -87,7 +87,7 @@ fn test_qubit_array() {
                             Block 4 [45-97] [Type Unit]:
                                 Stmt 18 [55-72]: Local (Immutable):
                                     Pat 19 [55-72] [Type Qubit[]]: Bind: Ident 7 [55-72] "q"
-                                    Expr 16 [55-72] [Type Qubit]: Call:
+                                    Expr 16 [55-72] [Type Qubit[]]: Call:
                                         Expr 15 [55-72] [Type (Int => Qubit[])]: Var: Item 6 (Package 0)
                                         Expr 9 [69-70] [Type Int]: Lit: Int(3)
                                 Stmt 10 [81-91]: Local (Immutable):
@@ -183,7 +183,7 @@ fn test_multiple_qubits_tuple() {
                                         Expr 27 [69-76] [Type Unit]: Unit
                                 Stmt 33 [78-86]: Local (Immutable):
                                     Pat 34 [78-86] [Type Qubit[]]: Bind: Ident 22 [78-86] "@generated_ident_22"
-                                    Expr 31 [78-86] [Type Qubit]: Call:
+                                    Expr 31 [78-86] [Type Qubit[]]: Call:
                                         Expr 30 [78-86] [Type (Int => Qubit[])]: Var: Item 6 (Package 0)
                                         Expr 14 [84-85] [Type Int]: Lit: Int(3)
                                 Stmt 35 [55-88]: Local (Immutable):
@@ -700,7 +700,7 @@ fn test_array_expr() {
                             Block 4 [45-150] [Type Unit]:
                                 Stmt 39 [55-125]: Local (Immutable):
                                     Pat 40 [55-125] [Type Qubit[]]: Bind: Ident 7 [55-125] "a"
-                                    Expr 37 [55-125] [Type Qubit]: Call:
+                                    Expr 37 [55-125] [Type Qubit[]]: Call:
                                         Expr 36 [55-125] [Type (Int => Qubit[])]: Var: Item 6 (Package 0)
                                         Expr 9 [69-123] [Type Int]: Expr Block: Block 10 [69-123] [Type Int]:
                                             Stmt 25 [83-99]: Local (Immutable):
diff --git a/library/src/tests/arithmetic.rs b/library/src/tests/arithmetic.rs
index f25209b7..c93c3410 100644
--- a/library/src/tests/arithmetic.rs
+++ b/library/src/tests/arithmetic.rs
@@ -11,7 +11,6 @@ fn check_maj() {
     test_expression(
         {
             "{
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
                 use q = Qubit[3];
                 mutable r = [];
@@ -107,7 +106,6 @@ fn check_inc_by_le_general() {
     test_expression(
         {
             "{  // General cases for IncByLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
                 use x1 = Qubit[10];
                 use y1 = Qubit[10];
@@ -173,7 +171,6 @@ fn check_ripple_carry_ttk_inc_by_le_general() {
     test_expression(
         {
             "{  // General cases for RippleCarryTTKIncByLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use x1 = Qubit[10];
@@ -279,7 +276,6 @@ fn check_ripple_carry_cg_inc_by_le_general() {
     test_expression(
         {
             "{  // General cases for RippleCarryCGIncByLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use x1 = Qubit[10];
@@ -436,7 +432,6 @@ fn check_inc_by_le_using_add_le_general() {
     test_expression(
         {
             "{  // General cases for IncByLEUsingAddLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use x1 = Qubit[10];
@@ -463,7 +458,6 @@ fn check_inc_by_i_general() {
     test_expression(
         {
             "{ // General cases for IncByI
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use y0 = Qubit[1];
@@ -519,7 +513,6 @@ fn check_ripple_carry_cg_inc_by_i_general() {
     test_expression(
         {
             "{  // General cases for IncByIUsingIncByLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use y0 = Qubit[10];
@@ -556,7 +549,6 @@ fn check_inc_by_l_general() {
     test_expression(
         {
             "{ // General cases for IncByL
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use y0 = Qubit[1];
@@ -612,7 +604,6 @@ fn check_ripple_carry_cg_inc_by_l_general() {
     test_expression(
         {
             "{  // Branching cases for IncByLUsingIncByLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use y0 = Qubit[10];
@@ -691,7 +682,6 @@ fn check_add_le_general() {
     test_expression(
         {
             "{   // General cases for AddLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use x1 = Qubit[10];
@@ -761,7 +751,6 @@ fn check_ripple_carry_cg_add_le_general() {
     test_expression(
         {
             "{  // General cases for RippleCarryAddLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use x1 = Qubit[10];
@@ -829,7 +818,6 @@ fn check_lookahead_dkrs_add_le_general() {
     test_expression(
         {
             "{  // General cases for LookAheadDKRSAddLE
-                open Microsoft.Quantum.Measurement;
                 open Microsoft.Quantum.Unstable.Arithmetic;
 
                 use x1 = Qubit[10];
@@ -965,7 +953,6 @@ fn check_apply_if_less_l_non_x_action() {
     test_expression(
         "{
             open Microsoft.Quantum.Unstable.Arithmetic;
-            open Microsoft.Quantum.Measurement;
             use input = Qubit[10];
             use output1 = Qubit[10];
             use output2 = Qubit[10];
diff --git a/library/src/tests/canon.rs b/library/src/tests/canon.rs
index 4d0c158f..7f617cd4 100644
--- a/library/src/tests/canon.rs
+++ b/library/src/tests/canon.rs
@@ -177,7 +177,6 @@ fn check_apply_p() {
     test_expression(
         {
             "{
-            open Microsoft.Quantum.Measurement;
             use q = Qubit[3];
             ApplyP(PauliX, q[0]);
             H(q[1]); ApplyP(PauliY, q[1]);
@@ -194,7 +193,6 @@ fn check_apply_pauli() {
     test_expression(
         {
             "{
-            open Microsoft.Quantum.Measurement;
             use q = Qubit[3];
             H(q[1]);
             H(q[2]); S(q[2]);
@@ -211,7 +209,6 @@ fn check_apply_pauli_from_bit_string() {
     test_expression(
         {
             "{
-            open Microsoft.Quantum.Measurement;
             use q = Qubit[3];
             ApplyPauliFromBitString(PauliX, false, [true, false, true], q);
             return MResetEachZ(q);
@@ -226,7 +223,6 @@ fn check_apply_pauli_from_int() {
     test_expression(
         {
             "{
-            open Microsoft.Quantum.Measurement;
             use q = Qubit[3];
             ApplyPauliFromInt(PauliX, false, 5, q);
             return MResetEachZ(q);
@@ -241,7 +237,6 @@ fn check_apply_controlled_on_int() {
     test_expression(
         {
             "{
-            open Microsoft.Quantum.Measurement;
             use c = Qubit[3];
             use t1 = Qubit();
             use t2 = Qubit();
@@ -264,7 +259,6 @@ fn check_apply_controlled_on_bitstring() {
     test_expression(
         {
             "{
-            open Microsoft.Quantum.Measurement;
             use c = Qubit[4];
             use t1 = Qubit();
             use t2 = Qubit();
@@ -323,7 +317,6 @@ fn check_swap_reverse_register() {
     test_expression(
         {
             "{
-                open Microsoft.Quantum.Measurement;
                 use q = Qubit[10];
                 ApplyXorInPlace(328, q);
                 SwapReverseRegister(q);
@@ -361,7 +354,6 @@ fn check_apply_xor_in_place_l() {
     test_expression(
         {
             "{
-            open Microsoft.Quantum.Measurement;
             use q = Qubit[100];
             ApplyXorInPlaceL(953L <<< 50, q);
             let result = MeasureInteger(q[50...]);
diff --git a/library/src/tests/measurement.rs b/library/src/tests/measurement.rs
index 8b36185f..ef937de0 100644
--- a/library/src/tests/measurement.rs
+++ b/library/src/tests/measurement.rs
@@ -140,7 +140,6 @@ fn check_measure_integer() {
     test_expression(
         {
             "{
-                open Microsoft.Quantum.Measurement;
                 use q = Qubit[16];
                 ApplyXorInPlace(45967, q);
                 let result = MeasureInteger(q);
diff --git a/library/src/tests/resources/add_le.qs b/library/src/tests/resources/add_le.qs
index b4cb2c85..948e57b5 100644
--- a/library/src/tests/resources/add_le.qs
+++ b/library/src/tests/resources/add_le.qs
@@ -2,7 +2,6 @@ namespace Test {
     open Microsoft.Quantum.Unstable.Arithmetic;
     open Microsoft.Quantum.Convert;
     open Microsoft.Quantum.Diagnostics;
-    open Microsoft.Quantum.Measurement;
 
     internal operation TestAddLE3(
         name : String,
diff --git a/library/src/tests/resources/compare.qs b/library/src/tests/resources/compare.qs
index 1f00db69..6b85f448 100644
--- a/library/src/tests/resources/compare.qs
+++ b/library/src/tests/resources/compare.qs
@@ -1,5 +1,4 @@
 namespace Test {
-    open Microsoft.Quantum.Measurement;
     open Microsoft.Quantum.Unstable.Arithmetic;
     open Microsoft.Quantum.Convert;
     open Microsoft.Quantum.Diagnostics;
diff --git a/library/src/tests/resources/inc_by_le.qs b/library/src/tests/resources/inc_by_le.qs
index d0797a54..d8c54161 100644
--- a/library/src/tests/resources/inc_by_le.qs
+++ b/library/src/tests/resources/inc_by_le.qs
@@ -2,7 +2,6 @@ namespace Test {
     open Microsoft.Quantum.Unstable.Arithmetic;
     open Microsoft.Quantum.Convert;
     open Microsoft.Quantum.Diagnostics;
-    open Microsoft.Quantum.Measurement;
 
     internal operation TestIncByLE2(
         name : String,
diff --git a/library/src/tests/resources/select.qs b/library/src/tests/resources/select.qs
index 4848006a..08087539 100644
--- a/library/src/tests/resources/select.qs
+++ b/library/src/tests/resources/select.qs
@@ -2,7 +2,6 @@ namespace Test {
     open Microsoft.Quantum.Arrays;
     open Microsoft.Quantum.Convert;
     open Microsoft.Quantum.Diagnostics;
-    open Microsoft.Quantum.Measurement;
     open Microsoft.Quantum.Random;
     open Microsoft.Quantum.Unstable.TableLookup;
 
diff --git a/library/src/tests/resources/state_preparation.qs b/library/src/tests/resources/state_preparation.qs
index 7e762ac9..3c80bb69 100644
--- a/library/src/tests/resources/state_preparation.qs
+++ b/library/src/tests/resources/state_preparation.qs
@@ -1,5 +1,4 @@
 namespace Test {
-    open Microsoft.Quantum.Measurement;
     open Microsoft.Quantum.Convert;
     open Microsoft.Quantum.Math;
     open Microsoft.Quantum.Diagnostics;
diff --git a/resource_estimator/src/counts/tests.rs b/resource_estimator/src/counts/tests.rs
index eb2fb624..08cf18b2 100644
--- a/resource_estimator/src/counts/tests.rs
+++ b/resource_estimator/src/counts/tests.rs
@@ -39,8 +39,6 @@ fn gates_are_counted() {
     verify_logical_counts(
         indoc! {"
             namespace Test {
-                open Microsoft.Quantum.Measurement;
-
                 operation Rotate(qs: Qubit[]) : Unit {
                     for q in qs {
                         Rx(1.0, q);
@@ -65,12 +63,13 @@ fn gates_are_counted() {
         "},
         None,
         &expect![["
-            LogicalResources {
+            LogicalResourceCounts {
                 num_qubits: 10,
                 t_count: 2,
                 rotation_count: 30,
                 rotation_depth: 5,
                 ccz_count: 2,
+                ccix_count: 0,
                 measurement_count: 10,
             }
         "]],
@@ -111,12 +110,13 @@ fn estimate_caching_works() {
         "#},
         None,
         &expect![["
-            LogicalResources {
+            LogicalResourceCounts {
                 num_qubits: 10,
                 t_count: 1,
                 rotation_count: 300,
                 rotation_depth: 30,
                 ccz_count: 0,
+                ccix_count: 0,
                 measurement_count: 0,
             }
         "]],
@@ -157,12 +157,13 @@ fn estimate_repeat_works() {
         "#},
         None,
         &expect![[r#"
-            LogicalResources {
+            LogicalResourceCounts {
                 num_qubits: 10,
                 t_count: 1,
                 rotation_count: 300,
                 rotation_depth: 30,
                 ccz_count: 0,
+                ccix_count: 0,
                 measurement_count: 0,
             }
         "#]],
@@ -195,12 +196,13 @@ fn account_for_estimates_works() {
         "},
         None,
         &expect![["
-            LogicalResources {
+            LogicalResourceCounts {
                 num_qubits: 11,
                 t_count: 2,
                 rotation_count: 3,
                 rotation_depth: 1,
                 ccz_count: 5,
+                ccix_count: 0,
                 measurement_count: 6,
             }
         "]],
diff --git a/resource_estimator/src/system/tests.rs b/resource_estimator/src/system/tests.rs
index 324f326a..623c0d4c 100644
--- a/resource_estimator/src/system/tests.rs
+++ b/resource_estimator/src/system/tests.rs
@@ -7,7 +7,6 @@ use crate::estimates::{
     ErrorBudget, ErrorCorrection, Factory, FactoryBuilder, Overhead, PhysicalResourceEstimation,
     PhysicalResourceEstimationResult,
 };
-use crate::LogicalResources;
 
 use super::estimate_physical_resources;
 
@@ -24,17 +23,18 @@ use std::rc::Rc;
 
 #[test]
 fn estimate_single() {
-    let logical_resources = LogicalResources {
+    let logical_resources = LogicalResourceCounts {
         num_qubits: 100,
         t_count: 0,
         rotation_count: 112_110,
         rotation_depth: 2001,
         ccz_count: 0,
+        ccix_count: 0,
         measurement_count: 0,
     };
 
     let params: &str = "[{}]";
-    let result = estimate_physical_resources(&logical_resources, params);
+    let result = estimate_physical_resources(logical_resources, params);
 
     let json_value: Vec<Value> =
         serde_json::from_str(&result.expect("result is err")).expect("Failed to parse JSON");
@@ -49,12 +49,13 @@ fn estimate_single() {
 
 #[test]
 fn estimate_frontier() {
-    let logical_resources = LogicalResources {
+    let logical_resources = LogicalResourceCounts {
         num_qubits: 100,
         t_count: 0,
         rotation_count: 112_110,
         rotation_depth: 2001,
         ccz_count: 0,
+        ccix_count: 0,
         measurement_count: 0,
     };
 
@@ -62,7 +63,7 @@ fn estimate_frontier() {
         "estimateType": "frontier"
     }]"#;
 
-    let result = estimate_physical_resources(&logical_resources, params);
+    let result = estimate_physical_resources(logical_resources, params);
 
     let json_value: Vec<Value> =
         serde_json::from_str(&result.expect("result is err")).expect("Failed to parse JSON");
@@ -78,12 +79,13 @@ fn estimate_frontier() {
 #[test]
 fn physical_estimates_crash() {
     let result = estimate_physical_resources(
-        &LogicalResources {
+        LogicalResourceCounts {
             num_qubits: 9,
             t_count: 160,
             rotation_count: 0,
             rotation_depth: 0,
             ccz_count: 8,
+            ccix_count: 0,
             measurement_count: 5,
         },
         r#"[{"qubitParams": {"name": "qubit_maj_ns_e6"},
@@ -147,7 +149,7 @@ pub fn test_no_tstates() {
         ftp,
         qubit,
         TFactoryBuilder::default(),
-        layout_overhead,
+        Rc::new(layout_overhead),
         partitioning,
     );
 
@@ -166,7 +168,7 @@ pub fn single_tstate() -> Result<()> {
         ftp,
         qubit,
         TFactoryBuilder::default(),
-        layout_overhead,
+        Rc::new(layout_overhead),
         partitioning,
     );
 
@@ -190,7 +192,7 @@ pub fn perfect_tstate() -> Result<()> {
         ftp,
         qubit,
         TFactoryBuilder::default(),
-        layout_overhead,
+        Rc::new(layout_overhead),
         partitioning,
     );
 
@@ -209,7 +211,7 @@ fn hubbard_overhead_and_partitioning() -> Result<(LogicalResourceCounts, ErrorBu
     Ok((logical_counts, partitioning))
 }
 
-fn validate_result_invariants<L: Overhead + Clone>(
+fn validate_result_invariants<L: Overhead>(
     result: &PhysicalResourceEstimationResult<Protocol, TFactory, L>,
 ) {
     assert_eq!(
@@ -249,7 +251,7 @@ pub fn test_hubbard_e2e() -> Result<()> {
         ftp,
         qubit.clone(),
         TFactoryBuilder::default(),
-        layout_overhead,
+        Rc::new(layout_overhead),
         partitioning,
     );
 
@@ -341,7 +343,7 @@ pub fn test_hubbard_e2e_measurement_based() -> Result<()> {
         ftp,
         qubit.clone(),
         TFactoryBuilder::default(),
-        layout_overhead,
+        Rc::new(layout_overhead),
         partitioning,
     );
 
@@ -430,7 +432,7 @@ pub fn test_hubbard_e2e_increasing_max_duration() -> Result<()> {
         ftp,
         qubit,
         TFactoryBuilder::default(),
-        layout_overhead,
+        Rc::new(layout_overhead),
         partitioning,
     );
 
@@ -458,7 +460,7 @@ pub fn test_hubbard_e2e_increasing_max_num_qubits() -> Result<()> {
         ftp,
         qubit,
         TFactoryBuilder::default(),
-        layout_overhead,
+        Rc::new(layout_overhead),
         partitioning,
     );
 
@@ -497,7 +499,13 @@ fn prepare_chemistry_estimation_with_expected_majorana(
     let partitioning = ErrorBudgetSpecification::Total(1e-3)
         .partitioning(&counts)
         .expect("partitioning should succeed");
-    PhysicalResourceEstimation::new(ftp, qubit, TFactoryBuilder::default(), counts, partitioning)
+    PhysicalResourceEstimation::new(
+        ftp,
+        qubit,
+        TFactoryBuilder::default(),
+        Rc::new(counts),
+        partitioning,
+    )
 }
 
 #[test]
@@ -672,7 +680,13 @@ fn prepare_factorization_estimation_with_optimistic_majorana(
     let partitioning = ErrorBudgetSpecification::Total(1e-3)
         .partitioning(&counts)
         .expect("partitioning should succeed");
-    PhysicalResourceEstimation::new(ftp, qubit, TFactoryBuilder::default(), counts, partitioning)
+    PhysicalResourceEstimation::new(
+        ftp,
+        qubit,
+        TFactoryBuilder::default(),
+        Rc::new(counts),
+        partitioning,
+    )
 }
 
 #[test]
@@ -801,7 +815,13 @@ fn prepare_ising20x20_estimation_with_pessimistic_gate_based(
     let partitioning = ErrorBudgetSpecification::Total(1e-3)
         .partitioning(&counts)
         .expect("cannot setup error budget partitioning");
-    PhysicalResourceEstimation::new(ftp, qubit, TFactoryBuilder::default(), counts, partitioning)
+    PhysicalResourceEstimation::new(
+        ftp,
+        qubit,
+        TFactoryBuilder::default(),
+        Rc::new(counts),
+        partitioning,
+    )
 }
 
 #[test]
@@ -897,7 +917,13 @@ fn prepare_bit_flip_code_resources_and_majorana_n6_qubit(
     let partitioning = ErrorBudgetSpecification::Total(1e-3)
         .partitioning(&counts)
         .expect("cannot setup error budget partitioning");
-    PhysicalResourceEstimation::new(ftp, qubit, TFactoryBuilder::default(), counts, partitioning)
+    PhysicalResourceEstimation::new(
+        ftp,
+        qubit,
+        TFactoryBuilder::default(),
+        Rc::new(counts),
+        partitioning,
+    )
 }
 
 #[test]
@@ -977,17 +1003,18 @@ fn code_distance_tests() {
 
 #[test]
 fn test_report() {
-    let logical_resources = LogicalResources {
+    let logical_resources = LogicalResourceCounts {
         num_qubits: 100,
         t_count: 0,
         rotation_count: 112_110,
         rotation_depth: 2001,
         ccz_count: 0,
+        ccix_count: 0,
         measurement_count: 0,
     };
 
     let params: &str = "[{}]";
-    let result = estimate_physical_resources(&logical_resources, params);
+    let result = estimate_physical_resources(logical_resources, params);
 
     let json_value: Vec<Value> =
         serde_json::from_str(&result.expect("result is err")).expect("Failed to parse JSON");
diff --git a/vscode/test/runTests.mjs b/vscode/test/runTests.mjs
index 210c3852..51f044ca 100644
--- a/vscode/test/runTests.mjs
+++ b/vscode/test/runTests.mjs
@@ -39,7 +39,7 @@ try {
   // Debugger tests
   await runSuite(
     join(thisDir, "out", "debugger", "index"),
-    join(thisDir, "..", "..", "samples"),
+    join(thisDir, "suites", "debugger", "test-workspace"),
   );
 } catch (err) {
   console.error("Failed to run tests", err);
diff --git a/vscode/test/suites/debugger/debugger.test.ts b/vscode/test/suites/debugger/debugger.test.ts
new file mode 100644
index 00000000..2a832845
--- /dev/null
+++ b/vscode/test/suites/debugger/debugger.test.ts
@@ -0,0 +1,567 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import * as vscode from "vscode";
+import { assert } from "chai";
+import { activateExtension, waitForCondition } from "../extensionUtils";
+import { DebugProtocol } from "@vscode/debugprotocol";
+
+/**
+ * Set to true to log Debug Adapter Protocol messages to the console.
+ * This is useful for debugging test failures.
+ */
+const logDebugAdapterActivity = false;
+
+suite("Q# Debugger Tests", function suite() {
+  const workspaceFolder =
+    vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0];
+  assert(workspaceFolder, "Expecting an open folder");
+  const fooUri = vscode.Uri.joinPath(workspaceFolder.uri, "src", "foo.qs");
+  const barUri = vscode.Uri.joinPath(workspaceFolder.uri, "src", "bar.qs");
+
+  let tracker: Tracker | undefined;
+  let disposable;
+
+  this.beforeAll(async () => {
+    await activateExtension();
+  });
+
+  this.beforeEach(async () => {
+    tracker = new Tracker();
+    disposable = vscode.debug.registerDebugAdapterTrackerFactory("qsharp", {
+      createDebugAdapterTracker(): vscode.ProviderResult<vscode.DebugAdapterTracker> {
+        return tracker;
+      },
+    });
+  });
+
+  this.afterEach(async () => {
+    disposable.dispose();
+    tracker = undefined;
+    await terminateSession();
+    vscode.commands.executeCommand("workbench.action.closeAllEditors");
+    vscode.debug.removeBreakpoints(vscode.debug.breakpoints);
+  });
+
+  test("Launch with debugEditorContents command", async () => {
+    await vscode.window.showTextDocument(fooUri);
+
+    // launch debugger
+    await vscode.commands.executeCommand("qsharp-vscode.debugEditorContents");
+
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 9,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 15,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+  });
+
+  test("Launch with launch.json configuration - workspaceFolder substitution", async () => {
+    // The DebugConfiguration object is what would go in launch.json,
+    // pass it in directly here
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${workspaceFolder}src/foo.qs",
+    });
+
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 9,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 15,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+  });
+
+  test("Launch with launch.json configuration - file substitution", async () => {
+    await vscode.window.showTextDocument(fooUri);
+
+    // ${file} will expand to the filesystem path of the currently opened file
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${file}",
+    });
+
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 9,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 15,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+  });
+
+  test("Run until completion", async () => {
+    // launch debugger
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${workspaceFolder}src/foo.qs",
+      stopOnEntry: true,
+    });
+
+    // should hit the breakpoint we set above
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 9,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 15,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+
+    vscode.commands.executeCommand("workbench.action.debug.continue");
+
+    // wait until there's no longer an active debug session
+    await waitForCondition(
+      () => !vscode.debug.activeDebugSession,
+      vscode.debug.onDidChangeActiveDebugSession,
+      2000,
+      "timed out waiting for the debugger to be terminated",
+    );
+  });
+
+  test("Set breakpoint in main file", async () => {
+    // Set a breakpoint on line 6 of foo.qs (5 when 0-indexed)
+    await vscode.debug.addBreakpoints([
+      new vscode.SourceBreakpoint(
+        new vscode.Location(fooUri, new vscode.Position(5, 0)),
+      ),
+    ]);
+
+    // launch debugger
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${workspaceFolder}src/foo.qs",
+      stopOnEntry: false,
+    });
+
+    // should hit the breakpoint we set above
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 6,
+        column: 9,
+        name: "Foo ",
+        endLine: 6,
+        endColumn: 15,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+  });
+
+  test("Set breakpoint in other file", async () => {
+    // Set a breakpoint on line 3 of bar.qs (2 when 0-indexed)
+    await vscode.debug.addBreakpoints([
+      new vscode.SourceBreakpoint(
+        new vscode.Location(barUri, new vscode.Position(2, 0)),
+      ),
+    ]);
+
+    // launch debugger
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${workspaceFolder}src/foo.qs",
+      stopOnEntry: false,
+    });
+
+    // should hit the breakpoint we set above
+    await waitUntilPaused([
+      {
+        id: 1,
+        source: {
+          name: "bar.qs",
+          path: "vscode-test-web://mount/src/bar.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 3,
+        column: 9,
+        name: "Bar ",
+        endLine: 3,
+        endColumn: 26,
+      },
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 12,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 14,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+
+    // text editor should now be open on bar.qs
+    await waitForTextEditorOn(barUri);
+  });
+
+  test("Step into other file", async () => {
+    // launch debugger
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${workspaceFolder}src/foo.qs",
+      stopOnEntry: true,
+    });
+
+    // should break on entry (per debug config above)
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 9,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 15,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+
+    // step into call (will be a call into bar.qs)
+    await vscode.commands.executeCommand("workbench.action.debug.stepInto");
+
+    await waitUntilPaused([
+      {
+        id: 1,
+        source: {
+          name: "bar.qs",
+          path: "vscode-test-web://mount/src/bar.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 3,
+        column: 9,
+        name: "Bar ",
+        endLine: 3,
+        endColumn: 26,
+      },
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 5,
+        column: 12,
+        name: "Foo ",
+        endLine: 5,
+        endColumn: 14,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+
+    // text editor should now be open on bar.qs
+    await waitForTextEditorOn(barUri);
+  });
+
+  test("Step into standard lib", async () => {
+    // Set a breakpoint on line 8 of foo.qs (7 when 0-indexed)
+    // This will be a call into stdlib
+    await vscode.debug.addBreakpoints([
+      new vscode.SourceBreakpoint(
+        new vscode.Location(fooUri, new vscode.Position(7, 0)),
+      ),
+    ]);
+
+    // launch debugger
+    await vscode.debug.startDebugging(workspaceFolder, {
+      name: "Launch foo.qs",
+      type: "qsharp",
+      request: "launch",
+      program: "${workspaceFolder}src/foo.qs",
+      stopOnEntry: false,
+    });
+
+    // should hit the breakpoint we set above
+    await waitUntilPaused([
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 8,
+        column: 9,
+        name: "Foo ",
+        endLine: 8,
+        endColumn: 14,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+
+    // step into call (will be a call into intrinsic.qs)
+    await vscode.commands.executeCommand("workbench.action.debug.stepInto");
+
+    await waitUntilPaused([
+      {
+        id: 1,
+        source: {
+          name: "intrinsic.qs",
+          path: "qsharp-library-source:intrinsic.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 165,
+        column: 13,
+        name: "H ",
+        endLine: 165,
+        endColumn: 44,
+      },
+      {
+        id: 0,
+        source: {
+          name: "foo.qs",
+          path: "vscode-test-web://mount/src/foo.qs",
+          sourceReference: 0,
+          adapterData: "qsharp-adapter-data",
+        },
+        line: 8,
+        column: 11,
+        name: "Foo ",
+        endLine: 8,
+        endColumn: 12,
+      },
+      { id: 0, line: 0, column: 0, name: "entry", source: undefined },
+    ]);
+
+    // text editor should now be open on intrinsic.qs
+    await waitForTextEditorOn(
+      vscode.Uri.parse("qsharp-library-source:intrinsic.qs"),
+    );
+  });
+
+  /**
+   * Wait until the debugger has entered the paused state.
+   *
+   * @param expectedStackTrace assert that the stack trace matches this value
+   */
+  function waitUntilPaused(expectedStackTrace: DebugProtocol.StackFrame[]) {
+    return tracker!.waitUntilPaused(expectedStackTrace);
+  }
+});
+
+/**
+ * Terminate the active debug session and wait for it to end.
+ */
+async function terminateSession() {
+  vscode.commands.executeCommand("workbench.action.debug.stop");
+  await waitForCondition(
+    () => !vscode.debug.activeDebugSession,
+    vscode.debug.onDidChangeActiveDebugSession,
+    2000,
+    "timed out waiting for the debugger to be terminated",
+  );
+}
+
+/**
+ * Wait for the active text editor to be open to the given document URI.
+ */
+async function waitForTextEditorOn(uri: vscode.Uri) {
+  await waitForCondition(
+    () =>
+      vscode.window.activeTextEditor?.document.uri.toString() ===
+      uri.toString(),
+    vscode.window.onDidChangeActiveTextEditor,
+    500,
+    `timed out waiting for the text editor to open to ${uri}.\nactive text editor is ${vscode.window.activeTextEditor?.document.uri}`,
+  );
+}
+
+/**
+ * This class will listen to the communication between VS Code and the debug adapter (our code).
+ *
+ * VS Code does not provide an easy way to hook into debug session state for our tests. But there
+ * is a predictable pattern of Debug Adapter Protocol messages we can listen to,
+ * to figure out when the debugger has entered the paused state (as a result of a breakpoint, step, breaking on entry, etc.).
+ *
+ * 1. a "stopped" event coming from the debug adapter.
+ * 2. a response to a "stackTrace" request.
+ * 3. a response to a "variables" request.
+ *
+ * The "variables" request is the last thing VS Code sends to the debug adapter, and thus we can
+ * use that event to reasonably determine we're ready to move on to the next test command.
+ *
+ * This pattern is based on the debug tests in the VS Code repo:
+ * https://github.com/microsoft/vscode/blob/13e49a698cf441f82984b357f09ed095779751b8/extensions/vscode-api-tests/src/singlefolder-tests/debug.test.ts#L52
+ */
+class Tracker implements vscode.DebugAdapterTracker {
+  private stoppedCount = 0;
+  private stackTrace;
+  private variables;
+  private onVariablesResponse: ((e: any) => void) | undefined;
+
+  /**
+   * Wait until the debugger has entered the paused state by waiting for the
+   * appropriate sequence of messages in the debug adapter.
+   *
+   * @param expectedStackTrace assert that the stack trace matches this value
+   */
+  async waitUntilPaused(expectedStackTrace: DebugProtocol.StackFrame[]) {
+    const start = performance.now();
+
+    await waitForCondition(
+      () => this.stoppedCount === 1 && this.stackTrace && this.variables,
+      (listener: (e: any) => void) => {
+        this.onVariablesResponse = listener;
+        return {
+          dispose() {
+            this.onVariablesResponse = undefined;
+          },
+        };
+      },
+      1800,
+      "timed out waiting for the debugger to stop",
+    );
+
+    assert.deepEqual(
+      this.stackTrace,
+      expectedStackTrace,
+      // print copy-pastable stack trace
+      `actual stack trace:\n${JSON.stringify(this.stackTrace)}\n`,
+    );
+
+    this.stoppedCount = 0;
+    this.stackTrace = undefined;
+    this.variables = undefined;
+
+    const stepMs = performance.now() - start;
+    if (stepMs > 700) {
+      // Not much we can control here if the debugger is taking too long,
+      // but log a warning so that we see it in the test log if we get
+      // close to hitting test timeouts.
+      // The default mocha test timeout is 2000ms.
+      console.log(`qsharp-tests: debugger took ${stepMs}ms to stop`);
+    }
+    if (logDebugAdapterActivity) {
+      console.log(`qsharp-tests: debugger paused`);
+    }
+  }
+
+  onWillReceiveMessage(message: any): void {
+    if (logDebugAdapterActivity) {
+      console.log(`qsharp-tests: ->  ${JSON.stringify(message)}`);
+    }
+  }
+
+  onDidSendMessage(message: any): void {
+    if (logDebugAdapterActivity) {
+      if (message.type === "response") {
+        console.log(`qsharp-tests:  <- ${JSON.stringify(message)}`);
+      } else {
+        // message.type === "event"
+        console.log(`qsharp-tests: <-* ${JSON.stringify(message)}`);
+      }
+    }
+
+    if (message.type === "event") {
+      if (message.event === "stopped") {
+        this.stoppedCount++;
+      }
+    } else if (message.type === "response") {
+      if (message.command === "variables") {
+        this.variables = message.body.variables;
+        this.onVariablesResponse?.(undefined);
+      } else if (message.command === "stackTrace") {
+        this.stackTrace = message.body.stackFrames;
+      }
+    }
+  }
+
+  onWillStartSession(): void {
+    if (logDebugAdapterActivity) {
+      console.log(`qsharp-tests: starting debug session`);
+    }
+  }
+
+  onWillStopSession(): void {
+    if (logDebugAdapterActivity) {
+      console.log(`qsharp-tests: stopping debug session`);
+    }
+  }
+
+  onError(error: Error): void {
+    console.log(`qsharp-tests: [error] error in debug session: ${error}`);
+  }
+
+  onExit(code: number, signal: string): void {
+    if (logDebugAdapterActivity) {
+      console.log(
+        `qsharp-tests: debug session exited with code ${code} and signal ${signal}`,
+      );
+    }
+  }
+}
diff --git a/vscode/test/suites/debugger/extension.test.ts b/vscode/test/suites/debugger/extension.test.ts
deleted file mode 100644
index e3b28a73..00000000
--- a/vscode/test/suites/debugger/extension.test.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-import * as vscode from "vscode";
-import { assert } from "chai";
-
-suite("Q# Debugger Tests", () => {
-  const workspaceFolder =
-    vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0];
-  assert(workspaceFolder, "Expecting an open folder");
-
-  test("Placeholder - success", async () => {
-    assert.equal(1, 1);
-  });
-});
diff --git a/vscode/test/suites/debugger/index.ts b/vscode/test/suites/debugger/index.ts
index 82a441be..e6ca2e26 100644
--- a/vscode/test/suites/debugger/index.ts
+++ b/vscode/test/suites/debugger/index.ts
@@ -8,6 +8,6 @@ export function run(): Promise<void> {
     // We can't use any wildcards or dynamically discovered
     // paths here since ESBuild needs these modules to be
     // real paths on disk at bundling time.
-    require("./extension.test");
+    require("./debugger.test");
   });
 }
diff --git a/vscode/test/suites/debugger/test-workspace/qsharp.json b/vscode/test/suites/debugger/test-workspace/qsharp.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/vscode/test/suites/debugger/test-workspace/qsharp.json
@@ -0,0 +1,1 @@
+{}
diff --git a/vscode/test/suites/debugger/test-workspace/src/bar.qs b/vscode/test/suites/debugger/test-workspace/src/bar.qs
new file mode 100644
index 00000000..d1e0eae0
--- /dev/null
+++ b/vscode/test/suites/debugger/test-workspace/src/bar.qs
@@ -0,0 +1,5 @@
+namespace Bar {
+    operation Bar() : Unit {
+        Message("hello");
+    }
+}
diff --git a/vscode/test/suites/debugger/test-workspace/src/foo.qs b/vscode/test/suites/debugger/test-workspace/src/foo.qs
new file mode 100644
index 00000000..b354cf1c
--- /dev/null
+++ b/vscode/test/suites/debugger/test-workspace/src/foo.qs
@@ -0,0 +1,11 @@
+namespace Foo {
+    open Bar;
+    @EntryPoint()
+    operation Foo() : Unit {
+        Bar();
+        Bar();
+        use q = Qubit();
+        H(q);
+        Reset(q);
+    }
+}