Repository: microsoft/qsharp
Pull request #1037 (2024-01-18 19:08:59)

Allow Range Fields on UDTs
Updated the type-checker to recognize UDT field references that refer to fields named `Start`, `Step`, and `End` without raising a type error.

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | 873.3±6.08µs | 877.5±8.72µs | +0.48% |
| Array update evaluation | 942.1±10.18µs | 949.1±7.04µs | +0.74% |
| Deutsch-Jozsa evaluation | 31.2±0.21ms | 31.3±0.23ms | +0.32% |
| Large file parity evaluation | 36.4±0.37ms | 36.3±0.36ms | -0.27% |
| Large input file | 38.4±0.69ms | 37.9±0.85ms | -1.30% |
| Standard library | 20.9±0.39ms | 20.7±0.44ms | -0.96% |
| Teleport evaluation | 104.6±2.09µs | 106.4±5.88µs | +1.72% |

  </details>
  
idavis: @ScottCarda-MS Does this address #857 
ScottCarda-MS: > @ScottCarda-MS Does this address #857

Yes I'll respond to and close that issue when this is merged in.

Patch:
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 6db27755..7f88f3f6 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -19,6 +19,7 @@
 				"esbenp.prettier-vscode",
 				"ms-python.python",
 				"ms-python.black-formatter",
+				"ms-toolsai.jupyter",
 				"rust-lang.rust-analyzer",
 				"vadimcn.vscode-lldb",
 				"swellaby.workspace-config-plus"
diff --git a/compiler/qsc/benches/eval.rs b/compiler/qsc/benches/eval.rs
index 9423da38..4b08c767 100644
--- a/compiler/qsc/benches/eval.rs
+++ b/compiler/qsc/benches/eval.rs
@@ -3,7 +3,7 @@
 
 use criterion::{criterion_group, criterion_main, Criterion};
 use indoc::indoc;
-use qsc::{interpret::stateful, PackageType};
+use qsc::{interpret::Interpreter, PackageType};
 use qsc_eval::output::GenericReceiver;
 use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
 
