Repository: microsoft/qsharp
Pull request #2064 (2024-12-12 20:48:34)

use common extension ID instead of hardcoded one
This was a drive-by update from my work in #2059. Just wanted it in a separate PR to keep the noise down.

It seems to have triggered some `prettier` fixes too.

billti: Was there a motivating reason for this change? (i.e. do we ever change it during development, testing, or publishing?)
sezna: I was registering a new code action and noticed that we do this inconsistently across other files. If we have a source of truth defined somewhere we might as well use it consistently or not at all. 

Patch:
diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs
index e1560bac..2c736ee7 100644
--- a/compiler/qsc/src/interpret.rs
+++ b/compiler/qsc/src/interpret.rs
@@ -11,6 +11,8 @@ mod package_tests;
 #[cfg(test)]
 mod tests;
 
+use std::rc::Rc;
+
 pub use qsc_eval::{
     debug::Frame,
     noise::PauliNoise,
@@ -21,6 +23,7 @@ pub use qsc_eval::{
     val::Value,
     StepAction, StepResult,
 };
+use qsc_hir::{global, ty};
 use qsc_linter::{HirLint, Lint, LintKind, LintLevel};
 use qsc_lowerer::{map_fir_package_to_hir, map_hir_package_to_fir};
 use qsc_partial_eval::ProgramEntry;
@@ -315,6 +318,72 @@ impl Interpreter {
         })
     }
 
+    /// Given a package ID, returns all the global items in the package.
+    /// Note this does not currently include re-exports.
+    fn package_globals(&self, package_id: PackageId) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
+        let mut exported_items = Vec::new();
+        let package = &self
+            .compiler
+            .package_store()
+            .get(map_fir_package_to_hir(package_id))
+            .expect("package should exist in the package store")
+            .package;
+        for global in global::iter_package(Some(map_fir_package_to_hir(package_id)), package) {
+            if let global::Kind::Term(term) = global.kind {
+                let store_item_id = fir::StoreItemId {
+                    package: package_id,
+                    item: fir::LocalItemId::from(usize::from(term.id.item)),
+                };
+                exported_items.push((
+                    global.namespace,
+                    global.name,
+                    Value::Global(store_item_id, FunctorApp::default()),
+                ));
+            }
+        }
+        exported_items
+    }
+
+    /// Get the global callables defined in the user source passed into initialization of the interpreter as `Value` instances.
+    pub fn user_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
+        self.package_globals(self.source_package)
+    }
+
+    /// Get the global callables defined in the open package being interpreted as `Value` instances, which will include any items
+    /// defined by calls to `eval_fragments` and the like.
+    pub fn source_globals(&self) -> Vec<(Vec<Rc<str>>, Rc<str>, Value)> {
+        self.package_globals(self.package)
+    }
+
+    /// Get the input and output types of a given value representing a global item.
+    /// # Panics
+    /// Panics if the item is not callable or a type that can be invoked as a callable.
+    pub fn global_tys(&self, item_id: &Value) -> Option<(ty::Ty, ty::Ty)> {
+        let Value::Global(item_id, _) = item_id else {
+            panic!("value is not a global callable");
+        };
+        let package_id = map_fir_package_to_hir(item_id.package);
+        let unit = self
+            .compiler
+            .package_store()
+            .get(package_id)
+            .expect("package should exist in the package store");
+        let item = unit
+            .package
+            .items
+            .get(qsc_hir::hir::LocalItemId::from(usize::from(item_id.item)))?;
+        match &item.kind {
+            qsc_hir::hir::ItemKind::Callable(decl) => {
+                Some((decl.input.ty.clone(), decl.output.clone()))
+            }
+            qsc_hir::hir::ItemKind::Ty(_, udt) => {
+                // We don't handle UDTs, so we return an error type that prevents later code from processing this item.
+                Some((udt.get_pure_ty(), ty::Ty::Err))
+            }
+            _ => panic!("item is not callable"),
+        }
+    }
+
     pub fn set_quantum_seed(&mut self, seed: Option<u64>) {
         self.quantum_seed = seed;
         self.sim.set_seed(seed);
@@ -467,6 +536,33 @@ impl Interpreter {
         )
     }
 
