Repository: microsoft/qsharp (rust)

Issue:
Name resolution from a project's Main.qs in Python or Circuits does not work
If you follow the Python or Jupyter instructions from the documentation [Running a Program](https://learn.microsoft.com/en-us/azure/quantum/how-to-work-with-qsharp-projects?tabs=tabid-qsharp%2Ctabid-notebook-run#running-the-programs), the code will actually fail to run. For some reason, callables from Main.qs do not end up accessible either by their short names or their fully qualified names in `%%qsharp` magic cells or in `qsharp.eval`. Note that they DO show up and work as expected in the newer style Python interop, where they can be run like `qsharp.code.Main.Main()`.

My best guess at starting place for investigation: we do some extra tricks to make items from Main.qs available without an additional qualifying namespace when loading those projects as dependencies. But we also do some special tricks to make the loaded project from a `qsharp.init` call show up not as a dependency of the notebook/Python but as a code defined in the current context. I think somehow these two special cases are working against each other and causing thins from Main.qs to not show up at all. The other Python interop mechanism, exposing namespaces and callables through `qsharp.code`, uses an entirely different execution mechanism, so that could explain why it is unaffected.

UPDATE: Turns out this affects circuits as well... if you are in a project that has a Main.qs, generating circuits for any operation in that Main.qs fail with the same name resolution error.

Pull request:
Fix name resolution from a project's Main.qs
This fixes an issue where items from the Main.qs in a project would not be visible with the explicit `Main` namespace. When the dependency has no alias we should skip the special handling that elides the `Main` so that items are accessible as expected (and as documented in [Running the Programs](https://learn.microsoft.com/en-us/azure/quantum/how-to-work-with-qsharp-projects?tabs=tabid-qsharp%2Ctabid-notebook-run#running-the-programs))

Fixes #2207

Patch:
diff --git a/build.py b/build.py
index 89af468e..b345380e 100755
--- a/build.py
+++ b/build.py
@@ -174,7 +174,11 @@ if npm_install_needed:
 
 if args.check:
     step_start("Running eslint and prettier checks")
-    subprocess.run([npm_cmd, "run", "check"], check=True, text=True, cwd=root_dir)
+    try:
+        subprocess.run([npm_cmd, "run", "check"], check=True, text=True, cwd=root_dir)
+    except subprocess.CalledProcessError:
+        print("Consider running 'npm run prettier:fix' to fix prettier errors.")
+        raise
 
     if build_wasm or build_cli:
         # If we're going to check the Rust code, do this before we try to compile it
diff --git a/compiler/qsc_frontend/src/resolve.rs b/compiler/qsc_frontend/src/resolve.rs
index 77807a8d..bed43ed7 100644
--- a/compiler/qsc_frontend/src/resolve.rs
+++ b/compiler/qsc_frontend/src/resolve.rs
@@ -1626,9 +1626,11 @@ impl GlobalTable {
             global.visibility == hir::Visibility::Public
                 || matches!(&global.kind, global::Kind::Term(t) if t.intrinsic)
         }) {
-            // If the namespace is `Main`, we treat it as the root of the package, so there's no
-            // namespace prefix.
-            let global_namespace = if global.namespace.len() == 1 && &*global.namespace[0] == "Main"
+            // If the namespace is `Main` and we have an alias, we treat it as the root of the package, so there's no
+            // namespace prefix between the dependency alias and the defined items.
+            let global_namespace = if global.namespace.len() == 1
+                && &*global.namespace[0] == "Main"
+                && alias.is_some()
             {
                 vec![]
             } else {
@@ -2309,7 +2311,7 @@ fn find_symbol_in_namespace<O>(
         return;
     }
 
-    // Attempt to get the symbol from the global scope. If the namespace is None, use the candidate_namespace_id as a fallback
+    // Attempt to get the symbol from the global scope.
     let res = namespace.and_then(|ns_id| globals.get(kind, ns_id, &provided_symbol_name.name));
 
     // If a symbol was found, insert it into the candidates map
diff --git a/katas/content/qubit/index.md b/katas/content/qubit/index.md
index d92beeeb..16629bb5 100644
--- a/katas/content/qubit/index.md
+++ b/katas/content/qubit/index.md
@@ -142,7 +142,7 @@ Complex numbers have a parameter called the phase. If a complex number $z = x +
 > between $-\pi$ and $\pi$ that has $\cos \theta = x$ and $\sin \theta = y$. Unlike using $\tan^{-1}(\frac{y}{x})$, `atan2` computes
 > the correct quadrant for the angle, since it preserves information about the signs of both sine and cosine of the angle.
 
-The probability amplitudes $\alpha$ and $\beta$ are complex numbers, therefore $\alpha$ and $\beta$ have a phase. For example, consider a qubit in state $\frac{1 + i}{2}\ket{0} + \frac{1 - i}{2}\ket{1}$. If you do the math, you see that the phase of $\ket{0}$ is $atan2(\frac12, \frac12) = \frac{\pi}{4}$, and the phase of $\ket{1}$ is $atan2(\frac12, -\frac12) = -\frac{\pi}{4}$. The difference between these two phases is known as **relative phase**.
+The probability amplitudes $\alpha$ and $\beta$ are complex numbers, therefore $\alpha$ and $\beta$ have a phase. For example, consider a qubit in state $\frac{1 + i}{2}\ket{0} + \frac{1 - i}{2}\ket{1}$. If you do the math, you see that the phase of $\ket{0}$ amplitude is $atan2(\frac12, \frac12) = \frac{\pi}{4}$, and the phase of $\ket{1}$ amplitude is $atan2(-\frac12, \frac12) = -\frac{\pi}{4}$. The difference between these two phases is known as **relative phase**.
 
 Multiplying the state of the entire system by $e^{i\theta}$ doesn't affect the relative phase: $\alpha\ket{0} + \beta\ket{1}$ has the same relative phase as $e^{i\theta}\big(\alpha\ket{0} + \beta\ket{1}\big)$. In the second expression, $\theta$ is known as the system's **global phase**.
 
diff --git a/vscode/src/registry.json b/vscode/src/registry.json
index a1e80d3f..b7357c7d 100644
--- a/vscode/src/registry.json
+++ b/vscode/src/registry.json
@@ -74,6 +74,18 @@
           "path": "library/qtest"
         }
       }
+    },
+    {
+      "name": "QuantumArithmetic",
+      "description": "Quantum arithmetic algorithms",
+      "dependency": {
+        "github": {
+          "owner": "fedimser",
+          "repo": "quant-arith-re",
+          "refs": [{ "ref": "main", "notes": "nightly, unstable" }],
+          "path": "lib"
+        }
+      }
     }
   ]
 }