@@ -14,7 +14,7 @@ const LARGE: &str = include_str!("./large.qs");
 pub fn teleport(c: &mut Criterion) {
     c.bench_function("Teleport evaluation", |b| {
         let sources = SourceMap::new([("Teleportation.qs".into(), TELEPORT.into())], None);
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -32,7 +32,7 @@ pub fn teleport(c: &mut Criterion) {
 pub fn deutsch_jozsa(c: &mut Criterion) {
     c.bench_function("Deutsch-Jozsa evaluation", |b| {
         let sources = SourceMap::new([("DeutschJozsa.qs".into(), DEUTSCHJOZSA.into())], None);
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -50,7 +50,7 @@ pub fn deutsch_jozsa(c: &mut Criterion) {
 pub fn large_file(c: &mut Criterion) {
     c.bench_function("Large file parity evaluation", |b| {
         let sources = SourceMap::new([("large.qs".into(), LARGE.into())], None);
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -80,7 +80,7 @@ pub fn array_append(c: &mut Criterion) {
                 .into(),
             ),
         );
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
@@ -110,7 +110,7 @@ pub fn array_update(c: &mut Criterion) {
                 .into(),
             ),
         );
-        let mut evaluator = stateful::Interpreter::new(
+        let mut evaluator = Interpreter::new(
             true,
             sources,
             PackageType::Exe,
diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs
index 323043fb..5e8264de 100644
--- a/compiler/qsc/src/bin/qsi.rs
+++ b/compiler/qsc/src/bin/qsi.rs
@@ -8,7 +8,7 @@ use clap::{crate_version, Parser};
 use miette::{Context, IntoDiagnostic, Report, Result};
 use num_bigint::BigUint;
 use num_complex::Complex64;
-use qsc::interpret::stateful::{self, InterpretResult, Interpreter};
+use qsc::interpret::{self, InterpretResult, Interpreter};
 use qsc_eval::{
     output::{self, Receiver},
     val::Value,
@@ -194,7 +194,7 @@ fn print_interpret_result(result: InterpretResult) {
     }
 }
 
-fn print_exec_result(result: Result<Value, Vec<stateful::Error>>) -> ExitCode {
+fn print_exec_result(result: Result<Value, Vec<interpret::Error>>) -> ExitCode {
     match result {
         Ok(value) => {
             println!("{value}");
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index d3894f69..5546eb59 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -2,11 +2,621 @@
 // Licensed under the MIT License.
 
 mod debug;
-pub mod stateful;
+
+#[cfg(test)]
+mod tests;
+
+#[cfg(test)]
+mod stepping_tests;
 
 pub use qsc_eval::{
     debug::Frame,
     output::{self, GenericReceiver},
     val::Value,
-    Error, StepAction, StepResult,
+    StepAction, StepResult,
+};
+
+use crate::{
+    error::{self, WithStack},
+    incremental::Compiler,
+};
+use debug::format_call_stack;
+use miette::Diagnostic;
+use num_bigint::BigUint;
+use num_complex::Complex;
+use qsc_codegen::qir_base::BaseProfSim;
+use qsc_data_structures::span::Span;
+use qsc_eval::{
+    backend::{Backend, SparseSim},
+    debug::{map_fir_package_to_hir, map_hir_package_to_fir},
+    eval_expr, eval_stmt,
+    output::Receiver,
+    val::{self},
+    Env, State, VariableInfo,
 };
+use qsc_fir::fir::{self, Global, PackageStoreLookup};
+use qsc_fir::{
+    fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId},
+    visit::{self, Visitor},
+};
+use qsc_frontend::{
+    compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, Source, SourceMap},
+    error::WithSource,
+};
+use qsc_passes::PackageType;
+use rustc_hash::FxHashSet;
+use thiserror::Error;
+
+impl Error {
+    #[must_use]
+    pub fn stack_trace(&self) -> &Option<String> {
+        match &self {
+            Error::Eval(err) => err.stack_trace(),
+            _ => &None,
+        }
+    }
+}
+
+#[derive(Clone, Debug, Diagnostic, Error)]
+pub enum Error {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Compile(#[from] crate::compile::Error),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Pass(#[from] WithSource<qsc_passes::Error>),
+    #[error("runtime error")]
+    #[diagnostic(transparent)]
+    Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
+    #[error("entry point not found")]
+    #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
+    NoEntryPoint,
+    #[error("unsupported runtime capabilities for code generation")]
+    #[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
+    UnsupportedRuntimeCapabilities,
+}
+
+/// A Q# interpreter.
+pub struct Interpreter {
+    /// The incremental Q# compiler.
+    compiler: Compiler,
+    /// The runtime capabilities used for compilation.
+    capabilities: RuntimeCapabilityFlags,
+    /// The number of lines that have so far been compiled.
+    /// This field is used to generate a unique label
+    /// for each line evaluated with `eval_fragments`.
+    lines: u32,
+    // The FIR store
+    fir_store: fir::PackageStore,
+    /// FIR lowerer
+    lowerer: qsc_eval::lower::Lowerer,
+    /// The ID of the current package.
+    /// This ID is valid both for the FIR store and the `PackageStore`.
+    package: PackageId,
+    /// The ID of the source package. The source package
+    /// is made up of the initial sources passed in when creating the interpreter.
+    /// This ID is valid both for the FIR store and the `PackageStore`.
+    source_package: PackageId,
+    /// The default simulator backend.
+    sim: SparseSim,
+    /// The evaluator environment.
+    env: Env,
+    /// The current state of the evaluator.
+    state: State,
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub type InterpretResult = Result<Value, Vec<Error>>;
+
+impl Interpreter {
+    /// Creates a new incremental compiler, compiling the passed in sources.
+    /// # Errors
+    /// If compiling the sources fails, compiler errors are returned.
+    pub fn new(
+        std: bool,
+        sources: SourceMap,
+        package_type: PackageType,
+        capabilities: RuntimeCapabilityFlags,
+    ) -> Result<Self, Vec<Error>> {
+        let mut lowerer = qsc_eval::lower::Lowerer::new();
+        let mut fir_store = fir::PackageStore::new();
+
+        let compiler =
+            Compiler::new(std, sources, package_type, capabilities).map_err(into_errors)?;
+
+        for (id, unit) in compiler.package_store() {
+            fir_store.insert(
+                map_hir_package_to_fir(id),
+                lowerer.lower_package(&unit.package),
+            );
+        }
+
+        let source_package_id = compiler.source_package_id();
+        let package_id = compiler.package_id();
+
+        Ok(Self {
+            compiler,
+            lines: 0,
+            capabilities,
+            fir_store,
+            lowerer,
+            env: Env::with_empty_scope(),
+            sim: SparseSim::new(),
+            state: State::new(map_hir_package_to_fir(source_package_id)),
+            package: map_hir_package_to_fir(package_id),
+            source_package: map_hir_package_to_fir(source_package_id),
+        })
+    }
+
+    /// Loads the entry expression to the top of the evaluation stack.
+    /// This is needed for debugging so that when begging to debug with
+    /// a step action the system is already in the correct state.
+    /// # Errors
+    /// Returns a vector of errors if loading the entry point fails.
+    pub fn set_entry(&mut self) -> Result<(), Vec<Error>> {
+        let expr = self.get_entry_expr()?;
+        qsc_eval::eval_push_expr(&mut self.state, expr);
+        Ok(())
+    }
+
+    /// Resumes execution with specified `StepAction`.
+    /// # Errors
+    /// Returns a vector of errors if evaluating the entry point fails.
+    pub fn eval_step(
+        &mut self,
+        receiver: &mut impl Receiver,
+        breakpoints: &[StmtId],
+        step: StepAction,
+    ) -> Result<StepResult, Vec<Error>> {
+        self.state
+            .eval(
+                &self.fir_store,
+                &mut self.env,
+                &mut self.sim,
+                receiver,
+                breakpoints,
+                step,
+            )
+            .map_err(|(error, call_stack)| {
+                eval_error(
+                    self.compiler.package_store(),
+                    &self.fir_store,
+                    call_stack,
+                    error,
+                )
+            })
+    }
+
+    /// Executes the entry expression until the end of execution.
+    /// # Errors
+    /// Returns a vector of errors if evaluating the entry point fails.
+    pub fn eval_entry(&mut self, receiver: &mut impl Receiver) -> Result<Value, Vec<Error>> {
+        let expr = self.get_entry_expr()?;
+        eval_expr(
+            &mut State::new(self.source_package),
+            expr,
+            &self.fir_store,
+            &mut Env::with_empty_scope(),
+            &mut self.sim,
+            receiver,
+        )
+        .map_err(|(error, call_stack)| {
+            eval_error(
+                self.compiler.package_store(),
+                &self.fir_store,
+                call_stack,
+                error,
+            )
+        })
+    }
+
+    /// Executes the entry expression until the end of execution, using the given simulator backend
+    /// and a new instance of the environment.
+    pub fn eval_entry_with_sim(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+    ) -> Result<Value, Vec<Error>> {
+        let expr = self.get_entry_expr()?;
+        eval_expr(
+            &mut State::new(self.source_package),
+            expr,
+            &self.fir_store,
+            &mut Env::with_empty_scope(),
+            sim,
+            receiver,
+        )
+        .map_err(|(error, call_stack)| {
+            eval_error(
+                self.compiler.package_store(),
+                &self.fir_store,
+                call_stack,
+                error,
+            )
+        })
+    }
+
+    fn get_entry_expr(&self) -> Result<ExprId, Vec<Error>> {
+        let unit = self
+            .fir_store
+            .get(self.source_package)
+            .expect("store should have package");
+        if let Some(entry) = unit.entry {
+            return Ok(entry);
+        };
+        Err(vec![Error::NoEntryPoint])
+    }
+
+    /// # Errors
+    /// If the parsing of the fragments fails, an error is returned.
+    /// If the compilation of the fragments fails, an error is returned.
+    /// If there is a runtime error when interpreting the fragments, an error is returned.
+    pub fn eval_fragments(
+        &mut self,
+        receiver: &mut impl Receiver,
+        fragments: &str,
+    ) -> InterpretResult {
+        let label = self.next_line_label();
+
+        let increment = self
+            .compiler
+            .compile_fragments_fail_fast(&label, fragments)
+            .map_err(into_errors)?;
+
+        let stmts = self.lower(&increment);
+
+        // Updating the compiler state with the new AST/HIR nodes
+        // is not necessary for the interpreter to function, as all
+        // the state required for evaluation already exists in the
+        // FIR store. It could potentially save some memory
+        // *not* to do hold on to the AST/HIR, but it is done
+        // here to keep the package stores consistent.
+        self.compiler.update(increment);
+
+        let mut result = Value::unit();
+
+        for stmt_id in stmts {
+            match eval_stmt(
+                stmt_id,
+                &self.fir_store,
+                &mut self.env,
+                &mut self.sim,
+                self.package,
+                receiver,
+            ) {
+                Ok(value) => result = value,
+                Err((error, call_stack)) => {
+                    return Err(eval_error(
+                        self.compiler.package_store(),
+                        &self.fir_store,
+                        call_stack,
+                        error,
+                    ))
+                }
+            }
+        }
+
+        Ok(result)
+    }
+
+    /// Runs the given entry expression on a new instance of the environment and simulator,
+    /// but using the current compilation.
+    pub fn run(
+        &mut self,
+        receiver: &mut impl Receiver,
+        expr: &str,
+    ) -> Result<InterpretResult, Vec<Error>> {
+        self.run_with_sim(&mut SparseSim::new(), receiver, expr)
+    }
+
+    /// Gets the current quantum state of the simulator.
+    pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
+        self.sim.capture_quantum_state()
+    }
+
+    /// Performs QIR codegen using the given entry expression on a new instance of the environment
+    /// and simulator but using the current compilation.
+    pub fn qirgen(&mut self, expr: &str) -> Result<String, Vec<Error>> {
+        if self.capabilities != RuntimeCapabilityFlags::empty() {
+            return Err(vec![Error::UnsupportedRuntimeCapabilities]);
+        }
+
+        let mut sim = BaseProfSim::new();
+        let mut stdout = std::io::sink();
+        let mut out = GenericReceiver::new(&mut stdout);
+
+        let val = self.run_with_sim(&mut sim, &mut out, expr)??;
+
+        Ok(sim.finish(&val))
+    }
+
+    /// Runs the given entry expression on the given simulator with a new instance of the environment
+    /// but using the current compilation.
+    pub fn run_with_sim(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+        expr: &str,
+    ) -> Result<InterpretResult, Vec<Error>> {
+        let stmt_id = self.compile_expr_to_stmt(expr)?;
+
+        Ok(self.run_internal(sim, receiver, stmt_id))
+    }
+
+    fn run_internal(
+        &mut self,
+        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+        receiver: &mut impl Receiver,
+        stmt_id: StmtId,
+    ) -> InterpretResult {
+        match eval_stmt(
+            stmt_id,
+            &self.fir_store,
+            &mut Env::with_empty_scope(),
+            sim,
+            self.package,
+            receiver,
+        ) {
+            Ok(value) => Ok(value),
+            Err((error, call_stack)) => Err(eval_error(
+                self.compiler.package_store(),
+                &self.fir_store,
+                call_stack,
+                error,
+            )),
+        }
+    }
+
+    fn compile_expr_to_stmt(&mut self, expr: &str) -> Result<StmtId, Vec<Error>> {
+        let increment = self.compiler.compile_expr(expr).map_err(into_errors)?;
+
+        let stmts = self.lower(&increment);
+
+        // Updating the compiler state with the new AST/HIR nodes
+        // is not necessary for the interpreter to function, as all
+        // the state required for evaluation already exists in the
+        // FIR store. It could potentially save some memory
+        // *not* to do hold on to the AST/HIR, but it is done
+        // 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");
+
+        Ok(*stmt_id)
+    }
+
+    fn lower(&mut self, unit_addition: &qsc_frontend::incremental::Increment) -> Vec<StmtId> {
+        let fir_package = self
+            .fir_store
+            .get_mut(self.package)
+            .expect("package should be in store");
+        self.lowerer
+            .lower_and_update_package(fir_package, &unit_addition.hir)
+    }
+
+    fn source_package(&self) -> &CompileUnit {
+        self.compiler
+            .package_store()
+            .get(map_fir_package_to_hir(self.source_package))
+            .expect("Could not load package")
+    }
+
+    fn next_line_label(&mut self) -> String {
+        let label = format!("line_{}", self.lines);
+        self.lines += 1;
+        label
+    }
+
+    #[must_use]
+    pub fn get_stack_frames(&self) -> Vec<StackFrame> {
+        let frames = self.state.get_stack_frames();
+        let stack_frames = frames
+            .iter()
+            .map(|frame| {
+                let callable = self
+                    .fir_store
+                    .get_global(frame.id)
+                    .expect("frame should exist");
+                let functor = format!("{}", frame.functor);
+                let name = match callable {
+                    Global::Callable(decl) => decl.name.name.to_string(),
+                    Global::Udt => "udt".into(),
+                };
+
+                let hir_package = self
+                    .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,
+                    lo: frame.span.lo - source.offset,
+                    hi: frame.span.hi - source.offset,
+                }
+            })
+            .collect();
+        stack_frames
+    }
+
+    pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
+        self.sim.capture_quantum_state()
+    }
+
+    #[must_use]
+    pub fn get_breakpoints(&self, path: &str) -> Vec<BreakpointSpan> {
+        let unit = self.source_package();
+
+        if let Some(source) = unit.sources.find_by_name(path) {
+            let package = self
+                .fir_store
+                .get(self.source_package)
+                .expect("package should have been lowered");
+            let mut collector = BreakpointCollector::new(&unit.sources, source.offset, package);
+            collector.visit_package(package);
+            let mut spans: Vec<_> = collector
+                .statements
+                .iter()
+                .map(|bps| BreakpointSpan {
+                    id: bps.id,
+                    lo: bps.lo,
+                    hi: bps.hi,
+                })
+                .collect();
+            spans.sort_by_key(|s| s.lo);
+            spans
+        } else {
+            Vec::new()
+        }
+    }
+
+    #[must_use]
+    pub fn get_locals(&self) -> Vec<VariableInfo> {
+        self.env
+            .get_variables_in_top_frame()
+            .into_iter()
+            .filter(|v| !v.name.starts_with('@'))
+            .collect()
+    }
+}
+
+/// Represents a stack frame for debugging.
+pub struct StackFrame {
+    /// The name of the callable.
+    pub name: String,
+    /// The functor of the callable.
+    pub functor: String,
+    /// The path of the source file.
+    pub path: String,
+    /// The start of the call site span in utf8 characters.
+    pub lo: u32,
+    /// The end of the call site span in utf8 characters.
+    pub hi: u32,
+}
+
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub struct BreakpointSpan {
+    /// The id of the statement representing the breakpoint location.
+    pub id: u32,
+    /// The start of the call site span in utf8 characters.
+    pub lo: u32,
+    /// The end of the call site span in utf8 characters.
+    pub hi: u32,
+}
+
+struct BreakpointCollector<'a> {
+    statements: FxHashSet<BreakpointSpan>,
+    sources: &'a SourceMap,
+    offset: u32,
+    package: &'a Package,
+}
+
+impl<'a> BreakpointCollector<'a> {
+    fn new(sources: &'a SourceMap, offset: u32, package: &'a Package) -> Self {
+        Self {
+            statements: FxHashSet::default(),
+            sources,
+            offset,
+            package,
+        }
+    }
+
+    fn get_source(&self, offset: u32) -> &Source {
+        self.sources
+            .find_by_offset(offset)
+            .expect("Couldn't find source file")
+    }
+
+    fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
+        let source: &Source = self.get_source(stmt.span.lo);
+        if source.offset == self.offset {
+            let span = stmt.span - source.offset;
+            if span != Span::default() {
+                let bps = BreakpointSpan {
+                    id: stmt.id.into(),
+                    lo: span.lo,
+                    hi: span.hi,
+                };
+                self.statements.insert(bps);
+            }
+        }
+    }
+}
+
+impl<'a> Visitor<'a> for BreakpointCollector<'a> {
+    fn visit_stmt(&mut self, stmt: StmtId) {
+        let stmt_res = self.get_stmt(stmt);
+        match stmt_res.kind {
+            qsc_fir::fir::StmtKind::Expr(expr) | qsc_fir::fir::StmtKind::Local(_, _, expr) => {
+                self.add_stmt(stmt_res);
+                visit::walk_expr(self, expr);
+            }
+            qsc_fir::fir::StmtKind::Qubit(_, _, _, block) => match block {
+                Some(block) => visit::walk_block(self, block),
+                None => self.add_stmt(stmt_res),
+            },
+            qsc_fir::fir::StmtKind::Item(_) | qsc_fir::fir::StmtKind::Semi(_) => {
+                self.add_stmt(stmt_res);
+            }
+        };
+    }
+
+    fn get_block(&mut self, id: BlockId) -> &'a Block {
+        self.package
+            .blocks
+            .get(id)
+            .expect("couldn't find block in FIR")
+    }
+
+    fn get_expr(&mut self, id: ExprId) -> &'a Expr {
+        self.package
+            .exprs
+            .get(id)
+            .expect("couldn't find expr in FIR")
+    }
+
+    fn get_pat(&mut self, id: PatId) -> &'a Pat {
+        self.package.pats.get(id).expect("couldn't find pat in FIR")
+    }
+
+    fn get_stmt(&mut self, id: StmtId) -> &'a Stmt {
+        self.package
+            .stmts
+            .get(id)
+            .expect("couldn't find stmt in FIR")
+    }
+}
+
+fn eval_error(
+    package_store: &PackageStore,
+    fir_store: &fir::PackageStore,
+    call_stack: Vec<Frame>,
+    error: qsc_eval::Error,
+) -> Vec<Error> {
+    let stack_trace = if call_stack.is_empty() {
+        None
+    } else {
+        Some(format_call_stack(
+            package_store,
+            fir_store,
+            call_stack,
+            &error,
+        ))
+    };
+
+    vec![error::from_eval(error, package_store, stack_trace).into()]
+}
+
+fn into_errors(errors: Vec<crate::compile::Error>) -> Vec<Error> {
+    errors
+        .into_iter()
+        .map(|error| Error::Compile(error.into_with_source()))
+        .collect::<Vec<_>>()
+}
diff --git a/compiler/qsc/src/interpret/stateful.rs b/compiler/qsc/src/interpret/stateful.rs
deleted file mode 100644
index 0e31dc8a..00000000
--- a/compiler/qsc/src/interpret/stateful.rs
+++ /dev/null
@@ -1,612 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-#[cfg(test)]
-mod tests;
-
-#[cfg(test)]
-mod stepping_tests;
-
-use super::debug::format_call_stack;
-use crate::{
-    error::{self, WithStack},
-    incremental::Compiler,
-};
-use miette::Diagnostic;
-use num_bigint::BigUint;
-use num_complex::Complex;
-use qsc_codegen::qir_base::BaseProfSim;
-use qsc_data_structures::span::Span;
-use qsc_eval::{
-    backend::{Backend, SparseSim},
-    debug::{map_fir_package_to_hir, map_hir_package_to_fir, Frame},
-    eval_expr, eval_stmt,
-    output::{GenericReceiver, Receiver},
-    val::{self, Value},
-    Env, State, StepAction, StepResult, VariableInfo,
-};
-use qsc_fir::fir::{self, Global, PackageStoreLookup};
-use qsc_fir::{
-    fir::{Block, BlockId, Expr, ExprId, Package, PackageId, Pat, PatId, Stmt, StmtId},
-    visit::{self, Visitor},
-};
-use qsc_frontend::{
-    compile::{CompileUnit, PackageStore, RuntimeCapabilityFlags, Source, SourceMap},
-    error::WithSource,
-};
-use qsc_passes::PackageType;
-use rustc_hash::FxHashSet;
-use thiserror::Error;
-
-impl Error {
-    #[must_use]
-    pub fn stack_trace(&self) -> &Option<String> {
-        match &self {
-            Error::Eval(err) => err.stack_trace(),
-            _ => &None,
-        }
-    }
-}
-
-#[derive(Clone, Debug, Diagnostic, Error)]
-pub enum Error {
-    #[error(transparent)]
-    #[diagnostic(transparent)]
-    Compile(#[from] crate::compile::Error),
-    #[error(transparent)]
-    #[diagnostic(transparent)]
-    Pass(#[from] WithSource<qsc_passes::Error>),
-    #[error("runtime error")]
-    #[diagnostic(transparent)]
-    Eval(#[from] WithStack<WithSource<qsc_eval::Error>>),
-    #[error("entry point not found")]
-    #[diagnostic(code("Qsc.Interpret.NoEntryPoint"))]
-    NoEntryPoint,
-    #[error("unsupported runtime capabilities for code generation")]
-    #[diagnostic(code("Qsc.Interpret.UnsupportedRuntimeCapabilities"))]
-    UnsupportedRuntimeCapabilities,
-}
-
-/// A Q# interpreter.
-pub struct Interpreter {
-    /// The incremental Q# compiler.
-    compiler: Compiler,
-    /// The runtime capabilities used for compilation.
-    capabilities: RuntimeCapabilityFlags,
-    /// The number of lines that have so far been compiled.
-    /// This field is used to generate a unique label
-    /// for each line evaluated with `eval_fragments`.
-    lines: u32,
-    // The FIR store
-    fir_store: fir::PackageStore,
-    /// FIR lowerer
-    lowerer: qsc_eval::lower::Lowerer,
-    /// The ID of the current package.
-    /// This ID is valid both for the FIR store and the `PackageStore`.
-    package: PackageId,
-    /// The ID of the source package. The source package
-    /// is made up of the initial sources passed in when creating the interpreter.
-    /// This ID is valid both for the FIR store and the `PackageStore`.
-    source_package: PackageId,
-    /// The default simulator backend.
-    sim: SparseSim,
-    /// The evaluator environment.
-    env: Env,
-    /// The current state of the evaluator.
-    state: State,
-}
-
-pub type InterpretResult = Result<Value, Vec<Error>>;
-
-impl Interpreter {
-    /// Creates a new incremental compiler, compiling the passed in sources.
-    /// # Errors
-    /// If compiling the sources fails, compiler errors are returned.
-    pub fn new(
-        std: bool,
-        sources: SourceMap,
-        package_type: PackageType,
-        capabilities: RuntimeCapabilityFlags,
-    ) -> Result<Self, Vec<Error>> {
-        let mut lowerer = qsc_eval::lower::Lowerer::new();
-        let mut fir_store = fir::PackageStore::new();
-
-        let compiler =
-            Compiler::new(std, sources, package_type, capabilities).map_err(into_errors)?;
-
-        for (id, unit) in compiler.package_store() {
-            fir_store.insert(
-                map_hir_package_to_fir(id),
-                lowerer.lower_package(&unit.package),
-            );
-        }
-
-        let source_package_id = compiler.source_package_id();
-        let package_id = compiler.package_id();
-
-        Ok(Self {
-            compiler,
-            lines: 0,
-            capabilities,
-            fir_store,
-            lowerer,
-            env: Env::with_empty_scope(),
-            sim: SparseSim::new(),
-            state: State::new(map_hir_package_to_fir(source_package_id)),
-            package: map_hir_package_to_fir(package_id),
-            source_package: map_hir_package_to_fir(source_package_id),
-        })
-    }
-
-    /// Loads the entry expression to the top of the evaluation stack.
-    /// This is needed for debugging so that when begging to debug with
-    /// a step action the system is already in the correct state.
-    /// # Errors
-    /// Returns a vector of errors if loading the entry point fails.
-    pub fn set_entry(&mut self) -> Result<(), Vec<Error>> {
-        let expr = self.get_entry_expr()?;
-        qsc_eval::eval_push_expr(&mut self.state, expr);
-        Ok(())
-    }
-
-    /// Resumes execution with specified `StepAction`.
-    /// # Errors
-    /// Returns a vector of errors if evaluating the entry point fails.
-    pub fn eval_step(
-        &mut self,
-        receiver: &mut impl Receiver,
-        breakpoints: &[StmtId],
-        step: StepAction,
-    ) -> Result<StepResult, Vec<Error>> {
-        self.state
-            .eval(
-                &self.fir_store,
-                &mut self.env,
-                &mut self.sim,
-                receiver,
-                breakpoints,
-                step,
-            )
-            .map_err(|(error, call_stack)| {
-                eval_error(
-                    self.compiler.package_store(),
-                    &self.fir_store,
-                    call_stack,
-                    error,
-                )
-            })
-    }
-
-    /// Executes the entry expression until the end of execution.
-    /// # Errors
-    /// Returns a vector of errors if evaluating the entry point fails.
-    pub fn eval_entry(&mut self, receiver: &mut impl Receiver) -> Result<Value, Vec<Error>> {
-        let expr = self.get_entry_expr()?;
-        eval_expr(
-            &mut State::new(self.source_package),
-            expr,
-            &self.fir_store,
-            &mut Env::with_empty_scope(),
-            &mut self.sim,
-            receiver,
-        )
-        .map_err(|(error, call_stack)| {
-            eval_error(
-                self.compiler.package_store(),
-                &self.fir_store,
-                call_stack,
-                error,
-            )
-        })
-    }
-
-    /// Executes the entry expression until the end of execution, using the given simulator backend
-    /// and a new instance of the environment.
-    pub fn eval_entry_with_sim(
-        &mut self,
-        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
-        receiver: &mut impl Receiver,
-    ) -> Result<Value, Vec<Error>> {
-        let expr = self.get_entry_expr()?;
-        eval_expr(
-            &mut State::new(self.source_package),
-            expr,
-            &self.fir_store,
-            &mut Env::with_empty_scope(),
-            sim,
-            receiver,
-        )
-        .map_err(|(error, call_stack)| {
-            eval_error(
-                self.compiler.package_store(),
-                &self.fir_store,
-                call_stack,
-                error,
-            )
-        })
-    }
-
-    fn get_entry_expr(&self) -> Result<ExprId, Vec<Error>> {
-        let unit = self
-            .fir_store
-            .get(self.source_package)
-            .expect("store should have package");
-        if let Some(entry) = unit.entry {
-            return Ok(entry);
-        };
-        Err(vec![Error::NoEntryPoint])
-    }
-
-    /// # Errors
-    /// If the parsing of the fragments fails, an error is returned.
-    /// If the compilation of the fragments fails, an error is returned.
-    /// If there is a runtime error when interpreting the fragments, an error is returned.
-    pub fn eval_fragments(
-        &mut self,
-        receiver: &mut impl Receiver,
-        fragments: &str,
-    ) -> InterpretResult {
-        let label = self.next_line_label();
-
-        let increment = self
-            .compiler
-            .compile_fragments_fail_fast(&label, fragments)
-            .map_err(into_errors)?;
-
-        let stmts = self.lower(&increment);
-
-        // Updating the compiler state with the new AST/HIR nodes
-        // is not necessary for the interpreter to function, as all
-        // the state required for evaluation already exists in the
-        // FIR store. It could potentially save some memory
-        // *not* to do hold on to the AST/HIR, but it is done
-        // here to keep the package stores consistent.
-        self.compiler.update(increment);
-
-        let mut result = Value::unit();
-
-        for stmt_id in stmts {
-            match eval_stmt(
-                stmt_id,
-                &self.fir_store,
-                &mut self.env,
-                &mut self.sim,
-                self.package,
-                receiver,
-            ) {
-                Ok(value) => result = value,
-                Err((error, call_stack)) => {
-                    return Err(eval_error(
-                        self.compiler.package_store(),
-                        &self.fir_store,
-                        call_stack,
-                        error,
-                    ))
-                }
-            }
-        }
-
-        Ok(result)
-    }
-
-    /// Runs the given entry expression on a new instance of the environment and simulator,
-    /// but using the current compilation.
-    pub fn run(
-        &mut self,
-        receiver: &mut impl Receiver,
-        expr: &str,
-    ) -> Result<InterpretResult, Vec<Error>> {
-        self.run_with_sim(&mut SparseSim::new(), receiver, expr)
-    }
-
-    /// Gets the current quantum state of the simulator.
-    pub fn get_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
-        self.sim.capture_quantum_state()
-    }
-
-    /// Performs QIR codegen using the given entry expression on a new instance of the environment
-    /// and simulator but using the current compilation.
-    pub fn qirgen(&mut self, expr: &str) -> Result<String, Vec<Error>> {
-        if self.capabilities != RuntimeCapabilityFlags::empty() {
-            return Err(vec![Error::UnsupportedRuntimeCapabilities]);
-        }
-
-        let mut sim = BaseProfSim::new();
-        let mut stdout = std::io::sink();
-        let mut out = GenericReceiver::new(&mut stdout);
-
-        let val = self.run_with_sim(&mut sim, &mut out, expr)??;
-
-        Ok(sim.finish(&val))
-    }
-
-    /// Runs the given entry expression on the given simulator with a new instance of the environment
-    /// but using the current compilation.
-    pub fn run_with_sim(
-        &mut self,
-        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
-        receiver: &mut impl Receiver,
-        expr: &str,
-    ) -> Result<InterpretResult, Vec<Error>> {
-        let stmt_id = self.compile_expr_to_stmt(expr)?;
-
-        Ok(self.run_internal(sim, receiver, stmt_id))
-    }
-
-    fn run_internal(
-        &mut self,
-        sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
-        receiver: &mut impl Receiver,
-        stmt_id: StmtId,
-    ) -> InterpretResult {
-        match eval_stmt(
-            stmt_id,
-            &self.fir_store,
-            &mut Env::with_empty_scope(),
-            sim,
-            self.package,
-            receiver,
-        ) {
-            Ok(value) => Ok(value),
-            Err((error, call_stack)) => Err(eval_error(
-                self.compiler.package_store(),
-                &self.fir_store,
-                call_stack,
-                error,
-            )),
-        }
-    }
-
-    fn compile_expr_to_stmt(&mut self, expr: &str) -> Result<StmtId, Vec<Error>> {
-        let increment = self.compiler.compile_expr(expr).map_err(into_errors)?;
-
-        let stmts = self.lower(&increment);
-
-        // Updating the compiler state with the new AST/HIR nodes
-        // is not necessary for the interpreter to function, as all
-        // the state required for evaluation already exists in the
-        // FIR store. It could potentially save some memory
-        // *not* to do hold on to the AST/HIR, but it is done
-        // 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");
-
-        Ok(*stmt_id)
-    }
-
-    fn lower(&mut self, unit_addition: &qsc_frontend::incremental::Increment) -> Vec<StmtId> {
-        let fir_package = self
-            .fir_store
-            .get_mut(self.package)
-            .expect("package should be in store");
-        self.lowerer
-            .lower_and_update_package(fir_package, &unit_addition.hir)
-    }
-
-    fn source_package(&self) -> &CompileUnit {
-        self.compiler
-            .package_store()
-            .get(map_fir_package_to_hir(self.source_package))
-            .expect("Could not load package")
-    }
-
-    fn next_line_label(&mut self) -> String {
-        let label = format!("line_{}", self.lines);
-        self.lines += 1;
-        label
-    }
-
-    #[must_use]
-    pub fn get_stack_frames(&self) -> Vec<StackFrame> {
-        let frames = self.state.get_stack_frames();
-        let stack_frames = frames
-            .iter()
-            .map(|frame| {
-                let callable = self
-                    .fir_store
-                    .get_global(frame.id)
-                    .expect("frame should exist");
-                let functor = format!("{}", frame.functor);
-                let name = match callable {
-                    Global::Callable(decl) => decl.name.name.to_string(),
-                    Global::Udt => "udt".into(),
-                };
-
-                let hir_package = self
-                    .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,
-                    lo: frame.span.lo - source.offset,
-                    hi: frame.span.hi - source.offset,
-                }
-            })
-            .collect();
-        stack_frames
-    }
-
-    pub fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
-        self.sim.capture_quantum_state()
-    }
-
-    #[must_use]
-    pub fn get_breakpoints(&self, path: &str) -> Vec<BreakpointSpan> {
-        let unit = self.source_package();
-
-        if let Some(source) = unit.sources.find_by_name(path) {
-            let package = self
-                .fir_store
-                .get(self.source_package)
-                .expect("package should have been lowered");
-            let mut collector = BreakpointCollector::new(&unit.sources, source.offset, package);
-            collector.visit_package(package);
-            let mut spans: Vec<_> = collector
-                .statements
-                .iter()
-                .map(|bps| BreakpointSpan {
-                    id: bps.id,
-                    lo: bps.lo,
-                    hi: bps.hi,
-                })
-                .collect();
-            spans.sort_by_key(|s| s.lo);
-            spans
-        } else {
-            Vec::new()
-        }
-    }
-
-    #[must_use]
-    pub fn get_locals(&self) -> Vec<VariableInfo> {
-        self.env
-            .get_variables_in_top_frame()
-            .into_iter()
-            .filter(|v| !v.name.starts_with('@'))
-            .collect()
-    }
-}
-
-/// Represents a stack frame for debugging.
-pub struct StackFrame {
-    /// The name of the callable.
-    pub name: String,
-    /// The functor of the callable.
-    pub functor: String,
-    /// The path of the source file.
-    pub path: String,
-    /// The start of the call site span in utf8 characters.
-    pub lo: u32,
-    /// The end of the call site span in utf8 characters.
-    pub hi: u32,
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
-pub struct BreakpointSpan {
-    /// The id of the statement representing the breakpoint location.
-    pub id: u32,
-    /// The start of the call site span in utf8 characters.
-    pub lo: u32,
-    /// The end of the call site span in utf8 characters.
-    pub hi: u32,
-}
-
-struct BreakpointCollector<'a> {
-    statements: FxHashSet<BreakpointSpan>,
-    sources: &'a SourceMap,
-    offset: u32,
-    package: &'a Package,
-}
-
-impl<'a> BreakpointCollector<'a> {
-    fn new(sources: &'a SourceMap, offset: u32, package: &'a Package) -> Self {
-        Self {
-            statements: FxHashSet::default(),
-            sources,
-            offset,
-            package,
-        }
-    }
-
-    fn get_source(&self, offset: u32) -> &Source {
-        self.sources
-            .find_by_offset(offset)
-            .expect("Couldn't find source file")
-    }
-
-    fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
-        let source: &Source = self.get_source(stmt.span.lo);
-        if source.offset == self.offset {
-            let span = stmt.span - source.offset;
-            if span != Span::default() {
-                let bps = BreakpointSpan {
-                    id: stmt.id.into(),
-                    lo: span.lo,
-                    hi: span.hi,
-                };
-                self.statements.insert(bps);
-            }
-        }
-    }
-}
-
-impl<'a> Visitor<'a> for BreakpointCollector<'a> {
-    fn visit_stmt(&mut self, stmt: StmtId) {
-        let stmt_res = self.get_stmt(stmt);
-        match stmt_res.kind {
-            qsc_fir::fir::StmtKind::Expr(expr) | qsc_fir::fir::StmtKind::Local(_, _, expr) => {
-                self.add_stmt(stmt_res);
-                visit::walk_expr(self, expr);
-            }
-            qsc_fir::fir::StmtKind::Qubit(_, _, _, block) => match block {
-                Some(block) => visit::walk_block(self, block),
-                None => self.add_stmt(stmt_res),
-            },
-            qsc_fir::fir::StmtKind::Item(_) | qsc_fir::fir::StmtKind::Semi(_) => {
-                self.add_stmt(stmt_res);
-            }
-        };
-    }
-
-    fn get_block(&mut self, id: BlockId) -> &'a Block {
-        self.package
-            .blocks
-            .get(id)
-            .expect("couldn't find block in FIR")
-    }
-
-    fn get_expr(&mut self, id: ExprId) -> &'a Expr {
-        self.package
-            .exprs
-            .get(id)
-            .expect("couldn't find expr in FIR")
-    }
-
-    fn get_pat(&mut self, id: PatId) -> &'a Pat {
-        self.package.pats.get(id).expect("couldn't find pat in FIR")
-    }
-
-    fn get_stmt(&mut self, id: StmtId) -> &'a Stmt {
-        self.package
-            .stmts
-            .get(id)
-            .expect("couldn't find stmt in FIR")
-    }
-}
-
-fn eval_error(
-    package_store: &PackageStore,
-    fir_store: &fir::PackageStore,
-    call_stack: Vec<Frame>,
-    error: qsc_eval::Error,
-) -> Vec<Error> {
-    let stack_trace = if call_stack.is_empty() {
-        None
-    } else {
-        Some(format_call_stack(
-            package_store,
-            fir_store,
-            call_stack,
-            &error,
-        ))
-    };
-
-    vec![error::from_eval(error, package_store, stack_trace).into()]
-}
-
-fn into_errors(errors: Vec<crate::compile::Error>) -> Vec<Error> {
-    errors
-        .into_iter()
-        .map(|error| Error::Compile(error.into_with_source()))
-        .collect::<Vec<_>>()
-}
diff --git a/compiler/qsc_frontend/src/typeck/infer.rs b/compiler/qsc_frontend/src/typeck/infer.rs
index cbb0df53..f66f4cea 100644
--- a/compiler/qsc_frontend/src/typeck/infer.rs
+++ b/compiler/qsc_frontend/src/typeck/infer.rs
@@ -897,7 +897,7 @@ fn check_has_field(
             }],
             Vec::new(),
         ),
