Repository: microsoft/qsharp
Pull request #1040 (2024-01-18 22:13:53)

update pipeline to run sample projects; add multi file sample
This PR updates our samples build stage in the pipeline to accept projects. We detect if a sample is a project in `build.py` and execute the correct `qsc` command accordingly.

Additionally, this PR adds a new command line flag to `qsc` and `qsi`: `--qsharp-json`. This flag allows the user to specify a path to a `qsharp.json` to compile a project via the command line. Previously, the only way to compile a project via the command line was to `cd` into it.

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | 878.9±8.39µs | **863.1±9.62µs** | **-1.80%** |
| Array update evaluation | 950.3±10.47µs | **934.4±13.40µs** | **-1.67%** |
| Deutsch-Jozsa evaluation | 31.3±0.20ms | 31.3±0.24ms | 0.00% |
| Large file parity evaluation | 36.3±0.08ms | 36.3±0.11ms | 0.00% |
| Large input file | **40.7±0.86ms** | 42.1±3.42ms | **+3.44%** |
| Standard library | **21.6±0.49ms** | 22.8±0.77ms | **+5.56%** |
| Teleport evaluation | 104.3±2.40µs | 104.8±1.67µs | +0.48% |

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

| Test | Base         | PR               | % |
|------|--------------|------------------|---|
| Array append evaluation | **862.7±7.59µs** | 873.5±11.80µs | **+1.25%** |
| Array update evaluation | 935.9±15.21µs | 943.9±28.59µs | +0.85% |
| Deutsch-Jozsa evaluation | 31.3±0.21ms | 31.3±0.74ms | 0.00% |
| Large file parity evaluation | 36.3±0.14ms | 36.4±0.39ms | +0.28% |
| Large input file | 38.6±1.05ms | 38.6±0.82ms | 0.00% |
| Standard library | 20.8±0.19ms | 20.9±0.20ms | +0.48% |
| Teleport evaluation | 104.1±1.68µs | 105.3±1.99µs | +1.15% |

  </details>
  

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/Cargo.lock b/Cargo.lock
index 9d74eb54..89545a52 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -34,9 +34,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anstream"
-version = "0.6.4"
+version = "0.6.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
+checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -54,27 +54,27 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.2"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.0.0"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
 dependencies = [
  "windows-sys",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.1"
+version = "3.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
 dependencies = [
  "anstyle",
  "windows-sys",
@@ -82,15 +82,15 @@ dependencies = [
 
 [[package]]
 name = "arbitrary"
-version = "1.3.1"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2e1373abdaa212b704512ec2bd8b26bd0b7d5c3f70117411a5d9a451383c859"
+checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
 
 [[package]]
 name = "async-trait"
-version = "0.1.74"
+version = "0.1.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
+checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -135,9 +135,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.4.1"
+version = "2.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
 
 [[package]]
 name = "bumpalo"
@@ -196,9 +196,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.4.6"
+version = "4.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
+checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -206,9 +206,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.4.6"
+version = "4.4.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
+checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
 dependencies = [
  "anstream",
  "anstyle",
@@ -218,9 +218,9 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.4.2"
+version = "4.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
+checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -230,9 +230,9 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.5.1"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
+checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
 
 [[package]]
 name = "colorchoice"
@@ -288,18 +288,18 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
 
 [[package]]
 name = "enum-iterator"
-version = "1.4.1"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689"
+checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94"
 dependencies = [
  "enum-iterator-derive",
 ]
 
 [[package]]
 name = "enum-iterator-derive"
-version = "1.2.1"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
+checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -308,9 +308,9 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.10.0"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
 dependencies = [
  "humantime",
  "is-terminal",
@@ -321,9 +321,9 @@ dependencies = [
 
 [[package]]
 name = "errno"
-version = "0.3.5"
+version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
 dependencies = [
  "libc",
  "windows-sys",
@@ -347,9 +347,9 @@ checksum = "4f4cdac9e4065d7c48e30770f8665b8cef9a3a73a63a4056a33a5f395bc7cf75"
 
 [[package]]
 name = "futures"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -362,9 +362,9 @@ dependencies = [
 
 [[package]]
 name = "futures-channel"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
 dependencies = [
  "futures-core",
  "futures-sink",
@@ -372,15 +372,15 @@ dependencies = [
 
 [[package]]
 name = "futures-core"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
 
 [[package]]
 name = "futures-executor"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
 dependencies = [
  "futures-core",
  "futures-task",
@@ -389,15 +389,15 @@ dependencies = [
 
 [[package]]
 name = "futures-io"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
 
 [[package]]
 name = "futures-macro"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -406,21 +406,21 @@ dependencies = [
 
 [[package]]
 name = "futures-sink"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
 
 [[package]]
 name = "futures-task"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
 
 [[package]]
 name = "futures-util"
-version = "0.3.29"
+version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
 dependencies = [
  "futures-channel",
  "futures-core",
@@ -444,9 +444,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.10"
+version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -457,9 +457,9 @@ dependencies = [
 
 [[package]]
 name = "gimli"
-version = "0.28.0"
+version = "0.28.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
 
 [[package]]
 name = "half"
@@ -475,9 +475,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
 
 [[package]]
 name = "hermit-abi"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
 
 [[package]]
 name = "humantime"
@@ -499,9 +499,9 @@ checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
 
 [[package]]
 name = "is-terminal"
-version = "0.4.9"
+version = "0.4.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
 dependencies = [
  "hermit-abi",
  "rustix",
@@ -525,9 +525,9 @@ dependencies = [
 
 [[package]]
 name = "itoa"
-version = "1.0.9"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
 
 [[package]]
 name = "jobserver"
@@ -540,9 +540,9 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.64"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
+checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -556,9 +556,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.149"
+version = "0.2.152"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
+checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
 
 [[package]]
 name = "libfuzzer-sys"
@@ -588,9 +588,9 @@ dependencies = [
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.4.10"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
 
 [[package]]
 name = "lock_api"
@@ -620,9 +620,9 @@ dependencies = [
 
 [[package]]
 name = "memchr"
-version = "2.6.4"
+version = "2.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
 
 [[package]]
 name = "memoffset"
@@ -728,18 +728,18 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.32.1"
+version = "0.32.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
 name = "once_cell"
-version = "1.18.0"
+version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "oorandom"
@@ -773,7 +773,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -806,18 +806,18 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.69"
+version = "1.0.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
 dependencies = [
  "unicode-ident",
 ]
 
 [[package]]
 name = "pyo3"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b"
+checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0"
 dependencies = [
  "cfg-if",
  "indoc",
@@ -833,9 +833,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-build-config"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5"
+checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be"
 dependencies = [
  "once_cell",
  "python3-dll-a",
@@ -844,9 +844,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-ffi"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b"
+checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1"
 dependencies = [
  "libc",
  "pyo3-build-config",
@@ -854,9 +854,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b"
+checksum = "05f738b4e40d50b5711957f142878cfa0f28e054aa0ebdfc3fd137a843f74ed3"
 dependencies = [
  "proc-macro2",
  "pyo3-macros-backend",
@@ -866,9 +866,9 @@ dependencies = [
 
 [[package]]
 name = "pyo3-macros-backend"
-version = "0.20.0"
+version = "0.20.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424"
+checksum = "0fc910d4851847827daf9d6cdd4a823fbdaab5b8818325c5e97a86da79e8881f"
 dependencies = [
  "heck",
  "proc-macro2",
@@ -978,7 +978,7 @@ dependencies = [
 name = "qsc_frontend"
 version = "0.0.0"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "enum-iterator",
  "expect-test",
  "indoc",
@@ -1109,7 +1109,7 @@ dependencies = [
 [[package]]
 name = "quantum-sparse-sim"
 version = "0.5.0"
-source = "git+https://github.com/qir-alliance/qir-runner?rev=f000e0066f56338595be37092cd0498f72ab10a2#f000e0066f56338595be37092cd0498f72ab10a2"
+source = "git+https://github.com/qir-alliance/qir-runner?rev=e7de80bf06dcaf69367576ec31f901c0496a9832#e7de80bf06dcaf69367576ec31f901c0496a9832"
 dependencies = [
  "ndarray",
  "num-bigint",
@@ -1121,9 +1121,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.33"
+version = "1.0.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
 dependencies = [
  "proc-macro2",
 ]
@@ -1248,11 +1248,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
 [[package]]
 name = "rustix"
-version = "0.38.20"
+version = "0.38.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
+checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
 dependencies = [
- "bitflags 2.4.1",
+ "bitflags 2.4.2",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -1261,9 +1261,9 @@ dependencies = [
 
 [[package]]
 name = "ryu"
-version = "1.0.15"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
 
 [[package]]
 name = "same-file"
@@ -1282,18 +1282,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.189"
+version = "1.0.195"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
+checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde-wasm-bindgen"
-version = "0.6.0"
+version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
+checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132"
 dependencies = [
  "js-sys",
  "serde",
@@ -1302,9 +1302,9 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.189"
+version = "1.0.195"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
+checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1313,9 +1313,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.107"
+version = "1.0.111"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
+checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
 dependencies = [
  "itoa",
  "ryu",
@@ -1333,9 +1333,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.11.1"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
+checksum = "3b187f0231d56fe41bfb12034819dd2bf336422a5866de41bc3fec4b2e3883e8"
 
 [[package]]
 name = "smawk"
@@ -1345,9 +1345,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
 
 [[package]]
 name = "special"
-version = "0.10.2"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322ed043a063dd3cabb4b53b2a37bce115854d94df19acf766d9dacaff218c5a"
+checksum = "b89cf0d71ae639fdd8097350bfac415a41aabf1d5ddd356295fdc95f09760382"
 dependencies = [
  "libm",
 ]
@@ -1388,9 +1388,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.38"
+version = "2.0.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1399,15 +1399,15 @@ dependencies = [
 
 [[package]]
 name = "target-lexicon"
-version = "0.12.12"
+version = "0.12.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
+checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
 
 [[package]]
 name = "termcolor"
-version = "1.3.0"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
 dependencies = [
  "winapi-util",
 ]
@@ -1435,18 +1435,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.49"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
+checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.49"
+version = "1.0.56"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
+checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1465,9 +1465,9 @@ dependencies = [
 
 [[package]]
 name = "tokio"
-version = "1.34.0"
+version = "1.35.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
 dependencies = [
  "backtrace",
  "pin-project-lite",
@@ -1533,9 +1533,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
+checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
 dependencies = [
  "cfg-if",
  "wasm-bindgen-macro",
@@ -1543,9 +1543,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
+checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
 dependencies = [
  "bumpalo",
  "log",
@@ -1558,9 +1558,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.37"
+version = "0.4.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
+checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -1570,9 +1570,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
+checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -1580,9 +1580,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
+checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1593,15 +1593,15 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.87"
+version = "0.2.90"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
+checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
 
 [[package]]
 name = "web-sys"
-version = "0.3.64"
+version = "0.3.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
+checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -1640,11 +1640,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
 [[package]]
 name = "windows-sys"
-version = "0.48.0"
+version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets",
+ "windows-targets 0.52.0",
 ]
 
 [[package]]
@@ -1653,13 +1653,28 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
 dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
 ]
 
 [[package]]
@@ -1668,38 +1683,80 @@ version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
diff --git a/Cargo.toml b/Cargo.toml
index 70fe31de..4d90afd8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,16 +30,16 @@ license = "MIT"
 version = "0.0.0"
 
 [workspace.dependencies]
-bitflags = "2.4.1"
+bitflags = "2.4.2"
 clap = "4.4"
 criterion = { version = "0.5", default-features = false }
-enum-iterator = "1.4"
-env_logger = "0.10.0"
+enum-iterator = "1.5"
+env_logger = "0.10"
 expect-test = "1.4"
 futures = "0.3"
 futures-util = "0.3"
 fasteval = "0.2"
-getrandom = { version = "0.2" }
+getrandom = "0.2"
 indoc = "2.0"
 js-sys = "0.3"
 libfuzzer-sys = "0.4"
@@ -58,11 +58,11 @@ serde-wasm-bindgen = "0.6"
 wasm-bindgen = "0.2"
 wasm-bindgen-futures = "0.4"
 rand = "0.8"
-serde_json = { version = "1.0" }
-pyo3 = { version = "0.20" }
-quantum-sparse-sim = { git = "https://github.com/qir-alliance/qir-runner", rev = "f000e0066f56338595be37092cd0498f72ab10a2", default-features = false }
+serde_json = "1.0"
+pyo3 = "0.20"
+quantum-sparse-sim = { git = "https://github.com/qir-alliance/qir-runner", rev = "e7de80bf06dcaf69367576ec31f901c0496a9832", default-features = false }
 async-trait = "0.1"
-tokio = { version = "1.34", features = ["macros", "rt"] }
+tokio = { version = "1.35", features = ["macros", "rt"] }
 
 [profile.release]
 debug = 1
diff --git a/build.py b/build.py
index 61a79b20..2eaee810 100755
--- a/build.py
+++ b/build.py
@@ -317,17 +317,28 @@ if build_wasm:
 
 if build_samples:
     step_start("Building qsharp samples")
+    project_directories = [dir for dir in os.walk(samples_src) if "qsharp.json" in dir[2]]
+    single_file_directories = [candidate for candidate in os.walk(samples_src) if all([not proj_dir[0] in candidate[0] for proj_dir in project_directories])]
+
     files = [
         os.path.join(dp, f)
-        for dp, _, filenames in os.walk(samples_src)
+        for dp, _, filenames in single_file_directories
         for f in filenames
         if os.path.splitext(f)[1] == ".qs"
     ]
+    projects =  [
+        os.path.join(dp, f)
+        for dp, _, filenames in project_directories
+        for f in filenames
+        if f == "qsharp.json"
+    ]
     cargo_args = ["cargo", "run", "--bin", "qsc"]
     if build_type == "release":
         cargo_args.append("--release")
     for file in files:
         subprocess.run((cargo_args + ["--", file]), check=True, text=True, cwd=root_dir)
+    for project in projects:
+        subprocess.run((cargo_args + ["--", "--qsharp-json", project]), check=True, text=True, cwd=root_dir)
     step_end()
 
 if build_npm:
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/qsc.rs b/compiler/qsc/src/bin/qsc.rs
index 87fc911e..e9907a55 100644
--- a/compiler/qsc/src/bin/qsc.rs
+++ b/compiler/qsc/src/bin/qsc.rs
@@ -51,6 +51,10 @@ struct Cli {
     /// Q# source files to compile, or `-` to read from stdin.
     #[arg()]
     sources: Vec<PathBuf>,
+
+    /// Path to a Q# manifest for a project
+    #[arg(short, long)]
+    qsharp_json: Option<PathBuf>,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
@@ -83,7 +87,7 @@ fn main() -> miette::Result<ExitCode> {
 
     if sources.is_empty() {
         let fs = StdFs;
-        let manifest = Manifest::load()?;
+        let manifest = Manifest::load(cli.qsharp_json)?;
         if let Some(manifest) = manifest {
             let project = fs.load_project(&manifest)?;
             let mut project_sources = project.sources;
diff --git a/compiler/qsc/src/bin/qsi.rs b/compiler/qsc/src/bin/qsi.rs
index 323043fb..2cfcabe7 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,
@@ -43,6 +43,10 @@ struct Cli {
     /// Exit after loading the files or running the given file(s)/entry on the command line.
     #[arg(long)]
     exec: bool,
+
+    /// Path to a Q# manifest for a project
+    #[arg(short, long)]
+    qsharp_json: Option<PathBuf>,
 }
 
 struct TerminalReceiver;
@@ -78,7 +82,7 @@ fn main() -> miette::Result<ExitCode> {
 
     if sources.is_empty() {
         let fs = StdFs;
-        let manifest = Manifest::load()?;
+        let manifest = Manifest::load(cli.qsharp_json)?;
         if let Some(manifest) = manifest {
             let project = fs.load_project(&manifest)?;
             let mut project_sources = project.sources;
@@ -194,7 +198,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/compiler/qsc_project/src/manifest.rs b/compiler/qsc_project/src/manifest.rs
index 6dc6ab77..baa7c51e 100644
--- a/compiler/qsc_project/src/manifest.rs
+++ b/compiler/qsc_project/src/manifest.rs
@@ -46,9 +46,14 @@ impl Manifest {
     /// a manifest file exists but is the wrong format.
     /// Returns `Ok(None)` if there is no file matching the manifest file
     /// name.
-    pub fn load() -> std::result::Result<Option<ManifestDescriptor>, Error> {
-        let current_dir = current_dir()?;
-        Self::load_from_path(current_dir)
+    pub fn load(
+        manifest_path: Option<PathBuf>,
+    ) -> std::result::Result<Option<ManifestDescriptor>, Error> {
+        let dir = match manifest_path {
+            Some(path) => path,
+            None => current_dir()?,
+        };
+        Self::load_from_path(dir)
     }
 
     /// Given a [PathBuf], traverse [PathBuf::ancestors] until a Manifest is found.
@@ -56,6 +61,16 @@ impl Manifest {
     /// Returns an error if a manifest is found, but is not parsable into the
     /// expected format.
     pub fn load_from_path(path: PathBuf) -> std::result::Result<Option<ManifestDescriptor>, Error> {
+        // if the given path points to a file, change it to point to the parent folder.
+        // This lets consumers pass in either the path directly to the manifest file, or the path
+        // to the folder containing the manifest file.
+        let path = if path.is_file() {
+            let mut path = path;
+            path.pop();
+            path
+        } else {
+            path
+        };
         let ancestors = path.ancestors();
         for ancestor in ancestors {
             let listing = ancestor.read_dir()?;
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/samples/language/MultiFileProject/qsharp.json b/samples/language/MultiFileProject/qsharp.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/samples/language/MultiFileProject/qsharp.json
@@ -0,0 +1,1 @@
+{}
diff --git a/samples/language/MultiFileProject/src/Main.qs b/samples/language/MultiFileProject/src/Main.qs
new file mode 100644
index 00000000..0f30c504
--- /dev/null
+++ b/samples/language/MultiFileProject/src/Main.qs
@@ -0,0 +1,19 @@
+/// # Sample
+/// Multi File Project
+///
+/// # Description
+/// Organizing code into multiple Q# source files is an important part of 
+/// writing readable and maintainable code. In this project, we have `Main.qs`,
+/// and `Particle.qs`, which defines a new namespace for particle operations.
+/// The presence of a Q# manifest file (`qsharp.json`) tells the compiler
+/// to include all Q# files under `src/`.
+namespace MyQuantumApp {
+  open Particle;
+  @EntryPoint()
+  operation Main() : Unit {
+    let particleA = Particle(0, 0, 0);
+    let particleB = Particle(1, 1, 1);
+
+    let particleC = addParticles(particleA, particleB);
+  }
+}
diff --git a/samples/language/MultiFileProject/src/Particle.qs b/samples/language/MultiFileProject/src/Particle.qs
new file mode 100644
index 00000000..bbe73ef9
--- /dev/null
+++ b/samples/language/MultiFileProject/src/Particle.qs
@@ -0,0 +1,9 @@
+namespace Particle {
+  newtype Particle = (x: Int, y: Int, z: Int);
+
+  function addParticles(a: Particle, b: Particle) : Particle {
+    let (x1, y1, z1) = a!;
+    let (x2, y2, z2) = b!;
+    return Particle(x1 + x2, y1 + y2, z1 + z2);
+  }
+}
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,
 };