Test patch:
diff --git a/compiler/qsc/src/interpret/tests.rs b/compiler/qsc/src/interpret/tests.rs
index 0d2b52bc..86fa3771 100644
--- a/compiler/qsc/src/interpret/tests.rs
+++ b/compiler/qsc/src/interpret/tests.rs
@@ -1447,12 +1447,11 @@ mod given_interpreter {
     }
 
     fn is_unit_with_output(result: &InterpretResult, output: &str, expected_output: &str) {
-        assert_eq!(expected_output, output);
-
         match result {
             Ok(value) => assert_eq!(Value::unit(), *value),
             Err(e) => panic!("Expected unit value, got {e:?}"),
         }
+        assert_eq!(expected_output, output);
     }
 
     fn is_only_error<E>(result: &Result<Value, Vec<E>>, output: &str, expected_errors: &Expect)
@@ -2089,5 +2088,73 @@ mod given_interpreter {
                 entry: None,
             }
         }
+
+        #[test]
+        fn name_resolution_from_source_named_main_should_succeed() {
+            let sources = SourceMap::new(
+                [(
+                    "Main".into(),
+                    r#"function Foo() : Unit { Message("hello there..."); }"#.into(),
+                )],
+                None,
+            );
+            let (std_id, store) =
+                crate::compile::package_store_with_stdlib(TargetCapabilityFlags::all());
+            let mut interpreter = Interpreter::new(
+                sources,
+                PackageType::Lib,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+                store,
+                &[(std_id, None)],
+            )
+            .expect("interpreter should be created");
+
+            // Operations defined in Main.qs should also be visible with Main qualifier.
+            let (result, output) = line(&mut interpreter, "Main.Foo()");
+            is_unit_with_output(&result, &output, "hello there...");
+
+            // Operations defined in Main.qs should be importable with fully qualified name.
+            let (result, output) = line(&mut interpreter, "import Main.Foo;");
+            is_only_value(&result, &output, &Value::unit());
+
+            // After import the operation can be invoked without Main qualifier.
+            let (result, output) = line(&mut interpreter, "Foo()");
+            is_unit_with_output(&result, &output, "hello there...");
+        }
+
+        #[test]
+        fn name_resolution_from_source_named_main_without_full_path_or_import_should_fail() {
+            let sources = SourceMap::new(
+                [(
+                    "Main".into(),
+                    r#"function Foo() : Unit { Message("hello there..."); }"#.into(),
+                )],
+                None,
+            );
+            let (std_id, store) =
+                crate::compile::package_store_with_stdlib(TargetCapabilityFlags::all());
+            let mut interpreter = Interpreter::new(
+                sources,
+                PackageType::Lib,
+                TargetCapabilityFlags::all(),
+                LanguageFeatures::default(),
+                store,
+                &[(std_id, None)],
+            )
+            .expect("interpreter should be created");
+
+            // Operations defined in Main.qs should also be visible with Main qualifier.
+            let (errors, _) = line(&mut interpreter, "Foo()");
+            is_error(
+                &errors.expect_err("line invocation should fail with error"),
+                &expect![[r#"
+                    name error: `Foo` not found
+                       [line_0] [Foo]
+                    type error: insufficient type information to infer type
+                       [line_0] [Foo()]
+                "#]],
+            );
+        }
     }
 }