diff --git a/Cargo.lock b/Cargo.lock index 1af94815f1..270b9bbaac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -952,6 +952,7 @@ dependencies = [ "log", "miette", "qsc", + "regex-lite", ] [[package]] @@ -1032,6 +1033,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f96ede7f386ba6e910092e7ccdc04176cface62abebea07ed6b46d870ed95ca2" + [[package]] name = "regex-syntax" version = "0.6.28" diff --git a/Cargo.toml b/Cargo.toml index 2ccf92ebe8..9edc33827f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ num-bigint = "0.4.3" num-complex = "0.4" num-traits = "0.2.15" indenter = "0.2" +regex-lite = "0.1.0" serde = "1.0" serde-wasm-bindgen = "0.5" wasm-bindgen = "0.2.84" diff --git a/language_service/Cargo.toml b/language_service/Cargo.toml index 01c18e04a0..a85e8d5a0c 100644 --- a/language_service/Cargo.toml +++ b/language_service/Cargo.toml @@ -17,3 +17,4 @@ log = { workspace = true } miette = { workspace = true } qsc = { path = "../compiler/qsc" } enum-iterator = { workspace = true } +regex-lite = { workspace = true } diff --git a/language_service/src/hover.rs b/language_service/src/hover.rs index 5a51a7f81d..9c03e0fa8a 100644 --- a/language_service/src/hover.rs +++ b/language_service/src/hover.rs @@ -8,6 +8,7 @@ use crate::display::CodeDisplay; use crate::qsc_utils::{find_item, map_offset, span_contains, Compilation}; use qsc::ast::visit::{walk_callable_decl, walk_expr, walk_pat, walk_ty_def, Visitor}; use qsc::{ast, hir, resolve}; +use regex_lite::Regex; use std::fmt::Display; use std::rc::Rc; @@ -23,6 +24,10 @@ pub struct Span { pub end: u32, } +struct Documentation { + summary: String, +} + pub(crate) fn get_hover( compilation: &Compilation, source_name: &str, @@ -188,18 +193,36 @@ impl Visitor<'_> for HoverVisitor<'_> { } fn markdown_with_doc(doc: &Rc, code: impl Display) -> String { - if doc.is_empty() { + let parsed_doc = parse_doc(doc); + if parsed_doc.summary.is_empty() { markdown_fenced_block(code) } else { format!( "{} {}", - doc, + parsed_doc.summary, markdown_fenced_block(code) ) } } +fn parse_doc(doc: &str) -> Documentation { + let re = Regex::new(r"(?mi)(?:^# Summary$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex"); + let summary = match re.captures(doc) { + Some(captures) => { + let capture = captures + .get(1) + .expect("Didn't find the capture for the given regex"); + capture.as_str() + } + None => doc, + } + .trim() + .to_string(); + + Documentation { summary } +} + fn markdown_fenced_block(code: impl Display) -> String { format!( "```qsharp diff --git a/language_service/src/hover/tests.rs b/language_service/src/hover/tests.rs index c4e73f9768..f2bcc042ed 100644 --- a/language_service/src/hover/tests.rs +++ b/language_service/src/hover/tests.rs @@ -40,12 +40,13 @@ fn hover_callable_unit_types() { check( indoc! {r#" namespace Test { - /// Doc comment! + /// Doc comment + /// with multiple lines! operation ◉B↘ar◉() : Unit {} } "#}, &expect![[r#" - "Doc comment!\n```qsharp\noperation Bar Unit => Unit\n```\n" + "Doc comment\nwith multiple lines!\n```qsharp\noperation Bar Unit => Unit\n```\n" "#]], ); } @@ -516,3 +517,171 @@ fn hover_foreign_call_functors() { "#]], ); } + +#[test] +fn hover_callable_summary() { + check( + indoc! {r#" + namespace Test { + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_stuff_before() { + check( + indoc! {r#" + namespace Test { + /// not the summary + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_other_header_before() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the summary. + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_other_header_after() { + check( + indoc! {r#" + namespace Test { + /// # Summary + /// This is a + /// multi-line summary! + /// # Not The Summary + /// This stuff is not the summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_other_headers() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the summary. + /// # Summary + /// This is a + /// multi-line summary! + /// # Also Not The Summary + /// This stuff is also not the summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_headers_but_no_summary() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the summary. + /// # Also Not The Summary + /// This stuff is also not the summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r##" + "# Not The Summary\nThis stuff is not the summary.\n# Also Not The Summary\nThis stuff is also not the summary.\n```qsharp\noperation Foo Unit => Unit\n```\n" + "##]], + ); +} + +#[test] +fn hover_callable_summary_only_header_matches() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// This stuff is not the # Summary. + /// # Summary + /// This is a + /// multi-line # Summary! + /// # Also Not The Summary + /// This stuff is also not the # Summary. + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line # Summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_summary_successive_headers() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// # Summary + /// This is a + /// multi-line summary! + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r#" + "This is a\nmulti-line summary!\n```qsharp\noperation Foo Unit => Unit\n```\n" + "#]], + ); +} + +#[test] +fn hover_callable_empty_summary() { + check( + indoc! {r#" + namespace Test { + /// # Not The Summary + /// # Summary + /// # Also Not The Summary + operation ◉F↘oo◉() : Unit {} + } + "#}, + &expect![[r##" + "```qsharp\noperation Foo Unit => Unit\n```\n" + "##]], + ); +} diff --git a/language_service/src/qsc_utils.rs b/language_service/src/qsc_utils.rs index 3a8ba4b9be..d31a9d8b3b 100644 --- a/language_service/src/qsc_utils.rs +++ b/language_service/src/qsc_utils.rs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use qsc::hir::visit::Visitor; -use qsc::hir::{Item, ItemId, LocalItemId, PackageId}; +use qsc::hir::{Item, ItemId, PackageId}; use qsc::{ compile::{self, Error}, PackageStore, SourceMap, @@ -46,32 +45,15 @@ pub(crate) fn map_offset(source_map: &SourceMap, source_name: &str, source_offse } pub(crate) fn find_item<'a>(compilation: &'a Compilation, id: &ItemId) -> Option<&'a Item> { - let mut finder_pass = FindItem { - id: &id.item, - item: None, - }; let package = if let Some(package_id) = id.package { - &compilation - .package_store - .get(package_id) - .unwrap_or_else(|| panic!("bad package id: {package_id}")) - .package + match &compilation.package_store.get(package_id) { + Some(compilation) => &compilation.package, + None => { + return None; + } + } } else { &compilation.unit.package }; - finder_pass.visit_package(package); - finder_pass.item -} - -struct FindItem<'a, 'b> { - pub id: &'a LocalItemId, - pub item: Option<&'b Item>, -} - -impl<'a, 'b> Visitor<'b> for FindItem<'a, 'b> { - fn visit_item(&mut self, item: &'b Item) { - if item.id == *self.id { - self.item = Some(item); - } - } + package.items.get(id.item) } diff --git a/library/std/arithmetic.qs b/library/std/arithmetic.qs index 4990af4b92..7f0aacb16a 100644 --- a/library/std/arithmetic.qs +++ b/library/std/arithmetic.qs @@ -122,6 +122,8 @@ namespace Microsoft.Quantum.Arithmetic { /// # Summary /// Reversible, in-place ripple-carry addition of two integers. + /// + /// # Description /// Given two $n$-bit integers encoded in LittleEndian registers `xs` and `ys`, /// and a qubit carry, the operation computes the sum of the two integers /// where the $n$ least significant bits of the result are held in `ys` and @@ -274,7 +276,7 @@ namespace Microsoft.Quantum.Arithmetic { Fact(Length(xs) > 0, "Array should not be empty."); - let nQubits = Length(xs); + let nQubits = Length(xs); for idx in 0..nQubits - 2 { CCNOT(xs[idx], ys[idx], xs[idx+1]); } diff --git a/library/std/intrinsic.qs b/library/std/intrinsic.qs index 89c15c5856..c22697b47d 100644 --- a/library/std/intrinsic.qs +++ b/library/std/intrinsic.qs @@ -200,7 +200,7 @@ namespace Microsoft.Quantum.Intrinsic { /// # Summary /// Performs a measurement of a single qubit in the - /// Pauli $Z$ basis. + /// Pauli _Z_ basis. /// /// # Description /// The output result is given by @@ -328,7 +328,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $\ket{1}$ state by a given angle. + /// Applies a rotation about the |1⟩ state by a given angle. /// /// # Description /// \begin{align} @@ -373,7 +373,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $\ket{1}$ state by an angle specified + /// Applies a rotation about the |1⟩ state by an angle specified /// as a dyadic fraction. /// /// # Description @@ -473,7 +473,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $x$-axis by a given angle. + /// Applies a rotation about the _x_-axis by a given angle. /// /// # Description /// \begin{align} @@ -519,7 +519,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising $XX$ rotation gate. + /// Applies the two qubit Ising _XX_ rotation gate. /// /// # Description /// \begin{align} @@ -567,7 +567,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $y$-axis by a given angle. + /// Applies a rotation about the _y_-axis by a given angle. /// /// # Description /// \begin{align} @@ -613,7 +613,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising $YY$ rotation gate. + /// Applies the two qubit Ising _YY_ rotation gate. /// /// # Description /// \begin{align} @@ -661,7 +661,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies a rotation about the $z$-axis by a given angle. + /// Applies a rotation about the _z_-axis by a given angle. /// /// # Description /// \begin{align} @@ -712,7 +712,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the two qubit Ising $ZZ$ rotation gate. + /// Applies the two qubit Ising _ZZ_ rotation gate. /// /// # Description /// \begin{align} @@ -943,7 +943,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli $X$ gate. + /// Applies the Pauli _X_ gate. /// /// # Description /// \begin{align} @@ -990,7 +990,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli $Y$ gate. + /// Applies the Pauli _Y_ gate. /// /// # Description /// \begin{align} @@ -1037,7 +1037,7 @@ namespace Microsoft.Quantum.Intrinsic { } /// # Summary - /// Applies the Pauli $Z$ gate. + /// Applies the Pauli _Z_ gate. /// /// # Description /// \begin{align} diff --git a/library/std/math.qs b/library/std/math.qs index c3db744a4d..fd632687f0 100644 --- a/library/std/math.qs +++ b/library/std/math.qs @@ -16,7 +16,7 @@ namespace Microsoft.Quantum.Math { elif (a > 0) { +1 } else { 0 } } - + /// # Summary /// Returns -1, 0 or +1 that indicates the sign of a number. function SignD (a : Double) : Int { @@ -38,7 +38,7 @@ namespace Microsoft.Quantum.Math { function AbsI (a : Int) : Int { a < 0 ? -a | a } - + /// # Summary /// Returns the absolute value of a double-precision floating-point number. function AbsD (a : Double) : Double { @@ -49,7 +49,7 @@ namespace Microsoft.Quantum.Math { function AbsL (a : BigInt) : BigInt { a < 0L ? -a | a } - + /// # Summary /// Returns the larger of two specified numbers. function MaxI(a : Int, b : Int) : Int { @@ -206,7 +206,7 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the natural (base $e$) logarithm of a specified number. + /// Returns the natural (base _e_) logarithm of a specified number. function Log(input : Double) : Double { body intrinsic; } @@ -361,8 +361,11 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the multiplicative inverse of a modular integer: - /// $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. + /// Returns the multiplicative inverse of a modular integer. + /// + /// # Description + /// This will calculate the multiplicative inverse of a + /// modular integer $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. function InverseModI(a : Int, modulus : Int) : Int { let (u, v) = ExtendedGreatestCommonDivisorI(a, modulus); let gcd = u * a + v * modulus; @@ -371,8 +374,11 @@ namespace Microsoft.Quantum.Math { } /// # Summary - /// Returns the multiplicative inverse of a modular integer: - /// $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. + /// Returns the multiplicative inverse of a modular integer. + /// + /// # Description + /// This will calculate the multiplicative inverse of a + /// modular integer $b$ such that $a \cdot b = 1 (\operatorname{mod} \texttt{modulus})$. function InverseModL(a : BigInt, modulus : BigInt) : BigInt { let (u, v) = ExtendedGreatestCommonDivisorL(a, modulus); let gcd = u * a + v * modulus;