+    /// Invokes the given callable with the given arguments using the current environment, simlator, and compilation.
+    pub fn invoke(
+        &mut self,
+        receiver: &mut impl Receiver,
+        callable: Value,
+        args: Value,
+    ) -> InterpretResult {
+        qsc_eval::invoke(
+            self.package,
+            self.classical_seed,
+            &self.fir_store,
+            &mut self.env,
+            &mut self.sim,
+            receiver,
+            callable,
+            args,
+        )
+        .map_err(|(error, call_stack)| {
+            eval_error(
+                self.compiler.package_store(),
+                &self.fir_store,
+                call_stack,
+                error,
+            )
+        })
+    }
+
     /// Runs the given entry expression on a new instance of the environment and simulator,
     /// but using the current compilation.
     pub fn run(
@@ -1033,7 +1129,7 @@ impl<'a> BreakpointCollector<'a> {
             .expect("Couldn't find source file")
     }
 
-    fn add_stmt(&mut self, stmt: &qsc_fir::fir::Stmt) {
+    fn add_stmt(&mut self, stmt: &fir::Stmt) {
         let source: &Source = self.get_source(stmt.span.lo);
         if source.offset == self.offset {
             let span = stmt.span - source.offset;
diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs
index dc957bfc..5847ab44 100644
--- a/compiler/qsc/src/lib.rs
+++ b/compiler/qsc/src/lib.rs
@@ -38,7 +38,8 @@ pub mod project {
 }
 
 pub use qsc_data_structures::{
-    language_features::LanguageFeatures, namespaces::*, span::Span, target::TargetCapabilityFlags,
+    functors::FunctorApp, language_features::LanguageFeatures, namespaces::*, span::Span,
+    target::TargetCapabilityFlags,
 };
 
 pub use qsc_passes::{lower_hir_to_fir, PackageType, PassContext};
diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs
index 8655e056..e3daac3b 100644
--- a/compiler/qsc_eval/src/lib.rs
+++ b/compiler/qsc_eval/src/lib.rs
@@ -282,6 +282,51 @@ pub fn eval(
     Ok(value)
 }
 
+/// Evaluates the given callable with the given context.
+/// # Errors
+/// Returns the first error encountered during execution.
+/// # Panics
+/// On internal error where no result is returned.
+#[allow(clippy::too_many_arguments)]
+pub fn invoke(
+    package: PackageId,
+    seed: Option<u64>,
+    globals: &impl PackageStoreLookup,
+    env: &mut Env,
+    sim: &mut impl Backend<ResultType = impl Into<val::Result>>,
+    receiver: &mut impl Receiver,
+    callable: Value,
+    args: Value,
+) -> Result<Value, (Error, Vec<Frame>)> {
+    let mut state = State::new(package, Vec::new().into(), seed);
+    // Push the callable value into the state stack and then the args value so they are ready for evaluation.
+    state.set_val_register(callable);
+    state.push_val();
+    state.set_val_register(args);
+
+    // Evaluate the call, which will pop the args and callable values from the stack and then either
+    // a) prepare the call stack for the execution of the callable, or
+    // b) invoke the callable directly if it is an intrinsic.
+    state
+        .eval_call(
+            env,
+            sim,
+            globals,
+            Span::default(),
+            Span::default(),
+            receiver,
+        )
+        .map_err(|e| (e, state.get_stack_frames()))?;
+
+    // Trigger evaluation of the state until the end of the stack is reached and a return value is obtained, which will be the final
+    // result of the invocation.
+    let res = state.eval(globals, env, sim, receiver, &[], StepAction::Continue)?;
+    let StepResult::Return(value) = res else {
+        panic!("eval should always return a value");
+    };
+    Ok(value)
+}
+
 /// The type of step action to take during evaluation
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum StepAction {
diff --git a/compiler/qsc_hir/src/global.rs b/compiler/qsc_hir/src/global.rs
index 754daf48..d4afe8fe 100644
--- a/compiler/qsc_hir/src/global.rs
+++ b/compiler/qsc_hir/src/global.rs
@@ -157,6 +157,17 @@ impl PackageIter<'_> {
                     intrinsic: decl.body.body == SpecBody::Gen(SpecGen::Intrinsic),
                 }),
             }),
+            (ItemKind::Callable(decl), None) => Some(Global {
+                namespace: Vec::new(),
+                name: alias.map_or_else(|| Rc::clone(&decl.name.name), |alias| alias.name.clone()),
+                visibility,
+                status,
+                kind: Kind::Term(Term {
+                    id,
+                    scheme: decl.scheme(),
+                    intrinsic: decl.body.body == SpecBody::Gen(SpecGen::Intrinsic),
+                }),
+            }),
             (ItemKind::Ty(name, def), Some(ItemKind::Namespace(namespace, _))) => {
                 self.next = Some(Global {
                     namespace: namespace.into(),
diff --git a/compiler/qsc_linter/src/linter.rs b/compiler/qsc_linter/src/linter.rs
index f6728a76..1be9eefc 100644
--- a/compiler/qsc_linter/src/linter.rs
+++ b/compiler/qsc_linter/src/linter.rs
@@ -26,7 +26,7 @@ pub fn run_lints(
         compile_unit,
     };
 
-    let mut ast_lints = run_ast_lints(&compile_unit.ast.package, config);
+    let mut ast_lints = run_ast_lints(&compile_unit.ast.package, config, compilation);
     let mut hir_lints = run_hir_lints(&compile_unit.package, config, compilation);
 
     let mut lints = Vec::new();
diff --git a/compiler/qsc_linter/src/linter/ast.rs b/compiler/qsc_linter/src/linter/ast.rs
index 697998ad..b9a201ba 100644
--- a/compiler/qsc_linter/src/linter/ast.rs
+++ b/compiler/qsc_linter/src/linter/ast.rs
@@ -16,7 +16,11 @@ use qsc_ast::{
 /// The entry point to the AST linter. It takes a [`qsc_ast::ast::Package`]
 /// as input and outputs a [`Vec<Lint>`](Lint).
 #[must_use]
-pub fn run_ast_lints(package: &qsc_ast::ast::Package, config: Option<&[LintConfig]>) -> Vec<Lint> {
+pub fn run_ast_lints(
+    package: &qsc_ast::ast::Package,
+    config: Option<&[LintConfig]>,
+    compilation: Compilation,
+) -> Vec<Lint> {
     let config: Vec<(AstLint, LintLevel)> = config
         .unwrap_or(&[])
         .iter()
@@ -29,7 +33,7 @@ pub fn run_ast_lints(package: &qsc_ast::ast::Package, config: Option<&[LintConfi
         })
         .collect();
 
-    let mut lints = CombinedAstLints::from_config(config);
+    let mut lints = CombinedAstLints::from_config(config, compilation);
 
     for node in &package.nodes {
         match node {
@@ -46,23 +50,71 @@ pub fn run_ast_lints(package: &qsc_ast::ast::Package, config: Option<&[LintConfi
 /// The trait provides default empty implementations for the rest of the methods,
 /// which will be optimized to a no-op by the rust compiler.
 pub(crate) trait AstLintPass {
-    fn check_attr(&self, _attr: &Attr, _buffer: &mut Vec<Lint>) {}
-    fn check_block(&self, _block: &Block, _buffer: &mut Vec<Lint>) {}
-    fn check_callable_decl(&self, _callable_decl: &CallableDecl, _buffer: &mut Vec<Lint>) {}
-    fn check_expr(&self, _expr: &Expr, _buffer: &mut Vec<Lint>) {}
-    fn check_functor_expr(&self, _functor_expr: &FunctorExpr, _buffer: &mut Vec<Lint>) {}
-    fn check_ident(&self, _ident: &Ident, _buffer: &mut Vec<Lint>) {}
-    fn check_item(&self, _item: &Item, _buffer: &mut Vec<Lint>) {}
-    fn check_namespace(&self, _namespace: &Namespace, _buffer: &mut Vec<Lint>) {}
-    fn check_package(&self, _package: &Package, _buffer: &mut Vec<Lint>) {}
-    fn check_pat(&self, _pat: &Pat, _buffer: &mut Vec<Lint>) {}
-    fn check_path(&self, _path: &Path, _buffer: &mut Vec<Lint>) {}
-    fn check_path_kind(&self, _path: &PathKind, _buffer: &mut Vec<Lint>) {}
-    fn check_qubit_init(&self, _qubit_init: &QubitInit, _buffer: &mut Vec<Lint>) {}
-    fn check_spec_decl(&self, _spec_decl: &SpecDecl, _buffer: &mut Vec<Lint>) {}
-    fn check_stmt(&self, _stmt: &Stmt, _buffer: &mut Vec<Lint>) {}
-    fn check_ty(&self, _ty: &Ty, _buffer: &mut Vec<Lint>) {}
-    fn check_ty_def(&self, _ty_def: &TyDef, _buffer: &mut Vec<Lint>) {}
+    fn check_attr(&mut self, _attr: &Attr, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_block(&mut self, _block: &Block, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_callable_decl(
+        &mut self,
+        _callable_decl: &CallableDecl,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
+    fn check_expr(&mut self, _expr: &Expr, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_functor_expr(
+        &mut self,
+        _functor_expr: &FunctorExpr,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
+    fn check_ident(&mut self, _ident: &Ident, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_item(&mut self, _item: &Item, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_namespace(
+        &mut self,
+        _namespace: &Namespace,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
+    fn check_package(
+        &mut self,
+        _package: &Package,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
+    fn check_pat(&mut self, _pat: &Pat, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_path(&mut self, _path: &Path, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_path_kind(
+        &mut self,
+        _path: &PathKind,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
+    fn check_qubit_init(
+        &mut self,
+        _qubit_init: &QubitInit,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
+    fn check_spec_decl(
+        &mut self,
+        _spec_decl: &SpecDecl,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
+    fn check_stmt(&mut self, _stmt: &Stmt, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_ty(&mut self, _ty: &Ty, _buffer: &mut Vec<Lint>, _compilation: Compilation) {}
+    fn check_ty_def(
+        &mut self,
+        _ty_def: &TyDef,
+        _buffer: &mut Vec<Lint>,
+        _compilation: Compilation,
+    ) {
+    }
 }
 
 /// This macro allow us to declare lints while avoiding boilerplate. It does three things:
@@ -82,7 +134,7 @@ macro_rules! declare_ast_lints {
         // This is a silly wrapper module to avoid contaminating the environment
         // calling the macro with unwanted imports.
         mod _ast_macro_expansion {
-            use crate::{linter::ast::{declare_ast_lints, AstLintPass}, Lint, LintLevel};
+            use crate::{linter::Compilation, linter::ast::{declare_ast_lints, AstLintPass}, Lint, LintLevel};
             use qsc_ast::{
                 ast::{
                     Attr, Block, CallableDecl, Expr, FunctorExpr, Ident, Item, Namespace, Package, Pat, Path, PathKind,
@@ -106,25 +158,14 @@ macro_rules! declare_ast_lints {
 
     // Declare & implement a struct representing a lint.
     (@LINT_STRUCT $lint_name:ident, $default_level:expr, $msg:expr, $help:expr) => {
-        pub(crate) struct $lint_name {
-            level: LintLevel,
-        }
-
-        impl Default for $lint_name {
-            fn default() -> Self {
-                Self { level: Self::DEFAULT_LEVEL }
-            }
-        }
-
-        impl From<LintLevel> for $lint_name {
-            fn from(value: LintLevel) -> Self {
-                Self { level: value }
-            }
-        }
-
         impl $lint_name {
             const DEFAULT_LEVEL: LintLevel = $default_level;
 
+            fn new() -> Self {
+                #[allow(clippy::needless_update)]
+                Self { level: Self::DEFAULT_LEVEL, ..Default::default() }
+            }
+
             const fn lint_kind(&self) -> LintKind {
                 LintKind::Ast(AstLint::$lint_name)
             }
@@ -164,24 +205,26 @@ macro_rules! declare_ast_lints {
         /// Combined AST lints for speed. This combined lint allow us to
         /// evaluate all the lints in a single AST pass, instead of doing
         /// an individual pass for each lint in the linter.
-        pub(crate) struct CombinedAstLints {
+        pub(crate) struct CombinedAstLints<'compilation> {
             pub buffer: Vec<Lint>,
+            pub compilation: Compilation<'compilation>,
             $($lint_name: $lint_name),*
         }
 
-        impl Default for CombinedAstLints {
-            fn default() -> Self {
+        impl<'compilation> CombinedAstLints<'compilation> {
+            fn new(compilation: Compilation<'compilation>) -> Self {
                 Self {
-                    buffer: Vec::default(),
-                    $($lint_name: <$lint_name>::default()),*
+                    buffer: Vec::new(),
+                    compilation,
+                    $($lint_name: <$lint_name>::new()),*
                 }
             }
         }
 
         // Most of the calls here are empty methods and they get optimized at compile time to a no-op.
-        impl CombinedAstLints {
-            pub fn from_config(config: Vec<(AstLint, LintLevel)>) -> Self {
-                let mut combined_ast_lints = Self::default();
+        impl<'compilation> CombinedAstLints<'compilation> {
+            pub fn from_config(config: Vec<(AstLint, LintLevel)>, compilation: Compilation<'compilation>) -> Self {
+                let mut combined_ast_lints = Self::new(compilation);
                 for (lint, level) in config {
                     match lint {
                         $(AstLint::$lint_name => combined_ast_lints.$lint_name.level = level),*
@@ -190,26 +233,26 @@ macro_rules! declare_ast_lints {
                 combined_ast_lints
             }
 
-            fn check_package(&mut self, package: &Package) { $(self.$lint_name.check_package(package, &mut self.buffer));*; }
-            fn check_namespace(&mut self, namespace: &Namespace) { $(self.$lint_name.check_namespace(namespace, &mut self.buffer));*; }
-            fn check_item(&mut self, item: &Item) { $(self.$lint_name.check_item(item, &mut self.buffer));*; }
-            fn check_attr(&mut self, attr: &Attr) { $(self.$lint_name.check_attr(attr, &mut self.buffer));*; }
-            fn check_ty_def(&mut self, def: &TyDef) { $(self.$lint_name.check_ty_def(def, &mut self.buffer));*; }
-            fn check_callable_decl(&mut self, decl: &CallableDecl) { $(self.$lint_name.check_callable_decl(decl, &mut self.buffer));*; }
-            fn check_spec_decl(&mut self, decl: &SpecDecl) { $(self.$lint_name.check_spec_decl(decl, &mut self.buffer));*; }
-            fn check_functor_expr(&mut self, expr: &FunctorExpr) { $(self.$lint_name.check_functor_expr(expr, &mut self.buffer));*; }
-            fn check_ty(&mut self, ty: &Ty) { $(self.$lint_name.check_ty(ty, &mut self.buffer));*; }
-            fn check_block(&mut self, block: &Block) { $(self.$lint_name.check_block(block, &mut self.buffer));*; }
-            fn check_stmt(&mut self, stmt: &Stmt) { $(self.$lint_name.check_stmt(stmt, &mut self.buffer));*; }
-            fn check_expr(&mut self, expr: &Expr) { $(self.$lint_name.check_expr(expr, &mut self.buffer));*; }
-            fn check_pat(&mut self, pat: &Pat) { $(self.$lint_name.check_pat(pat, &mut self.buffer));*; }
-            fn check_qubit_init(&mut self, init: &QubitInit) { $(self.$lint_name.check_qubit_init(init, &mut self.buffer));*; }
-            fn check_path(&mut self, path: &Path) { $(self.$lint_name.check_path(path, &mut self.buffer));*; }
-            fn check_path_kind(&mut self, path: &PathKind) { $(self.$lint_name.check_path_kind(path, &mut self.buffer));*; }
-            fn check_ident(&mut self, ident: &Ident) { $(self.$lint_name.check_ident(ident, &mut self.buffer));*; }
+            fn check_package(&mut self, package: &Package) { $(self.$lint_name.check_package(package, &mut self.buffer, self.compilation));*; }
+            fn check_namespace(&mut self, namespace: &Namespace) { $(self.$lint_name.check_namespace(namespace, &mut self.buffer, self.compilation));*; }
+            fn check_item(&mut self, item: &Item) { $(self.$lint_name.check_item(item, &mut self.buffer, self.compilation));*; }
+            fn check_attr(&mut self, attr: &Attr) { $(self.$lint_name.check_attr(attr, &mut self.buffer, self.compilation));*; }
+            fn check_ty_def(&mut self, def: &TyDef) { $(self.$lint_name.check_ty_def(def, &mut self.buffer, self.compilation));*; }
+            fn check_callable_decl(&mut self, decl: &CallableDecl) { $(self.$lint_name.check_callable_decl(decl, &mut self.buffer, self.compilation));*; }
+            fn check_spec_decl(&mut self, decl: &SpecDecl) { $(self.$lint_name.check_spec_decl(decl, &mut self.buffer, self.compilation));*; }
+            fn check_functor_expr(&mut self, expr: &FunctorExpr) { $(self.$lint_name.check_functor_expr(expr, &mut self.buffer, self.compilation));*; }
+            fn check_ty(&mut self, ty: &Ty) { $(self.$lint_name.check_ty(ty, &mut self.buffer, self.compilation));*; }
+            fn check_block(&mut self, block: &Block) { $(self.$lint_name.check_block(block, &mut self.buffer, self.compilation));*; }
+            fn check_stmt(&mut self, stmt: &Stmt) { $(self.$lint_name.check_stmt(stmt, &mut self.buffer, self.compilation));*; }
+            fn check_expr(&mut self, expr: &Expr) { $(self.$lint_name.check_expr(expr, &mut self.buffer, self.compilation));*; }
+            fn check_pat(&mut self, pat: &Pat) { $(self.$lint_name.check_pat(pat, &mut self.buffer, self.compilation));*; }
+            fn check_qubit_init(&mut self, init: &QubitInit) { $(self.$lint_name.check_qubit_init(init, &mut self.buffer, self.compilation));*; }
+            fn check_path(&mut self, path: &Path) { $(self.$lint_name.check_path(path, &mut self.buffer, self.compilation));*; }
+            fn check_path_kind(&mut self, path: &PathKind) { $(self.$lint_name.check_path_kind(path, &mut self.buffer, self.compilation));*; }
+            fn check_ident(&mut self, ident: &Ident) { $(self.$lint_name.check_ident(ident, &mut self.buffer, self.compilation));*; }
         }
 
-        impl<'a> Visitor<'a> for CombinedAstLints {
+        impl<'a> Visitor<'a> for CombinedAstLints<'_> {
             fn visit_package(&mut self, package: &'a Package) {
                 self.check_package(package);
                 visit::walk_package(self, package);
@@ -299,4 +342,4 @@ macro_rules! declare_ast_lints {
 
 pub(crate) use declare_ast_lints;
 
-use super::LintKind;
+use super::{Compilation, LintKind};
diff --git a/compiler/qsc_linter/src/lints/ast.rs b/compiler/qsc_linter/src/lints/ast.rs
index f78035a5..72bdf16a 100644
--- a/compiler/qsc_linter/src/lints/ast.rs
+++ b/compiler/qsc_linter/src/lints/ast.rs
@@ -2,8 +2,8 @@
 // Licensed under the MIT License.
 
 use super::lint;
-use crate::linter::ast::declare_ast_lints;
-use qsc_ast::ast::{BinOp, Block, Expr, ExprKind, Item, ItemKind, Lit, Stmt, StmtKind};
+use crate::linter::{ast::declare_ast_lints, Compilation};
+use qsc_ast::ast::{BinOp, Block, Expr, ExprKind, Item, ItemKind, Lit, NodeId, Stmt, StmtKind};
 use qsc_data_structures::span::Span;
 
 // Read Me:
@@ -28,10 +28,17 @@ declare_ast_lints! {
     (NeedlessParens, LintLevel::Allow, "unnecessary parentheses", "remove the extra parentheses for clarity"),
     (RedundantSemicolons, LintLevel::Warn, "redundant semicolons", "remove the redundant semicolons"),
     (DeprecatedNewtype, LintLevel::Allow, "deprecated `newtype` declarations", "`newtype` declarations are deprecated, use `struct` instead"),
+    (DeprecatedSet, LintLevel::Allow, "deprecated use of `set` keyword", "the `set` keyword is deprecated for assignments and can be removed"),
+    (DiscourageChainAssignment, LintLevel::Warn, "discouraged use of chain assignment", "assignment expressions always return `Unit`, so chaining them may not be useful"),
+}
+
+#[derive(Default)]
+struct DivisionByZero {
+    level: LintLevel,
 }
 
 impl AstLintPass for DivisionByZero {
-    fn check_expr(&self, expr: &Expr, buffer: &mut Vec<Lint>) {
+    fn check_expr(&mut self, expr: &Expr, buffer: &mut Vec<Lint>, _compilation: Compilation) {
         if let ExprKind::BinOp(BinOp::Div, _, ref rhs) = *expr.kind {
             if let ExprKind::Lit(ref lit) = *rhs.kind {
                 if let Lit::Int(0) = **lit {
@@ -42,6 +49,11 @@ impl AstLintPass for DivisionByZero {
     }
 }
 
+#[derive(Default)]
+struct NeedlessParens {
+    level: LintLevel,
+}
+
 impl NeedlessParens {
     /// The idea is that if we find a expr of the form:
     /// a + (expr)
@@ -82,7 +94,7 @@ impl NeedlessParens {
 }
 
 impl AstLintPass for NeedlessParens {
-    fn check_expr(&self, expr: &Expr, buffer: &mut Vec<Lint>) {
+    fn check_expr(&mut self, expr: &Expr, buffer: &mut Vec<Lint>, _compilation: Compilation) {
         match &*expr.kind {
             ExprKind::BinOp(_, left, right) => {
                 self.push(expr, left, buffer);
@@ -96,7 +108,7 @@ impl AstLintPass for NeedlessParens {
     }
 
     /// Checks the assignment statements.
-    fn check_stmt(&self, stmt: &Stmt, buffer: &mut Vec<Lint>) {
+    fn check_stmt(&mut self, stmt: &Stmt, buffer: &mut Vec<Lint>, _compilation: Compilation) {
         if let StmtKind::Local(_, _, right) = &*stmt.kind {
             if let ExprKind::Paren(_) = &*right.kind {
                 buffer.push(lint!(
@@ -109,6 +121,11 @@ impl AstLintPass for NeedlessParens {
     }
 }
 
+#[derive(Default)]
+struct RedundantSemicolons {
+    level: LintLevel,
+}
+
 impl RedundantSemicolons {
     /// Helper function that pushes a lint to the buffer if we have
     /// found two or more semicolons.
@@ -124,7 +141,7 @@ impl AstLintPass for RedundantSemicolons {
     /// semicolon is parsed as an Empty statement. If we have multiple empty
     /// statements in a row, we group them as single lint, that spans from
     /// the first redundant semicolon to the last redundant semicolon.
-    fn check_block(&self, block: &Block, buffer: &mut Vec<Lint>) {
+    fn check_block(&mut self, block: &Block, buffer: &mut Vec<Lint>, _compilation: Compilation) {
         // a finite state machine that keeps track of the span of the redundant semicolons
         // None: no redundant semicolons
         // Some(_): one or more redundant semicolons
@@ -164,11 +181,81 @@ fn precedence(expr: &Expr) -> u8 {
     }
 }
 
+#[derive(Default)]
+struct DeprecatedNewtype {
+    level: LintLevel,
+}
+
 /// Creates a lint for deprecated user-defined types declarations using `newtype`.
 impl AstLintPass for DeprecatedNewtype {
-    fn check_item(&self, item: &Item, buffer: &mut Vec<Lint>) {
+    fn check_item(&mut self, item: &Item, buffer: &mut Vec<Lint>, _compilation: Compilation) {
         if let ItemKind::Ty(_, _) = item.kind.as_ref() {
             buffer.push(lint!(self, item.span));
         }
     }
 }
+
+fn is_assign(expr: &Expr) -> bool {
+    matches!(
+        expr.kind.as_ref(),
+        ExprKind::Assign(_, _) | ExprKind::AssignOp(_, _, _) | ExprKind::AssignUpdate(_, _, _)
+    )
+}
+
+#[derive(Default)]
+struct DeprecatedSet {
+    level: LintLevel,
+}
+
+impl AstLintPass for DeprecatedSet {
+    fn check_expr(&mut self, expr: &Expr, buffer: &mut Vec<Lint>, compilation: Compilation) {
+        // If the expression is an assignment, check if the `set` keyword is used.
+        if is_assign(expr)
+            && ["set ", "set\n", "set\r\n", "set\t"]
+                .iter()
+                .any(|s| compilation.get_source_code(expr.span).starts_with(s))
+        {
+            // If the `set` keyword is used, push a lint.
+            let span = Span {
+                lo: expr.span.lo,
+                hi: expr.span.lo + 3,
+            };
+            buffer.push(lint!(self, span, vec![(String::new(), span)]));
+        }
+    }
+}
+
+#[derive(Default)]
+struct DiscourageChainAssignment {
+    level: LintLevel,
+    // Keeps track of the expressions that won't create lints because they are part of a chain.
+    repressed_exprs: Vec<NodeId>,
+}
+
+impl AstLintPass for DiscourageChainAssignment {
+    fn check_expr(&mut self, expr: &Expr, buffer: &mut Vec<Lint>, _compilation: Compilation) {
+        match expr.kind.as_ref() {
+            ExprKind::Assign(_, rhs)
+            | ExprKind::AssignOp(_, _, rhs)
+            | ExprKind::AssignUpdate(_, _, rhs) => {
+                // Check to see if this expressions is part of an existing chain.
+                let should_repress = self
+                    .repressed_exprs
+                    .last()
+                    .map_or_else(|| false, |l| *l == expr.id);
+                if should_repress {
+                    self.repressed_exprs.pop();
+                }
+                if is_assign(rhs) {
+                    // If this is a new chain, push a lint.
+                    if !should_repress {
+                        buffer.push(lint!(self, expr.span));
+                    }
+                    // Add the rhs to the repressed expressions because it is now part of an existing chain.
+                    self.repressed_exprs.push(rhs.id);
+                }
+            }
+            _ => {}
+        }
+    }
+}
diff --git a/compiler/qsc_linter/src/lints/hir.rs b/compiler/qsc_linter/src/lints/hir.rs
index ffbc70da..cddb2d20 100644
--- a/compiler/qsc_linter/src/lints/hir.rs
+++ b/compiler/qsc_linter/src/lints/hir.rs
@@ -222,7 +222,7 @@ impl HirLintPass for DeprecatedWithOperator {
                         (compilation.indentation_at_offset(info.span.lo) + 4) as usize;
                     let innermost_expr = compilation.get_source_code(expr.span);
                     let mut new_expr = if info.is_w_eq {
-                        format!("set {} = new {} {{\n", innermost_expr, info.ty_name)
+                        format!("{} = new {} {{\n", innermost_expr, info.ty_name)
                     } else {
                         format!("new {} {{\n", info.ty_name)
                     };
diff --git a/compiler/qsc_parse/src/expr.rs b/compiler/qsc_parse/src/expr.rs
index 433dcf2d..215da85c 100644
--- a/compiler/qsc_parse/src/expr.rs
+++ b/compiler/qsc_parse/src/expr.rs
@@ -27,7 +27,7 @@ use qsc_ast::ast::{
     self, BinOp, CallableKind, Expr, ExprKind, FieldAccess, FieldAssign, Functor, Lit, NodeId, Pat,
     PatKind, PathKind, Pauli, StringComponent, TernOp, UnOp,
 };
-use qsc_data_structures::span::Span;
+use qsc_data_structures::{language_features::LanguageFeatures, span::Span};
 use std::{result, str::FromStr};
 
 struct PrefixOp {
@@ -43,6 +43,9 @@ struct MixfixOp {
 enum OpKind {
     Postfix(UnOp),
     Binary(BinOp, Assoc),
+    Assign,
+    AssignUpdate,
+    AssignBinary(BinOp),
     Ternary(TernOp, TokenKind, Assoc),
     Rich(fn(&mut ParserContext, Box<Expr>) -> Result<Box<ExprKind>>),
 }
@@ -131,6 +134,20 @@ fn expr_op(s: &mut ParserContext, context: OpContext) -> Result<Box<Expr>> {
         s.advance();
         let kind = match op.kind {
             OpKind::Postfix(kind) => Box::new(ExprKind::UnOp(kind, lhs)),
+            OpKind::Assign => {
+                let rhs = expr_op(s, OpContext::Precedence(op.precedence))?;
+                Box::new(ExprKind::Assign(lhs, rhs))
+            }
+            OpKind::AssignUpdate => {
+                let mid = expr(s)?;
+                token(s, TokenKind::LArrow)?;
+                let rhs = expr_op(s, OpContext::Precedence(op.precedence))?;
+                Box::new(ExprKind::AssignUpdate(lhs, mid, rhs))
+            }
+            OpKind::AssignBinary(kind) => {
+                let rhs = expr_op(s, OpContext::Precedence(op.precedence))?;
+                Box::new(ExprKind::AssignOp(kind, lhs, rhs))
+            }
             OpKind::Binary(kind, assoc) => {
                 let precedence = next_precedence(op.precedence, assoc);
                 let rhs = expr_op(s, OpContext::Precedence(precedence))?;
@@ -211,8 +228,20 @@ fn expr_base(s: &mut ParserContext) -> Result<Box<Expr>> {
         Ok(Box::new(ExprKind::Repeat(body, cond, fixup)))
     } else if token(s, TokenKind::Keyword(Keyword::Return)).is_ok() {
         Ok(Box::new(ExprKind::Return(expr(s)?)))
-    } else if token(s, TokenKind::Keyword(Keyword::Set)).is_ok() {
-        expr_set(s)
+    } else if !s.contains_language_feature(LanguageFeatures::V2PreviewSyntax)
+        && token(s, TokenKind::Keyword(Keyword::Set)).is_ok()
+    {
+        // Need to rewrite the span of the expr to include the `set` keyword.
+        return expr(s).map(|assign| {
+            Box::new(Expr {
+                id: assign.id,
+                span: Span {
+                    lo,
+                    hi: assign.span.hi,
+                },
+                kind: assign.kind,
+            })
+        });
     } else if token(s, TokenKind::Keyword(Keyword::While)).is_ok() {
         Ok(Box::new(ExprKind::While(expr(s)?, stmt::parse_block(s)?)))
     } else if token(s, TokenKind::Keyword(Keyword::Within)).is_ok() {
@@ -316,29 +345,6 @@ fn expr_if(s: &mut ParserContext) -> Result<Box<ExprKind>> {
     Ok(Box::new(ExprKind::If(cond, body, otherwise)))
 }
 
-fn expr_set(s: &mut ParserContext) -> Result<Box<ExprKind>> {
-    let lhs = expr(s)?;
-    if token(s, TokenKind::Eq).is_ok() {
-        let rhs = expr(s)?;
-        Ok(Box::new(ExprKind::Assign(lhs, rhs)))
-    } else if token(s, TokenKind::WSlashEq).is_ok() {
-        let index = expr(s)?;
-        token(s, TokenKind::LArrow)?;
-        let rhs = expr(s)?;
-        Ok(Box::new(ExprKind::AssignUpdate(lhs, index, rhs)))
-    } else if let TokenKind::BinOpEq(op) = s.peek().kind {
-        s.advance();
-        let rhs = expr(s)?;
-        Ok(Box::new(ExprKind::AssignOp(closed_bin_op(op), lhs, rhs)))
-    } else {
-        Err(Error::new(ErrorKind::Rule(
-            "assignment operator",
-            s.peek().kind,
-            s.peek().span,
-        )))
-    }
-}
-
 fn expr_array(s: &mut ParserContext) -> Result<Box<ExprKind>> {
     token(s, TokenKind::Open(Delim::Bracket))?;
     let kind = expr_array_core(s)?;
@@ -357,8 +363,8 @@ fn expr_array_core(s: &mut ParserContext) -> Result<Box<ExprKind>> {
 
     s.expect(WordKinds::Size);
     let second = expr(s)?;
-    if is_ident("size", &second.kind) && token(s, TokenKind::Eq).is_ok() {
-        let size = expr(s)?;
+    if let Some(size) = is_array_size(&second.kind) {
+        let size = Box::new(size.clone());
         return Ok(Box::new(ExprKind::ArrayRepeat(first, size)));
     }
 
@@ -369,8 +375,18 @@ fn expr_array_core(s: &mut ParserContext) -> Result<Box<ExprKind>> {
     Ok(Box::new(ExprKind::Array(items.into_boxed_slice())))
 }
 
-fn is_ident(name: &str, kind: &ExprKind) -> bool {
-    matches!(kind, ExprKind::Path(PathKind::Ok(path)) if path.segments.is_none() && path.name.name.as_ref() == name)
+fn is_array_size(kind: &ExprKind) -> Option<&Expr> {
+    match kind {
+        ExprKind::Assign(lhs, rhs) => match lhs.kind.as_ref() {
+            ExprKind::Path(PathKind::Ok(path))
+                if path.segments.is_none() && path.name.name.as_ref() == "size" =>
+            {
+                Some(rhs)
+            }
+            _ => None,
+        },
+        _ => None,
+    }
 }
 
 fn expr_range_prefix(s: &mut ParserContext) -> Result<Box<ExprKind>> {
@@ -572,6 +588,18 @@ fn prefix_op(name: OpName) -> Option<PrefixOp> {
 #[allow(clippy::too_many_lines)]
 fn mixfix_op(name: OpName) -> Option<MixfixOp> {
     match name {
+        OpName::Token(TokenKind::Eq) => Some(MixfixOp {
+            kind: OpKind::Assign,
+            precedence: 0,
+        }),
+        OpName::Token(TokenKind::WSlashEq) => Some(MixfixOp {
+            kind: OpKind::AssignUpdate,
+            precedence: 0,
+        }),
+        OpName::Token(TokenKind::BinOpEq(kind)) => Some(MixfixOp {
+            kind: OpKind::AssignBinary(closed_bin_op(kind)),
+            precedence: 0,
+        }),
         OpName::Token(TokenKind::RArrow) => Some(MixfixOp {
             kind: OpKind::Rich(|s, input| lambda_op(s, *input, CallableKind::Function)),
             precedence: LAMBDA_PRECEDENCE,
diff --git a/jupyterlab/yarn.lock b/jupyterlab/yarn.lock
index f411cb02..599a9d4a 100644
--- a/jupyterlab/yarn.lock
+++ b/jupyterlab/yarn.lock
@@ -6,19 +6,14 @@ __metadata:
   cacheKey: 8
 
 "@codemirror/autocomplete@npm:^6.0.0, @codemirror/autocomplete@npm:^6.16.0, @codemirror/autocomplete@npm:^6.3.2, @codemirror/autocomplete@npm:^6.7.1":
-  version: 6.18.3
-  resolution: "@codemirror/autocomplete@npm:6.18.3"
+  version: 6.18.4
+  resolution: "@codemirror/autocomplete@npm:6.18.4"
   dependencies:
     "@codemirror/language": ^6.0.0
     "@codemirror/state": ^6.0.0
     "@codemirror/view": ^6.17.0
     "@lezer/common": ^1.0.0
-  peerDependencies:
-    "@codemirror/language": ^6.0.0
-    "@codemirror/state": ^6.0.0
-    "@codemirror/view": ^6.0.0
-    "@lezer/common": ^1.0.0
-  checksum: 48f3a09e05a2ab236641c3df0dbd577ef4c04da8ea8c26c47bc90f5652342a62d6e736e6b89428a85ff50efe039c01f0f0864134914a40a1513a4d57024cbb76
+  checksum: 4216f45a17f6cfd8d33df53f940396f7d3707662570bf3a79d8d333f926e273a265fac13c362e29e3fa57ccdf444f1a047862f5f56c672cfc669c87ee975858f
   languageName: node
   linkType: hard
 
@@ -45,15 +40,15 @@ __metadata:
   linkType: hard
 
 "@codemirror/lang-css@npm:^6.0.0, @codemirror/lang-css@npm:^6.2.1":
-  version: 6.3.0
-  resolution: "@codemirror/lang-css@npm:6.3.0"
+  version: 6.3.1
+  resolution: "@codemirror/lang-css@npm:6.3.1"
   dependencies:
     "@codemirror/autocomplete": ^6.0.0
     "@codemirror/language": ^6.0.0
     "@codemirror/state": ^6.0.0
     "@lezer/common": ^1.0.2
     "@lezer/css": ^1.1.7
-  checksum: e98e89fa436f0a27c95323efbb6a1c43a52ca0b9253ab3c12af16f38cb93670d42f8a63cc566e2f6b0348af2cdfa1a6c03cf045af2d6cb253b27b2efdce9b2b2
+  checksum: ed175d75d75bc0a059d1e60b3dcd8464d570da14fc97388439943c9c43e1e9146e37b83fe2ccaad9cd387420b7b411ea1d24ede78ecd1f2045a38acbb4dd36bc
   languageName: node
   linkType: hard
 
@@ -201,8 +196,8 @@ __metadata:
   linkType: hard
 
 "@codemirror/language@npm:^6.0.0, @codemirror/language@npm:^6.10.1, @codemirror/language@npm:^6.3.0, @codemirror/language@npm:^6.4.0, @codemirror/language@npm:^6.6.0, @codemirror/language@npm:^6.8.0":
-  version: 6.10.3
-  resolution: "@codemirror/language@npm:6.10.3"
+  version: 6.10.7
+  resolution: "@codemirror/language@npm:6.10.7"
   dependencies:
     "@codemirror/state": ^6.0.0
     "@codemirror/view": ^6.23.0
@@ -210,7 +205,7 @@ __metadata:
     "@lezer/highlight": ^1.0.0
     "@lezer/lr": ^1.0.0
     style-mod: ^4.0.0
-  checksum: 53fb72299500f63706f78c888d6b5fd81043ea11ea2fa4c72c13c6d4794bb6f4ec29450208c56b4f40e839984b3dc73505262803fa61416baf588da389a7c577
+  checksum: c9b71e2df8559bc677edae293a825a0dd196c98d49a6e20a98cc6bea51a01c67d268b07b5a761d7ac15b1d65415e17af1f644d5629ab4207268804e71cd48d7c
   languageName: node
   linkType: hard
 
@@ -224,42 +219,44 @@ __metadata:
   linkType: hard
 
 "@codemirror/lint@npm:^6.0.0":
-  version: 6.8.2
-  resolution: "@codemirror/lint@npm:6.8.2"
+  version: 6.8.4
+  resolution: "@codemirror/lint@npm:6.8.4"
   dependencies:
     "@codemirror/state": ^6.0.0
-    "@codemirror/view": ^6.0.0
+    "@codemirror/view": ^6.35.0
     crelt: ^1.0.5
-  checksum: 714fe911c2d600350ea8ca0f65ceb2de25ace511e71bf174a550ba0aefc9884ec4e099f0f500b55bfd0fccbd7fe3a342a0048ff5a49c8c20020ea16cc8bff3c3
+  checksum: 640e3dd44eb167d952eb5c5b8518919ba46e164aa3471776342f7f9361e676b4627a76a9f01d51b22127b97413f2bc9b8c60299d8dfdd5fc8ad0225d42de7669
   languageName: node
   linkType: hard
 
 "@codemirror/search@npm:^6.5.6":
-  version: 6.5.7
-  resolution: "@codemirror/search@npm:6.5.7"
+  version: 6.5.8
+  resolution: "@codemirror/search@npm:6.5.8"
   dependencies:
     "@codemirror/state": ^6.0.0
     "@codemirror/view": ^6.0.0
     crelt: ^1.0.5
-  checksum: 32d98eab5b096c20e923cc2b88f1510369ec9f6c89fa3ecd79e1a965b35dfdc323624da82739ce80e675333d8e9a1564756a1ae87464e6a918a98bd12f93cbfd
+  checksum: 0f9633037492a7b647b606c30255ea42c4327319e643be7ea3aa2913ed8e4aa662589f457e376636521c7d4d1215fae0e8939f127db9c0790b19ae3b654c3bc4
   languageName: node
   linkType: hard
 
-"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.4.1":
-  version: 6.4.1
-  resolution: "@codemirror/state@npm:6.4.1"
-  checksum: b81b55574091349eed4d32fc0eadb0c9688f1f7c98b681318f59138ee0f527cb4c4a97831b70547c0640f02f3127647838ae6730782de4a3dd2cc58836125d01
+"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.4.0, @codemirror/state@npm:^6.4.1, @codemirror/state@npm:^6.5.0":
+  version: 6.5.0
+  resolution: "@codemirror/state@npm:6.5.0"
+  dependencies:
+    "@marijn/find-cluster-break": ^1.0.0
+  checksum: f7fbed072cc67bf250f7d231668a00b988748cacaaa2ce3ea74ff13c7c392db76000e7d517933521cc6ad9dc3651c7b6d33accab3e3d4b9816cd3c5714661a84
   languageName: node
   linkType: hard
 
-"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.26.3, @codemirror/view@npm:^6.27.0":
-  version: 6.34.3
-  resolution: "@codemirror/view@npm:6.34.3"
+"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.26.3, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.35.0":
+  version: 6.36.1
+  resolution: "@codemirror/view@npm:6.36.1"
   dependencies:
-    "@codemirror/state": ^6.4.0
+    "@codemirror/state": ^6.5.0
     style-mod: ^4.1.0
     w3c-keyname: ^2.2.4
-  checksum: 371c5fc6ef52a4cb0f82d6fd014ce52fa31ddbaa8f7041c019ae49a695668d98d102b67d81f7a76006f768d894d7f9504cc4b2c2a359e243b9bf5e1979a0c4a3
+  checksum: 77728cbc6f07f16abc4b98c487b6fad522781c928e4b31597b28d54364da6aa5542ed7c9b5c77b90bec5095527c3c062450f156f54fc8ddbcacdf86b4b32c608
   languageName: node
   linkType: hard
 
@@ -278,13 +275,13 @@ __metadata:
   linkType: hard
 
 "@jridgewell/gen-mapping@npm:^0.3.5":
-  version: 0.3.5
-  resolution: "@jridgewell/gen-mapping@npm:0.3.5"
+  version: 0.3.8
+  resolution: "@jridgewell/gen-mapping@npm:0.3.8"
   dependencies:
     "@jridgewell/set-array": ^1.2.1
     "@jridgewell/sourcemap-codec": ^1.4.10
     "@jridgewell/trace-mapping": ^0.3.24
-  checksum: ff7a1764ebd76a5e129c8890aa3e2f46045109dabde62b0b6c6a250152227647178ff2069ea234753a690d8f3c4ac8b5e7b267bbee272bffb7f3b0a370ab6e52
+  checksum: c0687b5227461717aa537fe71a42e356bcd1c43293b3353796a148bf3b0d6f59109def46c22f05b60e29a46f19b2e4676d027959a7c53a6c92b9d5b0d87d0420
   languageName: node
   linkType: hard
 
@@ -319,7 +316,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@jridgewell/trace-mapping@npm:^0.3.20, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25":
+"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25":
   version: 0.3.25
   resolution: "@jridgewell/trace-mapping@npm:0.3.25"
   dependencies:
@@ -352,8 +349,8 @@ __metadata:
   linkType: hard
 
 "@jupyter/ydoc@npm:^3.0.0":
-  version: 3.0.1
-  resolution: "@jupyter/ydoc@npm:3.0.1"
+  version: 3.0.2
+  resolution: "@jupyter/ydoc@npm:3.0.2"
   dependencies:
     "@jupyterlab/nbformat": ^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0
     "@lumino/coreutils": ^1.11.0 || ^2.0.0
@@ -361,24 +358,24 @@ __metadata:
     "@lumino/signaling": ^1.10.0 || ^2.0.0
     y-protocols: ^1.0.5
     yjs: ^13.5.40
-  checksum: 4e14c40d81ee3c57cc0d84eb90d06f1bd98a18718f07510e918d12ae645a700b4f04c8862c1addf113eeab85102643324870598eea718f52d627f58b22e81977
+  checksum: 770f73459635c74bd0e5cacdca1ea1f77ee8efd6e7cd58f0ccbb167ae8374e73118620f4f3628646281160a7bc7389f374bd2106f1e799bdc8f78cad0ce05b28
   languageName: node
   linkType: hard
 
 "@jupyterlab/application@npm:^4.0.0":
-  version: 4.3.1
-  resolution: "@jupyterlab/application@npm:4.3.1"
+  version: 4.3.4
+  resolution: "@jupyterlab/application@npm:4.3.4"
   dependencies:
     "@fortawesome/fontawesome-free": ^5.12.0
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/docregistry": ^4.3.1
-    "@jupyterlab/rendermime": ^4.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/statedb": ^4.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/docregistry": ^4.3.4
+    "@jupyterlab/rendermime": ^4.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/statedb": ^4.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/application": ^2.4.1
     "@lumino/commands": ^2.3.1
@@ -389,23 +386,23 @@ __metadata:
     "@lumino/properties": ^2.0.2
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
-  checksum: f5893a67c6f15ca23b7a4c6e027fab324a9405cf83741f6324a3d5c8c15c2c0c869c8a1f752026eb9c58b4256ffe5081bad177d64161d2e84c9e86e0eb14476a
+  checksum: 7afd40976775799062cfc1f86c240642f7b5dc8d60558f5f7982337323a3afadb8df5bbb687d9fc0aed1618c80b62827c74946966e72b2760d22c4aa0825e6d1
   languageName: node
   linkType: hard
 
-"@jupyterlab/apputils@npm:^4.4.1":
-  version: 4.4.1
-  resolution: "@jupyterlab/apputils@npm:4.4.1"
-  dependencies:
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/settingregistry": ^4.3.1
-    "@jupyterlab/statedb": ^4.3.1
-    "@jupyterlab/statusbar": ^4.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+"@jupyterlab/apputils@npm:^4.4.4":
+  version: 4.4.4
+  resolution: "@jupyterlab/apputils@npm:4.4.4"
+  dependencies:
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/settingregistry": ^4.3.4
+    "@jupyterlab/statedb": ^4.3.4
+    "@jupyterlab/statusbar": ^4.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/commands": ^2.3.1
     "@lumino/coreutils": ^2.2.0
@@ -418,27 +415,27 @@ __metadata:
     "@types/react": ^18.0.26
     react: ^18.2.0
     sanitize-html: ~2.12.1
-  checksum: bcb91a90540003e1a89bae2c3187e15b655c0ecf8ef11bf82146cdbdad2f25589c76660520b2bb6625eec00502dbe271be263994b8ee3b0259c3f96690f14325
+  checksum: cac57d28905578799cda60c53af22a5ea14232aa6e2498d38398fc5d3ab8fbd69ddbeb4b04a70c60a89bd94cfef8bdd5a9c07613eb9a51bcfce15a5251b34366
   languageName: node
   linkType: hard
 
-"@jupyterlab/attachments@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/attachments@npm:4.3.1"
+"@jupyterlab/attachments@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/attachments@npm:4.3.4"
   dependencies:
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime": ^4.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime": ^4.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
     "@lumino/disposable": ^2.1.3
     "@lumino/signaling": ^2.1.3
-  checksum: c373823651cb0b5dec3e267c604df834a4744c868ffc96f1de060a49e80787816923e5b09dddc9fe752f2d6386129a583eb83877443e336e84824a887e1ae03e
+  checksum: 1e253e3ec6482d849573d561a13c3476624d9ddd8c14705268cfa8728a8d5d308decb4c0baf640f707f61f769054277b660bab3d4c6ff9df96a6fd958d583d34
   languageName: node
   linkType: hard
 
 "@jupyterlab/builder@npm:^4.0.0":
-  version: 4.3.1
-  resolution: "@jupyterlab/builder@npm:4.3.1"
+  version: 4.3.4
+  resolution: "@jupyterlab/builder@npm:4.3.4"
   dependencies:
     "@lumino/algorithm": ^2.0.2
     "@lumino/application": ^2.4.1
@@ -473,32 +470,32 @@ __metadata:
     worker-loader: ^3.0.2
   bin:
     build-labextension: lib/build-labextension.js
-  checksum: 8d2726e3e808732964facf9dcc6d0fcc01854c336d0b4f5ef5357ee78ef6941947b98bed78f94cdc9c8c14da4cda9f1057dddce46fd9db3ea0bbc0e6eb4f119c
+  checksum: fd495abe59afb1ad52f83e97cd79870dc6d5f879f706f02c6746e7a37e6f00af3a5d2fdc879a384048fa966d0160831369b3ae91115dcf903474709064046026
   languageName: node
   linkType: hard
 
-"@jupyterlab/cells@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/cells@npm:4.3.1"
+"@jupyterlab/cells@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/cells@npm:4.3.4"
   dependencies:
     "@codemirror/state": ^6.4.1
     "@codemirror/view": ^6.26.3
     "@jupyter/ydoc": ^3.0.0
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/attachments": ^4.3.1
-    "@jupyterlab/codeeditor": ^4.3.1
-    "@jupyterlab/codemirror": ^4.3.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/documentsearch": ^4.3.1
-    "@jupyterlab/filebrowser": ^4.3.1
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/outputarea": ^4.3.1
-    "@jupyterlab/rendermime": ^4.3.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/toc": ^6.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/attachments": ^4.3.4
+    "@jupyterlab/codeeditor": ^4.3.4
+    "@jupyterlab/codemirror": ^4.3.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/documentsearch": ^4.3.4
+    "@jupyterlab/filebrowser": ^4.3.4
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/outputarea": ^4.3.4
+    "@jupyterlab/rendermime": ^4.3.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/toc": ^6.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/domutils": ^2.0.2
@@ -509,23 +506,23 @@ __metadata:
     "@lumino/virtualdom": ^2.0.2
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: bcb3da3315d1b91dadd8697f49f50ce7a05a6e6290e5caa3029db3d9fb90e60bf51b5b94907ead92d52b96b8bf44778ed9f5b7fef44280a9d16ea56144119885
+  checksum: 7c0d9d1b48b9c7139ed9adef059b6f03cd9e62a30e8fdca3224044382facb145e0d3edec64d57818860d0e733e9defea89084bd4e83015cf5683f2711f54ebe5
   languageName: node
   linkType: hard
 
-"@jupyterlab/codeeditor@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/codeeditor@npm:4.3.1"
+"@jupyterlab/codeeditor@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/codeeditor@npm:4.3.4"
   dependencies:
     "@codemirror/state": ^6.4.1
     "@jupyter/ydoc": ^3.0.0
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/statusbar": ^4.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/statusbar": ^4.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
     "@lumino/dragdrop": ^2.1.5
@@ -533,13 +530,13 @@ __metadata:
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: d71158530cd53da4b853460236206d574c97020ec92e3dbec76469a2d81ce68eea3620c0bbba77226fe145356e48f54fdf78bcf768c2c6220d718b99d15484ae
+  checksum: bbd3d13a01450de40cd9d5bee5b347c7828b9c43a08433856540b0a73ac0c9703f669352f26c579ba8bd5ba7da35ab5de79db2a5afcc8a9f7b516d7d28f0b162
   languageName: node
   linkType: hard
 
-"@jupyterlab/codemirror@npm:^4.0.0, @jupyterlab/codemirror@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/codemirror@npm:4.3.1"
+"@jupyterlab/codemirror@npm:^4.0.0, @jupyterlab/codemirror@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/codemirror@npm:4.3.4"
   dependencies:
     "@codemirror/autocomplete": ^6.16.0
     "@codemirror/commands": ^6.5.0
@@ -562,11 +559,11 @@ __metadata:
     "@codemirror/state": ^6.4.1
     "@codemirror/view": ^6.26.3
     "@jupyter/ydoc": ^3.0.0
-    "@jupyterlab/codeeditor": ^4.3.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/documentsearch": ^4.3.1
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/translation": ^4.3.1
+    "@jupyterlab/codeeditor": ^4.3.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/documentsearch": ^4.3.4
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/translation": ^4.3.4
     "@lezer/common": ^1.2.1
     "@lezer/generator": ^1.7.0
     "@lezer/highlight": ^1.2.0
@@ -575,13 +572,13 @@ __metadata:
     "@lumino/disposable": ^2.1.3
     "@lumino/signaling": ^2.1.3
     yjs: ^13.5.40
-  checksum: db35ec00b518b54f5600ad2182fb9c3a76a49ec9029242116b93c87a8431333ab04a7e827269e606d68c127435fc2bd7415d7885db344006628b5c60c0ab162a
+  checksum: 9c9067f3cf5eb59891c474748c04b85c9fe2910cc9ba87c7d833fcd3c0b3d0212f0699f797b28e9a78c0fdd92fb67ad5d4165657712708fd9174c0b94d3811db
   languageName: node
   linkType: hard
 
-"@jupyterlab/coreutils@npm:^6.3.1":
-  version: 6.3.1
-  resolution: "@jupyterlab/coreutils@npm:6.3.1"
+"@jupyterlab/coreutils@npm:^6.3.4":
+  version: 6.3.4
+  resolution: "@jupyterlab/coreutils@npm:6.3.4"
   dependencies:
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -589,22 +586,22 @@ __metadata:
     minimist: ~1.2.0
     path-browserify: ^1.0.0
     url-parse: ~1.5.4
-  checksum: 9de967c43379d5c888981f60e1fda42b243f327ba86653bdbba2f4c1add733676a1963bda8f4a21a010ad095bf5ddd188a1000b3e81e140a4d75ee26c1048de7
+  checksum: 3db39307315acb29dd606d02d5fcc6c09a57556aa0d41ba439a0577cf69c7338a90ae99e1106ebd20d842861ebda39266a910644e5a41301f62a03bb33cc4555
   languageName: node
   linkType: hard
 
-"@jupyterlab/docmanager@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/docmanager@npm:4.3.1"
-  dependencies:
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/docregistry": ^4.3.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/statedb": ^4.3.1
-    "@jupyterlab/statusbar": ^4.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+"@jupyterlab/docmanager@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/docmanager@npm:4.3.4"
+  dependencies:
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/docregistry": ^4.3.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/statedb": ^4.3.4
+    "@jupyterlab/statusbar": ^4.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -614,24 +611,24 @@ __metadata:
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: eb0fb801872a1253fd4173b94b46e3e517703e5a8dcb874a477353050556d4aca48c88664a8de9e068a68c49139abe8236691e6eda3835a96599bda707a0f8a1
+  checksum: 5a6c15459a94180e3cc5ae7c023ba98ae043fdac2dffc8c83167b634d001734d0afad5862c85153179c790c4838a57caa394a4631122493351d001f84e2d53a6
   languageName: node
   linkType: hard
 
-"@jupyterlab/docregistry@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/docregistry@npm:4.3.1"
+"@jupyterlab/docregistry@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/docregistry@npm:4.3.4"
   dependencies:
     "@jupyter/ydoc": ^3.0.0
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/codeeditor": ^4.3.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime": ^4.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/codeeditor": ^4.3.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime": ^4.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -640,17 +637,17 @@ __metadata:
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: b5462d864015f40b3a7c712c95969b345cde588b118cdc62fbd6dbf753d7decaf99888969490a0df06e96fb27286f0fa2ca06ea4065f2e9e94093d07c0586d71
+  checksum: da1103a659dfdf90cf040efeccdba6ccd3e33cb898b46b6dc32fc9423280c1c609a45f558cd12646958d9ee0af5f5c0e562880d2c8778df6a4756a1c688765da
   languageName: node
   linkType: hard
 
-"@jupyterlab/documentsearch@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/documentsearch@npm:4.3.1"
+"@jupyterlab/documentsearch@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/documentsearch@npm:4.3.4"
   dependencies:
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/commands": ^2.3.1
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -659,23 +656,23 @@ __metadata:
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: 95f0e3b028c4f4a34a0f01080e76f1eb1cc7fecff52881f5120c494e8d1395ef3025be796973c34cf919dc6ddebd7908babc78e2ac2ac01aa085b095b760cc2d
+  checksum: 5866fb6ba1a14b1a7823bef3418abbc0d8607ec0afa85280ea2c9e05851148a0f72fd18ba62931bd08694bfdaf83753c4df11c8a7e11e73f6de3e8fbe251f769
   languageName: node
   linkType: hard
 
-"@jupyterlab/filebrowser@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/filebrowser@npm:4.3.1"
-  dependencies:
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/docmanager": ^4.3.1
-    "@jupyterlab/docregistry": ^4.3.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/statedb": ^4.3.1
-    "@jupyterlab/statusbar": ^4.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+"@jupyterlab/filebrowser@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/filebrowser@npm:4.3.4"
+  dependencies:
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/docmanager": ^4.3.4
+    "@jupyterlab/docregistry": ^4.3.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/statedb": ^4.3.4
+    "@jupyterlab/statusbar": ^4.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -687,21 +684,21 @@ __metadata:
     "@lumino/virtualdom": ^2.0.2
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: c9cfc581b5ec72288a52933027a3b86faed0871b6961a55fc7c60726d92f4ac3a74e874005d212ccbde863e8ef96423a2491d66371a75e49cb5ed9b45e911f1f
+  checksum: 84d24fff8cd416e9de8e71489714044d05c5af263623d560a4f24605a84e3f48af4ffc9eac134f02c57c7712998242355c8959adc9a270937d54fa07885cb607
   languageName: node
   linkType: hard
 
-"@jupyterlab/lsp@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/lsp@npm:4.3.1"
-  dependencies:
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/codeeditor": ^4.3.1
-    "@jupyterlab/codemirror": ^4.3.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/docregistry": ^4.3.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/translation": ^4.3.1
+"@jupyterlab/lsp@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/lsp@npm:4.3.4"
+  dependencies:
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/codeeditor": ^4.3.4
+    "@jupyterlab/codemirror": ^4.3.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/docregistry": ^4.3.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/translation": ^4.3.4
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
     "@lumino/signaling": ^2.1.3
@@ -710,41 +707,41 @@ __metadata:
     vscode-jsonrpc: ^6.0.0
     vscode-languageserver-protocol: ^3.17.0
     vscode-ws-jsonrpc: ~1.0.2
-  checksum: 9602bb05b18fbea44796917b63e7392ecaab2c1cef4f28ef7dc8a9dcc91bd686cfd1abb1b250cda8fdc864c577aa0157ec9aefb0d977a085c663a41937507636
+  checksum: 478bed4c947d01d1cec5e6afafeb33c3b6a1fe203c0eaab2890dd1f4a920678785c7d4a47dc7627b2a55fdf61744a28e9d289774b8706d8df8600cbb014f2977
   languageName: node
   linkType: hard
 
-"@jupyterlab/nbformat@npm:^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0, @jupyterlab/nbformat@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/nbformat@npm:4.3.1"
+"@jupyterlab/nbformat@npm:^3.0.0 || ^4.0.0-alpha.21 || ^4.0.0, @jupyterlab/nbformat@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/nbformat@npm:4.3.4"
   dependencies:
     "@lumino/coreutils": ^2.2.0
-  checksum: 009ce5f41785e2367a86e1445e30d092c178070ea32e9344c71422dce14fef29810cfcbf4c90476f5634b19f6a117d85d1a7dd53debd61af0469b3c47dd92c8a
+  checksum: 7c2b2bf9ce1632b8d4b0aa415e19c5b25e0fb155457cdd9fed9d7a162e477e728fefdef07d18ac25aa8ac1223534615abbc0e1f7d58c0607dc66326d694a8823
   languageName: node
   linkType: hard
 
 "@jupyterlab/notebook@npm:^4.0.0":
-  version: 4.3.1
-  resolution: "@jupyterlab/notebook@npm:4.3.1"
+  version: 4.3.4
+  resolution: "@jupyterlab/notebook@npm:4.3.4"
   dependencies:
     "@jupyter/ydoc": ^3.0.0
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/cells": ^4.3.1
-    "@jupyterlab/codeeditor": ^4.3.1
-    "@jupyterlab/codemirror": ^4.3.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/docregistry": ^4.3.1
-    "@jupyterlab/documentsearch": ^4.3.1
-    "@jupyterlab/lsp": ^4.3.1
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime": ^4.3.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/settingregistry": ^4.3.1
-    "@jupyterlab/statusbar": ^4.3.1
-    "@jupyterlab/toc": ^6.3.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/cells": ^4.3.4
+    "@jupyterlab/codeeditor": ^4.3.4
+    "@jupyterlab/codemirror": ^4.3.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/docregistry": ^4.3.4
+    "@jupyterlab/documentsearch": ^4.3.4
+    "@jupyterlab/lsp": ^4.3.4
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime": ^4.3.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/settingregistry": ^4.3.4
+    "@jupyterlab/statusbar": ^4.3.4
+    "@jupyterlab/toc": ^6.3.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -757,34 +754,34 @@ __metadata:
     "@lumino/virtualdom": ^2.0.2
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: 4f208119e412492e2b732be316f1a60222d662401c099ecb6f5ec54e5926b66568936b1c91341bb5fcd699570db84ccc4e3fb0117f506f78234b563c54a5514e
+  checksum: 4ee00b85f059cc9fe0cbd747db8566b728e2fe33a79f634a16a913c637e8dffca4a6dc16bdacb94db6fded96ad24770f518768ca64e2717c9f1f9422d6784330
   languageName: node
   linkType: hard
 
-"@jupyterlab/observables@npm:^5.3.1":
-  version: 5.3.1
-  resolution: "@jupyterlab/observables@npm:5.3.1"
+"@jupyterlab/observables@npm:^5.3.4":
+  version: 5.3.4
+  resolution: "@jupyterlab/observables@npm:5.3.4"
   dependencies:
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
     "@lumino/messaging": ^2.0.2
     "@lumino/signaling": ^2.1.3
-  checksum: 2c15097088e06a4f12a9e94f8fb26baec7f9fe86188f9e7b6fb421c655b2acbb5cb95f3abb53b86bb417649523561268789393fa9da7b92fa70d2f123320241e
+  checksum: ff8129e0801da786546091d534ff38a76b786efe59f1a20a928c638e7b0354dde5d871c59cece1df598731bff3fac9fe527b228a7da44430d22c9b1a7683569b
   languageName: node
   linkType: hard
 
-"@jupyterlab/outputarea@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/outputarea@npm:4.3.1"
-  dependencies:
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime": ^4.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/translation": ^4.3.1
+"@jupyterlab/outputarea@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/outputarea@npm:4.3.4"
+  dependencies:
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime": ^4.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/translation": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -792,65 +789,65 @@ __metadata:
     "@lumino/properties": ^2.0.2
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
-  checksum: 9bbbcd82148c68b63531f1b671229d0f4394afe3455afbc8808214d5d87b262a45a1f689dbcce0eaec1d6d2f012554e382adbc99824f9e36cce211b867454e4e
+  checksum: 4cc6c65af6e14838958a91f8f0a113e073426612503610979ea48a407ab6ceabd2e9faaab638f89a7e2a12b2d925440617589cb6d043767bad3f510ab9fa6903
   languageName: node
   linkType: hard
 
-"@jupyterlab/rendermime-interfaces@npm:^3.11.1":
-  version: 3.11.1
-  resolution: "@jupyterlab/rendermime-interfaces@npm:3.11.1"
+"@jupyterlab/rendermime-interfaces@npm:^3.11.4":
+  version: 3.11.4
+  resolution: "@jupyterlab/rendermime-interfaces@npm:3.11.4"
   dependencies:
     "@lumino/coreutils": ^1.11.0 || ^2.2.0
     "@lumino/widgets": ^1.37.2 || ^2.5.0
-  checksum: 296e3f4ebcdd760ebf503b6c9467a2f56ed6e19108c7f319a90cff32e0585b0a5147c982bc79a573fe1920e3bc4c6183d8d4e939659ed857baa2b12587061c57
+  checksum: c7d534b97bebeb7122418148469f66322e821bac7baba6952fe4f26fdf2b6965b090dbfd61f2a5fe2174f83e4eaaa3854c7e49d417430a91273da1d93d2a2bdb
   languageName: node
   linkType: hard
 
-"@jupyterlab/rendermime@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/rendermime@npm:4.3.1"
-  dependencies:
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/translation": ^4.3.1
+"@jupyterlab/rendermime@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/rendermime@npm:4.3.4"
+  dependencies:
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/translation": ^4.3.4
     "@lumino/coreutils": ^2.2.0
     "@lumino/messaging": ^2.0.2
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
     lodash.escape: ^4.0.1
-  checksum: 8ded08cbed91d983289f20e21d05d555888f3e19a83e6cbc8f4d72748345dfec1017de63da1644f82368f2a591eac6dae15aa6197b295e4242f5d84965566791
+  checksum: 3097e6eb133403b6cf52a8021612949ebdedde21559a23570e2241109840a98531886ff5c6dca217a8afe62e9e3229fa049bb4711bba524e2aa9e7ea3e96eaeb
   languageName: node
   linkType: hard
 
-"@jupyterlab/services@npm:^7.3.1":
-  version: 7.3.1
-  resolution: "@jupyterlab/services@npm:7.3.1"
+"@jupyterlab/services@npm:^7.3.4":
+  version: 7.3.4
+  resolution: "@jupyterlab/services@npm:7.3.4"
   dependencies:
     "@jupyter/ydoc": ^3.0.0
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/settingregistry": ^4.3.1
-    "@jupyterlab/statedb": ^4.3.1
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/settingregistry": ^4.3.4
+    "@jupyterlab/statedb": ^4.3.4
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
     "@lumino/polling": ^2.1.3
     "@lumino/properties": ^2.0.2
     "@lumino/signaling": ^2.1.3
     ws: ^8.11.0
-  checksum: d1d80fa50254fa0f5a8a7565355b57f24d1db19d414727688a6dc2c879a24812d7d660e585ab0188c0923689283195eb3070a850bb2c26c449da624db006c839
+  checksum: e962b30171ce94c6d9e60d8d06169fd6e7aa9178804b8e14e539dabac6bc04ac29a257be7b8a82c3b479291659738a55da73e2080c6dea3d986bbcc6f4e00850
   languageName: node
   linkType: hard
 
-"@jupyterlab/settingregistry@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/settingregistry@npm:4.3.1"
+"@jupyterlab/settingregistry@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/settingregistry@npm:4.3.4"
   dependencies:
-    "@jupyterlab/nbformat": ^4.3.1
-    "@jupyterlab/statedb": ^4.3.1
+    "@jupyterlab/nbformat": ^4.3.4
+    "@jupyterlab/statedb": ^4.3.4
     "@lumino/commands": ^2.3.1
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -860,28 +857,28 @@ __metadata:
     json5: ^2.2.3
   peerDependencies:
     react: ">=16"
-  checksum: c4f8fcedba9c524004fe919129242cdcbe1661ce7e71c2eb8bfe131ebe6e625367e1c05866b7326046599e4a7668bc07e303e526d87b960846d72457ea4defbd
+  checksum: e6e89174535d10268d70f9c5731bbb1ee6614d8cf87a73d5c4c3b40e6d051ecebb03ec23c508132fe3714473a0667b337674db07759d487b2fb679ca99fd8f35
   languageName: node
   linkType: hard
 
-"@jupyterlab/statedb@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/statedb@npm:4.3.1"
+"@jupyterlab/statedb@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/statedb@npm:4.3.4"
   dependencies:
     "@lumino/commands": ^2.3.1
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
     "@lumino/properties": ^2.0.2
     "@lumino/signaling": ^2.1.3
-  checksum: 781798f6107bcf1caae1f6f7714583145deba95d8c4a10a5a3adeabd6351f1505af60ae2a5b18d7698ba9584c68241c700209e6be48c4b625fd2c5d12254f726
+  checksum: dfb6e3904ca8898bf69d188448559b7356fdac8e579f8214779be7ba709db82372dc2836f245ff3f9c3ff8e382fa82abd354613e5cd89c60348b3d4f7597bf1c
   languageName: node
   linkType: hard
 
-"@jupyterlab/statusbar@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/statusbar@npm:4.3.1"
+"@jupyterlab/statusbar@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/statusbar@npm:4.3.4"
   dependencies:
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
@@ -889,56 +886,56 @@ __metadata:
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: 75edb4de39051c29bc37ec36379f42ae1ce711648a775202582860f70146872efdec8ea6cb08fea556adc7a10ee1533b29bdebf4acde5696aa5bacefae71eaa2
+  checksum: d923c9d5ac724197151a6b127c609f9711dfacf3e1ea4a0c73df166238d9b561d5dfaa6762fc24b0e2ae02500d9062e729716edc17ebb02f4d5fc4f4ceab3d8f
   languageName: node
   linkType: hard
 
-"@jupyterlab/toc@npm:^6.3.1":
-  version: 6.3.1
-  resolution: "@jupyterlab/toc@npm:6.3.1"
+"@jupyterlab/toc@npm:^6.3.4":
+  version: 6.3.4
+  resolution: "@jupyterlab/toc@npm:6.3.4"
   dependencies:
     "@jupyter/react-components": ^0.16.6
-    "@jupyterlab/apputils": ^4.4.1
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/docregistry": ^4.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime": ^4.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/translation": ^4.3.1
-    "@jupyterlab/ui-components": ^4.3.1
+    "@jupyterlab/apputils": ^4.4.4
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/docregistry": ^4.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime": ^4.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/translation": ^4.3.4
+    "@jupyterlab/ui-components": ^4.3.4
     "@lumino/coreutils": ^2.2.0
     "@lumino/disposable": ^2.1.3
     "@lumino/messaging": ^2.0.2
     "@lumino/signaling": ^2.1.3
     "@lumino/widgets": ^2.5.0
     react: ^18.2.0
-  checksum: dfa1edf4b5afbc2fefde9cf2914e335fa70443729183e2e1af69a0983e1411f8f152b81677c74f67866d38ee20b6e1af2eab281f32b4f8cdee4fa550e5c98e98
+  checksum: 61120a2bcfda7fbe7cef1b5f19291c16620fcee27b82dd7a7dba5c8217440a912ed3b6093c9d683bd87b06a37d57bacf158a300d0826dcbd000351abb9055a92
   languageName: node
   linkType: hard
 
-"@jupyterlab/translation@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/translation@npm:4.3.1"
+"@jupyterlab/translation@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/translation@npm:4.3.4"
   dependencies:
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/services": ^7.3.1
-    "@jupyterlab/statedb": ^4.3.1
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/services": ^7.3.4
+    "@jupyterlab/statedb": ^4.3.4
     "@lumino/coreutils": ^2.2.0
-  checksum: ecd63f816784ef6feffecdaa85903dba9f49cf65b5e5b9c664bb115a4dcb163d3701a56b4199dafb5c5eed986074ccd1da87a92450e11af5dbea99c66e662874
+  checksum: c2b386c55aa92ff5a463accf7a79ffd3781ba99ab8c9077c76276922ba6c9b55a8d85881d48f5a309970eec89f7ef1c04536b05caacc6b92aa061466a509759d
   languageName: node
   linkType: hard
 
-"@jupyterlab/ui-components@npm:^4.3.1":
-  version: 4.3.1
-  resolution: "@jupyterlab/ui-components@npm:4.3.1"
+"@jupyterlab/ui-components@npm:^4.3.4":
+  version: 4.3.4
+  resolution: "@jupyterlab/ui-components@npm:4.3.4"
   dependencies:
     "@jupyter/react-components": ^0.16.6
     "@jupyter/web-components": ^0.16.6
-    "@jupyterlab/coreutils": ^6.3.1
-    "@jupyterlab/observables": ^5.3.1
-    "@jupyterlab/rendermime-interfaces": ^3.11.1
-    "@jupyterlab/translation": ^4.3.1
+    "@jupyterlab/coreutils": ^6.3.4
+    "@jupyterlab/observables": ^5.3.4
+    "@jupyterlab/rendermime-interfaces": ^3.11.4
+    "@jupyterlab/translation": ^4.3.4
     "@lumino/algorithm": ^2.0.2
     "@lumino/commands": ^2.3.1
     "@lumino/coreutils": ^2.2.0
@@ -956,7 +953,7 @@ __metadata:
     typestyle: ^2.0.4
   peerDependencies:
     react: ^18.2.0
-  checksum: 8a9cb38b5e62cc3ca6feab3db3444b99ee9a436859358f0c67bb61518a9827c5e4daff008536e99cbd4d34c0086633d07e3212a79a83ab794c98f0243a9bdabf
+  checksum: 32184159fcf043d9c640135e0057031d4f9c9b189cc552c0c8345a7fc8b1c34b4beef87603651bd2043cc3616c4834c2092f47657d2a7bc0bdd0168d3bf0029b
   languageName: node
   linkType: hard
 
@@ -990,14 +987,14 @@ __metadata:
   linkType: hard
 
 "@lezer/generator@npm:^1.7.0":
-  version: 1.7.1
-  resolution: "@lezer/generator@npm:1.7.1"
+  version: 1.7.2
+  resolution: "@lezer/generator@npm:1.7.2"
   dependencies:
     "@lezer/common": ^1.1.0
     "@lezer/lr": ^1.3.0
   bin:
     lezer-generator: src/lezer-generator.cjs
-  checksum: e46df5a31252fb036ea17fce820acdf47672bb5405b2a38e26a430182b9a50b8513fde37d9a43d8334cde3bb2f2106ce7a5ab1a01e244876ce3217c4db59e627
+  checksum: b5d282c7c749d816f373478ebebbcad839125918ac2038d870d880fc0fb1c932b278b3652beb5854d9a0b5aa8e1a9ba24f8359c5ce4c2b9a5393f85c395a2a91
   languageName: node
   linkType: hard
 
@@ -1033,13 +1030,13 @@ __metadata:
   linkType: hard
 
 "@lezer/javascript@npm:^1.0.0":
-  version: 1.4.19
-  resolution: "@lezer/javascript@npm:1.4.19"
+  version: 1.4.21
+  resolution: "@lezer/javascript@npm:1.4.21"
   dependencies:
     "@lezer/common": ^1.2.0
     "@lezer/highlight": ^1.1.3
     "@lezer/lr": ^1.3.0
-  checksum: e305680dea6659570b88eded0d03eba3d33bb8860f8646b457798da955742916dd9cbe17fe6dd867bdb7767ef6c00717aadd45e520ee0b416bdc5e39046e6459
+  checksum: 5ff9edaf53fe399d5e1c0c2196837325ca5cf81b59fda546e8ae81a4748f7cbcc4d258202fe77bbb3d5d9561ce8fb2b79cb87f0922c5f5d1117eb6f545fc1055
   languageName: node
   linkType: hard
 
@@ -1085,13 +1082,13 @@ __metadata:
   linkType: hard
 
 "@lezer/python@npm:^1.1.4":
-  version: 1.1.14
-  resolution: "@lezer/python@npm:1.1.14"
+  version: 1.1.15
+  resolution: "@lezer/python@npm:1.1.15"
   dependencies:
     "@lezer/common": ^1.2.0
     "@lezer/highlight": ^1.0.0
     "@lezer/lr": ^1.0.0
-  checksum: 1608187f698e972d11b340dfdfd79e15b1359641e386e386befd37d5e5839620b45a5a39c5616792a24da29ef1d99d11ea0dad52b9617f1767e7ea6a11c2fed3
+  checksum: 106ef0a56d0ccac6bcba02ee91770f6cd96fe599edc061300e2d0556ae53a68d32fb8f94e8baeaa34f06127dcb39513425e180966b1fd5cba78da2b78e9ee3c3
   languageName: node
   linkType: hard
 
@@ -1267,6 +1264,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@marijn/find-cluster-break@npm:^1.0.0":
+  version: 1.0.2
+  resolution: "@marijn/find-cluster-break@npm:1.0.2"
+  checksum: 0d836de25e04d58325813401ef3c2d34caf040da985a5935fcbc9d84e7b47a21bdb15f57d70c2bf0960bd29ed3dbbb1afd00cdd0fc4fafbee7fd0ffe7d508ae1
+  languageName: node
+  linkType: hard
+
 "@microsoft/fast-colors@npm:^5.3.1":
   version: 5.3.1
   resolution: "@microsoft/fast-colors@npm:5.3.1"
@@ -1303,8 +1307,8 @@ __metadata:
   linkType: hard
 
 "@rjsf/core@npm:^5.13.4":
-  version: 5.22.4
-  resolution: "@rjsf/core@npm:5.22.4"
+  version: 5.23.2
+  resolution: "@rjsf/core@npm:5.23.2"
   dependencies:
     lodash: ^4.17.21
     lodash-es: ^4.17.21
@@ -1312,15 +1316,15 @@ __metadata:
     nanoid: ^3.3.7
     prop-types: ^15.8.1
   peerDependencies:
-    "@rjsf/utils": ^5.22.x
+    "@rjsf/utils": ^5.23.x
     react: ^16.14.0 || >=17
-  checksum: cc2d6b51959be277f727a0c8398c699bc4068cb63f3f096ddd051c35e5ba685beb5745c281d81e3906f1c92329e9468bc35f81244e48026794318c98b884dac7
+  checksum: 36b2505afd5402368a31a06a4b9d2264f63cab9766f2060cd3c3ecf8b4c08fc7fc8b1b82dd00788f357a2ca649d76c5b6e324152572dbf333bd2b93a0bcc99fd
   languageName: node
   linkType: hard
 
 "@rjsf/utils@npm:^5.13.4":
-  version: 5.22.4
-  resolution: "@rjsf/utils@npm:5.22.4"
+  version: 5.23.2
+  resolution: "@rjsf/utils@npm:5.23.2"
   dependencies:
     json-schema-merge-allof: ^0.8.1
     jsonpointer: ^5.0.1
@@ -1329,7 +1333,7 @@ __metadata:
     react-is: ^18.2.0
   peerDependencies:
     react: ^16.14.0 || >=17
-  checksum: 7dddc74b910fb7b87ebddc564879126b100f14eee853c0fbc390faa6c5f944a70023f34925a360aa92915f4e9c56bccf57141db9fe7b3c6a97348d886794bab8
+  checksum: 16980013258bab7accaff961c533e4bb8e3326c37a84670a7667b2a10c1ca395451eb51a6cf819ccbafb1aa8838df325ff1f314b410bb186fef98856135e1a06
   languageName: node
   linkType: hard
 
@@ -1368,28 +1372,28 @@ __metadata:
   linkType: hard
 
 "@types/node@npm:*":
-  version: 22.9.0
-  resolution: "@types/node@npm:22.9.0"
+  version: 22.10.2
+  resolution: "@types/node@npm:22.10.2"
   dependencies:
-    undici-types: ~6.19.8
-  checksum: c014eb3b8a110f1b87b614a40ef288d13e6b08ae9d5dafbd38951a2eebc24d352dc55330ed9d00c97ee9e64483c3cc14c4aa914c5df7ca7b9eaa1a30b2340dbd
+    undici-types: ~6.20.0
+  checksum: b22401e6e7d1484e437d802c72f5560e18100b1257b9ad0574d6fe05bebe4dbcb620ea68627d1f1406775070d29ace8b6b51f57e7b1c7b8bafafe6da7f29c843
   languageName: node
   linkType: hard
 
 "@types/prop-types@npm:*":
-  version: 15.7.13
-  resolution: "@types/prop-types@npm:15.7.13"
-  checksum: 8935cad87c683c665d09a055919d617fe951cb3b2d5c00544e3a913f861a2bd8d2145b51c9aa6d2457d19f3107ab40784c40205e757232f6a80cc8b1c815513c
+  version: 15.7.14
+  resolution: "@types/prop-types@npm:15.7.14"
+  checksum: d0c5407b9ccc3dd5fae0ccf9b1007e7622ba5e6f1c18399b4f24dff33619d469da4b9fa918a374f19dc0d9fe6a013362aab0b844b606cfc10676efba3f5f736d
   languageName: node
   linkType: hard
 
 "@types/react@npm:^18.0.26":
-  version: 18.3.12
-  resolution: "@types/react@npm:18.3.12"
+  version: 18.3.17
+  resolution: "@types/react@npm:18.3.17"
   dependencies:
     "@types/prop-types": "*"
     csstype: ^3.0.2
-  checksum: 4ab1577a8c2105a5e316536f724117c90eee5f4bd5c137fc82a2253d8c1fd299dedaa07e8dfc95d6e2f04a4be3cb8b0e1b06098c6233ebd55c508d88099395b7
+  checksum: 8107f6f5cc8706a3814e6c927e135ce0c7b40a6d9ae2b8dfb071fee03c6f714456041ecdf92dece599da0db8be7f56f6dc6353d4701f47a04772c7ec0cbb0b59
   languageName: node
   linkType: hard
 
@@ -1411,7 +1415,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.12.1":
+"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1":
   version: 1.14.1
   resolution: "@webassemblyjs/ast@npm:1.14.1"
   dependencies:
@@ -1497,7 +1501,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wasm-edit@npm:^1.12.1":
+"@webassemblyjs/wasm-edit@npm:^1.14.1":
   version: 1.14.1
   resolution: "@webassemblyjs/wasm-edit@npm:1.14.1"
   dependencies:
@@ -1538,7 +1542,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.12.1":
+"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1":
   version: 1.14.1
   resolution: "@webassemblyjs/wasm-parser@npm:1.14.1"
   dependencies:
@@ -1717,16 +1721,16 @@ __metadata:
   linkType: hard
 
 "browserslist@npm:^4.24.0":
-  version: 4.24.2
-  resolution: "browserslist@npm:4.24.2"
+  version: 4.24.3
+  resolution: "browserslist@npm:4.24.3"
   dependencies:
-    caniuse-lite: ^1.0.30001669
-    electron-to-chromium: ^1.5.41
-    node-releases: ^2.0.18
+    caniuse-lite: ^1.0.30001688
+    electron-to-chromium: ^1.5.73
+    node-releases: ^2.0.19
     update-browserslist-db: ^1.1.1
   bin:
     browserslist: cli.js
-  checksum: cf64085f12132d38638f38937a255edb82c7551b164a98577b055dd79719187a816112f7b97b9739e400c4954cd66479c0d7a843cb816e346f4795dc24fd5d97
+  checksum: 016efc9953350e3a7212edcfdd72210cb33b339c1a974a77c0715eb67d23d7e5cd0a073ce1c801ab09235d8c213425ca51b92d41bbb829b833872b45f885fe7c
   languageName: node
   linkType: hard
 
@@ -1737,10 +1741,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"caniuse-lite@npm:^1.0.30001669":
-  version: 1.0.30001680
-  resolution: "caniuse-lite@npm:1.0.30001680"
-  checksum: 2641d2b18c5ab0a6663cb350c5adc81e5ede1a7677d1c7518a8053ada87bf6f206419e1820a2608f76fa5e4f7bea327cbe47df423783e571569a88c0ea645270
+"caniuse-lite@npm:^1.0.30001688":
+  version: 1.0.30001690
+  resolution: "caniuse-lite@npm:1.0.30001690"
+  checksum: f2c1b595f15d8de4d9ccd155d61ac9f00ac62f1515870505a0186266fd52aef169fcddc90d8a4814e52b77107244806466fadc2c216662f23f1022a430e735ee
   languageName: node
   linkType: hard
 
@@ -1980,10 +1984,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"electron-to-chromium@npm:^1.5.41":
-  version: 1.5.63
-  resolution: "electron-to-chromium@npm:1.5.63"
-  checksum: aa3d0a48753af052158ef3e46d65042b91f1934a4ab02732a0ce27429ba989dea7e4badbf991e58dff8acf5e64b967a964b61985c05ce45e03457c4f917a06f8
+"electron-to-chromium@npm:^1.5.73":
+  version: 1.5.74
+  resolution: "electron-to-chromium@npm:1.5.74"
+  checksum: e93db5fdfafe927dcefe2ffef5b8670ae0c340eb8a4fe176918ec8efa53c37aff0ba4c3649c234950af2809d16a186ffead64dd921c9021d629658bf6d17c21f
   languageName: node
   linkType: hard
 
@@ -2298,12 +2302,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"is-core-module@npm:^2.13.0":
-  version: 2.15.1
-  resolution: "is-core-module@npm:2.15.1"
+"is-core-module@npm:^2.16.0":
+  version: 2.16.0
+  resolution: "is-core-module@npm:2.16.0"
   dependencies:
     hasown: ^2.0.2
-  checksum: df134c168115690724b62018c37b2f5bba0d5745fa16960b329c5a00883a8bea6a5632fdb1e3efcce237c201826ba09f93197b7cd95577ea56b0df335be23633
+  checksum: 98aa14eaee864c2e86fff4e08813e0da3ca7ac38dd0a43b9f10aeec48bfbc4827e2677349adb626f66921caef7fa9e2745c80b8b7cafe8ac24d46dc8124cc216
   languageName: node
   linkType: hard
 
@@ -2440,15 +2444,15 @@ __metadata:
   linkType: hard
 
 "lib0@npm:^0.2.85, lib0@npm:^0.2.98":
-  version: 0.2.98
-  resolution: "lib0@npm:0.2.98"
+  version: 0.2.99
+  resolution: "lib0@npm:0.2.99"
   dependencies:
     isomorphic.js: ^0.2.4
   bin:
     0ecdsa-generate-keypair: bin/0ecdsa-generate-keypair.js
     0gentesthtml: bin/gentesthtml.js
     0serve: bin/0serve.js
-  checksum: 8d17060deb4ffb73f825e634e6543c024d27dad589a7ce2e6334af34b36d4441434edabf3716930f6c5e1c32c5f3e867b8c1b922c1cc51b22469f281292e423b
+  checksum: 240e91bd3098daf310a320f0f662b1532787329a070b7522a1f784358f915eedcd4b57e3c12749f257a4104939f6eb2af3f90311adadc1a01bfc05ca7de71da7
   languageName: node
   linkType: hard
 
@@ -2532,11 +2536,11 @@ __metadata:
   linkType: hard
 
 "markdown-to-jsx@npm:^7.4.1":
-  version: 7.6.2
-  resolution: "markdown-to-jsx@npm:7.6.2"
+  version: 7.7.2
+  resolution: "markdown-to-jsx@npm:7.7.2"
   peerDependencies:
     react: ">= 0.14.0"
-  checksum: 0ae24df87678c880d97d886461eca3d3cd69a2f418c04535bbabc48e9a357c6842e87e9af7d11fbeb0b86682a42126c6c568fe7ae5dae7a8d1f347932a22d344
+  checksum: 8ee7bed1d57fa105fe8357e0b0f703b83dc129170a1bdad6ece426d5789c614a18a8ab9e51af25cff9d6c38505ea1c011cecc1f17c85c4332f7d4951bd4ccbbf
   languageName: node
   linkType: hard
 
@@ -2601,11 +2605,11 @@ __metadata:
   linkType: hard
 
 "nanoid@npm:^3.3.7":
-  version: 3.3.7
-  resolution: "nanoid@npm:3.3.7"
+  version: 3.3.8
+  resolution: "nanoid@npm:3.3.8"
   bin:
     nanoid: bin/nanoid.cjs
-  checksum: d36c427e530713e4ac6567d488b489a36582ef89da1d6d4e3b87eded11eb10d7042a877958c6f104929809b2ab0bafa17652b076cdf84324aa75b30b722204f2
+  checksum: dfe0adbc0c77e9655b550c333075f51bb28cfc7568afbf3237249904f9c86c9aaaed1f113f0fddddba75673ee31c758c30c43d4414f014a52a7a626efc5958c9
   languageName: node
   linkType: hard
 
@@ -2616,10 +2620,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"node-releases@npm:^2.0.18":
-  version: 2.0.18
-  resolution: "node-releases@npm:2.0.18"
-  checksum: ef55a3d853e1269a6d6279b7692cd6ff3e40bc74947945101138745bfdc9a5edabfe72cb19a31a8e45752e1910c4c65c77d931866af6357f242b172b7283f5b3
+"node-releases@npm:^2.0.19":
+  version: 2.0.19
+  resolution: "node-releases@npm:2.0.19"
+  checksum: 917dbced519f48c6289a44830a0ca6dc944c3ee9243c468ebd8515a41c97c8b2c256edb7f3f750416bc37952cc9608684e6483c7b6c6f39f6bd8d86c52cfe658
   languageName: node
   linkType: hard
 
@@ -2732,15 +2736,15 @@ __metadata:
   linkType: hard
 
 "postcss-modules-local-by-default@npm:^4.0.5":
-  version: 4.1.0
-  resolution: "postcss-modules-local-by-default@npm:4.1.0"
+  version: 4.2.0
+  resolution: "postcss-modules-local-by-default@npm:4.2.0"
   dependencies:
     icss-utils: ^5.0.0
     postcss-selector-parser: ^7.0.0
     postcss-value-parser: ^4.1.0
   peerDependencies:
     postcss: ^8.1.0
-  checksum: 64ac4803c21dd82e227179cf0a8489c645ea99a8c514475da028c9afe5d5b915485d00d8efbe94295d688a23a172965cc15f20d550168d1fed272dbdbbe053f0
+  checksum: 720d145453f82ad5f1c1d0ff7386d64722f0812808e4132e573c1a49909745e109fcce3792a0b0cb18770dbeb3d9741867e81c698dc8353a18bc664b7d6d9533
   languageName: node
   linkType: hard
 
@@ -2924,28 +2928,28 @@ __metadata:
   linkType: hard
 
 "resolve@npm:^1.20.0":
-  version: 1.22.8
-  resolution: "resolve@npm:1.22.8"
+  version: 1.22.9
+  resolution: "resolve@npm:1.22.9"
   dependencies:
-    is-core-module: ^2.13.0
+    is-core-module: ^2.16.0
     path-parse: ^1.0.7
     supports-preserve-symlinks-flag: ^1.0.0
   bin:
     resolve: bin/resolve
-  checksum: f8a26958aa572c9b064562750b52131a37c29d072478ea32e129063e2da7f83e31f7f11e7087a18225a8561cfe8d2f0df9dbea7c9d331a897571c0a2527dbb4c
+  checksum: dd7da6c4ccc60bb4884f636b987102bb1cae6c486f7172361719a7f52769660c9a83a2d3520bef71d91f3da27e4fb8a40404c63ee4372a15863f3208bef5af67
   languageName: node
   linkType: hard
 
 "resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>":
-  version: 1.22.8
-  resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin<compat/resolve>::version=1.22.8&hash=c3c19d"
+  version: 1.22.9
+  resolution: "resolve@patch:resolve@npm%3A1.22.9#~builtin<compat/resolve>::version=1.22.9&hash=c3c19d"
   dependencies:
-    is-core-module: ^2.13.0
+    is-core-module: ^2.16.0
     path-parse: ^1.0.7
     supports-preserve-symlinks-flag: ^1.0.0
   bin:
     resolve: bin/resolve
-  checksum: 5479b7d431cacd5185f8db64bfcb7286ae5e31eb299f4c4f404ad8aa6098b77599563ac4257cb2c37a42f59dfc06a1bec2bcf283bb448f319e37f0feb9a09847
+  checksum: 8db5c0f16ab65f58c036cb5be0964605c97c29b9fdf1e20f298ec027e2a4fd96ad0413aa14f6e761629956dc552cd478c2f9b6c5a07e37e4c85209090162501e
   languageName: node
   linkType: hard
 
@@ -2997,7 +3001,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0":
+"schema-utils@npm:^3.0.0, schema-utils@npm:^3.2.0":
   version: 3.3.0
   resolution: "schema-utils@npm:3.3.0"
   dependencies:
@@ -3008,15 +3012,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"schema-utils@npm:^4.0.0":
-  version: 4.2.0
-  resolution: "schema-utils@npm:4.2.0"
+"schema-utils@npm:^4.0.0, schema-utils@npm:^4.3.0":
+  version: 4.3.0
+  resolution: "schema-utils@npm:4.3.0"
   dependencies:
     "@types/json-schema": ^7.0.9
     ajv: ^8.9.0
     ajv-formats: ^2.1.1
     ajv-keywords: ^5.1.0
-  checksum: 26a0463d47683258106e6652e9aeb0823bf0b85843039e068b57da1892f7ae6b6b1094d48e9ed5ba5cbe9f7166469d880858b9d91abe8bd249421eb813850cde
+  checksum: 3dbd9056727c871818eaf3cabeeb5c9e173ae2b17bbf2a9c7a2e49c220fa1a580e44df651c876aea3b4926cecf080730a39e28202cb63f2b68d99872b49cd37a
   languageName: node
   linkType: hard
 
@@ -3038,7 +3042,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"serialize-javascript@npm:^6.0.1":
+"serialize-javascript@npm:^6.0.2":
   version: 6.0.2
   resolution: "serialize-javascript@npm:6.0.2"
   dependencies:
@@ -3183,14 +3187,14 @@ __metadata:
   linkType: hard
 
 "terser-webpack-plugin@npm:^5.3.10, terser-webpack-plugin@npm:^5.3.7":
-  version: 5.3.10
-  resolution: "terser-webpack-plugin@npm:5.3.10"
+  version: 5.3.11
+  resolution: "terser-webpack-plugin@npm:5.3.11"
   dependencies:
-    "@jridgewell/trace-mapping": ^0.3.20
+    "@jridgewell/trace-mapping": ^0.3.25
     jest-worker: ^27.4.5
-    schema-utils: ^3.1.1
-    serialize-javascript: ^6.0.1
-    terser: ^5.26.0
+    schema-utils: ^4.3.0
+    serialize-javascript: ^6.0.2
+    terser: ^5.31.1
   peerDependencies:
     webpack: ^5.1.0
   peerDependenciesMeta:
@@ -3200,13 +3204,13 @@ __metadata:
       optional: true
     uglify-js:
       optional: true
-  checksum: bd6e7596cf815f3353e2a53e79cbdec959a1b0276f5e5d4e63e9d7c3c5bb5306df567729da287d1c7b39d79093e56863c569c42c6c24cc34c76aa313bd2cbcea
+  checksum: c84c005d4041ad2e2eed0c9059b52a50ffd27f1e2afca7d34864a2b4b2bb1295405bc7578eeb25bae732e358339954d8cb6fbf6d83df52e8aa9333e8bf409ebe
   languageName: node
   linkType: hard
 
-"terser@npm:^5.26.0":
-  version: 5.36.0
-  resolution: "terser@npm:5.36.0"
+"terser@npm:^5.31.1":
+  version: 5.37.0
+  resolution: "terser@npm:5.37.0"
   dependencies:
     "@jridgewell/source-map": ^0.3.3
     acorn: ^8.8.2
@@ -3214,7 +3218,7 @@ __metadata:
     source-map-support: ~0.5.20
   bin:
     terser: bin/terser
-  checksum: 489afd31901a2b170f7766948a3aa0e25da0acb41e9e35bd9f9b4751dfa2fc846e485f6fb9d34f0839a96af77f675b5fbf0a20c9aa54e0b8d7c219cf0b55e508
+  checksum: 70c06a8ce1288ff4370a7e481beb6fc8b22fc4995371479f49df1552aa9cf8e794ace66e1da6e87057eda1745644311213f5043bda9a06cf55421eff68b3ac06
   languageName: node
   linkType: hard
 
@@ -3235,22 +3239,22 @@ __metadata:
   linkType: hard
 
 "typescript@npm:^5.0.2":
-  version: 5.6.3
-  resolution: "typescript@npm:5.6.3"
+  version: 5.7.2
+  resolution: "typescript@npm:5.7.2"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: ba302f8822777ebefb28b554105f3e074466b671e7444ec6b75dadc008a62f46f373d9e57ceced1c433756d06c8b7dc569a7eefdf3a9573122a49205ff99021a
+  checksum: b55300c4cefee8ee380d14fa9359ccb41ff8b54c719f6bc49b424899d662a5ce62ece390ce769568c7f4d14af844085255e63788740084444eb12ef423b13433
   languageName: node
   linkType: hard
 
 "typescript@patch:typescript@^5.0.2#~builtin<compat/typescript>":
-  version: 5.6.3
-  resolution: "typescript@patch:typescript@npm%3A5.6.3#~builtin<compat/typescript>::version=5.6.3&hash=85af82"
+  version: 5.7.2
+  resolution: "typescript@patch:typescript@npm%3A5.7.2#~builtin<compat/typescript>::version=5.7.2&hash=85af82"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: ade87bce2363ee963eed0e4ca8a312ea02c81873ebd53609bc3f6dc0a57f6e61ad7e3fb8cbb7f7ab8b5081cbee801b023f7c4823ee70b1c447eae050e6c7622b
+  checksum: 803430c6da2ba73c25a21880d8d4f08a56d9d2444e6db2ea949ac4abceeece8e4a442b7b9b585db7d8a0b47ebda2060e45fe8ee8b8aca23e27ec1d4844987ee6
   languageName: node
   linkType: hard
 
@@ -3264,10 +3268,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"undici-types@npm:~6.19.8":
-  version: 6.19.8
-  resolution: "undici-types@npm:6.19.8"
-  checksum: de51f1b447d22571cf155dfe14ff6d12c5bdaec237c765085b439c38ca8518fc360e88c70f99469162bf2e14188a7b0bcb06e1ed2dc031042b984b0bb9544017
+"undici-types@npm:~6.20.0":
+  version: 6.20.0
+  resolution: "undici-types@npm:6.20.0"
+  checksum: b7bc50f012dc6afbcce56c9fd62d7e86b20a62ff21f12b7b5cbf1973b9578d90f22a9c7fe50e638e96905d33893bf2f9f16d98929c4673c2480de05c6c96ea8b
   languageName: node
   linkType: hard
 
@@ -3490,14 +3494,14 @@ __metadata:
   linkType: hard
 
 "webpack@npm:^5.76.1":
-  version: 5.96.1
-  resolution: "webpack@npm:5.96.1"
+  version: 5.97.1
+  resolution: "webpack@npm:5.97.1"
   dependencies:
     "@types/eslint-scope": ^3.7.7
     "@types/estree": ^1.0.6
-    "@webassemblyjs/ast": ^1.12.1
-    "@webassemblyjs/wasm-edit": ^1.12.1
-    "@webassemblyjs/wasm-parser": ^1.12.1
+    "@webassemblyjs/ast": ^1.14.1
+    "@webassemblyjs/wasm-edit": ^1.14.1
+    "@webassemblyjs/wasm-parser": ^1.14.1
     acorn: ^8.14.0
     browserslist: ^4.24.0
     chrome-trace-event: ^1.0.2
@@ -3521,7 +3525,7 @@ __metadata:
       optional: true
   bin:
     webpack: bin/webpack.js
-  checksum: ec3662f64895fae408440a997f87299e374c9d9f911f77b880bab46402f52221c7836bdf101fc2556338d07fc7cb86da50661f944eb1d1041a8361a5b9247876
+  checksum: 649065e2258b495ae41a4088be804b4be2ec07d280aa514ebef43da79caf96fa973d26a08826c3902b5676a098d9b37c589f16be7b4da17b68b08b6c76441196
   languageName: node
   linkType: hard
 
diff --git a/pip/qsharp/__init__.py b/pip/qsharp/__init__.py
index 3b9984cf..d588a68e 100644
--- a/pip/qsharp/__init__.py
+++ b/pip/qsharp/__init__.py
@@ -56,5 +56,5 @@ __all__ = [
     "PauliNoise",
     "DepolarizingNoise",
     "BitFlipNoise",
-    "PhaseFlipNoise"
+    "PhaseFlipNoise",
 ]
diff --git a/pip/qsharp/_native.pyi b/pip/qsharp/_native.pyi
index adef263e..328ccb0f 100644
--- a/pip/qsharp/_native.pyi
+++ b/pip/qsharp/_native.pyi
@@ -103,6 +103,13 @@ class TargetProfile(Enum):
     Describes the unrestricted set of capabilities required to run any Q# program.
     """
 
+class GlobalCallable:
+    """
+    A callable reference that can be invoked with arguments.
+    """
+
+    ...
+
 class Interpreter:
     """A Q# interpreter."""
 
@@ -114,6 +121,7 @@ class Interpreter:
         read_file: Callable[[str], Tuple[str, str]],
         list_directory: Callable[[str], List[Dict[str, str]]],
         resolve_path: Callable[[str, str], str],
+        make_callable: Optional[Callable[[GlobalCallable], None]],
     ) -> None:
         """
         Initializes the Q# interpreter.
@@ -123,6 +131,7 @@ class Interpreter:
         :param read_file: A function that reads a file from the file system.
         :param list_directory: A function that lists the contents of a directory.
         :param resolve_path: A function that joins path segments and normalizes the resulting path.
+        :param make_callable: A function that registers a Q# callable in the in the environment module.
         """
         ...
 
@@ -159,6 +168,22 @@ class Interpreter:
         """
         ...
 
+    def invoke(
+        self,
+        callable: GlobalCallable,
+        args: Any,
+        output_fn: Callable[[Output], None],
+    ) -> Any:
+        """
+        Invokes the callable with the given arguments, converted into the appropriate Q# values.
+        :param callable: The callable to invoke.
+        :param args: The arguments to pass to the callable.
+        :param output_fn: A callback function that will be called with each output.
+        :returns values: A result or runtime errors.
+        :raises QSharpError: If there is an error interpreting the input.
+        """
+        ...
+
     def qir(self, entry_expr: str) -> str:
         """
         Generates QIR from Q# source code.
diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py
index 5bf6ba4f..198b52db 100644
--- a/pip/qsharp/_qsharp.py
+++ b/pip/qsharp/_qsharp.py
@@ -1,7 +1,7 @@
 # Copyright (c) Microsoft Corporation.
 # Licensed under the MIT License.
 
-from . import telemetry_events
+from . import telemetry_events, code
 from ._native import (
     Interpreter,
     TargetProfile,
@@ -9,6 +9,7 @@ from ._native import (
     QSharpError,
     Output,
     Circuit,
+    GlobalCallable,
 )
 from typing import (
     Any,
@@ -24,6 +25,8 @@ from typing import (
 from .estimator._estimator import EstimatorResult, EstimatorParams
 import json
 import os
+import sys
+import types
 from time import monotonic
 
 _interpreter = None
@@ -190,6 +193,25 @@ def init(
                 f"Error reading {qsharp_json}. qsharp.json should exist at the project root and be a valid JSON file."
             ) from e
 
+    # Loop through the environment module and remove any dynamically added attributes that represent
+    # Q# callables. This is necessary to avoid conflicts with the new interpreter instance.
+    keys_to_remove = []
+    for key in code.__dict__:
+        if hasattr(code.__dict__[key], "__qs_gen") or isinstance(
+            code.__dict__[key], types.ModuleType
+        ):
+            keys_to_remove.append(key)
+    for key in keys_to_remove:
+        code.__delattr__(key)
+
+    # Also remove any namespace modules dynamically added to the system.
+    keys_to_remove = []
+    for key in sys.modules:
+        if key.startswith("qsharp.code."):
+            keys_to_remove.append(key)
+    for key in keys_to_remove:
+        sys.modules.__delitem__(key)
+
     _interpreter = Interpreter(
         target_profile,
         language_features,
@@ -198,6 +220,7 @@ def init(
         list_directory,
         resolve,
         fetch_github,
+        _make_callable,
     )
 
     _config = Config(target_profile, language_features, manifest_contents, project_root)
@@ -372,6 +395,56 @@ def eval(
         return results["result"]
 
 
+# Helper function that knows how to create a function that invokes a callable. This will be
+# used by the underlying native code to create functions for callables on the fly that know
+# how to get the currently intitialized global interpreter instance.
+def _make_callable(callable: GlobalCallable, namespace: List[str], callable_name: str):
+    module = code
+    # Create a name that will be used to collect the hierachy of namespace identifiers if they exist and use that
+    # to register created modules with the system.
+    accumulated_namespace = "qsharp.code"
+    accumulated_namespace += "."
+    for name in namespace:
+        accumulated_namespace += name
+        # Use the existing entry, which should already be a module.
+        if hasattr(module, name):
+            module = module.__getattribute__(name)
+        else:
+            # This namespace entry doesn't exist as a module yet, so create it, add it to the environment, and
+            # add it to sys.modules so it supports import properly.
+            new_module = types.ModuleType(accumulated_namespace)
+            module.__setattr__(name, new_module)
+            sys.modules[accumulated_namespace] = new_module
+            module = new_module
+        accumulated_namespace += "."
+
+    def _callable(*args):
+        ipython_helper()
+
+        def callback(output: Output) -> None:
+            if _in_jupyter:
+                try:
+                    display(output)
+                    return
+                except:
+                    # If IPython is not available, fall back to printing the output
+                    pass
+            print(output, flush=True)
+
+        if len(args) == 1:
+            args = args[0]
+        elif len(args) == 0:
+            args = None
+
+        return get_interpreter().invoke(callable, args, callback)
+
+    # Each callable is annotated so that we know it is auto-generated and can be removed on a re-init of the interpreter.
+    _callable.__qs_gen = True
+
+    # Add the callable to the module.
+    module.__setattr__(callable_name, _callable)
+
+
 def run(
     entry_expr: str,
     shots: int,
diff --git a/pip/qsharp/code/__init__.py b/pip/qsharp/code/__init__.py
new file mode 100644
index 00000000..695b54fb
--- /dev/null
+++ b/pip/qsharp/code/__init__.py
@@ -0,0 +1,6 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+"""
+Code module that receives any user-defined Q# callables as Python functions.
+"""
diff --git a/pip/src/interpreter.rs b/pip/src/interpreter.rs
index f540360f..6f1c41a2 100644
--- a/pip/src/interpreter.rs
+++ b/pip/src/interpreter.rs
@@ -11,16 +11,17 @@ use crate::{
     noisy_simulator::register_noisy_simulator_submodule,
 };
 use miette::{Diagnostic, Report};
-use num_bigint::BigUint;
+use num_bigint::{BigInt, BigUint};
 use num_complex::Complex64;
 use pyo3::{
     create_exception,
     exceptions::{PyException, PyValueError},
     prelude::*,
-    types::{PyComplex, PyDict, PyList, PyTuple, PyType},
+    types::{PyComplex, PyDict, PyList, PyString, PyTuple, PyType},
 };
 use qsc::{
-    fir,
+    fir::{self},
+    hir::ty::{Prim, Ty},
     interpret::{
         self,
         output::{Error, Receiver},
@@ -73,6 +74,7 @@ fn _native<'a>(py: Python<'a>, m: &Bound<'a, PyModule>) -> PyResult<()> {
     m.add_class::<Output>()?;
     m.add_class::<StateDumpData>()?;
     m.add_class::<Circuit>()?;
+    m.add_class::<GlobalCallable>()?;
     m.add_function(wrap_pyfunction!(physical_estimates, m)?)?;
     m.add("QSharpError", py.get_type_bound::<QSharpError>())?;
     register_noisy_simulator_submodule(py, m)?;
@@ -213,9 +215,12 @@ impl From<ProgramType> for qsc_qasm3::ProgramType {
     }
 }
 
+#[allow(clippy::struct_field_names)]
 #[pyclass(unsendable)]
 pub(crate) struct Interpreter {
     pub(crate) interpreter: interpret::Interpreter,
+    /// The Python function to call to create a new function wrapping a callable invocation.
+    pub(crate) make_callable: Option<PyObject>,
 }
 
 thread_local! { static PACKAGE_CACHE: Rc<RefCell<PackageCache>> = Rc::default(); }
@@ -225,7 +230,7 @@ thread_local! { static PACKAGE_CACHE: Rc<RefCell<PackageCache>> = Rc::default();
 impl Interpreter {
     #[allow(clippy::too_many_arguments)]
     #[allow(clippy::needless_pass_by_value)]
-    #[pyo3(signature = (target_profile, language_features=None, project_root=None, read_file=None, list_directory=None, resolve_path=None, fetch_github=None))]
+    #[pyo3(signature = (target_profile, language_features=None, project_root=None, read_file=None, list_directory=None, resolve_path=None, fetch_github=None, make_callable=None))]
     #[new]
     /// Initializes a new Q# interpreter.
     pub(crate) fn new(
@@ -237,6 +242,7 @@ impl Interpreter {
         list_directory: Option<PyObject>,
         resolve_path: Option<PyObject>,
         fetch_github: Option<PyObject>,
+        make_callable: Option<PyObject>,
     ) -> PyResult<Self> {
         let target = Into::<Profile>::into(target_profile).into();
 
@@ -278,7 +284,19 @@ impl Interpreter {
             buildable_program.store,
             &buildable_program.user_code_dependencies,
         ) {
-            Ok(interpreter) => Ok(Self { interpreter }),
+            Ok(interpreter) => {
+                if let Some(make_callable) = &make_callable {
+                    // Add any global callables from the user source as Python functions to the environment.
+                    let exported_items = interpreter.user_globals();
+                    for (namespace, name, val) in exported_items {
+                        create_py_callable(py, make_callable, &namespace, &name, val)?;
+                    }
+                }
+                Ok(Self {
+                    interpreter,
+                    make_callable,
+                })
+            }
             Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
         }
     }
@@ -300,7 +318,19 @@ impl Interpreter {
     ) -> PyResult<PyObject> {
         let mut receiver = OptionalCallbackReceiver { callback, py };
         match self.interpreter.eval_fragments(&mut receiver, input) {
-            Ok(value) => Ok(ValueWrapper(value).into_py(py)),
+            Ok(value) => {
+                if let Some(make_callable) = &self.make_callable {
+                    // Get any global callables from the evaluated input and add them to the environment. This will grab
+                    // every callable that was defined in the input and by previous calls that added to the open package.
+                    // This is safe because either the callable will be replaced with itself or a new callable with the
+                    // same name will shadow the previous one, which is the expected behavior.
+                    let new_items = self.interpreter.source_globals();
+                    for (namespace, name, val) in new_items {
+                        create_py_callable(py, make_callable, &namespace, &name, val)?;
+                    }
+                }
+                Ok(ValueWrapper(value).into_py(py))
+            }
             Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
         }
     }
@@ -357,6 +387,56 @@ impl Interpreter {
         }
     }
 
+    #[pyo3(signature=(callable, args=None, callback=None))]
+    fn invoke(
+        &mut self,
+        py: Python,
+        callable: GlobalCallable,
+        args: Option<PyObject>,
+        callback: Option<PyObject>,
+    ) -> PyResult<PyObject> {
+        let mut receiver = OptionalCallbackReceiver { callback, py };
+        let (input_ty, output_ty) = self
+            .interpreter
+            .global_tys(&callable.0)
+            .ok_or(QSharpError::new_err("callable not found"))?;
+
+        // If the types are not supported, we can't convert the arguments or return value.
+        // Check this before trying to convert the arguments, and return an error if the types are not supported.
+        if let Some(ty) = first_unsupported_interop_ty(&input_ty) {
+            return Err(QSharpError::new_err(format!(
+                "unsupported input type: `{ty}`"
+            )));
+        }
+        if let Some(ty) = first_unsupported_interop_ty(&output_ty) {
+            return Err(QSharpError::new_err(format!(
+                "unsupported output type: `{ty}`"
+            )));
+        }
+
+        // Conver the Python arguments to Q# values, treating None as an empty tuple aka `Unit`.
+        let args = if matches!(&input_ty, Ty::Tuple(tup) if tup.is_empty()) {
+            // Special case for unit, where args should be None
+            if args.is_some() {
+                return Err(QSharpError::new_err("expected no arguments"));
+            }
+            Value::unit()
+        } else {
+            let Some(args) = args else {
+                return Err(QSharpError::new_err(format!(
+                    "expected arguments of type `{input_ty}`"
+                )));
+            };
+            // This conversion will produce errors if the types don't match or can't be converted.
+            convert_obj_with_ty(py, &args, &input_ty)?
+        };
+
+        match self.interpreter.invoke(&mut receiver, callable.0, args) {
+            Ok(value) => Ok(ValueWrapper(value).into_py(py)),
+            Err(errors) => Err(QSharpError::new_err(format_errors(errors))),
+        }
+    }
+
     fn qir(&mut self, _py: Python, entry_expr: &str) -> PyResult<String> {
         match self.interpreter.qirgen(entry_expr) {
             Ok(qir) => Ok(qir),
@@ -494,6 +574,85 @@ impl Interpreter {
     }
 }
 
+/// Finds any Q# type recursively that does not support interop with Python, meaning our code cannot convert it back and forth
+/// across the interop boundary.
+fn first_unsupported_interop_ty(ty: &Ty) -> Option<&Ty> {
+    match ty {
+        Ty::Prim(prim_ty) => match prim_ty {
+            Prim::Pauli
+            | Prim::BigInt
+            | Prim::Bool
+            | Prim::Double
+            | Prim::Int
+            | Prim::String
+            | Prim::Result => None,
+            Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => {
+                Some(ty)
+            }
+        },
+        Ty::Tuple(tup) => tup
+            .iter()
+            .find(|t| first_unsupported_interop_ty(t).is_some()),
+        Ty::Array(ty) => first_unsupported_interop_ty(ty),
+        _ => Some(ty),
+    }
+}
+
+/// Given a type, convert a Python object into a Q# value of that type. This will recur through tuples and arrays,
+/// and will return an error if the type is not supported or the object cannot be converted.
+fn convert_obj_with_ty(py: Python, obj: &PyObject, ty: &Ty) -> PyResult<Value> {
+    match ty {
+        Ty::Prim(prim_ty) => match prim_ty {
+            Prim::BigInt => Ok(Value::BigInt(obj.extract::<BigInt>(py)?)),
+            Prim::Bool => Ok(Value::Bool(obj.extract::<bool>(py)?)),
+            Prim::Double => Ok(Value::Double(obj.extract::<f64>(py)?)),
+            Prim::Int => Ok(Value::Int(obj.extract::<i64>(py)?)),
+            Prim::String => Ok(Value::String(obj.extract::<String>(py)?.into())),
+            Prim::Result => Ok(Value::Result(qsc::interpret::Result::Val(
+                obj.extract::<Result>(py)? == Result::One,
+            ))),
+            Prim::Pauli => Ok(Value::Pauli(match obj.extract::<Pauli>(py)? {
+                Pauli::I => fir::Pauli::I,
+                Pauli::X => fir::Pauli::X,
+                Pauli::Y => fir::Pauli::Y,
+                Pauli::Z => fir::Pauli::Z,
+            })),
+            Prim::Qubit | Prim::Range | Prim::RangeTo | Prim::RangeFrom | Prim::RangeFull => {
+                unimplemented!("primitive input type: {prim_ty:?}")
+            }
+        },
+        Ty::Tuple(tup) => {
+            if tup.len() == 1 {
+                let value = convert_obj_with_ty(py, obj, &tup[0]);
+                Ok(Value::Tuple(vec![value?].into()))
+            } else {
+                let obj = obj.extract::<Vec<PyObject>>(py)?;
+                if obj.len() != tup.len() {
+                    return Err(QSharpError::new_err(format!(
+                        "mismatched tuple arity: expected {}, got {}",
+                        tup.len(),
+                        obj.len()
+                    )));
+                }
+                let mut values = Vec::with_capacity(obj.len());
+                for (i, ty) in tup.iter().enumerate() {
+                    values.push(convert_obj_with_ty(py, &obj[i], ty)?);
+                }
+                Ok(Value::Tuple(values.into()))
+            }
+        }
+        Ty::Array(ty) => {
+            let obj = obj.extract::<Vec<PyObject>>(py)?;
+            let mut values = Vec::with_capacity(obj.len());
+            for item in &obj {
+                values.push(convert_obj_with_ty(py, item, ty)?);
+            }
+            Ok(Value::Array(values.into()))
+        }
+        _ => unimplemented!("input type: {ty}"),
+    }
+}
+
 #[pyfunction]
 pub fn physical_estimates(logical_resources: &str, job_params: &str) -> PyResult<String> {
     match re::estimate_physical_resources_from_json(logical_resources, job_params) {
@@ -840,3 +999,47 @@ where
         PyException::new_err(message)
     }
 }
+
+#[pyclass(unsendable)]
+#[derive(Clone)]
+struct GlobalCallable(Value);
+
+impl From<Value> for GlobalCallable {
+    fn from(val: Value) -> Self {
+        match val {
+            val @ Value::Global(..) => GlobalCallable(val),
+            _ => panic!("expected global callable"),
+        }
+    }
+}
+
+impl From<GlobalCallable> for Value {
+    fn from(val: GlobalCallable) -> Self {
+        val.0
+    }
+}
+
+/// Create a Python callable from a Q# callable and adds it to the given environment.
+fn create_py_callable(
+    py: Python,
+    make_callable: &PyObject,
+    namespace: &[Rc<str>],
+    name: &str,
+    val: Value,
+) -> PyResult<()> {
+    if namespace.is_empty() && name == "<lambda>" {
+        // We don't want to bind auto-generated lambda callables.
+        return Ok(());
+    }
+
+    let args = (
+        Py::new(py, GlobalCallable::from(val)).expect("should be able to create callable"), // callable id
+        PyList::new_bound(py, namespace.iter().map(ToString::to_string)), // namespace as string array
+        PyString::new_bound(py, name),                                    // name of callable
+    );
+
+    // Call into the Python layer to create the function wrapping the callable invocation.
+    make_callable.call1(py, args)?;
+
+    Ok(())
+}
diff --git a/samples/algorithms/MajoranaQubits/src/Main.qs b/samples/algorithms/MajoranaQubits/src/Main.qs
index 4047d7c9..71d56806 100644
--- a/samples/algorithms/MajoranaQubits/src/Main.qs
+++ b/samples/algorithms/MajoranaQubits/src/Main.qs
@@ -4,7 +4,7 @@
 /// # Description
 /// In hardware providing majorana qubits, common quantum operations
 /// are implemented using measurements and Pauli corrections. This
-/// sample shows a hypotetical hardware provider exposing some custom
+/// sample shows a hypothetical hardware provider exposing some custom
 /// gates to Q# and a small library built on top of it.
 
 /// Sample program using custom gates from a hardware provider.
diff --git a/samples/algorithms/SimpleIsing.qs b/samples/algorithms/SimpleIsing.qs
new file mode 100644
index 00000000..61945ca0
--- /dev/null
+++ b/samples/algorithms/SimpleIsing.qs
@@ -0,0 +1,113 @@
+/// # Sample
+/// Simulation of a simple Ising model evolution on a 2D grid
+///
+/// # Description
+/// This example demonstrates simulation of an Ising model Hamiltonian
+/// on an N1xN2 2D grid. This example can be easily simulated classically
+/// with 3x3 grid and about 500 shots.
+/// This sample is suitable for Base Profile.
+/// For the purpose of simplicity this sample intentionally doesn't
+/// post-process results or perform eigenvalue estimation.
+operation Main() : Result[] {
+    // Dimensions of a 2D grid is N1 x N2
+    let N1 : Int = 3;
+    let N2 : Int = 3;
+
+    // Total evolution time
+    let evolutionTime : Double = 4.0;
+    // Number of steps
+    let numberOfSteps : Int = 5;
+
+    // Coefficient for 2-qubit interactions between neighboring qubits
+    let J : Double = 1.0;
+    // Coefficient for external field interaction for individual qubits
+    let g : Double = 1.4;
+
+    // Also try simulating with different strength of external field:
+    // let g = 0.2;
+    // let g = 1.0;
+    // let g = 1.4;
+    // let g = 2.0;
+
+    // Model evolution
+    IsingModel2DEvolution(N1, N2, J, g, evolutionTime, numberOfSteps)
+}
+
+/// # Summary
+/// Simulate simple Ising model evolution
+///
+/// # Description
+/// Simulates state |𝜓⟩ evolution to find |𝜓(t)⟩=U(t)|𝜓(0)⟩.
+/// |𝜓(0)⟩ is taken to be |0...0⟩.
+/// U(t)=e⁻ⁱᴴᵗ, where H is an Ising model Hamiltonian H = -J·Σ'ᵢⱼZᵢZⱼ + g·ΣᵢXᵢ
+/// Here Σ' is taken over all pairs of neighboring qubits <i,j>.
+/// Simulation is done by performing K steps assuming U(t)≈U(t/K)ᴷ.
+operation IsingModel2DEvolution(
+    N1 : Int,
+    N2 : Int,
+    J : Double,
+    g : Double,
+    evolutionTime : Double,
+    numberOfSteps : Int
+) : Result[] {
+
+    // Allocate qubit grid and structure it as a 2D array.
+    use qubits = Qubit[N1 * N2];
+    let qubitsAs2D = Std.Arrays.Chunks(N2, qubits);
+
+    // Compute the step time
+    import Std.Convert.IntAsDouble;
+    let stepTime : Double = evolutionTime / IntAsDouble(numberOfSteps);
+
+    let theta_x = - g * stepTime;
+    let theta_zz = J * stepTime;
+
+    // Perform K steps
+    for i in 1..numberOfSteps {
+
+        // Single-qubit interaction with external field
+        for q in qubits {
+            Rx(2.0 * theta_x, q);
+        }
+
+        // All Rzz gates applied in the following two loops commute so they can be
+        // applied in any order. To reduce the depth of the algorithm, Rzz gates
+        // between horizontal "even" pairs of qubits are applied first - pairs
+        // that start at even indices. Then Rzz gates between "odd" pairs are
+        // applied. That way all Rzz between horizontal "even" pairs can potentially
+        // be done in parallel. Same is true about horizontal "odd"  pairs,
+        // vertical "even" pairs and vertical "odd" pairs.
+
+        // Horizontal two-qubit interactions
+        for row in 0..N1-1 {
+            // Horizontal interactions between "even" pairs
+            for col in 0..2..N2-2 {
+                Rzz(2.0 * theta_zz, qubitsAs2D[row][col], qubitsAs2D[row][col + 1]);
+            }
+
+            // Horizontal interactions between "odd" pairs
+            for col in 1..2..N2-2 {
+                Rzz(2.0 * theta_zz, qubitsAs2D[row][col], qubitsAs2D[row][col + 1]);
+            }
+        }
+
+        // Vertical two-qubit interactions
+        for col in 0..N2-1 {
+
+            // Vertical interactions between "even" pairs
+            for row in 0..2..N1-2 {
+                Rzz(2.0 * theta_zz, qubitsAs2D[row][col], qubitsAs2D[row + 1][col]);
+            }
+
+            // Vertical interactions between "odd" pairs
+            for row in 1..2..N1-2 {
+                Rzz(2.0 * theta_zz, qubitsAs2D[row][col], qubitsAs2D[row + 1][col]);
+            }
+
+        }
+
+    }
+
+    MResetEachZ(qubits)
+}
+ 
\ No newline at end of file
diff --git a/samples/notebooks/project.ipynb b/samples/notebooks/project.ipynb
index 5dc634ed..0c3da1c4 100644
--- a/samples/notebooks/project.ipynb
+++ b/samples/notebooks/project.ipynb
@@ -42,11 +42,32 @@
     "\n",
     "Sample.Main()\n"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5fad86c4",
+   "metadata": {},
+   "source": [
+    "The callables from the project are also available under `qsharp.code` and can be called or imported from Python."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "423b6fc4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from qsharp.code.Sample import Main\n",
+    "\n",
+    "res = Main()\n",
+    "print(f\"Got return value from Q# code: {res}\")"
+   ]
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3 (ipykernel)",
+   "display_name": "Python 3",
    "language": "python",
    "name": "python3"
   },
@@ -60,7 +81,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.11.6"
+   "version": "3.11.11"
   }
  },
  "nbformat": 4,
diff --git a/samples/notebooks/sample.ipynb b/samples/notebooks/sample.ipynb
index 583a999a..1d60bd2c 100644
--- a/samples/notebooks/sample.ipynb
+++ b/samples/notebooks/sample.ipynb
@@ -10,12 +10,6 @@
     "This enables the `%%qsharp` magic and initializes a Q# interpreter singleton."
    ]
   },
-  {
-   "cell_type": "markdown",
-   "id": "ed1b75bf",
-   "metadata": {},
-   "source": []
-  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -69,7 +63,7 @@
    "source": [
     "`qsharp.eval()` does the same thing as the `%%qsharp` magic.\n",
     "\n",
-    "`DumpMachine()` and `Message()` print to stdout and get displayed in the notebook as plain text"
+    "`DumpMachine()` and `Message()` print to stdout and get displayed in the notebook as plain text."
    ]
   },
   {
@@ -84,6 +78,24 @@
     "qsharp.eval(\"Main()\")\n"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "id": "19f4ef6d",
+   "metadata": {},
+   "source": [
+    "`qsharp.code` provides direct access to simulating callables defined in Q#."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "30b92222",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "qsharp.code.Main()"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "a3bde193",
@@ -256,7 +268,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 11,
    "id": "eb3cd29f",
    "metadata": {
     "vscode": {
@@ -382,7 +394,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 14,
    "id": "9b85eb2d",
    "metadata": {
     "vscode": {
@@ -414,6 +426,106 @@
    "source": [
     "qsharp.run(\"Bad()\", 10)\n"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9f738245",
+   "metadata": {},
+   "source": [
+    "When invoked from Python, arguments to Q# callables are converted from their Python type to the expected Q# type. If an argument cannot be converted to the right type, it will trigger a runtime exception."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9ae2729d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "qsharp.eval(\"\"\"\n",
+    "    function AddTwoInts(a : Int, b : Int) : Int {\n",
+    "        return a + b;\n",
+    "    }\n",
+    "    \"\"\")\n",
+    "\n",
+    "from qsharp.code import AddTwoInts\n",
+    "\n",
+    "print(AddTwoInts(2, 3))\n",
+    "\n",
+    "try:\n",
+    "    AddTwoInts(2, 3.0)\n",
+    "except TypeError as e:\n",
+    "    print(f\"TypeError: {e}\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0f0795e9",
+   "metadata": {},
+   "source": [
+    "If you define any Q# callables in a namespace (or when initializing with a Q# project), those callables will be exposed with a matching hierarchy of modules in Python:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e7b84a41",
+   "metadata": {
+    "vscode": {
+     "languageId": "qsharp"
+    }
+   },
+   "outputs": [],
+   "source": [
+    "%%qsharp\n",
+    "\n",
+    "import Std.Diagnostics.DumpMachine;\n",
+    "namespace Foo {\n",
+    "    operation Bar() : Unit {\n",
+    "        use qs = Qubit[2];\n",
+    "        for q in qs {\n",
+    "            H(q);\n",
+    "        }\n",
+    "        DumpMachine();\n",
+    "        ResetAll(qs);\n",
+    "    }\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5591587c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from qsharp.code.Foo import Bar\n",
+    "\n",
+    "Bar()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "020b244b",
+   "metadata": {},
+   "source": [
+    "If you run `qsharp.init()`, the compiler and simulator state are reset and all functions exposed into Python are cleared:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "3d73c247",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "qsharp.init()\n",
+    "\n",
+    "try:\n",
+    "    Bar()\n",
+    "except qsharp.QSharpError as e:\n",
+    "    print(f\"QsharpError: {e}\")"
+   ]
   }
  ],
  "metadata": {
@@ -432,7 +544,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.12.1"
+   "version": "3.11.11"
   }
  },
  "nbformat": 4,
diff --git a/samples/python_interop/generating_n_random_bits/RunGenerateRandom.py b/samples/python_interop/generating_n_random_bits/RunGenerateRandom.py
index cf62304a..f1d90a18 100644
--- a/samples/python_interop/generating_n_random_bits/RunGenerateRandom.py
+++ b/samples/python_interop/generating_n_random_bits/RunGenerateRandom.py
@@ -2,10 +2,10 @@ import qsharp
 
 qsharp.init(project_root=".")
 
+from qsharp.code.GenerateRandomNumbers import GenerateRandomNumbers
+
 nQubits = input("Enter the number of random bits to be generated: ")
-(results, number) = qsharp.eval(
-    f"GenerateRandomNumbers.GenerateRandomNumbers({nQubits})"
-)
+(results, number) = GenerateRandomNumbers(int(nQubits))
 
 count = 0
 for result in results:
diff --git a/version.py b/version.py
index d40389fc..67addb24 100755
--- a/version.py
+++ b/version.py
@@ -6,7 +6,7 @@
 import os
 
 # To be updated every time we start a new major.minor version.
-major_minor = "1.11"
+major_minor = "1.12"
 
 # Default to 'dev' builds
 BUILD_TYPE = os.environ.get("BUILD_TYPE") or "dev"
diff --git a/vscode/qsharp.schema.json b/vscode/qsharp.schema.json
index 34de82d3..ffb22cfe 100644
--- a/vscode/qsharp.schema.json
+++ b/vscode/qsharp.schema.json
@@ -34,9 +34,13 @@
               "divisionByZero",
               "needlessParens",
               "redundantSemicolons",
+              "deprecatedNewtype",
+              "deprecatedSet",
+              "discourageChainAssignment",
+              "needlessOperation",
+              "deprecatedFunctionConstructor",
               "deprecatedWithOperator",
-              "deprecatedDoubleColonOperator",
-              "deprecatedNewtype"
+              "deprecatedDoubleColonOperator"
             ]
           },
           "level": {
diff --git a/vscode/src/azure/commands.ts b/vscode/src/azure/commands.ts
index 5fe1cd8a..6775e957 100644
--- a/vscode/src/azure/commands.ts
+++ b/vscode/src/azure/commands.ts
@@ -23,8 +23,9 @@ import { getQirForActiveWindow } from "../qirGeneration";
 import { supportsAdaptive, targetSupportQir } from "./providerProperties";
 import { startRefreshCycle } from "./treeRefresher";
 import { getTokenForWorkspace } from "./auth";
+import { qsharpExtensionId } from "../common";
 
-const workspacesSecret = "qsharp-vscode.workspaces";
+const workspacesSecret = `${qsharpExtensionId}.workspaces`;
 
 export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
   const workspaceTreeProvider = new WorkspaceTreeProvider();
@@ -81,17 +82,17 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
 
       await vscode.commands.executeCommand(
         "setContext",
-        "qsharp-vscode.treeItemSupportsQir",
+        `${qsharpExtensionId}.treeItemSupportsQir`,
         supportsQir,
       );
       await vscode.commands.executeCommand(
         "setContext",
-        "qsharp-vscode.treeItemSupportsDownload",
+        `${qsharpExtensionId}.treeItemSupportsDownload`,
         supportsDownload,
       );
       await vscode.commands.executeCommand(
         "setContext",
-        "qsharp-vscode.treeItemIsWorkspace",
+        `${qsharpExtensionId}.treeItemIsWorkspace`,
         isWorkspace,
       );
     }),
@@ -99,7 +100,7 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.targetSubmit",
+      `${qsharpExtensionId}.targetSubmit`,
       async (arg: WorkspaceTreeItem) => {
         // Could be run via the treeItem icon or the menu command.
         const treeItem = arg || currentTreeItem;
@@ -156,17 +157,20 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
   );
 
   context.subscriptions.push(
-    vscode.commands.registerCommand("qsharp-vscode.workspacesRefresh", () => {
-      // The user manually triggered a refresh. Start a cycle for each workspace
-      const workspaceIds = workspaceTreeProvider.getWorkspaceIds();
-
-      workspaceIds.forEach((id) => {
-        const workspace = workspaceTreeProvider.getWorkspace(id);
-        if (workspace) {
-          startRefreshCycle(workspaceTreeProvider, workspace);
-        }
-      });
-    }),
+    vscode.commands.registerCommand(
+      `${qsharpExtensionId}.workspacesRefresh`,
+      () => {
+        // The user manually triggered a refresh. Start a cycle for each workspace
+        const workspaceIds = workspaceTreeProvider.getWorkspaceIds();
+
+        workspaceIds.forEach((id) => {
+          const workspace = workspaceTreeProvider.getWorkspace(id);
+          if (workspace) {
+            startRefreshCycle(workspaceTreeProvider, workspace);
+          }
+        });
+      },
+    ),
   );
 
   async function saveWorkspaceList() {
@@ -196,20 +200,23 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
   }
 
   context.subscriptions.push(
-    vscode.commands.registerCommand("qsharp-vscode.workspacesAdd", async () => {
-      const workspace = await queryWorkspaces();
-      if (workspace) {
-        workspaceTreeProvider.updateWorkspace(workspace);
-        await saveWorkspaceList();
-        // Just kick off the refresh loop, no need to await
-        startRefreshCycle(workspaceTreeProvider, workspace);
-      }
-    }),
+    vscode.commands.registerCommand(
+      `${qsharpExtensionId}.workspacesAdd`,
+      async () => {
+        const workspace = await queryWorkspaces();
+        if (workspace) {
+          workspaceTreeProvider.updateWorkspace(workspace);
+          await saveWorkspaceList();
+          // Just kick off the refresh loop, no need to await
+          startRefreshCycle(workspaceTreeProvider, workspace);
+        }
+      },
+    ),
   );
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.workspacesRemove",
+      `${qsharpExtensionId}.workspacesRemove`,
       async (arg: WorkspaceTreeItem) => {
         // Could be run via the treeItem icon or the menu command.
         const treeItem = arg || currentTreeItem;
@@ -224,7 +231,7 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.downloadResults",
+      `${qsharpExtensionId}.downloadResults`,
       async (arg: WorkspaceTreeItem) => {
         // Could be run via the treeItem icon or the menu command.
         const treeItem = arg || currentTreeItem;
@@ -273,7 +280,7 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.workspacePythonCode",
+      `${qsharpExtensionId}.workspacePythonCode`,
       async (arg: WorkspaceTreeItem) => {
         // Could be run via the treeItem icon or the menu command.
         const treeItem = arg || currentTreeItem;
@@ -300,7 +307,7 @@ export async function initAzureWorkspaces(context: vscode.ExtensionContext) {
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.workspaceOpenPortal",
+      `${qsharpExtensionId}.workspaceOpenPortal`,
       async (arg: WorkspaceTreeItem) => {
         // Could be run via the treeItem icon or the menu command.
         const treeItem = arg || currentTreeItem;
diff --git a/vscode/src/azure/workspaceActions.ts b/vscode/src/azure/workspaceActions.ts
index 9602e4fa..964c8a31 100644
--- a/vscode/src/azure/workspaceActions.ts
+++ b/vscode/src/azure/workspaceActions.ts
@@ -109,9 +109,13 @@ export function getPythonCodeForWorkspace(
 # should be configured and used for authentication. For more information, see
 # https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication-overview
 
-import azure.quantum
+from azure.quantum import Workspace
 
-workspace = azure.quantum.Workspace(
+# If using an access key, replace the below with: Workspace.from_connection_string(connection_string)
+# Or set the "AZURE_QUANTUM_CONNECTION_STRING" environment variable and just use: Workspace()
+# See https://learn.microsoft.com/en-us/azure/quantum/how-to-connect-workspace for more details.
+
+workspace = Workspace(
     subscription_id = "${subscriptionId || "MY_SUBSCRIPTION_ID"}",
     resource_group = "${resourceGroup || "MY_RESOURCE_GROUP"}",
     name = "${name || "MY_WORKSPACE_NAME"}",
diff --git a/vscode/src/createProject.ts b/vscode/src/createProject.ts
index 6bf478cf..2d6f59e4 100644
--- a/vscode/src/createProject.ts
+++ b/vscode/src/createProject.ts
@@ -4,11 +4,12 @@
 import * as vscode from "vscode";
 import { log, samples } from "qsharp-lang";
 import { EventType, sendTelemetryEvent } from "./telemetry";
+import { qsharpExtensionId } from "./common";
 
 export async function initProjectCreator(context: vscode.ExtensionContext) {
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.createProject",
+      `${qsharpExtensionId}.createProject`,
       async (folderUri: vscode.Uri | undefined) => {
         sendTelemetryEvent(EventType.CreateProject, {}, {});
 
@@ -66,7 +67,7 @@ export async function initProjectCreator(context: vscode.ExtensionContext) {
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.populateFilesList",
+      `${qsharpExtensionId}.populateFilesList`,
       async (qsharpJsonUri: vscode.Uri | undefined) => {
         // If called from the content menu qsharpJsonUri will be the full qsharp.json uri
         // If called from the command palette is will be undefined, so use the active editor
@@ -228,7 +229,7 @@ export async function initProjectCreator(context: vscode.ExtensionContext) {
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.addProjectReference",
+      `${qsharpExtensionId}.addProjectReference`,
       async (qsharpJsonUri: vscode.Uri | undefined) => {
         // If called from the content menu qsharpJsonUri will be the full qsharp.json uri
         // If called from the command palette is will be undefined, so use the active editor
diff --git a/vscode/src/diagnostics.ts b/vscode/src/diagnostics.ts
index e0fd154c..2b08e0cb 100644
--- a/vscode/src/diagnostics.ts
+++ b/vscode/src/diagnostics.ts
@@ -3,7 +3,11 @@
 
 import { IQSharpError, log, qsharpLibraryUriScheme } from "qsharp-lang";
 import * as vscode from "vscode";
-import { qsharpLanguageId, toVsCodeDiagnostic } from "./common.js";
+import {
+  qsharpExtensionId,
+  qsharpLanguageId,
+  toVsCodeDiagnostic,
+} from "./common.js";
 
 /**
  * Initialize diagnostics for `qsharp.json` files and failures
@@ -84,7 +88,7 @@ function startCommandDiagnostics(): vscode.Disposable[] {
     vscode.languages.createDiagnosticCollection(qsharpLanguageId);
 
   const dismissCommand = vscode.commands.registerCommand(
-    "qsharp-vscode.dismissCommandDiagnostics",
+    `${qsharpExtensionId}.dismissCommandDiagnostics`,
     clearCommandDiagnostics,
   );
 
@@ -102,7 +106,7 @@ function startCommandDiagnostics(): vscode.Disposable[] {
           );
           action.diagnostics = commandErrors;
           action.command = {
-            command: "qsharp-vscode.dismissCommandDiagnostics",
+            command: `${qsharpExtensionId}.dismissCommandDiagnostics`,
             title: "Dismiss errors for the last run Q# command",
           };
           action.isPreferred = true;
diff --git a/vscode/src/memfs.ts b/vscode/src/memfs.ts
index f5ac8570..387e55ac 100644
--- a/vscode/src/memfs.ts
+++ b/vscode/src/memfs.ts
@@ -3,6 +3,7 @@
 
 import { log, samples } from "qsharp-lang";
 import * as vscode from "vscode";
+import { qsharpExtensionId } from "./common";
 
 export const scheme = "qsharp-vfs";
 
@@ -69,7 +70,7 @@ export async function initFileSystem(context: vscode.ExtensionContext) {
 
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.openPlayground",
+      `${qsharpExtensionId}.openPlayground`,
       async () => {
         await vscode.commands.executeCommand(
           "vscode.openFolder",
@@ -80,47 +81,52 @@ export async function initFileSystem(context: vscode.ExtensionContext) {
   );
 
   context.subscriptions.push(
-    vscode.commands.registerCommand("qsharp-vscode.webOpener", async (uri) => {
-      log.debug(`webOpener called with URI ${uri}`);
-
-      // Open the README if the user has navigated to the playground
-      if (typeof uri === "string" && uri.endsWith("/playground/")) {
-        // Nice to have: First check if the readme is already open from a prior visit
-        await vscode.commands.executeCommand(
-          "markdown.showPreview",
-          playgroundRootUri.with({ path: "/README.md" }),
-        );
-        return;
-      }
+    vscode.commands.registerCommand(
+      `${qsharpExtensionId}.webOpener`,
+      async (uri) => {
+        log.debug(`webOpener called with URI ${uri}`);
+
+        // Open the README if the user has navigated to the playground
+        if (typeof uri === "string" && uri.endsWith("/playground/")) {
+          // Nice to have: First check if the readme is already open from a prior visit
+          await vscode.commands.executeCommand(
+            "markdown.showPreview",
+            playgroundRootUri.with({ path: "/README.md" }),
+          );
+          return;
+        }
 
-      // Example: https://vscode.dev/quantum?code=H4sIAAAAAAAAEz2Ouw6CQBRE%2B%2F2KITbQSI%2BNhRYWhkc0FoTiBm5kE9kld3c1xPDvEjSe8mQmM2mKS68dWtsxXuTgehLu8NQEwrU6RZFShgZ2I7WM81QGMj4Mhdi70IC3UljYH42XqbDa%2BDhZjR1ZyGtrcCZt4gQZKnbh4etmKeFHusznhzzDTbRnTDYIys33Tc%2FC239S2AcxqJvdqmY1qw8FRbBxvAAAAA%3D%3D
-      let linkedCode: string | undefined;
-      if (typeof uri === "string") {
-        const uriObj = vscode.Uri.parse(uri);
-        log.debug("uri query component: " + uriObj.query);
-
-        // The query appears to be URIDecoded already, which is causing issues with URLSearchParams, so extract with a regex for now.
-        const code = uriObj.query.match(/code=([^&]*)/)?.[1];
-
-        if (code) {
-          log.debug("code from query: " + code);
-          try {
-            linkedCode = await compressedBase64ToCode(code);
-            const codeFile = vscode.Uri.parse(`${scheme}:/code.qs`);
-
-            const encoder = new TextEncoder();
-            vfs.writeFile(codeFile, encoder.encode(linkedCode), {
-              create: true,
-              overwrite: true,
-            });
-            await vscode.commands.executeCommand("vscode.open", codeFile);
-            await vscode.commands.executeCommand("qsharp-vscode.showHelp");
-          } catch (err) {
-            log.warn("Unable to decode the code in the URL. ", err);
+        // Example: https://vscode.dev/quantum?code=H4sIAAAAAAAAEz2Ouw6CQBRE%2B%2F2KITbQSI%2BNhRYWhkc0FoTiBm5kE9kld3c1xPDvEjSe8mQmM2mKS68dWtsxXuTgehLu8NQEwrU6RZFShgZ2I7WM81QGMj4Mhdi70IC3UljYH42XqbDa%2BDhZjR1ZyGtrcCZt4gQZKnbh4etmKeFHusznhzzDTbRnTDYIys33Tc%2FC239S2AcxqJvdqmY1qw8FRbBxvAAAAA%3D%3D
+        let linkedCode: string | undefined;
+        if (typeof uri === "string") {
+          const uriObj = vscode.Uri.parse(uri);
+          log.debug("uri query component: " + uriObj.query);
+
+          // The query appears to be URIDecoded already, which is causing issues with URLSearchParams, so extract with a regex for now.
+          const code = uriObj.query.match(/code=([^&]*)/)?.[1];
+
+          if (code) {
+            log.debug("code from query: " + code);
+            try {
+              linkedCode = await compressedBase64ToCode(code);
+              const codeFile = vscode.Uri.parse(`${scheme}:/code.qs`);
+
+              const encoder = new TextEncoder();
+              vfs.writeFile(codeFile, encoder.encode(linkedCode), {
+                create: true,
+                overwrite: true,
+              });
+              await vscode.commands.executeCommand("vscode.open", codeFile);
+              await vscode.commands.executeCommand(
+                `${qsharpExtensionId}.showHelp`,
+              );
+            } catch (err) {
+              log.warn("Unable to decode the code in the URL. ", err);
+            }
           }
         }
-      }
-    }),
+      },
+    ),
   );
 }
 
diff --git a/vscode/src/notebook.ts b/vscode/src/notebook.ts
index 5e53cb54..6ee82e82 100644
--- a/vscode/src/notebook.ts
+++ b/vscode/src/notebook.ts
@@ -5,7 +5,7 @@ import { log } from "qsharp-lang";
 import * as vscode from "vscode";
 import { WorkspaceTreeProvider } from "./azure/treeView.js";
 import { getPythonCodeForWorkspace } from "./azure/workspaceActions.js";
-import { qsharpLanguageId } from "./common.js";
+import { qsharpExtensionId, qsharpLanguageId } from "./common.js";
 import { notebookTemplate } from "./notebookTemplate.js";
 
 const qsharpCellMagic = "%%qsharp";
@@ -130,7 +130,7 @@ export function registerCreateNotebookCommand(
 ) {
   context.subscriptions.push(
     vscode.commands.registerCommand(
-      "qsharp-vscode.createNotebook",
+      `${qsharpExtensionId}.createNotebook`,
       async () => {
         // Update the workspace connection info in the notebook if workspaces are already connected to
         const tree = WorkspaceTreeProvider.instance;
diff --git a/vscode/src/notebookTemplate.ts b/vscode/src/notebookTemplate.ts
index bf3f6c45..59773cd3 100644
--- a/vscode/src/notebookTemplate.ts
+++ b/vscode/src/notebookTemplate.ts
@@ -99,9 +99,14 @@ import matplotlib.pyplot as plt
 import numpy as np
 from collections import Counter
 
-results = qsharp.run("RandomBit()", shots=1000)
+# Use save_events to suppress the automatic printing of events for every shot
+results = qsharp.run("RandomBit()", shots=1000, save_events=True)
+
 # Sort the results so that the histogram labels appear in the correct order
+# We only care about the result values, not the event data, so extract just the result
+results = [entry["result"] for entry in results]
 results.sort()
+
 # Count the number of times each result appears
 counts = Counter(results)
 
@@ -111,7 +116,35 @@ plt.title("RandomBit() Results")
 plt.bar(xlabels, counts)
 plt.xticks(xlabels, values)
 plt.show()
-      `,
+`,
+    },
+    {
+      kind: vscode.NotebookCellKind.Markup,
+      languageId: "markdown",
+      value: `## Q# widgets
+
+You can also use the \`qsharp_widgets\` package to visualize data. Install with \`pip install qsharp-widgets\``,
+    },
+    {
+      kind: vscode.NotebookCellKind.Code,
+      languageId: "python",
+      value: `from qsharp_widgets import Histogram
+
+Histogram(results)
+`,
+    },
+    {
+      kind: vscode.NotebookCellKind.Code,
+      languageId: "python",
+      value: `from qsharp_widgets import EstimatesPanel
+
+estimate = qsharp.estimate("RandomBit()", [
+    {"errorBudget": 0.333, "qubitParams": {"name": "qubit_gate_ns_e3"}},
+    {"errorBudget": 0.333, "qubitParams": {"name": "qubit_gate_us_e4"}},
+    {"errorBudget": 0.333, "qubitParams": {"name": "qubit_maj_ns_e6"}, "qecScheme": {"name": "floquet_code"}}
+])
+EstimatesPanel(estimate)
+`,
     },
     {
       kind: vscode.NotebookCellKind.Markup,
diff --git a/vscode/src/qirGeneration.ts b/vscode/src/qirGeneration.ts
index 5539b0c9..23c46192 100644
--- a/vscode/src/qirGeneration.ts
+++ b/vscode/src/qirGeneration.ts
@@ -8,6 +8,7 @@ import { invokeAndReportCommandDiagnostics } from "./diagnostics";
 import { getActiveProgram } from "./programConfig";
 import { EventType, sendTelemetryEvent } from "./telemetry";
 import { getRandomGuid } from "./utils";
+import { qsharpExtensionId } from "./common";
 
 const generateQirTimeoutMs = 120000;
 
@@ -142,7 +143,7 @@ export function initCodegen(context: vscode.ExtensionContext) {
   ).toString();
 
   context.subscriptions.push(
-    vscode.commands.registerCommand("qsharp-vscode.getQir", async () => {
+    vscode.commands.registerCommand(`${qsharpExtensionId}.getQir`, async () => {
       try {
         const qir = await getQirForActiveWindow();
         const qirDoc = await vscode.workspace.openTextDocument({
diff --git a/vscode/src/statusbar.ts b/vscode/src/statusbar.ts
index ecd7f9eb..cb19a906 100644
--- a/vscode/src/statusbar.ts
+++ b/vscode/src/statusbar.ts
@@ -3,7 +3,7 @@
 
 import { log, TargetProfile } from "qsharp-lang";
 import * as vscode from "vscode";
-import { isQsharpDocument } from "./common";
+import { isQsharpDocument, qsharpExtensionId } from "./common";
 import { getTarget, getTargetFriendlyName, setTarget } from "./config";
 import { getActiveQSharpDocumentUri } from "./programConfig";
 
@@ -18,7 +18,7 @@ export function activateTargetProfileStatusBarItem(): vscode.Disposable[] {
   );
   disposables.push(statusBarItem);
 
-  statusBarItem.command = "qsharp-vscode.setTargetProfile";
+  statusBarItem.command = `${qsharpExtensionId}.setTargetProfile`;
 
   disposables.push(
     vscode.window.onDidChangeActiveTextEditor((editor) => {
@@ -75,7 +75,7 @@ export function activateTargetProfileStatusBarItem(): vscode.Disposable[] {
 
 function registerTargetProfileCommand() {
   return vscode.commands.registerCommand(
-    "qsharp-vscode.setTargetProfile",
+    `${qsharpExtensionId}.setTargetProfile`,
     async () => {
       const target = await vscode.window.showQuickPick(
         targetProfiles.map((profile) => ({
diff --git a/vscode/src/webviewPanel.ts b/vscode/src/webviewPanel.ts
index 7344a6c6..8373a151 100644
--- a/vscode/src/webviewPanel.ts
+++ b/vscode/src/webviewPanel.ts
@@ -25,6 +25,7 @@ import { getActiveProgram } from "./programConfig";
 import { EventType, sendTelemetryEvent } from "./telemetry";
 import { getRandomGuid } from "./utils";
 import { getPauliNoiseModel } from "./config";
+import { qsharpExtensionId } from "./common";
 
 const QSharpWebViewType = "qsharp-webview";
 const compilerRunTimeoutMs = 1000 * 60 * 5; // 5 minutes
@@ -43,7 +44,7 @@ export function registerWebViewCommands(context: ExtensionContext) {
   ).toString();
 
   context.subscriptions.push(
-    commands.registerCommand("qsharp-vscode.showRe", async () => {
+    commands.registerCommand(`${qsharpExtensionId}.showRe`, async () => {
       clearCommandDiagnostics();
       const associationId = getRandomGuid();
       sendTelemetryEvent(
@@ -271,7 +272,7 @@ export function registerWebViewCommands(context: ExtensionContext) {
   );
 
   context.subscriptions.push(
-    commands.registerCommand("qsharp-vscode.showHelp", async () => {
+    commands.registerCommand(`${qsharpExtensionId}.showHelp`, async () => {
       const message = {
         command: "help",
       };
@@ -280,7 +281,7 @@ export function registerWebViewCommands(context: ExtensionContext) {
   );
 
   context.subscriptions.push(
-    commands.registerCommand("qsharp-vscode.showHistogram", async () => {
+    commands.registerCommand(`${qsharpExtensionId}.showHistogram`, async () => {
       clearCommandDiagnostics();
 
       const associationId = getRandomGuid();
@@ -372,7 +373,7 @@ export function registerWebViewCommands(context: ExtensionContext) {
 
   context.subscriptions.push(
     commands.registerCommand(
-      "qsharp-vscode.showCircuit",
+      `${qsharpExtensionId}.showCircuit`,
       async (operation?: IOperationInfo) => {
         await showCircuitCommand(context.extensionUri, operation);
       },
@@ -380,9 +381,12 @@ export function registerWebViewCommands(context: ExtensionContext) {
   );
 
   context.subscriptions.push(
-    commands.registerCommand("qsharp-vscode.showDocumentation", async () => {
-      await showDocumentationCommand(context.extensionUri);
-    }),
+    commands.registerCommand(
+      `${qsharpExtensionId}.showDocumentation`,
+      async () => {
+        await showDocumentationCommand(context.extensionUri);
+      },
+    ),
   );
 }
 


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index ce372544..2b82256c 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -46,6 +46,21 @@ mod given_interpreter {
         (result, receiver.dump())
     }
 
+    fn invoke(
+        interpreter: &mut Interpreter,
+        callable: &str,
+        args: Value,
+    ) -> (InterpretResult, String) {
+        let mut cursor = Cursor::new(Vec::<u8>::new());
+        let mut receiver = CursorReceiver::new(&mut cursor);
+        let callable = match interpreter.eval_fragments(&mut receiver, callable) {
+            Ok(val) => val,
+            Err(e) => return (Err(e), receiver.dump()),
+        };
+        let result = interpreter.invoke(&mut receiver, callable, args);
+        (result, receiver.dump())
+    }
+
     mod without_sources {
         use expect_test::expect;
         use indoc::indoc;
@@ -514,6 +529,166 @@ mod given_interpreter {
             is_only_value(&result, &output, &Value::unit());
         }
 
+        #[test]
+        fn interpreter_without_sources_has_no_items() {
+            let interpreter = get_interpreter();
+            let items = interpreter.user_globals();
+            assert!(items.is_empty());
+        }
+
+        #[test]
+        fn fragment_without_items_has_no_items() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = line(&mut interpreter, "()");
+            is_only_value(&result, &output, &Value::unit());
+            let items = interpreter.source_globals();
+            assert!(items.is_empty());
+        }
+
+        #[test]
+        fn fragment_defining_items_has_items() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {r#"
+                    function Foo() : Int { 2 }
+                    function Bar() : Int { 3 }
+                "#},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let items = interpreter.source_globals();
+            assert_eq!(items.len(), 2);
+            // No namespace for top-level items
+            assert!(items[0].0.is_empty());
+            expect![[r#"
+                "Foo"
+            "#]]
+            .assert_debug_eq(&items[0].1);
+            // No namespace for top-level items
+            assert!(items[1].0.is_empty());
+            expect![[r#"
+                "Bar"
+            "#]]
+            .assert_debug_eq(&items[1].1);
+        }
+
+        #[test]
+        fn fragment_defining_items_with_namespace_has_items() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {r#"
+                    namespace Foo {
+                        function Bar() : Int { 3 }
+                    }
+                "#},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let items = interpreter.source_globals();
+            assert_eq!(items.len(), 1);
+            expect![[r#"
+                [
+                    "Foo",
+                ]
+            "#]]
+            .assert_debug_eq(&items[0].0);
+            expect![[r#"
+                "Bar"
+            "#]]
+            .assert_debug_eq(&items[0].1);
+        }
+
+        #[test]
+        fn fragments_defining_items_add_to_existing_items() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {r#"
+                    function Foo() : Int { 2 }
+                    function Bar() : Int { 3 }
+                "#},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let items = interpreter.source_globals();
+            assert_eq!(items.len(), 2);
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {r#"
+                    function Baz() : Int { 4 }
+                    function Qux() : Int { 5 }
+                "#},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let items = interpreter.source_globals();
+            assert_eq!(items.len(), 4);
+            // No namespace for top-level items
+            assert!(items[0].0.is_empty());
+            expect![[r#"
+                "Foo"
+            "#]]
+            .assert_debug_eq(&items[0].1);
+            // No namespace for top-level items
+            assert!(items[1].0.is_empty());
+            expect![[r#"
+                "Bar"
+            "#]]
+            .assert_debug_eq(&items[1].1);
+            // No namespace for top-level items
+            assert!(items[2].0.is_empty());
+            expect![[r#"
+                "Baz"
+            "#]]
+            .assert_debug_eq(&items[2].1);
+            // No namespace for top-level items
+            assert!(items[3].0.is_empty());
+            expect![[r#"
+                "Qux"
+            "#]]
+            .assert_debug_eq(&items[3].1);
+        }
+
+        #[test]
+        fn invoke_callable_without_args_succeeds() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = invoke(
+                &mut interpreter,
+                "Std.Diagnostics.DumpMachine",
+                Value::unit(),
+            );
+            is_unit_with_output(&result, &output, "STATE:\nNo qubits allocated");
+        }
+
+        #[test]
+        fn invoke_callable_with_args_succeeds() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = invoke(
+                &mut interpreter,
+                "Message",
+                Value::String("Hello, World!".into()),
+            );
+            is_unit_with_output(&result, &output, "Hello, World!");
+        }
+
+        #[test]
+        fn invoke_lambda_with_capture_succeeds() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = line(&mut interpreter, "let x = 1; let f = y -> x + y;");
+            is_only_value(&result, &output, &Value::unit());
+            let (result, output) = invoke(&mut interpreter, "f", Value::Int(2));
+            is_only_value(&result, &output, &Value::Int(3));
+        }
+
+        #[test]
+        fn invoke_lambda_with_capture_in_callable_expr_succeeds() {
+            let mut interpreter = get_interpreter();
+            let (result, output) = invoke(
+                &mut interpreter,
+                "{let x = 1; let f = y -> x + y; f}",
+                Value::Int(2),
+            );
+            is_only_value(&result, &output, &Value::Int(3));
+        }
+
         #[test]
         fn callables_failing_profile_validation_are_not_registered() {
             let mut interpreter =
@@ -1698,6 +1873,46 @@ mod given_interpreter {
             );
         }
 
+        #[test]
+        fn interpreter_returns_items_from_source() {
+            let sources = SourceMap::new(
+                [(
+                    "test".into(),
+                    "namespace A {
+                        operation B(): Unit { }
+                    }
+                    "
+                    .into(),
+                )],
+                Some("A.B()".into()),
+            );
+
+            let (std_id, store) =
+                crate::compile::package_store_with_stdlib(TargetCapabilityFlags::all());
+            let interpreter = Interpreter::new(
+                sources,
+                PackageType::Lib,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+                store,
+                &[(std_id, None)],
+            )
+            .expect("interpreter should be created");
+
+            let items = interpreter.user_globals();
+            assert_eq!(1, items.len());
+            expect![[r#"
+                [
+                    "A",
+                ]
+            "#]]
+            .assert_debug_eq(&items[0].0);
+            expect![[r#"
+                "B"
+            "#]]
+            .assert_debug_eq(&items[0].1);
+        }
+
         #[test]
         fn interpreter_can_be_created_from_ast() {
             let sources = SourceMap::new(
diff --git a/compiler/qsc_linter/src/tests.rs b/compiler/qsc_linter/src/tests.rs
index 44afd2fa..b9a014bf 100644
--- a/compiler/qsc_linter/src/tests.rs
+++ b/compiler/qsc_linter/src/tests.rs
@@ -14,6 +14,93 @@ use qsc_frontend::compile::{self, CompileUnit, PackageStore, SourceMap};
 use qsc_hir::hir::CallableKind;
 use qsc_passes::PackageType;
 
+#[test]
+fn daisy_chain_lint() {
+    check(
+        &wrap_in_callable("x = y = z", CallableKind::Function),
+        &expect![[r#"
+            [
+                SrcLint {
+                    source: "x = y = z",
+                    level: Warn,
+                    message: "discouraged use of chain assignment",
+                    help: "assignment expressions always return `Unit`, so chaining them may not be useful",
+                    code_action_edits: [],
+                },
+            ]
+        "#]],
+    );
+}
+
+#[test]
+fn long_daisy_chain_lint() {
+    check(
+        &wrap_in_callable("x = y = z = z = z", CallableKind::Function),
+        &expect![[r#"
+            [
+                SrcLint {
+                    source: "x = y = z = z = z",
+                    level: Warn,
+                    message: "discouraged use of chain assignment",
+                    help: "assignment expressions always return `Unit`, so chaining them may not be useful",
+                    code_action_edits: [],
+                },
+            ]
+        "#]],
+    );
+}
+
+#[test]
+fn nested_daisy_chain_lint() {
+    check(
+        &wrap_in_callable("x = y = { a = b = c; z } = z = z", CallableKind::Function),
+        &expect![[r#"
+            [
+                SrcLint {
+                    source: "x = y = { a = b = c; z } = z = z",
+                    level: Warn,
+                    message: "discouraged use of chain assignment",
+                    help: "assignment expressions always return `Unit`, so chaining them may not be useful",
+                    code_action_edits: [],
+                },
+                SrcLint {
+                    source: "a = b = c",
+                    level: Warn,
+                    message: "discouraged use of chain assignment",
+                    help: "assignment expressions always return `Unit`, so chaining them may not be useful",
+                    code_action_edits: [],
+                },
+            ]
+        "#]],
+    );
+}
+
+#[test]
+fn set_keyword_lint() {
+    check(
+        &wrap_in_callable("set x = 3;", CallableKind::Function),
+        &expect![[r#"
+            [
+                SrcLint {
+                    source: "set",
+                    level: Allow,
+                    message: "deprecated use of `set` keyword",
+                    help: "the `set` keyword is deprecated for assignments and can be removed",
+                    code_action_edits: [
+                        (
+                            "",
+                            Span {
+                                lo: 71,
+                                hi: 74,
+                            },
+                        ),
+                    ],
+                },
+            ]
+        "#]],
+    );
+}
+
 #[test]
 fn multiple_lints() {
     check(
@@ -468,23 +555,23 @@ fn deprecated_with_eq_op_for_structs() {
         struct Foo { x : Int }
         function Bar() : Foo {
             mutable foo = new Foo { x = 2 };
-            set foo w/= x <- 3;
+            foo w/= x <- 3;
             foo
         }
     "},
         &expect![[r#"
             [
                 SrcLint {
-                    source: "set foo w/= x <- 3",
+                    source: "foo w/= x <- 3",
                     level: Allow,
                     message: "deprecated `w/` and `w/=` operators for structs",
                     help: "`w/` and `w/=` operators for structs are deprecated, use `new` instead",
                     code_action_edits: [
                         (
-                            "set foo = new Foo {\n        ...foo,\n        x = 3,\n    }",
+                            "foo = new Foo {\n        ...foo,\n        x = 3,\n    }",
                             Span {
                                 lo: 115,
-                                hi: 133,
+                                hi: 129,
                             },
                         ),
                     ],
@@ -677,7 +764,7 @@ fn run_lints(
         compile_unit,
     };
 
-    let mut ast_lints = run_ast_lints(&compile_unit.ast.package, config);
+    let mut ast_lints = run_ast_lints(&compile_unit.ast.package, config, compilation);
     let mut hir_lints = run_hir_lints(&compile_unit.package, config, compilation);
     let mut lints = Vec::new();
     lints.append(&mut ast_lints);
diff --git a/compiler/qsc_parse/src/expr/tests.rs b/compiler/qsc_parse/src/expr/tests.rs
index 18069d76..562c4d20 100644
--- a/compiler/qsc_parse/src/expr/tests.rs
+++ b/compiler/qsc_parse/src/expr/tests.rs
@@ -699,6 +699,38 @@ fn set() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [8-9]: Path: Path _id_ [8-9] (Ident _id_ [8-9] "y")"#]],
     );
+    check(
+        expr,
+        "x = y",
+        &expect![[r#"
+            Expr _id_ [0-5]: Assign:
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "y")"#]],
+    );
+}
+
+#[test]
+fn set_chained() {
+    check(
+        expr,
+        "set x = set y = z",
+        &expect![[r#"
+            Expr _id_ [0-17]: Assign:
+                Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
+                Expr _id_ [8-17]: Assign:
+                    Expr _id_ [12-13]: Path: Path _id_ [12-13] (Ident _id_ [12-13] "y")
+                    Expr _id_ [16-17]: Path: Path _id_ [16-17] (Ident _id_ [16-17] "z")"#]],
+    );
+    check(
+        expr,
+        "x = y = z",
+        &expect![[r#"
+            Expr _id_ [0-9]: Assign:
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [4-9]: Assign:
+                    Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "y")
+                    Expr _id_ [8-9]: Path: Path _id_ [8-9] (Ident _id_ [8-9] "z")"#]],
+    );
 }
 
 #[test]
@@ -711,6 +743,14 @@ fn set_hole() {
                 Expr _id_ [4-5]: Hole
                 Expr _id_ [8-9]: Lit: Int(1)"#]],
     );
+    check(
+        expr,
+        "_ = 1",
+        &expect![[r#"
+            Expr _id_ [0-5]: Assign:
+                Expr _id_ [0-1]: Hole
+                Expr _id_ [4-5]: Lit: Int(1)"#]],
+    );
 }
 
 #[test]
@@ -727,6 +767,18 @@ fn set_hole_tuple() {
                     Expr _id_ [14-15]: Lit: Int(1)
                     Expr _id_ [17-18]: Lit: Int(2)"#]],
     );
+    check(
+        expr,
+        "(x, _) = (1, 2)",
+        &expect![[r#"
+            Expr _id_ [0-15]: Assign:
+                Expr _id_ [0-6]: Tuple:
+                    Expr _id_ [1-2]: Path: Path _id_ [1-2] (Ident _id_ [1-2] "x")
+                    Expr _id_ [4-5]: Hole
+                Expr _id_ [9-15]: Tuple:
+                    Expr _id_ [10-11]: Lit: Int(1)
+                    Expr _id_ [13-14]: Lit: Int(2)"#]],
+    );
 }
 
 #[test]
@@ -747,6 +799,22 @@ fn set_hole_tuple_nested() {
                         Expr _id_ [23-24]: Lit: Int(2)
                         Expr _id_ [26-27]: Lit: Int(3)"#]],
     );
+    check(
+        expr,
+        "(_, (x, _)) = (1, (2, 3))",
+        &expect![[r#"
+            Expr _id_ [0-25]: Assign:
+                Expr _id_ [0-11]: Tuple:
+                    Expr _id_ [1-2]: Hole
+                    Expr _id_ [4-10]: Tuple:
+                        Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "x")
+                        Expr _id_ [8-9]: Hole
+                Expr _id_ [14-25]: Tuple:
+                    Expr _id_ [15-16]: Lit: Int(1)
+                    Expr _id_ [18-24]: Tuple:
+                        Expr _id_ [19-20]: Lit: Int(2)
+                        Expr _id_ [22-23]: Lit: Int(3)"#]],
+    );
 }
 
 #[test]
@@ -759,6 +827,14 @@ fn set_bitwise_and() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]],
     );
+    check(
+        expr,
+        "x &&&= y",
+        &expect![[r#"
+            Expr _id_ [0-8]: AssignOp (AndB):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]],
+    );
 }
 
 #[test]
@@ -771,6 +847,14 @@ fn set_logical_and() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]],
     );
+    check(
+        expr,
+        "x and= y",
+        &expect![[r#"
+            Expr _id_ [0-8]: AssignOp (AndL):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]],
+    );
 }
 
 #[test]
@@ -783,6 +867,14 @@ fn set_bitwise_or() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]],
     );
+    check(
+        expr,
+        "x |||= y",
+        &expect![[r#"
+            Expr _id_ [0-8]: AssignOp (OrB):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]],
+    );
 }
 
 #[test]
@@ -795,6 +887,14 @@ fn set_exp() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]],
     );
+    check(
+        expr,
+        "x ^= y",
+        &expect![[r#"
+            Expr _id_ [0-6]: AssignOp (Exp):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]],
+    );
 }
 
 #[test]
@@ -807,6 +907,14 @@ fn set_bitwise_xor() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]],
     );
+    check(
+        expr,
+        "x ^^^= y",
+        &expect![[r#"
+            Expr _id_ [0-8]: AssignOp (XorB):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]],
+    );
 }
 
 #[test]
@@ -819,6 +927,14 @@ fn set_shr() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]],
     );
+    check(
+        expr,
+        "x >>>= y",
+        &expect![[r#"
+            Expr _id_ [0-8]: AssignOp (Shr):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]],
+    );
 }
 
 #[test]
@@ -831,6 +947,14 @@ fn set_shl() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]],
     );
+    check(
+        expr,
+        "x <<<= y",
+        &expect![[r#"
+            Expr _id_ [0-8]: AssignOp (Shl):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [7-8]: Path: Path _id_ [7-8] (Ident _id_ [7-8] "y")"#]],
+    );
 }
 
 #[test]
@@ -843,6 +967,14 @@ fn set_sub() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]],
     );
+    check(
+        expr,
+        "x -= y",
+        &expect![[r#"
+            Expr _id_ [0-6]: AssignOp (Sub):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]],
+    );
 }
 
 #[test]
@@ -855,6 +987,14 @@ fn set_logical_or() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [10-11]: Path: Path _id_ [10-11] (Ident _id_ [10-11] "y")"#]],
     );
+    check(
+        expr,
+        "x or= y",
+        &expect![[r#"
+            Expr _id_ [0-7]: AssignOp (OrL):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [6-7]: Path: Path _id_ [6-7] (Ident _id_ [6-7] "y")"#]],
+    );
 }
 
 #[test]
@@ -867,6 +1007,14 @@ fn set_mod() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]],
     );
+    check(
+        expr,
+        "x %= y",
+        &expect![[r#"
+            Expr _id_ [0-6]: AssignOp (Mod):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]],
+    );
 }
 
 #[test]
@@ -879,6 +1027,14 @@ fn set_add() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]],
     );
+    check(
+        expr,
+        "x += y",
+        &expect![[r#"
+            Expr _id_ [0-6]: AssignOp (Add):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]],
+    );
 }
 
 #[test]
@@ -891,6 +1047,14 @@ fn set_div() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]],
     );
+    check(
+        expr,
+        "x /= y",
+        &expect![[r#"
+            Expr _id_ [0-6]: AssignOp (Div):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]],
+    );
 }
 
 #[test]
@@ -903,6 +1067,14 @@ fn set_mul() {
                 Expr _id_ [4-5]: Path: Path _id_ [4-5] (Ident _id_ [4-5] "x")
                 Expr _id_ [9-10]: Path: Path _id_ [9-10] (Ident _id_ [9-10] "y")"#]],
     );
+    check(
+        expr,
+        "x *= y",
+        &expect![[r#"
+            Expr _id_ [0-6]: AssignOp (Mul):
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")"#]],
+    );
 }
 
 #[test]
@@ -916,6 +1088,15 @@ fn set_with_update() {
                 Expr _id_ [10-11]: Path: Path _id_ [10-11] (Ident _id_ [10-11] "i")
                 Expr _id_ [15-16]: Path: Path _id_ [15-16] (Ident _id_ [15-16] "y")"#]],
     );
+    check(
+        expr,
+        "x w/= i <- y",
+        &expect![[r#"
+            Expr _id_ [0-12]: AssignUpdate:
+                Expr _id_ [0-1]: Path: Path _id_ [0-1] (Ident _id_ [0-1] "x")
+                Expr _id_ [6-7]: Path: Path _id_ [6-7] (Ident _id_ [6-7] "i")
+                Expr _id_ [11-12]: Path: Path _id_ [11-12] (Ident _id_ [11-12] "y")"#]],
+    );
 }
 
 #[test]
@@ -1073,19 +1254,10 @@ fn array_repeat_no_items() {
         expr,
         "[size = 3]",
         &expect![[r#"
-            Error(
-                Token(
-                    Close(
-                        Bracket,
-                    ),
-                    Eq,
-                    Span {
-                        lo: 6,
-                        hi: 7,
-                    },
-                ),
-            )
-        "#]],
+            Expr _id_ [0-10]: Array:
+                Expr _id_ [1-9]: Assign:
+                    Expr _id_ [1-5]: Path: Path _id_ [1-5] (Ident _id_ [1-5] "size")
+                    Expr _id_ [8-9]: Lit: Int(3)"#]],
     );
 }
 
@@ -1095,19 +1267,12 @@ fn array_repeat_two_items() {
         expr,
         "[1, 2, size = 3]",
         &expect![[r#"
-            Error(
-                Token(
-                    Close(
-                        Bracket,
-                    ),
-                    Eq,
-                    Span {
-                        lo: 12,
-                        hi: 13,
-                    },
-                ),
-            )
-        "#]],
+            Expr _id_ [0-16]: Array:
+                Expr _id_ [1-2]: Lit: Int(1)
+                Expr _id_ [4-5]: Lit: Int(2)
+                Expr _id_ [7-15]: Assign:
+                    Expr _id_ [7-11]: Path: Path _id_ [7-11] (Ident _id_ [7-11] "size")
+                    Expr _id_ [14-15]: Lit: Int(3)"#]],
     );
 }
 
@@ -2496,6 +2661,30 @@ fn duplicate_commas_in_pattern() {
                 ),
             ]"#]],
     );
+    check(
+        expr,
+        "(x,, y) = (1, 2)",
+        &expect![[r#"
+            Expr _id_ [0-16]: Assign:
+                Expr _id_ [0-7]: Tuple:
+                    Expr _id_ [1-2]: Path: Path _id_ [1-2] (Ident _id_ [1-2] "x")
+                    Expr _id_ [3-3]: Err
+                    Expr _id_ [5-6]: Path: Path _id_ [5-6] (Ident _id_ [5-6] "y")
+                Expr _id_ [10-16]: Tuple:
+                    Expr _id_ [11-12]: Lit: Int(1)
+                    Expr _id_ [14-15]: Lit: Int(2)
+
+            [
+                Error(
+                    MissingSeqEntry(
+                        Span {
+                            lo: 3,
+                            hi: 3,
+                        },
+                    ),
+                ),
+            ]"#]],
+    );
 }
 
 #[test]
@@ -2523,6 +2712,29 @@ fn invalid_initial_commas_in_pattern() {
                 ),
             ]"#]],
     );
+    check(
+        expr,
+        "(, x) = (1, 2)",
+        &expect![[r#"
+            Expr _id_ [0-14]: Assign:
+                Expr _id_ [0-5]: Tuple:
+                    Expr _id_ [1-1]: Err
+                    Expr _id_ [3-4]: Path: Path _id_ [3-4] (Ident _id_ [3-4] "x")
+                Expr _id_ [8-14]: Tuple:
+                    Expr _id_ [9-10]: Lit: Int(1)
+                    Expr _id_ [12-13]: Lit: Int(2)
+
+            [
+                Error(
+                    MissingSeqEntry(
+                        Span {
+                            lo: 1,
+                            hi: 1,
+                        },
+                    ),
+                ),
+            ]"#]],
+    );
 }
 
 #[test]
diff --git a/pip/tests/test_qsharp.py b/pip/tests/test_qsharp.py
index a1f98925..f79541b0 100644
--- a/pip/tests/test_qsharp.py
+++ b/pip/tests/test_qsharp.py
@@ -3,6 +3,7 @@
 
 import pytest
 import qsharp
+import qsharp.code
 import qsharp.utils
 from contextlib import redirect_stdout
 import io
@@ -404,3 +405,207 @@ def test_target_profile_from_str_match_enum_values() -> None:
     assert qsharp.TargetProfile.from_str(str_value) == target_profile
     with pytest.raises(ValueError):
         qsharp.TargetProfile.from_str("Invalid")
+
+
+def test_callables_exposed_into_env() -> None:
+    qsharp.init()
+    qsharp.eval("function Four() : Int { 4 }")
+    assert qsharp.code.Four() == 4, "callable should be available"
+    qsharp.eval("function Add(a : Int, b : Int) : Int { a + b }")
+    assert qsharp.code.Four() == 4, "first callable should still be available"
+    assert qsharp.code.Add(2, 3) == 5, "second callable should be available"
+    # After init, the callables should be cleared and no longer available
+    qsharp.init()
+    with pytest.raises(AttributeError):
+        qsharp.code.Four()
+
+
+def test_callable_exposed_into_env_complex_types() -> None:
+    qsharp.eval(
+        "function Complicated(a : Int, b : (Double, BigInt)) : ((Double, BigInt), Int) { (b, a) }"
+    )
+    assert qsharp.code.Complicated(2, (3.0, 4000000000000000000000)) == (
+        (3.0, 4000000000000000000000),
+        2,
+    ), "callables that take complex types should marshall them correctly"
+
+
+def test_callable_exposed_into_env_with_array() -> None:
+    qsharp.init()
+    qsharp.eval("function Smallest(a : Int[]) : Int { Std.Math.Min(a)}")
+    assert (
+        qsharp.code.Smallest([1, 2, 3, 0, 4, 5]) == 0
+    ), "callable that takes array should work"
+
+
+def test_callable_with_int_exposed_into_env_fails_incorrect_types() -> None:
+    qsharp.init()
+    qsharp.eval("function Identity(a : Int) : Int { a }")
+    assert qsharp.code.Identity(4) == 4
+    with pytest.raises(TypeError):
+        qsharp.code.Identity("4")
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4.0)
+    with pytest.raises(OverflowError):
+        qsharp.code.Identity(4000000000000000000000)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity([4])
+
+
+def test_callable_with_double_exposed_into_env_fails_incorrect_types() -> None:
+    qsharp.init()
+    qsharp.eval("function Identity(a : Double) : Double { a }")
+    assert qsharp.code.Identity(4.0) == 4.0
+    assert qsharp.code.Identity(4) == 4.0
+    with pytest.raises(TypeError):
+        qsharp.code.Identity("4")
+    with pytest.raises(TypeError):
+        qsharp.code.Identity([4])
+
+
+def test_callable_with_bigint_exposed_into_env_fails_incorrect_types() -> None:
+    qsharp.init()
+    qsharp.eval("function Identity(a : BigInt) : BigInt { a }")
+    assert qsharp.code.Identity(4000000000000000000000) == 4000000000000000000000
+    with pytest.raises(TypeError):
+        qsharp.code.Identity("4")
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4.0)
+
+
+def test_callable_with_string_exposed_into_env_fails_incorrect_types() -> None:
+    qsharp.init()
+    qsharp.eval("function Identity(a : String) : String { a }")
+    assert qsharp.code.Identity("4") == "4"
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4.0)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity([4])
+
+
+def test_callable_with_bool_exposed_into_env_fails_incorrect_types() -> None:
+    qsharp.init()
+    qsharp.eval("function Identity(a : Bool) : Bool { a }")
+    assert qsharp.code.Identity(True) == True
+    with pytest.raises(TypeError):
+        qsharp.code.Identity("4")
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4.0)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity([4])
+
+
+def test_callable_with_array_exposed_into_env_fails_incorrect_types() -> None:
+    qsharp.init()
+    qsharp.eval("function Identity(a : Int[]) : Int[] { a }")
+    assert qsharp.code.Identity([4, 5, 6]) == [4, 5, 6]
+    assert qsharp.code.Identity([]) == []
+    assert qsharp.code.Identity((4, 5, 6)) == [4, 5, 6]
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity("4")
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4.0)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity([1, 2, 3.0])
+
+
+def test_callable_with_tuple_exposed_into_env_fails_incorrect_types() -> None:
+    qsharp.init()
+    qsharp.eval("function Identity(a : (Int, Double)) : (Int, Double) { a }")
+    assert qsharp.code.Identity((4, 5.0)) == (4, 5.0)
+    assert qsharp.code.Identity((4, 5)) == (4, 5.0)
+    assert qsharp.code.Identity([4, 5.0]) == (4, 5.0)
+    with pytest.raises(qsharp.QSharpError):
+        qsharp.code.Identity((4, 5, 6))
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity("4")
+    with pytest.raises(TypeError):
+        qsharp.code.Identity(4.0)
+    with pytest.raises(TypeError):
+        qsharp.code.Identity([4.0, 5])
+
+
+def test_callables_in_namespaces_exposed_into_env_submodules_and_removed_on_reinit() -> (
+    None
+):
+    qsharp.init()
+    # callables should be created with their namespaces
+    qsharp.eval("namespace Test { function Four() : Int { 4 } }")
+    qsharp.eval("function Identity(a : Int) : Int { a }")
+    # should be able to import callables from env and namespace submodule
+    from qsharp.code import Identity
+    from qsharp.code.Test import Four
+
+    assert Identity(4) == 4
+    assert Four() == 4
+    qsharp.init()
+    # namespaces should be removed
+    with pytest.raises(AttributeError):
+        qsharp.code.Test
+    with pytest.raises(AttributeError):
+        qsharp.code.Identity()
+    # imported callables should fail gracefully
+    with pytest.raises(qsharp.QSharpError):
+        Four()
+
+
+def test_callables_with_unsupported_types_raise_errors_on_call() -> None:
+    qsharp.init()
+    qsharp.eval("function Unsupported(a : Int, q : Qubit) : Unit { }")
+    with pytest.raises(qsharp.QSharpError, match="unsupported input type: `Qubit`"):
+        qsharp.code.Unsupported()
+
+
+def test_callables_with_unsupported_types_in_tuples_raise_errors_on_call() -> None:
+    qsharp.init()
+    qsharp.eval("function Unsupported(q : (Int, Qubit)[]) : Unit { }")
+    with pytest.raises(qsharp.QSharpError, match="unsupported input type: `Qubit`"):
+        qsharp.code.Unsupported()
+
+
+def test_callables_with_unsupported_return_types_raise_errors_on_call() -> None:
+    qsharp.init()
+    qsharp.eval('function Unsupported() : Qubit { fail "won\'t be called" }')
+    with pytest.raises(qsharp.QSharpError, match="unsupported output type: `Qubit`"):
+        qsharp.code.Unsupported()
+
+
+def test_callables_with_unsupported_udt_types_raise_errors_on_call() -> None:
+    qsharp.init()
+    qsharp.eval("function Unsupported(a : Std.Math.Complex) : Unit { }")
+    with pytest.raises(
+        qsharp.QSharpError, match='unsupported input type: `UDT<"Complex":'
+    ):
+        qsharp.code.Unsupported()
+
+
+def test_callable_with_unsupported_udt_return_types_raise_errors_on_call() -> None:
+    qsharp.init()
+    qsharp.eval('function Unsupported() : Std.Math.Complex { fail "won\'t be called" }')
+    with pytest.raises(
+        qsharp.QSharpError, match='unsupported output type: `UDT<"Complex":'
+    ):
+        qsharp.code.Unsupported()
+
+
+def test_struct_call_constructor_not_exposed_into_env() -> None:
+    qsharp.init()
+    qsharp.eval("struct CustomUDT { a : Int }")
+    with pytest.raises(AttributeError):
+        qsharp.code.CustomUDT
+
+
+def test_lambdas_not_exposed_into_env() -> None:
+    qsharp.init()
+    qsharp.eval("a -> a + 1")
+    assert not hasattr(qsharp.code, "<lambda>")
+    qsharp.eval("q => I(q)")
+    assert not hasattr(qsharp.code, "<lambda>")
diff --git a/samples_test/src/tests/algorithms.rs b/samples_test/src/tests/algorithms.rs
index f2d59688..413743f0 100644
--- a/samples_test/src/tests/algorithms.rs
+++ b/samples_test/src/tests/algorithms.rs
@@ -254,6 +254,10 @@ pub const SHOR_EXPECT_DEBUG: Expect = expect![[r#"
     Found factor=13
     Found factorization 143 = 13 * 11
     (13, 11)"#]];
+pub const SIMPLEISING_EXPECT: Expect =
+    expect!["[Zero, Zero, Zero, One, One, Zero, One, One, Zero]"];
+pub const SIMPLEISING_EXPECT_DEBUG: Expect =
+    expect!["[Zero, Zero, Zero, One, One, Zero, One, One, Zero]"];
 pub const SUPERDENSECODING_EXPECT: Expect = expect!["((false, true), (false, true))"];
 pub const SUPERDENSECODING_EXPECT_DEBUG: Expect = expect!["((false, true), (false, true))"];
 pub const SUPERPOSITION_EXPECT: Expect = expect!["Zero"];
diff --git a/vscode/test/suites/debugger/debugger.test.ts b/vscode/test/suites/debugger/debugger.test.ts
index 90447ef0..8ba06cd8 100644
--- a/vscode/test/suites/debugger/debugger.test.ts
+++ b/vscode/test/suites/debugger/debugger.test.ts
@@ -5,6 +5,7 @@ import * as vscode from "vscode";
 import { assert } from "chai";
 import { activateExtension, waitForCondition } from "../extensionUtils";
 import { DebugProtocol } from "@vscode/debugprotocol";
+import { qsharpExtensionId } from "../../../src/common";
 
 /**
  * Set to true to log Debug Adapter Protocol messages to the console.
@@ -47,7 +48,9 @@ suite("Q# Debugger Tests", function suite() {
     await vscode.window.showTextDocument(fooUri);
 
     // launch debugger
-    await vscode.commands.executeCommand("qsharp-vscode.debugEditorContents");
+    await vscode.commands.executeCommand(
+      `${qsharpExtensionId}.debugEditorContents`,
+    );
 
     await waitUntilPaused([
       {