-        (Err(()), Ty::Udt(_, Res::Item(id))) => {
+        (_, Ty::Udt(_, Res::Item(id))) => {
             match udts.get(id).and_then(|udt| udt.field_ty_by_name(&name)) {
                 Some(ty) => (
                     vec![Constraint::Eq {
diff --git a/compiler/qsc_project/src/js.rs b/compiler/qsc_project/src/js.rs
index 5b87f164..7ad65523 100644
--- a/compiler/qsc_project/src/js.rs
+++ b/compiler/qsc_project/src/js.rs
@@ -1,6 +1,8 @@
-use std::{convert::Infallible, path::PathBuf, sync::Arc};
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
 
 use crate::{DirEntry, EntryType};
+use std::{convert::Infallible, path::PathBuf, sync::Arc};
 
 #[derive(Debug)]
 pub struct JSFileEntry {
diff --git a/jupyterlab/src/index.ts b/jupyterlab/src/index.ts
index 854c1e9a..de2de4b0 100644
--- a/jupyterlab/src/index.ts
+++ b/jupyterlab/src/index.ts
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 import {
   JupyterFrontEnd,
   JupyterFrontEndPlugin,
diff --git a/katas/src/lib.rs b/katas/src/lib.rs
index b095c1bc..cb742e58 100644
--- a/katas/src/lib.rs
+++ b/katas/src/lib.rs
@@ -8,11 +8,7 @@
 mod tests;
 
 use qsc::{
-    interpret::{
-        output::Receiver,
-        stateful::{self, Interpreter},
-        Value,
-    },
+    interpret::{output::Receiver, Error, Interpreter, Value},
     target::Profile,
     PackageType, SourceContents, SourceMap, SourceName,
 };
@@ -31,7 +27,7 @@ pub const EXERCISE_ENTRY: &str = "Kata.Verification.CheckSolution()";
 pub fn check_solution(
     exercise_sources: Vec<(SourceName, SourceContents)>,
     receiver: &mut impl Receiver,
-) -> Result<bool, Vec<stateful::Error>> {
+) -> Result<bool, Vec<Error>> {
     let source_map = SourceMap::new(exercise_sources, Some(EXERCISE_ENTRY.into()));
     let mut interpreter: Interpreter = Interpreter::new(
         true,
diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs
index 1e0d4c73..4ac91b3e 100644
--- a/pip/src/interpreter.rs
+++ b/pip/src/interpreter.rs
@@ -19,8 +19,9 @@ use pyo3::{
 use qsc::{
     fir,
     interpret::{
+        self,
         output::{Error, Receiver},
-        stateful, Value,
+        Value,
     },
     project::{FileSystem, Manifest, ManifestDescriptor},
     target::Profile,
@@ -63,7 +64,7 @@ pub(crate) enum TargetProfile {
 
 #[pyclass(unsendable)]
 pub(crate) struct Interpreter {
-    pub(crate) interpreter: stateful::Interpreter,
+    pub(crate) interpreter: interpret::Interpreter,
 }
 
 pub(crate) struct PyManifestDescriptor(ManifestDescriptor);
@@ -125,7 +126,7 @@ impl Interpreter {
             SourceMap::default()
         };
 
-        match stateful::Interpreter::new(true, sources, PackageType::Lib, target.into()) {
+        match interpret::Interpreter::new(true, sources, PackageType::Lib, target.into()) {
             Ok(interpreter) => Ok(Self { interpreter }),
             Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
         }
@@ -229,7 +230,7 @@ create_exception!(
     "An error returned from the Q# interpreter."
 );
 
-fn format_errors(errors: Vec<stateful::Error>) -> String {
+fn format_errors(errors: Vec<interpret::Error>) -> String {
     errors
         .into_iter()
         .map(|e| {
@@ -249,8 +250,8 @@ fn format_errors(errors: Vec<stateful::Error>) -> String {
 }
 
 /// Additional help text for an error specific to the Python module
-fn python_help(error: &stateful::Error) -> Option<String> {
-    if matches!(error, stateful::Error::UnsupportedRuntimeCapabilities) {
+fn python_help(error: &interpret::Error) -> Option<String> {
+    if matches!(error, interpret::Error::UnsupportedRuntimeCapabilities) {
         Some("Unsupported target profile. Initialize Q# by running `qsharp.init(target_profile=qsharp.TargetProfile.Base)` before performing code generation.".into())
     } else {
         None
@@ -499,7 +500,7 @@ impl IntoPyErr for Report {
     }
 }
 
-impl IntoPyErr for Vec<stateful::Error> {
+impl IntoPyErr for Vec<interpret::Error> {
     fn into_py_err(self) -> PyErr {
         let mut message = String::new();
         for error in self {
diff --git a/resource_estimator/src/estimates/modeling/logical_qubit.rs b/resource_estimator/src/estimates/modeling/logical_qubit.rs
index f1f4df77..3935398f 100644
--- a/resource_estimator/src/estimates/modeling/logical_qubit.rs
+++ b/resource_estimator/src/estimates/modeling/logical_qubit.rs
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 #[cfg(test)]
 mod tests;
 
diff --git a/resource_estimator/src/lib.rs b/resource_estimator/src/lib.rs
index a18199c6..d294011f 100644
--- a/resource_estimator/src/lib.rs
+++ b/resource_estimator/src/lib.rs
@@ -9,10 +9,7 @@ pub use estimates::estimate_physical_resources_from_json;
 use counts::LogicalCounter;
 use estimates::estimate_physical_resources;
 use miette::Diagnostic;
-use qsc::interpret::{
-    stateful::{self, Interpreter},
-    GenericReceiver,
-};
+use qsc::interpret::{self, GenericReceiver, Interpreter};
 use thiserror::Error;
 
 #[derive(Debug)]
@@ -29,7 +26,7 @@ pub struct LogicalResources {
 #[error(transparent)]
 #[diagnostic(transparent)]
 pub enum Error {
-    Interpreter(stateful::Error),
+    Interpreter(interpret::Error),
     Estimation(estimates::Error),
 }
 
diff --git a/vscode/src/notebookTemplate.ts b/vscode/src/notebookTemplate.ts
index a948af79..78cc8f5d 100644
--- a/vscode/src/notebookTemplate.ts
+++ b/vscode/src/notebookTemplate.ts
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 import * as vscode from "vscode";
 
 // The VS Code notebook representation does not exactly match the ipynb JSON structure,
diff --git a/wasm/src/debug_service.rs b/wasm/src/debug_service.rs
index fdf7dfbf..eac5db6a 100644
--- a/wasm/src/debug_service.rs
+++ b/wasm/src/debug_service.rs
@@ -4,8 +4,7 @@
 use std::str::FromStr;
 
 use qsc::fir::StmtId;
-use qsc::interpret::stateful::Interpreter;
-use qsc::interpret::{stateful, StepAction, StepResult};
+use qsc::interpret::{Error, Interpreter, StepAction, StepResult};
 use qsc::{fmt_complex, target::Profile, PackageType, SourceMap};
 
 use crate::{get_source_map, serializable_type, CallbackReceiver};
@@ -146,7 +145,7 @@ impl DebugService {
         event_cb: F,
         bps: &[StmtId],
         step: StepAction,
-    ) -> Result<StepResult, Vec<stateful::Error>>
+    ) -> Result<StepResult, Vec<Error>>
     where
         F: Fn(&str),
     {
@@ -219,7 +218,7 @@ impl Default for DebugService {
     }
 }
 
-fn render_errors(errors: Vec<qsc::interpret::stateful::Error>) -> String {
+fn render_errors(errors: Vec<Error>) -> String {
     let mut msg = String::new();
     for error in errors {
         let error_string = render_error(error);
@@ -228,7 +227,7 @@ fn render_errors(errors: Vec<qsc::interpret::stateful::Error>) -> String {
     msg
 }
 
-fn render_error(error: qsc::interpret::stateful::Error) -> String {
+fn render_error(error: Error) -> String {
     format!("{:?}\n", miette::Report::new(error))
 }
 
diff --git a/wasm/src/diagnostic.rs b/wasm/src/diagnostic.rs
index eb76f423..eda2086d 100644
--- a/wasm/src/diagnostic.rs
+++ b/wasm/src/diagnostic.rs
@@ -3,7 +3,7 @@
 
 use crate::serializable_type;
 use miette::{Diagnostic, LabeledSpan, Severity};
-use qsc::{self, error::WithSource, interpret::stateful, SourceName};
+use qsc::{self, error::WithSource, interpret, SourceName};
 use serde::{Deserialize, Serialize};
 use std::{fmt::Write, iter};
 use wasm_bindgen::prelude::*;
@@ -52,16 +52,13 @@ impl VSDiagnostic {
     }
 
     /// Creates a [VSDiagnostic] from an interpreter error. See `VSDiagnostic::new()` for details.
-    pub(crate) fn from_interpret_error(
-        source_name: &str,
-        err: &qsc::interpret::stateful::Error,
-    ) -> Self {
+    pub(crate) fn from_interpret_error(source_name: &str, err: &interpret::Error) -> Self {
         let labels = match err {
-            stateful::Error::Compile(e) => error_labels(e),
-            stateful::Error::Pass(e) => error_labels(e),
-            stateful::Error::Eval(e) => error_labels(e.error()),
-            stateful::Error::NoEntryPoint => Vec::new(),
-            stateful::Error::UnsupportedRuntimeCapabilities => Vec::new(),
+            interpret::Error::Compile(e) => error_labels(e),
+            interpret::Error::Pass(e) => error_labels(e),
+            interpret::Error::Eval(e) => error_labels(e.error()),
+            interpret::Error::NoEntryPoint => Vec::new(),
+            interpret::Error::UnsupportedRuntimeCapabilities => Vec::new(),
         };
 
         Self::new(labels, source_name, err)
diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs
index f2643acd..7d3f58bd 100644
--- a/wasm/src/lib.rs
+++ b/wasm/src/lib.rs
@@ -12,8 +12,8 @@ use qsc::{
     compile,
     hir::PackageId,
     interpret::{
+        self,
         output::{self, Receiver},
-        stateful,
     },
     target::Profile,
     PackageStore, PackageType, SourceContents, SourceMap, SourceName, SparseSim,
@@ -102,7 +102,7 @@ fn _get_qir(sources: SourceMap) -> Result<String, String> {
 pub fn get_estimates(sources: Vec<js_sys::Array>, params: &str) -> Result<String, String> {
     let sources = get_source_map(sources, None);
 
-    let mut interpreter = stateful::Interpreter::new(
+    let mut interpreter = interpret::Interpreter::new(
         true,
         sources,
         PackageType::Exe,
@@ -111,7 +111,7 @@ pub fn get_estimates(sources: Vec<js_sys::Array>, params: &str) -> Result<String
     .map_err(|e| e[0].to_string())?;
 
     estimate_entry(&mut interpreter, params).map_err(|e| match &e[0] {
-        re::Error::Interpreter(stateful::Error::Eval(e)) => e.to_string(),
+        re::Error::Interpreter(interpret::Error::Eval(e)) => e.to_string(),
         re::Error::Interpreter(_) => unreachable!("interpreter errors should be eval errors"),
         re::Error::Estimation(e) => e.to_string(),
     })
@@ -202,7 +202,7 @@ where
     }
 }
 
-fn run_internal<F>(sources: SourceMap, event_cb: F, shots: u32) -> Result<(), Box<stateful::Error>>
+fn run_internal<F>(sources: SourceMap, event_cb: F, shots: u32) -> Result<(), Box<interpret::Error>>
 where
     F: FnMut(&str),
 {
@@ -213,7 +213,7 @@ where
         .expect("There must be a source to process")
         .to_string();
     let mut out = CallbackReceiver { event_cb };
-    let mut interpreter = match stateful::Interpreter::new(
+    let mut interpreter = match interpret::Interpreter::new(
         true,
         sources,
         PackageType::Exe,
diff --git a/wasm/src/project_system.rs b/wasm/src/project_system.rs
index 0a5a1b5c..f203c4ec 100644
--- a/wasm/src/project_system.rs
+++ b/wasm/src/project_system.rs
@@ -1,3 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
 use async_trait::async_trait;
 use js_sys::JsString;
 use qsc_project::{EntryType, JSFileEntry, Manifest, ManifestDescriptor, ProjectSystemCallbacks};


Test patch:
diff --git a/compiler/qsc/src/interpret/debug/tests.rs b/compiler/qsc/src/interpret/debug/tests.rs
index ef68f0a0..c0bba739 100644
--- a/compiler/qsc/src/interpret/debug/tests.rs
+++ b/compiler/qsc/src/interpret/debug/tests.rs
@@ -10,7 +10,7 @@ use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
 use qsc_passes::PackageType;
 use std::io::Cursor;
 
-use crate::interpret::stateful::{self, InterpretResult, Interpreter};
+use crate::interpret::{Error, InterpretResult, Interpreter};
 
 fn line(interpreter: &mut Interpreter, line: impl AsRef<str>) -> (InterpretResult, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
@@ -21,7 +21,7 @@ fn line(interpreter: &mut Interpreter, line: impl AsRef<str>) -> (InterpretResul
     )
 }
 
-fn eval(interpreter: &mut stateful::Interpreter) -> (Result<Value, Vec<stateful::Error>>, String) {
+fn eval(interpreter: &mut Interpreter) -> (Result<Value, Vec<Error>>, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     (interpreter.eval_entry(&mut receiver), receiver.dump())
diff --git a/compiler/qsc/src/interpret/stateful/stepping_tests.rs b/compiler/qsc/src/interpret/stepping_tests.rs
similarity index 93%
rename from compiler/qsc/src/interpret/stateful/stepping_tests.rs
rename to compiler/qsc/src/interpret/stepping_tests.rs
index 958f3e3b..415e4983 100644
--- a/compiler/qsc/src/interpret/stateful/stepping_tests.rs
+++ b/compiler/qsc/src/interpret/stepping_tests.rs
@@ -3,7 +3,7 @@
 
 #![allow(clippy::needless_raw_string_hashes)]
 
-use crate::interpret::stateful::Interpreter;
+use crate::interpret::Interpreter;
 use qsc_eval::{output::CursorReceiver, StepAction, StepResult};
 use qsc_fir::fir::StmtId;
 use qsc_frontend::compile::{RuntimeCapabilityFlags, SourceMap};
@@ -38,30 +38,21 @@ fn expect_bp(interpreter: &mut Interpreter, ids: &[StmtId], expected_id: StmtId)
 fn step_in(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     step(interpreter, breakpoints, qsc_eval::StepAction::In)
 }
 
 fn step_next(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     step(interpreter, breakpoints, qsc_eval::StepAction::Next)
 }
 
 fn step_out(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     step(interpreter, breakpoints, qsc_eval::StepAction::Out)
 }
 
@@ -69,10 +60,7 @@ fn step(
     interpreter: &mut Interpreter,
     breakpoints: &[StmtId],
     step: StepAction,
-) -> (
-    Result<StepResult, Vec<crate::interpret::stateful::Error>>,
-    String,
-) {
+) -> (Result<StepResult, Vec<crate::interpret::Error>>, String) {
     let mut cursor = Cursor::new(Vec::<u8>::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     (
@@ -137,7 +125,7 @@ mod given_interpreter_with_sources {
         use super::*;
 
         #[test]
-        fn in_one_level_operation_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn in_one_level_operation_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
@@ -161,7 +149,7 @@ mod given_interpreter_with_sources {
         }
 
         #[test]
-        fn next_crosses_operation_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn next_crosses_operation_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
@@ -181,7 +169,7 @@ mod given_interpreter_with_sources {
         }
 
         #[test]
-        fn in_multiple_operations_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn in_multiple_operations_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
@@ -208,7 +196,7 @@ mod given_interpreter_with_sources {
         }
 
         #[test]
-        fn out_multiple_operations_works() -> Result<(), Vec<crate::interpret::stateful::Error>> {
+        fn out_multiple_operations_works() -> Result<(), Vec<crate::interpret::Error>> {
             let sources = SourceMap::new([("test".into(), STEPPING_SOURCE.into())], None);
             let mut interpreter = Interpreter::new(
                 true,
diff --git a/compiler/qsc/src/interpret/stateful/tests.rs b/compiler/qsc/src/interpret/tests.rs
similarity index 99%
rename from compiler/qsc/src/interpret/stateful/tests.rs
rename to compiler/qsc/src/interpret/tests.rs
index 2da9196b..e2706e2a 100644
--- a/compiler/qsc/src/interpret/stateful/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -4,7 +4,7 @@
 #![allow(clippy::needless_raw_string_hashes)]
 
 mod given_interpreter {
-    use crate::interpret::stateful::{Error, InterpretResult, Interpreter};
+    use crate::interpret::{Error, InterpretResult, Interpreter};
     use expect_test::Expect;
     use miette::Diagnostic;
     use qsc_eval::{output::CursorReceiver, val::Value};
@@ -32,10 +32,7 @@ mod given_interpreter {
 
     fn entry(
         interpreter: &mut Interpreter,
-    ) -> (
-        Result<Value, Vec<crate::interpret::stateful::Error>>,
-        String,
-    ) {
+    ) -> (Result<Value, Vec<crate::interpret::Error>>, String) {
         let mut cursor = Cursor::new(Vec::<u8>::new());
         let mut receiver = CursorReceiver::new(&mut cursor);
         (interpreter.eval_entry(&mut receiver), receiver.dump())
@@ -1060,7 +1057,7 @@ mod given_interpreter {
     }
 
     fn is_unit_with_output_eval_entry(
-        result: &Result<Value, Vec<crate::interpret::stateful::Error>>,
+        result: &Result<Value, Vec<crate::interpret::Error>>,
         output: &str,
         expected_output: &str,
     ) {
diff --git a/compiler/qsc_frontend/src/typeck/tests.rs b/compiler/qsc_frontend/src/typeck/tests.rs
index a94f3214..d16bc316 100644
--- a/compiler/qsc_frontend/src/typeck/tests.rs
+++ b/compiler/qsc_frontend/src/typeck/tests.rs
@@ -1181,7 +1181,7 @@ fn ternop_update_array_range_takes_array() {
             }
         "},
         "",
-        &expect![[r##"
+        &expect![[r#"
             #6 30-32 "()" : Unit
             #8 38-112 "{\n        let xs = [0, 1, 2];\n        let ys = xs w/ 0..1 <- [3, 4];\n    }" : Unit
             #10 52-54 "xs" : Int[]
@@ -1198,7 +1198,7 @@ fn ternop_update_array_range_takes_array() {
             #26 99-105 "[3, 4]" : Int[]
             #27 100-101 "3" : Int
             #28 103-104 "4" : Int
-        "##]],
+        "#]],
     );
 }
 
@@ -1214,7 +1214,7 @@ fn ternop_update_array_range_with_non_array_error() {
             }
         "},
         "",
-        &expect![[r##"
+        &expect![[r#"
             #6 30-32 "()" : Unit
             #8 38-107 "{\n        let xs = [0, 1, 2];\n        let ys = xs w/ 0..1 <- 3;\n    }" : Unit
             #10 52-54 "xs" : Int[]
@@ -1230,7 +1230,7 @@ fn ternop_update_array_range_with_non_array_error() {
             #25 94-95 "1" : Int
             #26 99-100 "3" : Int
             Error(Type(Error(TyMismatch("Int[]", "Int", Span { lo: 85, hi: 100 }))))
-        "##]],
+        "#]],
     );
 }
 
@@ -3588,3 +3588,45 @@ fn inference_infinite_recursion_should_fail() {
         "#]],
     );
 }
+
+#[test]
+fn use_range_field_names_in_udt() {
+    check(
+        indoc! {"
+            namespace A {
+                newtype B = (
+                    Start : Int,
+                    Step : Int,
+                    End : Int
+                );
+                function C() : Unit {
+                    let udt = B(1, 2, 3);
+                    let start = udt::Start;
+                    let step = udt::Step;
+                    let end = udt::End;
+                }
+            }
+        "},
+        "",
+        &expect![[r#"
+            #24 112-114 "()" : Unit
+            #28 122-249 "{\n        let udt = B(1, 2, 3);\n        let start = udt::Start;\n        let step = udt::Step;\n        let end = udt::End;\n    }" : Unit
+            #30 136-139 "udt" : UDT<"B": Item 1>
+            #32 142-152 "B(1, 2, 3)" : UDT<"B": Item 1>
+            #33 142-143 "B" : ((Int, Int, Int) -> UDT<"B": Item 1>)
+            #36 143-152 "(1, 2, 3)" : (Int, Int, Int)
+            #37 144-145 "1" : Int
+            #38 147-148 "2" : Int
+            #39 150-151 "3" : Int
+            #41 166-171 "start" : Int
+            #43 174-184 "udt::Start" : Int
+            #44 174-177 "udt" : UDT<"B": Item 1>
+            #49 198-202 "step" : Int
+            #51 205-214 "udt::Step" : Int
+            #52 205-208 "udt" : UDT<"B": Item 1>
+            #57 228-231 "end" : Int
+            #59 234-242 "udt::End" : Int
+            #60 234-237 "udt" : UDT<"B": Item 1>
+        "#]],
+    );
+}
diff --git a/katas/src/tests.rs b/katas/src/tests.rs
index a35c9e61..a94b38c9 100644
--- a/katas/src/tests.rs
+++ b/katas/src/tests.rs
@@ -1,7 +1,7 @@
 // Copyright (c) Microsoft Corporation.
 // Licensed under the MIT License.
 
-use qsc::interpret::{output::CursorReceiver, stateful};
+use qsc::interpret::{output::CursorReceiver, Error};
 use std::{
     env, fs,
     io::Cursor,
@@ -14,7 +14,7 @@ fn test_cases_dir() -> PathBuf {
         .join("test_cases")
 }
 
-fn run_check_solution(solution: &str, verification: &str) -> Result<bool, Vec<stateful::Error>> {
+fn run_check_solution(solution: &str, verification: &str) -> Result<bool, Vec<Error>> {
     let mut cursor = Cursor::new(Vec::new());
     let mut receiver = CursorReceiver::new(&mut cursor);
     let result = crate::check_solution(
diff --git a/library/src/tests.rs b/library/src/tests.rs
index afc9bc88..a50e191e 100644
--- a/library/src/tests.rs
+++ b/library/src/tests.rs
@@ -14,7 +14,7 @@ mod table_lookup;
 
 use indoc::indoc;
 use qsc::{
-    interpret::{stateful, GenericReceiver, Value},
+    interpret::{GenericReceiver, Interpreter, Value},
     target::Profile,
     PackageType, SourceMap,
 };
@@ -43,9 +43,8 @@ pub fn test_expression_with_lib_and_profile(
 
     let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into()));
 
-    let mut interpreter =
-        stateful::Interpreter::new(true, sources, PackageType::Exe, profile.into())
-            .expect("test should compile");
+    let mut interpreter = Interpreter::new(true, sources, PackageType::Exe, profile.into())
+        .expect("test should compile");
     let result = interpreter
         .eval_entry(&mut out)
         .expect("test should run successfully");
diff --git a/resource_estimator/src/counts/tests.rs b/resource_estimator/src/counts/tests.rs
index 491c9bc8..dc0212c1 100644
--- a/resource_estimator/src/counts/tests.rs
+++ b/resource_estimator/src/counts/tests.rs
@@ -8,7 +8,7 @@ use std::convert::Into;
 use expect_test::{expect, Expect};
 use indoc::indoc;
 use qsc::{
-    interpret::{stateful::Interpreter, GenericReceiver},
+    interpret::{GenericReceiver, Interpreter},
     target::Profile,
     PackageType, SourceMap,
 };